1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

絶対にどこよりもわかりやすいコンテナ入門② 〜コンテナの本質を理解する〜

1
Posted at

前回の続き

前回の記事 では通常のプロセスとコンテナプロセスを比較しながら「コンテナとは何か」について解説していきましたが、ネットワークについて解説している途中で終わってしまったので、その続きからやっていこうと思います。

なのですが、さっそく脇道にそれて nginx のコンテナイメージをカスタマイズしていこうと思います。(その方がポートバインドする nginx コンテナを起動するのに便利なので。。)

Dockerfile でコンテナイメージをカスタマイズしよう

前回の記事では nginx:latest のコンテナイメージを使用してコンテナを起動した後に、起動したコンテナ内で
① apt update
② apt install -y procps
③ /usr/share/nginx/html/hello.html の作成
を行ったと思いますが、通常はこのような運用はやりません。
なぜならコンテナは基本的にエフェメラル(一時的)なものであり、コンテナが削除されるとコンテナ内で行った変更も消えるからです。確認してみましょう。
前回作成したコンテナには procps がインストールされていて、/usr/share/nginx/html/hello.html も作成されていますね。

[root@localhost ~]# podman ps -a
CONTAINER ID  IMAGE                           COMMAND               CREATED     STATUS      PORTS       NAMES
ac22be2830ef  docker.io/library/nginx:latest  nginx -g daemon o...  4 days ago  Up 3 days   80/tcp      festive_rhodes
[root@localhost ~]#
[root@localhost ~]# podman exec -it ac22be2830ef bash
root@ac22be2830ef:/#
root@ac22be2830ef:/# dpkg -l | grep procps #procpsパッケージの確認
ii  procps                        2:4.0.4-9                            amd64        /proc file system utilities
root@ac22be2830ef:/#
root@ac22be2830ef:/# cat /usr/share/nginx/html/hello.html #hello.htmlの確認
This is Container Process
root@ac22be2830ef:/#
root@ac22be2830ef:/# exit
exit
[root@localhost ~]# 

このコンテナを削除して、再度 nginx のコンテナを作成してみます。

[root@localhost ~]# podman rm -f ac22be2830ef
ac22be2830ef
[root@localhost ~]#
[root@localhost ~]# podman ps -a #何も表示されない(コンテナが削除された) 
CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES
[root@localhost ~]#
[root@localhost ~]# podman run -d docker.io/library/nginx
19af8938d3c064b76ca995631f68217a539f51179c5e7120ac29057e22c5c4f0
[root@localhost ~]#
[root@localhost ~]# podman ps -a
CONTAINER ID  IMAGE                           COMMAND               CREATED        STATUS        PORTS       NAMES
19af8938d3c0  docker.io/library/nginx:latest  nginx -g daemon o...  5 seconds ago  Up 5 seconds  80/tcp      xenodochial_ride

再作成したコンテナで確認すると procps がインストールされておらず、/usr/share/nginx/html/hello.html も作成されていないことがわかります。
※ちなみにここでは試していませんが、 rm(削除)ではなく stop(停止)しただけであれば、再度 start すれば変更内容は残ったままです。

[root@localhost ~]# podman exec -it 19af8938d3c0 bash
root@19af8938d3c0:/#
root@19af8938d3c0:/# dpkg -l | grep procps
root@19af8938d3c0:/#
root@19af8938d3c0:/# cat /usr/share/nginx/html/hello.html
cat: /usr/share/nginx/html/hello.html: No such file or directory
root@19af8938d3c0:/#
root@19af8938d3c0:/# exit
exit
[root@localhost ~]#

このようにコンテナ内で行った変更はコンテナを削除すると消えてしまうため、コンテナに変更を加えたい場合はコンテナイメージを編集します。

[root@localhost ~]# mkdir image_nginx
[root@localhost ~]# cd image_nginx/
[root@localhost image_nginx]#
[root@localhost image_nginx]# cat << EOF > Dockerfile
> FROM docker.io/library/nginx:latest
>
> RUN apt update
> RUN apt install -y procps
>
> COPY hello.html /usr/share/nginx/html/
> EOF
[root@localhost image_nginx]#
[root@localhost image_nginx]# cat << EOF > hello.html
> This is Container Process
> EOF
[root@localhost image_nginx]#
[root@localhost image_nginx]# ls -l
合計 8
-rw-r--r-- 1 root root 118  8月 24 16:42 Dockerfile
-rw-r--r-- 1 root root  26  8月 24 16:43 hello.html
[root@localhost image_nginx]#
[root@localhost image_nginx]# cat Dockerfile
FROM docker.io/library/nginx:latest #nginx:latestをベースイメージに指定

