TenForward

技術ブログ。はてなダイアリーから移転しました

技術書典13に出典し、「Linux Container Book (1) Namespace / Network 編」という本を出しました

gihyo.jp で連載している「LXCで学ぶコンテナ入門」やこのブログ、zenn.dev の記事をベースに Linux コンテナの本を書き、技術書典13で「Linux Container Book (1) Namespace/Network編」という本を出しました。

techbookfest.org

技術書典13は終了しましたが、電子版はいつでもご購入いただけますので、ぜひお買い求めください。

この本では Linux でコンテナを作成する場合に使われる Linux カーネルの機能のうち、Namespace とネットワーク関連の機能について説明しています。

Docker 動かしてるけどどんな仕組みで動いているんだろう?と思う方、コンテナランタイムを作るぞという方、その他興味を持たれた方は是非お買い求めいただくとうれしいです。

買うほどでないなあ、という方は是非 gihyo.jp さんで行っている連載だけでもご覧ください(現時点で古い記事が一覧で表示されないんですけど…)

正誤表

見つかり次第追加します。電子版は、後日更新版を技術書典のオンラインショップに上げます(紙版の入稿時点で電子版を更新予定です)。

  • 紙版は次の表で「紙版で修正済」としているものは修正した上で入稿しています(2022-09-29)
  • 電子版は「電子版で修正済」としているものは記載したバージョンで修正しています(2022-09-29)

電子版は技術書典の「自分の本棚」から最新版をダウンロードできますので、お買い上げ頂いた方は最新版の取得をお願いします。

場所 備考 修正
P.39, P.45 propagete_from propagete_from P.39下から2行目、P.45 4.6節の1行目
P.16 脚注 2) あとで説明するケーパビリティからchrootを行う特権 CAP_SYS_CHROOT を落とします。 ケーパビリティからchrootを行う特権 CAP_SYS_CHROOT を落とします。ケーパビリティについては本書では説明しません。 ケーパビリティについては後日執筆予定の続編で説明する予定です 紙版、電子版v1.0.1で修正済
P.46 脚注の 2) が 2 つありました P.46の脚注の 1), 2) は P.45 の表中に対する脚注です。2 つ目の 2) は P.46 中盤にある 2) に対する脚注です 紙版、電子版v1.0.1で修正済
第 3 章序盤 一部のみ読点が「,」になっていました 紙版、電子版v1.0.1で修正済
P.7 つまり、図 1 のように物理的なコンピュータ上で動く OS 上でさらに OS が動きます つまり、図 2.1 のように物理的なコンピュータ上で動く OS 上でさらに OS が動きます 図の番号が間違っていました 紙版、電子版v1.0.1で修正済
P.13 一般的には図1 のように 一般的には図2.1 のように 図の番号が間違っていました 紙版、電子版v1.0.1で修正済
P.57 例えば、図のように Namespace 内では UID、GID が共に 0 の root ユーザーを 例えば、図9.1のように Namespace 内では UID、GID が共に 0 の root ユーザーを 図番号を追加しました 紙版、電子版v1.0.1で修正済
P.70 図では macvlan0 には mac0 という MAC アドレスが 図10.2では macvlan0 には mac0 という MAC アドレスが 図番号を追加しました 紙版、電子版v1.0.1で修正済
P.22 表1に示した Namespace を単独で指定したり 表3.1 に示した Namespace を単独で指定したり 表番号が間違っていました 紙版、電子版v1.0.1で修正済
P.45 プロパゲーションがどのように変化するかを表2にに示します プロパゲーションがどのように変化するかを表4.4にに示します 表番号が間違っていました 紙版、電子版v1.0.1で修正済
第9章 紐付け ひもづけ 「ひもづけ」が漢字表記担っている部分があったので表記を合わせました 紙版、電子版v1.0.1で修正済
第9章 ネットワーク Namespace Network Namespace 一部だけ異なる表記になっていたので本の他の部分と合わせました 紙版、電子版v1.0.1で修正済

オフライン出展

9/11 のオフライン会場にも出展しました。当日お越しいただいた方ありがとうございました。

技術書典13

この日は物理本の印刷はせず、見本誌を展示していただけですが、多数の方に手に取っていただいてお買い上げいただきました。

少しお話ができた方もいて、楽しかったですし、見本誌を見ていただいているのを見てるだけでも楽しかったです。

展示の模様

docker-compose.yaml の volumes の指定でハマった話

私はDockerもDocker Composeもcontainerdも素人で、今回の問題がよく知られた問題なのか、そうでないのかもよく知りません。ちなみにDocker Composeは今日まで使ったことがなかったし、Rancher Desktopも今日はじめてインストールしました(^^)。

