minikubeを使ったnodejs開発環境構築

「DeNA IPプラットフォーム事業部 Advent Calendar 2017」15日目の記事です

# はじめに
GoogleKubernetesEngineなどKubernetes環境を採用している場合、ローカルの開発環境においてもKubernetes+minikubeを採用することがあるかと思います
今回はそのminikube上でのNode.js開発環境の構築手順とポイントをかんたんに解説します

※Mac前提で進めます、その他のOSをご利用の方ごめんなさい！🙇

# 必要なもの
- minikube
- kubectl
- docker
- nodejs(Mac上)

基本的なインストール手順はこちらを参考にしてください
https://kubernetes.io/docs/tutorials/stateless-application/hello-minikube/

Node.js（npm）を利用するのでインストールしてください
https://nodejs.org/ja/download/
※バイナリ以外の方法でも問題ありません

# 環境構築
## hyperkitドライバーでminikubeを起動する
hyperkitはvirtualboxなどと比べ動作が高速でした
※参考nodejs起動時間　virtualbox：3sec hyperkit:1sec

```bash
# hyperkitドライバーをインストール
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-hyperkit && chmod +x docker-machine-driver-hyperkit && sudo mv docker-machine-driver-hyperkit /usr/local/bin/ && sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit && sudo chmod u+s /usr/local/bin/docker-machine-driver-hyperkit
# minikubeを起動
$ minikube start --vm-driver=hyperkit --cpus 4 --memory 8192
# dockerのホストを設定する
$ eval $(minikube docker-env)
```
cpu,memoryは必要に応じて変えてください

## Dockerfile
```dockerfile

FROM node:8.6.0
ENV APP_ROOT /opt/node
WORKDIR $APP_ROOT
RUN npm install pm2 -g
EXPOSE 3000 3002
CMD ["npm", "run", "start"]
```
pm2をインストールしていますが目的は後ほど

### アプリケーションコード
npmスクリプトを使用するため以下のようなファイルにします

```package.json
{
  "name": "k8s-node-dev",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "pm2-docker start ./pm2.json"
  },
  "dependencies": {
  },
  "devDependencies": {
    "gulp": "^3.9.1"
  }
}
```

起動設定はpm2.jsonでおこないます

```pm2.json
{
    "apps": [
        {
            "name": "k8s-node-dev",
            "script": "./app.js",
            "node_args": "--inspect=0.0.0.0:3002"
        }
    ]
}
```

`node_args`にデバッグポートの設定をしています

## ソースコードNFSマウントする
mac上のソースコードをminikube上にマウントし、mac上で編集→kubernetes上で実行させます
`minikube mount`ではなくnfsを利用したほうが高速なため、nfsを使います

※参考 `minikube mount`の場合、nodeの起動に10秒以上要しました

```bash
$ cd project_dir
$ echo "$(pwd) -network 192.168.0.0 -mask 255.255.0.0 -alldirs -maproot=root:wheel" | sudo tee -a /etc/exports
$ sudo nfsd restart
$ minikube ssh -- sudo busybox mount -v -t nfs 192.168.99.1:$(pwd) /home/docker/shared_volumes/ -o rw,async,noatime,rsize=32768,wsize=32768,proto=tcp
```

## podを作成する
```k8s-node.yaml
apiVersion: v1
kind: Service
metadata:
  name: k8s-node-dev
spec:
  selector:
    app: k8s-node-dev
    role: app
    env: local
  ports:
  - protocol: TCP
    name: app
    port: 8081
    targetPort: 3000
    nodePort: 30082
  - name: debug
    port: 3002
    targetPort: 3002
    nodePort: 30089
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8s-node-dev
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: k8s-node-dev
        role: app
        env: local
    spec:
      containers:
      - name: k8s-node-dev
        image: "node-dev:v1"
        tty: true
        stdin: true
        ports:
        - containerPort: 3000
        - containerPort: 3002
        volumeMounts:
        - mountPath: "/opt/node"
          name: "node-src-volume"
      volumes:
      - name: "node-src-volume"
        hostPath:
          path: "/home/docker/shared_volumes/project_dir"
```

Dockerイメージをビルドしpodを起動します

```bash
$ docker build -f ./Dockerfile -t node-dev:v1 .
$ kubectl create -f ./k8s-node.yaml
``` 

動作の確認を行います

```bash
$ minikube service k8s-node-dev
```

ここまでで準備は完了です

# ファイルの変更を即時に反映する
nodejsは再起動しないと変更を反映させることができません
毎回手動で実行するのは辛いのでファイルの変更を監視し自動で再起動するようにしたいところ

通常、nodemonやnode-devなどで起動すればいいだけなのですが、今回の環境ではnfsマウント押している関係上ファイルの更新を検知することができません
（ポーリングで解決もできますがちょっと…

## mac上で監視を行う
pod上でだめなら大元のmac側で監視しましょう
今回はgulpを使います

### mac上でgulpをインストール
```bash
$ npm install -g gulp
$ npm install gulp
```

### 監視と再起動を行う

```gulpfile.js
let gulp = require('gulp');
let exec = require('child_process').exec;

gulp.task('watch', function(){
    gulp.watch('./**/*.*', ['reload']);
});
gulp.task('reload', function(){
    console.log('reload');
    exec('kubectl exec -it $(kubectl get pod | grep node  | awk -F" " \'{print $1}\') -- pm2 reload all', {shell: '/bin/bash'}, function(error, stdout, stderr) {
        console.log(stdout);
    });
});
```

```bash
$ gulp watch
```
#### 何をしているのか
1. `gulp.watch`でファイルを監視し、`exec`でbashに処理を渡す
2. `$(kubectl get pod | grep node  | awk -F" " \'{print $1}\')`でpodのIDを取得
3. `kubectl exec -it $ID -- $script`で$scriptを実行
4. `pm2 reload all`で再起動

pm2を採用したのはこのように外部から再起動を実行可能なためです

これで、スムーズに修正を反映する環境が整いました

# デバッグがしたい
すでにpm2.jsonにデバッグポート3002を指定し、k8s-node.yamlに3002にアクセスするnodePort:30089を開けているためかんたんにデバッグが可能です

## Chromeでデバッグ
1. `minikube service tama-bff-service  --format "{{.IP}}"`でIPを確認する
2. [chrome://inspect/#devices](chrome://inspect/#devices)を開く
3. Open dedecated ...をクリック
4. Connectionタブを開き、`${1で取得したIP}:30089`を登録する
6. 2の画面のRemote Targetにinspect対象が現れるので開く

これで、デバッグ可能になりました

# まとめ
minikubeを使ったnodejs環境構築ついてまとめました
NFSやgulpを組み合わせることで、ローカルでNode.jsを起動した場合と大差ないスムーズな開発が実現できました
また、Dockerfileやyamlに設定がまとまっているので他のメンバーへの導入も非常にかんたんにできます

現在エブリスタの開発において手探りで運用している環境なのでご質問・ご意見等ございましたらよろしくお願いします！

# 宣伝
エブリスタでは一緒に働くメンバーを熱烈に募集しています

- アーキテクチャ設計や言語選定から関わるエンジニアリングをしたい方
- 最新技術に触れたい方
- 小説やUGCに関わる仕事に興味がある方
- 出版業界・コンテンツ業界に変革を起こしたい方

などなど、「ちょっと興味ありそう」「もっと色々聞いてみたい」という方も
是非[こちら](https://www.wantedly.com/projects/96330?ql=gaJpZM4APJea)から応募頂くか、 @daponta まで気軽にご連絡ください！