RH_Icon_Container_with_App_Flat

Introducing the Atomic command

'/usr/bin/atomic'

The atomic command defines the entrypoint for Project Atomic hosts.  On an
Atomic Host, there are at least two distinct software delivery vehicles; Docker (often used in combination with the traditional RPM/yum/dnf), and rpm-ostree to provide atomic upgrades of the host system.

The goal of the atomic command is to provide a high level, coherent entrypoint to the system, and fill in gaps in Linux container implementations.

For Docker, atomic can make it easier to interact with special kinds of containers, such as super-privileged debugging tools and the like.

The atomic host subcommand wraps rpm-ostree, click to read more ...

The atomic command is also available on non Atomic Platforms.  You can run it on a standard RHEL, Fedora and Centos OS.  We would like to see it get used on other platforms in the future.

Container Image as a software delivery mechanism

The most exciting things about Docker:

Docker images are a new way of delivering software applications to a host operating system.

But, in a lot of ways Docker falls short as a software delivery mechanism.

The atomic command is our effort to close the gaps.

Red Hat has been using RPM to ship software for over 18 years now.  We can look at some of its features to see the short comings.

One of the big features of RPM that Docker image is missing is the mechanism to launch the application at boot time.  When I install the httpd package using yum/rpm it includes the systemd unit file used to launch it at boot time.

We would like to be able to ship one object that a user could easily install.  Currently when you want to install software using Docker images there are two different objects,  You do a Docker pull to install the image, then you build a systemd unit file to run the container, or you execute a complex Docker or Kubernetes command to have the software run as a Docker service.  Bottom line is the developer needs to not only create the Docker image, but he also needs to publish recommended installation procedures. The installation procedures would be easy to misconfigure and we could end up with a support headache.

Software Installation

We need a way for a container to 'install' itself in the system to be automatically restarted.

I introduced the concept of something I called a Super Privileged Container back in November.

https://developers.redhat.com/blog/2014/11/06/introducing-a-super-privileged-container-concept/

The idea here is to allow a container the ability to see and potentially to manipulate the container hosts. One large use of this would be to allow a container to install itself.

Problem Statement

I am developing a new Apache PHP based application, that I would like to allow my customers to install.  I want to use a systemd unit file.  And I would like to allow my customers to install multiple copies of my application that they could customize to run for different accounts.

The current method for users would be to write up a complicated installation procedure where each user could cut and paste a systemd unit file, and copy it into /etc/sytemd/system/APP.service.  They could download content and setup volume mounts for log directories, data directories and config directories. Perhaps even setup some default data.

The LABEL Patch

We worked with upstream Docker community for over a year to get the LABEL patch, into Docker, which finally got merged into Docker-1.6 package.  This patch allows a software developer to add additional JSON Fields (LABEL) into the Docker Image.  For example I could add a LABEL field to my Docker file like the following.

LABEL INSTALL="docker run --rm --privileged -v /:/host -e HOST=/host -e LOGDIR=${LOGDIR} -e CONFDIR=${CONFDIR} -e DATADIR=${DATADIR} --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE /bin/install.sh"

Note that this docker run command is actually a Super Privileged Container command, in that it is privileged and the host OS is mounted at /host within the container.

If an application developer added an LABEL INSTALL line like that in a container image named apache_php, users could examine the docker image for the install line and execute the command.  One problem is the command has NAME and IMAGE embedded in it rather then the container name and the image name.

Better yet the user could let the atomic command do install the application for him.

atomic install apache_php

The atomic command will:

  • The atomic command pulls the apache_php image from a registry if it is not currently installed.
  • The atomic command reads the LABEL INSTALL line from the container image json file.
  • The atomic command replaces any IMAGE values that it sees with the image name installed.  This means the -e IMAGE=IMAGE IMAGE will get substituted with -e IMAGE=apache_php apache_php.
  • The atomic command also allows you to specify a name for your container, and defaults to the image name if the container name is not specified.  In this case since the user did not specify the container name, atomic command will replace NAME with apache_php.  (--name apache_php -e NAME=apache_php).
  • The atomic command generates three directory names and sets passes them in as environment variables 'LOGDIR=/var/log/NAME, DATADIR=/var/lib/NAME and CONFDIR=/etc/NAME', where NAME is substituted with the container name (apache_php).  These directories can be used by the container installation procedure to create initial content and eventually could be volume mounted from the host.

If a user wanted to install multiple copies he could just execute

atomic install -n customer1 apache_php
atomic install -n customer2 apache_php

And two containers would be installed and ready to run.

Notice the LABEL INSTALL line in my example executes the /bin/install.sh script that we packaged into the container.  This allows the developer to imbed his installation script into the container image.  Since we are running the container as a SPC we are volume mounting / at /host within the container.

We would like /host to become the standard location for mounting / into a container.  We set the environment variable $HOST within the container to point at /host.  This allows the application developer to write scripts that install content ralative to $HOST/PATH. For example the install.sh might be creating a systemd unit file on the host in /etc/systemd/system/APP.service.  If the install.sh creates the file in ${HOST}/etc/sytemd/system/APP.service, the script would work in an SPC container where -e HOST=/host or if he ran it directly on his test machine outside a container and $HOST would not be set.

The install.sh could also use the $CONFDIR, $LOGDIR and $DATADIR to setup additional content for the container.

Here is my example

/bin/install.sh

#!/bin/sh
# Make Data Dirs
mkdir -p ${HOST}/${CONFDIR} ${HOST}/${LOGDIR}/httpd ${HOST}/${DATADIR}

