pfiセミナーh271022 ~コマンドを叩いて遊ぶ コンテナ仮想、その裏側~

Post on 15-Feb-2017

1.299 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

コマンドを叩いて遊ぶコンテナ仮想、その裏側

Preferred Infrastructure, Inc.

製品開発事業部 武井裕也

自己紹介 武井 裕也

github: takei-yuya

元インターン組 ( 筑波勢 )

日本酒と鶏肉と bash と vim

と猫と高輝度 LED を愛してやまないエンジニア

はじめに コンテナ型仮想化

物理マシンを仮想化 ( エミュレーション )する完全仮想化に対し、 OS のレイヤー以下で異なる環境を動かす仮想化

✔ 軽量 ( 省メモリ・省リソース ) ✔ 高速 ( オーバーヘッドほぼ無し ) ✘ 異なる OS/ カーネルを動かせない

e.g. Windows on Linux

仮想マシンA

仮想マシンカーネルプロセス

仮想マシンB

仮想マシンカーネルプロセス

ハイパーバイザ( ホスト OS)ハードウエア

コンテナ Aプロセス

ホストカーネルハードウエア

コンテナ Bプロセス

名前空間 名前空間

VM 仮想化

コンテナ仮想化

コンテナー仮想化あれこれ (Linux)

LXC (Linux Containers) Docker 以前から名を馳せていたコンテナ仮想化 cgroups の産みの親

Docker コンテナ仮想化ブームの立役者 Go 言語製 1 コンテナ 1 プロセスを想定している LXC ベースだったが途中から libcontainer を使う用に 最近色々物議を醸している

root 権限中央集権デーモン、 docker pull のセキュリティ等

コンテナー仮想化あれこれ (Linux)

CentOS/Rocket Docker の諸問題を解決すべく作られたコンテナ仮想化 コンテナーのオープン仕様

systemd-nspawn systemd の一機能として提供される仮想化機能 Rocket の裏で使われてたりする

MINCS shell script で書かれたコンテナ仮想化 何をやっているのか勉強するのにとても良い

で、コンテナ仮想化ってなにやってるの? Ans. 大ざっぱには、単にプロセスを起動するだけ

ただし、そのプロセスの 1. ルート (/) の差し替え (chroot / pivot_root) 2. 名前空間の分離 (unshare / clone) 3. リソースの制御 (cgroups) をする

今回は「名前空間の分離」と「ルートの差し替え」を話します cgroups はスライド作成が間に合わず orz

Linux 名前空間 名前空間の種類

ipc: 共有メモリやメッセージキューなど mnt: マウントポイント net: ネットワーク全般 pid: プロセス ID uts: ホスト / ドメイン名 user: uid / gid (>=3.8)

RHEL7 では未サポート (Kernel 自体は 3.10) 今回はスキップ ( 面白い機能なのですが…… )

プロセスの名前空間 各プロセスはなにかしらの名前空間に属している

fork した場合には基本的には親の名前空間を引き継ぐ clone(2) / unshare(2) など新しい名前空間を作ることが出来

る コマンドラインで遊ぶ場合には unshare(1)

/proc/${PID}/ns 以下のシンボリックリンクのリンク先で区別できる

$ ll /proc/$$/ns合計 0lrwxrwxrwx. 1 takei takei 0 10 月 17 20:57 ipc -> ipc:[4026531839]lrwxrwxrwx. 1 takei takei 0 10 月 17 20:57 mnt -> mnt:[4026531840]lrwxrwxrwx. 1 takei takei 0 10 月 17 20:57 net -> net:[4026531992]lrwxrwxrwx. 1 takei takei 0 10 月 17 20:57 pid -> pid:[4026531836]lrwxrwxrwx. 1 takei takei 0 10 月 17 20:57 uts -> uts:[4026531838]

mnt 名前空間 unshare コマンドに --mount オプションを付けると、新しい

mount 名前空間に属するプロセスを作ることが出来る。 mount 名前空間を分離させると、プロセスごとに異なるマウン

トポイントを持てる (e.g. PrivateTemp)$ readlink /proc/$$/ns/mnt # 元の mount 名前空間の確認mnt:[4026531840]$ sudo unshare --mount /bin/bash # 新しい mount 名前空間の作成# readlink /proc/$$/ns/mnt # 新しい mount 名前空間の確認mnt:[4026532249]# mkdir mnt; mount -t tmpfs tmpfs mnt# mount # マウントポイントの確認# exit$ mount # 元のマウントポイントの確認

