IKS マルチゾーン クラスタの Ingress (ALB)

IBM Cloud Kubernetes サービスでは、クラスタのノードを次のように配置できます。

* シングルゾーン・クラスタ ： 一つのデータセンターに k8sのワーカーノードを配置
* マルチゾーン・クラスタ　 ： 複数のデータセンターに k8sのワーカーノードを配置

それぞれのデータセンターは、完全に独立した設備で運営されており、当然ネットワークのアドレスも、データセンター毎に独立しています。そのため、マルチゾーン・クラスタとした場合、データセンター横断で、ロードバランスできなければなりません。

今回は、マルチゾーン・クラスタのイングレスの動作について、確認していきます。


## イングレス (Ingress)

イングレスは、k8sのサービス・オブジェクトを、DNS名で外部へ公開するもので、次の特長があります。

* 利用できるクラスタ： 無料のクラスタでは利用できません。標準クラスタでのみ動作
* プロトコル：　HTTP(80), HTTPS(443) または、TCP / UDP でポート番号を指定可能
* 一つのドメインネームで、パス部分に複数のアプリケーション（k8sのサービス）に対応づけて、要求をルーティングする。


イングレスの構成要素は、以下の３つです。

#### 1.Ingress Resource 　
基本的な設定は、CNCFのAPI仕様に従います。

* CNCF Concept Ingress, https://kubernetes.io/docs/concepts/services-networking/ingress/
* API Reference, https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#ingress-v1beta1-extensions

#### 2. Ingress Container ( IKSでは ALB )
IBMが独自実装する　Ingress Controller を　ALB (Application LoadBalancer) と表現されており、上記の Ingress Resource で記述されるマニフェストの　annotation に、ALB に与える設定を記述します。このドキュメントは以下になります。

* Ingress を使用してアプリを公開する、https://cloud.ibm.com/docs/containers/cs_ingress.html#ingress

#### 3. Multi Zone LoadBalancer (MZLB)
マルチゾーン・クラスタを作成すると、MZLBは自動的に作成されます。この実態は、仮想IPアドレスを持ちリクエストを分配するものでは無く、クラスタを代表するDNSアドレスに各ゾーンのALBのIPアドレスを対応づけます。しかし、DNSのラウンドロビンではありません。この動作には、Cloudflare のマルチゾーン・ロードバランサー(MZLB) が自動作成[1]されるとされていますが、その動作は隠蔽されており、Cloudflareを意識したり、別途注文する必要はありません。


## マルチゾーンの MZLB と ALB 動作確認

次のターミナル画面に表示されたマルチゾーンに作成したクラスタがあります。　これは「ワーカー・ゾーン:」にあるように、３つのデータセンター　tok02, tok04, tok05 にワーカーノードが配置されています。

~~~console
$ ibmcloud ks cluster-get iks1

ロケーション:                   tok04
マスター URL:                   https://c1.jp-tok.containers.cloud.ibm.com:31963
マスター・ロケーション:         Tokyo
マスター状況:                   Ready (16 hours ago)
入口サブドメイン:               iks1.jp-tok.containers.appdomain.cloud
入口の秘密:                     iks1
ワーカー:                       4
ワーカー・ゾーン:               tok02, tok05, tok04
バージョン:                     1.12.5_1537
所有者:                         TAKARA@jp.ibm.com
ダッシュボードのモニタリング:   https://metrics.ng.bluemix.net/app/***
リソース・グループ ID:          6fcbec624d904ab19a28a1b75c58d467
リソース・グループ名:           default
~~~

これのクラスタのワーカーをリストすると、次のようになります。（見やすくなるように表示を編集しています）ゾーンはデータセンターと同じレベルで、３つのデータセンターで、ベアメタル(mb2c)を含む４つのノードが存在しています。

