【IBM Cloud k8s検証メモ】フロントエンドPODとバックエンドPODを連携させる方法


フロントエンドのPODから、DBコンテナが入っているバックエンドのPODに接続する方法のメモです。 docker-compose であれば depends_onやlinkで設定すれば良いのですが、Kubernetesでは、どの様に実現したら良いのでしょうか?との疑問の解を探ってみました。

Kubernetesの概念について

k8sのクラスター、ボッド、デプロイメント、サービスという言葉の意味について、Bluemix のドキュメントに親切な説明がありましたので、一部注釈を付けて、そのまま引用します。[1]

Cluster (クラスター)

Kubernetes クラスターは、ワーカー・ノードと呼ばれる 1 つ以上の仮想マシンから成ります。 各ワーカー・ノードは、コンテナー化アプリをデプロイ、実行、管理できる場所であるコンピュート・ホストを表します。 ワーカー・ノードは、クラスター内のすべての Kubernetes リソースを一元的に制御してモニターする Kubernetes マスターによって管理されます。 コンテナー化アプリをデプロイする際、Kubernetes マスターは、デプロイメント要件とクラスターの使用可能容量を考慮に入れて、どこにアプリをデプロイするかを決定します。(注釈: IBMでは、ワーカー・ノードとして表記されていますが、Kubernetes のドキュメントには、単にノードとして表されています。)

POD (ポッド)

Kubernetes クラスターにデプロイされる各コンテナー化アプリは、ポッドによってデプロイ、実行、管理されます。 ポッドは Kubernetes クラスター内のデプロイ可能な最小のユニットを表し、単一のユニットとして処理される必要があるコンテナーをグループ化するのに使用します。ほとんどの場合、各コンテナーはそのコンテナー独自のポッドにデプロイされます。ただしアプリでは、コンテナーとその他のヘルパー・コンテナーを 1 つのポッドにデプロイすることによって、同じプライベート IP アドレスを使用してそれらのコンテナーをアドレス指定できるようにする必要がある場合もあります。(注釈:複数のコンテナが同一POD内で動作する際には、コンテナのポート番号が同じにならない様に、管理が必要です。)

Deployment (デプロイメント)

デプロイメントは Kubernetes リソースであり、デプロイメントの中でコンテナーと、アプリの実行に必要な他の Kubernetes リソース (永続ストレージ、サービス、アノテーションなど) が指定されます。 デプロイメントは Kubernetes デプロイメント・スクリプトで文書化されます。デプロイメントを実行するときは、 Kubernetes マスターが、クラスターのワーカー・ノードで使用可能な容量を考慮しながら、指定されたコンテナーをポッドにデプロイします。 他の Kubernetes リソースは、デプロイメント・スクリプトの指定どおりに作成され、構成されます。

デプロイメントを使用して、ローリング更新中に追加するポッドの数や、1 度に使用不可にできるポッドの数など、アプリの更新戦略を定義できます。 ローリング更新の実行時には、デプロイメントによって、リビジョンが動作しているかどうかが確認され、障害が検出されるとロールアウトが停止されます。

Service (サービス)

Kubernetes サービスによってポッドのセットをグループ化し、クラスター内の他のサービスからそれらのポッドにアクセスするためのネットワーク接続を提供します。そうすれば、各ポッドの実際のプライベート IP アドレスを公開する必要はありません。 サービスを使用することにより、アプリをクラスター内または公開インターネットで使用可能にできます。

KubernetesのServiceについて

上記の説明文では、サービスが、PODにアクセスするためのネットワーク接続を提供するとあります。この内容について、今度は、Kubernetesのドキュメントで参照すると、次の様に書かれています。[2]

Kebernetes のサービスとは、論理的ポッドのセットとアクセス方針を定義を 抽象化したものです。 そして、サービスは 通常 Label Selector によって識別される。