mnt 名前空間 - 余談 : マウントプロパゲーション マウントイベントの伝播を制御する仕組み

あるマウントポイント以下への mount/unmount のイベントを、関係するマウントポイントに伝播するかどうか。

shared(双方向 ) slave( 一方向 ) private( 伝播しない ) $ mkdir src dest src/{master,slave}# mount --bind src dest # src を dest に bind する# mount --make-slave dest # master から slave へは伝播する# mount -t tmpfs tmpfs src/master # マウント元 (src) へのマウント# mount -t tmpfs tmpfs dest/slave # マウント先 (dest) へのマウント$ mount tmpfs on /home/alice/src/master type tmpfs (rw,relatime,seclabel)tmpfs on /home/alice/dest/master type tmpfs (rw,relatime,seclabel)tmpfs on /home/alice/dest/slave type tmpfs (rw,relatime,seclabel)

mnt 名前空間 - 余談 : マウントプロパゲーション マウントプロパゲーションは mnt 名前空間を貫通する

もし名前空間で完全に区切られると……。 → 例えばプロセス独自 /tmp をマウントするために名前空間を

分離してデーモンを起動 → ユーザ CD-ROM をパソコンにいれる → CD-ROM が /media とかに自動マウントされる → デーモンは別 mnt 名前空間にいるのでマウントされない → デーモンさん (´ ・ ω ・ ) CD読めない……。

→ ディレクトリごとに、共有するしないが選べる!

mnt 名前空間 - 余談 : マウントプロパゲーション RHEL7 ではデフォルトで / が shared ( というか systemd のせ

い ) mnt 名前空間で遊ぶ場合、そもそも root(/) を切り替えるか、 一旦マウントプロパゲーションを private にする必要あり$ sudo unshare --mount /bin/bash # 新しい mount 名前空間の

作成# mkdir mnt# mount --make-private / # プロパゲーションを切る# mount -t tmpfs tmpfs mnt# mount --make-shared / # 元に戻す# mount # マウントポイントの確認# exit$ mount # 元のマウントポイントの確認

pid 名前空間 unshare --pid --fork で新しい pid 名前空間でプロセスを起動

新しいプロセスを作るために --fork が必須 新しいプロセスが pid=1 となり、他のプロセスは見られなくな

る 元の pid 名前空間 ( 親 ) からは新しい pid 名前空間の中身は見え

る ※ ps(1) コマンドなどは procファイルシステムを参照する

mount -t proc proc /proc でマウントしなおせば ok ただし mnt 名前空間を分離していないと他のプロセスの /

proc まで上書きマウントされるので注意。 --mount-proc オプションでこの辺を全部やってくれる

uts 名前空間 unshare --uts ホスト名やドメイン名の新しい名前空間を作成 /etc/hostname などファイルを書き換えるようなホスト名の指定は当然ファイルを書き換えるので注意。 (chroot や mntns など必要 )

$ hostnameip-172-31-13-102.ap-northeast-1.compute.internal$ sudo unshare --uts# hostname wonderland# hostnamewonderland# logout$ hostnameip-172-31-13-102.ap-northeast-1.compute.internal

net 名前空間 unshare --net で新しい net 名前空間でプロセスを起動 ただし ip(1) を使った方が名前付きで作れるのでおすすめ

/var/run/netns 以下に指定された名前で procfs がマウントされる$ sudo ip netns add test # test という名前で

netns を作成$ sudo ip netns list # 名前空間一覧test$ sudo ip netns exec test /bin/bash # test 名前空間でプロセスを起動# readlink /proc/$$/ns/net # netns 確認net:[4026532219]# ls -li /var/run/netns/test # /var/run/netns 以下にファイルができる4026532219 -r--r--r--. 1 root root 0 Oct 18 03:02 /run/netns/test# ip addr # loインターフェースしか見えない1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

net 名前空間 - 余談 : veth

通信するために仮想 eth インタフェース (veth) を作って遊んでみる 1. vethペア (master/slave) の作成と netns の設定

