Atomic Host 101 Lab Part 2: Container Storage

Introduction

In Part 1 of this series we learned a little about the technology behind Atomic Host and how to interact with a deployed system. In this section we will cover the Configuring Storage for Containers topic from the outline in Part 0.

History of Container Storage

One of the early goals of Atomic Host was to be a good platform for running containerized workloads. This is still a fundamental goal of Atomic Host and certainly includes making sure that the container runtime (currently the docker daemon) has proper storage configuration such that it can get a balance of good performance and stability.

For a long while the best option for Red Hat based operating systems was to use the devicemapper storage backend for docker. The benefits were that the devicemapper backend was stable, decently performant, and had SELinux support. More recently, the overlay2 backend has emerged as a legitimate option because SELinux support has landed and there are significant performance improvements over devicemapper.

However, most of the storage needs and original designs were centered around devicemapper and the fact that the backend requires there to be a separate dedicated block device that can be donated to the dm thin-pool. This could be an actual physical block device, or a LVM logical volume from an existing volume group.

The container-storage-setup Tool

The need for a device to be dedicated to container storage prompted the need for a tool, called container-storage-setup (previously known as docker-storage-setup) to make this easy for spinning up instances of a container host. Atomic Host has conveniently shipped with extra space available on pre-built disk images so that container-storage-setup could use it when configuring storage for docker.

Since container-storage-setup gets executed automatically by systemd when the docker.service starts, leveraging the tool is usually as simple as populating the /etc/sysconfig/docker-storage-setup file early in boot (i.e. cloud-init or some other mechanism) with some config values that tell the utility what to do.

In order to investigate some of the options we have available take a look at the existing contents of that file on the Atomic Host:

[root@localhost ~]# cat /etc/sysconfig/docker-storage-setup
# Edit this file to override any configuration options specified in
# /usr/share/container-storage-setup/container-storage-setup.
#
# For more details refer to "man container-storage-setup"
STORAGE_DRIVER=overlay2
CONTAINER_ROOT_LV_NAME=docker-root-lv
CONTAINER_ROOT_LV_MOUNT_PATH=/var/lib/docker
GROWPART=true

As you can see from the contents of the file, in Fedora 26 we have already moved on to setting overlay2 as the default storage driver for the container runtime. In this case we are also defauting to creating a new LV (docker-root-lv) and a filesystem (XFS) on top of that LV to mount on /var/lib/docker. The overlay2 backend will then run on top of that filesystem.

NOTE: There are a lot of options that can be provided to the container-storage-setup tool. You can investigate them by checking out the /usr/share/container-storage-setup/container-storage-setup file.

Inspecting The Booted System

We just saw the contents of the /etc/sysconfig/docker-storage-setup file. Let’s check out the output from the docker-storage-setup.service to see what happened when the system came up and the docker daemon was started:

[root@localhost ~]# systemctl status docker-storage-setup.service -o cat
● docker-storage-setup.service - Docker Storage Setup
   Loaded: loaded (/usr/lib/systemd/system/docker-storage-setup.service; enabled; vendor preset: disabled)
   Active: inactive (dead) since Mon 2017-08-28 00:15:50 UTC; 27min ago
 Main PID: 720 (code=exited, status=0/SUCCESS)

Starting Docker Storage Setup...
CHANGED: partition=2 start=616448 old: size=83269632 end=83886080 new: size=85366784,end=85983232
  Physical volume "/dev/vda2" changed
  1 physical volume(s) resized / 0 physical volume(s) not resized
  Logical volume "docker-root-lv" created.
Started Docker Storage Setup.

Looks like the root partition was extended and the docker-root-lv LV was created. Although we don’t explicitly see the output here it is also true that an XFS filesystem was created on top of that and storage options that specify overlay2 were added to the /etc/sysconfig/docker-storage configuration file.

We can see the LV is mounted on /var/lib/docker:

[root@localhost ~]# lsblk
NAME                          MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
vda                           252:0    0   41G  0 disk
├─vda1                        252:1    0  300M  0 part /boot
└─vda2                        252:2    0 40.7G  0 part
  ├─atomicos-root             253:0    0   13G  0 lvm  /sysroot
  └─atomicos-docker--root--lv 253:1    0 15.1G  0 lvm  /sysroot/ostree/deploy/fedora-atomic/var/lib/docker

Also, if we query docker we can see that it is using the overlay2 Storage Driver.

[root@localhost ~]# docker info 2>/dev/null | grep 'Storage Driver'
Storage Driver: overlay2

Changing Storage Configuration

NOTE: If you are going to change container storage on a system please be mindful that you will lose the container images/data that currently exist on your system. The Atomic CLI has atomic storage import/export to attempt to save and restore containers for this type of scenario.

We can also use the container-storage-setup utility to change the storage configuration on a system. Again, it is suggested that you look at the /usr/share/container-storage-setup/container-storage-setup file for an explanation of options that you can put in the /etc/sysconfig/docker-storage-setup configuration file.

For example, if you want to switch from overlay2 to devicemapper you would need to change it so that STORAGE_DRIVER=devicemapper. As an exercise, let’s switch this system to the devicemapper backend.

We’ll first stop docker, unmount the filesystem which was backing the overlay2 driver, and also remove the logical volume that filesystem was build on top of.