~~~
$ ibmcloud ks workers iks1
OK
ID                             プライベート IP  マシン・タイプ        状態     状況    ゾーン
kube-tok02-cr41679d0177fa-w18  10.132.253.30   u2c.2x4.encrypted     normal   Ready   tok02
kube-tok04-cr41679d0177fa-w11  10.192.9.68     mb2c.4x32.encrypted   normal   Ready   tok04
kube-tok04-cr41679d0177fa-w16  10.192.9.93     u2c.2x4.encrypted     normal   Ready   tok04
kube-tok05-cr41679d0177fa-w17  10.193.10.50    u2c.2x4.encrypted     normal   Ready   tok05
~~~

これらの ALB は、各ゾーンのワーカーノード上に、ポッドとして起動しています。そして、それらの上に マルチゾーン・ロードバランサー（MZLB）が動作しています。この動作をコマンドで参照することはできませんが、このクラスタを代表するDNS名でアドレスをひいてみると、MZLBの動作に一端がわかります。以下に、DNS名のアドレス解決結果を挙げます。この「iks1.jp-tok.containers.appdomain.cloud」は３つのIPアドレスに対応していることがわかります。

~~~
> iks1.jp-tok.containers.appdomain.cloud
サーバー:  google-public-dns-a.google.com
Address:  8.8.8.8

権限のない回答:
名前:    iks1.jp-tok.containers.appdomain.cloud
Addresses:  165.192.71.230
          128.168.73.198
          169.56.27.94
~~~

これの３つのIPアドレスは、旧SoftLayerで開発されたポータブルIPアドレスで、ノードのIPアドレスから独立して確保されたIPアドレスです。各ゾーンのポータブルIPアドレスは、ALBが動作するノードにマップされています。もしも、ノードが停止した場合、ポータブルIPは、同じゾーンの別のワーカーノードに対応づけられ、役割りを継続します。また、ゾーンが動作できなくなった場合は、前述のDNSの割り当て先から削除されリクエストの分配が停止します。

次のコマンド実行結果は、ALBをリストしたものです。 ALB IPに注目してください。先に挙げたドメイン名に対応した IPアドレスと同じことがわかります。

~~~
$ ibmcloud ks albs --cluster iks1
OK
ALB ID                                            有効化   状況       タイプ    ALB IP           ゾーン   ビルド
public-cr41679d0177fa4f92ab10262fa586956c-alb1    true     enabled    public    128.168.73.198   tok04    ingress:404/ingress-auth:300
public-cr41679d0177fa4f92ab10262fa586956c-alb2    true     enabled    public    165.192.71.230   tok05    ingress:404/ingress-auth:300
public-cr41679d0177fa4f92ab10262fa586956c-alb3    true     enabled    public    169.56.27.94     tok02    ingress:404/ingress-auth:300
~~~

ここで、ALBの一つを停止させると、DNSから引けるIPアドレスのエントリは削除されます。次のコマンドでALBの一つを停止することができます。

~~~
$ bx ks alb-configure --albID public-cr41679d0177fa4f92ab10262fa586956c-alb2  --disable
ALB を構成中...
~~~

しばらくしてから、ALBをリストすると、２番目のALBが停止しており、ALB IPの一つが削除されていることがわかります。

~~~
$  ibmcloud ks albs --cluster iks1
ALB ID                                            有効化   状況       タイプ    ALB IP           ゾーン   ビルド
public-cr41679d0177fa4f92ab10262fa586956c-alb1    true     enabled    public    128.168.73.198   tok04    ingress:404/ingress-auth:300
public-cr41679d0177fa4f92ab10262fa586956c-alb2    false    disabled   public    -                tok05    ingress:404/ingress-auth:300
public-cr41679d0177fa4f92ab10262fa586956c-alb3    true     enabled    public    169.56.27.94     tok02    ingress:404/ingress-auth:300
~~~

アドレス解決で、MZLBの動作を確認すると、つぎのように、２番目のIPアドレスが削除されました。このようにMZLBは、DNSと連動してデータセンターレベルの障害に対して、リクエストの分配先を削除することで、サービスの継続を維持します。

