Docker Container 内で systemd を使う方法

2023.05.10

最初に

アノテーション メンテナンスチームの shinonome です。
今回は systemd を Docker Container 内でも使いたい場合の実現方法の記事になります。

この記事は、cgroup v2 下で稼働する前提で、Debian 11 / Ubuntu 22.04 Cloud Image で検証済みの内容になります。
※Docker の Rootless と Rootful mode に若干違いはありますが、どちらも稼働出来ます。

なお、通常の macOS の Docker 環境(Docker Desktop、Rancher Desktop など)では今回の内容はやりづらいと思います。
実践する際は下記の記事を参考に、自分が管理できる Linux (w/ Docker engine) 環境を先に立ててください。

準備作業 (Linux part)

まずは Docker systemd service unit ファイルの Service 配下に Slice=docker.slice を追加後、docker service を再起動してください。
※こちらはこの後のコンテナー起動パラメーターの cgroup-parent に使われます。
※変化が気になる方は事前に systemctl status で変更前の状況を確認しましょう。

...
[Service]
...
Slice=docker.slice
...
# rootless
vim ~/.config/systemd/user/docker.service # add slice
systemctl --user daemon-reload
systemctl --user restart docker
systemctl --user status docker.slice
systemctl --user status # optional

# rootful
vim /lib/systemd/system/docker.service # add slice
systemctl daemon-reload
systemctl restart docker
systemctl status docker.slice
systemctl status # optional

準備作業 (Dockerfile part)

次は Dockerfile を準備しましょう。

大まかな内容は:

  • systemd 用の停止信号を指定
  • systemd をインストールして、不要な一時ファイル等を削除
  • Container 内に起動不要な systemd units を全て解除
  • 必要な分は残す(もしくは再起用)、例えば journald
  • Container 起動コマンドを systemd に指定
ARG TAG="latest"
FROM debian:${TAG}

ENV container docker
ENV LC_ALL C
ENV DEBIAN_FRONTEND noninteractive

# match the stop signal to systemd's.
STOPSIGNAL SIGRTMIN+3

USER root
WORKDIR /root

RUN apt-get update -y \
    && apt-get install --no-install-recommends -y systemd \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
    # allow login
    && rm -f /var/run/nologin

# remove all unnecessary service.
RUN rm -f \
    /etc/systemd/system/*.wants/* \
    /lib/systemd/system/*.wants/*

# optional: enable journald.
# known issus: audit won't work under rootless env due to permission.
RUN cd /lib/systemd/system \
    && ln -s ../systemd-journald.socket ./sockets.target.wants/systemd-journald.socket \
    && ln -s ../systemd-journald-audit.socket ./sockets.target.wants/systemd-journald-audit.socket \
    && ln -s ../systemd-journald-dev-log.socket ./sockets.target.wants/systemd-journald-dev-log.socket \
    && ln -s ../systemd-journald.service ./sysinit.target.wants/systemd-journald.service \
    && ln -s ../systemd-journal-flush.service ./sysinit.target.wants/systemd-journal-flush.service

VOLUME [ "/tmp", "/run", "/run/lock" ]

CMD [ "/lib/systemd/systemd" ]

起動

Dockerfile で Image を構築してから、記載したパラメーターでコンテナーを起動 (例は detach mode) してください。
※log 確認 / bash 呼び出し / 停止用の参考コマンドも記載しました。
※記載したコマンドでは、イメージ名をフォルダー名にしていますが、適宜変更してください。

docker build -t $(basename $PWD)-local .
docker run -d -t \
  --privileged \
  --cap-add CAP_SYS_ADMIN --cap-add CAP_MKNOD \
  --cgroup-parent=docker.slice --cgroupns private \
  --tmpfs /tmp --tmpfs /run --tmpfs /run/lock \
  --name systemd_container $(basename $PWD)-local:latest
docker logs -f -t systemd_container
docker exec -it systemd_container bash -c "systemctl status"
docker container stop systemd_container

※rootless の場合、権限が足りないため、systemd-journald-audit.socket の起動は失敗になり、systemd の状態は degraded になりますが、コンテナーを使うには特に支障はないと思います。
※動作確認時点のコンテナー内の systemd バージョン: systemd 247.3-7+deb11u1

最後に

今回は、Docker Container 内で systemd を使う方法を紹介しました。
Container 内でも systemd でサービスを管理したい場合、是非試してみてください。

本ブログ、お役に立てると光栄です。

参考情報

おまけに

cgroup v1 での実現もありますが、方法は若干違います。
v1 環境での方法はネット上にいっぱいありますので、必要でしたら検索してみてください。
例えば:https://developers.redhat.com/blog/2016/09/13/running-systemd-in-a-non-privileged-container

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。