ここで、サービスの定義の例を見てみましょう。 ここで、selector の中、app: mini-mysql が POD との対応づけになります。 そして、ports 以下が PODへのアクセス方針で、TCP 3306 を 3306で公開する様に定義してあります。

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  selector:
    app: mini-mysql  <--- PODを指定するには、selector に一致するラベルを記述する
    tier: backend
  ports:
  - protocol: TCP
    port: 3306
    targetPort: 3306

これを設定することで、PODにアクセスするためのIPアドレスが付与され、kube-dnsへ登録され、他のPODで、DNSを利用してホスト名をIPアドレスを引いて、目的のPODにアクセスできる様になります。

次のYAMLファイルは、PODを作成するためのもので、前述のServiceとの一致する箇所を確認するための抜粋です。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mysql-deploy
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mini-mysql  <--- これが サービスの selector の app の値と一致している必要がある。
        tier: backend
        track: stable
    spec:
      containers:
      - name: mysql

kube-dnsのアドレス解決

前述のservice の yaml を適用すると、サービスのリストに、ココとして指摘した行が表示される様になります。 これと同時に、kube-dnsにも登録されるので、mysqlというホスト名でIPアドレスを得られる様になります。

$ kubectl get svc
NAME           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
frontend-svc   172.21.127.62   169.60.13.186   80:32408/TCP   19h
kubernetes     172.21.0.1      <none>          443/TCP        2d
mysql          172.21.78.144   <none>          3306/TCP       19h  <-- ココ
redis          172.21.20.30    <none>          6379/TCP       19h

AP Container に入って、npingで確認します。 npingは、nmapパッケージに含まれるポートでpingを実行するためのツールです。
最初は、ホスト名 mysql で nping を実行します。 以下の結果から、アドレスが解決され、応答を受けていることが確認できます。

$ kubectl exec -it frontend-deploy-1665229993-12gd0 -c app bash

root@frontend-deploy-1665229993-12gd0:/var/www# nping --tcp -p 3306 -c 3 mysql

