kubernetes
ibmcloud
IBM-Cloud

【IBM Cloud k8s検証メモ】 k8sクラスタの通信についての検証レポ

k8sクラスタ内のサービスへ、k8sクラスタから外部システムへ、外部システムからk8sクラスタへのアクセスについて検証したものです。

k8s の実システムへの適用についてディスカッションすると、既存システムや、DBなどのベアメタルとの接続性や相互運用性について、イメージを描けないと言われる事があります。 これは、もっと、しっかりとイメージを描ける様に努力するべき点だと認識させられます。そんな事もあり、この検証を実施しました。

ノードのIPアドレス

IBM Cloud の k8sクラスタでは、k8sマスタはIBMのマネージドで起動し、k8sノードは、IBM Cloud Infrastructure 仮想サーバーとしてパブリックIPとプライベートIPアドレスを付与されて起動します。ユーザーはインターネットへのアクセス環境から、Bluemix CLIコマンド、kubectlコマンドを利用して、k8sクラスタを操作する事になります。 これらを概要図にすると次の様になります。

スクリーンショット 2017-12-21 0.29.18.png

次のslcliコマンドの実行結果は、東京データセンターでk8sクラスタを起動した例で、上記の図の水色網掛けの「ユーザーの仮想サーバーインスタンス」に相当する部分です。k8sノードは パブリックIP と プラベートIP を付与されて起動する事が解ります。
(以下、現在も動作しているノードのパブリックなIPアドレスなので、xxx.xxxとしてマスクさせてもらいました。)

$ slcli vs list -d tok02
:..........:..................................................:.................:...............:............:........:
:    id    :                     hostname                     :    primary_ip   :   backend_ip  : datacenter : action :
:..........:..................................................:.................:...............:............:........:
-    :
: 45360485 : kube-tok02-cr20bee482ba9d4a9687dea557b8b13271-w1 : 161.202.xxx.xxx : 10.132.253.17 :   tok02    :   -    :
: 45360539 : kube-tok02-cr20bee482ba9d4a9687dea557b8b13271-w2 : 161.202.xxx.xxx : 10.132.253.38 :   tok02    :   -    :
: 45360553 : kube-tok02-cr20bee482ba9d4a9687dea557b8b13271-w3 : 161.202.xxx.xxx : 10.132.253.30 :   tok02    :   -    :
:..........:..................................................:.................:...............:............:........:

ユーザーは、k8sクラスタ以外に、仮想サーバーやベアメタルサーバーを利用していれば、ユーザー専用のプライベートのアドレス範囲を通じて、連携できることになります。 また、k8sノードはパブリックIPを持っていますから、インターネット上の資源をアクセスすることも可能なはずです。そこで、具体的にアクセスできる範囲を確認してみました。

テスト環境のセットアップ

ここで、同じk8sクラスタ内に、デプロイメントとして、レプリカ・セットにポッドが3個設定されたexpress-appが存在します。
このexpress-appは、TCP 3000番ポートでリッスンしており、http://:3000/hostname でポッドのホスト名を返すRESTサービスが稼働しています。

$ kubectl get deployment
NAME          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
bash          1         1         1            1           10h
express-app   3         3         3            3           13h
$ kubectl get pod -o wide
NAME                           READY     STATUS    RESTARTS   AGE       IP               NODE
bash-57559bc848-xm6dk          1/1       Running   1          10h       172.30.11.46     10.132.253.30
express-app-77d5f67b99-gfxw4   1/1       Running   0          2m        172.30.11.58     10.132.253.30
express-app-77d5f67b99-rnx9p   1/1       Running   0          2m        172.30.170.239   10.132.253.17
express-app-77d5f67b99-sb6kc   1/1       Running   0          2m        172.30.180.159   10.132.253.38

k8sクラスタ内に、ubuntuのポッドを起動して疎通を確認していきます。

$ kubectl run -it --image ubuntu:latest bash

疎通確認の前に、updateして、必要なパッケージをインストールします。

/# apt-get update
/# apt-get install curl net-tools iputils-ping

ここでテスト用に作った環境の概要図を次に示します。

スクリーンショット 2017-12-21 0.51.48.png

これから、順番に疎通を確認していきます。

同一ノード上のポッドへの疎通

前述のkubectl get pod -o wideの結果からノードのIPアドレスを確認すると、bashで対話しているポッドと同じノード上に存在するポッドが、ノードのIPアドレスから判別できます。

スクリーンショット 2017-12-20 22.38.56.png

