本記事の目的
docker-composeを使ってNode.jsアプリのインテグレーションテストを行う #2 ではテストに用いるREST APIサーバーアプリを作成しました。
今回はdocker-composeを用いて、Node.jsアプリとPostgreSQLをコンテナで立ち上げて通信を行う方法を紹介します。
次回は、この方法でデプロイしたアプリのインテグレーションテストを作成して動作確認を行っていきます。
今回の注目点
・TypescriptファイルをJavascriptファイルへトランスパイルする
・Node.jsアプリをDockerビルドするときにキャッシュを用いる
・docker-compose up
実行時に、毎回アプリのDockerfileをビルドする(変更後の最新のアプリのビルドのため)
ソースコードはここに置いています。
重要なファイルの階層
インテグレーションテストのために用意するファイルはすべてe2eフォルダに入れます
root---- dist ← アプリビルド後の実行ディレクトリ
|
| - e2e --- docker-compose.yml
| |
| |- Dockerfile-postgres
|
| - rest-api-server ← REST API サーバーアプリ
|
| - Dockerfile-app ← アプリビルド用Dockerfile
REST APIサーバのコンテナ作成
まず、Typescriptで作成したREST APIサーバアプリをJavascriptファイルにトランスパイルします。
その後、Dockerfileを書いてコンテナbuildを行います。
REST APIサーバアプリのコンパイル
まず、コンテナ作成に先立って Typescript ファイルを Javascript ファイルへトランスパイルしておきます。
前回までは ts-node
コマンドを用いて Typescript ファイルを直接実行していました。
しかし、この方法ではトランスパイルを行うため直接 Javascript ファイルを実行するよりも遅くなります。
コンテナは実環境での動作が求められるので、Javascript ファイルへトランスパイルしてすぐに実行できるようにしておきます。
以下のコマンドで、トランスパイルに必要な二つのモジュールをインストールします
npm i -D tsc @types/node
tsc はトランスパイル用のコマンドです。
@types/nodeはNode.js組み込みモジュールの型定義モジュールです。
これを用いることで、import でのモジュールの依存関係をtscが解決できるようになります。
以上の準備の下、トランスパイルを以下のコマンドで行います。
tsc ./[アプリの起動ファイル名].ts --outDir dist/
[アプリの起動ファイル名]は、そのアプリの根幹のファイル名です(僕の場合はrouter.tsという名前です)
--outDir オプションは、トランスパイル後のファイル群の置き場所です。
dockerコンテナビルドのために、npm script に上記のトランスパイルコマンドを書いておきます。
npm scriptはpackage.json内のscriptsという部分に登録します。
package.jsonのscripts内に、build という名前で上記のコマンドを登録すると、上記のコマンドは、ターミナルで以下のコマンドを打つことで実行できます。
npm run build
dockerfileではこのbuild コマンドを使って、トランスパイルを行います。
dockerfile作成
次に、このアプリのDockerfileを書きます。
# このコンテナのベースイメージです。今回はnodeのv10.16.0を使います。
FROM node:10.16.0
# このコンテナのユーザーを定義しています。
USER node
# 最初はnpm install を行うためのpackage.jsonだけをコンテナ内の/appにコピーします。
# こうすると、package.jsonに変更がなければ、npm installは次回からはキャッシュが使われます。
COPY --chown=node ./package.json /app/
# このコンテナの活動ディレクトリを指定しています。
WORKDIR /app
# package.jsonから依存関係を読み取って、アプリに必要なモジュールをインストールします
RUN npm install
# npm installが終わってからアプリのコードを/appにコピーしています。
COPY --chown=node . /app
# そして、前節でpackage.jsonに登録したbuildコマンドでトランスパイルを行っています
RUN npm run build
# build完了後に/dist内にアプリのjavascriptファイルがあるので、それをnodeコマンドで実行しています。
# javascriptファイルを、サーバーで起動するにはnodeコマンドを使います。
CMD ["node", "dist/[アプリの起動ファイル名].js"]
コンテナ群を立ち上げる
e2eフォルダ内にdocker-compose.ymlファイルを作成し、以下のように書きます。
# docker-composeのバージョン
version: "3"
# 立ち上げるコンテナをservices以下に構成する
services:
# postgresコンテナを立ち上げる設定を書きます
# IPアドレスを意識しなくても、docker-composeで立ち上げた他のコンテナはここで指定した名前でアクセスできます
postgres-e2e:
# e2eフォルダ内に置いたdockerfile-postgresをビルドするための命令です
build:
context: ./
dockerfile: Dockerfile-postgres
# ここでの名前は、コンテナ自体の名前で、ユーザーがターミナルでこのコンテナを指定する名前はここで決めます
container_name: postgres-e2e
# rest-api-serverを立ち上げます
rest-api-server-e2e:
# build は一つ上の階層にあるDockerfile-appを用いて行う設定を書いています
build:
context: ../
dockerfile: Dockerfile-app
container_name: rest-api-server-e2e
# ここでは、ホストからコンテナへのフォワーディングポートを書いています。
# この設定では、localhost:3000でアクセスすると、このコンテナのポート3000にアクセスできます
ports:
- 3000:3000
上のymlファイルを以下のコマンドで実行するとデータベースとアプリが立ち上がります。
docker-compose up -d --build
up はymlファイルを読んでコンテナ群を立ち上げる命令です
-d はバックグラウンドで実行するためのオプションです
--build はyml ファイル内のbuildを毎回実行するためのオプションです
--build が無いと、ymlファイル内のbuildが古いイメージがあると実行されません。
それによって、アプリの内容を更新したのにbuildされずに古いアプリが使われる問題が生じます。
これを解決するために、--buildをつけています。
毎回buildされると効率が悪くなりますが、上で書いたdockerfileでは時間のかかるnpm installを前回のキャッシュが使われるように書いているので、時間のかかる処理はビルドの部分だけになります。
このように、Dockerfileを書く時にはキャッシュを使えるように工夫することでより効率的にコンテナ技術を使うことができます
docker-composeを用いることによる実装の変更点
前回はホストマシンからDBコンテナにアクセスする際には、ホストとDBコンテナをポートフォワーディングで通信をしていました。
コードは以下のようになっていました。
public static connect$(): Observable<PoolClient> {
const pool = new Pool({
user: "postgres",
password: "postgres",
database: "postgres",
// DBにアクセスするために、localhostを指定していた
host: "localhost",
port: 5432,
});
今回は、docker-composeを用いてコンテナ同士はyml ファイルで指定したサービス名で名前解決できるので以下のように書き換える必要があります。
このように、docker-composeを用いるとアクセス先の名前が変わります
public static connect$(): Observable<PoolClient> {
const pool = new Pool({
user: "postgres",
password: "postgres",
database: "postgres",
// yml ファイルでDBコンテナの名前をpostgres-e2eにした!!
host: "postgres-e2e",
port: 5432,
});
アプリの起動
今までは、dockerfile-postgresを実行してDBを立ち上げ、次にアプリをts-nodeを用いて実行していました。
でも今は、docker-compose up -d --build
を実行するだけでアプリの起動が自動的に行われます。
以下のHTTPリクエストを実行するとDBにデータを挿入することができます。
POST localhost:3000/cars のbodyに{"name": "demio", "maker": "mazda"}
GET localhost:3000/cars/demio でデータの取得もできます
まとめ
今回はdocker-composeを用いて必要なアプリ群を立ち上げました。
インテグレーションテストを行う際にもこの方法でアプリ群を立ち上げます。
次回はテストフレームワークJestを用いてテストを書き、インテグレーションテストを作成します。