【KubernetesでPodをスケーリングさせる】その１- 手動スケーリング

## 概要
KubernetesでPodを手動でスケールアウトさせる方法。
`replicas: 2`で作成したpodを4つにスケールアウトさせてみる。
ここではMinikube環境を利用したが、その他の環境の場合は適宜読み替えてください。

オートスケーリングさせる方法はこちら。
[【KubernetesでPodをスケーリングさせる】その２ - オートスケーリング](https://qiita.com/Esfahan/items/d8105741ebf283c9bebb)

Nodeをスケーリングさせることもできるが、ここでは触れない。
参考：[Kuberentes クラスタのオートスケール](https://qiita.com/shmurata/items/f75ff02a8096e8cfd6cf)

## 目次
- [Kubernetesハンズオン目次](https://qiita.com/Esfahan/items/4fcf51622fed20dffb88)

## 環境
- CentOS 7.6
- Minikube v1.1.1
- Kubernetes v1.14.3

## 関連記事
- [Minikubeを使ってローカルにkubernetes環境を構築](https://qiita.com/Esfahan/items/f5c846088281c39f73a4)
- [KubernetesでDockerコンテナ「php:7.0-apache」を利用してみる](https://qiita.com/Esfahan/items/35b31c6642ad5d375faf)

## Deploymentの仕組み
スケーリングをするためには、Deploymentの仕組みについての理解が必要となる。

- DeploymentはReplicaSetを生成、管理し、ReplicaSetはPodの生成、管理をする。
- コンテナイメージの変更などのアップデートがあった際には、新規にReplicaSetを生成し、旧ReplicaSetから新ReplicaSetへPodが置き換えられていく。（ローリングアップデート）
    - 旧ReplicaSetも一定期間保持され、ロールバックが可能となる。
- Replica数の変更のときは、新たなReplicaSetは生成せずに既存のReplicaSetの下のPod数を変更する。

参考: [Deployment の仕組み](https://qiita.com/tkusumi/items/01cd18c59b742eebdc6a#deployment-%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)

## 事前準備
### Dockerコンテナにmountするディレクトリとファイルを作成
コンテナのホスト名を返すスクリプトを実装。
スケールアウト実施後に、ロードバランシングされていることを確認するために、アクセスするたびに異なるコンテナにアクセスが行っていることを確認するため、ホスト名を取得するプログラムを仕込む。

後述のDeploymentの設定で、Dockerコンテナのvolumeとして/home/username/containers/web/html/をmountするので、その配下にindex.phpを設置する。

```php:/home/username/containers/web/html/index.php
<?php echo gethostname() . "\n"; ?>
```

### Deploymentを定義

```yaml:deployment.yml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: http-deployment
spec:
  selector:
    matchLabels:
      ########################################################
      # deploymentの.spec.selector.matchLabels.appの値と、
      # 後述のservice.ymlの.spec.selector.appの値を合わせることで、
      # serviceからdeploymentにリクエストを流すことが可能となる。
      ########################################################
      app: http-app
  # podの設定
  replicas: 2
  # ここでは触れないが、このPodTemlateの部分に変更があったときに、ローリングアップデートが実行される
  template:
    metadata:
      labels:
        # 上記のspec.selector.matchLabels.appと合っていないとエラーとなる
        app: http-app
    spec:
      containers:
        # Dockerコンテナ名
        - name: http-container
          # 利用するDockerイメージ
          image: php:7.0-apache
          # localイメージ利用。ただし、localに指定したイメージが存在しない場合はレポジトリを参照する
          imagePullPolicy: IfNotPresent
          ports:
            # service.ymlで定義する外部公開用port設定の箇所と名前を合わせることでbindされる
            # nameは15文字以内でないとエラーになる
            - name: ha-inner-port
              # コンテナ内のport
              containerPort: 80
          # mountするディレクトリ：コンテナ側の設定
          volumeMounts:
            - name: documentroot
              mountPath: /var/www/html
      # mountするディレクトリ：ホストOS側の設定
      volumes:
        - name: documentroot
          hostPath:
            path: /home/username/containers/web/html
```

### Serviceを定義

```yaml:service.yml
kind: Service
apiVersion: v1
metadata:
  # service名
  name: http-service
spec:
  selector:
    ########################################################
    # 前述のdeploymentの.spec.selector.matchLabels.appの値と、
    # service.ymlの.spec.selector.appの値を合わせることで、
    # serviceからdeploymentにリクエストを流すことが可能となる。
    ########################################################
    app: http-app
  ports:
    - name: ha-outer-port
      # 外部公開するport
      port: 8080
      # deployment.ymlで定義したport名と合わせることでbindされる
      targetPort: ha-inner-port
    # 以下のようにtargetPortを名前で合わせずに明記してもOK
    #- name: ha-outer-port
    #  protocol: "TCP"
    #  port: 8080
    #  targetPort: 80
  type: NodePort
```

### 作成

```shell-session
$ sudo kubectl create -f deployment.yml -f service.yml
deployment.extensions/http-deployment created
service/http-service created
```

### pod, service, deploymentの内容を確認

service/http-service、deployment.apps/http-deployment、それぞれのSELECTORが`app=http-app`になっていることを確認。labelが一致していることで、ServiceからDeploymentへのリクエストが可能となる。

```shell-session
$ sudo kubectl get all
NAME                                   READY   STATUS    RESTARTS   AGE
pod/http-deployment-7fc8f549fd-4nwxr   1/1     Running   0          1s
pod/http-deployment-7fc8f549fd-gkjxl   1/1     Running   0          1s

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/http-service   NodePort    10.100.109.122   <none>        8080:32021/TCP   2s
service/kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP          26h

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/http-deployment   2/2     2            2           2s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/http-deployment-7fc8f549fd   2         2         2       2s
```

### serviceの確認
service/http-serviceをdescribeして、以下を確認する。

- Selectorが設定したラベルになっている
- Port、TargetPortが設定したとおりになっている
- replica数を2にしたので、Endpointsが2つある

```shell-session
$ sudo kubectl describe service/http-service
Name:                     http-service
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=http-app
Type:                     NodePort
IP:                       10.100.109.122
Port:                     ha-outer-port  8080/TCP
TargetPort:               ha-inner-port/TCP
NodePort:                 ha-outer-port  32021/TCP
Endpoints:                192.168.249.5:80,192.168.249.6:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
```

### ロードバランシングされていることを確認
コンテナのホスト名を返すindex.phpを実装したので、curlで何度かアクセスしてみる。
そうすると、replicationされているので、実行するたびに違うホスト名が返ってくる。
これで、ロード・バランシングされていることを確認できる。

```shell-session
$ curl $(sudo minikube service http-service --url)
http-deployment-7fc8f549fd-gkjxl
$ curl $(sudo minikube service http-service --url)
http-deployment-7fc8f549fd-4nwxr
```

### podを削除してみる
削除する前に、podがreplicaで設定したとおり2つ存在していることを確認する。

```shell-session
$ sudo kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
http-deployment-7fc8f549fd-4nwxr   1/1     Running   0          68m
http-deployment-7fc8f549fd-gkjxl   1/1     Running   0          68m
```

podを1つ削除する。

```shell-session
$ sudo kubectl delete pod http-deployment-7fc8f549fd-4nwxr
pod "http-deployment-7fc8f549fd-4nwxr" deleted
```

削除後、podを確認すると新しく生成され、replicaで指定したとおり、常に2つに保たれていることが分かる。

```shell-session
$ sudo kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
http-deployment-7fc8f549fd-gkjxl   1/1     Running   0          69m
http-deployment-7fc8f549fd-xtjlr   1/1     Running   0          12s
```


**参考**

- [KubernetesのLabelとAnnotationの使い分けについて](https://tomokazu-kozuma.com/on-the-distinction-between-label-and-annotation-of-kubernetes/)
- [Kubernetes: Deployment の仕組み](https://qiita.com/tkusumi/items/01cd18c59b742eebdc6a)

## スケールアウト実行
replicaの数を4つにする

```shell-session
$ sudo kubectl scale deployments/http-deployment --replicas=4
deployment.extensions/http-deployment scaled
```

### スケールアウト後
- deployment
    - READY、UP-TO-DATE、AVAILABLEがそれぞれ4に変わった。
- pod
    - podが4つに増えた。

```shell-session
$ sudo kubectl get all
NAME                                   READY   STATUS    RESTARTS   AGE
pod/http-deployment-7fc8f549fd-4nwxr   1/1     Running   0          6m51s
pod/http-deployment-7fc8f549fd-gkjxl   1/1     Running   0          6m51s
pod/http-deployment-7fc8f549fd-s79t7   1/1     Running   0          13s
pod/http-deployment-7fc8f549fd-t9pm4   1/1     Running   0          13s

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/http-service   NodePort    10.100.109.122   <none>        8080:32021/TCP   6m51s
service/kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP          27h

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/http-deployment   4/4     4            4           6m51s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/http-deployment-7fc8f549fd   4         4         4       6m51s
```


describeの結果でも、Replicasが4つになったことが分かる。

```shell-session
$ sudo kubectl describe deployment/http-deployment
Name:                   http-deployment
Namespace:              default
CreationTimestamp:      Wed, 12 Jun 2019 13:26:30 +0900
Labels:                 app=http-app
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=http-app
Replicas:               4 desired | 4 updated | 4 total | 4 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 1 max surge
Pod Template:
  Labels:  app=http-app
  Containers:
   http-container:
    Image:        php:7.0-apache
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:
      /var/www/html from documentroot (rw)
  Volumes:
   documentroot:
    Type:          HostPath (bare host directory volume)
    Path:          /home/username/containers/web/html
    HostPathType:
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
OldReplicaSets:  <none>
NewReplicaSet:   http-deployment-7fc8f549fd (4/4 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  7m4s  deployment-controller  Scaled up replica set http-deployment-7fc8f549fd to 2
  Normal  ScalingReplicaSet  26s   deployment-controller  Scaled up replica set http-deployment-7fc8f549fd to 4
```


## スケールダウン
スケールアウトと同じ要領でreplicas数を指定すればOK。

```shell-session
$ sudo kubectl scale deployments/http-deployment --replicas=2
deployment.extensions/http-deployment scaled
```

2つになった。

```shell-session
$ sudo kubectl get all
NAME                                   READY   STATUS    RESTARTS   AGE
pod/http-deployment-7fc8f549fd-4nwxr   1/1     Running   0          7m39s
pod/http-deployment-7fc8f549fd-gkjxl   1/1     Running   0          7m39s

NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/http-service   NodePort    10.100.109.122   <none>        8080:32021/TCP   7m39s
service/kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP          27h

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/http-deployment   2/2     2            2           7m39s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/http-deployment-7fc8f549fd   2         2         2       7m39s
```

## オートスケーリングさせる方法はこちら
- [【KubernetesでPodをスケーリングさせる】その２ - オートスケーリング](https://qiita.com/Esfahan/items/d8105741ebf283c9bebb/)

## 参考
- [アプリケーションの複数インスタンスを実行](https://kubernetes.io/ja/docs/tutorials/kubernetes-basics/scale/)
- [[第1回]Kubernetesの公式チュートリアルをやって基本を押さえる -アプリのデプロイ-](https://dev.classmethod.jp/cloud/kubernetes-tutorial-1/)
- [[第2回]Kubernetesの公式チュートリアルをやって基本を押さえる -アプリ公開-](https://dev.classmethod.jp/cloud/kubernetes-tutorial-2/)
- [[第3回]Kubernetesの公式チュートリアルをやって基本を押さえる -スケールアウト-](https://dev.classmethod.jp/cloud/kubernetes-tutorial-3/)
- [KubernetesのWorkloadsリソース（その1）](https://thinkit.co.jp/article/13610)
- [Kubernetes勉強 その3（DeploymentとService）](https://qiita.com/hiroeorz@github/items/92912137699a063fdbcb)
- [Kubernetes の各リソースに対するメモ](https://qiita.com/apstndb/items/e75c40975183b8aabba6)