この辺りのコンテナランタイムの知識もないので、用語の使い方も間違ってたりするかもしれませんがご容赦を。

このエントリは単に「ちょっと時間使って調べた」ので「オレ頑張ったよ」と誰かに言わないと気がすまないし、最近技術ブログ書いてないよなと思ったので書いてみるだけです。

問題

「Sensu Goをコンテナで起動してるけど、コンテナを再起動するたびに初期化されるので、Sensuに詳しいだろうからちょっと見て」という相談を受けたのが始まりです。(ちなみにSensu Go詳しくないです。Sensu Goを使ったシステムをちょっと作ったことがありますが)結局は Sensu の知識は全く不要でした。

Rancher Desktop環境で、Sensu Goの公式ドキュメントにあるDocker Composeを使ってsensu-backend(sensuサーバー)を起動すると、再起動するたびにetcdのデータが初期化されているのか、毎回クライアント側も初期設定する必要があるということでした。

ちなみに yaml はこんな。

version: "3"
services:
  sensu-backend:
    ports:
    - 3000:3000
    - 8080:8080
    - 8081:8081
    volumes:
    - "sensu-backend-data:/var/lib/sensu/sensu-backend/etcd"
    command: "sensu-backend start --state-dir /var/lib/sensu/sensu-backend --log-level debug"
    environment:
    - SENSU_BACKEND_CLUSTER_ADMIN_USERNAME=admin
    - SENSU_BACKEND_CLUSTER_ADMIN_PASSWORD=admin
    image: sensu/sensu:latest

volumes:
  sensu-backend-data:
    driver: local

解決

問題自体はすぐに解決しました。手元に Rancher Desktop がなかったので、Linux 上の Docker 環境で docker-compose.yaml を書いて起動してみました。

しかしどのようにやっても、ちょっとコンテナを強制終了しても、特に再起動後は問題なく使えます。で、このことを相談を受けた人に伝えると、すぐに

  • Linux(のDocker)でやったらたしかに問題は起こらない
  • 試しに Rancher Desktop のコンテナランタイムを "containerd" にしていたのを "dockerd(moby)" に切り替えたら問題が起こらなくなった

というわけで、これで解決です。

問題を再燃させる

でも、気になりますよね、これ!ということであえて Rancher Desktop のコンテナランタイムを "containerd" に切り替えて調査!

ここで引き下がるのはなんか悔しいってことで色々調べてみました。調べたといってもほとんど Docker Compose の YAML の書き方とか、そもそも「ボリュームってなんやねん」とか、皆無だった基礎知識のあたりを調べてた時間がほとんどですが。

まず、最初に気づいたのは起動時に Warning が出ていること。

% nerdctl compose up -d
INFO[0000] Creating network sensu_default
WARN[0000] Ignoring: volume sensu-backend-data: [Driver] <-(コレ)
INFO[0000] Ensuring image sensu/sensu:latest
INFO[0000] Creating container sensu_sensu-backend_1

もしかして、ボリュームがうまくできてないの?とか思ったけど、これは文字通りWarningで関係ないことが後で判明します。多分ですが、

ココ

Unimplemented docker volume create flags: --driver, --opt

と書かれてるので、これが原因でしょう。

色々調べてると、どうやら再起動するたびにボリュームが新たに作られていってることに気づきます。これだったら、毎回新たに etcd のデータが作られるので、毎回初期設定が必要になるのもわかります。こんな感じ。再起動のたびに新たにボリュームが作られています。

