開発環境のコンテナ実行環境をモダンに構築する

# 動機

1. そういえば新環境に Docker 入れてないなあ
2. そういえば Container Runtime って色々あるらしいよね
3. せや！ **Docker やめたろ！**

という訳で Docker をやめてみるという話です.

# 謝辞

多くの情報をこの記事から得ています. 感謝.
執筆時現在 (2024/01/24), 最終更新は 2023/11/14 です (i.e. 割と最近の情報なんじゃないかな).

https://zenn.dev/ttnt_1013/articles/f36e251a0cd24e

# 環境

環境は ALARM (Arch Linux ARM) on Asahi Linux です. 雑になんか色々貼っておきます.

```:uname -a
Linux alarm 6.5.0-asahi-15-1-edge-ARCH #2 SMP PREEMPT_DYNAMIC Thu, 12 Oct 2023 08:22:22 +0000 aarch64 GNU/Linux
```

```:neofetch
                   -`                    nanai@alarm
                  .o+`                   -----------
                 `ooo/                   OS: Arch Linux ARM aarch64
                `+oooo:                  Host: Apple MacBook Pro (16-inch, M1 Pro, 2021)
               `+oooooo:                 Kernel: 6.5.0-asahi-15-1-edge-ARCH
               -+oooooo+:                Uptime: 10 hours
             `/:-:++oooo+:               Packages: 496 (pacman)
            `/++++/+++++++:              Shell: zsh 5.9
           `/++++++++++++++:             Resolution: 3456x2160
          `/+++ooooooooooooo/`           WM: sway
         ./ooosssso++osssssso+`          Terminal: alacritty
        .oossssso-````/ossssss+`         CPU: (10) @ 2.064GHz
       -osssssso.      :ssssssso.        Memory: 7392MiB / 15362MiB
      :osssssss/        osssso+++.
     /ossssssss/        +ssssooo/-
   `/ossssso+/:-        -:/+osssso+-
  `+sso+:-`                 `.-/+oso:
 `++:.                           `-/+/
 .`                                 `/ 
```

~~Firefox がメモリバカ食いしてるのがバレますね〜.~~

# containerd と runC の話

containerd と runC はどちらとも `docker` において規定で使用される Container Runtime です.

https://github.com/containerd/containerd

https://github.com/opencontainers/runc

特筆することはありません. Go で書かれています. ちゃんと動きます. みんな使ってます (多分).