$ sudo ip link add name master type veth peer name slave # vethペア作成$ sudo ip addr # 確認6: slave: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 3a:64:e8:80:03:5f brd ff:ff:ff:ff:ff:ff7: master: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 86:cf:cc:26:74:e4 brd ff:ff:ff:ff:ff:ff$ sudo ip link set slave netns test # 片方を netns test に移動$ sudo ip addr # インターフェースの確認7: master: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 86:cf:cc:26:74:e4 brd ff:ff:ff:ff:ff:ff$ sudo ip netns exec test ip addr6: slave: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 3a:64:e8:80:03:5f brd ff:ff:ff:ff:ff:ff

net 名前空間 - 余談 : veth

通信するために仮想 eth インタフェース (veth) を作って遊んでみる 2. IP アドレス割り振り & 疎通確認

$ sudo ip addr add 192.168.50.101/24 dev master # master に IP アドレス設定$ sudo ip link set dev master up # リンクアップ$ sudo ip netns exec test /bin/bash # 名前空間内に bash 起動# ip addr add 192.168.50.102/24 dev slave # slave に IP アドレス設定# ip link set dev slave up # リンクアップ# ping 192.168.50.101 -c1 # 疎通確認PING 192.168.50.101 (192.168.50.101) 56(84) bytes of data.64 bytes from 192.168.50.101: icmp_seq=1 ttl=64 time=0.047 ms# exit$ ping 192.168.50.102 -c1

net 名前空間 - 余談 : veth

通信するために仮想 eth インタフェース (veth) を作って遊んでみる 3. IP マスカレードの設定 & 外部ネットーワークへの疎通確認

$ sudo ip netns exec test /bin/bash# ip route add default via 192.168.50.101 dev slave # default gw の設定# ip routedefault via 192.168.50.101 dev slave192.168.50.0/24 dev slave proto kernel scope link src 192.168.50.102# exit$

# IP マスカレードの設定$ sudo iptables -t nat -A POSTROUTING -s 192.168.50.0/24 -o eth0 -j MASQUERADE$ sudo ip netns exec test /bin/bash# ping 8.8.8.8 -c1

# 外部への疎通確認PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.64 bytes from 8.8.8.8: icmp_seq=1 ttl=55 time=2.18 ms

Linux 名前空間 名前空間の種類

ipc: 共有メモリやメッセージキューなど mnt: マウントポイント net: ネットワーク全般 pid: プロセス ID uts: ホスト / ドメイン名 user: uid / gid

ユーザごとにユーザ一覧を持てる 一般ユーザなのに root っぽいユーザも作れる!

chroot

古の昔からある仮想化 (?) root(/) を張り替えた状態でプロセスを起動する 元々はビルドシステムをクリーンな / で実行する目的で作られた システムファイルを隠せるのでセキュリティ目的に使われることも 新しい root にもシステム (bin や lib 等 ) が必要なので chroot する

ためには OS のイメージなど (Gentoo の stage3 とか ) が必要 RHEL系の場合、 installroot を指定して Core 等を入れ直せば

okmkdir new-rootsudo yum -y --releasever=7Server --installroot=${PWD}/new-root install \ @Core @Base redhat-release-server vim-enhanced

pivot_root

chroot に似た機能 ディレクトリをルートにしてプロセスを起動する chroot に対し

て ルートファイルシステムを入れ替えることでルートを変える マウントポイントを変更するので要 mnt 名前空間

旧ルートは新ルートのサブディレクトリにマウントされる$ sudo unshare -m -p -f /bin/bash # pid/mnt 名前空間切り離し# mount --make-rprivate / # プロパゲーション off# mount -o loop /root.img /mnt/new-root/ # 新しい root をマウント# cd /mnt/new-root/# mkdir .old

# 旧 root のマウントポイント# pivot_root . .old # pivot!# ls /.old

# 旧 root の中身をみてみるbin boot data dev etc home lib...

pivot_root と chroot

pivot_root はマウントポイントを差し替えるだけなので、新しいルートの shell を exec し直したり、 chroot するなりする必要アリ 古い root を unmount するためには pts やらバイナリやらの張り直し、 exec し直しが必要になる

MINCS の場合、 pivot_root を二回 ( ルートの変更、旧ルートの移動 ) したあとで chroot している

ディレクトリやデバイスひとつが、 1つの環境になる → コンテナを複数作りたい場合には、複数デバイスや OS イメ

ージが必要……?

chroot - 余談 : overlayfs

