2020.07.03

KubernetesのServiceの動きを調べてみた

Pocket

こんにちは。次世代システム研究室のM.Mです。

現在、Kubernetes, Docker構成で作られたWebアプリの開発を担当しているのですが、そのWebアプリの開発ではあまり環境構築周りを担当していなかったため、Kubernetesについてあまり理解できていませんでした。
そして今回、古くなった他のWebアプリをKubernetes, Docker構成に移行するプロジェクトを担当することになり、現在担当しているWebアプリのKubernetesの設定を確認しつつ理解を深めていくことにしました。

まず気になっていたKubernetesのServiceについて調べてみることにしました。
今回、利用しているのはNodePort Serviceです。

目次

  1. KubernetesのServiceの何が気になっていたのか?
  2. Serviceの動きについて
  3. Serviceの動きの制御について

KubernetesのServiceの何が気になっていたのか?

まず、システム構成は以下の図の通り

最上部にLBがあります。
WEBアプリへのアクセスはそのLBにて、Web1とWeb2に分かれ、さらにWeb1、Web2のnginxの設定でNode1とNode2に分かれ、Node1、Node2上で稼働しているPHPのWEBアプリにアクセスが行くようになっています。

では、Node2へのアクセスを止めたい場合、Web1、Web2のnginxの設定でNode1にのみ振り分ければよいのでしょうか?
実際に、Web1とWeb2のnginxの設定を変更してNode1にのみアクセスが行くようにしたところ、以下の図のようなアクセスの仕方をしました。

なぜか、Node2へのアクセスが止まらない。
このNode2へのアクセスを制御しているのがKubernetesのServiceなんだろうなとは思いましたが、いったいどこで動いている?
Web1、Web2で振り分けているのに、Serviceでも振り分けるのが普通なのかな?
疑問に思ったまま放置もよくないので、もう少し調べてみることにしました。

Serviceの動きについて

実際には、Web1、Web2のnginxの設定は、上記図のようにNode1、Node2のNginxの80ポートを指定してるのではなく、Serviceを生成したときのNodePortを指定しています。
作成したServiceの情報は以下のようになっており、11行目にあるようにNodePortは31000になっています。
$ kubectl describe service test-service
Name:                     test-service
Namespace:                default
Labels:                   app=test-server
Annotations:              ....
Selector:                 app=test-server
Type:                     NodePort
IP:                       10.43.174.31
Port:                       80/TCP
TargetPort:               80/TCP
NodePort:                   31000/TCP
Endpoints:                10.42.19.2:80,10.42.20.2:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   
また、Web1、Web2のnginxの設定は以下のようにといった31000ポートを指定しています。
upstream testapp {
  server 192.168.0.11:31000 max_fails=3 fail_timeout=7s;   #Node1
  server 192.168.0.12:31000 max_fails=3 fail_timeout=7s;   #Node2
}
実際のアクセスイメージは以下の図のようになります。

Serviceがさらに設定されているEndpointに振り分けているのが分かります。
なので、先ほどのようにWeb1、Web2のnginxの設定で、Node2にアクセスしないようにしたところで、ServiceがNode2にアクセスを振り分けます。

では、どこで、どうやって振り分けているのか?
もう少し調べみると、kube-proxyというコンテナが動いていて、iptablesを操作しているとのこと。
iptablesの設定状況を調べてみると、以下のような設定がありました。(対象箇所だけ抜き取っています)
Chain KUBE-SVC-FCDRZOMGBO47PTZS (2 references)
target     prot opt source               destination
KUBE-SEP-XMEIUTLIHCVEEQRB  all  --  0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.50000000000
KUBE-SEP-T4IG46XR4M43OGK2  all  --  0.0.0.0/0            0.0.0.0/0

Chain KUBE-SEP-XMEIUTLIHCVEEQRB (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  10.42.19.2           0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp to:10.42.19.2:80

Chain KUBE-SEP-T4IG46XR4M43OGK2 (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  10.42.20.2           0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp to:10.42.20.2:80
右にスクロールしないと見えませんが、3行名の右端に”statistic mode random probability 0.50000000000″という記載があります。
このiptablesの設定でServiceは振り分けを行っていると分かりました。
kube-proxyが各kubernetes Nodeで動いており、Serviceが追加されたり、変更された場合、各Kubernetes Node上のiptablesが変更される。
Serviceがどこかで動いているというよりは、全Kubernetes Node上のiptablesで制御している模様。

Serviceの動きの制御について

Node2へアクセスが行かないようにするには、Web1、Web2のnginxの設定だけでは足りないことが分かりました。
では、どのように制御すればよいのか?

Node2で稼働しているpodのlabel名を別のlabel名に変更してServiceのEndpointの対象から外す

Node1、Node2で動いているpodのlabel名はapp=test-serverとしています。
また、Serviceは以下のようにapp=test-serverというlabel名のPodに振り分けるような設定になっています。
apiVersion: v1
kind: Service
metadata:
  name: test-service
  labels:
    app: test-server
spec:
  ports:
  - port: 80
    targetPort: 80
    nodePort: 31000
  selector:
    app: test-server
  type: NodePort
そして、以下のようにNode2(192.168.0.12)のlabelをapp=hogehogeに変更して、test-serviceのEndpointsを確認します。
$ kubectl label node 192.168.0.12 app=hogehoge --overwrite
node/192.168.0.12 labeled

$ kubectl describe service test-service
Name:                     test-service
Namespace:                default
Labels:                   app=test-server
Annotations:              ....
Selector:                 app=test-server
Type:                     NodePort
IP:                       10.43.174.31
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  31000/TCP
Endpoints:                10.42.19.2:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
15行名をみると、EndpointsからNode2(192.168.0.12)上で動いていたPod IP(10.42.20.2)のEndpointが消えていることが分かります。
もちろんiptablesの設定も変わっていました。
labelを変更してServiceのEndpointを外すことで、Node2へアクセスが行かないようにすることができました。

そもそもWeb1、Web2のnginxで振り分けているので、Serviceでは振り分けないようにする

デフォルトだと、Serviceはバランシング設定になるようで、以下のような設定にしてバランシングしないようにしてしまう。
apiVersion: v1
kind: Service
metadata:
  name: test-service
  labels:
    app: test-server
spec:
  ports:
  - port: 80
    targetPort: 80
    nodePort: 31000
  selector:
    app: test-server
  type: NodePort
  externalTrafficPolicy: Local
15行目にexternalTrafficPolicy: Localを追加しています。
externalTrafficPolicy: Localを追加すればアクセスがあったNode上のPodにしか転送されなくなります。
(もちろん、これは設計段階で決めるべきことだとは思いますが・・)

所感

今回は、すでにKubernetes, Docker構成で作られたWebアプリの設定を見ながら、疑問に思ったところを調べる形になりましたが、Kubernetesを一通り勉強してから環境構築すると、このブログで記載した内容は知っていて当たり前なのかも知れません。

私は古いシステムで動くWebアプリを長く開発・運用していたので、どのサーバーが落ちたらどうなる?サーバーをメンテナンスで停止したい場合はどうすればいい?
など、古いシステムだと何となくイメージが付くのですが、Kubernetesの場合、まだまだ分からないことが多そうです。
Kubernetesの前に、ネットワーク知識などもしっかり身に付けないとダメだなと思いながらも、今後も少しづつKubernetesの理解を深めてい行こうと思います。

最後に、次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。