From Fedora Project Wiki

Revision as of 02:08, 20 March 2017 by Yzhang (talk | contribs) (Created page with "This page is intended to give an more in-depth guide for building/running a system container. If you are not familiar with the concept of system containers, please check out [...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This page is intended to give an more in-depth guide for building/running a system container. If you are not familiar with the concept of system containers, please check out Container:System_Container#Useful_Links.

Files

This section describes the files necessary for the operation of a system container. A useful tool to check the correctness of the files, system container lint, can be found in the projectatomic repo on GitHub.

Dockerfile

A system container image can be built using docker from a Dockerfile.

If you are familiar with Dockerfile specifications, a more concise guide highlighting only the differences can be found at: Container:Guidelines#System_Containers

Example

FROM fedora:rawhide

ENV VERSION=0.1 RELEASE=1 ARCH=x86_64
LABEL name="$FGC/mycontainer" \
      version="$VERSION" \
      release="$RELEASE.$DISTTAG" \
      architecture="$ARCH" \
      summary="My test system container" \
      maintainer="Me <me@me.com>" \
      atomic.type='system'

RUN dnf -y --setopt=tsflags=nodocs install nmap-ncat && dnf clean all

COPY run.sh /usr/bin/

COPY tmpfiles.template service.template manifest.json \
     config.json.template /exports/

Notes

  1. Start by pinning the image to a specific version of the OS. This is important not only because system containers provide OS-specific services, but also to reduce local storage space required for multiple images. Ideally system container images for a specific OS/version will be from the same base image. That base image will be stored as a layer in OSTree, and all future containers will share that same layer as a base image. Thus an extra container image will only take up storage space necessary for extra files and packages specific to that container image, greatly reducing the necessary storage space to run multiple containers.
  2. Label information follow mostly the same Container:Guidelines#LABELS. The only special label for system containers is the atomic.type label. If this label exists and is specified to be system, it tells the atomic command line that this image is for a system container ONLY. By default it will be pulled to OSTree on your host instead. Do not specify this if you intend the container to work as other types of containers as well.
  3. Install necessary packages, note to --setopt=tsflags=nodocs and dnf clean all to reduce image size.
  4. Copy in necessary files (preferred over add as highlighted by docker best practices. Scripts used for setup/running the container should be added to /usr/bin/, and other files should be copied to /exports/. There are 4 files that the atomic command line recognizes as "special files" for system container runtime. These are: config.json.template manifest.json service.template tmpfiles.template. These must be in exports and their usage is highlighted below.

config.json.template

This file is a runc runtime config file, the exact details of which can be found at [their github page]. Essentially this file contains metadata necessary for operation of the container. This includes the process to run, environment variables to inject, etc.

This file is NOT mandatory. If you wish to run the container using runc as the runtime, it is highly recommended you include your own. If not included in the image under /exports/config.json.template, a default one will be generated during install time. Alternatively, the container can run just as a systemd service, in which case all the image needs is a service.template file to dictate the systemd service.

Example

{
    "ociVersion": "1.0.0",
    "platform": {
	"os": "linux",
	"arch": "amd64"
    },
    "process": {
	"terminal": false,
	"user": {
	    "uid": 0,
	    "gid": 0
	},
	"args": [
	    "/usr/bin/run.sh"
	],
	"env": [
	    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
	    "TERM=xterm",
            "NAME=$NAME"
	],
	"cwd": "/",
	"capabilities": [
            "CAP_CHOWN",
            "CAP_FOWNER",
            ...

	],
	"noNewPrivileges": false
    },
    "root": {
	"path": "rootfs",
	"readonly": true
    },
    "mounts": [
	{
	    "source": "/dev",
	    "destination": "/dev",
	    "type": "bind",
	    "options": [
		"rbind",
		"shared",
		"rw",
		"mode=755"
	    ]
	},
	{
	    "source": "/sys",
	    "destination": "/sys",
	    "type": "bind",
	    "options": [
		"rbind",
		"shared",
		"rw",
		"mode=755"
	    ]
	},
	...

	}
    ],
    "hooks": {},
    "linux": {
	"resources": {
	    "devices": [
		{
		    "allow": true,
		    "access": "rwm"
		}
	    ]
	},
	"namespaces": [
	    {
		"type": "mount"
	    }
            ...

	]
    }
}

Notes

  1. process['env'] is used to declare all ell environment variables to be used in the container.
  2. root['path'] must be "rootfs". The container will be checked-out at /var/lib/containers/atomic/$NAME, and the rootfs will be /var/lib/containers/atomic/$NAME/rootfs.
  3. root['readonly'] must be true. As mentioned above all containers with the same base image share that image. Essentially files in the checked-out container are hardlinks to the files in ostree storage. Thus the rootfs should not be modified.
  4. process['terminal'] must be false.
  5. process['args'] are the executables that will be run when the container is started. This can be a script you added to /usr/bin, or an existing binary.
  6. mounts are mounts to be used at runtime. This essentially is used to bind locations in the container to the locations on the host.

manifest.json

This is an OPTIONAL file that declare the default value of environment variables declared in process['env'].

Example

{
    "version": "1.0",
    "defaultValues": {
	"VAR_1": "value1",
	"VAR_2": "value2",
	...
    }
}

Notes

All variables declared in process['env'] MUST be given a default value here (can be "" for blank). There are some exceptions:

 * DESTDIR
 * NAME
 * EXEC_START
 * EXEC_STOP
 * HOST_UID
 * HOST_GID
 * RUN_DIRECTORY*
 * STATE_DIRECTORY*
 * UUID*

These variables are given default values. The ones denoted with * can be overridden with the --set flag during install. The others have a value set by the host OS and cannot be overridden.

service.template

This is the systemd service unit template file. During installation it will be converted to $CONTAINER_NAME.service and added to the host. This is used to start/stop the container (as a systemd service). Details can be found at the systemd service man page.

Example

[Unit]
Description=Useful Service
After=network.target

[Service]
ExecStart=$EXEC_START
ExecStop=$EXEC_STOP
Restart=on-failure
WorkingDirectory=$DESTDIR
RuntimeDirectory=${NAME}

[Install]
WantedBy=multi-user.target

Notes

  1. At the very minimum ExecStart ExecStop and WorkingDirectory should be defined.
  2. EXEC_START and EXEC_STOP are auto-generated to start and stop the service. They are respectively runc start $SERVICE_NAME and runc kill $SERVICE_NAME. runc start will in turn call the process['args'] defined in config.json.template. These variables cannot be overridden.
  3. If you do not wish to run the container with runc, do not use the above 2 variables. Instead directly use the command to start the service.
  4. WorkingDirectory ($DESTDIR) is the container checkout location.

tmpfiles.template

This is an OPTIONAL file that creates systemd tempfiles. For more info please refer to the manpage.

Example

d    ${STATE_DIRECTORY}/etcd/${NAME}.etcd   0700 ${HOST_UID} ${HOST_GID} - -
Z    ${STATE_DIRECTORY}/etcd/${NAME}.etcd   0700 ${HOST_UID} ${HOST_GID} - -
d    ${RUN_DIRECTORY}/${NAME}               -        -           -       - -

Building the Container Image

Once the above files are ready, they should be put into the same directory. Then you can build the image through docker with docker build -t $CONTAINER_NAME ..

Operation of a System Container

Let's say we want to create an awesome.service with the above files as a system container; the following steps will guide you through the process. System containers are managed by the atomic command line.

Pulling the Image

The images for system containers are stored in the local OSTree. The image can be acquired through the following methods:

  1. Pulling from a registry: atomic pull --storage ostree $REGISTRY/awesome will pull the image directly from the registry to local OSTree. The pull uses skopeo.
  2. Pulling from the local docker (e.g. if you built with the above process): atomic pull --storage ostree docker:awesome. The docker: prefix tells the atomic command line to pull from the local docker.
  3. Creating the image from a dockertar: atomic pull --storage ostree dockertar:awesome

Once the image is pulled to the local storage, you can view information of the image (Dockerfile labels and environment variable info) with atomic images info awesome.

Install/Uninstall a Container

The container is installed with atomic install --system awesome. This will perform a checkout of the image to /var/lib/containers/awesome.0, create /var/lib/containers/atomic/awesome as a link to the ".0" checkout, and create/enable the systemd service.

  • If you wish to set values for environment variables, you can add a --set $VAR=$VALUE flag.
  • If you wish to use an alternative location as the rootfs (e.g. through NFS), you can use the --rootfs=$LOCALTION_OF_ROOTFS flag.

The command atomic uninstall awesome will uninstall the container and remove the service plus tempfiles.

Runtime

Once the container is installed, it can be started with systemctl start awesome.service, and stopped through systemctl stop awesome.service.

You can view active containers with atomic containers list.

If there is a new version of the image you wish to use, you can pull the new version to OSTree and update the container with atomic containers update awesome. This will stop the service, create a new checkout at /var/lib/containers/atomic/awesome.1 (or .0 again if the container has been updated previously, as only 2 version are preserved), and restart the service with the new image. The environment variables set during the previous checkout will be preserved, but you can override them with --set VAR=VALUE flag during the update.

You can go back to the previous version with atomic containers rollback awesome, which performs the reverse of an update and goes to the previous deployment.

Useful links

Discussion

For suggestions, feedback, or to report issues with this page please contact the Fedora Cloud SIG.