7

I'm migrating an existing application from running on bare EC2 instances to a containerized setup with ECS. I have two situations where I need to share data between containers. One is an EFS share that stores some static and media files, the other is a log directory so that I can use the Cloudwatch Logs agent in a sidecar container to push logs to Cloudwatch.

The webserver needs to be able to write data to both of these locations, but so far I haven't been able to make it happen. The logs directory is a local volume, and is owned by root:root. The EFS share which I set up following these instruction, is owned by 1000:xfs (no username). In neither case can my web user (www-data) write to these locations.

How can I tell ECS to mount volumes inside containers with a given owner and/or group?

Joseph Montanaro
  • 518
  • 4
  • 13

1 Answers1

3

EDIT: Turns out this was a lot simpler than I was making it.

When you go to create a volume on ECS, it asks you for a name for the volume, and a "source path". When pressed for explanation it will specify that the source path is "The path on the host container instance that is presented to the container for this volume. If omitted, then the Docker daemon assigns a host path for you."

All very well and good, but it turns out that the difference is more than just "specifying a directory" vs "Docker picking a directory for you." This is the difference between a docker volume and a bind mount, and in fact if you docker inspect the container you will see that volumes for which you give ECS a "source path" get "Type": "bind", whereas volumes that don't specify get "Type": "volume".

One key difference between bind mounts and volumes is that while bind mounts inherit their ownership from the host filesystem, volumes inherit their ownership from the container filesystem. So the incredibly, frustratingly simple solution to my problem is just to make sure the directory exists in the image with the proper ownership, then create the volume in ECS without specifying a source path.

Incidentally, if your application involves multiple containers sharing the same volume, the volume will derive its permissions from the existing directory structure on whichever container gets up and running first. So you need to make sure that either a) the directory exists on all containers where the volume will be mounted, or b) the container that does have the directory in question is always launched first.

I will leave my original solution below in case it's ever useful to anybody.

Original solution 1: tmpfs mounts

Docker volumes accept a driver_opts parameter which works similarly to the mount command on Linux systems. So one option is to use a tmpfs mount, which allows for options that set the owner and group of the resulting files. On ECS, this can be accomplished thusly:

{
  "name": "myvolume",
  "dockerVolumeConfiguration": {
    "scope": "task",
    "driver": "local",
    "driverOpts": {
      "type": "tmpfs",
      "device": "tmpfs",
      "o": "uid=1000,gid=1000"
    }
  }
}

This will create a volume owned by user and group 1000 within the container.

The downside of this method is that, being tmpfs, it stores files in the host memory. Depending on your use case, this may or may not be acceptable - for me it's not ideal, because I need to store log files which can grow quite large.

(Note that the type and device parameters under driverOpts here are equivalent to the type and device parameters for the Linux mount command. This took me quite some time and frustration to figure out.)

Original solution 2: Matching UID over NFS

NFS simply stores the owner/group of a file as a numeric id. The reason the group was showing up as xfs for me was because as part of my redeployment, I'm moving from Ubuntu to Alpine. In both cases I want to use www-data for the group, but www-data is user/group 33 on Ubuntu and 82 on Alpine. On Alpine, 33 already exists as the "X font server" user, hence, xfs.

I still don't have a perfect solution for a non-persistent, shared "scratch work" directory where I can dump logs while they wait to be sent up to Cloudwatch. I may simply end up using the tmpfs solution and then running logrotate with a very aggressive set of parameters, so that the log files never consume more than a few MB of memory.

Joseph Montanaro
  • 518
  • 4
  • 13