Hard,  on  Containers Submissions: 6/65

You can use high-level Docker, Podman, nerdctl, or even containerd's ctr to run containers with comfort. However, if you want to get the real feel of what containers are, you should start at least one of them performing the main steps manually.

Different ways to start containers (Docker, Podman, nerdctl, ctr.)

In this challenge, you will need to create, start, access, and then destroy an Nginx container using the runc CLI. The following steps are required to achieve this:

  1. Prepare a container root filesystem.
  2. Prepare a container configuration file.
  3. Create a container using the runc CLI.
  4. Configure the container networking.
  5. Start the container using the runc CLI.
  6. Make sure it's accessible from the host.
  7. Terminate the container and clean up the resources.

A lot of things to learn, so let's get started!

A typical OCI container runtime (runc) workflow.

First things first - create a bundle directory that will hold the future container root filesystem and the configuration file.

Hint 1 💡

Despite the fancy name, a bundle directory is just a regular folder. Theoretically, it can be located anywhere on the filesystem, but to make the solution checker happy, you should create it in your $HOME directory.

Now, inside the bundle directory, create a configuration file of the future container.

Hint 2 💡

The format of the configuration file is defined by the OCI Runtime Specification. It's a JSON document that describes the container's runtime environment, including the process to run inside the container, its arguments, environment variables, namespaces, and so on. Even though it's relatively straightforward, it may get pretty lengthy, so writing it from scratch is not the best idea. Luckily, the runc spec command can generate a template configuration file for you.

Similarly, inside the bundle directory, create a rootfs subfolder with the files of the nginx:latest image.

Hint 4 💡

Still having trouble? runc spec --help has a decent example of how to create a bundle, including the extraction of the image files into the rootfs folder.

Your ultimate goal is to start an nginx process inside the container. To tell runc what process to start, you need to specify its name and arguments in the configuration file. Adjust the process.args field of the config.json file to make it point to the nginx binary.

Hint 5 💡

Not sure what to put in the process.args field? You can get some inspiration from the nginx image's ENTRYPOINT and CMD directives. docker inspect nginx:latest will help you with that 😉

With the bundle ready, it's time to create a container using the runc CLI!

Hint 6 💡

runc create --help is your friend 😉

Hint 7 💡

Getting the cannot allocate tty error? Since nginx is a typical server container, you can safely skip the terminal allocation. Make sure to adjust the config.json file accordingly.

Can you tell if the container is running yet? Find the PID of the process that you've just started. Is it Nginx?

Hint 8 💡

Linux containers are "just" regular processes. Can you spot the runc init process in the output of the ps command?

Hint 9 💡

Much like docker ps, you can use runc list to get the list of running containers.

The runc create command prepared a bunch of Linux namespaces and used the "stub" runc init process to hold them until the Nginx process is started. By default, a new container is fully isolated from the outside world, and the only network interface that exists in its network namespace is the loopback (lo). To be able to access the container from the host, configure the virtual Ethernet pair 192.168.0.1 <-> 192.168.0.2 and move its upper end into the container's network namespace.

Hint 10 💡

Feeling lost? No worries, container networking is a large topic. Try solving Reproduce a Docker Bridge Network Using Basic Linux Commands challenge first.

Hint 11 💡

There is a good chance that the ip netns command won't see the network namespaces created by runc. Here is a trick to make it work:

mkdir -p /run/netns
ln -sT /proc/$RUNC_INIT_PID/ns/net /run/netns/$CONTAINER_ID

Finally, start the container!

Hint 12 💡

Try running runc start and follow the dynamic hints from the solution checker.

Hint 13 💡

An attempt to start the container failed, and now you're getting the cannot start a container that has stopped error? Remove and re-create the container. But please use the same ID to make the solution checker happy.

Cleanup of the resources is an important part of the container lifecycle. Now, when the container served its purpose, it's time to terminate it.

Hint 14 💡

What command is used in Linux to send a signal to a process? runc has an eponymous subcommand to signal containerized processes.

Has the bundle directory been removed as well? Make sure the disk space is not wasted on the unused container filesystems.

Categories: Containers
Discussion:  Discord

Level up your server-side game — Join 6,400 engineers who receive insightful learning materials straight to their inbox