0

I am Dockerising an old project. A feature in the project pulls in user-specified Git repos, and since the size of a repo could cause the filing system to be overwhelmed, I created a local filing system of a fixed size, and then mounted it. This was intended to prevent the web host from having its file system filled up.

The general approach is this:

IMAGE=filesystem/image.img
MOUNT_POINT=filesystem/mount
SIZE=20
PROJECT_ROOT=`pwd`

# Number of M to set aside for this filing system
dd if=/dev/zero of=$IMAGE bs=1M count=$SIZE &> /dev/null

# Format: the -F permits creation even though it's not a "block special device"
mkfs.ext3 -F -q $IMAGE

# Mount if the filing system is not already mounted
$MOUNTCMD | cut -d ' ' -f 3 | grep -q "^${PROJECT_ROOT}/${MOUNT_POINT}$"
if [ $? -ne 0 ]; then
    # -p Create all parent dirs as necessary
    mkdir -p $MOUNT_POINT
    /bin/mount -t ext3 $IMAGE $MOUNT_POINT
fi

This works fine in a Linux local or remote VM. However, I'd like to run this shell code, or something like it, inside a container. Part of the reason I'd like to do that is to contain all fiddly stuff inside a container, so that building a new host machine is as kept as simple as possible (in my view, setting up custom mounts and cron-restart rules on the host works against that).

So, this command does not work inside a container ("filesystem" is an on-host Docker volume)

mount -t ext3 filesystem/image.img filesystem/mount
mount: can't setup loop device: No space left on device

It also does not work on a container folder ("filesystem2" is a container directory):

dd if=/dev/zero of=filesystem2/image.img bs=1M count=20
mount -t ext3 filesystem2/image.img filesystem2/mount
mount: can't setup loop device: No space left on device