% nerdctl volume list               
VOLUME NAME    DIRECTORY
% nerdctl compose up -d
INFO[0000] Creating network sensu_default               
WARN[0000] Ignoring: volume sensu-backend-data: [Driver] 
INFO[0000] Creating volume sensu_sensu-backend-data     
INFO[0000] Ensuring image sensu/sensu:latest            
INFO[0000] Creating container sensu_sensu-backend_1     
% nerdctl volume list  
VOLUME NAME                                                         DIRECTORY
9a3721923ba55334655208752d48846585c9072f11bce8f1e7fa9f2a4a5074a0    /var/lib/nerdctl/dbb19c5e/volumes/default/9a3721923ba55334655208752d48846585c9072f11bce8f1e7fa9f2a4a5074a0/_data
sensu_sensu-backend-data                                            /var/lib/nerdctl/dbb19c5e/volumes/default/sensu_sensu-backend-data/_data
% nerdctl compose down              
INFO[0000] Removing container sensu_sensu-backend_1     
INFO[0000] Removing network sensu_default               
karma@warbird sensu % nerdctl compose up -d
INFO[0000] Creating network sensu_default               
WARN[0000] Ignoring: volume sensu-backend-data: [Driver] 
INFO[0000] Ensuring image sensu/sensu:latest            
INFO[0000] Creating container sensu_sensu-backend_1     
% nerdctl volume list  
VOLUME NAME                                                         DIRECTORY
sensu_sensu-backend-data                                            /var/lib/nerdctl/dbb19c5e/volumes/default/sensu_sensu-backend-data/_data
0b65bbacb252e1ac8953c2b2903aa7ae94b0f213952776ce568d08bdb8b794d2    /var/lib/nerdctl/dbb19c5e/volumes/default/0b65bbacb252e1ac8953c2b2903aa7ae94b0f213952776ce568d08bdb8b794d2/_data
9a3721923ba55334655208752d48846585c9072f11bce8f1e7fa9f2a4a5074a0    /var/lib/nerdctl/dbb19c5e/volumes/default/9a3721923ba55334655208752d48846585c9072f11bce8f1e7fa9f2a4a5074a0/_data

たぶん、ここは sensu-backend-data という名前のボリュームがひとつだけ作られるのが正解っぽいけど、なぜか謎のボリュームが増殖していくという…

これは何が起こっているのかコンテナ内(コンテナのMount Namespace内)を確認しないとダメだね、ってことで、やってみました。

% nerdctl ps          
CONTAINER ID    IMAGE                           COMMAND                   CREATED          STATUS    PORTS                                                                     NAMES
0eeffa3553b5    docker.io/sensu/sensu:latest    "sensu-backend start…"    2 minutes ago    Up        0.0.0.0:3000->3000/tcp, 0.0.0.0:8080->8080/tcp, 0.0.0.0:8081->8081/tcp    sensu_sensu-backend_1    
% nerdctl exec -ti sensu_sensu-backend_1 -- /bin/sh
/ # cat /proc/self/mountinfo | grep sensu
146 125 253:1 /var/lib/nerdctl/dbb19c5e/volumes/default/sensu_sensu-backend-data/_data /var/lib/sensu/sensu-backend/etcd rw,relatime master:4 - ext4 /dev/disk/by-label/data-volume rw
147 125 253:1 /var/lib/nerdctl/dbb19c5e/volumes/default/0b65bbacb252e1ac8953c2b2903aa7ae94b0f213952776ce568d08bdb8b794d2/_data /var/lib/sensu rw,relatime master:4 - ext4 /dev/disk/by-label/data-volume rw

これでもうわかりましたね。ちゃんと指定のボリュームは作られているものの、別のマウントもされているので、おそらくはそっち側が上にマウントされてる感じかな?

ちなみに名前付きで作ってるボリュームについては、コンテナ内の別のディレクトリにマウントしてみると空でした。こっちが指定どおり sensu-backend-data という名前で作られてないのも気になりますね。("name" 属性を指定したらちゃんとその名前で作られたけど)

匿名のボリュームは /var/lib/sensu にマウントされていることから、コンテナ内に /var/lib/sensu は存在しなくても作ってくれるものの、さらにその子ディレクトリが /var/lib/sensu/sensu-backend/etcd のように指定されていると、コンテナ上にはそのようなディレクトリがないので、まずは /var/lib/sensu を作りに行って、それに新たにボリュームが作成されてマウントされてしまう感じでしょうか。名前付きのボリュームもマウントはされているので、そちらがどのような状態になっているのかが謎…

存在しないディレクトリにはマウントできないし、でも作成された後であとでマウントされたらそっちが見えるはずだし、でもどうやら匿名で作られた /var/lib/sensu にマウントされている方が上で見えているようだし…

というわけで、docker-compose.yaml には、存在しないディレクトリは /var/lib/sensu のように一段だけ指定して無事解決でした。

version: "3"
services:
  sensu-backend:
    ports:
    - 3000:3000
    - 8080:8080
    - 8081:8081
    volumes:
    - sensu-backend-data:/var/lib/sensu
    command: "sensu-backend start --state-dir /var/lib/sensu/sensu-backend --log-level debug"
    environment:
    - SENSU_BACKEND_CLUSTER_ADMIN_USERNAME=admin
    - SENSU_BACKEND_CLUSTER_ADMIN_PASSWORD=admin
    image: sensu/sensu:latest

volumes:
  sensu-backend-data:

