はじめに
コンテナ技術を学部の卒業論文のテーマにしたいと思い、調査をしていましたが厳しそうなのでOutputとして残すために投稿します。
また、本記事の対象者はDockerは使ったことあるけど中身を知らない、コンテナって美味しいの??という方向けです。簡単にコンテナがどのようにできているかを紹介した後、Linuxの機能を使ってコマンドラインベースで自作コンテナを立てます。
本記事投稿時は心がまっさらな純粋無垢な学生であるため、暖かいマサカリ頂けると幸いです。
ちなみに参考にさせていただいた記事も貼っておきます。1
基本的に参考記事に記載してあることは深くは触りません。本記事の目的としてはコンテナの簡単な理解と参考記事になかった、作成したコンテナのネットワークの設定をすることです。
コンテナとは??
まずは冒頭でも触れた通り簡単にコンテナの紹介をします。
コンテナとはただのプロセスです。
アーキテクチャ
上記の図の左がVMのアーキテクチャであり、右がコンテナのアーキテクチャです。2
このようにコンテナはホストのカーネルを共有しつつ、コンテナランタイムと呼ばれるコンテナの実行環境の上で各アプリケーションが稼働しています。Dockerはそのコンテナの実行環境の一つです。
ではそのコンテナランタイムとは何か??
コンテナランタイム
コンテナランタイムについてまとめてあるQiitaの記事を記載しておきます。
コンテナランタイムの動向を整理してみた件
記事を見ていただければお分かりになるかと思いますが主にHigh-LevelとLow-Levelに分かれている、ということですね。
また、CRIやOCIで標準化されているためそれに準拠したソフトウェアであれば互換性が保てるということが私個人的には素晴らしいと感じました。
Low-Level
代表例としてはrunc
やrunsc
、runcc
などがあると思います。
役割としてはコンテナを隔離して制限する部分を担っています。
ソースコードを見ていないので断言はできませんが、Linux環境下ではこのLow-Levelは基本的にLinuxの機能を利用して動いているため、やろうと思えば自作コンテナが作れてしまうということです
コンテナを自作するぞ!!!
コンテナがどのようなものか簡単に紹介しましたが、上記の説明では満足に理解できないと思うので各々調べていただけると幸いです。
環境
種類 | 環境 |
---|---|
OS | Ubuntu 20.04.2 LTS |
Host | macOS 11.4 |
Host Network | 192.168.10.1/24 |
Gest Network to Host | 192.168.10.10/24 |
br0(Gest Bridge to Container) | 10.10.0.1/24 |
Container Network | 10.10.0.10/24 |
lesson@lesson-VirtualBox:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04.2 LTS
Release: 20.04
MacBook:~ user$ sw_vers
ProductName: macOS
ProductVersion: 11.4
ネットワークについては本記事では説明しないため、わからない方はぜひ調べてください。
要素技術
Linuxの機能を使っていくため、その要素技術を記載していきます。
参考記事にその説明があるため深くは記述しませんが、本記事はその先のネットワークの設定まで行っています。
unshare
コンテナの分離を実現する重要なNamespaceを隔離するコマンドです。
unshareにはオプションが用意されており、それぞれの説明は参考記事をご覧ください。1
今回は下記のコマンドを利用します。
unshare -muinpfr /bin/sh -c
cgcreate, cgset, cgexec
コンテナの分離を実現する重要なCGroupを設定するコマンドです。
cgroupfsでも設定することができますが、コマンドベースで行いため今回は上記3つのコマンドを利用してCGroupを設定していきます。
filesystemで理解したい方はこちらの記事を参照してください。
LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術 第3回 Linuxカーネルのコンテナ機能[2] ─cgroupとは?(その1)
今回は下記のコマンドを利用します。
UUID=$(uuidgen)
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
cgexec -g cpu,memory:$UUID
uuidgen
でUUIDを作成し、その名前でサブグループを作成、それぞれ制限をつけて、実行する、という流れです。
こちらも参考記事に詳細がありますので参照してください。
capsh
コンテナの制限を実現する重要なCapabilitiesで実行するコマンドです。
Capability設定をサポートした /bin/bash
のラッパーです。
今回は下記のコマンドを利用します。
exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
ip
ip netnsはunshareと同様にNamespaceを隔離するコマンドですが、Network Namespaceを隔離、設定する際に利用します。
今回はunshareですでにNetwork Namespaceを作成していますのでそのNamespaceにネットワークを設定する際に利用します。
いざ実践、、!!
簡単にコンテナの紹介とコマンドの紹介をしたところで実際にコンテナを作ってみましょう。
先に、コマンドラインベースで行い、ネットワークを確認するため2つ以上のターミナルからGestOSに対してsshしておきましょう。
## ターミナル1
lesson@lesson-VirtualBox:~$ tty
/dev/pts/0
## ターミナル2
lesson@lesson-VirtualBox:~$ tty
/dev/pts/1
ではまず、ターミナル1でtempディレクトリを作成し、Dockerのfilesystemをtempディレクトリ上に展開し、bashが動くようにリンクを貼ります。
ROOTFS=$(mktemp -d)
CID=$(sudo docker container create bash)
sudo docker container export $CID | tar -x -C $ROOTFS
ln -s /usr/local/bin/bash $ROOTFS/bin/bash
sudo docker container rm $CID
続いて、CGroupを設定します。参考記事と同様の制限をかけます。
UUID=$(uuidgen)
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
そして、GestOS上にブリッジを作成し、コンテナに割り当てる用のvethも作成しておきます。
## to create bridge
sudo ip link add name br0 type bridge
sudo ip addr add 10.10.0.1/24 broadcast 10.10.0.255 label br0 dev br0
sudo ip link set dev br0 up
## to create veth
sudo ip link add veth1 type veth peer name veth1_peer
sudo ip link set dev veth1 master br0
sudo ip link set dev veth1 up
これを実行すると下記のようになります。
lesson@lesson-VirtualBox:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:11:36:26 brd ff:ff:ff:ff:ff:ff
inet 192.168.10.10/24 brd 192.168.10.255 scope global noprefixroute enp0s8
valid_lft forever preferred_lft forever
inet6 fe80::74ed:aa40:24d9:f457/64 scope link noprefixroute
valid_lft forever preferred_lft forever
5: br0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 96:f5:9c:89:f3:17 brd ff:ff:ff:ff:ff:ff
inet 10.10.0.1/24 brd 10.10.0.255 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::d8bf:f3ff:fe59:620a/64 scope link
valid_lft forever preferred_lft forever
6: veth1_peer@veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 1a:12:a0:d6:de:ec brd ff:ff:ff:ff:ff:ff
7: veth1@veth1_peer: <NO-CARRIER,BROADCAST,MULTICAST,UP,M-DOWN> mtu 1500 qdisc noqueue master br0 state LOWERLAYERDOWN group default qlen 1000
link/ether 96:f5:9c:89:f3:17 brd ff:ff:ff:ff:ff:ff
そしてようやく、紹介したコマンドを利用してコンテナを作成します。
CMD="/bin/sh"
cgexec -g cpu,memory:$UUID \
unshare -muinpfr /bin/sh -c "
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 &&
exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
"
上記を実行すると下記のようにPIDやネットワーク、filesystemが隔離されたコンテナが作成できます。
# ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
13 root 0:00 ps
# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
現状ではloopbackのネットワークしかありません。そのためコンテナは作成できましたが、GestOS-Container間の通信はすることができません。
次に、GestOSからContainerのNetwork Namespaceを閲覧できるようにします。
## コンテナを起動してるPIDを取得します。
lesson@lesson-VirtualBox:~$ ps -h
3005 pts/0 Ss 0:00 -bash
3206 pts/1 Ss 0:00 -bash
3357 pts/0 S 0:00 unshare -muinpfr /bin/sh -c mount -t proc proc /tmp/tmp.mBNY4xblZw/proc && touch /tmp/tmp.mBNY4xblZw/dev/pts/1; mount --bind /dev/pts/1 /tmp/tmp.mBNY4xblZw/dev/pts/1 && touch /tmp/tmp.mBNY4xblZw/d
3358 pts/0 S+ 0:00 /bin/sh
3373 pts/1 R+ 0:00 ps -h
## PIDからNetwork Namespaceを閲覧できるようにします。
lesson@lesson-VirtualBox:~$ sudo mkdir -p /var/run/netns
lesson@lesson-VirtualBox:~$ sudo ln -s /proc/3357/ns/net /var/run/netns/container01
lesson@lesson-VirtualBox:~$ ip netns list
container01
これでターミナル2からターミナル1で作成したNetwork Namespaceが閲覧できるようになります。
ip netns list
で表示されるNamespaceは/var/run/netns/
にあるディレクトリであるため、コンテナを起動しているPID先からリンクを貼ってあげると閲覧できます。mountでも試してみたんですが、lnだとうまくいきました。
そして、見えたNetwork Namespaceに対してveth1
を設定し、ターミナル1でethを立ち上げます。
lesson@lesson-VirtualBox:~$ sudo ip link set dev veth1_peer netns container01
lesson@lesson-VirtualBox:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:11:36:26 brd ff:ff:ff:ff:ff:ff
inet 192.168.10.10/24 brd 192.168.10.255 scope global noprefixroute enp0s8
valid_lft forever preferred_lft forever
inet6 fe80::74ed:aa40:24d9:f457/64 scope link noprefixroute
valid_lft forever preferred_lft forever
5: br0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 96:f5:9c:89:f3:17 brd ff:ff:ff:ff:ff:ff
inet 10.10.0.1/24 brd 10.10.0.255 scope global br0
valid_lft forever preferred_lft forever
inet6 fe80::d8bf:f3ff:fe59:620a/64 scope link
valid_lft forever preferred_lft forever
7: veth1@if6: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue master br0 state LOWERLAYERDOWN group default qlen 1000
link/ether 96:f5:9c:89:f3:17 brd ff:ff:ff:ff:ff:ff link-netns container01
# ip link set dev veth1_peer name eth0
# ip addr add 10.10.0.10/24 dev eth0
# ip link set dev eth0 up
# ip route add default via 10.10.0.1
# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP qlen 1000
link/ether 1a:12:a0:d6:de:ec brd ff:ff:ff:ff:ff:ff
inet 10.10.0.10/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::1812:a0ff:fed6:deec/64 scope link
valid_lft forever preferred_lft forever
はい、ここまででLinuxの機能を利用しコンテナを起動、そのコンテナに対してNetworkを設定しました。
Networkの設定以外は参考記事を大きく参照しています。
ぜひ、手元で自作コンテナを作ってみてください!!
# ping 10.10.0.1
PING 10.10.0.1 (10.10.0.1): 56 data bytes
64 bytes from 10.10.0.1: seq=0 ttl=64 time=0.158 ms
64 bytes from 10.10.0.1: seq=1 ttl=64 time=0.077 ms
64 bytes from 10.10.0.1: seq=2 ttl=64 time=0.077 ms
^C
--- 10.10.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.077/0.104/0.158 ms
lesson@lesson-VirtualBox:~$ ping 10.10.0.10
PING 10.10.0.10 (10.10.0.10) 56(84) bytes of data.
64 bytes from 10.10.0.10: icmp_seq=1 ttl=64 time=0.123 ms
64 bytes from 10.10.0.10: icmp_seq=2 ttl=64 time=0.385 ms
64 bytes from 10.10.0.10: icmp_seq=3 ttl=64 time=0.050 ms
^C
--- 10.10.0.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2035ms
rtt min/avg/max/mdev = 0.050/0.186/0.385/0.143 ms
GestOS-コンテナ間での通信できた
-
エンジニアHub -コンテナ技術入門 https://eh-career.com/engineerhub/entry/2019/02/05/103000# ↩ ↩2
-
Redhat -コンテナとVM https://www.redhat.com/ja/topics/containers/containers-vs-vms ↩