Getting Started with runC

This week the Open Container Project was introduced at DockerCon in San Francisco together with runC, a CLI tool to run containers. runC is a wrapper around libcontainer, which was donated to the Open Container Project by Docker to serve as a reference implementation. This sounds all really interesting, especially because all major vendors are part of the OCP, even CoreOS with rkt.

So, let's check out runC and start running Docker containers with it.

Install runC

At the moment no binary is provided. To compile runC you need a working Go environment, then checkout and build runC:

mkdir -p ~/golang/src/github.com/opencontainers/  
cd ~/golang/src/github.com/opencontainers/  
git clone https://github.com/opencontainers/runc  
cd ./runc  
make  
sudo make install  

These instructions worked on one machine with Ubuntu 14.04 but not on another.

Anyway, you should have your runC binary now.

Run a Container

runC is on a lower level than Docker and therefore only needs a folder or tar file with the image files.
We use a running Docker container and export it with docker export to a tar-file and extract it to a folder:

mkdir ./goapp  
cd ./goapp  
docker pull geku/go-app:0.1  
CONTAINER_ID=$(docker run -d geku/go-app:0.1)  
docker export -o go-app.tar $CONTAINER_ID  
tar -xf go-app.tar  
rm go-app.tar  

Now, we have a folder with the container files. To run it, we must create a container.json file:

{
        "version": "0.1",
        "os": "linux",
        "arch": "amd64",
        "processes": [
                {
                        "tty": true,
                        "user": "daemon",
                        "args": [
                                "/app/go-app"
                        ],
                        "env": [
                                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                                "TERM=xterm"
                        ],
                        "cwd": ""
                }
        ],
        "root": {
                "path": "./",
                "readonly": true
        },
        "cpus": 1.1,
        "memory": 128,
        "namespaces": [
                {
                        "type": "process"
                },
                {
                        "type": "mount"
                },
                {
                        "type": "ipc"
                }
        ],
        "capabilities": [
                "AUDIT_WRITE",
                "KILL",
                "NET_BIND_SERVICE"
        ],
        "devices": [
                "null",
                "random",
                "full",
                "tty",
                "zero",
                "urandom"
        ],
        "mounts": [
                {
                        "type": "proc",
                        "source": "proc",
                        "destination": "/proc",
                        "options": ""
                },
                {
                        "type": "tmpfs",
                        "source": "tmpfs",
                        "destination": "/dev",
                        "options": "nosuid,strictatime,mode=755,size=65536k"
                },
                {
                        "type": "devpts",
                        "source": "devpts",
                        "destination": "/dev/pts",
                        "options": "nosuid,noexec,newinstance,ptmxmode=0666,mode=0620,gid=5"
                },
                {
                        "type": "tmpfs",
                        "source": "shm",
                        "destination": "/dev/shm",
                        "options": "nosuid,noexec,nodev,mode=1777,size=65536k"
                },
                {
                        "type": "mqueue",
                        "source": "mqueue",
                        "destination": "/dev/mqueue",
                        "options": "nosuid,noexec,nodev"
                },
                {
                        "type": "sysfs",
                        "source": "sysfs",
                        "destination": "/sys",
                        "options": "nosuid,noexec,nodev"
                }
        ]
}

It's almost what runC generates when running runc spec. Obviously, we have to set the process command under processes/args and removed the network and uts namespace to share the host's network. Because of this, hostname is unnecessary and should be removed, too. Finally we have to set the root path to ./.

When the container.json is stored in the root directory of our image we are ready to start a container:

cd ./goapp  
sudo runc  

To verify if the application inside the container is running, we can send an HTTP request:

curl localhost:5000/json  
{"hostname":"demo","env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","TERM=xterm","HOME=/usr/sbin"],"num_cpu":4,"go_max_procs":1}

Yay, we have our first container running with runC! This is a very basic example, but demonstrates how runC can run Docker images.