Kubernetesの公式チュートリアルをやってみる その5

[Kubernetesの公式チュートリアルをやってみる その4][topic-4] では Node と Pod 概念について扱いました。
この記事のチュートリアルでは `Service` について取り扱います。

#### 注意 && disclaimer

翻訳・意訳が主目的ではないのでいろいろ端折ったりすることがあります。
明らかな間違いはしないつもりですが、何かあれば優しくツッコんであげてください。

→記事のまとめは [Kubernetesの公式チュートリアルをやってみる その1][topic-1] にあります。


## 本記事の取り扱う範囲

[Using a Service to Expose Your App][tutorial-4] の内容を扱います。


## Using a Service to Expose Your App

コンテナ化アプリを公開する `Service` 関連の内容

### Service

- `Service` とは、Pod の論理セットとそのアクセス方法を定義した抽象領域を指す。
    - Pod のレプリカがいくつあっても外部からは1つのエンドポイントとしてアクセスさせることができる。
    - Pod が停止したり入れ替える際もフロント側を無停止で制御することができる。
    - Pod のIP自体はグローバルに公開されないため、外部からのアクセス手段を提供する。
- `Service` は `selector` でラベルを指定することで、トラフィックを転送する先の Pod を識別する。
    - ラベルは Pod に key-value ペアで定義する。
    - 以下のようなユースケースがある。
        - 環境ラベル、development, test, production など
        - バージョンタグで公開バージョンを指定する
        - ロール毎にタグを分ける
- `Service` はいくつかの公開種別(`type`)がある。
    - `ClusterIP`
        - スペックに `type` を指定しない場合の既定値。
        - クラスタ内の内部IPを用いて公開する。
            - クラスタ外部からはアクセスできない。
    - `NodePort`
        - NAT経由で Node のポートを直接公開する。
        - `[ノードIP]:[ポート]` の指定で外部から直接アクセスできるようになる。
    - `LoadBalancer`
        - 外部にロードバランサーを構築し、ロードバランサーと Pod のセットを紐付ける。
        - クラウドなどサポートされている環境でのみ使用可能。
    - `ExternalName`
        - 外部のDNSで定義された `CNAME` レコードを用いて公開する。
        - `kube-dns` のv1.7以上で利用可能。
- `Service` は `selector` を指定しないで構築することもできる。
    - `Service` を特定のエンドポイントに紐付ける際に使用する。
    - `type` = `ExternalName` の際に使用することができる。
- `kubectl run` で Deployment を構築する際に `--expose` を付与することで同時に `Service` を作成することもできる。


### Interactive Tutorial

Estimate Time: 10分

- `kubectl get service`
    - `Service` をリストするコマンド。
        - このチュートリアルでは初期から `Service` が1つ作成されている。

```bash
$ kubectl get service
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   12s
```

- `kubectl expose [deployment name]`
    - `Service` を作成するコマンド。
    - 以下の例では NodePort の8080ポートでサービスを公開している。[^1]
    - デフォルトでは deployment と同じ名称でサービスができるらしい。

[^1]: minikube では `LoadBalancer` はサポートされていないため外部へは公開は試行できない。

```bash
$ kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 8080
service/kubernetes-bootcamp exposed
$ kubectl get service
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP          4m
kubernetes-bootcamp   NodePort    10.109.31.123   <none>        8080:31022/TCP   2m
```

- `kubectl describe service [service name]`
    - `Service` の詳細内容を表示する。[^2]

[^2]: チュートリアル上は `kubectl describe services/[service name]` となっているがどちらでも認識する。

```bash
$ kubectl describe service kubernetes-bootcamp
Name:                     kubernetes-bootcamp
Namespace:                default
Labels:                   run=kubernetes-bootcamp
Annotations:              <none>
Selector:                 run=kubernetes-bootcamp
Type:                     NodePort
IP:                       10.109.31.123
Port:                     <unset>  8080/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  31022/TCP
Endpoints:                172.18.0.2:8080
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
```

- curlで確認
    - クラスタ内でクラスタIPとノードポートを指定すると `Service` 経由でアプリへアクセスできる。

```bash
$ export NODE_PORT=$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')
$ echo NODE_PORT=$NODE_PORT
NODE_PORT=31022
$ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5c69669756-9bk6s | v=1
```

- ラベル管理
    - Pod や `Service` は管理元の deployment 名で `run = [deployment name]` のラベルが付与される。
    - 既存のリソースに対して追加でラベルを付与することもできる。
        - 既存のラベルを削除する場合は `kubectl label [type] name [key]-` とラベルキーにハイフンを付与する。

```bash
$ kubectl describe deployment | head -4
Name:                   kubernetes-bootcamp
Namespace:              default
CreationTimestamp:      Thu, 27 Sep 2018 15:23:36 +0000
Labels:                 run=kubernetes-bootcamp
# ラベル指定でPodをリストする
$ kubectl get pods -l run=kubernetes-bootcamp
NAME                                   READY     STATUS    RESTARTS   AGE
kubernetes-bootcamp-5c69669756-9bk6s   1/1       Running   0          20m
# ラベル指定でServiceをリストする
$ kubectl get services -l run=kubernetes-bootcamp
NAME                  TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes-bootcamp   NodePort   10.109.31.123   <none>        8080:31022/TCP   19m
```

```bash
# 新規ラベルを付与
$ kubectl get pod $POD_NAME -o yaml | grep -i label -A3
  labels:
    pod-template-hash: "1725225312"
    run: kubernetes-bootcamp
  name: kubernetes-bootcamp-5c69669756-9bk6s
$ kubectl label pod $POD_NAME app=v1
pod/kubernetes-bootcamp-5c69669756-9bk6s labeled
$ kubectl get pod $POD_NAME -o yaml | grep -i label -A3
  labels:
    app: v1
    pod-template-hash: "1725225312"
    run: kubernetes-bootcamp
```

- `kubectl delete service [ラベルセレクタ]`
    - ラベルを指定して `Service` を削除する。
        - `Service` を削除してもバックエンドの Pod は削除されない。

```bash
$ kubectl delete service -l run=kubernetes-bootcamp
service "kubernetes-bootcamp" deleted
# ノードポートはアクセスできなくなる。
$ curl $(minikube ip):$NODE_PORT
curl: (7) Failed to connect to 172.17.0.55 port 31022: Connection refused
# Pod内ではアプリは稼働している。
$ kubectl exec -ti $POD_NAME curl localhost:8080
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5c69669756-9bk6s | v=1
```

----

## 参考リンク

- [Kubernetes公式][kubernetes]
- [Kubernetes公式チュートリアル][tutorials]

[kubernetes]:https://kubernetes.io/
[tutorials]:https://kubernetes.io/docs/tutorials/kubernetes-basics/
[tutorial-4]:https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/

[topic-1]:https://qiita.com/kitamin/items/f50fe470a04e38023f7a
[topic-2]:https://qiita.com/kitamin/items/c4902edba777f4bf4826
[topic-3]:https://qiita.com/kitamin/items/c2fb850bbf0397acd9c2
[topic-4]:https://qiita.com/kitamin/items/a7657db9689111c9e4dc