Merge pull request #1138 from nathanleclaire/swarm_options

Implement configurable Swarm options
This commit is contained in:
Evan Hazlett 2015-05-26 15:33:06 -04:00
commit 0a0bbf7dc0
11 changed files with 226 additions and 77 deletions

View File

@ -203,6 +203,16 @@ var sharedCreateFlags = []cli.Flag{
Usage: "Discovery service to use with Swarm",
Value: "",
},
cli.StringFlag{
Name: "swarm-strategy",
Usage: "Define a default scheduling strategy for Swarm",
Value: "spread",
},
cli.StringSliceFlag{
Name: "swarm-opt",
Usage: "Define arbitrary flags for swarm",
Value: &cli.StringSlice{},
},
cli.StringFlag{
Name: "swarm-host",
Usage: "ip/socket to listen on for Swarm master",

View File

@ -77,11 +77,13 @@ func cmdCreate(c *cli.Context) {
TlsVerify: true,
},
SwarmOptions: &swarm.SwarmOptions{
IsSwarm: c.Bool("swarm"),
Master: c.Bool("swarm-master"),
Discovery: c.String("swarm-discovery"),
Address: c.String("swarm-addr"),
Host: c.String("swarm-host"),
IsSwarm: c.Bool("swarm"),
Master: c.Bool("swarm-master"),
Discovery: c.String("swarm-discovery"),
Address: c.String("swarm-addr"),
Host: c.String("swarm-host"),
Strategy: c.String("swarm-strategy"),
ArbitraryFlags: c.StringSlice("swarm-opt"),
},
}

View File