root@bash-57559bc848-xm6dk:/# ping -c 3 172.30.11.58 
PING 172.30.11.58 (172.30.11.58) 56(84) bytes of data.
64 bytes from 172.30.11.58: icmp_seq=1 ttl=63 time=0.093 ms
64 bytes from 172.30.11.58: icmp_seq=2 ttl=63 time=0.084 ms
64 bytes from 172.30.11.58: icmp_seq=3 ttl=63 time=0.060 ms
--- 172.30.11.58 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.060/0.079/0.093/0.013 ms

RESTサービスにアクセスすると、目的のポッドのホスト名を返してくれます。

root@bash-57559bc848-xm6dk:/# curl http://172.30.11.58:3000/hostname
hostname = express-app-77d5f67b99-gfxw4

異なるノード上のポッドへの疎通

次に、異なるノード上のポッドにアクセスします。ノードが異なる事で、pingのラウンド・トリップ時間が長くなります。

スクリーンショット 2017-12-20 22.39.04.png

root@bash-57559bc848-xm6dk:/# ping -c 3 172.30.170.239
PING 172.30.170.239 (172.30.170.239) 56(84) bytes of data.
64 bytes from 172.30.170.239: icmp_seq=1 ttl=62 time=0.358 ms
64 bytes from 172.30.170.239: icmp_seq=2 ttl=62 time=0.414 ms
64 bytes from 172.30.170.239: icmp_seq=3 ttl=62 time=0.353 ms

--- 172.30.170.239 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.353/0.375/0.414/0.027 ms

ホスト名を確認すると、異なるポッドにアクセスしていることが判ります。

root@bash-57559bc848-xm6dk:/# curl http://172.30.170.239:3000/hostname
hostname = express-app-77d5f67b99-rnx9p

ping応答時間が、同一ノード上ポッドでは0.06〜0.09ミリ秒台、他ノード上ポッドでは0.3〜0.4秒台と一桁の差があることが判ります。

もう一つのポッドへの疎通

ノードによる差は、同一データセンター内なので、ほとんど検知できない範囲と思われます。

スクリーンショット 2017-12-20 22.39.13.png

root@bash-57559bc848-xm6dk:/# ping -c 3 172.30.180.159
PING 172.30.180.159 (172.30.180.159) 56(84) bytes of data.
64 bytes from 172.30.180.159: icmp_seq=1 ttl=62 time=0.560 ms
64 bytes from 172.30.180.159: icmp_seq=2 ttl=62 time=0.466 ms
64 bytes from 172.30.180.159: icmp_seq=3 ttl=62 time=0.375 ms

--- 172.30.180.159 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.375/0.467/0.560/0.075 ms
root@bash-57559bc848-xm6dk:/# curl http://172.30.180.159:3000/hostname
hostname = express-app-77d5f67b99-sb6kc

クラスタIPアドレスへの疎通

RESTのクライアントは、各ポッドのIPアドレスやホスト名を意識してアクセスするのは、管理上大変です。
この問題の解決策として、k8sではサービスを定義して、ポッドのレプリカセットを抽象化してアクセスすることができます。

スクリーンショット 2017-12-20 22.51.30.png

そこで、下記のYAML定義を利用して、デプロイメント express-app を抽象化するサービスを立ち上げます。

express-app-svc.yml
apiVersion: v1
kind: Service
metadata:
  name: express-app-svc
spec:
  selector:
    app: express
  ports:
  - protocol: TCP
    port: 3000

次のコマンドで、サービスの設定を実行します。

$ kubectl create -f express-app-svc.yml

サービスの実行を確認します。

$ kubectl get svc
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
express-app-svc   ClusterIP   172.21.127.174   <none>        3000/TCP   9s

それは、bashを動作させているポッド(コンテナ)からテストしてみます。

root@bash-57559bc848-xm6dk:/# curl http://172.21.127.174:3000/hostname
hostname = express-app-77d5f67b99-rnx9p

サービスを定義することで、kube-dnsへ登録されるので、DNS名でアクセスできます。

root@bash-57559bc848-xm6dk:/# curl http://express-app-svc:3000/hostname
hostname = express-app-77d5f67b99-rnx9p

ネームスペースを含めたDNS名でのアクセスも可能です。

root@bash-57559bc848-xm6dk:/# curl http://express-app-svc.default:3000/hostname
hostname = express-app-77d5f67b99-gfxw4

連続でアクセスすると、各ポッドへ負荷分散されていることが、ホスト名の末尾四文字から、判別できます。

