😎

runcコマンドでnginxを立ち上げる

2023/03/17に公開

概要

runcのコードを読んでみたいというモチベーションが湧いたのでその前にどのような挙動をするのかを把握するために直接runcを使ってnginxを立ち上げる. 公式も参考になるがshを起動するだけで物足りなかったのでnginxで試してみた.

準備

umoci, skopeoを使うのでインストール.
それぞれのコマンドに対する自分の認識. 最低限しか使ってないので恐らく他にも用途はある.
umoci: OCI準拠のイメージをファイルシステムに展開する
skopeo: DockerイメージをOCI準拠のイメージに変換してダウンロード

sudo apt-get install umoci skopeo

実践

1. DockerイメージをOCI準拠のイメージに変換

skopeoコマンドでDockerイメージをOCI準拠のイメージに変換してダウンロード. そしてumociコマンドでOCI準拠のイメージをファイルシステムに展開する. bundle/rootfs以下に展開される.

skopeo copy docker://nginx:latest oci:nginx-image:latest
sudo umoci unpack --image nginx-image bundle 

// 確認
tree -L 2 bundle
bundle
├── config.json
├── rootfs
│   ├── bin
│   ├── boot
│   ├── dev
│   ├── docker-entrypoint.d
│   ├── docker-entrypoint.sh
│   ├── etc
│   ├── home
│   ├── lib
│   ├── lib64
│   ├── media
│   ├── mnt
│   ├── opt
│   ├── proc
│   ├── root
│   ├── run
│   ├── sbin
│   ├── srv
│   ├── sys
│   ├── tmp
│   ├── usr
│   └── var
├── sha256_1fbb850f12af0a4d2bb020ef8aa7b14eb84b9fb67ba1ba731150e9aefc93ba04.mtree
└── umoci.json

21 directories, 4 files

2. config.jsonを編集

bundle/config.jsonを編集する. デフォルトのままだとCapabilityの設定が足りてないため, 以下Capabilityを付与する.

  • CAP_SETUID
  • CAP_SETGID
  • CAP_CHOWN

以下のようなイメージ.

"capabilities": {
        "bounding": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
		"CAP_SETUID",
		"CAP_SETGID",
                "CAP_CHOWN"
        ],
        "effective": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
		"CAP_SETUID",
		"CAP_SETGID",
                "CAP_CHOWN"
        ],
        "inheritable": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
		"CAP_SETUID",
		"CAP_SETGID",
                "CAP_CHOWN"
        ],
        "permitted": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
		"CAP_SETUID",
		"CAP_SETGID",
                "CAP_CHOWN"
        ],
        "ambient": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
		"CAP_SETUID",
		"CAP_SETGID",
                "CAP_CHOWN"
        ]
},

3. nginxイメージを起動

以下コマンドでnginxイメージが立ち上がる. ただしこれはnetnsが切り離された場所で起動しているため, このままだとローカルからアクセスすることができない. そのためnetnsの設定をする必要があるが, それについては後述する.

もしくはconfig.jsonでlinux.namespaces.type: "network"の設定がある. これを削除すればホストと同じnetnsを使うことになるため, その設定で立ち上げてからlocalhost:80を叩けばレスポンスが返ってくる.

cd bundle

sudo runc run nginx-container
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/03/17 01:29:22 [notice] 1#1: using the "epoll" event method
2023/03/17 01:29:22 [notice] 1#1: nginx/1.23.3
2023/03/17 01:29:22 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/03/17 01:29:22 [notice] 1#1: OS: Linux 5.15.0-1031-aws
2023/03/17 01:29:22 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1024:1024
2023/03/17 01:29:22 [notice] 1#1: start worker processes
2023/03/17 01:29:22 [notice] 1#1: start worker process 21
2023/03/17 01:38:09 [notice] 1#1: signal 28 (SIGWINCH) received
2023/03/17 01:38:09 [notice] 21#21: signal 28 (SIGWINCH) received

4. コンテナ用のnetnsの設定

ここの操作は別ターミナルで行う. まずはコンテナのPIDを確認.

sudo runc list
ID                PID         STATUS      BUNDLE                          CREATED                          OWNER
nginx-container   7696        running     /home/ubuntu/workspace/bundle   2023-03-17T01:29:22.670909233Z   root

PID=7696

このコンテナのnetnsは/proc/$PID/ns/netにあるため, ipコマンドで管理できるようにするためには/var/run/netns以下にシンボリックを貼る.

sudo ls /proc/$PID/ns/net
/proc/7696/ns/net

sudo ln -s /proc/$PID/ns/net /var/run/netns/container_netns

// ipコマンドで確認できるようになる
ip netns
container_netns

あとはhostのnetnsとcontainer_netnsを繋ぎこむだけ. 以下にコマンドだけ示すのでこの辺りがわからないのであればip netnsコマンドで学ぶNetwork Namespaceを読むと良い.
ちなみにコンテナのpidがわかっているためip netns exec ${netns_name}コマンドはnsenterでも代用できる.

# vethペアを作る
sudo ip link add name veth0 type veth peer name container_veth0

# container_veth0をcontainer_netnsにアタッチ
sudo ip link set dev container_veth0 netns container_netns

# IPアドレスを付与
sudo ip netns exec container_netns ip addr add 192.168.10.1/24 dev container_veth0
sudo ip addr add 192.168.10.2/24 dev veth0

# リンクup
sudo ip netns exec container_netns ip link set dev container_veth0 up  
sudo ip link set dev veth0 up

5. 疎通確認

4の手順でコンテナネットワークと繋がったため疎通確認してみる.

curl 192.168.10.1:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

できた.

番外

手順3でconfig.jsonのlinux.namespaces.type: "network"の設定を削除すればhostのnetnsとコンテナのnetnsを共有するため手順5が不要になると述べたが本当にその通りかを確認する.
config.jsonのlinux.namespacesが以下のようになっているので"type": "network"を削除する.

"linux": {
...
        "namespaces": [
                {
                        "type": "pid"
                },
                {
                        "type": "network"
                },
                {
                        "type": "ipc"
                },
                {
                        "type": "uts"
                },
                {
                        "type": "mount"
                }
        ],
...

そしてruncコマンドでコンテナを起動後に別ターミナルでlocalhostにリクエストを投げる.

sudo runc run nginx-container

// 別ターミナルにて
curl localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

これはdockerコマンドでnginxコンテナを起動した場合, 以下の手順と同じ挙動になる.

sudo docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
4798c648cee0   bridge    bridge    local
e7beb828488c   host      host      local
13b6e93a57f9   none      null      local

// hostネットワークを使うとnetnsを分離しない
sudo docker run -d --name nginx --net=host nginx:latest

// netnsが分離されないため, localhost:80でnginxが起動していることがわかる
curl localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

所感

runcの挙動やconfig.jsonなどのそれぞれの設定値の意味の雰囲気は掴むことができたと思う.
今回は簡単のためhostとコンテナ用netnsを直接つないだが, dockerコマンドを使わずにコンテナを作る - 2のようにブリッジを作ってネットワークを構成するとよりdockerに近いネットワーク構成になったと思う.

参照

ip netnsコマンドで学ぶNetwork Namespace

Discussion