MacのDocker DesktopでさくっとKubernetesコンテナを動かしてみる。

Kubernetesはどこのパブリッククラウドでも動くようになってるしすごいですね。

ただ、今回は手持ちのMacで動かしてみたいと思います。Docker Desktopはインストールされている前提です。

## Mac:KubernetesをONにする。

<img width="612" alt="k8s.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51116/927da422-e576-05eb-22a0-60e7bcdccba8.png">

Docker DesktopアプリのPreferenceから、Kubernetesを有効にしてください。起動には結構時間がかかりますので次のステップを進めておきましょう。

## サンプルのダウンロード

VSCodeの拡張機能でサクッとGKEにデプロイできるCloud Codeというのがあります。
これのサンプルで[cloud-code-samples](https://github.com/GoogleCloudPlatform/cloud-code-samples.git)をダウンロードします。

デスクトップなどでもどこでもいいです。そしてnodejsのサンプルにはいってください。

```sh
cd ~/Desktop
git clone https://github.com/GoogleCloudPlatform/cloud-code-samples.git
cd cloud-code-samples/nodejs/nodejs-hello-world/
```

ファイルはこんな感じになってるはずです。

```text
.
├── Dockerfile
├── README.md
├── img
│   └── diagram.png
├── kubernetes-manifests
│   ├── hello.deployment.yaml
│   └── hello.service.yaml
├── package-lock.json
├── package.json
├── skaffold.yaml
└── src
    └── app.js
```

ここのREADMEにもあるようにGKEをつかうならコマンド一発でできるそうです。

```sh
skaffold run --default-repo=gcr.io/your-project-id-here/cloudcode
```

ローカルで頑張るには少し作業が必要です。skaffoldの設定でもできるのかもしれませんがよくわからないので使いません（教えてくださいw）

## app.jsを少し変更する

あとでわかりやすいようにapp.jsを少し変えます。一ヶ所だけです。

```javascript:src/app.js
const express = require('express');

const app = express();

// returns a simple respnse 
app.get('/', (req, res) => {
  console.log(`received request: ${req.method} ${req.url}`);
  res.status(200).send(`Hello, world! - ${process.env.HOSTNAME}`); // ここを変更
});

// starts an http server on the $PORT environment variable
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}`);
  console.log('Press Ctrl+C to quit.');
});

module.exports = app
```

## Dockerfileをビルドしてイメージを作成する

直下のDockerfileをビルドします。
イメージ名はnode-hello-worldです。

```sh
docker build -t node-hello-world:latest .
```

これでイメージもできました。

```sh
$ docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
node-hello-world                     latest              f1204ec69435        6 seconds ago       921MB
```

## マニフェストファイルを変更

Dockerだけならこの段階で動かせるぜ！となりますが、サンプルプロジェクトのKubernetesマニフェストはすこし変更が必要です。
ついでに稼働するPod数も1つではつまらないので3つにしてみます。
kubernetes-manifests/hello.deployment.yaml を変更します。
replicasを3にするのと、imagePullPolicy: Neverというのを追加します。

```yaml:kubernetes-manifests/hello.deployment.yaml
# This Deployment manifest defines:
# - single-replica deployment of the container image, with label "app: node-hello-world"
# - Pod exposes port 8080
# - specify PORT environment variable to the container process
# Syntax reference https://kubernetes.io/docs/concepts/configuration/overview/
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-hello-world
spec:
  ### ↓ 3に増やす ###
  replicas: 3
  selector:
    matchLabels:
      app: node-hello-world
  template:
    metadata:
      labels:
        app: node-hello-world
    spec:
      containers:
        - name: server
          image: node-hello-world
          ### ↓ これを追加 ###
          imagePullPolicy: Never
          ports:
          - containerPort: 8080
          env:
          - name: PORT
            value: "8080"
```

imagePullPolicy:とは、イメージをどこから取ってくるかの設定です。Neverにしとけばローカルに作成したイメージを使ってくれるとのことです。

https://kubernetes.io/docs/concepts/containers/images/#pre-pulling-images

> By default, the kubelet will try to pull each image from the specified registry. However, if the imagePullPolicy property of the container is set to IfNotPresent or Never, then a local image is used (preferentially or exclusively, respectively).


## 実行！

いよいよ実行してみます。kubernetes-manifests以下の2つのマニフェストファイルを使いますので下記コマンドのようになります。

```sh
kubectl apply -f kubernetes-manifests
```

こんな表示がでます。

```sh
deployment.apps/node-hello-world configured
service/node-hello-world-external unchanged
```

次に状態を見てみましょう。

```sh
kubectl get pods,services
```

起動中だとステータス違いますがしばらく立ってうまく起動してるとこんな表示になるはずです。

```sh
NAME                                   READY   STATUS    RESTARTS   AGE
pod/node-hello-world-5f6c8599c-758wv   1/1     Running   0          13s
pod/node-hello-world-5f6c8599c-q4jqp   1/1     Running   0          13s
pod/node-hello-world-5f6c8599c-xfdl7   1/1     Running   0          13s

