okadato の雑記帳

スタートアップでSREとしてはたらくokadatoの雑記です。

破天荒家出少女から学ぶ!?コンテナ要素技術再入門【ちょっぴり実践編】

さて前回 EC2 インスタンスを作成し、必要なパッケージを一通りインストールしましたね。

いきなり手を動かしてもヨクワカラナカッタので、改めて概念の予習をしておきます。



コンテナを構成する考え方

元記事では大きく分けて以下の3つがあげられていました。
ざっくり述べると、いずれもなんらかの制約を課すための技術です。

・Isolation / Namespace (リソース空間の隔離)

・Limitation / cgroup (物理リソースの制限)

・Restriction / Capability (権限の制約)



それぞれの要素技術の実態がどのようなものなのか?
具体的な技術の解説に入る前に、まずは以下の説明をお読みください。



とある家族の物語

f:id:okadato623:20191023005223p:plain

「あんなボロ家でまともに生きていけると思ってるのか!?本当にそう思うなら好きにしろ!今すぐ出ていけ!!!」



「言われなくたって出ていくわよこんな家!パパのわからずや!もう知らない!!」



オロオロと事態を見守ることしかできない母。



「今までお世話になりました!これからはあの人と幸せになりますのでお構いなく!!」



語気を荒げ、叩きつけるように鍵を置き、バタンと力強くドアを閉めて家を出ていく娘。





・・・





「はぁ…どうしてこうなっちゃったんだろ・・・」



さっきまでの強気な態度とは打って変わって、外に出るなり座り込んでしまう。頬には一筋の涙が。



「ペロ…」



悲しげな表情からなにかを察し、心配そうに身体を寄り添わせてくるペロ。



「ペロ、つらくなったらさ、会いに来てもいいかな?ペロだけはいつまでも私の味方でいてね…」

f:id:okadato623:20191027010627p:plain










f:id:okadato623:20191026165229j:plain






─── 数日後。



「 ─── あ、もしもし?ママですけど」



「ママ…なに?どうしたの?」



「どうしたのって、あれから連絡も寄越さないもんだからどうしてるのかしらって。無事にやってるの?」



「・・・うん、想像してたよりいろいろ大変だけど、なんとかやってるよ。ママには心配かけちゃってごめんね」



「あら、パパだって心配してるわよ。まああんたもあの人に似てガンコだから、すんなりとは片付かないでしょうけどね」



「・・・」



「そういえばどうなの、突然家出して転がり込んだりして。彼氏さんは迷惑してるんじゃない?」



「・・・うん、そうかも。泊めてもらってる身だから、料理とか掃除なんかは私がしてるけどね。家事なんて全部ママに頼りっきりだったからさ、こんなに大変だなんて知らなかったよ。自分のやりたいことなんてやってる暇ないもん」



「あらそう?ま、元気でやってるならなんだっていいのよ、あんたの人生なんだから。・・・あ、そうそうお金、ちょっとだけだけど振り込んどいたから」



「え・・・」



「普段のおこづかいと比べるとほんの少しだけだけどね。あたしのヘソクリから出してるんだから、感謝しなさいよ?」



「ママ・・・」



「じゃ、またね。なにかあったらすぐに連絡すること。いつ帰ってきたっていいんだからね」



「うん、ありがとうママ。おやすみなさいーーー」








さてここまでの説明を図解すると、以下のようになります。




f:id:okadato623:20191023005332p:plain




👆 このような状況が




f:id:okadato623:20191027145944p:plain




👆 このように変化したわけですね。



このとき家出してしまった娘さんは恋人である男性の家に転がり込み、ちょっぴり不自由な生活を強いられることになります。



また鍵を置いてきた娘さんは簡単には家に帰ることができなくなってしまいました。
当然これまで家族で食べていたごはんは食べられなくなってしまいますし、お気に入りのベッドで寝ることもできなくなってしまいます。



