-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
deployer.go
257 lines (224 loc) · 9.42 KB
/
deployer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package deployer
import (
"fmt"
"io"
"os"
"sort"
"time"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kv1core "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest"
kapi "k8s.io/kubernetes/pkg/api"
kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl"
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"github.com/openshift/origin/pkg/client"
ocmd "github.com/openshift/origin/pkg/cmd/cli/cmd"
"github.com/openshift/origin/pkg/cmd/templates"
"github.com/openshift/origin/pkg/cmd/util"
deployapi "github.com/openshift/origin/pkg/deploy/apis/apps"
"github.com/openshift/origin/pkg/deploy/strategy"
"github.com/openshift/origin/pkg/deploy/strategy/recreate"
"github.com/openshift/origin/pkg/deploy/strategy/rolling"
deployutil "github.com/openshift/origin/pkg/deploy/util"
)
var (
deployerLong = templates.LongDesc(`
Perform a deployment
This command launches a deployment as described by a deployment configuration. It accepts the name
of a replication controller created by a deployment and runs that deployment to completion. You can
use the --until flag to run the deployment until you reach the specified condition.
Available conditions:
* "start": after old deployments are scaled to zero
* "pre": after the pre hook completes (even if no hook specified)
* "mid": after the mid hook completes (even if no hook specified)
* A percentage of the deployment, based on which strategy is in use
* "0%" Recreate after the previous deployment is scaled to zero
* "N%" Recreate after the acceptance check if this is not the first deployment
* "0%" Rolling before the rolling deployment is started, equivalent to "pre"
* "N%" Rolling the percentage of pods in the target deployment that are ready
* "100%" All after the deployment is at full scale, but before the post hook runs
Unrecognized conditions will be ignored and the deployment will run to completion. You can run this
command multiple times when --until is specified - hooks will only be executed once.`)
)
type config struct {
Out, ErrOut io.Writer
rcName string
Namespace string
Until string
}
// NewCommandDeployer provides a CLI handler for deploy.
func NewCommandDeployer(name string) *cobra.Command {
cfg := &config{}
cmd := &cobra.Command{
Use: fmt.Sprintf("%s [--until=CONDITION]", name),
Short: "Run the deployer",
Long: deployerLong,
Run: func(c *cobra.Command, args []string) {
cfg.Out = os.Stdout
cfg.ErrOut = c.OutOrStderr()
err := cfg.RunDeployer()
if strategy.IsConditionReached(err) {
fmt.Fprintf(os.Stdout, "--> %s\n", err.Error())
return
}
kcmdutil.CheckErr(err)
},
}
cmd.AddCommand(ocmd.NewCmdVersion(name, nil, os.Stdout, ocmd.VersionOptions{}))
flag := cmd.Flags()
flag.StringVar(&cfg.rcName, "deployment", util.Env("OPENSHIFT_DEPLOYMENT_NAME", ""), "The deployment name to start")
flag.StringVar(&cfg.Namespace, "namespace", util.Env("OPENSHIFT_DEPLOYMENT_NAMESPACE", ""), "The deployment namespace")
flag.StringVar(&cfg.Until, "until", "", "Exit the deployment when this condition is met. See help for more details")
return cmd
}
func (cfg *config) RunDeployer() error {
if len(cfg.rcName) == 0 {
return fmt.Errorf("--deployment or OPENSHIFT_DEPLOYMENT_NAME is required")
}
if len(cfg.Namespace) == 0 {
return fmt.Errorf("--namespace or OPENSHIFT_DEPLOYMENT_NAMESPACE is required")
}
kcfg, err := restclient.InClusterConfig()
if err != nil {
return err
}
kc, err := kclientset.NewForConfig(kcfg)
if err != nil {
return err
}
oc, err := client.New(kcfg)
if err != nil {
return err
}
deployer := NewDeployer(kc, oc, cfg.Out, cfg.ErrOut, cfg.Until)
return deployer.Deploy(cfg.Namespace, cfg.rcName)
}
// NewDeployer makes a new Deployer from a kube client.
func NewDeployer(client kclientset.Interface, oclient client.Interface, out, errOut io.Writer, until string) *Deployer {
scaler, _ := kubectl.ScalerFor(kapi.Kind("ReplicationController"), client)
return &Deployer{
out: out,
errOut: errOut,
until: until,
getDeployment: func(namespace, name string) (*kapi.ReplicationController, error) {
return client.Core().ReplicationControllers(namespace).Get(name, metav1.GetOptions{})
},
getDeployments: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {
return client.Core().ReplicationControllers(namespace).List(metav1.ListOptions{LabelSelector: deployutil.ConfigSelector(configName).String()})
},
scaler: scaler,
strategyFor: func(config *deployapi.DeploymentConfig) (strategy.DeploymentStrategy, error) {
switch config.Spec.Strategy.Type {
case deployapi.DeploymentStrategyTypeRecreate:
return recreate.NewRecreateDeploymentStrategy(client, oclient, &kv1core.EventSinkImpl{Interface: kv1core.New(client.Core().RESTClient()).Events("")}, kapi.Codecs.UniversalDecoder(), out, errOut, until), nil
case deployapi.DeploymentStrategyTypeRolling:
recreate := recreate.NewRecreateDeploymentStrategy(client, oclient, &kv1core.EventSinkImpl{Interface: kv1core.New(client.Core().RESTClient()).Events("")}, kapi.Codecs.UniversalDecoder(), out, errOut, until)
return rolling.NewRollingDeploymentStrategy(config.Namespace, client, oclient, &kv1core.EventSinkImpl{Interface: kv1core.New(client.Core().RESTClient()).Events("")}, kapi.Codecs.UniversalDecoder(), recreate, out, errOut, until), nil
default:
return nil, fmt.Errorf("unsupported strategy type: %s", config.Spec.Strategy.Type)
}
},
}
}
// Deployer prepares and executes the deployment process. It will:
//
// 1. Validate the deployment has a desired replica count and strategy.
// 2. Find the last completed deployment.
// 3. Scale down to 0 any old deployments which aren't the new deployment or
// the last complete deployment.
// 4. Pass the last completed deployment and the new deployment to a strategy
// to perform the deployment.
type Deployer struct {
// out and errOut control display when deploy is invoked
out, errOut io.Writer
// until is a condition to run until
until string
// strategyFor returns a DeploymentStrategy for config.
strategyFor func(config *deployapi.DeploymentConfig) (strategy.DeploymentStrategy, error)
// getDeployment finds the named deployment.
getDeployment func(namespace, name string) (*kapi.ReplicationController, error)
// getDeployments finds all deployments associated with a config.
getDeployments func(namespace, configName string) (*kapi.ReplicationControllerList, error)
// scaler is used to scale replication controllers.
scaler kubectl.Scaler
}
// Deploy starts the deployment process for rcName.
func (d *Deployer) Deploy(namespace, rcName string) error {
// Look up the new deployment.
to, err := d.getDeployment(namespace, rcName)
if err != nil {
return fmt.Errorf("couldn't get deployment %s: %v", rcName, err)
}
// Decode the config from the deployment.
config, err := deployutil.DecodeDeploymentConfig(to, kapi.Codecs.UniversalDecoder())
if err != nil {
return fmt.Errorf("couldn't decode deployment config from deployment %s: %v", to.Name, err)
}
// Get a strategy for the deployment.
s, err := d.strategyFor(config)
if err != nil {
return err
}
// New deployments must have a desired replica count.
desiredReplicas, hasDesired := deployutil.DeploymentDesiredReplicas(to)
if !hasDesired {
return fmt.Errorf("deployment %s has already run to completion", to.Name)
}
// Find all deployments for the config.
unsortedDeployments, err := d.getDeployments(namespace, config.Name)
if err != nil {
return fmt.Errorf("couldn't get controllers in namespace %s: %v", namespace, err)
}
deployments := make([]*kapi.ReplicationController, 0, len(unsortedDeployments.Items))
for i := range unsortedDeployments.Items {
deployments = append(deployments, &unsortedDeployments.Items[i])
}
// Sort all the deployments by version.
sort.Sort(deployutil.ByLatestVersionDesc(deployments))
// Find any last completed deployment.
var from *kapi.ReplicationController
for _, candidate := range deployments {
if candidate.Name == to.Name {
continue
}
if deployutil.IsCompleteDeployment(candidate) {
from = candidate
break
}
}
if deployutil.DeploymentVersionFor(to) < deployutil.DeploymentVersionFor(from) {
return fmt.Errorf("deployment %s is older than %s", to.Name, from.Name)
}
// Scale down any deployments which aren't the new or last deployment.
for _, candidate := range deployments {
// Skip the from/to deployments.
if candidate.Name == to.Name {
continue
}
if from != nil && candidate.Name == from.Name {
continue
}
// Skip the deployment if it's already scaled down.
if candidate.Spec.Replicas == 0 {
continue
}
// Scale the deployment down to zero.
retryWaitParams := kubectl.NewRetryParams(1*time.Second, 120*time.Second)
if err := d.scaler.Scale(candidate.Namespace, candidate.Name, uint(0), &kubectl.ScalePrecondition{Size: -1, ResourceVersion: ""}, retryWaitParams, retryWaitParams); err != nil {
fmt.Fprintf(d.errOut, "error: Couldn't scale down prior deployment %s: %v\n", deployutil.LabelForDeployment(candidate), err)
} else {
fmt.Fprintf(d.out, "--> Scaled older deployment %s down\n", candidate.Name)
}
}
if d.until == "start" {
return strategy.NewConditionReachedErr("Ready to start deployment")
}
// Perform the deployment.
if err := s.Deploy(from, to, int(desiredReplicas)); err != nil {
return err
}
fmt.Fprintf(d.out, "--> Success\n")
return nil
}