~~~
> iks1.jp-tok.containers.appdomain.cloud
サーバー:  google-public-dns-a.google.com
Address:  8.8.8.8

権限のない回答:
名前:    iks1.jp-tok.containers.appdomain.cloud
Addresses:  128.168.73.198
          169.56.27.94
~~~


## ALB と ポッドの対応づけ

クラスタのDNS名とポッドを対応づけるためには、つぎの３つの Kubernetes オブジェクトを作成します。

* Ingress Resource (ingress-mz.yml イングレスの設定)
* Service (deploy-web.yml のサービス部分)
* Controller / Pod (deploy-web.yml のデプロイメント部分)

イングレスとサービスの対応づけは、イングレスのマニフェストに、サービス名を指定します。
そして、サービスとポッドの対応は、ラベルとセレクターによっておこないます。


## マルチゾーンクラスタでの動作の様子

GitHub https://github.com/takara9/container-session-check からクローンして利用します。マニフェストのコードは、GitHubを参照ねがいます。ここで適用したマニフェストは、試験用PHPのアプリケーションのポッドを、デプロイメント・コントローラーで起動します。そして、これらのポッドを代表するサービスを起動します。
サービスは、ポッドのクラスタを代表するIPアドレス 172.21.7.146 を獲得しています。このIPアドレスは、サービスオブジェクトが存在している間は変更されること無く、k8sクラスタ内のDNSで名前で、IPアドレスを解決できます。

~~~console
$ kubectl apply -f deploy-test.yaml
deployment.apps/web created
service/web-svc created

$ kubectl get all
NAME                      READY     STATUS    RESTARTS   AGE
pod/web-ddd5df9c7-6wk9m   1/1       Running   0          9s
pod/web-ddd5df9c7-7sf48   1/1       Running   0          9s
pod/web-ddd5df9c7-8px6k   1/1       Running   0          9s
pod/web-ddd5df9c7-9txgh   1/1       Running   0          9s
pod/web-ddd5df9c7-bn86q   1/1       Running   0          9s
pod/web-ddd5df9c7-dkg6f   1/1       Running   0          9s
pod/web-ddd5df9c7-hntc6   1/1       Running   0          9s
pod/web-ddd5df9c7-nvtjp   1/1       Running   0          9s
pod/web-ddd5df9c7-qkcgm   1/1       Running   0          9s
pod/web-ddd5df9c7-wm7tq   1/1       Running   0          9s

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   172.21.0.1     <none>        443/TCP    115d
service/web-svc      ClusterIP   172.21.7.146   <none>        9080/TCP   9s

NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   10        10        10           10          9s

NAME                            DESIRED   CURRENT   READY     AGE
replicaset.apps/web-ddd5df9c7   10        10        10        9s
~~~

次の結果から読み取れるように、10個のポッドは、４つのサーバー（NODE）にデプロイされて動作していることがわかります。

~~~console
$ kubectl get po -o wide
NAME                  READY     STATUS    RESTARTS   AGE       IP               NODE
web-ddd5df9c7-6wk9m   1/1       Running   0          15s       172.30.33.207    10.192.9.93
web-ddd5df9c7-7sf48   1/1       Running   0          15s       172.30.119.19    10.192.9.68
web-ddd5df9c7-8px6k   1/1       Running   0          15s       172.30.210.215   10.132.253.30
web-ddd5df9c7-9txgh   1/1       Running   0          15s       172.30.169.96    10.193.10.50
web-ddd5df9c7-bn86q   1/1       Running   0          15s       172.30.210.214   10.132.253.30
web-ddd5df9c7-dkg6f   1/1       Running   0          15s       172.30.119.18    10.192.9.68
web-ddd5df9c7-hntc6   1/1       Running   0          15s       172.30.210.216   10.132.253.30
web-ddd5df9c7-nvtjp   1/1       Running   0          15s       172.30.119.13    10.192.9.68
web-ddd5df9c7-qkcgm   1/1       Running   0          15s       172.30.169.97    10.193.10.50
web-ddd5df9c7-wm7tq   1/1       Running   0          15s       172.30.119.9     10.192.9.68
~~~