Arch なら `docker`<sup>[x86_64 extra](https://archlinux.org/packages/extra/x86_64/docker/)</sup> で入ると思います (うろ覚え). rootless にしたいなら AUR の `docker-rootless-extras`<sup>[aur](https://aur.archlinux.org/packages/docker-rootless-extras)</sup> を入れればすんなり動きます (体験談). あと `docker-credential-pass`<sup>[aur](https://aur.archlinux.org/packages/docker-credential-pass)</sup> とか入れておくと幸せになれます (体験談). 兎にも角にも 聖書<sup>[[ArchWiki](https://wiki.archlinux.org/) のこと]</sup> を読んでおけばどうにかなります (世界の理). 大丈夫です, works well です.

https://wiki.archlinux.org/title/Docker

# CRI-O の話

CRI-O は立ち位置としては dockerd と同様です. 思想の違いとしては OCI[^oci] の最低限の機能を実装するとしている点と, 内部の設計が若干違う点らしいです.

[^oci]: https://opencontainers.org/

https://github.com/cri-o/cri-o

`pacman` から `cri-o`<sup>[x86_64 extra](https://archlinux.org/packages/extra/x86_64/cri-o/)</sup> は簡単に導入できますし動作しますが, rootless にしないことには始まらない[^about-rootless] ので, これを試してみます.

[^about-rootless]: 特権持ちとして container を動かすと root 持ちのファイルが散乱するので嫌なんですよね. セキュリティの観点とかは詳しく知らないのでパス… 強いて言えば, container から突然 `rm -rf /` とかされたらキレるよね, みたいな部分もある.

ただ, 文献が殆ど無い. 公式の `man 5 crio.conf`[^crio-conf] を読んで頑張ってみることに.

[^crio-conf]: https://man.archlinux.org/man/crio.conf.5

で出来上がった 1 つ目の産物がこちら. ちなみにちゃっかり runC じゃなくて crun (後述) を使うように変更してます.

```sh
sudo setcap cap_chown,cap_sys_admin=ep /usr/bin/crio

env _CRIO_ROOTLESS=1 crio                                                         \
    --listen                      $XDG_RUNTIME_DIR/crio/crio.sock                 \
    --default-runtime             crun                                            \
    --log-dir                     $HOME/log-crio-pods                             \
    --root                        /tmp/crio-root                                  \
    --runtimes                    crun:::oci:false:                               \
    --container-attach-socket-dir $XDG_RUNTIME_DIR/crio                           \
    --container-exits-dir         $XDG_RUNTIME_DIR/crio/exits                     \
    --version-file                $XDG_RUNTIME_DIR/crio/version                   \
    --clean-shutdown-file         $XDG_RUNTIME_DIR/crio/clean.shutdown            \
    --storage-driver              overlay                                         \
    --storage-opt                 "overlay.mount_program=/usr/bin/fuse-overlayfs"
```

悪戦苦闘の経緯:

1. Permission Denied が頻発した
  s. 規定で特権の必要なディレクトリ構成になっているので, 全部 `$XDG_RUNTIME_DIR` に押し込めた
  r. それ自体は動いた
2. chown が出来ないとか mount が出来ないとか言われた
  s. capabilities の問題かと思い, `cap_chown,cap_sys_admin=ep` を付与
  r. 動いた
3. 結局なんかうまく動かなかった
  s. rootlesskit を使ってみることに

という訳で rootlesskit を使ってみた版が〜… コマンドを紛失しました. ガチうろ覚えですがこんな感じ…

```sh
wget https://raw.githubusercontent.com/cri-o/cri-o/2ead2413a7987de2e8a975f8a509233ce7bdcd47/contrib/cni/11-crio-ipv4-bridge.conflist
sudo mv 11-crio-ipv4-bridge.conflist /etc/cni/cfg.d

rootlesskit \
    --copy-up /run            \
    --copy-up /var/cache/crio \
    --copy-up /var/run/crio   \
    crio                      \
        --listen                      $XDG_RUNTIME_DIR/crio/crio.sock \
        --default-runtime             crun                            \
        --runtimes                    crun:::oci:false:
```

1. ファイルを書き込めないとか騒ぎ始めた
  s. 全部 `--copy-up` に指定した
  r. 起動まで漕ぎ付いた
1. pod 作成時に cni 関係が準備できないと言われた
  s. [適当な cni の設定]((https://github.com/cri-o/cri-o/tree/2ead2413a7987de2e8a975f8a509233ce7bdcd47/contrib/cni))を落として配置した
  r. 動いた
1. container 作成時に pinns に関する ns file が作れないとか言われた
  s. 無理

pin ってなんすか (無知). いや Linux の namespace 絡みなんですけども. 驚くほど文献が無かった… 1 作業日くらい丸ごと費やしましたが動作までは辿り着けませんでした… ぐぬぬ. 仕方ない.

後出しですが<sub>1</sub> rootlesskit は Arch だと `rootlesskit`<sup>[x86_64 extra](https://archlinux.org/packages/extra/x86_64/rootlesskit/)</sup> にあります. 先に挙げた `docker-rootless-extras`<sup>[aur](https://aur.archlinux.org/packages/docker-rootless-extras)</sup> で内部的に使われていたりします. 

後出しですが<sub>2</sub> CLI には `crictl` を使っていました (Arch なら `crictl`<sup>[x86_64 extra](https://archlinux.org/packages/extra/x86_64/crictl/)</sup>).

https://github.com/kubernetes-sigs/cri-tools

これにて私の CRI-O + crun という浪漫, 野望, 願望は潰えたのでした. おしまい.

# Container Runtime の話

~~`container.conf` のキーを見るに, Runtime は containerd らを指すのに対して runC らは *engine*, Container Engine な気もするのだが, ちょっと詳しいことは分からないので割愛.~~

## runC [Go]

runC は前述した通り Go で書かれており, `docker` における規定の Container Runtime です. Arch では `crun`<sup>[x86_64 extra](https://archlinux.org/packages/extra/x86_64/runc/)</sup> が提供しています.

https://github.com/opencontainers/runc

## crun [C]

crun は C で書かれた Container Runtime です. README に記載のある公式ベンチマークを見るにめっちゃ速いです. すげー. ただ production ready ではないらしい. Arch では `crun`<sup>[x86_64 extra](https://archlinux.org/packages/extra/x86_64/crun/)</sup> が提供.

https://github.com/containers/crun

## scrun (gVisor) [Go]

scrun, もとい gVisor の Container Runtime は Go で書かれています. Arch なら AUR に `gvisor-bin`<sup>[aur](https://aur.archlinux.org/packages/gvisor-bin)</sup> があります. ただ runC などと違うのは, scrun もとい gVisor が runC らとは異なるアプローチで抽象化を行っており, 非常にセキュアな設計である事だとか.

https://github.com/google/gvisor

ちなみに弊環境では動きませんでした. ちゃんと設定すれば動いたのかもしれません.

```
panic: Only 4K page size is supported on arm64!

goroutine 1 [running]:
gvisor.dev/gvisor/pkg/hostarch.init.0()
    pkg/hostarch/hostarch_arm64.go:59 +0x44
```

## youki [Rust]

youki は Rust で書かれた Container Runtime です. crun に並んで高速ですが, それよりも若干遅いらしい (その分コンパイラによってメモリ安全性が担保されていると思いたい). こちらも多分 production ready ではないのだと思います (知らんけど). Arch だと AUR に `youki`<sup>[aur](https://aur.archlinux.org/packages/youki)</sup> があります.

https://github.com/containers/youki

---

さて, runC は試してませんがきっと動くんでしょう (弊環境では未確認). scrun は先に述べたとおり動かず. crun と youki は動くことを雑に確認しましたが, 私は Rustacean なので youki を使ってみます. 今の所問題には遭遇してません.

# Podman + youki の話

Podman は Container Runtime で containerd や CRI-O と同様の立ち位置ですが, daemon-less である点が非常に特徴的な設計となっています. ところで, 元を辿ると Podman は crun と同じ RedHat 社絡みのプロジェクトらしいです. Arch なら `podman`<sup>[x86_64 extra](https://archlinux.org/packages/extra/x86_64/podman/)</sup> が提供しています.

https://github.com/containers/podman

Podman は規定で crun を使いますが, youki を使いたいので規定の Container Runtime に設定してみましょう. rootless なら `$HOME/.config/containers/containers.conf` を読んでくれるらしい[^containers-conf] のでここに設定を記入します.

[^containers-conf]: https://man.archlinux.org/man/containers.conf.5

```toml:.config/containers/containers.conf
[engine]
runtime = "youki"
```

おっけー. これだけ. いざ実行.

```sh
podman run -it alpine:latest busybox sh
```

動いた. easy ですね.

ちなみに弊環境は aarch64 なので `archlinux` は動かないのです (というか pull が拒否される). そして, 有志の `docker.io/menci/archlinuxarm`[^menci-archlinuxarm] があったりするので, ALARM on ALARM しましょう.

[^menci-archlinuxarm]: https://hub.docker.com/r/menci/archlinuxarm

```sh
podman run -it docker.io/menci/archlinuxarm sh
```

動きました (地味).


# 他の話

rkt が使いたかった. 既に EoL が宣言されています.

https://github.com/rkt/rkt

# 後記

もうちょっと *ドラマチック* な環境構築になるかと期待したのですが, 結局 rootless OCI-O は頓挫してしまったので悔いが残る感じです. ちょっと残念. 誰か教えてください.

最近は別プロジェクトで Linux の共有メモリとかに触れていましたが, 常用かリッチアプリケーション程度の開発では触れない機能, 知らない機能が沢山あるなーと思います (今回だと Linux の仮想化技術). まあこれを直接触って運用… という機会は中々無いと思いますが, 詳しく知りたいですね.

# 参考

読んで参考になったなーってものを羅列しておきます. 

https://qiita.com/mamomamo/items/ed5db2ab1555078f8a24

https://qiita.com/cfg17771855/items/0c0f5df26ffcfe18a74d

https://qiita.com/Alaska_Panda/items/30a6b0fdc2cf455b6d5a

https://qiita.com/literalice/items/6ec471501f0207dbe3c9

https://docs.daper.io/flatcar/crio