バックエンド側 node.js AWS環境デプロイ(Docker化確認)

2023-11-06

パクリ22発目!

パクリ元

内容

以下一覧を完了させて ECS リリースさせることでバックエンド側を終わりとする。順序は適宜入れ替える。

  • 開発環境構築
  • プロジェクト作成・最適なフォルダ構成
  • 認証チェック(含めた前処理) 
  • ログ処理
  • APIによってはIPチェックやHeaderチェック
  • バリデーション
  • 環境ファイルによる振り分け
  • DB参照・更新
  • 外部API呼び出し
  • 外部API呼び出し待ち合わせ
  • 認証・認可
  • SPA における CSRF CORS
  • 長時間処理を非同期処理で
  • メール送信
  • 画像アップロード
  • 画像ダウンロード
  • EXCEL/PDF作成
  • テスト
  • (AWS環境デプロイ)    ←←← やっと
  • Docker 化確認
  • CodePipeline
  • RDS 対応
  • CloudFront

AWS環境へデプロイ(Docker 化確認)

いやーやっと最終工程まできたけどまだまだ作業目白押し。一つの項目の中にいろいろありすぎるんだよなー

ビルドされた js ファイルを Docker 化

AWS に上げてもいきなり動かないものだから、ローカルでコンテナが動くことを確認する。Docker 環境が整えてある wsl を立ち上げ、パクリ元1つ目を参考に Dockerfile 作成。

Docker環境:~/docker/node/Dockerfile
FROM node:16-alpine3.15 as builder

WORKDIR /app

ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini
RUN chmod +x /tini

COPY package.json yarn.lock ./
RUN yarn install --prod --frozen-lockfile

FROM gcr.io/distroless/nodejs:16
ENV NODE_ENV production

WORKDIR /app

COPY --from=builder --chown=nonroot:nonroot /tini /tini
COPY --from=builder --chown=nonroot:nonroot /app/node_modules ./node_modules
COPY --chown=nonroot:nonroot . .

USER nonroot
EXPOSE 3000

ENTRYPOINT [ "/tini", "--", "/nodejs/bin/node" ]
CMD ["/app/index.js"]

この中で yarn というのに気づく。パッケージ管理 npm と同じ階層のものらしいが、使ってないので npm に直す。パクリ元2つ目から npm ci –production で対応することに。合ってるか知らんけど。yarn.lock も package-lock.json に。さらに開発はポート3001で進めていたので合わせる。

Docker環境:~/docker/node/Dockerfile
・・・
#COPY package.json yarn.lock ./
COPY package.json package-lock.json ./
#RUN yarn install --prod --frozen-lockfile
RUN npm ci --production
・・・
#EXPOSE 3000
EXPOSE 3001
・・・

次に開発環境のファイルを Docker 環境に移動。

開発環境 Dir
nodetest
┣━ ・・・
┣━ dist
┃  ┗━ src
┃     ┗━ ・・・
┣━ ・・・
┣━ package-lock.json
┗━ package.json

上記 dist に出来た src 内の js、package.json と package-lock.json を Dockerfile と同じ場所に。

Docker環境 Dir
docker/node
┣━ controllers
┃  ┗━ ・・・.js
┣━ middleware
┃  ┗━ ・・・.js
┣━ models
┃  ┗━ ・・・.js
┣━ routes
┃  ┗━ ・・・.js
┣━ types
┃  ┗━ ・・・.js
┣━ validators
┃  ┗━ ・・・.js
┣━ Dockerfile
┣━ index.js
┣━ package-lock.json
┗━ package.json

ビルドして立ち上げる。

~/docker/node$ docker build -t node_container .
~/docker/node$ docker run -p 3001:3001 node_container
・・・
Error: Problem reading config from file "./config/log4js.json". Error was ENOENT: no such file or directory, open './config/log4js.json'
・・・

オーよく考えたら log4js の設定ファイルは src の外に置いたんだった。そりゃ dist に入らんわ。さらに dist/prisma には seed.js しか入ってない。migration てどうすんだ?テストもそうだ!とりあえず 開発環境:nodetest/config/log4js.json の中身を app.js の log4js.configure の引数として直書きして再度ビルドしてみた。

