Kubernetes の Service(ClusterIP) を検証する

メモ。

[Services](https://kubernetes.io/docs/concepts/services-networking/service/)

# 環境

```bash
$kubectl version
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.3", GitCommit:"2bba0127d85d5a46ab4b778548be28623b32d0b0", GitTreeState:"clean", BuildDate:"2018-05-28T20:03:09Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"11+", GitVersion:"v1.11.5-eks-6bad6d", GitCommit:"6bad6d9c768dc0864dab48a11653aa53b5a47043", GitTreeState:"clean", BuildDate:"2018-12-06T23:13:14Z", GoVersion:"go1.10.3", Compiler:"gc", Platform:"linux/amd64"}
```

# ClusterIP Serviceで Pod への負荷を分散する


「type:CluserIP」のサービスを作ると Kubernetes クラスタ内からのみアクセス可能な仮想 IP が割り振られ、これを ClusterIP という。

やってみる。

[Kubernetes完全ガイド impress top gearシリーズ](https://www.amazon.co.jp/dp/B07HFS7TDT/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)の Github リポジトリのマニュフェストを利用させて頂く。

[MasayaAoyama/kubernetes-perfect-guide](https://github.com/MasayaAoyama/kubernetes-perfect-guide)


マニュフェストファイルは以下の2点を利用する。

+ sample-deployment.yaml
+ sample-clusterip.yaml

```sample-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
```

意味としては以下

+ Deployment.これを作成すると ReplicaSet と Pod も出来る.作成される Pod は3
+ nginx コンテナでは port 80 でリクエストを受け付ける

```sample-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-clusterip
spec:
  type: ClusterIP
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
  selector:
    app: sample-app
```

意味としては以下。

+ Service を作成
+ Service へのリクエストは port 8080 で可能
+ Service で受け付けたリクエストは selector で記載された Pod に対して port 80 で転送する

以下より検証。

```bash
# deployment を作成
$kubectl apply -f sample-deployment.yaml
deployment.apps "sample-deployment" created

# 問題なく Pod も3つ起動している
$ kubectl get deployment
NAME                DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
sample-deployment   3         3         3            3           48s

# 各 Pod の IP を確認
$ kubectl get pod -l app=sample-app -o custom-columns="NAME:{metadata.name},IP:{status.podIP}"
NAME                                 IP
sample-deployment-86d576464c-hnkm7   172.31.20.41
sample-deployment-86d576464c-wkhjd   172.31.29.9
sample-deployment-86d576464c-x75hx   172.31.9.21


# ClusterIP service を作成
$kubectl apply -f sample-clusterip.yaml
service "sample-clusterip" created

# 10.100.97.31:8080 でクラスター内からアクセス出来る
$kubectl get service sample-clusterip
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
sample-clusterip   ClusterIP   10.100.97.31   <none>        8080/TCP   14s

# 各 Pod の IP　が Service の Endpoints で確認できる
$kubectl describe service sample-clusterip
Name:              sample-clusterip
Namespace:         default
Labels:            <none>
Annotations:       kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-clusterip","namespace":"default"},"spec":{"ports":[{"name":"http-port",...
Selector:          app=sample-app
Type:              ClusterIP
IP:                10.100.97.31
Port:              http-port  8080/TCP
TargetPort:        80/TCP
Endpoints:         172.31.20.41:80,172.31.29.9:80,172.31.9.21:80
Session Affinity:  None
Events:            <none>

# 各 Pod で"hostname ファイルを /usr/share/nginx/html/index.html"にコピー
$for PODNAME in `kubectl get pod -l app=sample-app -o jsonpath='{.items[*].metadata.name}'`;
do
   kubectl exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html
done

# ファイル確認 OK
$for PODNAME in `kubectl get pod -l app=sample-app -o jsonpath='{.items[*].metadata.name}'`;
do
 kubectl exec -it ${PODNAME} -- cat /usr/share/nginx/html/index.html
done
sample-deployment-86d576464c-hnkm7
sample-deployment-86d576464c-wkhjd
sample-deployment-86d576464c-x75hx


# kubectl run を使って service に curl する.以下のようなオプションを利用して Pod を起動しつつ終わったらすぐに削除する
$kubectl run testpod --image=centos:6 --restart=Never -i --rm -- curl -s http://10.100.97.31:8080/
sample-deployment-86d576464c-wkhjd

# 何回かやってみると Service へのリクエストが各 Pod に分散されているのが分かる
$kubectl run testpod --image=centos:6 --restart=Never -i --rm -- curl -s http://10.100.97.31:8080/
sample-deployment-86d576464c-hnkm7

$kubectl run testpod --image=centos:6 --restart=Never -i --rm -- curl -s http://10.100.97.31:8080/
sample-deployment-86d576464c-x75hx

```

# A レコードを使って Service へリクエストする

[Service-DNS](https://kubernetes.io/docs/concepts/services-networking/service/#dns)

>For example, if you have a Service called "my-service" in a Kubernetes Namespace called "my-ns", a DNS record for "my-service.my-ns" is created

Service へのリクエスト時に IP 以外に A レコードに登録された DNS 名を利用することが出来る。


Service 名を使った **http://sample-clusterip:8080/** で接続してみる。

```bash
$ kubectl run testpod --image=centos:6 --restart=Never -i --rm -- curl -s http://sample-clusterip:8080/
sample-deployment-86d576464c-x75hx
```

A レコードは Service 名と Namespace を使って構成され、実際には[service name].[namespace].svc.cluser.local　という FQDN で登録されている。
名前解決を行うと Service の ClusterIP が返却されることが分かる。

```bash
# 名前解決を実施
$kubectl run testpod --image=centos:6 --restart=Never -i --rm -- dig sample-clusterip.default.svc.cluster.local

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.68.rc1.el6_10.1 <<>> sample-clusterip.default.svc.cluster.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61959
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;sample-clusterip.default.svc.cluster.local. IN A

;; ANSWER SECTION:
sample-clusterip.default.svc.cluster.local. 5 IN A 10.100.97.31

;; Query time: 4 msec
;; SERVER: 10.100.0.10#53(10.100.0.10)
;; WHEN: Fri Dec 28 07:27:56 2018
;; MSG SIZE  rcvd: 118
```

Service 名のみでアクセス出来るのは `/etc/resolv.conf` の search に「default.svc.cluster.local」がある為。

```bash
$ kubectl run testpod --image=centos:6 --restart=Never -i --rm -- cat /etc/resolv.conf
nameserver 10.100.0.10
search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-1.compute.internal us-west-2.compute.internal
options ndots:5
```

# SRV レコードを確認する

[Service-DNS](https://kubernetes.io/docs/concepts/services-networking/service/#dns)

>Kubernetes also supports DNS SRV (service) records for named ports. If the "my-service.my-ns" Service has a port named "http" with protocol TCP, you can do a DNS SRV query for "_http._tcp.my-service.my-ns" to discover the port number for "http".

A レコードだけではなく、Service を作成すると SRV レコードも作成される。
SRV レコードではポート番号も定義できるため、クライアントが SRV レコードに対応している場合、こちらを使うことでポート番号情報を設定する必要がなくなる。

SRV レコードの登録を確認する。
Service で設定した Port 名とプロトコル(TCP/UDP)の情報が必要。
今回、Service の Port 名は「http-port」となっており、プロトコルは TCP となっている。
上記場合、先程の A レコードの前に「_http-port._tcp」を付与して名前解決を行うことで IP に加えポート番号も分かる。

```bash
$ kubectl run testpod --image=centos:6 --restart=Never -i --rm -- dig _http-port._tcp.sample-clusterip.default.svc.cluster.local SRV
If you don't see a command prompt, try pressing enter.
Error attaching, falling back to logs: unable to upgrade connection: container testpod not found in pod testpod_default

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.68.rc1.el6_10.1 <<>> _http-port._tcp.sample-clusterip.default.svc.cluster.local SRV
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12229
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; QUESTION SECTION:
;_http-port._tcp.sample-clusterip.default.svc.cluster.local. IN SRV

;; ANSWER SECTION:
_http-port._tcp.sample-clusterip.default.svc.cluster.local. 5 IN SRV 0 100 8080 sample-clusterip.default.svc.cluster.local.

;; ADDITIONAL SECTION:
sample-clusterip.default.svc.cluster.local. 5 IN A 10.100.97.31

;; Query time: 4 msec
;; SERVER: 10.100.0.10#53(10.100.0.10)
;; WHEN: Fri Dec 28 07:34:56 2018
;; MSG SIZE  rcvd: 254
```