mirror of https://github.com/docker/docs.git
Merge pull request #1138 from nathanleclaire/swarm_options
Implement configurable Swarm options
This commit is contained in:
commit
0a0bbf7dc0
|
@ -203,6 +203,16 @@ var sharedCreateFlags = []cli.Flag{
|
||||||
Usage: "Discovery service to use with Swarm",
|
Usage: "Discovery service to use with Swarm",
|
||||||
Value: "",
|
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{
|
cli.StringFlag{
|
||||||
Name: "swarm-host",
|
Name: "swarm-host",
|
||||||
Usage: "ip/socket to listen on for Swarm master",
|
Usage: "ip/socket to listen on for Swarm master",
|
||||||
|
|
|
@ -82,6 +82,8 @@ func cmdCreate(c *cli.Context) {
|
||||||
Discovery: c.String("swarm-discovery"),
|
Discovery: c.String("swarm-discovery"),
|
||||||
Address: c.String("swarm-addr"),
|
Address: c.String("swarm-addr"),
|
||||||
Host: c.String("swarm-host"),
|
Host: c.String("swarm-host"),
|
||||||
|
Strategy: c.String("swarm-strategy"),
|
||||||
|
ArbitraryFlags: c.StringSlice("swarm-opt"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -592,6 +592,40 @@ $ docker-machine create -d virtualbox \
|
||||||
gdns
|
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
|
#### config
|
||||||
|
|
||||||
Show the Docker client configuration for a machine.
|
Show the Docker client configuration for a machine.
|
||||||
|
|
|
@ -213,7 +213,7 @@ func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOpt
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := configureSwarm(provisioner, swarmOptions); err != nil {
|
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -117,7 +117,7 @@ func (provisioner *RancherProvisioner) Provision(swarmOptions swarm.SwarmOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Configuring swarm")
|
log.Debugf("Configuring swarm")
|
||||||
if err := configureSwarm(provisioner, swarmOptions); err != nil {
|
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,7 @@ func (provisioner *RedHatProvisioner) Provision(swarmOptions swarm.SwarmOptions,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := configureSwarm(provisioner, swarmOptions); err != nil {
|
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ func (provisioner *UbuntuProvisioner) Provision(swarmOptions swarm.SwarmOptions,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := configureSwarm(provisioner, swarmOptions); err != nil {
|
if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
|
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"github.com/docker/machine/libmachine/auth"
|
||||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
@ -180,58 +179,3 @@ func ConfigureAuth(p Provisioner) error {
|
||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,4 +18,5 @@ type SwarmOptions struct {
|
||||||
TlsCert string
|
TlsCert string
|
||||||
TlsKey string
|
TlsKey string
|
||||||
TlsVerify bool
|
TlsVerify bool
|
||||||
|
ArbitraryFlags []string
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue