KubernetesとNode.jsでマイクロサービスを作成する 5/6 Dockerを使ったサービス構築

# 第5章 Dockerを使ったサービス構築
第4章までで、Twitterライクなマイクロサービスを構成する、3つのサービスを作成してきました。
次はいよいよ、これらのサービスをマイクロサービスとして構築するのですが、いきなりKubernetesを利用する前にワンクッションはさみます。

本章では、各サービスのDockerイメージを作成し、以下のような構成で、ローカルのDocker環境でTwitterライクなサービスを起動します。

![microservice-sample-docker.png](https://qiita-image-store.s3.amazonaws.com/0/214526/d015e874-d3fc-d7b9-6134-7f1562f11d39.png)


完成版のリポジトリはこちらにあるので、実装がうまくいかない場合は比較してみてください。
[reireias/microservice-sample-integration](https://github.com/reireias/microservice-sample-integration)

## チュートリアル全体
- [第1章 概要](https://qiita.com/reireias/items/6a78d6b8df781a76f94f)
- [第2章 Tweetサービス](https://qiita.com/reireias/items/82cfbb285130caf3e091)
- [第3章 Userサービス](https://qiita.com/reireias/items/27e21896b701360bc152)
- [第4章 Webサービス](https://qiita.com/reireias/items/204c080cd5ec1bf432fd)
- [第5章 Dockerを使ったサービス構築](https://qiita.com/reireias/items/efefe718d74ed8447931)
- [第6章 Kubernetes with minikube](https://qiita.com/reireias/items/084888abb3a757c0c5e2)

## 構成
![microservice-tutorial01.png](https://qiita-image-store.s3.amazonaws.com/0/214526/36201231-6030-9ac3-03ad-aaa99619eed2.png)

## dockerとdocker-compose
まずはローカルに`docker`と`docker-compose`がインストールされていることを確認しましょう。

```sh
docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.4
 Git commit:        e68fc7a
 Built:             Fri Jan 25 14:33:54 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       e68fc7a
  Built:            Thu Jan 24 10:56:33 2019
  OS/Arch:          linux/amd64
  Experimental:     false
```

```sh
docker-compose version
docker-compose version 1.23.2, build 1110ad0
docker-py version: 3.7.0
CPython version: 3.7.2
OpenSSL version: OpenSSL 1.0.2g  1 Mar 2016
```

## microservice-sample-integrationリポジトリの作成
ここからは、各サービスのリポジトリ内のコードを利用して、DockerイメージやKubernetesの設定を作成していくことになります。
そのためには、各サービスのコードが参照できる構成が望ましいです。

今回は下記のディレクトリ構成をとるような、`microservice-sample-integration`リポジトリを作成します。

```
microservice-sample-integration
└── services
    ├── microservice-sample-tweet
    ├── microservice-sample-user
    └── microservice-sample-web
```

実際の開発現場では、このリポジトリの管理をSREチームが主導して行ったり、各サービスの開発者が協力して行ったりします。

`microservice-sample-integration`という名前でリポジトリを作成し、`clone`しておきましょう。

つづいて、このリポジトリ内で行う各種操作を`Makefile`に記述し、`make`コマンドから実行できるようにします。
作成するコマンドは下記の通りです。

- `clone`: 各サービスを`services`ディレクトリ以下に`clone`する
- `pull`: 各サービスを`git pull`する
- `build`: 各サービスのDockerイメージのビルドを行う
- `up`: 全サービスを`docker-compose`を使って、ローカルのDocker環境で立ち上げる
- `down`: 全サービスを`docker-compose`を使って削除する
- `seed`: UserサービスとTweetサービスで`scripts/initialize.js`を実行する

`Makefile`は以下のようになります。

```make:Makefile
WEB_REPOSITORY := https://github.com/reireias/microservice-sample-web
USER_REPOSITORY := https://github.com/reireias/microservice-sample-user
TWEET_RESPOSITORY := https://github.com/reireias/microservice-sample-tweet

clone:
	git clone $(WEB_REPOSITORY) ./services/microservice-sample-web
	git clone $(USER_REPOSITORY) ./services/microservice-sample-user
	git clone $(TWEET_RESPOSITORY) ./services/microservice-sample-tweet

pull:
	cd ./services/microservice-sample-web && git pull
	cd ./services/microservice-sample-user && git pull
	cd ./services/microservice-sample-tweet && git pull

build:
	docker-compose build

up:
	docker-compose up -d

down:
	docker-compose down

seed:
	docker-compose exec user node /app/scripts/initialize.js
	docker-compose exec tweet node /app/scripts/initialize.js
```

`Makefile`が作成できたら、`make pull`を実行し、各サービスを`services`ディレクトリ配下へ`clone`します。

```sh
make clone
```

## Dockerイメージの作成
つづいて、各サービスのDockerイメージを作成するための、Dockerファイルを実装していきます。

このファイルから生成されるDockerイメージは本番環境でも使用されます。
実際の開発現場では各サービスが責任を持って作成する場合もありますし、SREチームなど、Dockerのノウハウに長けたチームがサービス横断で管理するケースもあるでしょう。

```docker:services/microservice-sample-tweet/Dockerfile
FROM node:11.10-alpine

ENV NODE_ENV=production

WORKDIR /app
RUN apk add --no-cache curl && \
    curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

COPY package.json .
RUN yarn install && ./bin/node-prune
COPY . .
EXPOSE 3000
CMD /wait && yarn start
```

```docker:services/microservice-sample-user/Dockerfile
FROM node:11.10-alpine

ENV NODE_ENV=production

WORKDIR /app
RUN apk add --no-cache curl && \
    curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

COPY package.json .
RUN yarn install && ./bin/node-prune
COPY . .
EXPOSE 3000
CMD /wait && yarn start
```

```docker:services/microservice-sample-web/Dockerfile
FROM node:11.10-alpine as builder

ENV NODE_ENV=production
ARG GITHUB_CLIENT_ID
ARG GITHUB_CLIENT_SECRET
WORKDIR /app

RUN apk add --no-cache curl && \
    curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh

COPY package.json .
COPY yarn.lock .
RUN yarn install && ./bin/node-prune
COPY . .
RUN yarn build

FROM node:11.10-alpine

WORKDIR /app
ADD package.json ./
ADD nuxt.config.js ./

COPY --from=builder ./app/server ./server
COPY --from=builder ./app/node_modules ./node_modules
COPY --from=builder ./app/.nuxt ./.nuxt

EXPOSE 3000
CMD yarn start
```

Webサービスに関してはマルチステージビルドを利用して最終的なイメージのサイズを軽くするように工夫しています。

実装後、各`Dockerfile`は`commit`しておきましょう。

## docker-compose.ymlの作成
上記で用意した`Dockerfile`を、`docker`コマンドを利用してビルド、コンテナの起動等やってもよいのですが、環境変数等、引数が多くなってしまうため、今回は`docker-compose`を利用して一元管理します。

`docker-compose.yml`に起動したいコンテナの設定を書いていくことで、一括で立ち上げたり、削除することが可能です。

今回は、Webサービス、Userサービス、Tweetサービスと、Userサービス用DB、Tweetサービス用DBの合計5つのコンテナを定義します。

```yml:docker-compose.yml
---
version: '3'
services:
  web:
    build:
      context: ./services/microservice-sample-web
      dockerfile: Dockerfile
    image: microservice_web:1.0
    ports:
      - 3000:3000
    environment:
      NUXT_HOST: 0.0.0.0
      USER_SERVICE: http://user:3000
      TWEET_SERVICE: http://tweet:3000
      GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
      GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
  user:
    build:
      context: ./services/microservice-sample-user
      dockerfile: Dockerfile
    image: microservice_user:1.0
    environment:
      MONGODB_URL: mongodb://user-db:27017/user
      MONGODB_ADMIN_NAME: root
      MONGODB_ADMIN_PASS: example
      WAIT_HOSTS: user-db:27017
    depends_on:
      - user-db
  user-db:
    image: mongo
    volumes:
      - user-db:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  tweet:
    build:
      context: ./services/microservice-sample-tweet
      dockerfile: Dockerfile
    image: microservice_tweet:1.0
    environment:
      MONGODB_URL: mongodb://tweet-db:27017/user
      MONGODB_ADMIN_NAME: root
      MONGODB_ADMIN_PASS: example
      WAIT_HOSTS: tweet-db:27017
      USER_SERVICE: http://user:3000
    depends_on:
      - tweet-db
  tweet-db:
    image: mongo
    volumes:
      - tweet-db:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
volumes:
  user-db:
  tweet-db:
```

GitHubのOAuthのキー等はGit管理する対象ではないので、`.env`ファイルに記述します。
docker-composeの機能により、カレントディレクトリの`.env`ファイルが読み込まれ、`docker-compose.yml`中で利用することができるようになります。
値はDocker環境の場合エンドポイントが`localhost:3000`と、前章までと同じなため、前章で使用していたのと同じGitHubのOAuth設定が利用可能です。

```:.env
GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxx
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

`services/.gitkeep`ファイルを作成しておきます。

```sh
touch services/.gitkeep
```

`.gitignore`ファイルを以下のように設定します。

```:.gitignore
services/*
!services/.gitkeep
.env
```

## サービスを起動してみる
それでは、サービスを起動してみます。

```sh
# Dockerイメージをビルド
make build
# サービスの立ち上げ
make up
# ダミーデータの投入
make seed
```

`http://localhost:3000`へアクセスしてみましょう。
問題なく動作していれば本章の実装は完了になります。

## 第5章まとめ
本章では、各サービスの`Dockerfile`を作成し、`docker-compose`を利用してマイクロサービスをローカルのDocker環境上に構築しました。

次の章ではいよいよKubernetes上でマイクロサービスを動作させてみます。

次章: [第6章 Kubernetes with minikube](https://qiita.com/reireias/items/084888abb3a757c0c5e2)