【Kubernetes入門】ローカル環境にReactアプリをデプロイする

## やること
今回は簡単にKubernetes環境が作れる「**[Minikube](https://minikube.sigs.k8s.io/docs/)**」を用いてローカル環境にReactアプリケーションをデプロイし、疎通確認をしていきます。
また、今回のソースコードはGitHubに乗せています。
[![MobilitySDG/react-k8s - GitHub](https://gh-card.dev/repos/MobilitySDG/react-k8s.svg?fullname=)](https://github.com/MobilitySDG/react-k8s)


## 使用する環境
- OS : Apple M1 macOS BigSur 11.4
- docker : v20.10.7


## Minikubeのインストール
[こちらのサイト](https://minikube.sigs.k8s.io/docs/start/)からインストールを行ってください。
（Intel版、Apple M1版ありますので、自分の環境にあったものをインストールしてください。）
M1のMacの場合は以下で入手できます。

```
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64
$ sudo install minikube-darwin-arm64 /usr/local/bin/minikube
```

また、上記のURLページ内にも記載がありますが、Minikubeを使うにはDocker等のコンテナ、仮想マシン環境が必要になるので別途インストールをお願いします。

## kubectlのインストール
Kubernetesを使用する際には「**kubectl**」コマンドを使うため使えるようにします。
[こちらのサイト](https://kubernetes.io/docs/tasks/tools/install-kubectl-macos/)からインストールを行ってください。
M1のMacの方は以下で入手できます。

```
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl"
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl.sha256"
$ echo "$(<kubectl.sha256)  kubectl" | shasum -a 256 --check
$ chmod +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl
$ sudo chown root: /usr/local/bin/kubectl
```

## Minikubeの動作確認
インストールしたminikubeとkubectlの確認をしてみましょう。
以下コマンドで起動できます。

```
$ minikube start
😄  Darwin 11.4 (arm64) 上の minikube v1.21.0
✨  dockerドライバーが自動的に選択されました
👍  コントロールプレーンのノード minikube を minikube 上で起動しています
🚜  イメージを Pull しています...
🔥  docker container (CPUs=2, Memory=1988MB) を作成しています...

🧯  Docker is nearly out of disk space, which may cause deployments to fail! (87% of capacity)
💡  提案: 

    Try one or more of the following to free up space on the device:
    
    1. Run "docker system prune" to remove unused Docker data (optionally with "-a")
    2. Increase the storage allocated to Docker for Desktop by clicking on:
    Docker icon > Preferences > Resources > Disk Image Size
    3. Run "minikube ssh -- docker system prune" if using the Docker container runtime
🍿  Related issue: https://github.com/kubernetes/minikube/issues/9024

🐳  Docker 20.10.7 で Kubernetes v1.20.7 を準備しています...
    ▪ 証明書と鍵を作成しています...
    ▪ Control Plane を起動しています...
    ▪ RBAC のルールを設定中です...
🔎  Kubernetes コンポーネントを検証しています...
    ▪ イメージ gcr.io/k8s-minikube/storage-provisioner:v5 を使用しています
🌟  有効なアドオン: storage-provisioner, default-storageclass
🏄  完了しました！ kubectl が「"minikube"」クラスタと「"default"」ネームスペースを使用するよう構成されました
```

kubectlコマンドも確認しましょう。

```
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.2", GitCommit:"092fbfbf53427de67cac1e9fa54aaa09a28371d7", GitTreeState:"clean", BuildDate:"2021-06-16T12:59:11Z", GoVersion:"go1.16.5", Compiler:"gc", Platform:"darwin/arm64"}
Server Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.7", GitCommit:"132a687512d7fb058d0f5890f07d4121b3f0a2e2", GitTreeState:"clean", BuildDate:"2021-05-12T12:32:49Z", GoVersion:"go1.15.12", Compiler:"gc", Platform:"linux/arm64"}
```

また、minikubeではダッシュボードを用いてデプロイ状況をGUIで確認することができるので、合わせてみておくと良いと思います。

```
$ minikube dashboard
```
![スクリーンショット 2021-07-03 22.08.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1457643/782cd8e6-e94f-902e-38d6-9f28d382212c.png)


## ローカルのDockerイメージを使えるように設定
後ほどminikubeでデプロイする際に、ローカルのイメージを使うことができるように環境変数を設定します。

```
$ eval $(minikube docker-env)
```
※この後の作業で、Dockerイメージのビルド、Kubernetesのymlファイルの実行は上記コマンドを実行したターミナルで行ってください。


## Reactアプリケーションの準備
今回はアプリの中までは作成しないため、簡単に[create-react-app](https://github.com/facebook/create-react-app)で作成します。

```
$ npx create-react-app react-k8s
$ cd react-k8s
```
せっかくなので文言を少し変えておきましょう。
10行目の文言を変えておきます。

```react:/src/App.js
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          {/* Edit <code>src/App.js</code> and save to reload. */}
          {/* ↑ここを変える */} 
          Hello React Application!!
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
```

変更できたら、以下コマンドで起動させて[localhost:3000](localhost:3000)へアクセス。

```
$ yarn start
```

![スクリーンショット 2021-07-03 16.30.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1457643/bbc654f6-5a43-ae9e-5a37-9e0050f81c2a.png)
OKですね！

## Dockerfileの作成
デプロイする際にはdockerイメージが必要なので、そこに使うためにDockerfileを作成します。

```dockerfile:Dockerfile
# nodeの軽量版を使用
FROM node:14.17.0-alpine

# ワークディレクトリを設定
WORKDIR /app

# ホストの.（カレントディレクトリ）に作成したreactアプリをコンテナの.(=/app)にコピー
COPY . .

# 依存関係のインストール
RUN yarn install

# 起動コマンド
CMD ["yarn", "start"]
```

イメージを作成して動かしてみましょう。

```
$ docker build -t mobilitysdg/react-k8s:1.0 .
$ docker images
REPOSITORY                           TAG          IMAGE ID       CREATED         SIZE
mobilitysdg/react-k8s                1.0          9c30bb0dd575   3 minutes ago   589MB
```
できていますね！イメージを起動して動くか確認しましょう。

```
$ docker run -p 3000:3000 mobilitysdg/react-k8s:1.0
yarn run v1.22.5
$ react-scripts start
ℹ ｢wds｣: Project is running at http://172.17.0.3/
ℹ ｢wds｣: webpack output is served from 
ℹ ｢wds｣: Content not from webpack is served from /app/public
ℹ ｢wds｣: 404s will fallback to /
Starting the development server...

Compiled successfully!

You can now view reacr-k8s in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://172.17.0.3:3000

Note that the development build is not optimized.
To create a production build, use yarn build.
```
良さそうですね！

## Kubenetesのymlファイルを書く
### 1.deployment.yml

```yml:deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: react-k8s
spec:
  replicas: 1
  selector:
    matchLabels:
      app: react-k8s
  template:
    metadata:
      labels:
        app: react-k8s
    spec:
      containers:
      - name: react-k8s
        image: mobilitysdg/react-k8s:1.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000
        command: ["yarn", "start"]
         
```
ローカルのイメージを使用する場合は、`imagePullPolicy`を`IfNotPresent`か`Never`に設定してください。

### 2.service.yml
上記deployment.ymlで設定した内容をクラスター外へ公開するためにserviceを作成するための設定を書きます。

```yml:service.yml
apiVersion: v1
kind: Service
metadata:
  name: react-k8s
spec:
  type: NodePort
  selector:
    app: react-k8s
  ports:
    - port: 3000
      targetPort: 3000
      protocol: TCP
      name: react-k8s
```
クラスターの外部からアクセスする際には `type`に`NodePort`や`LoadBalancer`を使用しますが、今回は`NodePort`を使用します。
またこれらにはL4ロードバランサーの役割しかないため、ipアドレスのみでの接続となります。
本番運用する際には[Ingress](https://kubernetes.io/ja/docs/concepts/services-networking/ingress/)という別のサービスを使用してHTTPでのアクセスができるようにします。

## デプロイする
上で作成した`deployment.yml`と`service.yml`を用いて実際にデプロイをしていきます。

まずは`deployment.yml`から。

```
$ kubectl apply -f deployment.yml 
deployment.apps/react-k8s created

$ kubectl get all
NAME                             READY   STATUS    RESTARTS   AGE
pod/react-k8s-75cf4d8ff6-4ksf2   1/1     Running   0          17s

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

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/react-k8s   1/1     1            1           17s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/react-k8s-75cf4d8ff6   1         1         1       17s
```
`pod`がRunning状態になっており、`replicaset`と`deployment`が作成されているのがわかります。

ちなみにダッシュボードを見てみると以下のようにデプロイされていることが確認できます。
![スクリーンショット 2021-07-03 22.18.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1457643/30461d42-3855-a7ad-3ce1-e7a453d1f367.png)

次に`service.yml`です。

```
$ kubectl apply -f service.yml
service/react-k8s created

$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP          3h59m
react-k8s    NodePort    10.110.66.69   <none>        3000:31609/TCP   23s
```

react-k8sというserviceが作成されていますね。
これでデプロイ作業は終了です！

ちなみに、今回は役割ごとにdelpoyment.ymlとservie.ymlを分けて書きましたが、一つのファイルにすることもできます。その場合は`---`でつなげて以下のように書くことができます。

```yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: react-k8s
spec:
  replicas: 1
  selector:
    matchLabels:
      app: react-k8s
  template:
    metadata:
      labels:
        app: react-k8s
    spec:
      containers:
      - name: react-k8s
        image: mobilitysdg/react-k8s:1.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000
        command: ["yarn", "start"]
         
---
apiVersion: v1
kind: Service
metadata:
  name: react-k8s
spec:
  type: NodePort
  selector:
    app: react-k8s
  ports:
    - port: 3000
      targetPort: 3000
      protocol: TCP
      name: react-k8s
```

このファイルをkubectl applyすることでまとめてデプロイすることができます。

## 接続確認
本来であれば`minikube ip`コマンドで取得できるipアドレスと、service作成時に決められたポート（今回は31609）を用いてブラウザからアクセス可能（例えば192.168.49.2:31609）ですが、m1のMacは接続ができないようです、、
なので[minikube service](https://minikube.sigs.k8s.io/docs/commands/service/)を使用します。サービス名は今回`react-k8s`としたので以下コマンドでサービスへの接続先を確認します。

```
$ minikube service react-k8s　--url
🏃  Starting tunnel for service react-k8s.
|-----------|-----------|-------------|------------------------|
| NAMESPACE |   NAME    | TARGET PORT |          URL           |
|-----------|-----------|-------------|------------------------|
| default   | react-k8s |             | http://127.0.0.1:60778 |
|-----------|-----------|-------------|------------------------|
http://127.0.0.1:60778
```
上記で確認したURLにアクセスすると、、
![スクリーンショット 2021-07-03 23.01.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1457643/32870061-06e9-3b09-8441-b7fd9548b35b.png)

アクセスが確認できました！
確認ができたらデプロイしたリソースは削除しておきます。

```
$ kubectl delete -f service.yml
service "react-k8s" deleted

$ kubectl delete -f deployment.yml 
deployment.apps "react-k8s" deleted
```

## まとめ
今回はアプリ作成〜デプロイまでを広く浅くの形で一通り実施してみました。
Kubernetesを用いたデプロイの参考になればと思います！

## 宣伝
パーソルプロセス＆テクノロジー株式会社（以下パーソルP&T）、システムソリューション（SSOL）事業部所属の戸田です。

私はモビリティソリューションデザインチームに所属しており、モビリティ（ここでは移動手段全般）に関するサービスを考えたり、アプリを構築したりしております。

いわゆる「MaaS」に取り組んでおります。

私たちが「MaaS」に取り組む中で、現在活用している、もしくは活用する予定の技術やサービスやとりあえず発信したいことなどなど、幅広くチームメンバーと共に執筆していきたいと思います。
メンバーごとに違った内容を発信していきますので、お楽しみに！

また、「MaaS」について詳しく知りたい方は、チームメンバーの吉田が記事を掲載しておりますので、
ぜひそちらをご覧ください。

[「MaaSとは」でたどり着いて欲しい記事　（1/3 前編）](https://note.com/ppt_msdg_maas/n/naae427dbc37c)
[「MaaSとは」でたどり着いて欲しい記事　（2/3 中編）](https://note.com/ppt_msdg_maas/n/na45c90b2f02f)
[「MaaSとは」でたどり着いて欲しい記事　（3/3 後編）](https://note.com/ppt_msdg_maas/n/n8fd724f7db6c)

最後まで読んでいただき、ありがとうございました！