~/docker/node$ docker rmi node_container -f
~/docker/node$ docker builder prune
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y
・・・
~/docker/node$ docker build -t node_container .
~/docker/node$ docker run -p 3001:3001 node_container
・・・
Error: EACCES: permission denied, mkdir './log'
・・・

オー今度は権限の話か。コンテナ内にフォルダなんて作成することは通常しないから log4js の カテゴリ全てのアペンダーを console だけに変更して再度。

~/docker/node$ docker rmi node_container -f
~/docker/node$ docker builder prune
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y
・・・
~/docker/node$ docker build -t node_container .
~/docker/node$ docker run -p 3001:3001 node_container
environment:LOCAL / listening on port:3001!

エラーなく起動。

~$ curl localhost:3001/user/list
{"result":"failure","message":"authentication error"}

とりあえず動いた、、がテストとか考えると TypeScript のままで docker 化を考えた方が良さそう。

TypeScript 環境を Docker 化

ということでパクリ元3つ目を参考に。開発環境に Docker を入れるのが早いかもしれないけどとりあえず別々で。ポートは3001、js ファイルの作成場所は dist 、index.js は server.js に自分環境に合わせて、パクリ元2つ目により npm install は npm ci に修正。

Docker環境:~/docker/node/Dockerfile
##########################################################
#### ビルドステージ
FROM node:18.7.0-alpine3.15 as builder
WORKDIR /work

# ビルド用の依存パッケージをインストール
COPY package*.json ./
RUN npm ci

# TypeScript コードをコピーしてビルド
COPY src tsconfig.json ./
RUN npm run build

##########################################################
#### 実行用イメージの作成
FROM node:18.7.0-alpine3.15 as runner
WORKDIR /work

ENV NODE_ENV production
ENV PORT 3001
EXPOSE 3001

# 本番環境用のパッケージをインストール
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force

# builder からビルド結果だけコピー
COPY --from=builder /work/dist ./dist

# Node.js アプリを起動
CMD ["node", "./dist/server.js"]
Docker環境:~/docker/node/.dockerignore
node_modules
npm-debug.log
開発環境:~/nodetest/package.json
・・・
"scripts": {
    ・・・
    "build": "tsc"
  },
・・・

なるほどね、docker 化にもいくつかの段階に分けて対応すると。開発環境から dist / node_modeles 以外のフォルダを全コピーしてビルド。

~/docker/node$ docker image build -t myapp .
・・・
 => ERROR [builder 6/6] RUN npm run build                                                                                       ・・・
3.222 config/localStrategyWithSessionAuthenticate.ts(4,10): error TS2305: Module '"@prisma/client"' has no exported member 'User'.
3.224 config/localStrategyWithTokenAuthenticate.ts(9,10): error TS2305: Module '"@prisma/client"' has no exported member 'User'.
3.224 models/userModel.ts(1,32): error TS2305: Module '"@prisma/client"' has no exported member 'User'.
・・・

パクリ元4つ目5つ目を参考に Dockerfile 修正。builder 側にも runner 側にも追加。

Docker環境:~/docker/node/Dockerfile
・・・
+COPY prisma ./prisma
+RUN npx prisma generate
・・・
~/docker/node$ docker builder prune
~/docker/node$ docker image build -t myapp .
[+] Building 40.0s (17/17) FINISHED
~/docker/node$ docker container run --rm -p 3001:3001 --init --name myapp myapp
・・・
Error: Cannot find module '@aws-sdk/s3-request-presigner'
・・・

まだか。調査したら @aws-sdk は package.json の devDependencies 側にしかない。前回の記事みたら「npm install –save-dev @aws-sdk/s3-request-presigner」としっかり書いてある。@がついてるから間違えたんだな、、開発環境でインストールしなおして package.json、package-lock.json を再度 Docker 環境へ移動させてビルド。元記事も直しとこ。

~/nodetest$ npm uninstall --save-dev npm @aws-sdk/s3-request-presigner @aws-sdk/client-s3
~/nodetest$ npm install --save npm @aws-sdk/s3-request-presigner @aws-sdk/client-s3
~/docker/node$ docker rmi myapp
~/docker/node$ docker builder prune
~/docker/node$ docker image build -t myapp .
[+] Building 40.8s (17/17) FINISHED
~/docker/node$ docker container run --rm -p 3001:3001 --init --name myapp myapp
Warning: connect.session() MemoryStore is not
designed for a production environment, as it will leak
memory, and will not scale past a single process.
environment:production / listening on port:3001!