RUN apt update #①実行コマンド
RUN apt install -y procps #②実行コマンド

COPY hello.html /usr/share/nginx/html/ #③ホスト上のhello.htmlをコンテナイメージの/usr/share/nginx/html/にコピー
[root@localhost image_nginx]#
[root@localhost image_nginx]# cat hello.html
This is Container Process

Dockefile と hello.html の2つのファイルを作成しました。
Dockerfile にはどのようなコンテナイメージを作るかを記載します。(記載内容の意味はコメントを確認ください。)
作成した Dockerfile もとにコンテナイメージを build します。イメージの名前もわかりやすく custom_nginx:v1 としておきます。

[root@localhost image_nginx]# podman build -t custom_nginx:v1 .
STEP 1/4: FROM docker.io/library/nginx:latest
STEP 2/4: RUN apt update
<中略>
--> 98438043a8ea
STEP 3/4: RUN apt install -y procps
<中略>
--> c537f1b46bf2
STEP 4/4: COPY hello.html /usr/share/nginx/html/
COMMIT custom_nginx:v1
--> b955b683b7af
Successfully tagged localhost/custom_nginx:v1

podman build の標準出力を見てみると、Dockerfile に書いた内容が上から実行されているのが確認できるかと思います。
イメージのビルドができたので確認してみると localhost/custom_nginx:v1 が無事に作成されていますね。(localhost/ が補完されるんですね。。)

[root@localhost image_nginx]# podman images
REPOSITORY                   TAG         IMAGE ID      CREATED        SIZE
localhost/custom_nginx       v1          b955b683b7af  9 minutes ago  189 MB
docker.io/library/nginx      latest      f075e3f94986  4 days ago     165 MB

では、作成した custom_nginx:v1 を使ってコンテナを起動してみましょう。

[root@localhost image_nginx]# podman run -d custom_nginx:v1
fbfabf23b991281264a056d4a6d6f15d29034b349ebe3348510d58418c323149
[root@localhost image_nginx]#
[root@localhost image_nginx]# podman ps -a
CONTAINER ID  IMAGE                           COMMAND               CREATED         STATUS         PORTS       NAMES
19af8938d3c0  docker.io/library/nginx:latest  nginx -g daemon o...  54 minutes ago  Up 54 minutes  80/tcp      xenodochial_ride
fbfabf23b991  localhost/custom_nginx:v1       nginx -g daemon o...  3 seconds ago   Up 4 seconds   80/tcp      exciting_gates

最初から procps がインストールされ、/usr/share/nginx/html/hello.html も作成された状態でコンテナが起動しています。

[root@localhost image_nginx]# podman exec -it fbfabf23b991 bash
root@fbfabf23b991:/#
root@fbfabf23b991:/# dpkg -l | grep procps
ii  procps                        2:4.0.4-9                            amd64        /proc file system utilities
root@fbfabf23b991:/#
root@fbfabf23b991:/# cat /usr/share/nginx/html/hello.html
This is Container Process
root@fbfabf23b991:/#
root@fbfabf23b991:/# exit
exit
[root@localhost image_nginx]#

このようにコンテナに変更を加えたいときは、コンテナイメージに変更を加えてそのイメージを使ってコンテナを起動する、というのが基本的な運用になります。
では、nginx のイメージもカスタマイズできたところでネットワークの説明に戻りましょう。

ポートバインドでコンテナをあげてみよう

前回は起動したコンテナのIPを直接指定してアクセスできることを確認しました。
ただ、このコンテナのIPは起動するたびに変わります。
そのため、外部からコンテナへアクセスさせる方法として、通常はホストのポートとコンテナのポートをバインドします。確認してみましょう。