頼みの綱はママからもらえる少しのおこづかいと、玄関先で待っているバセットハウンドのペロ。
これまで住んでいた家庭環境という視点から娘さんの暮らしを比較すると、様々な制約が課されることになってしまいました(´;ω;`)



制約が課される・・・?

というわけで、話を戻しましょう。 以下ではコンテナを構成する要素技術とそれらに固有のコマンドについて、上記の物語と紐付けながら簡単に説明します。
上記の物語では自宅がホストマシン、転がり込んだ先の彼氏さんのお宅がコンテナにあたるものとお考えください。



・Isolation / Namespace (リソース空間の隔離)

  • unshare : 新しいリソース空間を作成し、隔離
    • "本当にそう思うなら好きにしろ!今すぐ出ていけ!!!"


・Limitation / cgroup (物理リソースの制限)

  • cgcreate : 物理リソースを制限する cgroup (Control Group) を作成
    • "言われなくたって出ていくわよこんな家!"
  • cgset : 作成した cgroup にリソース制限値を設定
    • "自分のやりたいことなんてやってる暇ないもん"
    • "普段のおこづかいと比べるとほんの少しだけだけどね"
  • cgexec : 作成した cgroup 内でコマンドを実行
    • "突然家出して転がり込んだりして。"


・Restriction / Capability (権限の制約)

  • capsh : 権限を制約したうえでコマンドを実行
    • "叩きつけるように鍵を置き、バタンと力強くドアを閉めて家を出ていってしまった娘"
  • setcap : プロセスに必要最小限の権限を設定
    • "ペロ、つらくなったらさ、会いに来てもいいかな?"



処理の中身を参照しつつ解説

さてこれを実際の処理ベースで解説し直してみます。

# CPU, メモリを制限する cgroup を作成し、リソース制限値を設定
sudo cgcreate -t $(id -un):$(id -gn) -a $(id -un):$(id -gn) -g cpu,memory:$UUID
cgset -r memory.limit_in_bytes=10000000 $UUID
cgset -r cpu.cfs_period_us=1000000 $UUID
cgset -r cpu.cfs_quota_us=300000 $UUID
# cgroup のなかで、新しいリソース空間(≒ コンテナ)を作成!
cgexec -g cpu,memory:$UUID \
unshare -muinpfr /bin/sh -c "
$PREP
exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
"



7行目 〜 12行目で cgexecしたうえで unshare し、新しいリソース制限を作成しています。
これが「いますぐ出ていけ!!!」で、それが故に娘さんは彼氏さんの家に転がり込むことになり、自宅に帰ることができなくなってしまったのでした。
(今回は出てきませんでしたが「ペロにだけ会いに来る」のように一部の権限のみ許容するために setcap を使用します)



また 1行目 〜 5行目までで cgcreate し、リソース制限を行っています。 これが家出をすることに伴うリソースの制限にあたり、娘さんは自分のための時間が減ったり、おこづかいが減ったりなどの制約が課されることになったのでした。





と、いうわけで。

ようやく本題、実際にコンテナを作ってみましょう!

元記事を参考にコンテナ作成の一通りのコマンドを整理してスクリプト化しました。
前回作成した container_study 内に create_container.sh として以下のファイルを配置してください。

# 事前準備
ROOTFS=$(mktemp -d)
CID=$(sudo docker container create bash)
UUID=$(uuidgen)
CMD="/bin/sh"
# 今回のスコープ外なので一旦おまじない化
PREP="
mount -t proc proc $ROOTFS/proc &&
touch $ROOTFS$(tty); mount --bind $(tty) $ROOTFS$(tty) &&
touch $ROOTFS/dev/pts/ptmx; mount --bind /dev/pts/ptmx $ROOTFS/dev/pts/ptmx &&
ln -sf /dev/pts/ptmx $ROOTFS/dev/ptmx &&
touch $ROOTFS/dev/null && mount --bind /dev/null $ROOTFS/dev/null &&
/bin/hostname $UUID &&
"
# Docker の bash イメージを一時ディレクトリに展開
sudo docker container export $CID | tar -x -C $ROOTFS
ln -s /usr/local/bin/bash $ROOTFS/bin/bash
sudo docker container rm $CID
# CPU, メモリを制限する cgroup を作成し、リソース制限値を設定
sudo cgcreate -t $(id -un):$(id -gn) -a $(id -un):$(id -gn) -g cpu,memory:$UUID
cgset -r memory.limit_in_bytes=10000000 $UUID
cgset -r cpu.cfs_period_us=1000000 $UUID
cgset -r cpu.cfs_quota_us=300000 $UUID
# cgroup のなかで、新しいリソース空間(≒ コンテナ)を作成!
cgexec -g cpu,memory:$UUID \
unshare -muinpfr /bin/sh -c "
$PREP
exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
"



検証検証〜

さて、せっかく作ったのでコンテナの中に入ってみましょう!
bash create_container.sh を実行します!

ubuntu@ip-10-0-144-131:~/container_study$ bash create_container.sh
a0ae248046d4b734e07886c08ad11d4773a60f439e5fd76ce0e608dfe63a12e6
/ # ls -l
total 64
drwxr-xr-x    2 root     root          4096 Oct 22 02:01 bin
drwxr-xr-x    4 root     root          4096 Oct 22 02:01 dev
drwxr-xr-x   16 root     root          4096 Oct 22 02:01 etc
drwxr-xr-x    2 root     root          4096 Aug 20 10:30 home
drwxr-xr-x    5 root     root          4096 Aug 29 21:35 lib
drwxr-xr-x    5 root     root          4096 Aug 20 10:30 media
drwxr-xr-x    2 root     root          4096 Aug 20 10:30 mnt
drwxr-xr-x    2 root     root          4096 Aug 20 10:30 opt
dr-xr-xr-x  146 nobody   nobody           0 Oct 22 02:01 proc
drwx------    2 root     root          4096 Aug 20 10:30 root
drwxr-xr-x    2 root     root          4096 Aug 20 10:30 run
drwxr-xr-x    2 root     root          4096 Aug 20 10:30 sbin
drwxr-xr-x    2 root     root          4096 Aug 20 10:30 srv
drwxr-xr-x    2 root     root          4096 Aug 20 10:30 sys
drwxrwxr-x    2 root     root          4096 Aug 29 21:35 tmp
drwxr-xr-x    8 root     root          4096 Aug 29 21:35 usr
drwxr-xr-x   11 root     root          4096 Aug 29 21:35 var

これがコンテナの中身であり、彼氏さんのおうちです。
元記事のとおりに yes コマンドで負荷をかけてみましょう。
その様子を別プロセスから top コマンドで確認すると、確かに CPU 利用率はおよそ30%となっていますね 🎉

f:id:okadato623:20191027003544p:plain



ちょっと脱線

さらにせっかくコンテナを作ったので、もうちょっとだけ遊んでみましょう。
みんなのトラウマコマンドを、エイヤッ!

(必ずコンテナ内で実行してくださいね!!)



/ # rm -rf /
    :
    :
rm: can't remove '/proc/11/timerslack_ns': Operation not permitted
rm: can't remove '/proc/11/patch_state': Operation not permitted
rm: can't remove '/dev/pts/6': Resource busy
rm: can't remove '/dev/pts/ptmx': Resource busy
rm: can't remove '/dev/null': Resource busy


削除できないファイルもたくさんありました。

    :
    :
rm: can't remove '/dev/pts/7': Resource busy
rm: can't remove '/dev/pts/ptmx': Resource busy
rm: can't remove '/dev/null': Resource busy
/ # ls
/bin/sh: ls: not found


が、 ls は無事(?)消えたようです 😊
なにもできなくなったので再度 exit します。


あれ?そういえば exit コマンドが使えなくなるってなことはないのだろうか…?
もし使えなくなったらさすがに困るワ系のコマンドって結構ありますよね。
というわけでググってみると…



Linux には Built-in commands とやらがあるみたいでした!ひとあんしん。
help コマンドで出てくるこの子たちが Built-in commands らしいです。


/ # help
Built-in commands:
------------------
    . : [ [[ alias bg break cd chdir command continue echo eval exec
    exit export false fg getopts hash help history jobs kill let
    local printf pwd read readonly return set shift source test times
    trap true type ulimit umask unalias unset wait


cd が生きているということはタブ補完でディレクトリ確認はできそう。


/ # cd
dev/   proc/



ちなみに proc 配下ファイルの owner は大半が nobody という擬似ユーザで、権限は Readonly みたいです。残念(´・ω・`)
lsattrchattr が入っていればキレイサッパリできたかもしれませんね(なかった)