まとめると、

  • containerd をランタイムとして使うと、volumesで指定したパスのうち、コンテナ内に存在するディレクトリ(ここでは /var/lib)の下に存在しないディレクトリ(マウントポイント)は、直下のディレクトリ(ここでは /var/lib/sensu まで)は作成してくれる
  • それ以上深いディレクトリを指定しても作ってくれなくて、とりあえず新しいボリュームを作成し、存在するディレクトリの一層下までのディレクトリまで作成し、そこにマウントしてしまう(ここでは /var/lib/sensu
  • 存在しない深いディレクトリ(ここでは /var/lib/sensu/sensu-backend/etcd)を指定すると、それもマウントはしてくれるようだが、その上にさらに上記のマウントを重ねてしまうようなので見えないので結局は使えない(マウント情報を見ての想像です)
  • Rancher Desktop でランタイムとして "dockerd" 指定すると、最初に示した YAML で問題なく動く
  • ここの volumes の指定をどのように行うのが正解なのかはよく知らない(わからない)<ドキュメントとか読んでません

という感じです。雑なまとめ。

うーん、Mac 上から VM 層が隠蔽されていると、VM 内を覗けないのでトラブルシューティングしにくいなあ…(やる方法あるのかもしれんけどしらん)

ま、これ以上は特に興味がない個別のプロダクトのお話になるのでこの辺りで終わり!

2022 年やりたいこと

技術ブログだけど、今年やりたいことといえば技術のことよりもそれ以外のことが思い浮かぶなあ。フォロワーさんが書いてるのをみて、一年の初めってのもあるし、歳もひとつとったしということで、書いてみるかなと思って書いてみました。

技術

これまで通りのことをやるくらいかな。昨年は忙しかったし、なんか仕事で色々疲れてあまり新しいことをやろうという気がおこらなかった。今年もあまりやる気が湧いてくる気もしないけど。

  • 連載記事を 2021 年よりはたくさん書きたい
  • 連載記事をまとめて技術書典に出すとか何か形にしたいな
  • linuxcontainers.org の翻訳はこれまで通り
  • コンテナの勉強会を最低 1 回はやりたい
    • けれども何もネタを思いつきません。何か面白そうなテーマが思い浮かんで、話してくれる人が見つかりますように
  • 昨年よりはもう少し新しいコンテナ関連の新技術を調べたい
  • Go とか Rust とか新しい言語やりたいな(昨年 Go はちょっと手をつけたけど結局放置…)
  • 仕事で昨年からまたセキュリティ関連やってるんだけど、まあその辺りをぼちぼちと…(あまり技術によってない感じなので(以下略))

趣味

  • 昨年よりはドラマ(韓国ドラマ)の視聴を増やしたい
  • 昨年よりはちゃんと音楽聴きたい
    • イベント復活したらいきたいですね。昨年は久々に King Crimson でコンサート行きました
  • 韓国語
    • Duolingo と NHKテレビ講座で進めて 10 分程度のヨントンに耐えられるようにしたい(それ以前にコミュニケーション能力に問題あるんだけど :-p)
    • ハングル検定 5 級くらい受けてみたいな
  • 本を読みたいな。買って放置してるやつ多い(歳と共に本を読む集中力がなくなってきた)
  • 久々にメガネを買いたいな(JFReyが好きで、これまで10本ほどヤフオクで買ってます)(2021-01-04追記)

運動

  • 昨年はワクチン接種後以外はほぼ毎日運動をしたので継続したい。内容は変わらずジムに行くか、5km ほどの決まったコースを走るか

まとめ

  • なんか「やる気に満ち溢れてる!」感がないなあ。実現しないんちゃう? :-p

『Infra Study 2nd #2「クラウドネイティブを支えるインフラ技術」』でお話してきました

6/11(金)に Infra Study 2nd #2 ってイベントで登壇しました。そのレポートを会社のテックブログで書きました。

blog.idcf.jp

まあ、参加してない方や、アーカイブ動画を見てない方は、このレポートを読むというよりは、もとのアーカイブ動画を見ましょう、って感じですね。

www.youtube.com

5.11 で追加された OverlayFS の非特権マウント(2)

前回は、OverlayFS の非特権マウントの基本的な部分について説明しました。

ところで OverlayFS はシンプルながらも、より進んだ使い方ができる機能やオプションが存在します。基本的な機能以上の機能を使う場合、OverlayFS は拡張ファイル属性を使います。

例えば連載第18回で説明した「opaque(不透明)ディレクトリ」機能や、以前のブログエントリーで説明したredirect_dir機能などです。

しかし拡張ファイル属性で、この trusted で始まる属性(trusted名前空間)を使う場合は特権が必要であり、当然 User Namespace 内の root ではこの属性を使用できません。このような場合、代わりに自由に定義して使える属性(名前空間)として user が存在します1

OverlayFS でも、この機能を使うように実装されており、trusted の代わりに user を使う場合は、マウントオプションとして userxattr を指定してマウントします。この場合、trusted.overlay.* ではなく user.overlay.* を使うようになります。

非特権の場合の拡張ファイル属性

この拡張ファイル属性の動きを連載で説明した「opaque(不透明)ディレクトリ」を使って説明しましょう。この機能を使うと lowerdir に指定した下層側のディレクトリの内容が見えなくなります。

opaqueディレクトリを使いたい場合、拡張ファイル属性 trusted.overlay.opaquey という値を入れます。非特権 OverlayFS の場合は、この代わりに user.overlay.opaque を使うわけです

この機能の動きを見るための環境を作成しましょう。

$ mkdir lower upper work overlay
$ mkdir {lower,upper}/opaquetest
$ touch lower/opaquetest/testfile_lower upper/opaquetest/testfile_upper

これまでの実行例と同様に lowerupperworkoverlay というディレクトリを作成し、lowerupper の両方のディレクトリに opaquetest というディレクトリを作成します(連載の例と同じです)。そして lowerupper 配下の opaquetest ディレクトリ内にそれぞれ testfile_lowertestfile_upper というファイルを置きます。

この環境で普通に OverlayFS マウントを行うと、次のように見えるはずです。

# tree overlay/
overlay/
└── opaquetest
    ├── testfile_lower
    └── testfile_upper

1 directory, 2 files

userxattr オプションを使わない場合

まずは userxattr オプションを指定せずに OverlayFS マウントを行い、動きを見てみましょう。当然、期待する動きはしないはずです。

# setfattr -n "user.overlay.opaque" -v "y" upper/opaquetest/ (拡張ファイル属性を設定する)
# getfattr -n "user.overlay.opaque"  upper/opaquetest/ (拡張ファイル属性が設定されたのを確認)
# file: upper/opaquetest/
user.overlay.opaque="y"

# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
# tree overlay/ (拡張ファイル属性を設定したものの、特にOverlayFSの動きに変化はない)
overlay/
└── opaquetest
    ├── testfile_lower
    └── testfile_upper

1 directory, 2 files
# umount overlay

上記の例のように user.overlay.opaque 属性を設定しても、特に先ほどの普通に OverlayFS マウントを行ったときと動きに変化はありません。

userxattr オプションを追加してマウントした場合

それでは OverlayFS マウントを行う際に userxattr オプションを追加してみましょう。

# getfattr -n "user.overlay.opaque"  upper/opaquetest/ (拡張属性が設定されているのを確認)
# file: upper/opaquetest/
user.overlay.opaque="y"

# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work,userxattr overlay overlay (userxattrオプションを指定してマウント)
# tree lower upper (lower, upper以下のopaquetestディレクトリにはそれぞれファイルが存在する)
lower
└── opaquetest
    └── testfile_lower
upper
└── opaquetest
    └── testfile_upper

2 directories, 2 files
# tree overlay/ (overlayディレクトリを見ると上層側に置いたファイルしか見えない)
overlay/
└── opaquetest
    └── testfile_upper

1 directory, 1 file

このように上層側のファイルしか見えません。opaque ディレクトリの機能が働いていることがわかります。

非特権マウントの場合に使えない機能

以上のように、非特権マウントの場合は trusted.overlay の代わりに user.overlay を使って OverlayFS 独自の機能を実現します。

しかし非特権の場合、特権を持っているケースと同じように機能を使うと危険なケースがあります(特権の取得につながるとか)。このような機能については非特権の場合には使えないようになっています。

この機能のひとつが、以前ブログで紹介したredirect_dir機能 です。

次のように、この機能を使うためにオプションを指定してマウントしようとするとエラーになります。

$ unshare --user --map-root-user --mount
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work,userxattr,redirect_dir=on overlay overlay
mount: /home/karma/tmp/overlay: wrong fs type, bad option, bad superblock on overlay, missing codepage or helper program, or other error.

当然ですが、redirect_dir=off とするとマウントできます。

# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work,userxattr,redirect_dir=off overlay overlay
# grep overlay /proc/self/mountinfo 
119 109 0:62 / /home/karma/tmp/overlay rw,relatime - overlay overlay rw,lowerdir=lower,upperdir=upper,workdir=work,redirect_dir=off,index=off,metacopy=off

他にも非特権の場合、metacopy 機能が使えないようです(こちらの機能は私はまだ調べていないので詳細はまたの機会に)。


  1. ここで使っている名前空間は拡張ファイル属性の名前空間でコンテナで使う名前空間ではありません。