[root@localhost image_nginx]# podman run -d -p 8080:80 custom_nginx:v1
7406226064a552aadd1ab3369798f48ef7356639c96b9a7fb1859ef51205d328
[root@localhost image_nginx]#
[root@localhost image_nginx]#
[root@localhost image_nginx]# podman ps -a
CONTAINER ID  IMAGE                           COMMAND               CREATED            STATUS            PORTS                 NAMES
19af8938d3c0  docker.io/library/nginx:latest  nginx -g daemon o...  About an hour ago  Up About an hour  80/tcp                xenodochial_ride
fbfabf23b991  localhost/custom_nginx:v1       nginx -g daemon o...  16 minutes ago     Up 16 minutes     80/tcp                exciting_gates
7406226064a5  localhost/custom_nginx:v1       nginx -g daemon o...  6 seconds ago      Up 6 seconds      0.0.0.0:8080->80/tcp  hopeful_banzai

-p オプションでホストの8080番ポートに来た通信をコンテナの80番ポートに転送するように設定したコンテナを起動しました。(一番下のコンテナ)
ホスト側でリッスンポートを確認するとちゃんと8080番ポートでリッスンしていることがわかります。

[root@localhost image_nginx]# ss -lntp | grep 8080
LISTEN 0      4096         0.0.0.0:8080      0.0.0.0:*    users:(("conmon",pid=114840,fd=5))

では、ホストの8080番ポートに対してアクセスしてみましょう。

[root@localhost image_nginx]# curl http://localhost:8080/hello.html
This is Container Process

hello.html にアクセスしてみると「This is Container Process」が返ってくるので、通常のプロセスの nginx ではなくコンテナプロセスの nginx のほうが応答していることがわかりますね。
ブラウザからも確認してみます。

image.png

繰り返しになりますが、ホストの80番ポートにアクセスすると、 「This is Normal Process」 のほうが返ってきます。
※ 80番ポートは既に通常プロセスの nginx が使用しているので、コンテナポートにバインドさせようとすると競合して失敗します。

[root@localhost image_nginx]# curl http://localhost/hello.html
This is Normal Process

どうやって実行環境を分離している?

前回の記事から今回の記事にかけてファイルシステム、PID、ネットワークの観点からコンテナプロセスがホストと異なる実行環境を持っていることを見てきました。
これらは linux カーネルの namespace という機能によって実現されています。namespace についてみていきましょう。

namespace とは

namespace とは 「プロセスごとに異なるシステムの見え方を作る仕組み」 です。
linux には以下の namespace があります。使用している namespace が同じなら同じものが見え、使用している namespace が異なれば当然異なるものが見えます。

image.png

プロセスがそれぞれどの namespace を使用しているかは、
/proc//ns から確認することができます。
ホストのプロセスの代表として systemd(PID 1)と通常プロセスの nginx(PID 1171)、コンテナプロセスの nginx(PID 114842)それぞれの namespace を見てみましょう。
※OSを一度再起動したので前回の記事からPIDが変わっています。

image.png

systemd と通常プロセスの nginx を比較すると mount namespace 以外すべての namespace を共有していることがわかります。
てっきりすべての namespace を共有しているものと思っていたら mount だけ共有してなかったですが、これは nginx.service の設定で PrivateTmp=yes になっており、/tmp と /var/tmp だけプロセスが認識するマウントを変えているからでした。
(通常プロセスがホストと namespace が異なっていたらちょっと話がブレるなと思いつつ。。)

[root@localhost ~]# systemctl show nginx.service | grep Private
PrivateTmp=yes ★ 
PrivateDevices=no
PrivateNetwork=no
PrivateUsers=no
PrivateMounts=no
PrivateIPC=no
[root@localhost ~]#
[root@localhost ~]# grep /tmp /proc/1171/mountinfo
729 711 253:0 /tmp/systemd-private-xxxxx-nginx.service-bLolVn/tmp /tmp rw,relatime shared:365 master:1 - xfs /dev/mapper/cs-root rw,attr2,inode64,logbufs=8,logbsize=32k,noquota
736 711 253:0 /var/tmp/systemd-private-xxxxx-nginx.service-T9L3Kh/tmp /var/tmp rw,relatime shared:382 master:1 - xfs /dev/mapper/cs-root rw,attr2,inode64,logbufs=8,logbsize=32k,noquota