NAME                                TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/kubernetes                  ClusterIP      10.96.0.1        <none>        443/TCP        156m
service/node-hello-world-external   LoadBalancer   10.108.217.246   localhost     80:30063/TCP   126m
```

LoadBalancerとしてnode-hello-world-externalが動いていて、そこにぶら下がるPodが3つというわけですね。

## 動作確認

curlで見てみます。

```sh
curl http://localhost/
```

こんな結果が返ってきました。なるほど、HOST_NAMEがPodの名前になっていますね。

```text
Hello, world! - node-hello-world-5f6c8599c-xfdl7
```

3つのPodが動いているはずなので他のPodからのレスポンスは返ってくるでしょうか？

```sh
sh-3.2$ for i in {1..10};do echo `curl -s http://localhost/`;done
Hello, world! - node-hello-world-5f6c8599c-xfdl7
Hello, world! - node-hello-world-5f6c8599c-xfdl7
Hello, world! - node-hello-world-5f6c8599c-758wv
Hello, world! - node-hello-world-5f6c8599c-q4jqp
Hello, world! - node-hello-world-5f6c8599c-758wv
Hello, world! - node-hello-world-5f6c8599c-q4jqp
Hello, world! - node-hello-world-5f6c8599c-758wv
Hello, world! - node-hello-world-5f6c8599c-758wv
Hello, world! - node-hello-world-5f6c8599c-xfdl7
Hello, world! - node-hello-world-5f6c8599c-758wv
```

お〜いい感じです。

ロードバランサはいい感じで分散してくれるのでしょうか？

```sh
sh-3.2$ for i in {1..99};do echo `curl -s http://localhost/`;done | sort | uniq -c
  33 Hello, world! - node-hello-world-5f6c8599c-758wv
  34 Hello, world! - node-hello-world-5f6c8599c-q4jqp
  32 Hello, world! - node-hello-world-5f6c8599c-xfdl7
```

お〜、これまたいい感じ。

## イメージを更新したらrollout

Dockerイメージを作り直した際にはrolloutしましょう。

hello.deployment.yamlを更新します。

```sh
sh-3.2$ kubectl rollout restart -f kubernetes-manifests/hello.deployment.yaml
deployment.apps/node-hello-world restarted
```

すかさず状態をみるとこんな感じです。

```sh
sh-3.2$ kubectl get pods,services
NAME                                    READY   STATUS              RESTARTS   AGE
pod/node-hello-world-58d65988bc-fqhmn   0/1     ContainerCreating   0          0s
pod/node-hello-world-58d65988bc-zdh5f   1/1     Running             0          2s
pod/node-hello-world-5f6c8599c-758wv    1/1     Running             0          49m
pod/node-hello-world-5f6c8599c-q4jqp    1/1     Terminating         0          11m
pod/node-hello-world-5f6c8599c-xfdl7    1/1     Running             0          11m

NAME                                TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/kubernetes                  ClusterIP      10.96.0.1        <none>        443/TCP        168m
service/node-hello-world-external   LoadBalancer   10.108.217.246   localhost     80:30063/TCP   137m
```

Podが入れ替わってますね。入れ替わってる最中もアクセスできますし、しばらくすると安定します。

## 終了

サービスを停止します。Podを単独で停止してもすぐ復旧しちゃうので元から止めましょう。

```sh
sh-3.2$ kubectl delete -f kubernetes-manifests/
deployment.apps "node-hello-world" deleted
service "node-hello-world-external" deleted
```

止まりましたかね？確認してみます。

```sh
sh-3.2$ kubectl get pods,services

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   174m
```

大丈夫そうです。

プログラムはどうでしょう？

```sh
sh-3.2$ curl http://localhost/
curl: (7) Failed to connect to localhost port 80: Connection refused
```

ばっちりですね。

## それにしても...

Kubernetesすごいな〜（Macが重くなるけど...）