上記だけでは、ポッドのアプリケーションは、外部へ公開されませんから、イングレスコントローラー(ALB)に、サービス名 web-svc を登録して、ポッドへ転送できるようにします。 

~~~console
$ kubectl apply -f ingress.yml
ingress.extensions/ingress-web created

$ kubectl get ing
NAME          HOSTS                                    ADDRESS                                      PORTS     AGE
ingress-web   iks1.jp-tok.containers.appdomain.cloud   128.168.73.198,165.192.71.230,169.56.27.94   80        3s

$ kubectl describe ing
Name:             ingress-web
Namespace:        default
Address:          128.168.73.198,165.192.71.230,169.56.27.94
Default backend:  default-http-backend:80 (<none>)
Rules:
  Host                                    Path  Backends
  ----                                    ----  --------
  iks1.jp-tok.containers.appdomain.cloud
                                          /   web-svc:9080 (<none>)
Annotations:
  kubectl.kubernetes.io/last-applied-configuration:  {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"ingress-web","namespace":"default"},"spec":{"rules":[{"host":"iks1.jp-tok.containers.appdomain.cloud","http":{"paths":[{"backend":{"serviceName":"web-svc","servicePort":9080},"path":"/"}]}}]}}

Events:
  Type    Reason   Age   From                                                             Message
  ----    ------   ----  ----                                                             -------
  Normal  Success  11s   public-cr41679d0177fa4f92ab10262fa586956c-alb2-64478db6dc-xswqh  Successfully applied ingress resource.
  Normal  Success  11s   public-cr41679d0177fa4f92ab10262fa586956c-alb1-668b6578db-p5pn5  Successfully applied ingress resource.
  Normal  Success  11s   public-cr41679d0177fa4f92ab10262fa586956c-alb3-b9df97448-j9skc   Successfully applied ingress resource.
  Normal  Success  11s   public-cr41679d0177fa4f92ab10262fa586956c-alb1-668b6578db-zslwp  Successfully applied ingress resource.
~~~

前述のように、コマンド「ibmcloud ks albs iks1」によって、ALBのリストを表示すると、各ゾーン（データセンター）ごとにALBが一つIPアドレスを確保して動作いています。ポッドのレベルで確認すると、待機中のALBポッドを含めて、各ノードに一つは動作しています。

~~~console
$ kubectl get po -n kube-system -o wide |grep public-cr*
NAME                                                              READY     STATUS    RESTARTS   AGE       IP               NODE            NOMINATED NODE
public-cr41679d0177fa4f92ab10262fa586956c-alb1-668b6578db-p5pn5   4/4       Running   0          14h       172.30.33.205    10.192.9.93     <none>
public-cr41679d0177fa4f92ab10262fa586956c-alb1-668b6578db-zslwp   4/4       Running   0          17h       172.30.119.47    10.192.9.68     <none>
public-cr41679d0177fa4f92ab10262fa586956c-alb2-64478db6dc-kkp52   0/4       Pending   0          17h       <none>           <none>          <none>
public-cr41679d0177fa4f92ab10262fa586956c-alb2-64478db6dc-xswqh   4/4       Running   0          17h       172.30.169.87    10.193.10.50    <none>
public-cr41679d0177fa4f92ab10262fa586956c-alb3-b9df97448-j9skc    4/4       Running   0          17h       172.30.210.209   10.132.253.30   <none>
public-cr41679d0177fa4f92ab10262fa586956c-alb3-b9df97448-jm5rz    0/4       Pending   0          14h       <none>           <none>          <none>
~~~

前述のマニフェストでデプロイされるコンテナ　maho/session-check:1.2 が返す結果をリストしたものです。

このリストの中で、ALBは、NGINXをベースとするプロキシサーバーのポッドであるため、HTTP_X_FORWARDED_FOR の ALBが動作するノードのIPアドレス、REMOTE_ADDRに、ALBのポッドのIPアドレスがセットされる。