root@bash-57559bc848-xm6dk:/# curl http://express-app-svc.default:3000/hostname
hostname = express-app-77d5f67b99-sb6kc
root@bash-57559bc848-xm6dk:/# curl http://express-app-svc.default:3000/hostname
hostname = express-app-77d5f67b99-gfxw4
root@bash-57559bc848-xm6dk:/# curl http://express-app-svc.default:3000/hostname
hostname = express-app-77d5f67b99-gfxw4
root@bash-57559bc848-xm6dk:/# curl http://express-app-svc.default:3000/hostname
hostname = express-app-77d5f67b99-sb6kc
root@bash-57559bc848-xm6dk:/# curl http://express-app-svc.default:3000/hostname
hostname = express-app-77d5f67b99-sb6kc
root@bash-57559bc848-xm6dk:/# curl http://express-app-svc.default:3000/hostname
hostname = express-app-77d5f67b99-rnx9p

パブリック(インターネット上)へのアドレスへのアクセス

k8sのポッドからインターネット上のパブリックIPへアクセスすることができます。

スクリーンショット 2017-12-20 21.23.44.png

DNS名でpingで確認します。

root@bash-57559bc848-xm6dk:/# ping -c 3 www.yahoo.co.jp
PING edge.g.yimg.jp (183.79.249.124) 56(84) bytes of data.
64 bytes from 183.79.249.124: icmp_seq=1 ttl=52 time=9.33 ms
64 bytes from 183.79.249.124: icmp_seq=2 ttl=52 time=9.20 ms
64 bytes from 183.79.249.124: icmp_seq=3 ttl=52 time=9.47 ms

--- edge.g.yimg.jp ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 9.206/9.340/9.478/0.136 ms

ボディまで取ってくると長いので、ヘッダーを取得して確認します。

root@bash-57559bc848-xm6dk:/# curl -I http://www.yahoo.co.jp/
HTTP/1.1 301 Redirect
Date: Wed, 20 Dec 2017 00:46:20 GMT
Connection: keep-alive
Via: http/1.1 edge1565.img.bbt.yahoo.co.jp (ApacheTrafficServer [c s f ])
Server: ATS
Cache-Control: no-store
Location: https://www.yahoo.co.jp:443/
Content-Type: text/html
Content-Language: en
Content-Length: 6794

ポッドのIPアドレスを http://ifconfig.co/ へアクセスして確認してみます。 このアドレスは、ノードのパブリック側のインタフェースのアドレスであることが判ります。 (xxx.xxxはパブリックのIPアドレスの公開を避けるためです)

root@bash-57559bc848-xm6dk:/# curl http://ifconfig.co/
161.202.xxx.xxx

プライベートアドレス上のベアメタルへのアクセス

k8sクラスタから、外部のベアメタル上で動作するデータベースへアクセスするケースになります。 もちろんk8sクラスタ内のポッドから永続ストレージをマウントしてもデータの永続性を持ったデータベースを構築できるのですが、ポッドと永続ストレージ間のアクセスは、iSCSIやNFSを利用するためにトランザクション性能が得られないという課題が発生します。特に小さな更新を頻繁に行う様なケースではとても不利になります。 そこで、ベアメタルの内臓SSDやPCI接続型超高速ストレージを利用して、専用の高速データベース・サーバーを構築するアイデアの実現性についての確認です。

スクリーンショット 2017-12-20 20.47.42.png

IBM Cloud Infrastructure の同一ユーザーIDで、自己のVLAN上にベアメタルサーバーが起動しています。 このIPアドレスへアクセスを確認したします。

$ slcli server list
:........:..........:.................:...............:............:........:
:   id   : hostname :    primary_ip   :   backend_ip  : datacenter : action :
:........:..........:.................:...............:............:........:
: 866447 :  test57  : 161.202.xxx.xxx : 10.132.253.39 :   tok02    :   -    :
:........:..........:.................:...............:............:........:

下記の様に、k8sのポッドから疎通できることが確認できました。

root@bash-57559bc848-xm6dk:/# ping -c 3 10.132.253.39
PING 10.132.253.39 (10.132.253.39) 56(84) bytes of data.
64 bytes from 10.132.253.39: icmp_seq=1 ttl=63 time=0.685 ms
64 bytes from 10.132.253.39: icmp_seq=2 ttl=63 time=0.534 ms
64 bytes from 10.132.253.39: icmp_seq=3 ttl=63 time=0.306 ms

--- 10.132.253.39 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.306/0.508/0.685/0.156 ms

このk8sクラスタ外部のIPアドレスを kube-dnsに登録するには、次のYAMLを利用します。

