Posted at

CNI プラグインを手作業で利用する

More than 1 year has passed since last update.

Kubernetes 関連技術を自動化の流れに反して手作業でやってみよう!シリーズ2回目です。1回目は「Kubernetes の Service type LoadBalancer を手作業で作る」 でした。(シリーズのつもりはない。


CNI プラグインを手作業で利用する

Kubernetes でコンテナのネットワークインタフェースを設定するために使われている CNI。その仕様も使い方も単純な割に意外とその詳細を知ってる人が少ない感じがしたのでこのドキュメントを記す。日本語で。

(英語が読める人はそのまんま 仕様 を読むのが早いと思います。)


CNI とは!

簡単にいうと、コンテナのネットワークインタフェースを設定するための、runtimeplugin 間のインタフェースです。

ここでいう runtime は例えば Kubernetes とか Apache Mesos になるわけです。で、今回のこの記事の趣旨は、 runtime として Kubernetes の代わりに がそのインタフェースを使って plugin とやりとりし、あわよくばコンテナにネットワークインタフェースをブッ刺してやろう、そういうことになります。


コンテナとは!

Linux Container とは?ということにちゃんと答えようとすると色々面倒だったりするのですが、ことネットワーク、CNI の仕様が気にする Linux Container とは何か、というのは簡単です。要するに network namespace のことです。CNI は単純化すると network namespace に eth0, eth1 を追加するための仕様、プラグインはその実装、っていうことになります。


CNI プラグインとは!

ざっくりいうと CNI を実装したプラグインは以下のようなものだそうで。


  1. 実行可能なファイルであること。

  2. コンテナをネットワークに参加させる operation を実装していること。

  3. コンテナをネットワークから外す operation を実装していること。

  4. サポートする 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 にインストールされている。

Kubernetes Cluster Network


コンテナにネットワークインタフェースを刺す

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 を刺した順に抜かなければならないようなので、その点を注意しなければならないかも。

以上でした。仕様を知ってると色々遊べそうですね!