なになれ

IT系のことを記録していきます。

Docker Composeによる開発環境の構築(TypeScript,Node.js,Redis,Elasticsearch)

TypeScriptで作ってみるシリーズの作業ログです。
今回はDockerおよびでDocker ComposeでTypeScriptの開発環境を作ります。
TypeScriptによるNode.jsアプリの他にRedis、Elasticsearchを使います。

前回はこちらです。

hi1280.hatenablog.com

Node.jsアプリの作成

Expressを使ってWebAPI化します。

index.ts

import bodyParser from 'body-parser';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import helmet from 'helmet';
import { main } from './main';

const app = express();

app.use(compression());
app.use(helmet());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.get('/', async (_, res) => {
  const data = await main();
  return res.json(data);
});

const port = process.env.PORT || '3000';
app.listen(port, () => console.info(`API running on localhost:${port}`));

特に難しいことはしていないので、説明は省略します。

Redisのセットアップ

公式のDockerイメージを使います。

docker pull redis:5.0.3-alpine3.8
docker run --rm -d -p 6379:6379/tcp redis:5.0.3-alpine3.8

npmモジュールをインストールします。

npm install --save redis
npm install --save-dev @types/redis

Node.jsでRedisが使えるようになります。

redis.ts

import redis from 'redis';
const client = redis.createClient();

client.on('error', (err: any) => {
  console.log('Error ' + err);
});

client.set('string key', 'string val', redis.print);
client.hset('hash key', 'hashtest 1', 'some value', redis.print);
client.hset('hash key', 'hashtest 2', 'some other value', redis.print);
client.hkeys('hash key', (_, replies: any) => {
  console.log(replies.length + ' replies:');
  replies.forEach((reply: any, i: any) => {
    console.log('    ' + i + ': ' + reply);
  });
  client.quit();
});

Elasticsearchのセットアップ

ElasticsearchとKibanaを起動するため、Docker Composeを使います。

docker-compose.yml

version: '2'
services:
  elasticsearch:
    image: elasticsearch:6.5.3
    volumes:
        - elasticsearch-data:/usr/share/elasticsearch/data
    ports:
        - 9200:9200
    expose:
        - 9300
  kibana:
    image: kibana:6.5.3
    ports:
        - 5601:5601
volumes:
    elasticsearch-data:
        driver: local
docker-compose up

npmモジュールをインストールします。

npm install --save elasticsearch
npm install --save-dev @types/elasticsearch

Node.jsでElasticsearchが使えるようになります。

elasticsearch.ts

import elasticsearch from 'elasticsearch';
const client = new elasticsearch.Client({
  host: 'localhost:9200',
  log: 'trace',
});

client.ping(
  {
    requestTimeout: 30000,
  },
  function(error: any) {
    if (error) {
      console.error('elasticsearch cluster is down!');
    } else {
      console.log('All is well');
    }
  },
);

client.index({
  body: {
    text: 'Hello World!',
  },
  index: 'myindex',
  type: 'mytype',
});

client.search({
    body: {
      query: {
        match_all: {},
      },
    },
  })
  .then(
    function(resp: any) {
      const hits = resp.hits.hits;
    },
    function(err: any) {
      console.trace(err.message);
    },
  );

Node.jsアプリのDockerfile作成

TypeScriptによるNode.jsアプリのDockerfileを作成します。

Dockerfile

# Build
FROM node:10.14.2-alpine AS build-stage
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --silent
COPY . .
RUN npm run build
# Release
FROM node:10.14.2-alpine AS release-stage
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production --silent
COPY --from=build-stage /usr/src/app/dist /usr/src/app/dist
EXPOSE 3000
ENV NODE_ENV production
CMD [ "node", "dist/index.js" ]

Multi-Stage Buildを使って、TypeScriptからJavaScriptへのトランスパイル処理とNode.jsの実行を分けています。
これによって、Node.jsの実行には不要なTypeScript関係のモジュールを排除してDockerイメージのサイズを削減しています。

Docker Composeのセットアップ

Elasticsearch,Redis,Kibanaが起動するようにDocker Composeを構成します。

docker-compose.yml

version: '2'
services:
  elasticsearch:
    image: elasticsearch:6.5.3
    volumes:
        - elasticsearch-data:/usr/share/elasticsearch/data
    ports:
        - 9200:9200
    expose:
        - 9300
  kibana:
    image: kibana:6.5.3
    ports:
        - 5601:5601
  redis:
    image: redis:5.0.3-alpine3.8
    ports:
        - 6379:6379
  ts-example-api:
    build: .
    ports:
      - 3000:3000
    environment:
      - ES_HOST=elasticsearch
      - ES_PORT=9200
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    depends_on:
      - elasticsearch
      - kibana
      - redis
volumes:
    elasticsearch-data:
        driver: local

Node.jsアプリのソースはこちらです。
main.ts

import axios from 'axios';
import elasticsearch from 'elasticsearch';
import redis from 'redis';
import { promisify } from 'util';
import { Qiita } from './qiita';

const esclient = new elasticsearch.Client({
  host: `${process.env.ES_HOST}:${process.env.ES_PORT}`,
});

export async function main() {
  const client = redis.createClient({
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT!, 10),
  });
  const getAsync = promisify(client.get).bind(client);
  const items = await getAsync('items');
  let returned = null;
  if (items) {
    returned = JSON.parse(items);
  } else {
    const res = await get();
    client.set('items', JSON.stringify(res.data), 'EX', 1800);
    const body: Array<{}> = [];
    res.data.forEach(d => {
      body.push({
        index: {
          _id: d.id,
        },
      });
      body.push(d);
    });
    esclient.bulk({
      body,
      index: 'qiita',
      type: 'items',
    });
    returned = res.data;
  }
  client.quit();
  return returned;
}

export function get() {
  return axios.get<Qiita[]>('https://qiita.com/api/v2/items');
}

KibanaでもElasticsearchの内容が見れます。
f:id:hi1280:20181215235832p:plain

まとめ

ここまでの成果物

github.com

プログラム実行に複数のミドルウェアが関係する場合、Docker Composeでコンテナ環境を作ってしまうのが開発が楽になります。

参考

Redis

Elasticsearch

Docker