Files
kind/pkg/cluster/cluster.go

184 lines
5.9 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 implements kind local cluster management
package cluster
import (
"fmt"
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
// TODO(bentheelder): fill this in
}
// 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-07-23 10:06:37 -07:00
// TODO(bentheelder): more advanced provisioning
// TODO(bentheelder): multiple nodes
return c.provisionNode()
}
// 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...)
}
func (c *Context) provisionNode() error {
// TODO(bentheelder): multiple nodes...
2018-08-09 11:26:18 -07:00
nodeName := "kind-" + c.config.Name + "-control-plane"
2018-07-23 10:06:37 -07:00
// create the "node" container (docker run, but it is paused, see createNode)
if err := c.createNode(nodeName); err != nil {
return err
}
// 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
if err := c.runOnNode(nodeName, []string{
"mount", "-o", "remount,ro", "/sys",
}); err != nil {
// TODO(bentheelder): logging here
c.deleteNodes(nodeName)
return err
}
// TODO(bentheelder): insert other provisioning here
// (eg enabling / disabling units, installing kube...)
// signal the node to boot into systemd
if err := c.actuallyStartNode(nodeName); err != nil {
// TODO(bentheelder): logging here
c.deleteNodes(nodeName)
return err
}
return nil
}
// createNode `docker run`s the node image, note that due to
// images/node/entrypoint being the entrypoint, this container will
// effectively be paused until we call actuallyStartNode(...)
func (c *Context) createNode(name string) error {
// TODO(bentheelder): use config
// TODO(bentheelder): logging
// TODO(bentheelder): many of these flags should be derived from the config
cmd := exec.Command("docker", "run")
cmd.Args = append(cmd.Args,
"-d", // run the container detached
"-t", // we need a pseudo-tty for systemd logs
// running containers in a container requires privileged
// NOTE: we could try to replicate this with --cap-add, and use less
// privileges, but this flag also changes some mounts that are necessary
// including some ones docker would otherwise do by default.
// for now this is what we want. in the future we may revisit this.
"--privileged",
"--security-opt", "seccomp=unconfined", // also ignore seccomp
"--tmpfs", "/tmp", // various things depend on working /tmp
"--tmpfs", "/run", // systemd wants a writable /run
// docker in docker needs this, so as not to stack overlays
"--tmpfs", "/var/lib/docker:exec",
//"-v", "/sys/fs/cgroup:/sys/fs/cgroup:ro",
// some k8s things want /lib/modules
"-v", "/lib/modules:/lib/modules:ro",
"--hostname", name, // make hostname match container name
"--name", name, // ... and set the container name
// label the node with the cluster ID
2018-08-09 11:26:18 -07:00
"--label", c.config.clusterLabel(),
2018-07-23 10:06:37 -07:00
"kind-node", // use our image, TODO: make this configurable
)
// TODO(bentheelder): collect output instead of connecting these
2018-08-06 17:05:46 -07:00
cmd.InheritOutput = true
2018-07-23 10:06:37 -07:00
return cmd.Run()
}
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()
}
// runOnNode execs command on the named node
func (c *Context) runOnNode(nameOrID string, command []string) error {
// TODO(bentheelder): use config
// TODO(bentheelder): logging
cmd := exec.Command("docker", "exec")
cmd.Args = append(cmd.Args,
"-t", // use a tty so we can get output
"--privileged", // run with priliges so we can remount etc..
nameOrID, // ... against the "node" container
)
cmd.Args = append(cmd.Args,
command..., // finally, run the command supplied by the user
)
// TODO(bentheelder): collect output instead of connecting these
2018-08-06 17:05:46 -07:00
cmd.InheritOutput = true
2018-07-23 10:06:37 -07:00
return cmd.Run()
}
// signal our entrypoint (images/node/entrypoint) to boot
func (c *Context) actuallyStartNode(name string) error {
// TODO(bentheelder): use config
// TODO(bentheelder): logging
cmd := exec.Command("docker", "kill")
cmd.Args = append(cmd.Args,
"-s", "SIGUSR1",
name,
)
// TODO(bentheelder): collect output instead of connecting these
2018-08-06 17:05:46 -07:00
cmd.InheritOutput = true
2018-07-23 10:06:37 -07:00
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
)
// optionally show nodes that are stopped
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
}