Starting Nping 0.6.47 ( http://nmap.org/nping ) at 2017-09-29 02:53 UTC
SENT (0.0333s) TCP 172.30.212.137:32598 > 172.21.78.144:3306 S ttl=64 id=19140 iplen=40  seq=325308650 win=1480 
RCVD (0.2196s) TCP 172.21.78.144:3306 > 172.30.212.137:32598 SA ttl=62 id=0 iplen=44  seq=733617065 win=28800 <mss 1440>
SENT (1.0336s) TCP 172.30.212.137:32598 > 172.21.78.144:3306 S ttl=64 id=19140 iplen=40  seq=325308650 win=1480 
RCVD (1.2236s) TCP 172.21.78.144:3306 > 172.30.212.137:32598 SA ttl=62 id=0 iplen=44  seq=749241736 win=28800 <mss 1440>
SENT (2.0345s) TCP 172.30.212.137:32598 > 172.21.78.144:3306 S ttl=64 id=19140 iplen=40  seq=325308650 win=1480 
RCVD (2.2236s) TCP 172.21.78.144:3306 > 172.30.212.137:32598 SA ttl=62 id=0 iplen=44  seq=764881349 win=28800 <mss 1440>

Max rtt: 189.863ms | Min rtt: 186.162ms | Avg rtt: 188.318ms
Raw packets sent: 3 (120B) | Rcvd: 3 (132B) | Lost: 0 (0.00%)
Nping done: 1 IP address pinged in 2.25 seconds

今度は、同じくAPコンテナで、nslookup を利用して他のサービスも含めて、アドレス解決を試みてみました。 期待通りに、IPアドレスが返されます。

root@frontend-deploy-1665229993-12gd0:/var/www# nslookup
> server
Default server: 172.21.0.10
Address: 172.21.0.10#53
> mysql
Server:     172.21.0.10
Address:    172.21.0.10#53

Name:   mysql.default.svc.cluster.local
Address: 172.21.78.144
> redis
Server:     172.21.0.10
Address:    172.21.0.10#53

Name:   redis.default.svc.cluster.local
Address: 172.21.20.30

環境変数の提供

既にサービスが提供されている下記の状態で、新たにPODをデプロイすると、サービスと接続するための環境変数がコンテナにセットされます。

$ kubectl get service
NAME             CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
frontend-svc     172.21.218.44    169.60.13.186   80:30305/TCP   1h
kubernetes       172.21.0.1       <none>          443/TCP        2d
mysql            172.21.115.104   <none>          3306/TCP       1h
redis            172.21.27.5      <none>          6379/TCP       1h
web-phpmyadmin   172.21.168.132   <nodes>         80:31580/TCP   2h

サービスの環境変数の例を以下に示します。これらの環境変数を利用してサービスへ接続することも可能です。 注意点としては、環境変数の場合、コンテナ稼働中の変更は反映されないので留意して利用する必要があります。

$ kubectl exec -it frontend-deploy-1665229993-37d57 -c app bash
root@frontend-deploy-1665229993-37d57:/var/www# env |grep MYSQL |sort
MYSQL_PORT=tcp://172.21.115.104:3306
MYSQL_PORT_3306_TCP=tcp://172.21.115.104:3306
MYSQL_PORT_3306_TCP_ADDR=172.21.115.104
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_SERVICE_HOST=172.21.115.104
MYSQL_SERVICE_PORT=3306
root@frontend-deploy-1665229993-37d57:/var/www# env |grep REDIS |sort
REDIS_PORT=tcp://172.21.27.5:6379
REDIS_PORT_6379_TCP=tcp://172.21.27.5:6379
REDIS_PORT_6379_TCP_ADDR=172.21.27.5
REDIS_PORT_6379_TCP_PORT=6379
REDIS_PORT_6379_TCP_PROTO=tcp
REDIS_SERVICE_HOST=172.21.27.5
REDIS_SERVICE_PORT=6379

アプリサーバーからのアクセス

サービスが定義が完了すれば、APコンテナからは、ホスト名を指定してアクセスするだけとなります。 次のPHPの例ですが、host=mysqlとすることで、mysqlのホスト名がkube-dnsによってIPアドレスが求められ、アクセスできる様になります。

<?php
Class DaoAnimals
{
    private $dsn;
    private $dbh;

    // Constructor
    function __construct() {
        $this->dsn = "mysql:host=mysql;port=3306;dbname=testdb";
        $this->dbh = new PDO($this->dsn, getenv('DB_USER'),getenv('DB_PASSWORD'));
    }

ラベル tier について

例えば、次の様に tier を無効にして動作に支障はありません。 このtierは何のためにあるのでしょうか? この疑問に答えてくれるのが、Labels and Selectors[5]です。

      labels:
        app: mini-mysql
#        tier: backend
#        track: stable

このラベルを設定しておくことで、フロントエンドだけ、または、バックエンドだけといったリストを出すなど、クロスカットのオペレーションができる様になります。

$ kubectl get pods -l tier=frontend
NAME                               READY     STATUS    RESTARTS   AGE
frontend-deploy-1665229993-5nrr9   2/2       Running   0          40s

$ kubectl get pods -l tier=backend
NAME                            READY     STATUS    RESTARTS   AGE
mysql-deploy-3344093262-b92p1   1/1       Running   0          57s
redis-deploy-2921693572-z87qn   1/1       Running   0          1m

Bluemix-k8s 残念なところ

現在 2017年9月28日で、Bluemix-k8sには Internal LoadBalancerが実装されていません。 AWS,GCP,Azureでは、既に提供済みで、とても残念です。

参考資料

[1] Kubernetes クラスターと IBM Bluemix Container Service について https://console.bluemix.net/docs/containers/cs_ov.html?pos=2#cs_ov
[2] Services https://kubernetes.io/docs/concepts/services-networking/service/

[3] Connect a Front End to a Back End Using a Service https://kubernetes.io/docs/tasks/access-application-cluster/connecting-frontend-backend/
[4] Internal Load Balancer https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer
[5] Labels and Selectors https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/