Kubernetes クラスタの外からのアクセスに ClusterIP をロードバランサとして使う

# はじめに

Kubernetes を触り始めて困惑したのは、クラスタの外からどうやってアクセスするのか？ということでした。
GKE なら提供されているロードバランサを使えば良いですが、GKE に頼れない環境でも良い方法が欲しいものです。

`ClusterIP` はクラスタ内部からアクセスする際に使用するもので、外部からアクセスすることは意図されていませんが、
これをそのまま利用できればとても楽ができそうです。

`ClusterIP` へのアクセスは複数の `Pod` に対して振り分けられるので、[Ingress](https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx) や [Service Load Balancer](https://github.com/kubernetes/contrib/tree/master/service-loadbalancer) が無くてもクラスタの外に対してロードバランサを提供することができます。
調べたところ `Project Calico` と `kube-proxy` を利用するとクラスタの外から `ClusterIP` にアクセスできたので、やり方をまとめておきます。

# テスト環境

テスト環境では 5つのホストを構築します。
すべて CentOS 7.2 で作成しています。

![Use_ClusterIP+c.png](https://qiita-image-store.s3.amazonaws.com/0/44206/1f516106-8bc9-2878-a5e4-0f0bf17be36b.png)

* kubemaster ... `kube-apiserver` や `etcd` などを動作させる Kubernetes の `Master` です
* kubenode01 ... `kubelet` や `kube-proxy` を動作させる Kubernetes の `Node` 1号機です
* kubenode02 ... `kubelet` や `kube-proxy` を動作させる Kubernetes の `Node` 2号機です
* kubegw ... `kube-proxy` を動作させ、外部との通信を中継する `ルータ兼ロードバランサ` (L3 ルーティング + NAPT) です

`Calico` は上記すべてのホストで動作させます。
`Client` はクラスタの外に存在するホストです。ここから `ClusterIP` 宛てにリクエストを投げます。
`Pod` は `172.16.0.0/16` のアドレスレンジから割り当てます。
`ClusterIP` は `10.0.100.0/24` のアドレスレンジから割り当てます。 

実環境に適用するなら既存のルータから kubegw に向けたスタティックルートを設定するか、kubegw を含める形でダイナミックルーティングを設定することになるでしょう。
ここでは `Client` に直接 kubegw に向けたスタティックルートを設定します。



# Project Calico について

![Project-Calico-logo-1000px.png](https://qiita-image-store.s3.amazonaws.com/0/44206/86fe7f0d-e167-fcce-0f82-d1b707320eff.png)


[Kubernetes のドキュメント](http://kubernetes.io/docs/admin/networking/) では僅かに触れられている程度ですが、
`kubelet` の Network Plugin として使用すると、クラスタで作成された `Pod` (コンテナ) 間で `Pod` の IP アドレスをそのまま使って通信できるようになります。
`flannel` で overlay network を作る代わりに使います。

各ホストに Docker コンテナとして `calico/node` をデプロイし、すべての `calico/node` はフルメッシュで BGP ピアリングします。

詳細は [Calico のドキュメント](http://docs.projectcalico.org/en/stable/index.html) を参照してください。



# Kubernetes クラスタ構築

まずは Kubernetes のクラスタを構築します。大部分は `yum` でさくっといきます。
各ホストは `hosts` で名前解決できるようにしています。

```
192.168.100.10  kubemaster
192.168.100.1   kubenode01
192.168.100.2   kubenode02
```

## Master 構築

バージョンは以下の通りでした

* etcd-2.2.5-1.el7.x86_64
* docker-1.9.1-25.el7.centos.x86_64
* kubernetes-master-1.2.0-0.9.alpha1.gitb57e8bd.el7.x86_64
* calico/node:v0.19.0

```console:インストール
# yum install etcd docker kubernetes-master

## 後で使うので EPEL から jq をインストールしておく
# yum install http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-6.noarch.rpm
# yum install jq
```

まずは `etcd` を起動します。この例では `etcd` はクラスタを構成せず 1台で起動させています。
この `etcd` は `Kubernetes` だけでなく `Calico` の設定も保存させます。

```bash:/var/lib/etcd/etcd.conf
ETCD_NAME=default
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.100.10:2379"
```

```console:etcd起動
# systemctl enable etcd
# systemctl start etcd
```

続いて `Calico` をセットアップします。`Docker` を使用するのでこの時点で起動させています。
storage driver は overlayfs にしていますが、何でもよいです。

```bash:/etc/sysconfig/docker
OPTIONS=
DOCKER_CERT_PATH=/etc/docker
```

```bash:/etc/sysconfig/docker-storage
DOCKER_STORAGE_OPTIONS='-s overlay'
```

```console:calicoインストール
# systemctl enable docker
# systemctl start docker

# mkdir -p /opt/calico/bin
# wget https://github.com/projectcalico/calico-containers/releases/download/v0.19.0/calicoctl
# chmod +x calicoctl
# mv calicoctl /opt/calico/bin

## calico のパスを通しておく
# vi ~/.bash_profile
PATH=$PATH:$HOME/bin:/opt/calico/bin
# . ~/.bash_profile
# ETCD_AUTHORITY=127.0.0.1:2379 calicoctl node --ip=192.168.100.10
Pulling Docker image calico/node:v0.19.0

Running Docker container with the following command:

docker run -d --restart=always --net=host --privileged --name=calico-node -e HOSTNAME=kubemaster -e IP=192.168.100.10 -e IP6= -e CALICO_NETWORKING=true -e AS= -e NO_DEFAULT_POOLS= -e ETCD_AUTHORITY=127.0.0.1:2379 -e ETCD_SCHEME=http -v /var/log/calico:/var/log/calico -v /lib/modules:/lib/modules -v /var/run/calico:/var/run/calico calico/node:v0.19.0

Calico node is running with id: f6c2897855834f53d10626051197d22c35f46905c32e542c270ca52dc068b058
Waiting for successful startup
Calico node started successfully
```

完了すると `calico/node` のコンテナが起動します。
起動コマンドを表示してくれているので分かりますが、このコンテナは Restart Policy が always になっています。
起動後は以下のように見えてきます。

```console:確認
# calicoctl node show
+------------+----------------+-----------+-------------------+--------------+--------------+
|  Hostname  |   Bird IPv4    | Bird IPv6 |       AS Num      | BGP Peers v4 | BGP Peers v6 |
+------------+----------------+-----------+-------------------+--------------+--------------+
| kubemaster | 192.168.100.10 |           | 64511 (inherited) |              |              |
+------------+----------------+-----------+-------------------+--------------+--------------+
```

デフォルトだと `192.168.0.0/16` をコンテナに割り当てるようですが
クラスC を他で使っていて見づらいので、ここでは `172.16.0.0/16` に変更しておきます。

```console:poolアドレス変更
# calicoctl pool show --ipv4
+----------------+---------+
|   IPv4 CIDR    | Options |
+----------------+---------+
| 192.168.0.0/16 |         |
+----------------+---------+
# calicoctl pool add 172.16.0.0/16
# calicoctl pool remove 192.168.0.0/16
# calicoctl pool show --ipv4
+---------------+---------+
|   IPv4 CIDR   | Options |
+---------------+---------+
| 172.16.0.0/16 |         |
+---------------+---------+
```

`kube-apiserver` は手っ取り早く `--insecure-bind-address=0.0.0.0` にして Admission Control も外します。
`kube-scheduler` と `kube-controller-manager` はデフォルトで `http://127.0.0.1:8080` に向いているのでそのままいきます。

```bash:/etc/kubernetes/apiserver
KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"
KUBE_ETCD_SERVERS="--etcd-servers=http://127.0.0.1:2379"
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.0.100.0/24"
KUBE_ADMISSION_CONTROL=""
KUBE_API_ARGS=""
```

```console:Master起動
# systemctl enable kube-apiserver
# systemctl enable kube-controller-manager
# systemctl enable kube-scheduler
# systemctl start kube-apiserver
# systemctl start kube-controller-manager
# systemctl start kube-scheduler
```

私の環境では `/var/run/kubernetes` が作成されておらず、`kube-apiserver` の自己署名証明書も作られませんでした。
一度 OS を再起動してみると、今度は作成され 6443 が Listen されていました。

```console
# systemctl reboot
# ss -nltp | grep 6443
LISTEN     0      128         :::6443                    :::*                   users:(("kube-apiserver",pid=1637,fd=13))
```


## Node 構築

バージョンは以下の通りでした

* docker-1.9.1-25.el7.centos.x86_64
* kubernetes-node-1.2.0-0.9.alpha1.gitb57e8bd.el7.x86_64
* calico/node:v0.19.0

```console:インストール
# yum install docker kubernetes-node
```

`Calico` をインストールする部分は `Master` とほぼ同じで、IPアドレス指定だけ変更します。

```bash:/etc/sysconfig/docker
OPTIONS=
DOCKER_CERT_PATH=/etc/docker
```

```bash:/etc/sysconfig/docker-storage
DOCKER_STORAGE_OPTIONS='-s overlay'
```

```console:calicoインストール
# systemctl enable docker
# systemctl start docker

# mkdir -p /opt/calico/bin
# wget https://github.com/projectcalico/calico-containers/releases/download/v0.19.0/calicoctl
# chmod +x calicoctl
# mv calicoctl /opt/calico/bin

## calico のパスを通しておく
# vi ~/.bash_profile
PATH=$PATH:$HOME/bin:/opt/calico/bin
# . ~/.bash_profile
## etcd は Master のアドレスを指定する
## Node 2号機 (kubenode02) は --ip=192.168.100.2 にする
# ETCD_AUTHORITY=192.168.100.10:2379 calicoctl node --ip=192.168.100.1
Pulling Docker image calico/node:v0.19.0

Running Docker container with the following command:

docker run -d --restart=always --net=host --privileged --name=calico-node -e HOSTNAME=kubenode01 -e IP=192.168.100.1 -e IP6= -e CALICO_NETWORKING=true -e AS= -e NO_DEFAULT_POOLS= -e ETCD_AUTHORITY=192.168.100.10:2379 -e ETCD_SCHEME=http -v /var/log/calico:/var/log/calico -v /lib/modules:/lib/modules -v /var/run/calico:/var/run/calico calico/node:v0.19.0

Calico node is running with id: bed0fa13d54064786450a90b5a1a8d1d2d98a80434367f756ad35bf245129854
Waiting for successful startup
Calico node started successfully
```

`Node` 2台が追加されたことを確認します。
この時点ではまだ `Pod` は存在しないので経路情報はアドバタイズされません。

```console:確認
## kubenode01,02 から確認する場合は etcd の場所を指定する
# ETCD_AUTHORITY=192.168.100.10:2379 calicoctl node show
+------------+----------------+-----------+-------------------+--------------+--------------+
|  Hostname  |   Bird IPv4    | Bird IPv6 |       AS Num      | BGP Peers v4 | BGP Peers v6 |
+------------+----------------+-----------+-------------------+--------------+--------------+
| kubemaster | 192.168.100.10 |           | 64511 (inherited) |              |              |
| kubenode01 | 192.168.100.1  |           | 64511 (inherited) |              |              |
| kubenode02 | 192.168.100.2  |           | 64511 (inherited) |              |              |
+------------+----------------+-----------+-------------------+--------------+--------------+

# ETCD_AUTHORITY=192.168.100.10:2379 calicoctl status
calico-node container is running. Status: Up 5 minutes
Running felix version 1.4.0rc1

IPv4 BGP status
IP: 192.168.100.1    AS Number: 64511 (inherited)
+----------------+-------------------+-------+----------+-------------+
|  Peer address  |     Peer type     | State |  Since   |     Info    |
+----------------+-------------------+-------+----------+-------------+
| 192.168.100.10 | node-to-node mesh |   up  | 20:59:02 | Established |
| 192.168.100.2  | node-to-node mesh |   up  | 20:59:09 | Established |
+----------------+-------------------+-------+----------+-------------+

IPv6 BGP status
No IPv6 address configured.
```


それでは `Calico` を使用するように `kubelet` をセットアップします。

```bash:/etc/kubernetes/config
KUBE_LOGTOSTDERR="--logtostderr=true"
KUBE_LOG_LEVEL="--v=0"
KUBE_ALLOW_PRIV="--allow-privileged=false"
KUBE_MASTER="--master=http://192.168.100.10:8080"
```

```bash:/etc/kubernetes/kubelet
KUBELET_ADDRESS="--address=0.0.0.0"
KUBELET_HOSTNAME=
KUBELET_API_SERVER="--api-servers=http://192.168.100.10:8080"
KUBELET_ARGS="--network-plugin=cni --network-plugin-dir=/etc/cni/net.d"
```

`kube-proxy` は proxy mode を iptables にします。

```bash:/etc/kubernetes/proxy
KUBE_PROXY_ARGS="--proxy-mode=iptables"
```

```console:network-plugin設定
# mkdir -p /etc/cni/net.d
# cat >/etc/cni/net.d/10-calico.conf <<EOF
{
    "name": "calico-k8s-network",
    "type": "calico",
    "etcd_authority": "192.168.100.10:2379",
    "log_level": "info",
    "ipam": {
        "type": "calico-ipam"
    }
}
EOF

# wget https://github.com/projectcalico/calico-cni/releases/download/v1.3.0/calico
# chmod +x calico
# mv calico /opt/calico/bin
```


```console:起動
# systemctl enable kube-proxy
# systemctl enable kubelet
# systemctl start kube-proxy
# systemctl start kubelet
```

問題なく進んでいれば、`Master` で 2台の `Node` が登録されている様子が確認できます。

```console:確認
# kubectl get node
NAME         LABELS                              STATUS    AGE
kubenode01   kubernetes.io/hostname=kubenode01   Ready     18m
kubenode02   kubernetes.io/hostname=kubenode02   Ready     16m
```

### クラスタ内の通信試験

まずはクラスタ内で `Pod`, `Service` を作成して `ClusterIP` 宛てに通信できることを確認してみます。

適当に Nginx の `Replication Controller` を作ってみます。
`kubectl` の操作は `Master` で行います。

```yaml:nginx-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: test01
spec:
  replicas: 2
  template:
    metadata:
      labels:
         test: test01
    spec:
      containers:
      - name: nginx-test01
        image: nginx
        ports:
        - containerPort: 80
```

```console:
# kubectl create -f nginx-rc.yaml
# kubectl get rc
CONTROLLER   CONTAINER(S)   IMAGE(S)   SELECTOR      REPLICAS   AGE
test01       nginx-test01   nginx      test=test01   2          5m
# kubectl get pod -o wide
NAME           READY     STATUS    RESTARTS   AGE       NODE
test01-chw8x   1/1       Running   0          5m        kubenode01
test01-qeftr   1/1       Running   0          5m        kubenode02
# kubectl get pod -o json | jq .items[].status.podIP
"172.16.0.64"
"172.16.0.0"
```

さて、`Replication Controller` を通して `Pod` がそれぞれの `Node` に作成されました。
この時点で各 `Pod` には /32 の IPアドレスがアサインされ、そのアドレスが BGP でアドバタイズされています。
`Calico` は 1号機・2号機の `Pod` にそれぞれ `172.16.0.64/32` `172.16.0.0/32` をアサインし、1号機・2号機で使うアドレスレンジとしてそれぞれ `172.16.0.64/26` `172.16.0.0/26` のブロックをアサインしたようです。
`172.17.0.0/16` は消しておけばよかったですが、Docker がデフォルトで作成したものです。

```console:Masterの経路情報
# ip r
default via 192.168.100.254 dev eno16777984
172.16.0.0/26 via 192.168.100.2 dev eno16777984  proto bird
172.16.0.64/26 via 192.168.100.1 dev eno16777984  proto bird
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
192.168.100.0/24 dev eno16777984  proto kernel  scope link  src 192.168.100.10
```

```console:Node1号機の経路情報
# ip r
default via 192.168.100.254 dev eno16777984
172.16.0.0/26 via 192.168.100.2 dev eno16777984  proto bird
172.16.0.64 dev calieca82516130  scope link
blackhole 172.16.0.64/26  proto bird
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
192.168.100.0/24 dev eno16777984  proto kernel  scope link  src 192.168.100.1
```

```console:Node2号機の経路情報
# ip r
default via 192.168.100.254 dev eno16777984
172.16.0.0 dev caliecb4366c130  scope link
blackhole 172.16.0.0/26  proto bird
172.16.0.64/26 via 192.168.100.1 dev eno16777984  proto bird
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
192.168.100.0/24 dev eno16777984  proto kernel  scope link  src 192.168.100.2
```

経路があるので `Pod` の IP アドレスに対しては `Master` からでも `Node` からでも通信可能です。

```console:
# curl -s 172.16.0.64 | grep h1
<h1>Welcome to nginx!</h1>
# curl -s 172.16.0.0 | grep h1
<h1>Welcome to nginx!</h1>
```

`Service` も作ってみます。

```yaml:nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: test01
spec:
  selector:
    test: test01
  clusterIP: 10.0.100.80
  ports:
  - name: http
    port: 80
    protocol: TCP
```

```console:
# kubectl create -f nginx-svc.yaml
# kubectl get svc test01
NAME      CLUSTER_IP    EXTERNAL_IP   PORT(S)   SELECTOR      AGE
test01    10.0.100.80   <none>        80/TCP    test=test01   31s
# kubectl get ep test01
NAME      ENDPOINTS                      AGE
test01    172.16.0.0:80,172.16.0.64:80   41s
```

この `ClusterIP` `10.0.100.80` は、`kube-proxy` がいない `Master` からはアクセスできませんが、`Node` や `Pod` からはアクセス可能です。

```console:
# uname -n; curl -s 10.0.100.80 | grep h1
kubenode01
<h1>Welcome to nginx!</h1>

## nginx イメージに curl は無いので nsenter を使って Pod 内からアクセスできることを確認
## Pod はまだ Node に 1つずつしかないので単純に pgrep して pause と同じ Namespace に入る
# uname -n
kubenode01
# nsenter -t $(pgrep pause) -u -n
# ip -4 a show dev eth0
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link-netnsid 0
    inet 172.16.0.64/32 scope global eth0
       valid_lft forever preferred_lft forever
# uname -n; curl -s 10.0.100.80 | grep h1
test01-chw8x
<h1>Welcome to nginx!</h1>

# uname -n
kubenode02
# nsenter -t $(pgrep pause) -u -n
# ip -4 a show dev eth0
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link-netnsid 0
    inet 172.16.0.0/32 scope global eth0
       valid_lft forever preferred_lft forever
# uname -n; curl -s 10.0.100.80 | grep h1
test01-qeftr
<h1>Welcome to nginx!</h1>
```


# ルータ兼ロードバランサ (kubegw) 構築

いよいよ、外部からアクセスさせるためのポイントになる `ルータ兼ロードバランサ` を構築します。

```console:ルーティング可能にする
# echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/routing.conf
# systemctl restart systemd-sysctl
```

`Docker` と `kube-proxy` が必要なのでインストールします。バージョンは `Node` と同じです。
`Docker` は `calico/node` を動作させるために使用します。

```console:インストール
# yum install docker kubernetes-node
```

```bash:/etc/sysconfig/docker
OPTIONS=
DOCKER_CERT_PATH=/etc/docker
```

```bash:/etc/sysconfig/docker-storage
DOCKER_STORAGE_OPTIONS='-s overlay'
```

```console:calicoインストール
# systemctl enable docker
# systemctl start docker

# mkdir -p /opt/calico/bin
# wget https://github.com/projectcalico/calico-containers/releases/download/v0.19.0/calicoctl
# chmod +x calicoctl
# mv calicoctl /opt/calico/bin

## calico のパスを通しておく
# vi ~/.bash_profile
PATH=$PATH:$HOME/bin:/opt/calico/bin
# . ~/.bash_profile
# ETCD_AUTHORITY=192.168.100.10:2379 calicoctl node --ip=192.168.100.254
Pulling Docker image calico/node:v0.19.0

Running Docker container with the following command:

docker run -d --restart=always --net=host --privileged --name=calico-node -e HOSTNAME=kubegw -e IP=192.168.100.254 -e IP6= -e CALICO_NETWORKING=true -e AS= -e NO_DEFAULT_POOLS= -e ETCD_AUTHORITY=192.168.100.10:2379 -e ETCD_SCHEME=http -v /var/log/calico:/var/log/calico -v /lib/modules:/lib/modules -v /var/run/calico:/var/run/calico calico/node:v0.19.0

Calico node is running with id: 37955df4f7f99b59f4aec79ae7d8c5564d1ef3ee25af203853ca715c8d2c3212
Waiting for successful startup
Calico node started successfully

## 状態確認
# ETCD_AUTHORITY=192.168.100.10:2379 calicoctl status
calico-node container is running. Status: Up 18 seconds
Running felix version 1.4.0rc1

IPv4 BGP status
IP: 192.168.100.254    AS Number: 64511 (inherited)
+----------------+-------------------+-------+----------+-------------+
|  Peer address  |     Peer type     | State |  Since   |     Info    |
+----------------+-------------------+-------+----------+-------------+
| 192.168.100.10 | node-to-node mesh |   up  | 22:44:49 | Established |
| 192.168.100.1  | node-to-node mesh |   up  | 22:44:48 | Established |
| 192.168.100.2  | node-to-node mesh |   up  | 22:44:48 | Established |
+----------------+-------------------+-------+----------+-------------+

# ip r
172.16.0.0/26 via 192.168.100.2 dev eno33557248  proto bird
172.16.0.64/26 via 192.168.100.1 dev eno33557248  proto bird
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
192.168.10.0/24 dev eno16777984  proto kernel  scope link  src 192.168.10.100
192.168.100.0/24 dev eno33557248  proto kernel  scope link  src 192.168.100.254
```

`Calico` が動作することで `Pod` への経路が作られます。
続いて `kube-proxy` を設定・起動します。

```bash:/etc/kubernetes/config
KUBE_LOGTOSTDERR="--logtostderr=true"
KUBE_LOG_LEVEL="--v=0"
KUBE_ALLOW_PRIV="--allow-privileged=false"
KUBE_MASTER="--master=http://192.168.100.10:8080"
```

```bash:/etc/kubernetes/proxy
KUBE_PROXY_ARGS="--proxy-mode=iptables"
```

```console:起動
# systemctl enable kube-proxy
# systemctl start kube-proxy
```

`kube-proxy` が動作することで iptables のエントリが作成され、クラスタの外からも `ルータ兼ロードバランサ` を利用すれば `ClusterIP` 宛てにアクセスできるようになります。

```console:iptables確認
# iptables -t nat -nL PREROUTING
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
felix-PREROUTING  all  --  0.0.0.0/0            0.0.0.0/0
KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL
# iptables -t nat -nL POSTROUTING
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
felix-POSTROUTING  all  --  0.0.0.0/0            0.0.0.0/0
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0
MASQUERADE  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */ mark match 0x4d415351
# iptables -t nat -nL KUBE-SERVICES
Chain KUBE-SERVICES (2 references)
target     prot opt source               destination
KUBE-SVC-BP3QRF6W53YFYJDU  tcp  --  0.0.0.0/0            10.0.100.80          /* default/test01:http cluster IP */ tcp dpt:80
KUBE-SVC-NPX46M4PTMTKRN6Y  tcp  --  0.0.0.0/0            10.0.100.1           /* default/kubernetes:https cluster IP */ tcp dpt:443
KUBE-NODEPORTS  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
# iptables -t nat -nL KUBE-SVC-BP3QRF6W53YFYJDU
Chain KUBE-SVC-BP3QRF6W53YFYJDU (1 references)
target     prot opt source               destination
KUBE-SEP-M6FWVBZWU3TAHUXK  all  --  0.0.0.0/0            0.0.0.0/0            /* default/test01:http */ statistic mode random probability 0.50000000000
KUBE-SEP-3VEHY3E2FGHEBZJY  all  --  0.0.0.0/0            0.0.0.0/0            /* default/test01:http */
# iptables -t nat -nL KUBE-SEP-M6FWVBZWU3TAHUXK
Chain KUBE-SEP-M6FWVBZWU3TAHUXK (1 references)
target     prot opt source               destination
MARK       all  --  172.16.0.0           0.0.0.0/0            /* default/test01:http */ MARK set 0x4d415351
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/test01:http */ tcp to:172.16.0.0:80
# iptables -t nat -nL KUBE-SEP-3VEHY3E2FGHEBZJY
Chain KUBE-SEP-3VEHY3E2FGHEBZJY (1 references)
target     prot opt source               destination
MARK       all  --  172.16.0.64          0.0.0.0/0            /* default/test01:http */ MARK set 0x4d415351
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/test01:http */ tcp to:172.16.0.64:80
```

クラスタの外にいる `Client` から確認してみましょう。
このホストで `ClusterIP` である `10.0.100.0/24` は `ルータ兼ロードバランサ` に経路を向けておきます。
また、`PodIP` として使用される `172.16.0.0/16` の経路も向ければ `Pod` とも通信できます。(`PodIP` と通信するだけなら `kube-proxy` 無しでも可能です)

```console:通信確認
# ip r add 10.0.100.0/24 via 192.168.10.100
# ip r add 172.16.0.0/16 via 192.168.10.100
# curl -s 10.0.100.80 | grep h1
<h1>Welcome to nginx!</h1>
# curl -s 172.16.0.64 | grep h1
<h1>Welcome to nginx!</h1>
# curl -s 172.16.0.0 | grep h1
<h1>Welcome to nginx!</h1>
```

無事、通信できました。実際の環境で使うなら `ルータ兼ロードバランサ` は冗長化する必要があるでしょう。


# IPアドレスの変遷

`Client` から `PublicIP (10.0.100.80)` に通信する際に IP アドレスは以下のように変化します。

| タイミング | Src Address | Dest Address |
|:-----------|:------------|:-------------|
| Client から出る時点 | 192.168.10.1 | 10.0.100.80 |
| ルータ兼ロードバランサ 通過後 (往路) | 192.168.10.1 | 172.16.0.64 <br>または<br> 172.16.0.0 |
| Server (Pod) から戻る時点 | 172.16.0.64 <br> または <br> 172.16.0.0 | 192.168.10.1 |
| ルータ兼ロードバランサ 通過後 (復路) | 10.0.100.80 | 192.168.10.1 |

`ClusterIP` からどちらの `Pod` 宛てに DNAT されるかは、iptables の `statistics` モジュールでランダムに (各 `Pod` の確率は等しい) 決定されます。
パケット毎に宛先が選択されるのではなく、connection tracking によって同じ TCP コネクションの通信なら同じ `Pod` 宛てに転送されます。