1
0
mirror of https://github.com/rancher/os.git synced 2025-09-02 07:15:41 +00:00

Add multi docker command

This commit is contained in:
Jason-ZW
2018-06-27 12:08:32 +08:00
committed by niusmallnan
parent 05c2a40aa5
commit 0821c9a4ea
9 changed files with 489 additions and 14 deletions

View File

@@ -2,15 +2,27 @@ package control
import (
"fmt"
"io/ioutil"
"net"
"os"
"os/user"
"path"
"sort"
"strconv"
"strings"
"golang.org/x/net/context"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/codegangsta/cli"
"github.com/docker/docker/reference"
"github.com/docker/engine-api/types"
composeConfig "github.com/docker/libcompose/config"
"github.com/docker/libcompose/project/options"
composeYaml "github.com/docker/libcompose/yaml"
"github.com/pkg/errors"
"github.com/rancher/os/cmd/control/service"
"github.com/rancher/os/cmd/control/service/app"
"github.com/rancher/os/compose"
"github.com/rancher/os/config"
"github.com/rancher/os/docker"
@@ -19,6 +31,11 @@ import (
"github.com/rancher/os/util/network"
)
var (
SupportedEngineVersions = []string{"docker:17.12.1-dind", "docker:18.03-dind", "docker:18.03.1-dind"}
SSHKeyPathDefault = "%s/.ssh/authorized_keys"
)
func engineSubcommands() []cli.Command {
return []cli.Command{
{
@@ -36,6 +53,64 @@ func engineSubcommands() []cli.Command {
},
},
},
{
Name: "create",
Usage: "create Docker engine without a reboot",
ArgsUsage: "<name>",
Before: preFlightValidate,
Action: engineCreate,
Flags: []cli.Flag{
cli.StringFlag{
Name: "version, v",
Value: SupportedEngineVersions[0],
Usage: "set the version for the engine",
},
cli.StringFlag{
Name: "network",
Usage: "set the network for the engine",
},
cli.StringFlag{
Name: "fixed-ip",
Usage: "set the fix ip for the engine",
},
cli.IntFlag{
Name: "ssh-port",
Value: randomSSHPort(),
Usage: "set the ssh port for the engine",
},
cli.StringFlag{
Name: "authorized-keys",
Usage: "set the ssh authorized_keys path for the engine",
},
},
},
{
Name: "rm",
Usage: "remove Docker engine without a reboot",
ArgsUsage: "<name>",
Before: func(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.New("Must specify exactly one Docker engine to remove")
}
return nil
},
Action: engineRemove,
Flags: []cli.Flag{
cli.IntFlag{
Name: "timeout,t",
Usage: "Specify a shutdown timeout in seconds.",
Value: 10,
},
cli.BoolFlag{
Name: "force,f",
Usage: "Allow deletion of all services",
},
cli.BoolFlag{
Name: "v",
Usage: "Remove volumes associated with containers",
},
},
},
{
Name: "enable",
Usage: "set Docker engine to be switched on next reboot",
@@ -82,6 +157,96 @@ func engineSwitch(c *cli.Context) error {
return nil
}
func engineCreate(c *cli.Context) error {
name := c.Args()[0]
version := c.String("version")
sshPort := c.Int("ssh-port")
authorizedKeys := c.String("authorized-keys")
network := c.String("network")
fixedIP := c.String("fixed-ip")
if authorizedKeys == "" {
authorizedKeys = authorizedKeysPath()
}
// generate & create engine compose
err := generateEngineCompose(name, version, sshPort, authorizedKeys, network, fixedIP)
if err != nil {
return err
}
// stage engine service
cfg := config.LoadConfig()
var enabledServices []string
if val, ok := cfg.Rancher.ServicesInclude[name]; !ok || !val {
cfg.Rancher.ServicesInclude[name] = true
enabledServices = append(enabledServices, name)
}
if len(enabledServices) > 0 {
if err := compose.StageServices(cfg, enabledServices...); err != nil {
log.Fatal(err)
}
if err := config.Set("rancher.services_include", cfg.Rancher.ServicesInclude); err != nil {
log.Fatal(err)
}
}
// generate engine script
err = util.GenerateEngineScript(name)
if err != nil {
log.Fatal(err)
}
return nil
}
func engineRemove(c *cli.Context) error {
name := c.Args()[0]
cfg := config.LoadConfig()
p, err := compose.GetProject(cfg, true, false)
if err != nil {
log.Fatalf("Get project failed: %v", err)
}
// 1. service stop
err = app.ProjectStop(p, c)
if err != nil {
log.Fatalf("Stop project service failed: %v", err)
}
// 2. service delete
err = app.ProjectDelete(p, c)
if err != nil {
log.Fatalf("Delete project service failed: %v", err)
}
// 3. service delete
changed := false
if _, ok := cfg.Rancher.ServicesInclude[name]; !ok {
log.Fatalf("Failed to found enabled service %s", name)
}
delete(cfg.Rancher.ServicesInclude, name)
changed = true
if changed {
if err = config.Set("rancher.services_include", cfg.Rancher.ServicesInclude); err != nil {
log.Fatal(err)
}
}
// 4. remove service from file
err = RemoveEngineFromCompose(name)
if err != nil {
log.Fatal(err)
}
return nil
}
func engineEnable(c *cli.Context) error {
if len(c.Args()) != 1 {
log.Fatal("Must specify exactly one Docker engine to enable")
@@ -166,3 +331,207 @@ func CurrentEngine() (engine string) {
return
}
func preFlightValidate(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.New("Must specify one engine name")
}
name := c.Args()[0]
if name == "" {
return errors.New("Must specify one engine name")
}
version := c.String("version")
if version == "" {
return errors.New("Must specify one engine version")
}
port := c.Int("ssh-port")
if port == 0 {
return errors.New("Must specify one engine ssh port")
}
network := c.String("network")
if network == "" {
return errors.New("Must specify network")
}
userDefineNetwork, err := CheckUserDefineNetwork(network)
if err != nil {
return err
}
fixedIP := c.String("fixed-ip")
if fixedIP == "" {
return errors.New("Must specify fix ip")
}
err = CheckUserDefineIPv4Address(fixedIP, *userDefineNetwork)
if err != nil {
return err
}
isVersionMatch := false
for _, v := range SupportedEngineVersions {
if v == version {
isVersionMatch = true
break
}
}
if !isVersionMatch {
return errors.Errorf("Engine version not supported only %v are supported", SupportedEngineVersions)
}
addr, err := net.ResolveTCPAddr("tcp", "localhost:"+strconv.Itoa(port))
if err != nil {
return errors.Errorf("Failed to resolve tcp addr: %v", err)
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return errors.Errorf("Failed to listen tcp: %v", err)
}
defer l.Close()
return nil
}
func authorizedKeysPath() string {
home := "/home/rancher"
user, err := user.Current()
if err == nil {
home = user.HomeDir
}
return fmt.Sprintf(SSHKeyPathDefault, home)
}
func randomSSHPort() int {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
log.Errorf("Failed to resolve tcp addr: %v", err)
return 0
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port
}
func generateEngineCompose(name, version string, sshPort int, authorizedKeys, network, fixedIP string) error {
if err := os.MkdirAll(path.Dir(config.MultiDockerConfFile), 0700); err != nil && !os.IsExist(err) {
log.Errorf("Failed to create directory for file %s: %v", config.MultiDockerConfFile, err)
return err
}
composeConfigs := map[string]composeConfig.ServiceConfigV1{}
if _, err := os.Stat(config.MultiDockerConfFile); err == nil {
// read from engine compose
bytes, err := ioutil.ReadFile(config.MultiDockerConfFile)
if err != nil {
return err
}
err = yaml.Unmarshal(bytes, &composeConfigs)
if err != nil {
return err
}
}
if err := os.MkdirAll(config.MultiDockerDataDir+"/"+name, 0700); err != nil && !os.IsExist(err) {
log.Errorf("Failed to create directory for file %s: %v", config.MultiDockerDataDir+"/"+name, err)
return err
}
composeConfigs[name] = composeConfig.ServiceConfigV1{
Image: "${REGISTRY_DOMAIN}/" + version,
Restart: "always",
Privileged: true,
Net: network,
Ports: []string{strconv.Itoa(sshPort) + ":22"},
Volumes: []string{
"/lib/modules:/lib/modules",
config.MultiDockerDataDir + "/" + name + ":" + config.MultiDockerDataDir + "/" + name,
authorizedKeys + ":/root/.ssh/authorized_keys",
},
VolumesFrom: []string{},
Command: composeYaml.Command{
"dockerd-entrypoint.sh",
"--storage-driver=overlay2",
"--data-root=" + config.MultiDockerDataDir + "/" + name,
"--host=unix://" + config.MultiDockerDataDir + "/" + name + "/docker-" + name + ".sock",
},
Labels: composeYaml.SliceorMap{
"io.rancher.os.scope": "system",
"io.rancher.os.after": "console",
config.UserDockerLabel: name,
config.UserDockerNetLabel: network,
config.UserDockerFIPLabel: fixedIP,
},
}
bytes, err := yaml.Marshal(composeConfigs)
if err != nil {
return err
}
return ioutil.WriteFile(config.MultiDockerConfFile, bytes, 0640)
}
func RemoveEngineFromCompose(name string) error {
composeConfigs := map[string]composeConfig.ServiceConfigV1{}
if _, err := os.Stat(config.MultiDockerConfFile); err == nil {
// read from engine compose
bytes, err := ioutil.ReadFile(config.MultiDockerConfFile)
if err != nil {
return err
}
err = yaml.Unmarshal(bytes, &composeConfigs)
if err != nil {
return err
}
}
delete(composeConfigs, name)
bytes, err := yaml.Marshal(composeConfigs)
if err != nil {
return err
}
return ioutil.WriteFile(config.MultiDockerConfFile, bytes, 0640)
}
func CheckUserDefineNetwork(name string) (*types.NetworkResource, error) {
systemClient, err := docker.NewSystemClient()
if err != nil {
return nil, err
}
networks, err := systemClient.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
return nil, err
}
for _, network := range networks {
if network.Name == name {
return &network, nil
}
}
return nil, errors.Errorf("Failed to found the user define network: %s", name)
}
func CheckUserDefineIPv4Address(ipv4 string, network types.NetworkResource) error {
for _, config := range network.IPAM.Config {
_, ipnet, _ := net.ParseCIDR(config.Subnet)
if ipnet.Contains(net.ParseIP(ipv4)) {
return nil
}
}
return errors.Errorf("IP %s is not in the specified cidr", ipv4)
}