github.com/coreos/mantle@v0.13.0/kola/tests/kubernetes/setup.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kubernetes 16 17 import ( 18 "bytes" 19 "fmt" 20 "regexp" 21 "strings" 22 "text/template" 23 "time" 24 25 "github.com/coreos/mantle/kola/cluster" 26 "github.com/coreos/mantle/kola/tests/etcd" 27 "github.com/coreos/mantle/platform" 28 "github.com/coreos/mantle/platform/conf" 29 "github.com/coreos/mantle/util" 30 ) 31 32 // kCluster just keeps track of which machines are which in a 33 // platform.TestCluster with kubernetes running. 34 type kCluster struct { 35 etcd platform.Machine 36 master platform.Machine 37 workers []platform.Machine 38 } 39 40 // resolve ambiguity with TestCluster.Name() 41 type clusterWrapper struct { 42 *cluster.TestCluster 43 } 44 45 func (cw clusterWrapper) Name() string { 46 return cw.Cluster.Name() 47 } 48 49 // Setup a multi-node cluster based on generic scrips from coreos-kubernetes repo. 50 // https://github.com/coreos/coreos-kubernetes/tree/master/multi-node/generic 51 func setupCluster(c cluster.TestCluster, nodes int, version, runtime string) *kCluster { 52 // start single-node etcd 53 etcdNode, err := c.NewMachine(etcdConfig) 54 if err != nil { 55 c.Fatalf("error creating etcd: %v", err) 56 } 57 58 if err := etcd.GetClusterHealth(c, etcdNode, 1); err != nil { 59 c.Fatalf("error checking etcd health: %v", err) 60 } 61 62 // passing cloud-config has the side effect of populating `/etc/environment`, 63 // which the install script depends on 64 master, err := c.NewMachine(conf.CloudConfig("")) 65 if err != nil { 66 c.Fatalf("error creating master: %v", err) 67 } 68 69 options := map[string]string{ 70 "HYPERKUBE_IMAGE_REPO": "quay.io/coreos/hyperkube", 71 "MASTER_HOST": master.PrivateIP(), 72 "ETCD_ENDPOINTS": fmt.Sprintf("http://%v:2379", etcdNode.PrivateIP()), 73 "CONTROLLER_ENDPOINT": fmt.Sprintf("https://%v:443", master.PrivateIP()), 74 "K8S_SERVICE_IP": "10.3.0.1", 75 "K8S_VER": version, 76 "CONTAINER_RUNTIME": runtime, 77 } 78 79 // generate TLS assets on master 80 if err := generateMasterTLSAssets(c, master, options); err != nil { 81 c.Fatalf("error creating master tls: %v", err) 82 } 83 84 // create worker nodes 85 workers, err := platform.NewMachines(clusterWrapper{&c}, conf.CloudConfig(""), nodes) 86 if err != nil { 87 c.Fatalf("error creating workers: %v", err) 88 } 89 90 // generate tls assets on workers by transfering ca from master 91 if err := generateWorkerTLSAssets(c, master, workers); err != nil { 92 c.Fatalf("error creating worker tls: %v", err) 93 } 94 95 // configure nodes via generic install scripts 96 runInstallScript(c, master, controllerInstallScript, options) 97 98 for _, worker := range workers { 99 runInstallScript(c, worker, workerInstallScript, options) 100 } 101 102 // configure kubectl 103 if err := configureKubectl(c, master, master.PrivateIP(), version); err != nil { 104 c.Fatalf("error configuring master kubectl: %v", err) 105 } 106 107 // check that all nodes appear in kubectl 108 f := func() error { 109 return nodeCheck(c, master, workers) 110 } 111 if err := util.Retry(15, 30*time.Second, f); err != nil { 112 c.Fatalf("error waiting for nodes: %v", err) 113 } 114 115 cluster := &kCluster{ 116 etcd: etcdNode, 117 master: master, 118 workers: workers, 119 } 120 return cluster 121 } 122 123 func generateMasterTLSAssets(c cluster.TestCluster, master platform.Machine, options map[string]string) error { 124 var buffer = new(bytes.Buffer) 125 126 tmpl, err := template.New("masterCNF").Parse(masterCNF) 127 if err != nil { 128 return err 129 } 130 if err := tmpl.Execute(buffer, options); err != nil { 131 return err 132 } 133 134 if err := platform.InstallFile(buffer, master, "/home/core/openssl.cnf"); err != nil { 135 return err 136 } 137 138 var cmds = []string{ 139 // gen master assets 140 "openssl genrsa -out ca-key.pem 2048", 141 `openssl req -x509 -new -nodes -key ca-key.pem -days 10000 -out ca.pem -subj "/CN=kube-ca"`, 142 "openssl genrsa -out apiserver-key.pem 2048", 143 `openssl req -new -key apiserver-key.pem -out apiserver.csr -subj "/CN=kube-apiserver" -config openssl.cnf`, 144 "openssl x509 -req -in apiserver.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out apiserver.pem -days 365 -extensions v3_req -extfile openssl.cnf", 145 146 // gen cluster admin keypair 147 "openssl genrsa -out admin-key.pem 2048", 148 `openssl req -new -key admin-key.pem -out admin.csr -subj "/CN=kube-admin"`, 149 "openssl x509 -req -in admin.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out admin.pem -days 365", 150 151 // move into /etc/kubernetes/ssl 152 "sudo mkdir -p /etc/kubernetes/ssl", 153 "sudo cp /home/core/ca.pem /etc/kubernetes/ssl/ca.pem", 154 "sudo cp /home/core/apiserver.pem /etc/kubernetes/ssl/apiserver.pem", 155 "sudo cp /home/core/apiserver-key.pem /etc/kubernetes/ssl/apiserver-key.pem", 156 "sudo chmod 600 /etc/kubernetes/ssl/*-key.pem", 157 "sudo chown root:root /etc/kubernetes/ssl/*-key.pem", 158 } 159 160 for _, cmd := range cmds { 161 b, err := c.SSH(master, cmd) 162 if err != nil { 163 return fmt.Errorf("Failed on cmd: %s with error: %s and output %s", cmd, err, b) 164 } 165 } 166 return nil 167 } 168 169 func generateWorkerTLSAssets(c cluster.TestCluster, master platform.Machine, workers []platform.Machine) error { 170 for i, worker := range workers { 171 // copy tls assets from master to workers 172 err := platform.TransferFile(master, "/etc/kubernetes/ssl/ca.pem", worker, "/home/core/ca.pem") 173 if err != nil { 174 return err 175 } 176 err = platform.TransferFile(master, "/home/core/ca-key.pem", worker, "/home/core/ca-key.pem") 177 if err != nil { 178 return err 179 } 180 181 // place worker-openssl.cnf on workers 182 cnf := strings.Replace(workerCNF, "{{.WORKER_IP}}", worker.PrivateIP(), -1) 183 in := strings.NewReader(cnf) 184 if err := platform.InstallFile(in, worker, "/home/core/worker-openssl.cnf"); err != nil { 185 return err 186 } 187 188 // gen certs 189 workerFQDN := fmt.Sprintf("kube-worker-%v", i) 190 cmds := []string{ 191 fmt.Sprintf("openssl genrsa -out worker-key.pem 2048"), 192 fmt.Sprintf(`openssl req -new -key worker-key.pem -out %v-worker.csr -subj "/CN=%v" -config worker-openssl.cnf`, workerFQDN, workerFQDN), 193 fmt.Sprintf(`openssl x509 -req -in %v-worker.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out worker.pem -days 365 -extensions v3_req -extfile worker-openssl.cnf`, workerFQDN), 194 195 // move into /etc/kubernetes/ssl 196 "sudo mkdir -p /etc/kubernetes/ssl", 197 "sudo chmod 600 /home/core/*-key.pem", 198 "sudo chown root:root /home/core/*-key.pem", 199 "sudo cp /home/core/worker.pem /etc/kubernetes/ssl/worker.pem", 200 "sudo cp /home/core/worker-key.pem /etc/kubernetes/ssl/worker-key.pem", 201 "sudo cp /home/core/ca.pem /etc/kubernetes/ssl/ca.pem", 202 } 203 204 for _, cmd := range cmds { 205 b, err := c.SSH(worker, cmd) 206 if err != nil { 207 return fmt.Errorf("Failed on cmd: %s with error: %s and output %s", cmd, err, b) 208 } 209 } 210 } 211 return nil 212 } 213 214 // https://coreos.com/kubernetes/docs/latest/configure-kubectl.html 215 func configureKubectl(c cluster.TestCluster, m platform.Machine, server string, version string) error { 216 // ignore suffix like '-coreos.1' to grab upstream kubelet 217 version, err := stripSemverSuffix(version) 218 if err != nil { 219 return err 220 } 221 222 var ( 223 ca = "/home/core/ca.pem" 224 adminKey = "/home/core/admin-key.pem" 225 adminCert = "/home/core/admin.pem" 226 kubeURL = fmt.Sprintf("https://storage.googleapis.com/kubernetes-release/release/%v/bin/linux/amd64/kubectl", version) 227 ) 228 229 if _, err := c.SSH(m, "wget -q "+kubeURL); err != nil { 230 return err 231 } 232 if _, err := c.SSH(m, "chmod +x ./kubectl"); err != nil { 233 return err 234 } 235 236 // cmds to configure kubectl 237 cmds := []string{ 238 fmt.Sprintf("./kubectl config set-cluster default-cluster --server=https://%v --certificate-authority=%v", server, ca), 239 fmt.Sprintf("./kubectl config set-credentials default-admin --certificate-authority=%v --client-key=%v --client-certificate=%v", ca, adminKey, adminCert), 240 "./kubectl config set-context default-system --cluster=default-cluster --user=default-admin", 241 "./kubectl config use-context default-system", 242 } 243 for _, cmd := range cmds { 244 b, err := c.SSH(m, cmd) 245 if err != nil { 246 return fmt.Errorf("Failed on cmd: %s with error: %s and output %s", cmd, err, b) 247 } 248 } 249 return nil 250 } 251 252 var semverPrefix = regexp.MustCompile(`^v[\d]+\.[\d]+\.[\d]+`) 253 254 // Strip semver suffix -- e.g., v1.1.8_coreos.1 --> v1.1.8. If no match 255 // found, return error. 256 func stripSemverSuffix(v string) (string, error) { 257 v = semverPrefix.FindString(v) 258 if v == "" { 259 return "", fmt.Errorf("error stripping semver suffix") 260 } 261 262 return v, nil 263 } 264 265 // Run and configure the coreos-kubernetes generic install scripts. 266 func runInstallScript(c cluster.TestCluster, m platform.Machine, script string, options map[string]string) { 267 c.MustSSH(m, "sudo stat /usr/lib/coreos/kubelet-wrapper") 268 269 var buffer = new(bytes.Buffer) 270 271 tmpl, err := template.New("installScript").Parse(script) 272 if err != nil { 273 c.Fatal(err) 274 } 275 if err := tmpl.Execute(buffer, options); err != nil { 276 c.Fatal(err) 277 } 278 279 if err := platform.InstallFile(buffer, m, "/home/core/install.sh"); err != nil { 280 c.Fatal(err) 281 } 282 283 // use client to collect stderr 284 client, err := m.SSHClient() 285 if err != nil { 286 c.Fatal(err) 287 } 288 defer client.Close() 289 session, err := client.NewSession() 290 if err != nil { 291 c.Fatal(err) 292 } 293 defer session.Close() 294 295 stderr := bytes.NewBuffer(nil) 296 session.Stderr = stderr 297 298 err = session.Start("sudo /home/core/install.sh") 299 if err != nil { 300 c.Fatal(err) 301 } 302 303 // timeout script to prevent it looping forever 304 errc := make(chan error) 305 go func() { 306 errc <- session.Wait() 307 }() 308 select { 309 case err := <-errc: 310 if err != nil { 311 c.Fatal(err) 312 } 313 case <-time.After(time.Minute * 7): 314 c.Fatal("Timed out waiting for install script to finish.") 315 } 316 } 317 318 var ( 319 etcdConfig = conf.ContainerLinuxConfig(` 320 etcd: 321 advertise_client_urls: http://{PUBLIC_IPV4}:2379 322 listen_client_urls: http://0.0.0.0:2379 323 systemd: 324 units: 325 - name: etcd-member.service 326 enabled: true 327 `) 328 ) 329 330 const ( 331 masterCNF = `[req] 332 req_extensions = v3_req 333 distinguished_name = req_distinguished_name 334 [req_distinguished_name] 335 [ v3_req ] 336 basicConstraints = CA:FALSE 337 keyUsage = nonRepudiation, digitalSignature, keyEncipherment 338 subjectAltName = @alt_names 339 [alt_names] 340 DNS.1 = kubernetes 341 DNS.2 = kubernetes.default 342 IP.1 = {{.K8S_SERVICE_IP}} 343 IP.2 = {{.MASTER_HOST}}` 344 345 workerCNF = `[req] 346 req_extensions = v3_req 347 distinguished_name = req_distinguished_name 348 [req_distinguished_name] 349 [ v3_req ] 350 basicConstraints = CA:FALSE 351 keyUsage = nonRepudiation, digitalSignature, keyEncipherment 352 subjectAltName = @alt_names 353 [alt_names] 354 IP.1 = {{.WORKER_IP}}` 355 )