セッションストアを設定してないので警告でているけどOK。

テストを実行

テストも Dockerfile にねじ込んでみる。

Docker環境:~/docker/node/Dockerfile
#### テストステージ
FROM node:18.7.0-alpine3.15 as test

# ビルド用の依存パッケージをインストール
WORKDIR /work
COPY package*.json ./
RUN npm ci

# TypeScript コードをコピー
WORKDIR /work/src
COPY src ./

# TypeScript コードをコピー
WORKDIR /work
COPY tsconfig.json ./

# TypeScript テストコードをコピー
WORKDIR /work/__tests__
COPY __tests__ ./

# prisma
WORKDIR /work/prisma
COPY prisma ./
RUN npx prisma generate

WORKDIR /work
RUN npm run test
・・・

テストソースから本体ソース見てるからフォルダ構造を同じようにしないとダメだった。

 ~/docker/node$ docker image build -t myapp --target test .

テストがダメな場合はエラーが出て止まる。OK。

DB マイグレーションを実行

マイグレーションもやっておきたい。Docker で MySql を用意する。

Docker環境:~/docker/mysql/docker-compose.yml
version: '2'

services:
  mysql:
    image: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: pass
      MYSQL_DATABASE: db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_HOST: '%'
    restart: always
~/docker/mysql$ docker-compose up -d

schema.prisma に provider = “sqlite" とあるので provider = “mysql" としておき、DATABASE_URL を Dockerfile に環境変数として設定する(provider は env で渡せない?様子)。Dockerfile は以下。

Docker環境:~/docker/node/Dockerfile
##########################################################
#### マイグレーション用イメージの作成
FROM node:18.7.0-alpine3.15 as migration
WORKDIR /work

ENV NODE_ENV production
ENV PORT 3001
ENV DATABASE_URL "mysql://root:pass@localhost:3306/db"
EXPOSE 3001

# 本番環境用のパッケージをインストール
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force

# builder からビルド結果だけコピー
COPY --from=builder /work/dist ./dist

# prisma
COPY prisma ./prisma
RUN npx prisma generate

RUN npx prisma migrate dev --name init

でもどうしても Error: P1001: Can’t reach database server at mysql docker が出てprisma からつながらない。QA サイトあさって出てきた、DB も docker だから DATABASE_URL を mysql://root:pass@mysql(サービス名):3306/db とやっても全くダメ。ハマりまくり。こちらからDBコンテナが見えないようなので、Docker ネットワーク関係と当たりをつけ、ネットワークいろいろ作ったりしたが、こちらはコンテナではなくコンテナ作成中だからやはり見えない。その後 DB コンテナに固定IP を割り当てようとしたりどうにか見ようとしていたが、そのうちパクリ元6つ目を参考に、「ホスト側のネットワークを使ってビルド」するようにしたら見えるようになって少し進んだ(ホスト側からなので DBコンテナは localhost:3306 で見える)!今回のハマりポイント。実際には RDS 等の DB がみれればいいだけなんで気にする必要はないと思うけど、

~/docker/node$ docker image build -t myapp --target migration . --network host
 => ERROR [migration 8/8] RUN npx prisma migrate dev                                                              ・・・
1.409 The datasource provider `mysql` specified in your schema does not match the one specified in the migration_lock.toml, `sqlite`. Please remove your current migration directory and start a new migration history with prisma migrate dev. 
・・・

sqlite から mysql に変えたから migration_lock とか全部消せだと?そりゃそうだ。消して再度実行。

~/docker/node$ docker image build -t myapp --target migration . --network host
[+] Building 40.1s (18/18) FINISHED

おー。DB確認したらテーブルが作成されていた。やっと Docker 化確認が終わり。進みはまるで遅いが、テスト/ Typescriptビルド/コンテナ化/マイグレーション のタスクを作成できたので後は CI ツールから呼ぶだけでいいと思う。

雑感

環境側が入ってくることで使う筋肉が違ってくるので疲れる。お願いマッスル!