diff --git a/client/driver/docker.go b/client/driver/docker.go index 2b4fdd78e..0fbf64714 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -37,11 +37,36 @@ func NewDockerDriver(ctx *DriverContext) Driver { return &DockerDriver{*ctx} } +// dockerClient creates *docker.Client. In test / dev mode we can use ENV vars +// to connect to the docker daemon. In production mode we will read +// docker.endpoint from the config file. +func (d *DockerDriver) dockerClient() (*docker.Client, error) { + // In dev mode, read DOCKER_* environment variables DOCKER_HOST, + // DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH. This allows you to run tests and + // demo against boot2docker or a VM on OSX and Windows. This falls back on + // the default unix socket on linux if tests are run on linux. + // + // Also note that we need to turn on DevMode in the test configs. + if d.config.DevMode { + return docker.NewClientFromEnv() + } + + // In prod mode we'll read the docker.endpoint configuration and fall back + // on the host-specific default. We do not read from the environment. + defaultEndpoint, err := docker.DefaultDockerHost() + if err != nil { + return nil, fmt.Errorf("Unable to determine default docker endpoint: %s", err) + } + dockerEndpoint := d.config.ReadDefault("docker.endpoint", defaultEndpoint) + + return docker.NewClient(dockerEndpoint) +} + func (d *DockerDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { // Initialize docker API client - dockerEndpoint := d.config.ReadDefault("docker.endpoint", "unix:///var/run/docker.sock") - client, err := docker.NewClient(dockerEndpoint) + client, err := d.dockerClient() if err != nil { + d.logger.Printf("[DEBUG] driver.docker: could not connect to docker daemon: %v", err) return false, nil } @@ -56,6 +81,7 @@ func (d *DockerDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool env, err := client.Version() if err != nil { + d.logger.Printf("[DEBUG] driver.docker: could not read version from daemon: %v", err) // Check the "no such file" error if the unix file is missing if strings.Contains(err.Error(), "no such file") { return false, nil @@ -212,10 +238,9 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle } // Initialize docker API client - dockerEndpoint := d.config.ReadDefault("docker.endpoint", "unix:///var/run/docker.sock") - client, err := docker.NewClient(dockerEndpoint) + client, err := d.dockerClient() if err != nil { - return nil, fmt.Errorf("Failed to connect to docker.endpoint (%s): %s", dockerEndpoint, err) + return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err) } repo, tag := docker.ParseRepositoryTag(image) @@ -309,10 +334,9 @@ func (d *DockerDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, er d.logger.Printf("[INFO] driver.docker: re-attaching to docker process: %s", handleID) // Initialize docker API client - dockerEndpoint := d.config.ReadDefault("docker.endpoint", "unix:///var/run/docker.sock") - client, err := docker.NewClient(dockerEndpoint) + client, err := d.dockerClient() if err != nil { - return nil, fmt.Errorf("Failed to connect to docker.endpoint (%s): %s", dockerEndpoint, err) + return nil, fmt.Errorf("Failed to connect to docker daemon: %s", err) } // Look for a running container with this ID @@ -401,6 +425,7 @@ func (h *dockerHandle) Kill() error { err = h.client.RemoveImage(h.imageID) if err != nil { containers, err := h.client.ListContainers(docker.ListContainersOptions{ + // The image might be in use by a stopped container, so check everything All: true, Filters: map[string][]string{ "image": []string{h.imageID}, diff --git a/client/driver/docker_test.go b/client/driver/docker_test.go index 6af8e28d1..3f526b66f 100644 --- a/client/driver/docker_test.go +++ b/client/driver/docker_test.go @@ -9,6 +9,12 @@ import ( "github.com/hashicorp/nomad/nomad/structs" ) +func testDockerDriverContext(task string) *DriverContext { + cfg := testConfig() + cfg.DevMode = true + return NewDriverContext(task, cfg, cfg.Node, testLogger()) +} + // dockerLocated looks to see whether docker is available on this system before // we try to run tests. We'll keep it simple and just check for the CLI. func dockerLocated() bool { @@ -33,7 +39,7 @@ func TestDockerDriver_Handle(t *testing.T) { // The fingerprinter test should always pass, even if Docker is not installed. func TestDockerDriver_Fingerprint(t *testing.T) { - d := NewDockerDriver(testDriverContext("")) + d := NewDockerDriver(testDockerDriverContext("")) node := &structs.Node{ Attributes: make(map[string]string), } @@ -56,14 +62,14 @@ func TestDockerDriver_StartOpen_Wait(t *testing.T) { } task := &structs.Task{ - Name: "python-demo", + Name: "redis-demo", Config: map[string]string{ "image": "redis", }, Resources: basicResources, } - driverCtx := testDriverContext(task.Name) + driverCtx := testDockerDriverContext(task.Name) ctx := testDriverExecContext(task, driverCtx) defer ctx.AllocDir.Destroy() d := NewDockerDriver(driverCtx) @@ -93,7 +99,7 @@ func TestDockerDriver_Start_Wait(t *testing.T) { } task := &structs.Task{ - Name: "python-demo", + Name: "redis-demo", Config: map[string]string{ "image": "redis", "command": "redis-server -v", @@ -104,7 +110,7 @@ func TestDockerDriver_Start_Wait(t *testing.T) { }, } - driverCtx := testDriverContext(task.Name) + driverCtx := testDockerDriverContext(task.Name) ctx := testDriverExecContext(task, driverCtx) defer ctx.AllocDir.Destroy() d := NewDockerDriver(driverCtx) @@ -140,7 +146,7 @@ func TestDockerDriver_Start_Kill_Wait(t *testing.T) { } task := &structs.Task{ - Name: "python-demo", + Name: "redis-demo", Config: map[string]string{ "image": "redis", "command": "sleep 10", @@ -148,7 +154,7 @@ func TestDockerDriver_Start_Kill_Wait(t *testing.T) { Resources: basicResources, } - driverCtx := testDriverContext(task.Name) + driverCtx := testDockerDriverContext(task.Name) ctx := testDriverExecContext(task, driverCtx) defer ctx.AllocDir.Destroy() d := NewDockerDriver(driverCtx) @@ -222,7 +228,7 @@ func TestDocker_StartN(t *testing.T) { // Let's spin up a bunch of things var err error for idx, task := range taskList { - driverCtx := testDriverContext(task.Name) + driverCtx := testDockerDriverContext(task.Name) ctx := testDriverExecContext(task, driverCtx) defer ctx.AllocDir.Destroy() d := NewDockerDriver(driverCtx) @@ -271,7 +277,7 @@ func TestDocker_StartNVersions(t *testing.T) { // Let's spin up a bunch of things var err error for idx, task := range taskList { - driverCtx := testDriverContext(task.Name) + driverCtx := testDockerDriverContext(task.Name) ctx := testDriverExecContext(task, driverCtx) defer ctx.AllocDir.Destroy() d := NewDockerDriver(driverCtx) @@ -309,7 +315,7 @@ func TestDockerHostNet(t *testing.T) { CPU: 512, }, } - driverCtx := testDriverContext(task.Name) + driverCtx := testDockerDriverContext(task.Name) ctx := testDriverExecContext(task, driverCtx) defer ctx.AllocDir.Destroy() d := NewDockerDriver(driverCtx) diff --git a/website/source/docs/drivers/docker.html.md b/website/source/docs/drivers/docker.html.md index 78ca25e45..c6ea40a77 100644 --- a/website/source/docs/drivers/docker.html.md +++ b/website/source/docs/drivers/docker.html.md @@ -111,11 +111,21 @@ The `docker` driver has the following configuration options: * `docker.endpoint` - Defaults to `unix:///var/run/docker.sock`. You will need to customize this if you use a non-standard socket (http or another location). +* `docker.cleanup.container` Defaults to `true`. Changing this to `false` will + prevent Nomad from removing containers from stopped tasks. + +* `docker.cleanup.image` Defaults to `true`. Changing this to `false` will + prevent Nomad from removing images from stopped tasks. + +Note: When testing or using the `-dev` flag you can use `DOCKER_HOST`, +`DOCKER_TLS_VERIFY`, and `DOCKER_CERT_PATH` to customize Nomad's behavior. In +production Nomad will always read `docker.endpoint`. + ## Client Attributes The `docker` driver will set the following client attributes: -* `driver.Docker` - This will be set to "1", indicating the +* `driver.docker` - This will be set to "1", indicating the driver is available. ## Resource Isolation