ディレクトリを「重ねて」マウントできる fs 変更は「上」にだけ保存され「下」には影響を与えない 基準となるイメージを共有しつつ chroot ごとに差分を適応でき

る$ mkdir upper work # 差分ディレクトリと作業用ディレクトリ$ sudo mount -t overlay \ -o lowerdir=/,upperdir=upper,workdir=work overlayfs new-root$ touch /home/alice/file1 new-root/home/alice/file2$ ls -l new-root/home/alice/file* # 両方とも見える-rw-rw-r--. 1 alice alice 0 Oct 18 12:30 new-root/home/alice/file1-rw-rw-r--. 1 alice alice 0 Oct 18 12:30 new-root/home/alice/file2$ rm new-root/home/alice/file1 # ファイルを消してみる$ ll upper/home/alice/file* # 差分が upper にだけ残るc---------. 1 alice alice 0, 0 Oct 18 12:32 upper/home/alice/file1-rw-rw-r--. 1 alice alice 0 Oct 18 12:30 upper/home/alice/file2

chroot - 余談 : thin provisioning(device mapper) device mapper の一機能 : thin porvisioning (dm-thin)

ディスク領域のプーリングとスナップショットの機能を持つ プーリング : 必要に応じてストレージプールから一部分を取り出し、一意な id を振って管理する

スナップショット : 切り出してきたストレージパッチを使い CoW(Copy On Write) 差分スナップショットを提供する

LVM の皮をかぶせて、容量変更やスナップショットを、ヴォリュームレベルで提供できる

RHEL の Docker のデフォルトでは生 dm-thin を使っている

chroot - 余談 : thin provisioning(device mapper) 手作業でいじるのは面倒なので Docker の挙動を追ってみる 1. loop back に dm-thin の pool を割り当てる

実体は /var/lib/docker/devicemapper/devicemapper/{,meta}data

$ sudo systemctl start docker # docker デーモンの起動

$ losetup # loop に pool が割り当てられる

NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE/dev/loop0 0 0 1 0 /var/lib/docker/devicemapper/devicemapper/data/dev/loop1 0 0 1 0 /var/lib/docker/devicemapper/devicemapper/metadata$ sudo ls -hl /var/lib/docker/devicemapper/devicemapper/ # 100G と 2G ( デフォルト値 )total 4.5G-rw-------. 1 root root 100G Oct 19 04:54 data-rw-------. 1 root root 2.0G Oct 19 04:56 metadata

chroot - 余談 : thin provisioning(device mapper) (続き ) デーモン起動時 : スパースファイル

pool は RHEL7 デフォルトだとスパースファイルで定義される ただし、スパースファイルは断片化しやすく、 loopback マウ

ントも性能もよろしくないので RHEL7 的には本番環境では非推奨 → dm-thin 用の LV を割り当てよう。$ sudo du -h /var/lib/docker/devicemapper/devicemapper/data

4.4G /var/lib/docker/devicemapper/devicemapper/data$ sudo ls -lh /var/lib/docker/devicemapper/devicemapper/data-rw-------. 1 root root 100G Oct 19 04:54 /var/lib/docker/devicemapper/devicemapper/data

$ fallocate -o 9223372036854775807 -l 1 huge # スパースファイルは fallocate で作れる$ ls -lh huge; du -h huge # 8EB( エクサバイト )!!-rw-r--r--. 1 alice alice 8.0E Oct 19 05:10 huge4.0K huge

chroot - 余談 : thin provisioning(device mapper) 2. コンテナの起動

10G ずつ切り出されてコンテナ用に使われる (RHEL7 default)$ docker run -d centos:centos7 /sbin/init # 適当なコンテナを起動$ docker ps # コンテナー ID の確認 CONTAINER ID IMAGE COMMAND ...b90ed5b981ae centos:centos7 "/sbin/init" ...$ lsblk # ブロックデバイス一覧を確認NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTxvda 202:0 0 30G 0 disk├─xvda1 202:1 0 1M 0 part└─xvda2 202:2 0 30G 0 part /loop0 7:0 0 100G 0 loop└─docker-202:2-62765-pool 253:0 0 100G 0 dm └─docker-202:2-62765-b90ed5b981ae9d06...ee67 253:1 0 10G 0 dmloop1 7:1 0 2G 0 loop└─docker-202:2-62765-pool 253:0 0 100G 0 dm └─docker-202:2-62765-b90ed5b981ae9d06...ee67 253:1 0 10G 0 dm

