linuxdockeralpine-linuxopenrc

Is there a better way to run openrc in a container than enabling 'softlevel'?


Context

I am running alpine linux in a Balena container composition. This is not your typical docker usecase. Instead, Balena deploys docker container compositions to embedded software as an easy way to deploy complex applications on small, idiosyncratic devices.

In that context, there is a lot of freedom. Docker is used more as an application module, and the balena OS is designed to handle this.

So, an atypical use case for docker containers is to run systemctl/systemd services in a container; but this represents a very typical use case for a Balena deployment when porting a normal, PC-centric application to the Balena world.

For the purposes of this question, I am unable to change code, and running services is an absolute requirement. Is is exactly what needs to be done, and the question is: what is the best way to do it on alpine? (I've already got it working on debian w/ systemctl)


With that in mind...

I can get services running over openrc, alpine's analog to systemctl|systemd in debian. However, in order to do it, I am enabling softlevel mode in the docker container:

touch /run/openrc/softlevel

Rather than "booting" from openrc. However, openrc itself comes with a bright yellow warning line obtained from within the running docker container:

/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/blkio/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/cpu/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/cpuacct/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/cpuset/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/devices/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/freezer/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/hugetlb/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/memory/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/misc/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/net_cls/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/net_prio/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/perf_event/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/pids/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/rdma/tasks: Read-only file system
/lib/rc/sh/openrc-run.sh: line 108: can't create /sys/fs/cgroup/systemd/tasks: Read-only file system
 * You are attempting to run an openrc service on a
 * system which openrc did not boot.
 * You may be inside a chroot or you may have used
 * another initialization system to boot this system.
 * In this situation, you will get unpredictable results!
 * If you really want to do this, issue the following command:
 * touch /run/openrc/softlevel

Is this fine, or is there a better approach to getting openrc running in docker? Does it even matter? Or will it just work?

Why I am not sure...

Ideally, I'll keep the CMD|ENTRYPOINT arguments to the docker container clean so it is as usable as possible, with openrc working as is, if possible.

ENTRYPOINT specifies the command to run when the container starts. If the Dockerfile has both ENTRYPOINT and CMD instructions, the CMD arguments are passed as arguments to the ENTRYPOINT command. You can override the ENTRYPOINT command by specifying a new command when you run the container.

This explains that, even if I get the docker container set up correctly, it will not behave as intended if the CMD/ENTRYPOINT docker commands contain some openrc startup invective. That means downstream usage of this container with ENTRYPOINT overloads in scripts, or by myself some time from now will break unless I know to include the correct openrc abra-cadabra.

Instead, maybe softlevel mode is exactly what I want to enable if I am running in the balenaOS with openrc, and ENTRYPOINT will often be overridden.

But I have no reason other than testing and seeing if it works to believe that is the case.

What would be the best way to run services with openrc in a docker container given the context?


Solution

  • A container doesn't really "boot". You simply choose what process starts when the container starts -- maybe that's a process manager, like openrc, but more generally it's a specific service.

    If we're designing a container to work with openrc, we could start with something like this:

    FROM docker.io/alpine:latest
    
    RUN apk add openrc mdevd-openrc
    RUN sed -i '/getty/d' /etc/inittab
    
    CMD ["/sbin/init"]
    

    A container that runs a couple of services under the control of openrc might look like this:

    FROM openrc-base
    
    RUN apk add openssh-server openssh-server-common-openrc darkhttpd darkhttpd-openrc 
    RUN rc-update add sshd default && rc-update add darkhttpd default
    
    RUN mkdir -p -m 700 /root/.ssh
    COPY id_rsa.pub /root/.ssh/authorized_keys
    RUN chmod 600 /root/.ssh/authorized_keys
    

    If we build an image from that Dockerfile and run it...

    docker run --rm -p 2200:22 -p 8080:80 openrc-multiservice
    

    ...we'll see output like this:

     * /proc is already mounted
     * /run/openrc: creating directory
     * /run/lock: correcting mode
     * /run/lock: correcting owner
       OpenRC 0.52.1 is starting up Linux 6.7.10-200.fc39.x86_64 (x86_64)
    
     * Caching service dependencies ... [ ok ]
     * /var/log/darkhttpd: correcting owner
     * Starting darkhttpd web server ... [ ok ]
    ssh-keygen: generating new host keys: RSA ECDSA ED25519
     * Starting sshd ... [ ok ]
    

    No errors, I didn't have to enable "softlevel" mode, and our services start up as expected. But maybe someone only wants to run a single service without using openrc! In that case, they can override CMD on the command line:

    docker run --rm -p 8080:80 openrc-multiservice darkhttpd /etc --port 80
    

    That works, too.


    In general, images based on our openrc image should be designed with the fact that openrc is an option in mind (they should install rc files for any provided services, etc), but they can have any ENTRYPOINT script they want as long as it respects CMD and ultimately starts /sbin/init.

    At the same time, they can support single-service instances as well.