test(ci): コンテナイメージのビルドと /health 起動確認を CI に追加する - #155
Merged
Conversation
build-and-test は SDK 上で直接ビルド/テストするだけで Dockerfile を一切
評価しないため、ベースイメージの破損が CI 緑のまま素通りしていた。
具体的な検知漏れ:
アプリは net8.0 を対象にビルドされ、runtimeconfig.json は
Microsoft.AspNetCore.App 8.0.0 を要求する。rollForward は既定の "Minor" で
メジャー跨ぎのロールフォワードをしないため、ランタイム段を aspnet:9.0 へ
上げると 8.0 ランタイムが存在せずコンテナは起動時に落ちる。しかし
dotnet build / dotnet test はホストの SDK 8 で走るので緑のままになる。
対策として docker-image ジョブを追加し、イメージを実際にビルドして起動し
/health が 200 を返すところまでを検証する。これによりベースイメージと
アプリの TFM のズレを機械的に検出できる。
検証内容 (ローカルで実機確認済み):
- Production + プレーン HTTP で /health が 200 を返すことを確認。
UseHttpsRedirection は HTTPS ポート未構成時にリダイレクトせず素通しする
("Failed to determine the https port for redirect" を確認済み)。
- Audit__HashSalt 未設定だと Production 起動が fail-closed で失敗することを
確認したため、スモークテスト専用の使い捨て値を渡している。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018nkWELTPsYuGxyayheyhPG
新設した docker-image ジョブが、main の時点で既にコンテナイメージを
ビルドできない状態だったことを検出した。
原因:
.csproj の CompileTypeScript ターゲットが publish 中に `npm ci` と
`npx --no-install tsc` を実行するが、mcr.microsoft.com/dotnet/sdk:8.0 には
Node/npm が含まれない。このため publish が次のエラーで失敗する。
/usr/bin/sh: npm: not found
error MSB3073: The command "npm ci" exited with code 127.
CI が Dockerfile を一度もビルドしていなかったため、この破損は今まで
検知されずに残っていた。
対応:
- build ステージへ公式 node イメージから Node/npm/npx をコピーする。
apt ではなくイメージからコピーするのは、バージョンをタグで固定でき
再現性が高いため。sdk:8.0 は Debian 12 (bookworm) ベースなので
glibc を揃えるため node も bookworm 系を使う。
- package.json / package-lock.json だけ先にコピーして `npm ci` を実行し、
レイヤキャッシュを効かせる。これにより publish 内の CompileTypeScript は
Condition="!Exists(node_modules)" により npm ci をスキップする。
- .dockerignore に node_modules を追加する。除外しないと後続の `COPY . .` が
ホスト側 (別 OS/アーキテクチャの可能性がある) のバイナリで上書きしてしまう。
SkipTsBuild=true での回避は採らない。wwwroot/js/*.js は tsc の生成物で
Git 管理外のため、スキップすると JS を一切含まないイメージが出来上がり、
ダッシュボード/分析のグラフが動かなくなる。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018nkWELTPsYuGxyayheyhPG
コードレビューの指摘に対応する。 1. packages.lock.json をイメージのビルド前にコピーし --locked-mode で restore する これまで sln/csproj しかコピーしていなかったため、RestorePackagesWithLockFile=true の下で NuGet はロックファイルを「検証」せず「新規生成」していた。結果として CI の `dotnet restore --locked-mode` が守っている依存の固定が、実際に配布される イメージでは効かず、ロック更新漏れの PR でも docker build だけ通る状態だった。 2. .dockerignore に var/backups/ を追加する scripts/backup-db.sh の出力先で PHI を含む pg_dump が置かれる (.gitignore でも 除外済み)。除外しないと `COPY . .` が患者データを build ステージのレイヤに 焼き込む。最終イメージには残らないが、レイヤキャッシュや --cache-to / --target build のエクスポート経由で外部に出得るため、コンテキストに載せない。 3. TypeScript 生成物がイメージに含まれることを検証する手順を追加する /health は DB 接続しか見ないため、JS が 1 つも無いイメージでも 200 を返す。 つまり本 PR の Dockerfile 修正が守ろうとしている退行を、スモークテスト自身が 検出できなかった。publish 出力の wwwroot/js/*.js を直接確認するようにした。 4. コンテナが起動待ちの途中で終了したら即座に打ち切る ランタイム不一致で起動直後に落ちるケース(このジョブが主に狙う退行)で、 60 秒待って「応答なし」と報告するのは原因から遠い。異常終了を検出して exit code を報告する。 5. ログ出力ステップをコンテナ生成後に限定する `if: failure()` だけだとイメージビルド失敗時にも走り、"No such container" で 二重に赤くなって本当の失敗理由が埋もれる (実際に初回実行で発生した)。 6. CI の Node を 22 に上げて Dockerfile と揃える 同じ CompileTypeScript ターゲットを CI は Node 20、イメージは Node 22 で 動かしており、「CI は緑だがイメージでは失敗する」食い違いが起き得た。 7. README / docs/deployment.md の docker run 例に Audit__HashSalt を追記する Dockerfile は ASPNETCORE_ENVIRONMENT=Production を固定しており、この変数が 無いと fail-closed で起動に失敗する。ドキュメント通りに実行すると必ず落ちる 状態だった。 8. Dockerfile のコメントから過剰な再現性の主張を削る `node:22-bookworm-slim` は 22.x のパッチ更新で中身が変わる可動タグであり、 「バージョンがタグで固定される」という記述は誤り。 検証: dotnet build (0 warning) / dotnet test 374 passed / npm run typecheck / `dotnet restore --locked-mode` 成功 / publish 出力に wwwroot/js の 3 ファイルを確認。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018nkWELTPsYuGxyayheyhPG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
コードレビューの過程で、CI に Dockerfile を評価する経路が一切ないことが判明した。
build-and-testジョブはホストの SDK 上でdotnet build/dotnet testを実行するだけで、Dockerfileはビルドすらされない。このため、ベースイメージを壊す変更が CI 緑のまま素通りする。
実際に素通りするケース(現在 open な Dependabot PR #142)
net8.0を対象にビルドされ、生成されるruntimeconfig.jsonはMicrosoft.AspNetCore.App8.0.0 を要求する(rollForwardは未設定=既定のMinor)。Minorはメジャー跨ぎのロールフォワードをしないため、ランタイム段をaspnet:8.0→aspnet:9.0に上げると 8.0 ランタイムが存在せず、コンテナは起動時に落ちる。dotnet build/dotnet testはホストの SDK 8 で走るので緑のまま。変更内容
.github/workflows/ci.ymlにdocker-imageジョブを 1 本追加する。既存のbuild-and-testは変更しない。docker buildでイメージを実際にビルドする/healthが 200 を返すまで最大 60 秒ポーリングするdocker logsを出力する(起動失敗の原因は stdout に出るため)これによりベースイメージとアプリの TFM のズレを機械的に検出できる。
検証(ローカルで実機確認済み)
Docker デーモンが使えない環境のため、コンテナ内と同じ構成をホスト上で再現してスモークテストの前提を 2 点とも実測で確認した。
dotnet builddotnet testnpm run typecheckci.ymlの YAML パースbuild-and-test,docker-image)前提1: Production かつプレーン HTTP で
/healthが 200 を返すかUseHttpsRedirectionはMapHealthChecksより前に実行されるため 307 リダイレクトを疑ったが、HTTPS ポート未構成時はリダイレクトせず素通しすることを実測で確認した。コンテナは
ASPNETCORE_URLS=http://+:8080のみで HTTPS エンドポイントを持たないため、同じ経路になる。前提2:
Audit__HashSaltが本当に必要か未設定で起動すると fail-closed で失敗することを確認した。そのためスモークテスト専用の使い捨て値を渡している(本番の秘密情報ではない)。
期待される効果
mainに対しては PASS するaspnet8.0→9.0)に対しては FAIL し、マージ前に破損を止められる補足: 関連する Dependabot PR の調査結果
本 PR の範囲外だが、同じレビューで以下を実測した(いずれも本 PR では変更しない)。
TypeLoadException: Method 'get_LockReleaseBehavior' ... does not have an implementationdocker-imageジョブが検出する対象#144 / #146 は既存の
dotnet testが検知できるため CI は赤くなる。#142 だけが検知できていなかった。🤖 Generated with Claude Code
https://claude.ai/code/session_018nkWELTPsYuGxyayheyhPG
Generated by Claude Code