# Copy Config
cp -pR /etc/httpd ${HOST}/${CONFDIR}

# Create Container
chroot ${HOST} /usr/bin/docker create -v /var/log/${NAME}/httpd:/var/log/httpd:Z -v /var/lib/${NAME}:/var/lib/httpd:Z --name ${NAME} ${IMAGE}

# Install systemd unit file for running container
sed -e "s/NAME/${NAME}/g" etc/systemd/system/httpd_template.service > ${HOST}/etc/systemd/system/httpd_${NAME}.service

# Enabled systemd unit file
chroot ${HOST} /usr/bin/systemctl enable /etc/systemd/system/httpd_${NAME}.service

Notice how the install script is creating directories on the host for the container.  Also it modifies the systemd httpd template file below into a systemd unit file and enables the service.

/etc/systemd/system/httpd_template.service

[Unit]
Description=The Apache HTTP Server for NAME
After=Docker.service

[Service]
ExecStart=/usr/bin/docker start NAME
ExecStop=/usr/bin/docker stop NAME
ExecReload=/usr/bin/docker exec -t NAME /usr/sbin/httpd $OPTIONS -k graceful

[Install]
WantedBy=multi-user.target

When the installation is done the service is ready to run.  And will run on reboot.

Software Removal

The atomic command can also be used to uninstall software.  It will use the LABEL UNINSTALL option if available.

In our example we will use a LABEL like:

LABEL UNINSTALL="docker run --rm --privileged -v /:/host -e HOST=/host -e IMAGE=IMAGE -e NAME=NAME IMAGE /bin/uninstall.sh

Then the user can execute the following uninstall command:

atomic uninstall apache_php

The atomic command will execute the /bin/uninstall.sh script

/bin/uninstall.sh

#!/bin/sh
chroot ${HOST} /usr/bin/systemctl disable /etc/systemd/system/httpd_${NAME}.service
rm -f ${HOST}/etc/systemd/system/httpd_${NAME}.service

Notice the script disables the service and then remove the unit file.

Finally the atomic uninstall will attempt to docker rm a container name if the name is specified or default to the image name.

If the container name is the same as the image name atomic uninstall with also docker rmi the container image.

How do I run the application?

Problem Statement

  • My application is nicely rolled into a container images.
  • My application run mostly confined but needs additional privileges?
    -- How do I tell the user to run it?</b></p>

Lets look at an example

The FreeIPA team has been experimenting with running the IPA daemons as separate containers.  One daemon they currently use is ntpd.  The nptd container needs to run with --cap_add SYS_TIME, in order to adjust the system hosts time.  The ntpd container developer has to tell users to run the container with the following command.

Docker run -d -n ntpd --cap_add SYS_TIME ntpd

The atomic command supports another label LABEL RUN, which the application developer can use to define how his application can be run.

FROM rhel7
RUN yum -y install ntpd; yum -y clean all
LABEL RUN=&amp;quot;docker run -d -n NAME --cap_add SYS_TIME IMAGE&amp;quot;
CMD /usr/bin/ntpd

Now if the user examined the Docker image, he would know exactly how to run the container. He could inspect the installed image and then cut and paste the image line.   We automate this process by adding the atomic run command.  A user only needs to execute the following command:

atomic run ntpd

This will do a Docker pull of the ntpd container package onto your host and then execute the label RUN command, if it exists.  If the command does not exist the command will default to a

Docker create -ti -n ntpd ntpd

  • This gives us the ability to define how a specific container expects to be run. Specifically this includes the privilege level required, as well as special mounts and host access, etc.

Other features of the atomic command.

One of the key features of rpm is to list information about a package.

# rpm -qi Docker
Name        : Docker
Version     : 1.5.0
Release     : 25.git5ebfacd.fc23
Architecture: x86_64
Install Date: Thu 26 Mar 2015 03:05:47 PM EDT
Group       : Unspecified
Size        : 21735169
License     : ASL 2.0
Signature   : (none)
Source RPM  : Docker-1.5.0-25.git5ebfacd.fc23.src.rpm
Build Date  : Thu 26 Mar 2015 01:01:50 AM EDT
Build Host  : buildhw-05.phx2.fedoraproject.org
Relocations : (not relocatable)
Packager    : Fedora Project
Vendor      : Fedora Project
URL         : http://www.Docker.com
Summary     : Automates deployment of containerized applications
Description :
Docker is an open-source engine that automates the deployment of any application as a lightweight, portable, self-sufficient container that will run virtually anywhere.

Docker containers can encapsulate any payload, and will run consistently on and between virtually any server. The same container that a developer builds and tests on a laptop will run at scale, in production*, on VMs, bare-metal servers, OpenStack clusters, public instances, or combinations of the above.

We want to be able to include data similar to this in the container image.  We again take advantage of the LABEL patch, and add data like the following to the Dockerfile.

LABEL Name=apache_php
LABEL Version=1.0
LABEL Vendor="Red Hat" License=GPLv3
LABLE Description="
The Apache PHP Application is a example of using atomic command to install a service onto a machine."

atomic info apache_php
Name         : apache_php
Version      : 1.0
Vendor       : Red Hat
License      : GPLv3
INSTALL      : docker run --rm --privileged -v /:/host -e HOST=/host -e LOGDIR= -e CONFDIR= -e DATADIR= -e IMAGE=IMAGE -e NAME=NAME IMAGE /bin/install.sh
UNINSTALL    : docker run --rm --privileged -v /:/host -e HOST=/host -e IMAGE=IMAGE -e NAME=NAME IMAGE /bin/uninstall.sh
Last updated: February 22, 2024