From 0e1aecb954e7cc940a429b5a97236bb33b4d97b4 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Thu, 13 Oct 2016 16:18:18 -0700 Subject: [PATCH 01/10] Fix standard mounts in rkt and tests --- client/driver/rkt.go | 25 ++++++++++++++++++++++--- client/driver/rkt_test.go | 6 +++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/client/driver/rkt.go b/client/driver/rkt.go index ac422ec6e365..5530cb4e570e 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -43,6 +43,10 @@ const ( // The key populated in the Node Attributes to indicate the presence of the // Rkt driver rktDriverAttr = "driver.rkt" + + // rktVolumesConfigOption is the key for enabling the use of custom + // bind volumes. + rktVolumesConfigOption = "rkt.volumes.enabled" ) // RktDriver is a driver for running images via Rkt @@ -211,15 +215,30 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e insecure = true } cmdArgs = append(cmdArgs, "run") - cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", task.Name, ctx.AllocDir.SharedDir)) - cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", task.Name, ctx.AllocDir.SharedDir)) + + // Mount /alloc + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%salloc,kind=host,source=%s", task.Name, ctx.AllocDir.SharedDir)) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%salloc,target=%s", task.Name, allocdir.SharedAllocContainerPath)) + + // Mount /local + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%slocal,kind=host,source=%s", task.Name, filepath.Join(taskDir, allocdir.TaskLocal))) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%slocal,target=%s", task.Name, allocdir.TaskLocalContainerPath)) + + // Mount /secrets + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%ssecrets,kind=host,source=%s", task.Name, filepath.Join(taskDir, allocdir.TaskSecrets))) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%ssecrets,target=/%s", task.Name, allocdir.TaskSecretsContainerPath)) + cmdArgs = append(cmdArgs, img) - if insecure == true { + if insecure { cmdArgs = append(cmdArgs, "--insecure-options=all") } cmdArgs = append(cmdArgs, fmt.Sprintf("--debug=%t", debug)) // Inject environment variables + d.taskEnv.SetAllocDir(allocdir.SharedAllocContainerPath) + d.taskEnv.SetTaskLocalDir(allocdir.TaskLocalContainerPath) + d.taskEnv.SetTaskLocalDir(allocdir.TaskSecretsContainerPath) + d.taskEnv.Build() for k, v := range d.taskEnv.EnvMap() { cmdArgs = append(cmdArgs, fmt.Sprintf("--set-env=%v=%v", k, v)) } diff --git a/client/driver/rkt_test.go b/client/driver/rkt_test.go index 6418320e36df..731f8f089280 100644 --- a/client/driver/rkt_test.go +++ b/client/driver/rkt_test.go @@ -171,7 +171,7 @@ func TestRktDriver_Start_Wait(t *testing.T) { if !res.Successful() { t.Fatalf("err: %v", res) } - case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): + case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second): t.Fatalf("timeout") } } @@ -223,7 +223,7 @@ func TestRktDriver_Start_Wait_Skip_Trust(t *testing.T) { if !res.Successful() { t.Fatalf("err: %v", res) } - case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): + case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second): t.Fatalf("timeout") } } @@ -276,7 +276,7 @@ func TestRktDriver_Start_Wait_AllocDir(t *testing.T) { if !res.Successful() { t.Fatalf("err: %v", res) } - case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): + case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second): t.Fatalf("timeout") } From a5ae80ad42c611dc458aa8369d6a106885d4882f Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Thu, 13 Oct 2016 16:34:47 -0700 Subject: [PATCH 02/10] Add arbitrary volume support to rkt --- client/driver/rkt.go | 19 ++++++++++++++++++- client/driver/rkt_test.go | 14 ++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 5530cb4e570e..159bd1ede040 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -65,6 +65,7 @@ type RktDriverConfig struct { DNSServers []string `mapstructure:"dns_servers"` // DNS Server for containers DNSSearchDomains []string `mapstructure:"dns_search_domains"` // DNS Search domains for containers Debug bool `mapstructure:"debug"` // Enable debug option for rkt command + Volumes []string `mapstructure:"volumes"` // Host-Volumes to mount in, syntax: /path/to/host/directory:/destination/path/in/container } // rktHandle is returned from Start/Open as a handle to the PID @@ -226,7 +227,23 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e // Mount /secrets cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%ssecrets,kind=host,source=%s", task.Name, filepath.Join(taskDir, allocdir.TaskSecrets))) - cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%ssecrets,target=/%s", task.Name, allocdir.TaskSecretsContainerPath)) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%ssecrets,target=%s", task.Name, allocdir.TaskSecretsContainerPath)) + + // Mount arbitrary volumes if enabled + if len(driverConfig.Volumes) > 0 { + if enabled := d.config.ReadBoolDefault(rktVolumesConfigOption, false); !enabled { + return nil, fmt.Errorf("%s is false; cannot use rkt volumes: %+q", rktVolumesConfigOption, driverConfig.Volumes) + } + + for i, rawvol := range driverConfig.Volumes { + parts := strings.Split(rawvol, ":") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid rkt volume: %q", rawvol) + } + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s%d,kind=host,source=%s", task.Name, i, parts[0])) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s%d,target=%s", task.Name, i, parts[1])) + } + } cmdArgs = append(cmdArgs, img) if insecure { diff --git a/client/driver/rkt_test.go b/client/driver/rkt_test.go index 731f8f089280..85f6330fbaae 100644 --- a/client/driver/rkt_test.go +++ b/client/driver/rkt_test.go @@ -12,7 +12,6 @@ import ( "time" "github.com/hashicorp/nomad/client/config" - "github.com/hashicorp/nomad/client/driver/env" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/testutil" @@ -237,6 +236,12 @@ func TestRktDriver_Start_Wait_AllocDir(t *testing.T) { exp := []byte{'w', 'i', 'n'} file := "output.txt" + tmpvol, err := ioutil.TempDir("", "nomadtest_dockerdriver_volumes") + if err != nil { + t.Fatalf("error creating temporary dir: %v", err) + } + defer os.RemoveAll(tmpvol) + hostpath := filepath.Join(tmpvol, file) task := &structs.Task{ Name: "alpine", @@ -245,8 +250,9 @@ func TestRktDriver_Start_Wait_AllocDir(t *testing.T) { "command": "/bin/sh", "args": []string{ "-c", - fmt.Sprintf(`echo -n %s > ${%s}/%s`, string(exp), env.AllocDir, file), + fmt.Sprintf(`echo -n %s > foo/%s`, string(exp), file), }, + "volumes": []string{fmt.Sprintf("%s:/foo", tmpvol)}, }, LogConfig: &structs.LogConfig{ MaxFiles: 10, @@ -259,6 +265,7 @@ func TestRktDriver_Start_Wait_AllocDir(t *testing.T) { } driverCtx, execCtx := testDriverContexts(task) + driverCtx.config.Options = map[string]string{rktVolumesConfigOption: "1"} defer execCtx.AllocDir.Destroy() d := NewRktDriver(driverCtx) @@ -281,8 +288,7 @@ func TestRktDriver_Start_Wait_AllocDir(t *testing.T) { } // Check that data was written to the shared alloc directory. - outputFile := filepath.Join(execCtx.AllocDir.SharedDir, file) - act, err := ioutil.ReadFile(outputFile) + act, err := ioutil.ReadFile(hostpath) if err != nil { t.Fatalf("Couldn't read expected output: %v", err) } From 0cae8ddd2e910b307dca4ceb472826be277aed81 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Thu, 13 Oct 2016 16:38:31 -0700 Subject: [PATCH 03/10] Fix docker reference in rkt test --- client/driver/rkt_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/driver/rkt_test.go b/client/driver/rkt_test.go index 85f6330fbaae..c6252c20e818 100644 --- a/client/driver/rkt_test.go +++ b/client/driver/rkt_test.go @@ -236,7 +236,7 @@ func TestRktDriver_Start_Wait_AllocDir(t *testing.T) { exp := []byte{'w', 'i', 'n'} file := "output.txt" - tmpvol, err := ioutil.TempDir("", "nomadtest_dockerdriver_volumes") + tmpvol, err := ioutil.TempDir("", "nomadtest_rktdriver_volumes") if err != nil { t.Fatalf("error creating temporary dir: %v", err) } From be542758906a91a3539e25b56dc2daf5594d5867 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 18 Oct 2016 16:58:25 -0700 Subject: [PATCH 04/10] Update rkt script and test rkt in travis --- .travis.yml | 1 + Vagrantfile | 1 + scripts/install_rkt.sh | 24 +++++++++++------------- scripts/install_rkt_vagrant.sh | 8 ++++++++ scripts/test.sh | 2 +- 5 files changed, 22 insertions(+), 14 deletions(-) create mode 100755 scripts/install_rkt_vagrant.sh diff --git a/.travis.yml b/.travis.yml index f03d4f361fa3..ab5715648670 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ before_install: - sudo apt-get update - sudo apt-get install -y docker-engine - sudo apt-get install -y qemu + - ./scripts/install_rkt.sh - ./scripts/install_consul.sh - ./scripts/install_vault.sh diff --git a/Vagrantfile b/Vagrantfile index 36853892bdd0..cf9f451e9995 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -60,6 +60,7 @@ cd /opt/gopath/src/github.com/hashicorp/nomad && make bootstrap # Install rkt, consul and vault bash scripts/install_rkt.sh +bash scripts/install_rkt_vagrant.sh bash scripts/install_consul.sh bash scripts/install_vault.sh diff --git a/scripts/install_rkt.sh b/scripts/install_rkt.sh index 9c3b60c1d603..22df0a5cf5ca 100755 --- a/scripts/install_rkt.sh +++ b/scripts/install_rkt.sh @@ -2,29 +2,27 @@ set -ex -RKT_VERSION="v1.5.1" -RKT_SHA512="8163ca59fc8c44c9c2997431d16274d81d2e82ff2956c860607f4c111de744b78cdce716f8afbacf7173e0cdce25deac73ec95a30a8849bbf58d35faeb84e398" -DEST_DIR="/usr/local/bin" +RKT_VERSION="v1.17.0" +RKT_SHA512="30fd15716e148afa34ed28e6d5d778226e5e9761e9df3eb98f397cb2a7f3e3fc78e3dad2b717eee4157afc58183778cb1872aa82f3d05cc2bc9fb41193e81a7f" +CMD="cp" -sudo mkdir -p /etc/rkt/net.d -echo '{"name": "default", "type": "ptp", "ipMasq": false, "ipam": { "type": "host-local", "subnet": "172.16.28.0/24", "routes": [ { "dst": "0.0.0.0/0" } ] } }' | sudo tee -a /etc/rkt/net.d/99-network.conf +if [ ! -v DEST_DIR ]; then + DEST_DIR="/usr/local/bin" + CMD="sudo cp" +fi if [ ! -d "rkt-${RKT_VERSION}" ]; then printf "rkt-%s/ doesn't exist\n" "${RKT_VERSION}" if [ ! -f "rkt-${RKT_VERSION}.tar.gz" ]; then printf "Fetching rkt-%s.tar.gz\n" "${RKT_VERSION}" + echo "$RKT_SHA512 rkt-${RKT_VERSION}.tar.gz" > rkt-$RKT_VERSION.tar.gz.sha512sum wget https://github.com/coreos/rkt/releases/download/$RKT_VERSION/rkt-$RKT_VERSION.tar.gz - expected_version=$(printf 'SHA512(rkt-%s.tar.gz)= %s' "${RKT_VERSION}" "${RKT_SHA512}") - actual_version=$(openssl sha512 rkt-${RKT_VERSION}.tar.gz) - if [ "${expected_version}" != "${actual_version}" ]; then - printf "SHA512 of rkt-%s failed\n" "${RKT_VERSION}" - exit 1 - fi + sha512sum --check rkt-$RKT_VERSION.tar.gz.sha512sum tar xzvf rkt-$RKT_VERSION.tar.gz fi fi -sudo cp rkt-$RKT_VERSION/rkt $DEST_DIR -sudo cp rkt-$RKT_VERSION/*.aci $DEST_DIR +$CMD rkt-$RKT_VERSION/rkt $DEST_DIR +$CMD rkt-$RKT_VERSION/*.aci $DEST_DIR rkt version diff --git a/scripts/install_rkt_vagrant.sh b/scripts/install_rkt_vagrant.sh new file mode 100755 index 000000000000..679b6e153041 --- /dev/null +++ b/scripts/install_rkt_vagrant.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -ex + +# Configure rkt networking +sudo mkdir -p /etc/rkt/net.d +echo '{"name": "default", "type": "ptp", "ipMasq": false, "ipam": { "type": "host-local", "subnet": "172.16.28.0/24", "routes": [ { "dst": "0.0.0.0/0" } ] } }' | sudo tee -a /etc/rkt/net.d/99-network.conf + diff --git a/scripts/test.sh b/scripts/test.sh index d7058fdc1d33..f2baeba241fa 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -12,6 +12,6 @@ go build -tags "nomad_test" -o $TEMPDIR/nomad || exit 1 # Run the tests echo "--> Running tests" GOBIN="`which go`" -sudo -E PATH=$TEMPDIR:$PATH -E GOPATH=$GOPATH \ +sudo -E PATH=$TEMPDIR:$PATH -E GOPATH=$GOPATH -E NOMAD_TEST_RKT=1 \ $GOBIN test -tags "nomad_test" ${GOTEST_FLAGS:--cover -timeout=900s} $($GOBIN list ./... | grep -v /vendor/) From 524b96eda9b7a579294bbdc85129640de656271e Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 18 Oct 2016 17:36:19 -0700 Subject: [PATCH 05/10] Bump minimum required rkt version; update docs Make section names match between docker and rkt --- CHANGELOG.md | 1 + client/driver/rkt.go | 13 ++++++---- website/source/docs/drivers/docker.html.md | 6 ++--- website/source/docs/drivers/rkt.html.md | 30 ++++++++++++++++------ 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f59547cbbd05..c7a41b484576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ IMPROVEMENTS: * driver: Export `NOMAD_JOB_NAME` environment variable [GH-1804] * driver/docker: Support Docker volumes [GH-1767] * driver/docker: Allow Docker logging to be configured [GH-1767] + * driver/rkt: Support rkt volumes (rkt >= 1.0.0 required) [GH-1812] BUG FIXES: * agent: Handle the SIGPIPE signal preventing panics on journalctl restarts diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 159bd1ede040..499102b88b5a 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -38,7 +38,7 @@ const ( // minRktVersion is the earliest supported version of rkt. rkt added support // for CPU and memory isolators in 0.14.0. We cannot support an earlier // version to maintain an uniform interface across all drivers - minRktVersion = "0.14.0" + minRktVersion = "1.0.0" // The key populated in the Node Attributes to indicate the presence of the // Rkt driver @@ -47,6 +47,9 @@ const ( // rktVolumesConfigOption is the key for enabling the use of custom // bind volumes. rktVolumesConfigOption = "rkt.volumes.enabled" + + // rktCmd is the command rkt is installed as. + rktCmd = "rkt" ) // RktDriver is a driver for running images via Rkt @@ -147,7 +150,7 @@ func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, e return false, nil } - outBytes, err := exec.Command("rkt", "version").Output() + outBytes, err := exec.Command(rktCmd, "version").Output() if err != nil { delete(node.Attributes, rktDriverAttr) return false, nil @@ -168,7 +171,7 @@ func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, e minVersion, _ := version.NewVersion(minRktVersion) currentVersion, _ := version.NewVersion(node.Attributes["driver.rkt.version"]) if currentVersion.LessThan(minVersion) { - // Do not allow rkt < 0.14.0 + // Do not allow ancient rkt versions d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion) node.Attributes[rktDriverAttr] = "0" } @@ -203,7 +206,7 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e insecure := false if trustPrefix != "" { var outBuf, errBuf bytes.Buffer - cmd := exec.Command("rkt", "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix), fmt.Sprintf("--debug=%t", debug)) + cmd := exec.Command(rktCmd, "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix), fmt.Sprintf("--debug=%t", debug)) cmd.Stdout = &outBuf cmd.Stderr = &errBuf if err := cmd.Run(); err != nil { @@ -331,7 +334,7 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e return nil, fmt.Errorf("failed to set executor context: %v", err) } - absPath, err := GetAbsolutePath("rkt") + absPath, err := GetAbsolutePath(rktCmd) if err != nil { return nil, err } diff --git a/website/source/docs/drivers/docker.html.md b/website/source/docs/drivers/docker.html.md index 0b05dabbdd1b..fb4635812156 100644 --- a/website/source/docs/drivers/docker.html.md +++ b/website/source/docs/drivers/docker.html.md @@ -315,7 +315,7 @@ Some networking modes like `container` or `none` will require coordination outside of Nomad. First-class support for these options may be improved later through Nomad plugins or dynamic job configuration. -## Host Requirements +## Client Requirements Nomad requires Docker to be installed and running on the host alongside the Nomad agent. Nomad was developed against Docker `1.8.2` and `1.9`. @@ -333,7 +333,7 @@ user to the `docker` group so you can run Nomad without root: For the best performance and security features you should use recent versions of the Linux Kernel and Docker daemon. -## Agent Configuration +## Client Configuration The `docker` driver has the following [client configuration options](/docs/agent/config.html#options): @@ -392,7 +392,7 @@ client { } ``` -## Agent Attributes +## Client Attributes The `docker` driver will set the following client attributes: diff --git a/website/source/docs/drivers/rkt.html.md b/website/source/docs/drivers/rkt.html.md index 6f83e636a489..b721f3b18eff 100644 --- a/website/source/docs/drivers/rkt.html.md +++ b/website/source/docs/drivers/rkt.html.md @@ -75,10 +75,15 @@ The `rkt` driver supports the following configuration in the job spec: * `debug` - (Optional) Enable rkt command debug option. -## Task Directories +* `volumes` - (Optional) A list of `host_path:container_path` strings to bind + host paths to container paths. Can only be run on clients with the + `rkt.volumes.enabled` option set to true. -The `rkt` driver currently does not support mounting of the `alloc/` and `local/` directories. -Once support is added, version `v0.10.0` or above of `rkt` will be required. + ```hcl + config { + volumes = ["/path/on/host:/path/in/container"] + } + ``` ## Client Requirements @@ -87,15 +92,24 @@ The `trust_prefix` must be accessible by the node running Nomad. This can be an internal source, private to your cluster, but it must be reachable by the client over HTTP. +## Client Configuration + +The `rkt` driver has the following [client configuration +options](/docs/agent/config.html#options): + +* `rkt.volumes.enabled`: Defaults to `false`. Allows tasks to bind host paths + (`volumes`) inside their container. Disabled by default as it removes the + isolation between containers' data. + ## Client Attributes The `rkt` driver will set the following client attributes: * `driver.rkt` - Set to `1` if rkt is found on the host node. Nomad determines -this by executing `rkt version` on the host and parsing the output -* `driver.rkt.version` - Version of `rkt` eg: `0.8.1`. Note that the minimum required -version is `0.14.0` -* `driver.rkt.appc.version` - Version of `appc` that `rkt` is using eg: `0.8.1` + this by executing `rkt version` on the host and parsing the output +* `driver.rkt.version` - Version of `rkt` eg: `1.1.0`. Note that the minimum required + version is `1.0.0` +* `driver.rkt.appc.version` - Version of `appc` that `rkt` is using eg: `1.1.0` Here is an example of using these properties in a job file: @@ -105,7 +119,7 @@ job "docs" { constraint { attribute = "${driver.rkt.version}" operator = ">" - value = "0.8" + value = "1.2" } } ``` From ac2ba99f0cf9f76aedfc67e39cd2dce2aaffa973 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Fri, 21 Oct 2016 15:58:35 -0700 Subject: [PATCH 06/10] Make volume name unique --- client/driver/rkt.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 499102b88b5a..4938d37eacfb 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -221,16 +221,19 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e cmdArgs = append(cmdArgs, "run") // Mount /alloc - cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%salloc,kind=host,source=%s", task.Name, ctx.AllocDir.SharedDir)) - cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%salloc,target=%s", task.Name, allocdir.SharedAllocContainerPath)) + allocVolName := fmt.Sprintf("%s-%s-alloc", ctx.AllocID, task.Name) + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", allocVolName, ctx.AllocDir.SharedDir)) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", allocVolName, allocdir.SharedAllocContainerPath)) // Mount /local - cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%slocal,kind=host,source=%s", task.Name, filepath.Join(taskDir, allocdir.TaskLocal))) - cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%slocal,target=%s", task.Name, allocdir.TaskLocalContainerPath)) + localVolName := fmt.Sprintf("%s-%s-local", ctx.AllocID, task.Name) + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", localVolName, filepath.Join(taskDir, allocdir.TaskLocal))) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", localVolName, allocdir.TaskLocalContainerPath)) // Mount /secrets - cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%ssecrets,kind=host,source=%s", task.Name, filepath.Join(taskDir, allocdir.TaskSecrets))) - cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%ssecrets,target=%s", task.Name, allocdir.TaskSecretsContainerPath)) + secretsVolName := fmt.Sprintf("%s-%s-secrets", ctx.AllocID, task.Name) + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", secretsVolName, filepath.Join(taskDir, allocdir.TaskSecrets))) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", secretsVolName, allocdir.TaskSecretsContainerPath)) // Mount arbitrary volumes if enabled if len(driverConfig.Volumes) > 0 { @@ -243,8 +246,9 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e if len(parts) != 2 { return nil, fmt.Errorf("invalid rkt volume: %q", rawvol) } - cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s%d,kind=host,source=%s", task.Name, i, parts[0])) - cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s%d,target=%s", task.Name, i, parts[1])) + volName := fmt.Sprintf("%s-%s-%d", ctx.AllocID, task.Name, i) + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", volName, i, parts[0])) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, i, parts[1])) } } From 68b74720c78956ccb9a3fefd72f8a42af9d5e8b2 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Wed, 19 Oct 2016 17:13:45 -0700 Subject: [PATCH 07/10] Enable rkt and docker volume mounting by default --- client/driver/docker.go | 7 ++++--- client/driver/docker_test.go | 2 +- client/driver/rkt.go | 5 +++-- client/driver/rkt_test.go | 1 - website/source/docs/drivers/docker.html.md | 9 ++++----- website/source/docs/drivers/rkt.html.md | 9 ++++----- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/client/driver/docker.go b/client/driver/docker.go index 19902d556c7d..5e02e34c535a 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -65,7 +65,8 @@ const ( // dockerVolumesConfigOption is the key for enabling the use of custom // bind volumes. - dockerVolumesConfigOption = "docker.volumes.enabled" + dockerVolumesConfigOption = "docker.volumes.enabled" + dockerVolumesConfigDefault = true // dockerPrivilegedConfigOption is the key for running containers in // Docker's privileged mode. @@ -370,7 +371,7 @@ func (d *DockerDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool node.Attributes["driver.docker.version"] = env.Get("Version") // Advertise if this node supports Docker volumes (by default we do not) - if d.config.ReadBoolDefault(dockerVolumesConfigOption, false) { + if d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) { node.Attributes["driver."+dockerVolumesConfigOption] = "1" } @@ -397,7 +398,7 @@ func (d *DockerDriver) containerBinds(driverConfig *DockerDriverConfig, alloc *a secretDirBind := fmt.Sprintf("%s:%s", secret, allocdir.TaskSecretsContainerPath) binds := []string{allocDirBind, taskLocalBind, secretDirBind} - volumesEnabled := d.config.ReadBoolDefault(dockerVolumesConfigOption, false) + volumesEnabled := d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) if len(driverConfig.Volumes) > 0 && !volumesEnabled { return nil, fmt.Errorf("%s is false; cannot use Docker Volumes: %+q", dockerVolumesConfigOption, driverConfig.Volumes) } diff --git a/client/driver/docker_test.go b/client/driver/docker_test.go index be70f9cc5084..0dabfcf8f5f6 100644 --- a/client/driver/docker_test.go +++ b/client/driver/docker_test.go @@ -998,6 +998,7 @@ func setupDockerVolumes(t *testing.T, cfg *config.Config) (*structs.Task, Driver func TestDockerDriver_VolumesDisabled(t *testing.T) { cfg := testConfig() + cfg.Options = map[string]string{dockerVolumesConfigOption: "false"} task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg) defer cleanup() @@ -1010,7 +1011,6 @@ func TestDockerDriver_VolumesDisabled(t *testing.T) { func TestDockerDriver_VolumesEnabled(t *testing.T) { cfg := testConfig() - cfg.Options = map[string]string{dockerVolumesConfigOption: "true"} task, driver, execCtx, hostpath, cleanup := setupDockerVolumes(t, cfg) defer cleanup() diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 4938d37eacfb..40908c206701 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -46,7 +46,8 @@ const ( // rktVolumesConfigOption is the key for enabling the use of custom // bind volumes. - rktVolumesConfigOption = "rkt.volumes.enabled" + rktVolumesConfigOption = "rkt.volumes.enabled" + rktVolumesConfigDefault = true // rktCmd is the command rkt is installed as. rktCmd = "rkt" @@ -237,7 +238,7 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e // Mount arbitrary volumes if enabled if len(driverConfig.Volumes) > 0 { - if enabled := d.config.ReadBoolDefault(rktVolumesConfigOption, false); !enabled { + if enabled := d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault); !enabled { return nil, fmt.Errorf("%s is false; cannot use rkt volumes: %+q", rktVolumesConfigOption, driverConfig.Volumes) } diff --git a/client/driver/rkt_test.go b/client/driver/rkt_test.go index c6252c20e818..36a494c83f99 100644 --- a/client/driver/rkt_test.go +++ b/client/driver/rkt_test.go @@ -265,7 +265,6 @@ func TestRktDriver_Start_Wait_AllocDir(t *testing.T) { } driverCtx, execCtx := testDriverContexts(task) - driverCtx.config.Options = map[string]string{rktVolumesConfigOption: "1"} defer execCtx.AllocDir.Destroy() d := NewRktDriver(driverCtx) diff --git a/website/source/docs/drivers/docker.html.md b/website/source/docs/drivers/docker.html.md index fb4635812156..619a783b2ea1 100644 --- a/website/source/docs/drivers/docker.html.md +++ b/website/source/docs/drivers/docker.html.md @@ -162,8 +162,8 @@ The `docker` driver supports the following configuration in the job spec: ``` * `volumes` - (Optional) A list of `host_path:container_path` strings to bind - host paths to container paths. Can only be run on clients with the - `docker.volumes.enabled` option set to true. + host paths to container paths. Can be disabled on clients by setting the + `docker.volumes.enabled` option set to false. ```hcl config { @@ -363,9 +363,8 @@ options](/docs/agent/config.html#options): * `docker.cleanup.image` Defaults to `true`. Changing this to `false` will prevent Nomad from removing images from stopped tasks. -* `docker.volumes.enabled`: Defaults to `false`. Allows tasks to bind host - paths (`volumes`) or other containers (`volums_from`) inside their container. - Disabled by default as it removes the isolation between containers' data. +* `docker.volumes.enabled`: Defaults to `true`. Allows tasks to bind host paths + (`volumes`) inside their container. * `docker.volumes.selinuxlabel`: Allows the operator to set a SELinux label to the allocation and task local bind-mounts to containers. If used diff --git a/website/source/docs/drivers/rkt.html.md b/website/source/docs/drivers/rkt.html.md index b721f3b18eff..82a4d75396d1 100644 --- a/website/source/docs/drivers/rkt.html.md +++ b/website/source/docs/drivers/rkt.html.md @@ -76,8 +76,8 @@ The `rkt` driver supports the following configuration in the job spec: * `debug` - (Optional) Enable rkt command debug option. * `volumes` - (Optional) A list of `host_path:container_path` strings to bind - host paths to container paths. Can only be run on clients with the - `rkt.volumes.enabled` option set to true. + host paths to container paths. Can be disabled on clients by setting the + `rkt.volumes.enabled` option set to false. ```hcl config { @@ -97,9 +97,8 @@ over HTTP. The `rkt` driver has the following [client configuration options](/docs/agent/config.html#options): -* `rkt.volumes.enabled`: Defaults to `false`. Allows tasks to bind host paths - (`volumes`) inside their container. Disabled by default as it removes the - isolation between containers' data. +* `rkt.volumes.enabled`: Defaults to `true`. Allows tasks to bind host paths + (`volumes`) inside their container. ## Client Attributes From 0696e633c6b262b89634909994cd85e83320a6ba Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Thu, 20 Oct 2016 14:00:27 -0700 Subject: [PATCH 08/10] Allow mounting alloc-dir-relative paths in docker --- client/driver/docker.go | 29 ++++++++++++--- client/driver/docker_test.go | 71 ++++++++++++++++++++++++++---------- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/client/driver/docker.go b/client/driver/docker.go index 5e02e34c535a..bafde0517188 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -64,7 +64,7 @@ const ( dockerSELinuxLabelConfigOption = "docker.volumes.selinuxlabel" // dockerVolumesConfigOption is the key for enabling the use of custom - // bind volumes. + // bind volumes to arbitrary host paths. dockerVolumesConfigOption = "docker.volumes.enabled" dockerVolumesConfigDefault = true @@ -399,12 +399,29 @@ func (d *DockerDriver) containerBinds(driverConfig *DockerDriverConfig, alloc *a binds := []string{allocDirBind, taskLocalBind, secretDirBind} volumesEnabled := d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) - if len(driverConfig.Volumes) > 0 && !volumesEnabled { - return nil, fmt.Errorf("%s is false; cannot use Docker Volumes: %+q", dockerVolumesConfigOption, driverConfig.Volumes) - } + for _, userbind := range driverConfig.Volumes { + parts := strings.Split(userbind, ":") + if len(parts) < 2 { + return nil, fmt.Errorf("invalid docker volume: %q", userbind) + } + + // Resolve dotted path segments + parts[0] = filepath.Clean(parts[0]) + + // Absolute paths aren't always supported + if filepath.IsAbs(parts[0]) { + if !volumesEnabled { + // Disallow mounting arbitrary absolute paths + return nil, fmt.Errorf("%s is false; cannot mount host paths: %+q", dockerVolumesConfigOption, userbind) + } + binds = append(binds, userbind) + continue + } - if len(driverConfig.Volumes) > 0 { - binds = append(binds, driverConfig.Volumes...) + // Relative paths are always allowed as they mount within a container + // Expand path relative to alloc dir + parts[0] = filepath.Join(shared, parts[0]) + binds = append(binds, strings.Join(parts, ":")) } if selinuxLabel := d.config.Read(dockerSELinuxLabelConfigOption); selinuxLabel != "" { diff --git a/client/driver/docker_test.go b/client/driver/docker_test.go index 0dabfcf8f5f6..4c02905d45f1 100644 --- a/client/driver/docker_test.go +++ b/client/driver/docker_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "math/rand" "os" - "path" "path/filepath" "reflect" "runtime/debug" @@ -944,19 +943,14 @@ done } } -func setupDockerVolumes(t *testing.T, cfg *config.Config) (*structs.Task, Driver, *ExecContext, string, func()) { +func setupDockerVolumes(t *testing.T, cfg *config.Config, hostpath string) (*structs.Task, Driver, *ExecContext, string, func()) { if !testutil.DockerIsConnected(t) { t.SkipNow() } - tmpvol, err := ioutil.TempDir("", "nomadtest_dockerdriver_volumes") - if err != nil { - t.Fatalf("error creating temporary dir: %v", err) - } - randfn := fmt.Sprintf("test-%d", rand.Int()) - hostpath := path.Join(tmpvol, randfn) - contpath := path.Join("/mnt/vol", randfn) + hostfile := filepath.Join(hostpath, randfn) + containerFile := filepath.Join("/mnt/vol", randfn) task := &structs.Task{ Name: "ls", @@ -964,8 +958,8 @@ func setupDockerVolumes(t *testing.T, cfg *config.Config) (*structs.Task, Driver "image": "busybox", "load": []string{"busybox.tar"}, "command": "touch", - "args": []string{contpath}, - "volumes": []string{fmt.Sprintf("%s:/mnt/vol", tmpvol)}, + "args": []string{containerFile}, + "volumes": []string{fmt.Sprintf("%s:/mnt/vol", hostpath)}, }, LogConfig: &structs.LogConfig{ MaxFiles: 10, @@ -980,7 +974,9 @@ func setupDockerVolumes(t *testing.T, cfg *config.Config) (*structs.Task, Driver execCtx := NewExecContext(allocDir, alloc.ID) cleanup := func() { execCtx.AllocDir.Destroy() - os.RemoveAll(tmpvol) + if filepath.IsAbs(hostpath) { + os.RemoveAll(hostpath) + } } taskEnv, err := GetTaskEnv(allocDir, cfg.Node, task, alloc, "") @@ -993,26 +989,63 @@ func setupDockerVolumes(t *testing.T, cfg *config.Config) (*structs.Task, Driver driver := NewDockerDriver(driverCtx) copyImage(execCtx, task, "busybox.tar", t) - return task, driver, execCtx, hostpath, cleanup + return task, driver, execCtx, hostfile, cleanup } func TestDockerDriver_VolumesDisabled(t *testing.T) { cfg := testConfig() cfg.Options = map[string]string{dockerVolumesConfigOption: "false"} - task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg) - defer cleanup() + { + tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesdisabled") + if err != nil { + t.Fatalf("error creating temporary dir: %v", err) + } - _, err := driver.Start(execCtx, task) - if err == nil { - t.Fatalf("Started driver successfully when volumes should have been disabled.") + task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg, tmpvol) + defer cleanup() + + if _, err := driver.Start(execCtx, task); err == nil { + t.Fatalf("Started driver successfully when volumes should have been disabled.") + } } + + // Relative paths should still be allowed + { + task, driver, execCtx, fn, cleanup := setupDockerVolumes(t, cfg, ".") + defer cleanup() + + handle, err := driver.Start(execCtx, task) + if err != nil { + t.Fatalf("err: %v", err) + } + defer handle.Kill() + + select { + case res := <-handle.WaitCh(): + if !res.Successful() { + t.Fatalf("unexpected err: %v", res) + } + case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): + t.Fatalf("timeout") + } + + if _, err := ioutil.ReadFile(filepath.Join(execCtx.AllocDir.SharedDir, fn)); err != nil { + t.Fatalf("unexpected error reading %s: %v", fn, err) + } + } + } func TestDockerDriver_VolumesEnabled(t *testing.T) { cfg := testConfig() - task, driver, execCtx, hostpath, cleanup := setupDockerVolumes(t, cfg) + tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesenabled") + if err != nil { + t.Fatalf("error creating temporary dir: %v", err) + } + + task, driver, execCtx, hostpath, cleanup := setupDockerVolumes(t, cfg, tmpvol) defer cleanup() handle, err := driver.Start(execCtx, task) From eb2f333e4e1b0da76c462c36d0c723ee1d4bb48f Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Fri, 21 Oct 2016 10:34:01 -0700 Subject: [PATCH 09/10] Document relative vs absolute volume paths --- website/source/docs/drivers/docker.html.md | 16 ++++++++++++---- website/source/docs/drivers/rkt.html.md | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/website/source/docs/drivers/docker.html.md b/website/source/docs/drivers/docker.html.md index 619a783b2ea1..2372cb4ea038 100644 --- a/website/source/docs/drivers/docker.html.md +++ b/website/source/docs/drivers/docker.html.md @@ -162,12 +162,19 @@ The `docker` driver supports the following configuration in the job spec: ``` * `volumes` - (Optional) A list of `host_path:container_path` strings to bind - host paths to container paths. Can be disabled on clients by setting the - `docker.volumes.enabled` option set to false. + host paths to container paths. Mounting host paths outside of the alloc + directory tasks normally have access to can be disabled on clients by setting + the `docker.volumes.enabled` option set to false. ```hcl config { - volumes = ["/path/on/host:/path/in/container"] + volumes = [ + # Use absolute paths to mount arbitrary paths on the host + "/path/on/host:/path/in/container", + + # Use relative paths to rebind paths already in the allocation dir + "relative/to/alloc:/also/in/container" + ] } ``` @@ -364,7 +371,8 @@ options](/docs/agent/config.html#options): prevent Nomad from removing images from stopped tasks. * `docker.volumes.enabled`: Defaults to `true`. Allows tasks to bind host paths - (`volumes`) inside their container. + (`volumes`) inside their container. Binding relative paths is always allowed + and will be resolved relative to the allocation's directory. * `docker.volumes.selinuxlabel`: Allows the operator to set a SELinux label to the allocation and task local bind-mounts to containers. If used diff --git a/website/source/docs/drivers/rkt.html.md b/website/source/docs/drivers/rkt.html.md index 82a4d75396d1..c82094baf7da 100644 --- a/website/source/docs/drivers/rkt.html.md +++ b/website/source/docs/drivers/rkt.html.md @@ -76,12 +76,19 @@ The `rkt` driver supports the following configuration in the job spec: * `debug` - (Optional) Enable rkt command debug option. * `volumes` - (Optional) A list of `host_path:container_path` strings to bind - host paths to container paths. Can be disabled on clients by setting the - `rkt.volumes.enabled` option set to false. + host paths to container paths. Mounting host paths outside of the alloc + directory tasks normally have access to can be disabled on clients by setting + the `rkt.volumes.enabled` option set to false. ```hcl config { - volumes = ["/path/on/host:/path/in/container"] + volumes = [ + # Use absolute paths to mount arbitrary paths on the host + "/path/on/host:/path/in/container", + + # Use relative paths to rebind paths already in the allocation dir + "relative/to/alloc:/also/in/container" + ] } ``` @@ -98,7 +105,9 @@ The `rkt` driver has the following [client configuration options](/docs/agent/config.html#options): * `rkt.volumes.enabled`: Defaults to `true`. Allows tasks to bind host paths - (`volumes`) inside their container. + (`volumes`) inside their container. Binding relative paths is always allowed + and will be resolved relative to the allocation's directory. + ## Client Attributes From 535757952c49d54b5782cb56c7ab6453d1386b39 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Mon, 24 Oct 2016 16:00:19 -0700 Subject: [PATCH 10/10] Fingerprint rkt volume support and make periodic Fix rkt docs and custom volume mounting --- client/driver/docker.go | 2 +- client/driver/rkt.go | 15 +++++++++++---- website/source/docs/drivers/rkt.html.md | 12 ++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/client/driver/docker.go b/client/driver/docker.go index bafde0517188..5e64cd8c6896 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -370,7 +370,7 @@ func (d *DockerDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool node.Attributes[dockerDriverAttr] = "1" node.Attributes["driver.docker.version"] = env.Get("Version") - // Advertise if this node supports Docker volumes (by default we do not) + // Advertise if this node supports Docker volumes if d.config.ReadBoolDefault(dockerVolumesConfigOption, dockerVolumesConfigDefault) { node.Attributes["driver."+dockerVolumesConfigOption] = "1" } diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 40908c206701..735ff20ae7ad 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -21,7 +21,6 @@ import ( "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/driver/executor" dstructs "github.com/hashicorp/nomad/client/driver/structs" - "github.com/hashicorp/nomad/client/fingerprint" cstructs "github.com/hashicorp/nomad/client/structs" "github.com/hashicorp/nomad/helper/discover" "github.com/hashicorp/nomad/helper/fields" @@ -58,7 +57,6 @@ const ( // planned in the future type RktDriver struct { DriverContext - fingerprint.StaticFingerprinter } type RktDriverConfig struct { @@ -176,9 +174,18 @@ func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, e d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion) node.Attributes[rktDriverAttr] = "0" } + + // Advertise if this node supports rkt volumes + if d.config.ReadBoolDefault(rktVolumesConfigOption, rktVolumesConfigDefault) { + node.Attributes["driver."+rktVolumesConfigOption] = "1" + } return true, nil } +func (d *RktDriver) Periodic() (bool, time.Duration) { + return true, 15 * time.Second +} + // Run an existing Rkt image. func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) { var driverConfig RktDriverConfig @@ -248,8 +255,8 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e return nil, fmt.Errorf("invalid rkt volume: %q", rawvol) } volName := fmt.Sprintf("%s-%s-%d", ctx.AllocID, task.Name, i) - cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", volName, i, parts[0])) - cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, i, parts[1])) + cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", volName, parts[0])) + cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", volName, parts[1])) } } diff --git a/website/source/docs/drivers/rkt.html.md b/website/source/docs/drivers/rkt.html.md index c82094baf7da..2056c76bcfa2 100644 --- a/website/source/docs/drivers/rkt.html.md +++ b/website/source/docs/drivers/rkt.html.md @@ -76,19 +76,11 @@ The `rkt` driver supports the following configuration in the job spec: * `debug` - (Optional) Enable rkt command debug option. * `volumes` - (Optional) A list of `host_path:container_path` strings to bind - host paths to container paths. Mounting host paths outside of the alloc - directory tasks normally have access to can be disabled on clients by setting - the `rkt.volumes.enabled` option set to false. + host paths to container paths. ```hcl config { - volumes = [ - # Use absolute paths to mount arbitrary paths on the host - "/path/on/host:/path/in/container", - - # Use relative paths to rebind paths already in the allocation dir - "relative/to/alloc:/also/in/container" - ] + volumes = ["/path/on/host:/path/in/container"] } ```