-
Notifications
You must be signed in to change notification settings - Fork 786
/
create_cluster_gke.go
386 lines (322 loc) · 11.8 KB
/
create_cluster_gke.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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
package cmd
import (
"io"
"strings"
"fmt"
"errors"
os_user "os/user"
"regexp"
"github.com/Pallinder/go-randomdata"
"github.com/jenkins-x/jx/pkg/cloud/gke"
"github.com/jenkins-x/jx/pkg/jx/cmd/templates"
"github.com/jenkins-x/jx/pkg/log"
"github.com/jenkins-x/jx/pkg/util"
"github.com/spf13/cobra"
"gopkg.in/AlecAivazis/survey.v1"
"gopkg.in/AlecAivazis/survey.v1/terminal"
)
// CreateClusterOptions the flags for running create cluster
type CreateClusterGKEOptions struct {
CreateClusterOptions
Flags CreateClusterGKEFlags
}
type CreateClusterGKEFlags struct {
AutoUpgrade bool
ClusterName string
ClusterIpv4Cidr string
ClusterVersion string
DiskSize string
ImageType string
MachineType string
MinNumOfNodes string
MaxNumOfNodes string
Network string
ProjectId string
SkipLogin bool
SubNetwork string
Zone string
Namespace string
Labels string
}
const CLUSTER_LIST_HEADER = "PROJECT_ID"
var (
createClusterGKELong = templates.LongDesc(`
This command creates a new Kubernetes cluster on GKE, installing required local dependencies and provisions the
Jenkins X platform
You can see a demo of this command here: [https://jenkins-x.io/demos/create_cluster_gke/](https://jenkins-x.io/demos/create_cluster_gke/)
Google Kubernetes Engine is a managed environment for deploying containerized applications. It brings our latest
innovations in developer productivity, resource efficiency, automated operations, and open source flexibility to
accelerate your time to market.
Google has been running production workloads in containers for over 15 years, and we build the best of what we
learn into Kubernetes, the industry-leading open source container orchestrator which powers Kubernetes Engine.
`)
createClusterGKEExample = templates.Examples(`
jx create cluster gke
`)
disallowedLabelCharacters = regexp.MustCompile("[^a-z0-9-]")
)
// NewCmdGet creates a command object for the generic "init" action, which
// installs the dependencies required to run the jenkins-x platform on a Kubernetes cluster.
func NewCmdCreateClusterGKE(f Factory, in terminal.FileReader, out terminal.FileWriter, errOut io.Writer) *cobra.Command {
options := CreateClusterGKEOptions{
CreateClusterOptions: createCreateClusterOptions(f, in, out, errOut, GKE),
}
cmd := &cobra.Command{
Use: "gke",
Short: "Create a new Kubernetes cluster on GKE: Runs on Google Cloud",
Long: createClusterGKELong,
Example: createClusterGKEExample,
Run: func(cmd *cobra.Command, args []string) {
options.Cmd = cmd
options.Args = args
err := options.Run()
CheckErr(err)
},
}
options.addCreateClusterFlags(cmd)
options.addCommonFlags(cmd)
cmd.Flags().StringVarP(&options.Flags.ClusterName, optionClusterName, "n", "", "The name of this cluster, default is a random generated name")
cmd.Flags().StringVarP(&options.Flags.ClusterIpv4Cidr, "cluster-ipv4-cidr", "", "", "The IP address range for the pods in this cluster in CIDR notation (e.g. 10.0.0.0/14)")
cmd.Flags().StringVarP(&options.Flags.ClusterVersion, optionKubernetesVersion, "v", "", "The Kubernetes version to use for the master and nodes. Defaults to server-specified")
cmd.Flags().StringVarP(&options.Flags.DiskSize, "disk-size", "d", "", "Size in GB for node VM boot disks. Defaults to 100GB")
cmd.Flags().BoolVarP(&options.Flags.AutoUpgrade, "enable-autoupgrade", "", false, "Sets autoupgrade feature for a cluster's default node-pool(s)")
cmd.Flags().StringVarP(&options.Flags.MachineType, "machine-type", "m", "", "The type of machine to use for nodes")
cmd.Flags().StringVarP(&options.Flags.MinNumOfNodes, "min-num-nodes", "", "", "The minimum number of nodes to be created in each of the cluster's zones")
cmd.Flags().StringVarP(&options.Flags.MaxNumOfNodes, "max-num-nodes", "", "", "The maximum number of nodes to be created in each of the cluster's zones")
cmd.Flags().StringVarP(&options.Flags.Network, "network", "", "", "The Compute Engine Network that the cluster will connect to")
cmd.Flags().StringVarP(&options.Flags.ProjectId, "project-id", "p", "", "Google Project ID to create cluster in")
cmd.Flags().StringVarP(&options.Flags.SubNetwork, "subnetwork", "", "", "The Google Compute Engine subnetwork to which the cluster is connected")
cmd.Flags().StringVarP(&options.Flags.Zone, "zone", "z", "", "The compute zone (e.g. us-central1-a) for the cluster")
cmd.Flags().BoolVarP(&options.Flags.SkipLogin, "skip-login", "", false, "Skip Google auth if already logged in via gcloud auth")
cmd.Flags().StringVarP(&options.Flags.Labels, "labels", "", "", "The labels to add to the cluster being created such as 'foo=bar,whatnot=123'. Label names must begin with a lowercase character ([a-z]), end with a lowercase alphanumeric ([a-z0-9]) with dashes (-), and lowercase alphanumeric ([a-z0-9]) between.")
cmd.AddCommand(NewCmdCreateClusterGKETerraform(f, in, out, errOut))
return cmd
}
func (o *CreateClusterGKEOptions) Run() error {
err := o.installRequirements(GKE)
if err != nil {
return err
}
err = o.createClusterGKE()
if err != nil {
log.Errorf("error creating cluster %v", err)
return err
}
return nil
}
func (o *CreateClusterGKEOptions) createClusterGKE() error {
surveyOpts := survey.WithStdio(o.In, o.Out, o.Err)
var err error
if !o.Flags.SkipLogin {
err := o.runCommandVerbose("gcloud", "auth", "login", "--brief")
if err != nil {
return err
}
}
projectId := o.Flags.ProjectId
if projectId == "" {
projectId, err = o.getGoogleProjectId()
if err != nil {
return err
}
}
err = o.runCommandVerbose("gcloud", "config", "set", "project", projectId)
if err != nil {
return err
}
// lets ensure we've got container and compute enabled
glcoudArgs := []string{"services", "enable", "container", "compute"}
log.Infof("Let's ensure we have container and compute enabled on your project via: %s\n", util.ColorInfo("gcloud "+strings.Join(glcoudArgs, " ")))
err = o.runCommandVerbose("gcloud", glcoudArgs...)
if err != nil {
return err
}
if o.Flags.ClusterName == "" {
o.Flags.ClusterName = strings.ToLower(randomdata.SillyName())
log.Infof("No cluster name provided so using a generated one: %s\n", o.Flags.ClusterName)
}
zone := o.Flags.Zone
if zone == "" {
availableZones, err := gke.GetGoogleZones(projectId)
if err != nil {
return err
}
prompts := &survey.Select{
Message: "Google Cloud Zone:",
Options: availableZones,
PageSize: 10,
Help: "The compute zone (e.g. us-central1-a) for the cluster",
}
err = survey.AskOne(prompts, &zone, nil, surveyOpts)
if err != nil {
return err
}
}
machineType := o.Flags.MachineType
if machineType == "" {
prompts := &survey.Select{
Message: "Google Cloud Machine Type:",
Options: gke.GetGoogleMachineTypes(),
Help: "We recommend a minimum of n1-standard-2 for Jenkins X, a table of machine descriptions can be found here https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-architecture",
PageSize: 10,
Default: "n1-standard-2",
}
err := survey.AskOne(prompts, &machineType, nil, surveyOpts)
if err != nil {
return err
}
}
minNumOfNodes := o.Flags.MinNumOfNodes
if minNumOfNodes == "" {
prompt := &survey.Input{
Message: "Minimum number of Nodes",
Default: "3",
Help: "We recommend a minimum of 3 for Jenkins X, the minimum number of nodes to be created in each of the cluster's zones",
}
survey.AskOne(prompt, &minNumOfNodes, nil, surveyOpts)
}
maxNumOfNodes := o.Flags.MaxNumOfNodes
if maxNumOfNodes == "" {
prompt := &survey.Input{
Message: "Maximum number of Nodes",
Default: "5",
Help: "We recommend at least 5 for Jenkins X, the maximum number of nodes to be created in each of the cluster's zones",
}
survey.AskOne(prompt, &maxNumOfNodes, nil, surveyOpts)
}
// mandatory flags are machine type, num-nodes, zone,
args := []string{"container", "clusters", "create",
o.Flags.ClusterName, "--zone", zone,
"--num-nodes", minNumOfNodes,
"--machine-type", machineType,
"--enable-autoscaling",
"--min-nodes", minNumOfNodes,
"--max-nodes", maxNumOfNodes}
if o.Flags.DiskSize != "" {
args = append(args, "--disk-size", o.Flags.DiskSize)
}
if o.Flags.ClusterIpv4Cidr != "" {
args = append(args, "--cluster-ipv4-cidr", o.Flags.ClusterIpv4Cidr)
}
if o.Flags.ClusterVersion != "" {
args = append(args, "--cluster-version", o.Flags.ClusterVersion)
}
if o.Flags.AutoUpgrade {
args = append(args, "--enable-autoupgrade", "true")
}
if o.Flags.ImageType != "" {
args = append(args, "--image-type", o.Flags.ImageType)
}
if o.Flags.Network != "" {
args = append(args, "--network", o.Flags.Network)
}
if o.Flags.SubNetwork != "" {
args = append(args, "--subnetwork", o.Flags.SubNetwork)
}
labels := o.Flags.Labels
user, err := os_user.Current()
if err == nil && user != nil {
username := sanitizeLabel(user.Username)
if username != "" {
sep := ""
if labels != "" {
sep = ","
}
labels += sep + "created-by=" + username
}
}
if labels != "" {
args = append(args, "--labels="+strings.ToLower(labels))
}
log.Info("Creating cluster...\n")
err = o.RunCommand("gcloud", args...)
if err != nil {
return err
}
log.Info("Initialising cluster ...\n")
o.InstallOptions.Flags.DefaultEnvironmentPrefix = o.Flags.ClusterName
err = o.initAndInstall(GKE)
if err != nil {
return err
}
err = o.RunCommand("gcloud", "container", "clusters", "get-credentials", o.Flags.ClusterName, "--zone", zone, "--project", projectId)
if err != nil {
return err
}
context, err := o.getCommandOutput("", "kubectl", "config", "current-context")
if err != nil {
return err
}
ns := o.InstallOptions.Flags.Namespace
if ns == "" {
_, ns, _ = o.KubeClient()
if err != nil {
return err
}
}
err = o.RunCommand("kubectl", "config", "set-context", context, "--namespace", ns)
if err != nil {
return err
}
err = o.RunCommand("kubectl", "get", "ingress")
if err != nil {
return err
}
return nil
}
func sanitizeLabel(username string) string {
sanitized := strings.ToLower(username)
return disallowedLabelCharacters.ReplaceAllString(sanitized, "-")
}
// asks to chose from existing projects or optionally creates one if none exist
func (o *CreateClusterGKEOptions) getGoogleProjectId() (string, error) {
surveyOpts := survey.WithStdio(o.In, o.Out, o.Err)
out, err := o.getCommandOutput("", "gcloud", "projects", "list")
if err != nil {
return "", err
}
lines := strings.Split(string(out), "\n")
var existingProjects []string
for _, l := range lines {
if strings.Contains(l, CLUSTER_LIST_HEADER) {
continue
}
fields := strings.Fields(l)
existingProjects = append(existingProjects, fields[0])
}
var projectId string
if len(existingProjects) == 0 {
confirm := &survey.Confirm{
Message: fmt.Sprintf("No existing Google Projects exist, create one now?"),
Default: true,
}
flag := true
err = survey.AskOne(confirm, &flag, nil, surveyOpts)
if err != nil {
return "", err
}
if !flag {
return "", errors.New("no google project to create cluster in, please manual create one and rerun this wizard")
}
if flag {
return "", errors.New("auto creating projects not yet implemented, please manually create one and rerun the wizard")
}
} else if len(existingProjects) == 1 {
projectId = existingProjects[0]
log.Infof("Using the only Google Cloud Project %s to create the cluster\n", util.ColorInfo(projectId))
} else {
prompts := &survey.Select{
Message: "Google Cloud Project:",
Options: existingProjects,
Help: "Select a Google Project to create the cluster in",
}
err := survey.AskOne(prompts, &projectId, nil, surveyOpts)
if err != nil {
return "", err
}
}
if projectId == "" {
return "", errors.New("no Google Cloud Project to create cluster in, please manual create one and rerun this wizard")
}
return projectId, nil
}