/ # ls
/bin/sh: ls: not found
/ # cd
dev/   proc/



さらにちなむと、なんで /bin がないのに /bin/sh が見れてるんでしょうなぁ??
など、面白いポイントは多々ありますので、調べてみると学びがあると思います!



最後に

こーんな感じでコンテナの中身をぶっ壊しても、exit して再度 bash create_container.sh すれば一瞬で復元できます 👏
それがコンテナの大きなメリットのひとつなので、ぜひいろいろ遊んでみてください!



もうひとつくらい記事を書き、

・Isolation / Namespace (リソース空間の隔離)

・Limitation / cgroup (物理リソースの制限)

・Restriction / Capability (権限の制約)


をさらに深堀りしつつ周辺技術についてもちょっぴり解説する予定だったのですが、少し書き出したところ完全に元記事の劣化版焼き直しになってしまうことが判明しました。。。



ので、本記事でざっくりと概念を学ぶことはできた(?)と思いますので、それを踏まえて詳細部分は改めて以下の記事をご参照いただければと思います!



イメージ、このへんまでは進んだとおもうので

f:id:okadato623:20191023013031p:plain



今回の理解をベースに元記事を読んでいただければ、このへんまでは進めるかと思います(完全丸投げスタイル)

f:id:okadato623:20191027083649j:plain



というわけで要素技術の概要説明に終止してしまいましたが、自分としては How コンテナ works? について理解しつつ、元記事様の素晴らしさを改めて感じました 🙏


もしわかりにくい箇所などあったらぼく @okadato623 もしくは @hayajo さんまでお気軽に聞いていただければと思います(盛大な巻き込み)







・・・あ、このままだとストーリーのバッドエンド感が拭えませんねぇ。













娘さんは彼氏さんの家をハチャメチャにぶち壊しましたが($ rm -rf /)実家は無事だったため実家に戻り、家族は仲直りして末永く仲良く暮らしましたとさ。

めでたしめでたし 😊