I wonder whether containers just don't have the right internal machinery to do mounting, and thus whether I should change course. I'd prefer not to spend too much time on this (I'm just moving a project to a Docker-only server) which is why I would like to get mount working if I can.

Other options

If that's not possible, then a size-limited Docker volume, that works with both Docker and Swarm, may be an alternative I'd need to look into. There are conflicting reports on the web as to whether this actually works (see this question).

There is a suggestion here to say this is supported in Flocker. However, I am hesitant to use that, as it appears to be abandoned, presumably having been affected by ClusterHQ going bust.

This post indicates I can use --storage-opt size=120G with docker run. However, it does not look like it is supported by docker service create (unless perhaps the option has been renamed).

Update

As per the comment convo, I made some progress; I found that adding --privileged to the docker run enables mounting, at the cost of removing security isolation. A helpful commenter says that it is better to use the more fine-grained control of --cap-add SYS_ADMIN, allowing the container to retain some of its isolation.

However, Docker Swarm has not yet implemented either of these flags, so I can't use this solution. This lengthy feature request suggests to me that this feature is not going to be added in a hurry; it's been pending for two years already.

halfer
  • 19,824
  • 17
  • 99
  • 186
  • Ah, I just mounted a file in an Ubuntu container with `--privileged`. Now, all I need is to do that in a service, but that's not allowed yet; the workaround is [somewhere in here](https://github.com/moby/moby/issues/24862). – halfer Sep 15 '18 at 21:51
  • 1
    You can allow `mount` in container with `--cap-add SYS_ADMIN` instead of `--privileged`. – mviereck Sep 15 '18 at 22:03
  • Thanks @mviereck. I agree that would be safer, but it looks like (my current version of) Swarm (`docker service create`) also does not support that. – halfer Sep 15 '18 at 22:14

2 Answers2

1

I have found a size-limiting solution I am happy with, and it does not use the Linux mount command at all. I've not implemented it yet, but the tests documented below are satisfying enough. Readers may wish to note the minor warnings at the end.

I had not tried mounting Docker volumes prior to asking this question, since part of my research stumbled on a Stack Overflow poster casting doubt on whether Docker volumes can be made to respect a size limitation. My test indicates that they can, but you may wish to test this on your own platform to ensure it works for you.

Size limit on Docker container

The below commands have been cobbled together from various sources on the web.

To start with, I create a volume like so, with a 20m size limit:

docker volume create \
    --driver local \
    --opt o=size=20m \
    --opt type=tmpfs \
    --opt device=tmpfs \
    hello-volume

I then create an Alpine Swarm service with a mount on this container:

docker service create \
    --mount source=hello-volume,target=/myvol \
    alpine \
    sleep 10000

We can ensure the container is mounted by getting a shell on the single container in this service:

docker exec -it amazing_feynman.1.lpsgoyv0jrju6fvb8skrybqap
/ # ls - /myvol
total 0

OK, great. So, while remaining in this shell, let's try slowly overwhelming this disk, in 5m increments. We can see that it fails on the fifth try, which is what we would expect:

/ # cd /myvol
/myvol # ls
/myvol # dd if=/dev/zero of=image1 bs=1M count=5
5+0 records in
5+0 records out
/myvol # dd if=/dev/zero of=image2 bs=1M count=5
5+0 records in
5+0 records out
/myvol # ls -l
total 10240
-rw-r--r--    1 root     root       5242880 Sep 16 13:11 image1
-rw-r--r--    1 root     root       5242880 Sep 16 13:12 image2
/myvol # dd if=/dev/zero of=image3 bs=1M count=5
5+0 records in
5+0 records out
/myvol # dd if=/dev/zero of=image4 bs=1M count=5
5+0 records in
5+0 records out
/myvol # ls -l
total 20480
-rw-r--r--    1 root     root       5242880 Sep 16 13:11 image1
-rw-r--r--    1 root     root       5242880 Sep 16 13:12 image2
-rw-r--r--    1 root     root       5242880 Sep 16 13:12 image3
-rw-r--r--    1 root     root       5242880 Sep 16 13:12 image4
/myvol # dd if=/dev/zero of=image5 bs=1M count=5
dd: writing 'image5': No space left on device
1+0 records in
0+0 records out
/myvol # 

Finally, let's see if we can get an error by overwhelming the disk in one go, in case the limitation only applies to newly opened file handles in a full disk:

/ # cd /myvol
/ # rm *
/myvol # dd if=/dev/zero of=image1 bs=1M count=21
dd: writing 'image1': No space left on device
21+0 records in
20+0 records out

It turns out we can, so that looks pretty robust to me.

Nota bene

The volume is created with a type and a device of "tmpfs", which sounded to me worryingly like a RAM disk. I've successfully checked that the volume remains connected and intact after a system reboot, so it looks good to me, at least for now.

However, I'd say that when it comes to organising your data persistence systems, don't just copy what I have. Make sure the volume is robust enough for your use case before you put it into production, and of course, make sure you include it in your back-up process.

(This is for Docker version 18.06.1-ce, build e68fc7a).

halfer
  • 19,824
  • 17
  • 99
  • 186
  • Strange, that directory should reset to empty with no more than a restart of the container. "tmpfs" is a ram disk. – BMitch Sep 17 '18 at 20:21
  • Ah, thanks @BMitch - I will do some more testing. I've only tried it with `dd` random files so far, but yes, they did seem to be preserved across an Ubuntu host `reboot`. – halfer Sep 17 '18 at 20:56
1

You won't be able to safely do this inside of a container. Docker removes the mount privilege from containers because using this you could mount the host filesystem and escape the container. However, you can do this outside of the container and mount the filesystem into the container as a volume using the default local driver. The size option isn't supported by most filesystems, tmpfs being one of the few exceptions. Most of them use the size of the underlying device which you defined with the image file creation command:

dd if=/dev/zero of=filesystem/image.img bs=1M count=$SIZE

I had trouble getting docker to create the loop device dynamically, so here's the process to create it manually:

$ sudo losetup --find --show ./vol-image.img
/dev/loop0
$ sudo mkfs -t ext3 /dev/loop0
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 10240 1k blocks and 2560 inodes
Filesystem UUID: 25c95fcd-6c78-4b8e-b923-f808517b28df
Superblock backups stored on blocks:
        8193

Allocating group tables: done
Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

When defining the volume mount options are passed almost verbatim from the mount command you run on the command line:

docker volume create --driver local --opt type=ext3 \
  --opt device=filesystem/image.img app_vol
docker service create --mount type=volume,src=app_vol,dst=/filesystem/mount ...

or in a single service create command:

docker service create \
  --mount type=volume,src=app_vol,dst=/filesystem/mount,volume-driver=local,volume-opt=type=ext3,volume-opt=device=filesystem/image.img ...

With docker run, the command looks like:

$ docker run -it --rm --mount type=volume,dst=/data,src=ext3vol,volume-driver=local,volume-opt=type=ext3,volume-opt=device=/dev/loop0 busybox /bin/sh
/ # ls -al /data
total 17
drwxr-xr-x    3 root     root          1024 Sep 19 14:39 .
drwxr-xr-x    1 root     root          4096 Sep 19 14:40 ..
drwx------    2 root     root         12288 Sep 19 14:39 lost+found

The only prerequisite is that you create this file and loop device before creating the service, and that this file is accessible wherever the service is scheduled. I would also suggest making all of the paths in these commands fully qualified rather than relative to the current directory. I'm pretty sure there are a few places that relative paths don't work.

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • Thanks for this BMitch, great stuff. In relation to the creation of a size-limited volume, is it important to get things like `type` exactly right? For example, I found a thread elsewhere that said `ext4` won't work because that driver doesn't support size limits. Would you normally reach for `ext3` here? – halfer Sep 17 '18 at 20:55
  • @halfer I picked ext3 to match your question. Docker is just passing the mount option through to the underlying mount command, and most of them limit to the device size. – BMitch Sep 17 '18 at 21:11
  • Ah right, fair enough. As per my comment under my own answer, I will switch to `ext3` and see if the size limits are respected in the way that `tmpfs` supports, and I'll update with my findings. – halfer Sep 17 '18 at 21:14
  • 1
    @halfer I don't believe size is a valid option with any of the extX filesystem types. You can check the man pages on mount to verify. It needs to be done with the underlying device size (with the dd command you use to create the image file). Tmpfs is one of the few filesystems that supports this option to avoid using all the ram. – BMitch Sep 17 '18 at 22:21
  • Ah, that's most useful, thanks BMitch - that knowledge has probably saved me some fiddling, since I can just go with the file-based mount. If you can edit that information into your answer somewhere, I'll take your answer in preference to my own. – halfer Sep 17 '18 at 22:28
  • Ah, interesting: I tried to implement this, and it looks like Docker volumes need to be based on a block device. The error in Swarm against failed containers is `starting container failed: error while mounting volume '': error while mounting volume with options: type='ext3' device='/var/run/awooga/data.img' o='loop': block device required`. I will raise a new question when I get the chance. My short term fix will probably to do a manual mount in the host, and then use a bind mount in Docker on the mounted folder. – halfer Sep 19 '18 at 08:49
  • @halfer did you `dd` to create your img file first? – BMitch Sep 19 '18 at 09:23
  • Yep, `dd` first, then a `mkfs.ext3`, then `docker create volume`, then start the service. I've not tried a single service create command though, will do that next. I did briefly ponder if the `img` file needed to be writeable by the `docker` group, but this did not seem to assist. I tried adding `o=loop`, as that seems to have fixed the error for other folks (in non-Docker contexts) but to no avail. – halfer Sep 19 '18 at 10:41
  • @halfer Updated with the steps to create a loop device, not sure if that's a docker limitation or I just messed up the o=loop myself. – BMitch Sep 19 '18 at 14:45
  • Thanks for additional information, much appreciated; I will try that as soon as I am able. It's odd that `losetup` might be needed, since the `mount` command was entirely sufficient on its own to create a file-based FS outside of a Docker context. – halfer Sep 19 '18 at 17:05