Kubernetes で意図的に障害を起こしたらどうなるのか?

Posted on

Kubernetes を本格的に使っていくにあたり Kubernetes の裏側の仕組みを勉強しています。抽象化が進みブラックボックスになっているものを何となくの知識で運用するのは怖いからです。仕組みをちゃんと理解しているかどうかは障害時にはっきりと現れます。

というわけで、Kubernetes で意図的に障害を起こしたらどうなるのか試してみました。今回は特殊な Node 障害を想定して、Kubernetes のネットワークで重要な役割を担っている iptables のルールがすべて消えたという想定です。

なお、この検証で使った Dockerfile や Kubernetes の Manifest ファイルは GitHub で公開しています。 Docker イメージも Docker Hub の Public リポジトリにあるので、Kubernetes クラスタさえあればすぐに試すことができます。

検証環境

AWS 上に Kops で立てた Kubernetes クラスタで検証しています。 Kubernetes のバージョンは 1.8.6 です。

Kubernetes クラスタの Node, Pod, Service の状態は次のとおり。

$ kubectl get no
NAME                                               STATUS    ROLES     AGE       VERSION
ip-172-20-46-130.ap-northeast-1.compute.internal   Ready     master    17h       v1.8.6
ip-172-20-64-88.ap-northeast-1.compute.internal    Ready     node      18h       v1.8.6

$ kubectl get po
NAME                               READY     STATUS    RESTARTS   AGE
k8s-hello-world-55f48f8c94-7shq5   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-9w5tj   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-cdc64   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-lkdvj   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-npkn6   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-ppsqk   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-sc9pf   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-tjg4n   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-vrkr9   1/1       Running   0          1m
k8s-hello-world-55f48f8c94-xzvlc   1/1       Running   0          1m

$ kubectl get svc
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
k8s-hello-world   NodePort    100.69.211.31   <none>        8080:30000/TCP   3h
kubernetes        ClusterIP   100.64.0.1      <none>        443/TCP          18h

Node は 1 台でその上に 10 個の Pod が動いています。 Service の NodePort へは ALB を経由してインターネットからアクセスできるようにしています。

iptables のルールを削除してみる

さっそく Node サーバにログインして iptables のルールを iptables -F ですべて削除してみます。これで Service と Pod の通信が絶たれるはずです。

このとき、別のターミナルから 1 秒おきに curl コマンドを叩いてリクエストが落ちる瞬間があるか確認します。比較できるように date +%s でタイムスタンプを表示しています。

$ date +%s && sudo iptables -F
1519526806
$ while sleep 1; do date +%s; curl -sS http://k8s-hello-world.manabusakai.com/ | grep ^Hello; done
(snip)
1519526803
Hello world! via k8s-hello-world-55f48f8c94-vrkr9
1519526804
Hello world! via k8s-hello-world-55f48f8c94-tjg4n
1519526805
Hello world! via k8s-hello-world-55f48f8c94-vrkr9
1519526806
Hello world! via k8s-hello-world-55f48f8c94-tjg4n
1519526807
Hello world! via k8s-hello-world-55f48f8c94-tjg4n
1519526834
Hello world! via k8s-hello-world-55f48f8c94-vrkr9
1519526835
Hello world! via k8s-hello-world-55f48f8c94-vrkr9
^C

結果を見ると 1519526807 のあとに 27 秒の間が空いて 1519526834 にリクエストが返ってきています。この間 curl コマンドは応答を待っている状態になっていました。

再び Node サーバで iptables -L すると削除したはずのルールが元に戻っています。

$ sudo iptables -L KUBE-FORWARD
Chain KUBE-FORWARD (1 references)
target     prot opt source               destination
ACCEPT     all  --  anywhere             anywhere             /* kubernetes forwarding rules */ mark match 0x4000/0x4000
ACCEPT     all  --  100.96.0.0/11        anywhere             /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             100.96.0.0/11        /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED

何度やってもだいたい 20 秒ちょっとで元に戻ります。この 20 秒という数字はあとで理由がわかります。

誰が iptables のルールを元に戻したのか?

iptables のルールを削除してもすぐに元に戻ることはわかりました。ここで疑問なのが、誰が iptables のルールを元に戻しているのかということ。

iptables を使って Pod へのプロキシを行なっているのは kube-proxy ですが、/var/log/daemon.log を追いかけると iptables -F でルールを削除したあとに次のようなログが出力されていました(本来は 1 行です)。

Feb 25 03:17:33 ip-172-20-64-88 kubelet[1229]: I0225 03:17:33.070531
1229 qos_container_manager_linux.go:320] [ContainerManager]: Updated QoS cgroup configuration

ソースコードを追いかけると kubernetes/pkg/kubelet/cm/qos_container_manager_linux.go の中でこのログを出力していました。

kubelet の説明を読むと次のような記述があります。

The kubelet takes a set of PodSpecs that are provided through various mechanisms (primarily through the apiserver) and ensures that the containers described in those PodSpecs are running and healthy.

File: Path passed as a flag on the command line. Files under this path will be monitored periodically for updates. The monitoring period is 20s by default and is configurable via a flag.

kubelet のプロセスを確認すると、たしかに --pod-manifest-path=/etc/kubernetes/manifests という引数が指定されています。そのディレクトリを確認すると…。

$ ls -l /etc/kubernetes/manifests
total 4
-rw-r--r-- 1 root root 1398 Feb 24 08:08 kube-proxy.manifest

kube-proxy.manifest というファイルがありました! kubelet は 20 秒おきに状態を監視して、違っていれば Manifest ファイルの設定に戻しているわけですね。検証したときに 20 秒ちょっとで元に戻ったのもつじつまが合います。

まとめ

Kubernetes の耐障害性は、よくできた仕組みで実現されていることがわかりました。

ドキュメントに書かれたことを鵜呑みにせず、自分で手を動かしてソースコードを読んでみることが大切ですね。エンジニアとしてこの基本を忘れずにいようと思います。

Popular Entries

Recent Entries