forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
deployer.go
200 lines (175 loc) · 6.82 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
package deployer
import (
"fmt"
"sort"
"time"
"github.com/golang/glog"
"github.com/spf13/cobra"
kapi "k8s.io/kubernetes/pkg/api"
kclient "k8s.io/kubernetes/pkg/client"
"k8s.io/kubernetes/pkg/kubectl"
"github.com/openshift/origin/pkg/api/latest"
"github.com/openshift/origin/pkg/cmd/util"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"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"
"github.com/openshift/origin/pkg/version"
)
const (
deployerLong = `
Perform a deployment
This command launches a deployment as described by a deployment configuration.`
)
type config struct {
Config *clientcmd.Config
DeploymentName string
Namespace string
}
// NewCommandDeployer provides a CLI handler for deploy.
func NewCommandDeployer(name string) *cobra.Command {
cfg := &config{
Config: clientcmd.NewConfig(),
}
cmd := &cobra.Command{
Use: fmt.Sprintf("%s%s", name, clientcmd.ConfigSyntax),
Short: "Run the deployer",
Long: deployerLong,
Run: func(c *cobra.Command, args []string) {
_, kClient, err := cfg.Config.Clients()
if err != nil {
glog.Fatal(err)
}
if len(cfg.DeploymentName) == 0 {
glog.Fatal("deployment is required")
}
if len(cfg.Namespace) == 0 {
glog.Fatal("namespace is required")
}
deployer := NewDeployer(kClient)
if err = deployer.Deploy(cfg.Namespace, cfg.DeploymentName); err != nil {
glog.Fatal(err)
}
},
}
cmd.AddCommand(version.NewVersionCommand(name))
flag := cmd.Flags()
cfg.Config.Bind(flag)
flag.StringVar(&cfg.DeploymentName, "deployment", util.Env("OPENSHIFT_DEPLOYMENT_NAME", ""), "The deployment name to start")
flag.StringVar(&cfg.Namespace, "namespace", util.Env("OPENSHIFT_DEPLOYMENT_NAMESPACE", ""), "The deployment namespace")
return cmd
}
// NewDeployer makes a new Deployer from a kube client.
func NewDeployer(client kclient.Interface) *Deployer {
scaler, _ := kubectl.ScalerFor("ReplicationController", kubectl.NewScalerClient(client))
return &Deployer{
getDeployment: func(namespace, name string) (*kapi.ReplicationController, error) {
return client.ReplicationControllers(namespace).Get(name)
},
getDeployments: func(namespace, configName string) (*kapi.ReplicationControllerList, error) {
return client.ReplicationControllers(namespace).List(deployutil.ConfigSelector(configName))
},
scaler: scaler,
strategyFor: func(config *deployapi.DeploymentConfig) (strategy.DeploymentStrategy, error) {
switch config.Template.Strategy.Type {
case deployapi.DeploymentStrategyTypeRecreate:
return recreate.NewRecreateDeploymentStrategy(client, latest.Codec), nil
case deployapi.DeploymentStrategyTypeRolling:
recreate := recreate.NewRecreateDeploymentStrategy(client, latest.Codec)
return rolling.NewRollingDeploymentStrategy(config.Namespace, client, latest.Codec, recreate), nil
default:
return nil, fmt.Errorf("unsupported strategy type: %s", config.Template.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 {
// 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 deploymentName.
func (d *Deployer) Deploy(namespace, deploymentName string) error {
// Look up the new deployment.
to, err := d.getDeployment(namespace, deploymentName)
if err != nil {
return fmt.Errorf("couldn't get deployment %s/%s: %v", namespace, deploymentName, err)
}
// Decode the config from the deployment.
config, err := deployutil.DecodeDeploymentConfig(to, latest.Codec)
if err != nil {
return fmt.Errorf("couldn't decode deployment config from deployment %s/%s: %v", to.Namespace, to.Name, err)
}
// Get a strategy for the deployment.
strategy, 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 no desired replica count", deployutil.LabelForDeployment(to))
}
// 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 := unsortedDeployments.Items
// Sort all the deployments by version.
sort.Sort(deployutil.DeploymentsByLatestVersionDesc(deployments))
// Find any last completed deployment.
var from *kapi.ReplicationController
for _, candidate := range deployments {
if candidate.Name == to.Name {
continue
}
if deployutil.DeploymentStatusFor(&candidate) == deployapi.DeploymentStatusComplete {
from = &candidate
break
}
}
// 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{-1, ""}, retryWaitParams, retryWaitParams); err != nil {
glog.Errorf("Couldn't scale down prior deployment %s: %v", deployutil.LabelForDeployment(&candidate), err)
} else {
glog.Infof("Scaled down prior deployment %s", deployutil.LabelForDeployment(&candidate))
}
}
// Perform the deployment.
if from == nil {
glog.Infof("Deploying %s for the first time (replicas: %d)", deployutil.LabelForDeployment(to), desiredReplicas)
} else {
glog.Infof("Deploying from %s to %s (replicas: %d)", deployutil.LabelForDeployment(from), deployutil.LabelForDeployment(to), desiredReplicas)
}
return strategy.Deploy(from, to, desiredReplicas)
}