$ cat t014-extenal-svc.yml 
kind: Service
apiVersion: v1
metadata:
  name: bm-service
  namespace: default
spec:
  type: ExternalName
  externalName: 10.132.253.39

適用した結果、サービスは以下の様になります。

$ kubectl get svc
NAME              TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)    AGE
bm-service        ExternalName   <none>           10.132.253.39   <none>     7s

ポッドからのアクセスは、metadata: name フィールドで登録した名前で、アクセスできます。

root@bash-57559bc848-xm6dk:/# ping -c 3 bm-service
PING bm-service.default.svc.cluster.local (10.132.253.39) 56(84) bytes of data.
64 bytes from 10.132.253.39: icmp_seq=1 ttl=63 time=0.579 ms
64 bytes from 10.132.253.39: icmp_seq=2 ttl=63 time=0.361 ms
64 bytes from 10.132.253.39: icmp_seq=3 ttl=63 time=0.328 ms

--- bm-service.default.svc.cluster.local ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.328/0.422/0.579/0.113 ms

ネームスペースのdefaultとサービスの名前を組み合わせてのアクセスもできます。 このケースのdefaultの代わりに、qa_test, production などのネームスペースに分けて、ネームスペースで本番用データベース、品質保証テスト用データベースを切り替えてアクセスする事ができます。 これによって、開発から本番へのリリースの手間を減らす事ができます。

root@bash-57559bc848-xm6dk:/# ping -c 3 bm-service.default
PING bm-service.default.svc.cluster.local (10.132.253.39) 56(84) bytes of data.
64 bytes from 10.132.253.39: icmp_seq=1 ttl=63 time=0.506 ms
64 bytes from 10.132.253.39: icmp_seq=2 ttl=63 time=0.278 ms
64 bytes from 10.132.253.39: icmp_seq=3 ttl=63 time=0.320 ms

--- bm-service.default.svc.cluster.local ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.278/0.368/0.506/0.099 ms

東京DCからダラスDCのサーバーへのアクセス

東京DCのk8sポッドのコンテナから、ダラスDCの仮想サーバーへアクセスしてみます. ポッドは独自のクラスタIPを持っているため、クラスタ外のサーバーと通信できるか心配ですが、ノードのIPアドレスにNATされるため、問題なくアクセス出来ました。

スクリーンショット 2017-12-20 18.38.57.png

東京のk8sクラスタ内のコンテナから、ダラスの仮想サーバーへpingを実行した結果です。 太平洋を横断して北米南部までのRTTで132ミリ秒です。

root@bash-57559bc848-xm6dk:/# ping -c 3 10.93.66.153 
PING 10.93.66.153 (10.93.66.153) 56(84) bytes of data.
64 bytes from 10.93.66.153: icmp_seq=1 ttl=50 time=132 ms
64 bytes from 10.93.66.153: icmp_seq=2 ttl=50 time=132 ms
64 bytes from 10.93.66.153: icmp_seq=3 ttl=50 time=132 ms

--- 10.93.66.153 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 132.333/132.523/132.840/0.476 ms

下記の通りHTTPでのアクセスもOKです。

root@bash-57559bc848-xm6dk:/# curl -I http://10.93.66.153/
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Wed, 20 Dec 2017 01:22:54 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Wed, 20 Dec 2017 01:22:35 GMT
Connection: keep-alive
ETag: "5a39bb5b-264"
Accept-Ranges: bytes

仮想サーバーやベアメタルからのk8sクラスタへのアクセス(その1)

この方法はライトプランでも利用でき方法です。 NodePortサービスを設定すると、どれか一つのノードのIPアドレスにアクセスしても、kube-proxyによって各ポッドにアクセスが分散されます。 アクセスしているノードが止まった場合、可用性が失われますが、クライアント側にノードのIPアドレス・リストを持たせることで、代替ノードへ切り替えることが可能となります。 この方法では、プライベートだけにポートを開くと事が出来ないため、別途アクセス制限を実施する必要があります。
また、NodePortのフロントエンドに、IBM Cloud Infrastructure のロードバランサーを設定することも可能です。

スクリーンショット 2017-12-20 17.53.17.png

図ではkube-proxyプロセスが、リクエストを中継している様に記載していますが、デフォルトでは、kube-proxy のプロセスはカーネルのユーザー空間で動作するのではなく、iptablesを設定してプロキシ・ポートを作成する役割を担っています。詳しい説明は、Virtual IPs and service proxiesを参照ください。

