【Docker】訪問者カウンター作成を通してDocker Composeを学習

Dockerについて記事を書くのは3回目です。

https://qiita.com/suzuki0430/items/b2527cfba7602f9fe3bc

https://qiita.com/suzuki0430/items/fcf57968365d15419601

今回はDocker Composeを使い、Node.jsとRedisからなる簡単な"訪問者カウンター"を作ってみました。
![スクリーンショット 2021-03-07 10.47.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/ff4a70f1-c0ed-89d1-4fa2-693cb8ec88c5.png)

#Docker Composeって何?
複数のコンテナからなるシステムを簡単に構築するためのツールです。YAMLファイルを使ってアプリケーションサービスの設定を行い、コマンドを１つ実行するだけで、設定内容に基づいたアプリケーションサービスの生成・起動を行うことができます。

#システム構築

##Docker Composeを使わずに構築トライ
`package.json`と`index.js`と`Dockerfile`を作成します。

```package.json
{
  "dependencies": {
    "express": "*",
    "redis": "2.8.0"
  },
  "scripts": {
    "start": "node index.js"
  }
}
```
`index.js`のapp.getの中で、redisへの訪問者数(visits)の保存とカウントを行います。

```index.js
const express = require('express');
const redis = require('redis');

const app = express();
const client = redis.createClient();
client.set('visits', 0);

app.get('/', (req, res) => {
  client.get('visits', (err, visits) => {
    res.send('Number of visits is ' + visits);
    client.set('visits', parseInt(visits) + 1);
  });
});

app.listen(8081, () => {
  console.log('Listening on port 8081');
});
```
また、`Dockerfile`では、ベースイメージを`node:alpine`とし、ファイルのコピー先を'/app'と指定しています。

```dockerfile
FROM node:alpine

WORKDIR '/app'

COPY package.json .
RUN npm install
COPY . .

CMD ["npm", "start"]
```

`docker run redis`でredisのコンテナを起動した後に、ターミナルの別タブを開いて、作成したDockerfileからイメージのビルド(`docker build -t suzuki0430/visits:latest .`)とコンテナ起動(`docker run suzuki0430/visits`)を行います。

すると、redis-serverとの接続ができないというエラーがでます。

```
Error: getaddrinfo ENOTFOUND redis-server
```
コンテナプロセスは独立して動いているので、各コンテナを接続したい場合はそれに関する設定を行う必要があります。
**ここで使用するのがDocker Composeです。**

##Docker Composeを使って構築トライ
複数のDockerコンテナを同時起動するために、`docker-compose.yml`ファイルを以下のように記述します。

```docker-compose.yml
version: '3' #docker-composeのバージョン
services: #起動したいコンテナの種類
  redis-server:
    image: 'redis' #redisイメージでコンテナを起動
  node-app:
    build: . #カレントディレクトリのDockerfileからイメージをビルドしてコンテナを起動
    ports: #ポートマッピング([localhostのポート番号]:[コンテナのポート番号])
      - '4001:8081'
```
また、`redis-server`と`node-app`の接続を行うために、`index.js`に以下を追記します。6379はredisのデフォルトポート番号です。

```js
const client = redis.createClient({
  host: 'redis-server',
  port: 6379,
});
```
Docker Composeはdocker-compose CLIのコマンドで操作します。

- `docker-compose up` : コンテナ起動
- `docker-compose up --build` : イメージビルド + コンテナ起動
- `docker-compose up -d` : バックグラウンでコンテナ起動
- `docker-compose down` : コンテナ停止
- `docker-compose ps` : プロセス確認

`docker-compose up`を実行してみると、`node-app`と`redis-server`のコンテナが起動します。

```
(base) [16:12:32] → docker-compose up                                                                                                             ~/Programs/docker/visits
Recreating visits_node-app_1   ... done
Starting visits_redis-server_1 ... done
Attaching to visits_redis-server_1, visits_node-app_1
redis-server_1  | 1:C 06 Mar 2021 07:12:39.624 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis-server_1  | 1:C 06 Mar 2021 07:12:39.624 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1, just started
redis-server_1  | 1:C 06 Mar 2021 07:12:39.624 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis-server_1  | 1:M 06 Mar 2021 07:12:39.626 * Running mode=standalone, port=6379.
redis-server_1  | 1:M 06 Mar 2021 07:12:39.626 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis-server_1  | 1:M 06 Mar 2021 07:12:39.626 # Server initialized
redis-server_1  | 1:M 06 Mar 2021 07:12:39.627 * Loading RDB produced by version 6.0.9
redis-server_1  | 1:M 06 Mar 2021 07:12:39.627 * RDB age 10216869 seconds
redis-server_1  | 1:M 06 Mar 2021 07:12:39.627 * RDB memory usage when created 0.79 Mb
redis-server_1  | 1:M 06 Mar 2021 07:12:39.627 * DB loaded from disk: 0.001 seconds
redis-server_1  | 1:M 06 Mar 2021 07:12:39.627 * Ready to accept connections
node-app_1      |
node-app_1      | > start
node-app_1      | > node index.js
node-app_1      |
node-app_1      | Listening on port 8081
```
以下の部分で、コンテナ同士の接続も確認することができます。

```
Attaching to visits_redis-server_1, visits_node-app_1
```
ブラウザで`localhost:4001`に接続すると、ちゃんと開けました。

![スクリーンショット 2021-03-07 10.51.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/62e730d7-acfd-565d-e95d-8369404da69e.png)

#Docker Composeの自動再起動
システムの構築は完了したのですが、Docker Composeの理解を深めるために自動再起動の方法についても学びました。

`index.js`のapp.getで`process.exit(0)`を記述し、`node-app`のプロセスを終了するようにします。

```js
const process = require('process');

app.get('/', (req, res) => {
  process.exit(0);
});
```
次に`docker-compose.yml`に`restart: always`を加えます。

```docker-compose.yml
version: '3'
services:
  redis-server:
    image: 'redis'
  node-app:
    restart: always
    build: .
    ports:
      - '4001:8081'
```

`docker-compose up --build`でイメージビルドとコンテナ起動を再実行して`localhost:4001`にアクセスすると、プロセス終了後にコンテナを再起動しようとします。

```
node-app_1      |
node-app_1      | > start
node-app_1      | > node index.js
node-app_1      |
node-app_1      | Listening on port 8081
visits_node-app_1 exited with code 0
node-app_1      |
node-app_1      | > start
node-app_1      | > node index.js
node-app_1      |
node-app_1      | Listening on port 8081
visits_node-app_1 exited with code 0
```

`restart: always`以外にも以下のような再起動ポリシーがあります。

- "no": 再起動しない
- always: コンテナが停止したらいかなる理由でも再起動する
- on-failure: コンテナがエラーコードをだして停止したら再起動する
- unless-stopped: ユーザがコンテナを強制終了しない限り常に再起動する

#おわりに
次はDockerコンテナの本番環境へのデプロイについて学習します。

#参考資料

https://www.udemy.com/course/docker-and-kubernetes-the-complete-guide/