Files
kind/pkg/cluster/cluster.go

242 lines
7.5 KiB
Go
Raw Normal View History

2018-07-23 10:06:37 -07:00
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"fmt"
2018-08-27 10:21:43 -07:00
"io/ioutil"
"os"
2018-08-23 18:27:52 -07:00
"time"
2018-08-27 10:21:43 -07:00
"github.com/golang/glog"
2018-08-23 18:27:52 -07:00
"github.com/pkg/errors"
2018-08-06 17:05:46 -07:00
2018-08-27 10:21:43 -07:00
"k8s.io/test-infra/kind/pkg/cluster/kubeadm"
2018-08-06 17:05:46 -07:00
"k8s.io/test-infra/kind/pkg/exec"
2018-07-23 10:06:37 -07:00
)
// Context contains Config and is used to create / manipulate
// kubernetes-in-docker clusters
type Context struct {
2018-08-09 11:26:18 -07:00
config Config
2018-07-23 10:06:37 -07:00
}
// NewContext returns a new cluster management context with Config config
func NewContext(config Config) *Context {
return &Context{
2018-08-09 11:26:18 -07:00
config: config,
2018-07-23 10:06:37 -07:00
}
}
// Create provisions and starts a kubernetes-in-docker cluster
func (c *Context) Create() error {
2018-08-09 11:26:18 -07:00
// validate config first
if err := c.config.Validate(); err != nil {
return err
}
2018-08-23 18:27:52 -07:00
2018-08-27 10:21:43 -07:00
// TODO(bentheelder): multiple nodes ...
kubeadmConfig, err := c.provisionControlPlane(
fmt.Sprintf("kind-%s-control-plane", c.config.Name),
)
// clean up the kubeadm config file
// NOTE: in the future we will use this for other nodes first
if kubeadmConfig != "" {
defer os.Remove(kubeadmConfig)
}
if err != nil {
return err
}
println("\nYou can now use the cluster with:\n")
println("export KUBECONFIG=\"" + c.config.KubeConfigPath() + "\"")
println("kubectl cluster-info\n")
return nil
2018-07-23 10:06:37 -07:00
}
// Delete tears down a kubernetes-in-docker cluster
func (c *Context) Delete() error {
nodes, err := c.ListNodes(true)
if err != nil {
return fmt.Errorf("error listing nodes: %v", err)
}
return c.deleteNodes(nodes...)
}
2018-08-27 10:21:43 -07:00
// provisionControlPlane provisions the control plane node
// and the cluster kubeadm config
func (c *Context) provisionControlPlane(name string) (kubeadmConfigPath string, err error) {
2018-07-23 10:06:37 -07:00
// create the "node" container (docker run, but it is paused, see createNode)
2018-08-27 10:21:43 -07:00
node, err := createNode(name, c.config.clusterLabel())
if err != nil {
return "", err
2018-07-23 10:06:37 -07:00
}
// systemd-in-a-container should have read only /sys
// https://www.freedesktop.org/wiki/Software/systemd/ContainerInterface/
// however, we need other things from `docker run --privileged` ...
// and this flag also happens to make /sys rw, amongst other things
2018-08-27 10:21:43 -07:00
if err := node.Run("mount", "-o", "remount,ro", "/sys"); err != nil {
2018-07-23 10:06:37 -07:00
// TODO(bentheelder): logging here
2018-08-23 18:27:52 -07:00
// TODO(bentheelder): add a flag to retain the broken nodes for debugging
2018-08-27 10:21:43 -07:00
c.deleteNodes(node.nameOrID)
return "", err
2018-07-23 10:06:37 -07:00
}
2018-08-27 10:21:43 -07:00
// signal the node entrypoint to continue booting into systemd
if err := node.SignalStart(); err != nil {
2018-07-23 10:06:37 -07:00
// TODO(bentheelder): logging here
2018-08-27 10:21:43 -07:00
// TODO(bentheelder): add a flag to retain the broken nodes for debugging
c.deleteNodes(node.nameOrID)
return "", err
2018-07-23 10:06:37 -07:00
}
2018-08-23 18:27:52 -07:00
// wait for docker to be ready
2018-08-27 10:21:43 -07:00
if !node.WaitForDocker(time.Now().Add(time.Second * 30)) {
// TODO(bentheelder): logging here
// TODO(bentheelder): add a flag to retain the broken nodes for debugging
c.deleteNodes(node.nameOrID)
return "", fmt.Errorf("timed out waiting for docker to be ready on node")
2018-08-23 18:27:52 -07:00
}
2018-08-27 10:21:43 -07:00
// load the docker image artifacts into the docker daemon
node.LoadImages()
// get installed kubernetes version from the node image
kubeVersion, err := node.KubeVersion()
if err != nil {
// TODO(bentheelder): logging here
// TODO(bentheelder): add a flag to retain the broken nodes for debugging
c.deleteNodes(node.nameOrID)
return "", fmt.Errorf("failed to get kubernetes version from node: %v", err)
}
// create kubeadm config file
kubeadmConfig, err := c.createKubeadmConfig("", kubeadm.ConfigData{
ClusterName: c.config.ClusterName(),
KubernetesVersion: kubeVersion,
})
// copy the config to the node
if err := node.CopyTo(kubeadmConfig, "/kind/kubeadm.conf"); err != nil {
// TODO(bentheelder): logging here
// TODO(bentheelder): add a flag to retain the broken nodes for debugging
c.deleteNodes(node.nameOrID)
return kubeadmConfig, errors.Wrap(err, "failed to copy kubeadm config to node")
}
// run kubeadm
if err := node.Run(
// init because this is the control plane node
2018-08-23 18:27:52 -07:00
"kubeadm", "init",
2018-08-27 10:21:43 -07:00
// preflight errors are expected, in particular for swap being enabled
// TODO(bentheelder): limit the set of acceptable errors
2018-08-23 18:27:52 -07:00
"--ignore-preflight-errors=all",
2018-08-27 10:21:43 -07:00
// specify our generated config file
"--config=/kind/kubeadm.conf",
); err != nil {
// TODO(bentheelder): logging here
// TODO(bentheelder): add a flag to retain the broken nodes for debugging
c.deleteNodes(node.nameOrID)
return kubeadmConfig, errors.Wrap(err, "failed to init node with kubeadm")
2018-08-23 18:27:52 -07:00
}
2018-08-27 10:21:43 -07:00
// set up the $KUBECONFIG
kubeConfigPath := c.config.KubeConfigPath()
if err = node.WriteKubeConfig(kubeConfigPath); err != nil {
// TODO(bentheelder): logging here
// TODO(bentheelder): add a flag to retain the broken nodes for debugging
c.deleteNodes(node.nameOrID)
return kubeadmConfig, errors.Wrap(err, "failed to get kubeconfig from node")
}
2018-08-23 18:27:52 -07:00
2018-08-27 10:21:43 -07:00
// TODO(bentheelder): support other overlay networks
if err = node.Run(
"/bin/sh", "-c",
`kubectl apply --kubeconfig=/etc/kubernetes/admin.conf -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"`,
); err != nil {
return kubeadmConfig, errors.Wrap(err, "failed to apply overlay network")
}
2018-07-23 10:06:37 -07:00
2018-08-27 10:21:43 -07:00
// if we are only provisioning one node, remove the master taint
// https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#master-isolation
if c.config.NumNodes == 1 {
if err = node.Run(
"kubectl", "--kubeconfig=/etc/kubernetes/admin.conf",
"taint", "nodes", "--all", "node-role.kubernetes.io/master-",
); err != nil {
return kubeadmConfig, errors.Wrap(err, "failed to remove master taint")
2018-08-23 18:27:52 -07:00
}
}
2018-08-27 10:21:43 -07:00
return kubeadmConfig, nil
2018-08-23 18:27:52 -07:00
}
2018-08-27 10:21:43 -07:00
// createKubeadmConfig creates the kubeadm config file for the cluster
// by running data through the template and writing it to a temp file
// the config file path is returned, this file should be removed later
func (c *Context) createKubeadmConfig(template string, data kubeadm.ConfigData) (path string, err error) {
// create kubeadm config file
f, err := ioutil.TempFile("", "")
if err != nil {
return "", errors.Wrap(err, "failed to create kubeadm config")
}
path = f.Name()
// generate the config contents
config, err := kubeadm.Config(template, data)
if err != nil {
os.Remove(path)
return "", err
}
glog.Infof("Using KubeadmConfig:\n\n%s\n", config)
_, err = f.WriteString(config)
if err != nil {
os.Remove(path)
return "", err
}
return path, nil
2018-07-23 10:06:37 -07:00
}
func (c *Context) deleteNodes(names ...string) error {
cmd := exec.Command("docker", "rm")
cmd.Args = append(cmd.Args,
"-f", // force the container to be delete now
)
cmd.Args = append(cmd.Args, names...)
return cmd.Run()
}
// ListNodes returns the list of container IDs for the "nodes" in the cluster
func (c *Context) ListNodes(alsoStopped bool) (containerIDs []string, err error) {
cmd := exec.Command("docker", "ps")
cmd.Args = append(cmd.Args,
// quiet output for parsing
"-q",
// filter for nodes with the cluster label
2018-08-09 11:26:18 -07:00
"--filter", "label="+c.config.clusterLabel(),
2018-07-23 10:06:37 -07:00
)
2018-08-27 10:21:43 -07:00
// optionally list nodes that are stopped
2018-07-23 10:06:37 -07:00
if alsoStopped {
cmd.Args = append(cmd.Args, "-a")
}
2018-08-06 17:05:46 -07:00
return cmd.CombinedOutputLines()
2018-07-23 10:06:37 -07:00
}