Node.jsをDockerで動かすときのPID 1問題について動作確認する

概要

Node.jsとDockerのPID1問題について動作確認する。

結論

Dockerでnodeプロセスを動かす場合

  • 直接動かすときはtiniやdocker run --initオプションを使う
  • npmを使って起動しない

確認

下記のようなファイルを用意する

$ls
Dockerfile  server.js

Dockerfile

FROM node
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

server.js

require("http")
  .createServer(function (request, response) {
    response.end("test");
  })
  .listen(3000);

イメージ作成

$docker build -t node-pid .

コンテナ起動

$docker run node-pid

起動確認

$docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
c446d9f1d8be        node-pid            "docker-entrypoint.s…"   8 seconds ago       Up 7 seconds                            inspiring_margulis

プロセスを止める

$docker kill -s TERM c446d9f1d8be

確認

$docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
c446d9f1d8be        node-pid            "docker-entrypoint.s…"   8 seconds ago       Up 7 seconds                            inspiring_marguli

すぐにはコンテナが停止せずに残っていることが確認できる。

対応

tiniを利用する

GitHub - krallin/tini: A tiny but valid `init` for containers

Dockerfileを下記のように変更する。

FROM node
COPY . .
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
EXPOSE 3000
ENTRYPOINT ["/tini", "--"]
CMD ["node", "server.js"]

ビルド

$docker build -t node-pid-tini .

起動

$docker run node-pid-tini

確認

$docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
fd19dacfb23b        node-pid-tini       "/tini -- node hello…"   5 seconds ago       Up 4 seconds        3000/tcp            stoic_galois
node-pid

プロセスを止める

$docker kill -s TERM fd19dacfb23b
fd19dacfb23b

確認

$docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

停止することが確認できた。

initオプションを利用する

Docker run reference | Docker Documentation

FROM node
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

起動

$docker run --init node-pid

確認

 $docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
4a01d8a4b1e9        node-pid            "docker-entrypoint.s…"   8 seconds ago       Up 7 seconds                            angry_yonath

プロセスを止める

$docker kill -s TERM 4a01d8a4b1e9

確認

$docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

停止することが確認できた。

参考

Docker で node.js を動かすときは PID 1 にしてはいけない - ngzmのブログ

docker-node/BestPractices.md at master · nodejs/docker-node · GitHub

Docker/Kubernetes で PID 1 問題を回避する | text․superbrothers․dev

CodeDeployのhook時に実行環境を判別する

自分用メモ

概要

CodeDeployのhook時に、現在prdなのかstgなのか判別したい。
ユースケースとしてはアクセスするS3バケット名を変えたいときなど。
アプリケーションリポジトリは一つなのでhook scriptの中で分岐させる必要がある 。

やりかた

下記ドキュメントのようにhooksでアクセスできる環境変数に実行環境名を指定する。 https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html#reference-appspec-file-structure-environment-variable-availability

下記のように環境ごとのプレフィクスのついたdeployment groupを作る。
stgのdeployment group...stg-hoge-group
prdのdeployment group...prd-hoge-group

application-repo/scripts/start.sh

if [[ "$DEPLOYMENT_GROUP_NAME" =~ ^prd ]]; then
#prdの場合の処理
fi

SPAアプリケーションのコンテナ化と設定値について

概要

コンパイルされたJavaScriptのSPAアプリケーションがある。
これはDockerコンテナ上で動いている。
このアプリはdev,stg,prdなどの環境ごとに異なるAPIエンドポイントを持つ。
この環境ごとに異なる設定値をどのように扱うか。

問題点

フロントエンドで実行されるJSファイルに設定値が含まれているので、コンテナ実行時に環境変数として渡すことができない。

結論

各方針のデメリットと現状の要件を照らし合わせ適切な方法を選択する。

方法一覧

案1

環境ごとにアプリケーションををビルドする。 実行時にどのバンドルを実行するか指定する。

デメリット

ビルドに時間がかかる。 イメージの容量が大きくなる。

下記にNext.jsの具体例を示す。

Dockerfile

FROM node:alpine as builder

WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci \
    && APP_ENV=development npm run build \
    && mv /usr/src/app/.next /usr/src/app/.next_development \
    && APP_ENV=staging npm run build \
    && mv /usr/src/app/.next /usr/src/app/.next_staging \
    && APP_ENV=production npm run build \
    && mv /usr/src/app/.next /usr/src/app/.next_production \
    && npm ci --production
COPY . /usr/src/app/

FROM node:alpine as runner
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/.next_development /usr/src/app/.next_development
COPY --from=builder /usr/src/app/.next_staging /usr/src/app/.next_staging
COPY --from=builder /usr/src/app/.next_production /usr/src/app/.next_production
COPY --from=builder /usr/src/app/node_modules /usr/src/app/node_modules
COPY package*.json ./
COPY start.sh ./

CMD ash -c "./start.sh -d && ./node_modules/.bin/next start"

start.sh

#!/bin/ash
ln -s /usr/src/app/.next_${APP_ENV}/ /usr/src/app/.next

案2

環境ごとの設定ファイルを作り、実行時にフロントエンドから取得する。
具体的なイメージは参考リンク参照。
参考:https://www.barrydobson.com/post/configure-spa-docker/