* **HTTP_X_FORWARDED_FOR**  HTTPプロキシサーバまたはロードバランサを経由してウェブサーバに接続するクライアントの送信元IPアドレスを特定する際のデファクトスタンダード
* **REMOTE_ADDR** クライアントの接続元IPアドレスであるが、ロードバランサやプロキシを間に挟むと、REMOTE_ADDR がロードバランサ等のアドレスになる


~~~console
$ while true; do curl -s http://iks1.jp-tok.containers.appdomain.cloud;sleep 60; done
Hostname: web-ddd5df9c7-9txgh<br>
1th time access.
<br>
HTTP_CLIENT_IP = <br>
HTTP_X_FORWARDED_FOR = 10.193.10.50<br>
HTTP_X_FORWARDED = <br>
HTTP_X_CLUSTER_CLIENT_IP = <br>
HTTP_FORWARDED_FOR = <br>
HTTP_FORWARDED = <br>
REMOTE_ADDR = 172.30.169.87<br>
Hostname: web-ddd5df9c7-nvtjp<br>
1th time access.
<br>
HTTP_CLIENT_IP = <br>
HTTP_X_FORWARDED_FOR = 10.132.253.30<br>
HTTP_X_FORWARDED = <br>
HTTP_X_CLUSTER_CLIENT_IP = <br>
HTTP_FORWARDED_FOR = <br>
HTTP_FORWARDED = <br>
REMOTE_ADDR = 172.30.210.209<br>
Hostname: web-ddd5df9c7-qkcgm<br>
1th time access.
<br>
HTTP_CLIENT_IP = <br>
HTTP_X_FORWARDED_FOR = 10.193.10.50<br>
HTTP_X_FORWARDED = <br>
HTTP_X_CLUSTER_CLIENT_IP = <br>
HTTP_FORWARDED_FOR = <br>
HTTP_FORWARDED = <br>
REMOTE_ADDR = 172.30.169.87<br>
Hostname: web-ddd5df9c7-nvtjp<br>
1th time access.
<br>
HTTP_CLIENT_IP = <br>
HTTP_X_FORWARDED_FOR = 10.192.9.68<br>
HTTP_X_FORWARDED = <br>
HTTP_X_CLUSTER_CLIENT_IP = <br>
HTTP_FORWARDED_FOR = <br>
HTTP_FORWARDED = <br>
REMOTE_ADDR = 172.30.119.47<br>
Hostname: web-ddd5df9c7-nvtjp<br>
1th time access.
<br>
HTTP_CLIENT_IP = <br>
HTTP_X_FORWARDED_FOR = 10.192.9.68<br>
HTTP_X_FORWARDED = <br>
HTTP_X_CLUSTER_CLIENT_IP = <br>
HTTP_FORWARDED_FOR = <br>
HTTP_FORWARDED = <br>
REMOTE_ADDR = 172.30.33.205<br>
~~~


## まとめ

IKSのクラスタを複数ゾーンで構成する場合のイングレスについて、ポイントをまとめると以下になりました。

* マルチゾーンにノードを立てると、自動的にMZLBが設定され、別途CISをオーダーしたり設定する必要はない。
* MZLBを利用するためには、イングレス（ALB)を利用しなければならない。
* ALBは、NGINXイングレス・コントローラと同等のポッドベースのため、アプリケーションはクライアントのIPアドレスを知ることができない。
* ALBのIPアドレスは、ポータブルIPのアドレスが利用される。
* MZLBのDNS名は、代表IPアドレスを持つものでは無く、ALBのIPアドレスを返す。



## 参考資料

[1] Ingress のコンポーネントとアーキテクチャー https://cloud.ibm.com/docs/containers/cs_ingress.html#planning
[2] ウィキペディア X-Forwarded-For https://ja.wikipedia.org/wiki/X-Forwarded-For