続いてコンテナプロセスの nginx のほうを見てみると、time と user の namespace 以外すべての namespace をホストと共有していないことがわかります。
だからこれまで見てきたように、ルートファイルシステムやPID、ネットワークがホストと違っていたわけですね。
このように linux カーネルの namespace の機能がコンテナをコンテナたらしめる中核となっていることがご理解いただけたかと思います。

cgroup とは

次に、コンテナ技術のもう一つの中核となっている cgroup について説明していきます。cgroup とは 「プロセスに対してCPU・メモリなどのリソース使用量を制御する仕組み」 です。
一般的な Kubernetes の運用においては、コンテナに対して使える cpu / memory の上限(limit)を設定することが多く、それを実現しているのがこの cgroup の機能になります。実際に確認してみましょう。
プロセスは必ずどこかの cgroup に属しており、/proc//cgroup から確認することができます。

[root@localhost ~]# cat /proc/114842/cgroup
0::/machine.slice/libpod-74xxx.scope/container

nginx のコンテナプロセスは /machine.slice/libpod-74xxx.scope/container の cgroup に属していることがわかります。
現在主流となっている cgroup v2 においては cgroup は / を頂点とした階層構造になっており、
container の親 cgroup は libpod-74xxx.scope、さらにその親は machine.slice という関係になっています。
そして、親 cgroup に設定したリソース制限は子 cgroup にも継承されます。

[root@localhost ~]# cat /sys/fs/cgroup/machine.slice/memory.max
max
[root@localhost ~]#
[root@localhost ~]# cat /sys/fs/cgroup/machine.slice/libpod-74xxx.scope/memory.max
max
[root@localhost ~]#
[root@localhost ~]# cat /sys/fs/cgroup/machine.slice/libpod-74xxx.scope/container/memory.max
max

それぞれの cgroup の memory.max の値を見てみると、現状では machine.slice、libpod-74xxx.scope、container いずれの cgroup においても memory.max の値が max、つまり memory を限界まで使える設定になっていることがわかります。
では、memory を制限したコンテナを作成してみましょう。

[root@localhost ~]# podman run -d --memory=1g custom_nginx:v1
03ccb25e144d874f41ce1b9747a063c568c9032315440d7e18490ebaa0b196d3
[root@localhost ~]#
[root@localhost ~]# podman ps -a
CONTAINER ID  IMAGE                      COMMAND               CREATED        STATUS        PORTS                 NAMES
7406226064a5  localhost/custom_nginx:v1  nginx -g daemon o...  25 hours ago   Up 25 hours   0.0.0.0:8080->80/tcp  hopeful_banzai
03ccb25e144d  localhost/custom_nginx:v1  nginx -g daemon o...  5 seconds ago  Up 5 seconds  80/tcp                focused_mirzakhani
[root@localhost ~]#
[root@localhost ~]# podman inspect 03ccb25e144d | jq '.[0].State.Pid'
118728
[root@localhost ~]#
[root@localhost ~]# cat /proc/118728/cgroup
0::/machine.slice/libpod-03xxx.scope/container
[root@localhost ~]#
[root@localhost ~]# cat /sys/fs/cgroup/machine.slice/memory.max
max
[root@localhost ~]#
[root@localhost ~]# cat /sys/fs/cgroup/machine.slice/libpod-03xxx.scope/memory.max
1073741824
[root@localhost ~]#
[root@localhost ~]# cat /sys/fs/cgroup/machine.slice/libpod-03xxx.scope/container/memory.max
1073741824

--memory=1g で使用できる memory 上限を 1g に設定したコンテナで確認すると、このコンテナプロセスが属する cgroup の memory.max の値が 1073741824(1024^3)になっていることが確認できます。
(正確に言うなら、/machine.slice/libpod-03xxx.scope の cgroup に制限がかかり、子である /machine.slice/libpod-03xxx.scope/container もその制限を引き継いでいる。)

このように namespace と cgroup という機能を中核にしてコンテナが作成されているわけです。

おわりに

ここまで読んでくださった方はありがとうございます。「プロセス」という観点からコンテナを見てきましたがいかがでしたでしょうか。
我ながら正直ここまで「コンテナとはなんぞや」というところを解説している記事はなかなかないんじゃないかなと自負しているので、この記事が皆様の学習の一助になったなら幸いです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?