デメリット

ユーザが設定を取得するためのリクエストを送らなければならない。

案3

環境ごとにDockerイメージをビルドする。

これはアンチパターンなので採用しない。
理由

  • ある環境で保証されていたことが別の環境では保証されなくなる
  • 開発が進むに連れ環境ごとの差分が発生する

参考:https://codefresh.io/containers/docker-anti-patterns/

案4

コンテナ実行時にビルドする。

デメリット

  • Dockerのライフサイクルとアプリケーションのライフサイクルがことなる
  • コンテナ実行時にビルドが失敗してコンテナが起動できない可能性がある

ライフサイクルがことなることで他のアプリケーションとの違いを意識する必要がある。メトリクス計測に影響が出る。
ビルドに成功したDockerイメージのみをDockerレジストリに登録する。

案4.5

コンテナ実行時に再ビルドする

  • docker build時にnpm run build
  • docker run時にnpm build && npm start

デメリット

上記と同じくDockerのライフサイクルとアプリケーションのライフサイクルがことなる

Goの開発tips

自分用メモ。

go test ./... -coverprofile=coverage.out && go tool cover -html=coverage.out

ShellCheckでSC1090: Can't follow non-constant source. Use a directive to specify location.が出たときの対処法

概要

super linterをGitHub Actionsで使っていてShellCheckで下記エラーが出た。

SC1090: Can't follow non-constant source. Use a directive to specify location.

super linterバージョン
linter.yaml

        uses: docker://github/super-linter:v3

https://github.com/github/super-linter

ローカル検証時のバージョン

$shellcheck --version
ShellCheck - shell script analysis tool
version: 0.7.1
license: GNU General Public License, version 3
website: https://www.shellcheck.net

原因

https://github.com/koalaman/shellcheck/wiki/SC1090

ShellCheck is not able to include sourced files from paths that are determined at runtime. The file will not be read, potentially resulting in warnings about unassigned variables and similar.

ShellCheckは静的解析ツールなので動的に実行時に動的に決まるディレクトリを解析できない。

解決方法

方法1

代わりに読み込める固定のパスを書く。パスが読み込めなければエラーになるので注意。

# shellcheck source=src/util.sh
. "${util_path}"

方法2

無効にする。

# shellcheck source=/dev/null
"${util_path}"

方法3

無効にする。下記のように特定のルールをdisableすることも可。

#shellcheck disable=SC1090
"${util_path}"

参考

ShellCheckのインストール方法

brew install shellcheck

Gitで WARNING: UNPROTECTED PRIVATE KEY FILE!が出たときの対処法

概要

git cloneしようとしたら下記のようなエラーが出る。

$git version
git version 2.26.0
Cloning into 'hoge'...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/hoge/.ssh/hoge_id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/Users/hoge/.ssh/hoge_id_rsa": bad permissions
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

解決方法

グループとその他に対して実行以上(1以上)の権限がある場合に発生する。 今回の場合0644なので0600に変更すると解決する。

$chmod 600 /Users/hoge/.ssh/hoge_id_rsa

https://en.wikipedia.org/wiki/Chmod

機能組織について調べるために読んだ本まとめ

概要

機能組織と職能組織について調べたので文献をメモ。

まとめ

  • 機能組織は外部とのやり取りを減らし意思決定が早くなる
  • 職能組織は専門性を深める
  • 現代のWeb業界のソフトウェア開発だと機能組織がベター
  • 機能組織と職能組織をかけ合わせ相乗効果を狙う

『エンジニアリング組織論への招待 ~不確実性に向き合う思考と組織のリファクタリング

Chapter5技術組織の力学とアーキテクチャで組織構造とチーム間の「取引コスト」や職能横断組織について詳しく書かれている。

ドラッカー名著集14 マネジメント[中]』

45章で職能別組織とチーム型組織(機能組織)について語られている。

『マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

  • 作者:Sam Newman
  • 発売日: 2016/02/26
  • メディア: 単行本(ソフトカバー)
10章コンウェイの法則とシステム設計でフィーチャーチーム(機能組織)について語られている。

雑感

責任と権限について

例えば、あるアプリケーションを作る上で開発チームが、サーバ等のリソースをインフラ部に依頼して作ってもらうような場合、責任と権限が分離していると言える。 アプリケーションに対する責任があるのに、作業する権限がないという状態になる。
責任と権限の分離を防ぐために、アプリケーションを作ると決まったらそれに必要なスキルを持ったエンジニアをアサインしてチームを作る。

職能別組織の必要性について

Web業界はジョブ型雇用であるケースが多い。エンジニアとしてのキャリアパスを定める上で職能別組織が機能する。
また、専門知識が必要な仕事を集約・抽象化し、それを横展開するケースでも職能組織のほうが適している。 一人のエンジニアは一つ以上の機能組織と一つの職能組織に所属するようなイメージだと理解した。

正解は組織ごとにある

目的は事業を成功させること、仕事を正確に早く終わらせることであり、それらが達成されていれば形に拘る必要はない。
また、組織の課題は組織ごとの固有のものなので他社の事例や書籍の事例が100%適用されるわけではない。