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  )