Kubernetes の Service で LoadBalancer Service　と　Headless services　を試す

作業メモ。

[Kubernetes完全ガイド impress top gearシリーズ](https://www.amazon.co.jp/dp/B07HFS7TDT/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)を読みながら手元で確認した時のメモ。

# 環境

```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"}
```

Kubernetes 環境としては EKS を利用。




# LoadBalancer Service

[Type LoadBalancer](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer)

ExternalIP 及び NodePort は Node のグローバル IP にアクセスし、Pod へのトラフィックの転送を行っていた。
LoadBalancer Service では Pod への転送を行う LoadBalancer はクラスタ外で作成する。
例えば AWS であれば ClassicLoadBalancer(以降 CLB) を作成し、トラフィックを転送出来る。

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

```

意味としては以下。

+ CLB でリッスンするポート番号:8080
+ 各 Node への IP アドレスで受け付けるポート:30082
+ コンテナで受け付けるポート:80

```bash
# サービスを作成
$ kubectl apply -f sample-lb.yaml
service "sample-lb" created

# 一定時間経過後 サービスの情報が確認できる. CLUSTER-IP 及び EXTERNAL-IP が存在する
$kubectl get service sample-lb
NAME        TYPE           CLUSTER-IP     EXTERNAL-IP        PORT(S)          AGE
sample-lb   LoadBalancer   10.100.51.18   af6a051c10ba7...   8080:30082/TCP   15s

# 詳細は describe で確認出来る
$kubectl describe service sample-lb
Name:                     sample-lb
Namespace:                default
Labels:                   <none>
Annotations:              kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-lb","namespace":"default"},"spec":{"ports":[{"name":"http-port","nodePo...
Selector:                 app=sample-app
Type:                     LoadBalancer
IP:                       10.100.51.18
LoadBalancer Ingress:     af6a051c10ba711e9a297068bea14a4c-639722574.ap-northeast-1.elb.amazonaws.com
Port:                     http-port  8080/TCP
TargetPort:               80/TCP
NodePort:                 http-port  30082/TCP
Endpoints:                172.31.20.41:80,172.31.29.9:80,172.31.9.21:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason                Age   From                Message
  ----    ------                ----  ----                -------
  Normal  EnsuringLoadBalancer  54s   service-controller  Ensuring load balancer
  Normal  EnsuredLoadBalancer   49s   service-controller  Ensured load balancer

# ExternalIP の DNS 名でアクセス可能
$curl http://af6a051c10ba711e9a297068bea14a4c-639722574.ap-northeast-1.elb.amazonaws.com:8080/
sample-deployment-86d576464c-wkhjd
```

今回の場合、CLB には各インスタンスの 30082 ポート番号で登録されていた。
また、上記の際に 各ワーカー Node のセキュリティグループには CLB に設定されているセキュリティグループからの Inbound をすべて許可するルールが追加設定されており、これによって CLB->Node 間の通信が可能になっている模様。

## 接続元 IP アドレスを絞る

[Configure Your Cloud Provider's Firewalls](https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/)

ロードバランサーに対して接続元 IP を絞る場合があるが、先程のマニュフェストファイルの場合、接続元 IP アドレスは絞り込みされておらず、どの IP からでもアクセスが出来る。

接続元 IP アドレスを制限した場合、 **loadBalancerSourceRanges**
 を遣う。

```bash
$cp sample-lb.yaml sample-lb-restrict-ip.yaml

$vi sample-lb-restrict-ip.yaml
```

```sample-lb-restrict-ip.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-lb
spec:
  type: LoadBalancer
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
      nodePort: 30082
  selector:
    app: sample-app
  loadBalancerSourceRanges:
    - 130.211.204.1/32
```


```bash
# 適用
$kubectl apply -f sample-lb-restrict-ip.yaml
service "sample-lb" configured

# 先程は接続できたクライアントからも接続できない
$curl http://af6a051c10ba711e9a297068bea14a4c-639722574.ap-northeast-1.elb.amazonaws.com:8080/

# CLB に設定されているセキュリティグループを見ると 8080 ポートの接続元 IP が絞り込まれている
$aws ec2 describe-security-groups --group-ids sg-040af982fa57f35e5
{
    "SecurityGroups": [
        {
            "Description": "Security group for Kubernetes ELB af6a051c10ba711e9a297068bea14a4c (default/sample-lb)",
            "GroupName": "k8s-elb-af6a051c10ba711e9a297068bea14a4c",
            "IpPermissions": [
                {
                    "FromPort": 8080,
                    "IpProtocol": "tcp",
                    "IpRanges": [
                        {
                            "CidrIp": "130.211.204.1/32"
                        }
                    ],
                    "Ipv6Ranges": [],
                    "PrefixListIds": [],
                    "ToPort": 8080,
                    "UserIdGroupPairs": []
                },
                {
                    "FromPort": 3,
                    "IpProtocol": "icmp",
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    "Ipv6Ranges": [],
                    "PrefixListIds": [],
                    "ToPort": 4,
                    "UserIdGroupPairs": []
                }
            ],
            "OwnerId": "xxxx",
            "GroupId": "sg-040af982fa57f35e5",
            "IpPermissionsEgress": [
                {
                    "IpProtocol": "-1",
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    "Ipv6Ranges": [],
                    "PrefixListIds": [],
                    "UserIdGroupPairs": []
                }
            ],
            "Tags": [
                {
                    "Key": "kubernetes.io/cluster/eks-first-cluster",
                    "Value": "owned"
                }
            ],
            "VpcId": "vpc-8086a2e5"
        }
    ]
}

```

# Headless services を試す

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

Headless services  を使うことで個々の Pod の IP アドレスを DNS ラウンドロビンで返却することが出来る。

今までの Service では提供されるエンドポイントへアクセスすると負荷分散が行われた。
しかし、負荷分散を行いたくない場合もある。
そのような場合に　Headless services を使う。

試す。
Headless Service を使いたい時には **clusterIP:None** を指定する。

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


```bash
$ kubectl apply -f sample-headless.yaml
service "sample-headless" created

# エンドポイントが提供されないので CLUSTER-IP が  None になっている
$kubectl get service sample-headless
NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
sample-headless   ClusterIP   None         <none>        80/TCP    39s

# 各 Pod の IP を確認
$kubectl describe pod -l app=sample-app |grep IP
IP:                 172.31.20.41
IP:                 172.31.29.9
IP:                 172.31.9.21

# サービス名で名前解決すると各 Pod の IP が返却される
$kubectl run testpod --image=centos:6 --restart=Never -i --rm -- dig +short sample-headless.default.svc.cluster.local
172.31.20.41
172.31.29.9
172.31.9.21
```

## SatefulSet で使って Pod 名で名前解決する

Service と紐づくのが StatefulSet の場合、Pod 名での名前解決も可能。
StatefulSet と Headless service を作って確認

```sample-statefulset-headless.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset-headless
spec:
  serviceName: sample-headless
  replicas: 3
  selector:
    matchLabels:
      app: sample-app-headless
  template:
    metadata:
      labels:
        app: sample-app-headless
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
```

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

```bash
# StatefulSet と Healess Serivce を作成
$kubectl apply -f sample-statefulset-headless.yaml
$kubectl apply -f sample-headless.yaml

# Pod 「sample-statefulset-headless-0」の IP は172.31.28.56
$kubectl describe pod sample-statefulset-headless-0 |grep IP
IP:                 172.31.28.56

# Pod の名前解決で IP 取得可能
$kubectl run testpod --image=centos:6 --restart=Never -i --rm -- dig +short sample-statefulset-headless-0.sample-headless.default.svc.cluster.local
172.31.28.56

# サービス名で名前解決した場合、同じように各 Pod の IP が返却される
$kubectl run testpod --image=centos:6 --restart=Never -i --rm -- dig +short sample-headless.default.svc.cluster.local
172.31.25.247
172.31.28.56
172.31.7.49

# サービス名で名前解決できる IP と Pod の IP が同じ
$kubectl describe pod -l app=sample-app-headless |grep IP
IP:                 172.31.28.56
IP:                 172.31.7.49
IP:                 172.31.25.247

```