@ -592,6 +592,40 @@ $ docker-machine create -d virtualbox \
gdns
```
##### Specifying Swarm options for the created machine
In addition to being able to configure Docker Engine options as listed above,
you can use Machine to specify how the created Swarm master should be
configured). There is a `--swarm-strategy` flag, which you can use to specify
the [scheduling strategy](https://docs.docker.com/swarm/scheduler/strategy/)
which Docker Swarm should use (Machine defaults to the `spread` strategy).
There is also a general purpose `--swarm-opt` option which works similar to how
the aforementioned `--engine-opt` option does, except that it specifies options
for the `swarm manage` command (used to boot a master node) instead of the base
command. You can use this to configure features that power users might be
interested in, such as configuring the heartbeat interval or Swarm's willingness
to over-commit resources.
If you're not sure how to configure these options, it is best to not specify
configuration at all. Docker Machine will choose sensible defaults for you and
you won't have to worry about it.
Example create:
```
$ docker-machine create -d virtualbox \
--swarm \
--swarm-master \
--swarm-discovery token://<token> \
--swarm-strategy binpack \
--swarm-opt heartbeat=5 \
upbeat
```
This will set the swarm scheduling strategy to "binpack" (pack in containers as
tightly as possible per host instead of spreading them out), and the "heartbeat"
interval to 5 seconds.
#### config
Show the Docker client configuration for a machine.

View File

@ -213,7 +213,7 @@ func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOpt
return err
}
if err := configureSwarm(provisioner, swarmOptions); err != nil {
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
return err
}

View File

@ -0,0 +1,120 @@
package provision
import (
"bytes"
"fmt"
"net/url"
"strings"
"text/template"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/log"
)
type SwarmCommandContext struct {
ContainerName string
DockerDir string
DockerPort int
Ip string
Port string
AuthOptions auth.AuthOptions
SwarmOptions swarm.SwarmOptions
SwarmImage string
}
// Wrapper function to generate a docker run swarm command (manage or join)
// from a template/context and execute it.
func runSwarmCommandFromTemplate(p Provisioner, cmdTmpl string, swarmCmdContext SwarmCommandContext) error {
var (
executedCmdTmpl bytes.Buffer
)
parsedMasterCmdTemplate, err := template.New("swarmMasterCmd").Parse(cmdTmpl)
if err != nil {
return err
}
parsedMasterCmdTemplate.Execute(&executedCmdTmpl, swarmCmdContext)
log.Debugf("The swarm command being run is: %s", executedCmdTmpl.String())
if _, err := p.SSHCommand(executedCmdTmpl.String()); err != nil {
return err
}
return nil
}
func configureSwarm(p Provisioner, swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions) error {
if !swarmOptions.IsSwarm {
return nil
}
ip, err := p.GetDriver().GetIP()
if err != nil {
return err
}
u, err := url.Parse(swarmOptions.Host)
if err != nil {
return err
}
parts := strings.Split(u.Host, ":")
port := parts[1]
dockerDir := p.GetDockerOptionsDir()
swarmCmdContext := SwarmCommandContext{
ContainerName: "",
DockerDir: dockerDir,
DockerPort: 2376,
Ip: ip,
Port: port,
AuthOptions: authOptions,
SwarmOptions: swarmOptions,
SwarmImage: swarm.DockerImage,
}
// First things first, get the swarm image.
if _, err := p.SSHCommand(fmt.Sprintf("sudo docker pull %s", swarm.DockerImage)); err != nil {
return err
}
swarmMasterCmdTemplate := `sudo docker run -d \
--restart=always \
--name swarm-agent-master \
-p {{.Port}}:{{.Port}} \
-v {{.DockerDir}}:{{.DockerDir}} \
{{.SwarmImage}} \
manage \
--tlsverify \
--tlscacert={{.AuthOptions.CaCertRemotePath}} \
--tlscert={{.AuthOptions.ServerCertRemotePath}} \
--tlskey={{.AuthOptions.ServerKeyRemotePath}} \
-H {{.SwarmOptions.Host}} \
--strategy {{.SwarmOptions.Strategy}} {{range .SwarmOptions.ArbitraryFlags}} --{{.}}{{end}} {{.SwarmOptions.Discovery}}
`
swarmWorkerCmdTemplate := `sudo docker run -d \
--restart=always \
--name swarm-agent \
{{.SwarmImage}} \
join --addr {{.Ip}}:{{.DockerPort}} {{.SwarmOptions.Discovery}}
`
if swarmOptions.Master {
log.Debug("Launching swarm master")
if err := runSwarmCommandFromTemplate(p, swarmMasterCmdTemplate, swarmCmdContext); err != nil {
return err
}
}
log.Debug("Launch swarm worker")
if err := runSwarmCommandFromTemplate(p, swarmWorkerCmdTemplate, swarmCmdContext); err != nil {
return err
}
return nil
}

View File

@ -117,7 +117,7 @@ func (provisioner *RancherProvisioner) Provision(swarmOptions swarm.SwarmOptions
}
log.Debugf("Configuring swarm")
if err := configureSwarm(provisioner, swarmOptions); err != nil {
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
return err
}

View File

@ -174,7 +174,7 @@ func (provisioner *RedHatProvisioner) Provision(swarmOptions swarm.SwarmOptions,
return err
}
if err := configureSwarm(provisioner, swarmOptions); err != nil {
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
return err
}

View File

@ -131,7 +131,7 @@ func (provisioner *UbuntuProvisioner) Provision(swarmOptions swarm.SwarmOptions,
return err
}
if err := configureSwarm(provisioner, swarmOptions); err != nil {
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
return err
}

View File

@ -11,7 +11,6 @@ import (
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/log"
"github.com/docker/machine/utils"
)
@ -180,58 +179,3 @@ func ConfigureAuth(p Provisioner) error {
return nil
}
func configureSwarm(p Provisioner, swarmOptions swarm.SwarmOptions) error {
if !swarmOptions.IsSwarm {
return nil
}
log.Debug("configuring swarm")
basePath := p.GetDockerOptionsDir()
ip, err := p.GetDriver().GetIP()
if err != nil {
return err
}
tlsCaCert := path.Join(basePath, "ca.pem")
tlsCert := path.Join(basePath, "server.pem")
tlsKey := path.Join(basePath, "server-key.pem")
masterArgs := fmt.Sprintf("--tlsverify --tlscacert=%s --tlscert=%s --tlskey=%s -H %s %s",
tlsCaCert, tlsCert, tlsKey, swarmOptions.Host, swarmOptions.Discovery)
nodeArgs := fmt.Sprintf("--addr %s:2376 %s", ip, swarmOptions.Discovery)
u, err := url.Parse(swarmOptions.Host)
if err != nil {
return err
}
parts := strings.Split(u.Host, ":")
port := parts[1]
if _, err := p.SSHCommand(fmt.Sprintf("sudo docker pull %s", swarm.DockerImage)); err != nil {
return err
}
dockerDir := p.GetDockerOptionsDir()
// if master start master agent
if swarmOptions.Master {
log.Debug("launching swarm master")
log.Debugf("master args: %s", masterArgs)
if _, err = p.SSHCommand(fmt.Sprintf("sudo docker run -d -p %s:%s --restart=always --name swarm-agent-master -v %s:%s %s manage %s",
port, port, dockerDir, dockerDir, swarm.DockerImage, masterArgs)); err != nil {
return err
}
}
// start node agent
log.Debug("launching swarm node")
log.Debugf("node args: %s", nodeArgs)
if _, err = p.SSHCommand(fmt.Sprintf("sudo docker run -d --restart=always --name swarm-agent -v %s:%s %s join %s",
dockerDir, dockerDir, swarm.DockerImage, nodeArgs)); err != nil {
return err
}
return nil
}

View File

@ -6,16 +6,17 @@ const (
)
type SwarmOptions struct {
IsSwarm bool
Address string
Discovery string
Master bool
Host string
Strategy string
Heartbeat int
Overcommit float64
TlsCaCert string
TlsCert string
TlsKey string
TlsVerify bool
IsSwarm bool
Address string
Discovery string
Master bool
Host string
Strategy string
Heartbeat int
Overcommit float64
TlsCaCert string
TlsCert string
TlsKey string
TlsVerify bool
ArbitraryFlags []string
}

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bats
load helpers
export DRIVER=virtualbox
export MACHINE_STORAGE_PATH=/tmp/machine-bats-test-$DRIVER
export TOKEN=$(curl -sS -X POST "https://discovery-stage.hub.docker.com/v1/clusters")
@test "create swarm master" {
run machine create -d virtualbox --swarm --swarm-master --swarm-discovery "token://$TOKEN" --swarm-strategy binpack --swarm-opt heartbeat=5 queenbee
[[ "$status" -eq 0 ]]
}
@test "create swarm node" {
run machine create -d virtualbox --swarm --swarm-discovery "token://$TOKEN" workerbee
[[ "$status" -eq 0 ]]
}
@test "ensure strategy is correct" {
strategy=$(docker $(machine config --swarm queenbee) info | grep "Strategy:" | awk '{ print $2 }')
echo ${strategy}
[[ "$strategy" == "binpack" ]]
}
@test "ensure heartbeat" {
heartbeat_arg=$(docker $(machine config queenbee) inspect -f '{{index .Args 9}}' swarm-agent-master)
echo ${heartbeat_arg}
[[ "$heartbeat_arg" == "--heartbeat=5" ]]
}
@test "clean up created nodes" {
run machine rm queenbee workerbee
[[ "$status" -eq 0 ]]
}
@test "remove dir" {
rm -rf $MACHINE_STORAGE_PATH
}