フロントエンドアプリをGKEで動かす

# はじめに
この記事は、Dockerもしくはkuberntes初心者が、個々の技術単体ではなく一連の開発の流れとして理解することを目的としています。

私自身がkubernetesを勉強し始めている中で、「Dockerもなんとなく理解した、kubernetesもなんとなく理解した、しかし合わせて考えるとまだつながっていないな」という状態になっていました。そこで、手元のローカルPCで動くアプリケーションをGKEで動くように、実際の活用イメージをつけていきました。今回はその手順を紹介します。

# 構成
最終的に図のように構成します。
![Cアーキテクチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/211033/a56bfad6-1404-5c63-72c8-ec5ac000d29d.jpeg)

# 構築手順
## アプリ作成
今回はNode.jsでビルドできるフロントエンドアプリとして、React.jsを選択しました。

- Node.jsのインストール
    - [nodist](https://github.com/nullivex/nodist)を使ってNode.jsをインストールする
    - 今回はv8.13.0を使用
- Reactの環境構築ツールで雛形を作成するために、create-react-appをインストール
    - `npm install -g create-react-app` を実行
        - この際、Node.jsのバージョンではまることがあるので注意
- 以下のコマンドで雛形を作成し起動する
    - 開発中アプリを起動するため、`npm start`とする。作成された雛形のpackage.jsonに    `"start": "react-scripts start"`と記述されている。

```text
create-react-app react-sample
cd react-sample
npm start
```

```json:package.json
{
  "name": "react-sample",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.6.3",
    "react-dom": "^16.6.3",
    "react-scripts": "2.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}
```
- 起動したことをブラウザから確認
    - `localhost:3000`にアクセスし、表示されたらOK
![reactsample.JPG](https://qiita-image-store.s3.amazonaws.com/0/211033/737d78b1-6a4d-9dbc-69cc-afeaa504f373.jpeg)

- この時点でのフォルダ構成は以下の通り

```text
react-sample
├── node_modules
├── public
├── src
├── package.json
├── .gitignore
└── README.md
```

## Dockerイメージ作成
- Docker Toolboxをインストール
    - https://docs.docker.com/toolbox/toolbox_install_windows/
- 以下のDockerfileをサンプルアプリと同じディレクトリに用意
    - 今回はgit管理しませんが、Dockerfileはソースコードと同様に管理できるようしておきます。
    - Node.jsがインストールされたビルド用コンテナでReactサンプルをビルドした後、Nginxがインストールされたコンテナで実行します。このように、マルチステージビルドにすることで別のコンテナで生成されたファイルを`COPY`することができます。

```text
react-sample
├── node_modules
├── public
├── src
├── .gitignore
├── Dockerfile
├── package.json
└── README.md
```

```dockerfile:Dockerfile
FROM node:8-alpine as builder
WORKDIR /app
ADD . .
RUN npm install
RUN npm run build

FROM nginx
COPY --from=builder /app/build /usr/share/nginx/html
```

- 以下の.dockerignoreファイルをDockerfileと同じディレクトリに作成
    - 今回はコンテナ上でソースのビルドを行うので、ローカルにあるnode_modules、build配下のファイルはローカルからコピーしておく必要はありません。
    - これをしないと大量のファイルがdaemonに送られることになるため、ビルドに時間がかかります。

```text
react-sample
├── node_modules
├── public
├── src
├── .dockerignore
├── .gitignore
├── Dockerfile
├── package.json
└── README.md
```

```docker:.dockerignore
node_modules
build
```

- Docker Quickstart Terminalを起動し、コンソールから以下のコマンドでイメージを作成

```text
cd react-sample
docker build -t react-sample .
```
- 以下のコマンドでホストのIPを確認後、コンテナを起動
    - Docker Quickstart Terminalを使うとホストとしてVirtualBoxが起動するため、ホストIPはlocalhostではありません。
    - `ホストIP:80`にアクセスしローカルで動作確認した時と同様に表示されたらOK

```text
docker-machine ip
docker run -d react-sampe
```

## Dockerイメージアップロード
- Docker Hubのアカウント、リポジトリを作成
- イメージの名前を付け替える
    - `docker tag react-sample <アカウント名>/<リポジトリ名>`
- 以下のコマンドでリポジトリにアップロードする

```text
docker push <アカウント名>/<リポジトリ名>:latest
```
## GCPプロジェクト作成
Gmailアカウントで[こちら](https://cloud.google.com/)からコンソールにログインし、プロジェクトを作成する。
![GCPプロジェクト.JPG](https://qiita-image-store.s3.amazonaws.com/0/211033/ab8f23e3-fd02-9e11-2f0e-30d6cab16e57.jpeg)

## GKEクラスタ作成
- GKEのコンソールにアクセスし、クラスタを作成する
    - 「最初のクラスタ」を選択するとNodeの台数は1台となるため、サンプルを実行する上ではこれで十分です。

![最初のクラスター.JPG](https://qiita-image-store.s3.amazonaws.com/0/211033/3dbe0f82-ccb7-b0c0-9086-75d71fbd7fac.jpeg)

## CloudShellからクラスタにアクセスする
上記で作成したクラスタに対してkubectlコマンドを実行するために認証が必要となるため、以下のコマンドをCloudShellで実行します。

クラスタが作成されているプロジェクトのID、ゾーンとクラスタ名を指定します。

```text
gcloud config set project [PROJECT_ID]
gcloud config set compute/zone us-central1-a
gcloud container clusters get-credentials react-sample
```

## Deployment作成
最終的にPodを起動させるためにDeploymentを作成します。kubernetesを勉強し始めると序盤で疑問に感じると思いますが、kubernetesにはいくつかリソースがあります。Podを管理するリソースは階層構造になっており、DeploymentがReplicaSetを、ReplicaSetがPodを、という階層で管理されます。今回は、DeploymentのyamlファイルからPodを作成します。

- GCPのCloudShellから以下のyamlファイルを作成

```yaml:reactSample-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: react-sample
spec:
 replicas: 1
 template:
  metadata:
   labels:
    app: react-sample
  spec:
   containers:
    - image: <アカウント名>/<リポジトリ名>:latest
      name: sample
      ports:
      - containerPort: 80
        name: sample
```
- `kubectl apply -f reactSample-deployment.yml` でDeploymentを作成
- `kubectl get pods` でコンテナが起動しているか確認

```text
NAME                           READY     STATUS    RESTARTS   AGE
react-sample-7cb857cbcb-wpdg9   1/1       Running   0          13s
```
## Service作成
Serviceを作成することでエンドポイントが作られます。`type: LoadBalancer`とすることでGoogle Cloud Load Balancingが裏で作られるため、外部IPが振られ外からアクセスすることができます。

- GCPのCloudShellから以下のyamlファイルを作成

```yaml:reactSample-service.yaml
apiVersion: v1
kind: Service
metadata:
 name: react-sample
 labels:
  app: react-sample
spec:
 type: LoadBalancer
 ports:
 - port: 80
 selector:
  app: react-sample
```
- `kubectl apply -f reactSample-service.yml` でServiceを作成
- `kubectl get service` でserviceが作成されているか確認

## 動作確認
- 上記で確認したEXTERNAL-IPにアクセスし、サンプルアプリにアクセスできることを確認します。
- GCPのコンソールで「負荷分散」メニューを選択すると、裏で作成されたロードバランサーが表示されEXTERNAL-IPがロードバランサーのIPと同じであることを確認できます。

# おわりに
今回はアプリケーションを本番環境で動かすイメージのハンズオンでしたが、実際の開発現場では本番環境でコンテナを使わない場合があるので、個人のローカル開発環境構築に活かしていけたらと思っています。最後まで読んでいただきありがとうございました。