chroot - 余談 : thin provisioning(device mapper) 3. run されていないイメージの情報

$ # タグ付きイメージの id の確認$ sudo jq . /var/lib/docker/repositories-devicemapper{ "Repositories": { "test": { "latest": "a02698bf3...e5c42b" } }, "ConfirmDefPush": true}$ # イメージの dm-thin デバイス情報$ sudo jq . /var/lib/docker/devicemapper/metadata/a02698bf3...e5c42b{ "device_id": 352, "size": 10737418240, "transaction_id": 582, "initialized": false}

chroot - 余談 : thin provisioning(device mapper) (おまけ ) docker のイメージをマウントしてみる

$ # device_id と size を確認$ sudo jq . /var/lib/docker/devicemapper/metadata/a02698bf...5c42b ... "device_id": 352, "size": 10737418240, ...$ # プール名を確認$ lsblkloop0└─docker-202:2-62765-pool

$ # dm デバイスの準備$ sudo dmsetup create dockervol \ --table "0 $((10737418240 / 512)) thin /dev/mapper/docker-202:2-62765-pool 352"$ # dm デバイスの確認とマウント$ ll /dev/mapper/dockervollrwxrwxrwx. 1 root root 7 Oct 19 06:10 /dev/mapper/dockervol -> ../dm-3$ mkdir mnt$ sudo mount /dev/mapper/dockervol mnt

chroot - 余談 : thin provisioning(device mapper) (続き ) docker のイメージをマウントしてみる

$ ll mnt/ # 早速中を見てみるtotal 24-rw-------. 1 root root 64 Aug 26 23:08 iddrwx------. 2 root root 16384 Aug 26 22:58 lost+found$ ll mnt/rootfs/ # docker のディスクイメージの中身 (OS)total 64lrwxrwxrwx. 1 root root 7 Jun 18 08:34 bin -> usr/bindrwxr-xr-x. 3 root root 4096 Oct 18 12:56 boot:$ sudo cat mnt/id # id はオリジナルのディスクイメージの id らしいf1b10cd842498c23d206ee0cbeaa9de8d2ae09ff3c7af2723a9e337a6965d639$ docker history test:latestIMAGE CREATED CREATED BY ...a02698bf3120 17 hours ago /bin/sh -c yum install -y httpda6673f7926d7 7 weeks ago /bin/sh -c #(nop) MAINTAINER TAKEI Yuya <take7322fbe74aa5 4 months ago /bin/sh -c #(nop) CMD ["/bin/bash"]c852f6d61e65 4 months ago /bin/sh -c #(nop) ADD file:82835f82606420c764f1b10cd84249 6 months ago /bin/sh -c #(nop) MAINTAINER The CentOS Proje

まとめ コンテナ仮想化?

→ ただの名前空間・ルート ( ・リソース ) の切り離しだ! 名前空間は、 OS につき一つしかないものを分離する機構

unshare で分離できる! ルートは chroot や pivot_root で切れる

複数の環境を管理するために overlayfs や dm-thin が便利 リソースは cgroups( コントロールグループ ) で管理できる

今回は資料が間に合いませんでした orz

参考 URL Red Hat Enterprise Linux 7 - 製品マニュアル

https://access.redhat.com/site/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/index.html IBM developerWorks - マウント名前空間を適用する

http://www.ibm.com/developerworks/jp/linux/library/l-mount-namespaces.html github - MINCS

https://github.com/mhiramat/mincs TenForward の日記 - シェルスクリプトで書かれた軽量コンテナ MINCS がすばらしい (1)

http://d.hatena.ne.jp/defiant/20150701/1435749116 めもめも - Docker のネットワーク管理と netns の関係

http://enakai00.hatenablog.com/entry/20140424/1398321672 めもめも - RHEL7 における Docker のディスクイメージ管理方式

http://enakai00.hatenablog.com/entry/20140420/1397981156 paiza 開発日誌 - 知らぬはエンジニアの恥。今さら聞けない【コンテナ / 仮想化技術】 11選

http://paiza.hatenablog.com/entry/2014/10/21/知らぬはエンジニアの恥%E3%80%82 今さら聞けない【コン

top related