NodePortの設定は次の様になります。 k8s NodePortサービスでは、指定しなければ、30000〜32767からポートを自動的に割り当てます。

t015-nodeport.yml
apiVersion: v1
kind: Service
metadata:
  name: express-app-ext
spec:
  type: NodePort
  selector:
    app: express
  ports:
  - protocol: TCP
    port: 3000
    nodePort: 31500

下記のコマンドで、NodePortのサービスを定義を反映します。

$ kubectl create -f t015-nodeport.yml

このNodePortサービスでは、EXTERNAL-IPが付与されません。k8sクラスタに属するノードのポート番号にアクセスすることで、RESTサービスにプロキシーされます。

$ kubectl get svc
NAME              TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
express-app-ext   NodePort       172.21.133.54    <none>          3000:31500/TCP   8s

k8sクラスタ以外の仮想サーバーからノードのIPアドレスに、NodePortで解放した31500ポートにアクセスします。 連続してアクセスすると、一つのノードにアクセスしても、kube-proxyの機能によって、クラスタネットワーク上のポッドへ負荷分散されていることが判ります。

root@server1:~# curl http://10.132.253.17:31500/hostname
hostname = express-app-77d5f67b99-sb6kc
root@server1:~# curl http://10.132.253.17:31500/hostname
hostname = express-app-77d5f67b99-gfxw4
root@server1:~# curl http://10.132.253.17:31500/hostname
hostname = express-app-77d5f67b99-gfxw4
root@server1:~# curl http://10.132.253.17:31500/hostname
hostname = express-app-77d5f67b99-gfxw4
root@server1:~# curl http://10.132.253.17:31500/hostname
hostname = express-app-77d5f67b99-rnx9p

仮想サーバーやベアメタルからのk8sクラスタへのアクセス(その2) ロードバランサーの利用

次の図の様にIBM Cloud Infrastructure のベアメタルや仮想サーバーから、k8sクラスタのマイクロサービスを利用する構成です。

スクリーンショット 2017-12-20 17.08.58.png

k8sサービスのロードバランサーに、アノテーション`service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: private`を付与することで、プライベートIPアドレスに、ロードバランサーのサービスIPを確保します。 もし、このアノテーションを設定しなければ、パブリック側にサービスIPを確保します。

t016-loadbalancer.yml
apiVersion: v1
kind: Service
metadata:
  name: express-app-lb
  annotations:
    service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: private
spec:
  type: LoadBalancer
  selector:
    app: express
  ports:
  - protocol: TCP
    port: 3000
    targetPort: 3000
  sessionAffinity: None

適用方法は以下の通りです。

$ kubectl create -f t016-loadbalancer.yml

次のコマンドで、ロードバランサ・サービスが起動したことが確認できます。

$ kubectl get svc express-app-lb
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE
express-app-lb   LoadBalancer   172.21.203.78   10.132.160.55   3000:30258/TCP   15m

k8sクラスタ外部のサーバーから、k8sクラスタにRESTでアクセスした結果です。 均等にアクセスが分散されていることが判ります。

root@server1:~# curl http://10.132.160.55:3000/hostname
hostname = express-app-77d5f67b99-gfxw4
root@server1:~# curl http://10.132.160.55:3000/hostname
hostname = express-app-77d5f67b99-rnx9p
root@server1:~# curl http://10.132.160.55:3000/hostname
hostname = express-app-77d5f67b99-sb6kc

検証したところ、プライベート・サービスIPは、削除するとサービスIPが解放され、作成すると再び取得されるのですが、作成のたびに異なるIPアドレスが割り当てられます。 v1.8.2において loadBalancerIp:はフィールドとして認識されませんでした。

まとめ

今回は次の様な範囲、すなわち、k8sクラスタ内、k8sクラスタ外へ、外からk8sクラスタへのアクセスを確認する事ができました。

  • ノード内、ノード間のポッド同士のアクセス
  • ポッドからインターネットへのリソースへのアクセス
  • k8sクラスタ外部のベアメタルサーバー、仮想サーバーへのアクセス
  • kube-dnsに登録された名前解決でのアクセス
  • ClusterIPを利用したkube-proxyによる、k8sクラスタ内のポッドからのアクセス
  • k8sクラスタ外部の海外のIBM Cloud データセンター上のサーバーとのアクセス
  • k8sサービス NodePortを利用してプライベートとパブリックのノードIPによるk8sクラスタ外からのアクセス
  • k8sサービス LoadBalancerを利用してクラスタ外からのプライベートIPでのアクセス

参考資料