[root@localhost ~]# systemctl stop docker
[root@localhost ~]# umount /var/lib/docker
[root@localhost ~]# lvremove /dev/atomicos/docker-root-lv
Do you really want to remove active logical volume atomicos/docker-root-lv? [y/n]: y
  Logical volume "docker-root-lv" successfully removed

Since we just unmounted and removed the LV behind /var/lib/docker we need to remove the systemd mount unit that was configured to mount /var/lib/docker. Removing this file is like removing an entry from the /etc/fstab.

[root@localhost ~]# rm /etc/systemd/system/docker-storage-setup.service.wants/var-lib-docker.mount
[root@localhost ~]# systemctl daemon-reload

Next we’ll remove the /etc/sysconfig/docker-storage file, which has options to give to the docker daemon to tell it what storage driver to use, and also we’ll overwrite all settings in the /etc/sysconfig/docker-storage-setup so that container-storage-setup will know we want to use the devicemapper backend. We’ll then run container-storage-setup to create the devicemapper thin pool and populate a new /etc/sysconfig/docker-storage with options for the docker daemon.

NOTE: Some or all of these operations may be able to be done by the atomic storage modify command. Feel free to investigate that option.

[root@localhost ~]# rm /etc/sysconfig/docker-storage
[root@localhost ~]# echo 'STORAGE_DRIVER=devicemapper' > /etc/sysconfig/docker-storage-setup
[root@localhost ~]# /usr/bin/container-storage-setup
  Using default stripesize 64.00 KiB.
  Rounding up size to full physical extent 44.00 MiB
  Logical volume "docker-pool" created.
  Logical volume atomicos/docker-pool changed.

Now that this is done you can see thin LV that was set up in the output from lsblk and lvs:

[root@localhost ~]# lsblk
NAME                            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
vda                             252:0    0   41G  0 disk
├─vda1                          252:1    0  300M  0 part /boot
└─vda2                          252:2    0 40.7G  0 part
  ├─atomicos-root               253:0    0   13G  0 lvm  /sysroot
  ├─atomicos-docker--pool_tmeta 253:1    0   44M  0 lvm
  │ └─atomicos-docker--pool     253:3    0   11G  0 lvm
  └─atomicos-docker--pool_tdata 253:2    0   11G  0 lvm
    └─atomicos-docker--pool     253:3    0   11G  0 lvm
[root@localhost ~]# lvs
  LV          VG       Attr       LSize  Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  docker-pool atomicos twi-a-t--- 11.02g             0.00   0.09
  root        atomicos -wi-ao---- 12.93g

Now we can start docker and verify that the devicemapper backend is being used:

[root@localhost ~]# systemctl start docker
[root@localhost ~]# docker info 2>/dev/null | grep 'Storage Driver'
Storage Driver: devicemapper

To cap things off let’s import a container image from our lab files and run a container to see that new devicemapper objects are getting created for this storage:

[root@localhost ~]# atomic storage import --dir  /srv/localweb/containers/
Importing image: a16c8800bb14
7d4769f4070d: Loading layer [==================================================>] 242.5 MB/242.5 MB
25ca5f0393cd: Loading layer [==================================================>]   359 MB/359 MB
960139190bea: Loading layer [==================================================>] 71.77 MB/71.77 MB
Loaded image: registry.fedoraproject.org/f25/httpd:latest
Importing volumes
atomic import completed successfully
Would you like to cleanup (rm -rf /srv/localweb/containers/) the temporary directory [y/N]N
Please restart docker daemon for the changes to take effect
[root@localhost ~]#
[root@localhost ~]# docker run -d a16c8800bb14 sleep 600
b0509ea6c6b6811915f3f2d15a9fd344836f628a638d797683e7b27c4be84205
[root@localhost ~]#
[root@localhost ~]# lsblk
NAME                            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
vda                             252:0    0   41G  0 disk
├─vda1                          252:1    0  300M  0 part /boot
└─vda2                          252:2    0 40.7G  0 part
  ├─atomicos-root               253:0    0   13G  0 lvm  /sysroot
  ├─atomicos-docker--pool_tmeta 253:1    0   44M  0 lvm
  │ └─atomicos-docker--pool     253:3    0   11G  0 lvm
  │   └─docker-253:0-14918141-d748db4190b91666509effaabaa06c24d6e5ed836e5421ce914250e7eb1ac5a0
  │                             253:4    0   10G  0 dm   /var/lib/docker/devicemapper/mnt/d748db4190b916
  └─atomicos-docker--pool_tdata 253:2    0   11G  0 lvm
    └─atomicos-docker--pool     253:3    0   11G  0 lvm
      └─docker-253:0-14918141-d748db4190b91666509effaabaa06c24d6e5ed836e5421ce914250e7eb1ac5a0
                                253:4    0   10G  0 dm   /var/lib/docker/devicemapper/mnt/d748db4190b916

As the container runs (sleep 600) we can see the new devicemapper mounts that were created for the container in the lsblk output.

Part 2 Wrap Up

Part 2 of this lab has introduced us to Container Storage and the necessity for the container-storage-setup tool. We also inspected how the storage gets configured on an Atomic Host and switched the storage from one storage backend to another. In the next lab we’ll cover rebase, upgrade and rollback for Atomic Host as well as some basic methods for viewing OSTree commit history and inspecting changes to a running system.