Kubernetes 関連技術を自動化の流れに反して手作業でやってみよう!シリーズ2回目です。1回目は「Kubernetes の Service type LoadBalancer を手作業で作る」 でした。(シリーズのつもりはない。
CNI プラグインを手作業で利用する
Kubernetes でコンテナのネットワークインタフェースを設定するために使われている CNI
。その仕様も使い方も単純な割に意外とその詳細を知ってる人が少ない感じがしたのでこのドキュメントを記す。日本語で。
(英語が読める人はそのまんま 仕様 を読むのが早いと思います。)
CNI とは!
簡単にいうと、コンテナのネットワークインタフェースを設定するための、runtime
と plugin
間のインタフェースです。
ここでいう runtime
は例えば Kubernetes とか Apache Mesos になるわけです。で、今回のこの記事の趣旨は、 runtime
として Kubernetes の代わりに 人 がそのインタフェースを使って plugin
とやりとりし、あわよくばコンテナにネットワークインタフェースをブッ刺してやろう、そういうことになります。
コンテナとは!
Linux Container とは?ということにちゃんと答えようとすると色々面倒だったりするのですが、ことネットワーク、CNI の仕様が気にする Linux Container とは何か、というのは簡単です。要するに network namespace のことです。CNI は単純化すると network namespace に eth0, eth1 を追加するための仕様、プラグインはその実装、っていうことになります。
CNI プラグインとは!
ざっくりいうと CNI を実装したプラグインは以下のようなものだそうで。
- 実行可能なファイルであること。
- コンテナをネットワークに参加させる operation を実装していること。
- コンテナをネットワークから外す operation を実装していること。
- サポートする CNI version を表示できること。
Kubernetes はこの CNI プラグインを実行可能ファイルとして実行して、コンテナ/Pod にネットワークインタフェースを刺したり外したりしている、ということになります。
前提
それでは早速試して見ましょう、と行きたいところなんだけれども折角試すのだから既存の Kubernetes の pod network に自分で作ったコンテナを参加させたりしてみたい、ということで以下のような環境が準備されているものとします。
- 2 node の Kubernetes クラスター。
- Node01: 172.18.201.121
- Node02: 172.18.201.122
- CNI プラグインとして Flannel がすでにセットアップされている。
- Pod network: 10.244.0.0/16
- ついでに 10.244.5.64 というアドレスを持った Pod が起動している。
- CNI プラグインは
/opt/cni/bin
にインストールされている。
コンテナにネットワークインタフェースを刺す
Node01 にいる Pod とこれから作るコンテナ間で Ping
が届くか試して見たいので、Node02 にログインしてコンテナを作って見ましょう。
$ ssh core@172.18.201.121
CNI でいうコンテナとは単なる network namespace のことでした。ではコンテナを作ります。
$ sudo su -
# contid=test-container
# netns=/var/run/netns/$contid
# ip netns add $contid
できました。
CNI の仕様では引数を環境変数として設定して起動するので、環境変数を設定します。
export CNI_PATH=/opt/cni/bin
export CNI_COMMAND=ADD
export PATH=$CNI_PATH:$PATH
export CNI_CONTAINERID=$contid
export CNI_NETNS=$netns
export CNI_IFNAME=eth0
では、プラグインを実行しましょう。今回は flannel プラグインを実行して見ます。設定ファイルは標準入力で入力するようです。
# /opt/cni/bin/flannel <<EOF
> {
> "name": "cbr0",
> "type": "flannel",
> "delegate": {
> "isDefaultGateway": true
> }
> }
> EOF
{
"cniVersion": "0.2.0",
"ip4": {
"ip": "10.244.1.5/24",
"gateway": "10.244.1.1",
"routes": [
{
"dst": "10.244.0.0/16",
"gw": "10.244.1.1"
},
{
"dst": "0.0.0.0/0",
"gw": "10.244.1.1"
}
]
},
"dns": {}
}
何やら帰ってきました。早速コンテナにログインして確認してみます。
# ip netns exec $contid bash
# ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 0a:58:0a:f4:01:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.5/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::6057:23ff:fec4:1db6/64 scope link
valid_lft forever preferred_lft forever
IP アドレスがついてますね、素晴らしい!Node01 の Pod に Ping
は通るかしら。
# ping 10.244.5.64
PING 10.244.5.64 (10.244.5.64) 56(84) bytes of data.
64 bytes from 10.244.5.64: icmp_seq=1 ttl=62 time=0.449 ms
64 bytes from 10.244.5.64: icmp_seq=2 ttl=62 time=0.283 ms
64 bytes from 10.244.5.64: icmp_seq=3 ttl=62 time=0.353 ms
^C
--- 10.244.5.64 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2076ms
rtt min/avg/max/mdev = 0.283/0.361/0.449/0.071 ms
完璧です。
後片付けをしましょう。
# export CNI_COMMAND=DEL
# /opt/cni/bin/flannel <<EOF
{
"name": "cbr0",
"type": "flannel",
"delegate": {
"isDefaultGateway": true
}
}
EOF
# ip netns del $contid
Kubernetes が管理する Pod に2枚目の NIC を刺す
こうなってくると調子に乗りたくなってきます。CNI の仕様ではどうやらコンテナに NIC は何本でも刺さるようです。ということは、Kubernetes が管理する Pod も所詮は (自分が作った環境では) Docker コンテナの集合なので、その network namespace に NIC をブッ刺すことなんてさっきやったのと同じくらい簡単なはず。
まずは Pod の network namespace を取得しましょう。Pod の network namespace は pause コンテナーのものを共有している、ということは Kubernetes をやってる人ならみんなが知ってる常識です。適当な pause コンテナーの docker container id をゲットします。
# pause_contid=$(docker ps | grep pause | cut -f1 -d' ' | head -n1)
次に、pause コンテナの PID から network namespace を探し当てます。
# pid=$(docker inspect -f '{{ .State.Pid }}' $pause_contid)
# netns=/proc/$pid/ns/net
ここまできたら、あとは先ほどと手順は同じです。flannel プラグインをキックしてみましょう。まずは環境変数の設定、先ほどと違うのは NIC の名前が eth1 になっていることくらい。(すでに Kubenetes が eth0 を刺してるので)
export CNI_PATH=/opt/cni/bin
export CNI_COMMAND=ADD
export PATH=$CNI_PATH:$PATH
export CNI_CONTAINERID=$contid
export CNI_NETNS=$netns
export CNI_IFNAME=eth1
そして、flannel を実行。
# /opt/cni/bin/flannel <<EOF
> {
> "name": "cbr0",
> "type": "flannel",
> "delegate": {
> "isDefaultGateway": false
> }
> }
> EOF
{
"cniVersion": "0.2.0",
"ip4": {
"ip": "10.244.1.6/24",
"gateway": "10.244.1.1",
"routes": [
{
"dst": "10.244.0.0/16",
"gw": "10.244.1.1"
}
]
},
"dns": {}
}
どうやらついたようです。。。
先ほどの pause コンテナから対応する Pod を調べて、Pod にログインしてみましょう。
$ kubectl exec -ti hmd0-hackmd-7df97457-8gt7g sh
/hackmd $ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
3: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 0a:58:0a:f4:01:07 brd ff:ff:ff:ff:ff:ff
inet 10.244.1.7/24 scope global eth0
valid_lft forever preferred_lft forever
7: eth1@if22: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 0a:58:0a:f4:01:06 brd ff:ff:ff:ff:ff:ff
inet 10.244.1.6/24 scope global eth1
valid_lft forever preferred_lft forever
おー、2枚目の NIC がついとる!
忘れず後片付けを。
# export CNI_COMMAND=DEL
# /opt/cni/bin/flannel <<EOF
{
"name": "cbr0",
"type": "flannel",
"delegate": {
"isDefaultGateway": false
}
}
EOF
CNI の仕様によると NIC を刺した順に抜かなければならないようなので、その点を注意しなければならないかも。
以上でした。仕様を知ってると色々遊べそうですね!