runcコマンドでnginxを立ち上げる
概要
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に近いネットワーク構成になったと思う.
Discussion