この投稿はでも表示されます。
はじめに:脆弱性スキャンの「形骸化」という構造的課題
コンテナ基盤の運用において、セキュリティ品質の向上を目的に脆弱性スキャンを導入するケースは一般的です。しかし、運用を続けていく中で、多くのチームが「スキャン結果のノイズによる運用負荷の増大」という課題に直面します。
標準的な ubuntu や debian などをベースイメージに使用している場合、アプリケーションのコードとは無関係なOSパッケージ(curl、git、古いシステムライブラリ等)に起因する脆弱性が大量に検知されます。その結果、以下のような負のサイクルが発生しがちです。
- トリアージの限界: 検知数が膨大(時に100件超)になり、真に対応すべきアプリケーション層の脆弱性が埋没する。
- 判断の遅延: 実行環境に不要なツールであっても、脆弱性として検知される以上、無視してよいかの判断に時間を要する。
- 形骸化: 恒常的に大量の警告が出る状態に慣れてしまい、セキュリティアラートに対する感度が低下する。
本記事では、このモグラ叩きのような運用から脱却し、「攻撃面(Attack Surface)を物理的に最小化する」というアプローチについてご紹介します。
具体的には、Distroless image の採用による実行環境の純粋化と、Trivy によるガードレール構築を組み合わせた、持続可能なコンテナセキュリティの実践手法を紹介します。
コンテナの脆弱性スキャン
コンテナは「一度ビルドすればどこでも動く」というポータビリティが魅力ですが、それは同時に「ビルドした瞬間の脆弱性も一緒にパッケージングされ、固定される」ことを意味します。
従来のサーバー運用であれば yum update 等で定期的にパッチを当てられますが、コンテナはイメージを更新して再デプロイしない限り、脆弱性は残り続けます。
- サプライチェーン攻撃の防止: 依存ライブラリに悪意あるコードが混入するリスクへの対策。
- ランタイムのリスク低減: シェルや不要なバイナリが残っていると、万が一アプリケーションが突破された際に、攻撃者に「自由な操作(シェル操作)」を許す踏み台になる
- コンプライアンス: 昨今のシステム開発において、OSレベルの脆弱性放置は監査上のリスク
スキャンの実行タイミング
「どこでスキャンをかけるか」は、運用の堅牢性と開発スピードのトレードオフになります。一般的には、以下の3つのタイミングでチェックを行います。
| タイミング | 実行場所 | 目的 | ツール例 |
|---|---|---|---|
| 開発時 | ローカル環境 | 開発者がコードを書いている最中に早期発見する。 | Trivy, Snyk, IDEプラグイン |
| ビルド時 | CIパイプライン | 【今回メイン】 本番用イメージが作られる際に強制的にチェックし、汚染されたイメージの作成を防ぐ。 | Trivy, Grype |
| デプロイ後 | レジストリ / 稼働環境 | デプロイ後に発見された新しい脆弱性(ゼロデイ)を検知する。 | ECR Inspector, Aqua, Datadog |
今回、私たちが特に注力したのは CI(GitHub Actions等)でのスキャン です。 理由は、「脆弱なイメージをレジストリにプッシュさせない(=汚染を未然に防ぐ)ガードレール」として機能するポイントとなります。
スキャンツールの選定
コンテナスキャンの分野には多くのツールが存在しますが、私たちは「運用負荷の低さ」と「検知範囲の広さ」のバランスを重視し、Trivy を選定しました。主要なツールとの比較は以下の通りです。
| 比較項目 | Trivy | Snyk (Free/Open Source) | Grype | Docker Scout |
|---|---|---|---|---|
| 開発元 | Aqua Security | Snyk | Anchore | Docker |
| ライセンス | Apache-2.0 (OSS) | 商用 (無料枠あり) | Apache-2.0 (OSS) | 商用 (無料枠あり) |
| 検知対象 | OS, 言語ライブラリ, IaC, 設定 | OS, 言語ライブラリ, IaC | OS, 言語ライブラリ | OS, 言語ライブラリ |
| 導入の容易さ | ◎ (バイナリ1つ) | △ (要アカウント作成) | ◎ (バイナリ1つ) | ○ (Docker Desktop統合) |
| CI親和性 | ○ (GitHub Action等) | ○ (CLI/統合あり) | ○ (GitHub Action等) | ○ (Docker連携) |
| オフライン実行 | 可能 | 不可 (要API連携) | 可能 | 不可 |
Trivyを選定した理由
- 「これ1つで済む」網羅性: 多くのツールがOSパッケージと一部の言語ライブラリに限定される中、TrivyはDockerfileの設定ミス(root実行など)や、Terraform等のIaCスキャンにも対応しています。ツールを使い分ける管理コストを最小化できる点が大きな魅力でした。
- 完全なOSSであることによる自由度: Snyk等の商用ツールは非常に強力ですが、CI/CDで大量にスキャンを回す際にレートリミットやライセンス費用を考慮する必要があります。TrivyはOSSでありながらデータベースの更新頻度が非常に高く、コストを気にせずパイプラインに組み込めます。
- SBOM(Software Bill of Materials)への対応: 昨今のセキュリティ要件で求められるSBOM(CycloneDX / SPDX)の出力にも標準で対応しており、単なる脆弱性検知を超えた「資産管理」への拡張性があります。
Trivyは単なる脆弱性検知ツールではなく、SBOMの生成器としても機能します。これにより、『今、脆弱性があるか』をチェックするだけでなく、『将来新しい脆弱性が出たときに、即座に影響範囲を特定できる体制(資産の透明化)』までを視野に入れて導入を決定しました。
運用上の課題
TrivyをCI/CDパイプラインに統合し、自動スキャンを定着させた段階で、新たな構造的課題が顕在化しました。それは、スキャン結果に含まれる膨大な「ノイズ」による、運用負荷の増大です。
OSパッケージ由来の脆弱性による埋没
例えば、標準的な golang:1.22-bookworm (Debianベース) をスキャンした際、検知される脆弱性の8割以上が、アプリケーションの実行には直接関与しないOSパッケージ(libsqlite3、curl、perl 等)に起因するものでした。
- 検知総数: 3000件超
- 重要度: CriticalおよびHighが数十件含まれる
これらはベースイメージに「汎用的な利便性」のために同梱されているものですが、セキュリティの観点では、「真に修正すべきアプリケーション層の脆弱性(go.mod由来)」を視認困難にするノイズとなります。
例)ローカルでTrivyを実行した結果の一部。
スキャン結果を精査すると、OS由来(debian)と言語ツール由来(gobinary)の2種類が混在。その内、debianだけで2954件、脆弱性として検知されている。
Report Summary
┌──────────────────────────────────────────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐
│ Target │ Type │ Vulnerabilities │ Secrets │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ vuln-demo:standard (debian 12.9) │ debian │ 2954 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/src/cmd/vendor/github.com/google/pprof/third_party/d3flamegraph/pa- │ node-pkg │ 0 │ - │
│ ckage.json │ │ │ │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ app/hello │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/bin/go │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/bin/gofmt │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/addr2line │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/asm │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/buildid │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/cgo │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/compile │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/covdata │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/cover │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/doc │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/fix │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/link │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/nm │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/objdump │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/pack │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/pprof │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/test2json │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/trace │ gobinary │ 22 │ - │
├──────────────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_arm64/vet │ gobinary │ 22 │ - │
└──────────────────────────────────────────────────────────────────────────────────┴──────────┴─────────────────┴─────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)
特にOSパッケージは、修正パッチが未配布(Fixed Versionが空)のものも多く、通常のアップデートでは消せない『管理不能なノイズ』となっていることがわかる。
vuln-demo:standard (debian 12.9)
================================
Total: 2954 (UNKNOWN: 5, LOW: 589, MEDIUM: 1820, HIGH: 530, CRITICAL: 10)
┌────────────────────────────┬─────────────────────┬──────────┬──────────────┬───────────────────────┬────────────────────────┬──────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├────────────────────────────┼─────────────────────┼──────────┼──────────────┼───────────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤
│ apt │ CVE-2011-3374 │ LOW │ affected │ 2.6.1 │ │ It was found that apt-key in apt, all versions, do not │
│ │ │ │ │ │ │ correctly... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2011-3374 │
├────────────────────────────┼─────────────────────┤ │ ├───────────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤
│ bash │ TEMP-0841856-B18BAF │ │ │ 5.2.15-2+b7 │ │ [Privilege escalation possible to other user than root] │
│ │ │ │ │ │ │ https://security-tracker.debian.org/tracker/TEMP-0841856-B1- │
│ │ │ │ │ │ │ 8BAF │
├────────────────────────────┼─────────────────────┤ │ ├───────────────────────┼────────────────────────┼──────────────────────────────────────────────────────────────┤
│ binutils │ CVE-2017-13716 │ │ │ 2.40-2 │ │ binutils: Memory leak with the C++ symbol demangler routine │
│ │ │ │ │ │ │ in libiberty │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2017-13716 │
│ ├─────────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2018-20673 │ │ │ │ │ libiberty: Integer overflow in demangle_template() function │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2018-20673 │
│ ├─────────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2018-20712 │ │ │ │ │ libiberty: heap-based buffer over-read in d_expression_1 │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2018-20712 │
│ ├─────────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2018-9996 │ │ │ │ │ binutils: Stack-overflow in libiberty/cplus-dem.c causes │
│ │ │ │ │ │ │ crash │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2018-9996 │
│ ├─────────────────────┤ │ │ ├────────────────────────┼──────────────────────────────────────────────────────────────┤
トリアージのコストと形骸化のリスク
大量の検知結果に対し、以下のプロセスを全てのビルドで回すことは現実的ではありませんでした。
- 脆弱性の精査: 実行環境においてそのパッケージが実際に利用されているかの確認。
- 修正可否の判断: ベースイメージ側で修正パッチが提供されていない(Unfixed)脆弱性への対処方針の策定。
- ignoreリストの管理:
.trivyignore等を用いた除外設定のメンテナンス負荷。
結果として、アラートの多さが原因で開発者の「セキュリティに対する感度」が低下する、いわゆる「アラート疲れ」が発生しました。重要な警告がノイズの中に埋もれ、形骸化の兆候が見え始めたのです。
「パッチ適用」から「攻撃面の排除」への転換
当初はベースイメージの更新頻度を上げることで対処を試みましたが、汎用OSイメージを使用し続ける限り、アップストリームで未修正の脆弱性が常に数件〜十数件残るという限界がありました。
ここで私たちは、「脆弱性を一つずつ修正する」対症療法的なアプローチではなく、「脆弱性の温床となる不要なパッケージそのものを実行環境から物理的に排除する」 という、アーキテクチャレベルでの解決が必要であるという結論に至りました。
イメージサイズの極小化
Goアプリケーションにおいて、実行環境にシェルやパッケージマネージャーは本来不要です。ビルド済みのバイナリさえあれば動作するため、実行環境を最小化する「Multi-stage build」と「Distroless」の組み合わせが非常に強力なソリューションとなります。
Multi-stage buildによる実行環境の分離
まず、ビルド環境と実行環境を完全に分離します。ビルドには豊富なツールが含まれる golang イメージを使用し、実行用にはバイナリのみをコピーした distroless イメージを使用します。
# Stage 1: Build
FROM golang:1.22-bookworm AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# 静的リンクを強制してバイナリを作成
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
# Stage 2: Runtime
FROM gcr.io/distroless/static-debian12:latest
COPY /app/main /main
USER nonroot:nonroot
ENTRYPOINT ["/main"]
なぜ「alpine」ではなく「distroless」なのか
軽量イメージとして alpine が選ばれることも多いですが、セキュリティの観点では明確な差があります。
| 比較項目 | alpine | distroless (static) |
|---|---|---|
| OSパッケージ | apk (パッケージマネージャー) 搭載 | なし |
| シェル | sh / ash 搭載 | なし |
| 標準Cライブラリ | musl libc | なし (Go等の静的バイナリ向け) |
| 実行ユーザー | デフォルト root | デフォルトで nonroot 指定可能 |
| 攻撃面 | シェル経由の任意実行が可能 | バイナリ以外の実行手段がほぼ皆無 |
| 脆弱性ノイズ | OSパッケージ由来が混入しやすい | 極めてゼロに近い |
導入後の定量的変化
実際にこの構成へ移行した結果、以下のような顕著な効果が得られました。
- イメージサイズ: 800MB(標準Debian) → 約20MB(Distroless)へ削減。
- 脆弱性検知数: 100件超(OSパッケージ由来含む) → 数件(Goライブラリ由来のみ)へ減少。
Distroless環境でのデバッグ術:不便さを補うモダンなアプローチ
Distrolessを導入する際、開発現場から必ず上がる懸念が「シェルがない環境でどうやってトラブルシューティングをするのか」という点です。docker exec や kubectl exec でコンテナ内に入り、ls や cat で調査する従来の手法は使えなくなります。
不便さを解消するために下記の対応を行いました。
Kubernetes Ephemeral Containers の活用
Kubernetes 1.25からGA(一般利用可能)となった Ephemeral Containers を利用すれば、稼働中のPodにデバッグ用のコンテナを一時的にアタッチできます。
kubectl debug -it <pod-name> --image=busybox --target=<container-name>
この方法であれば、本番用イメージには不要なツールを一切含めず、必要な時だけ外側から調査ツールを持ち込むという運用が可能になります。
デバッグ用タグの併用
開発環境やステージング環境など、頻繁な調査が必要な環境向けに、ツール類を含んだデバッグ用のイメージを別途ビルドする戦略も有効です。
myapp:latest(Distrolessベース / 本番用)myapp:debug(Distrolessの :debug タグベース。BusyBoxのシェルが含まれる)
モダンな kubectl debug が使えない環境への備えとして、Distrolessの :debug タグを活用したイメージの作り分けも実用的です。ARG(ビルド引数)を利用して、本番用には『何も入っていないイメージ』、検証用には『調査用シェル入りのイメージ』を生成することで、開発の機動力と本番の安全性を両立できます。
ARG RUNTIME_TAG=latest
FROM gcr.io/distroless/static-debian12:${RUNTIME_TAG}
...
可観測性(Observability)への投資:コンテナの外から「中」を知る
「コンテナの中に入れない」という制約は、トラブルシューティングの手法を「場当たり的な現物確認」から「データに基づいた客観的分析」へとシフトさせます。これを実現するために不可欠なのが、下記取り組みとなります。
1. 構造化ログ(Structured Logging)の徹底
シェルに入れない環境では、cat /var/log/app.log を叩くことはできません。すべてのログは「外」へ出ることが大前提となります。
- JSON形式への統一: ログを文字列ではなくJSON形式で標準出力(stdout)に吐き出します。これにより、CloudWatch LogsやDatadog、ELKなどのログ基盤で「特定のユーザーID」「特定の処理時間(500ms以上)」といった条件での高度なフィルタリングが可能になります。
- コンテキストの付与: リクエストIDやトレースIDを各ログに含めることで、一つのリクエストがどのコンテナでどう処理されたかを、シェルに入らずとも追跡できるようになります。
2. 分散トレーシング(Distributed Tracing)の導入
マイクロサービス環境では、エラーがどのサービスで発生したかを特定するのが困難です。OpenTelemetry などを導入し、リクエストの「全行程」を可視化します。
- ボトルネックの可視化: コンテナに入って
topを叩かなくても、どの関数の処理に時間がかかっているのか、どのDBクエリが遅延しているのかがグラフ上で一目瞭然になります。 - エラーの相関分析: エラーが発生した際、その直前の処理や依存している外部サービスのレスポンス状況を時系列で把握できます。
3. リソースモニタリングとプロファイリング
プロセスの死活監視だけでなく、バイナリ内部の状態を外部から観測可能にします。
- メトリクス: Goであれば
net/http/pprofや Prometheus エクスポーターを活用し、メモリ使用量、Goroutine数、GC(ガベージコレクション)の頻度などをリアルタイムで監視します。 - 継続的プロファイリング: 「コンテナに入って
pprofを実行する」のではなく、Datadog Continuous Profiler のようなツールを使い、常にCPUやメモリのプロファイルを収集し続けることで、再現性の低いスパイク現象も後から分析可能になります。
不便さが生んだ「健全な運用」
Distrolessの導入は、一時的にはデバッグの難易度を上げたように見えました。しかし、その結果として「ログ、メトリクス、トレース」の三本柱が整備され、「特定の誰かがコンテナに潜り込み、個人の技量で解決する」運用から、「チーム全員がダッシュボードを囲み、データに基づいて解決する」運用へと進化することができました。
開発を止めない脆弱性スキャン:TrivyとCIの統合
スキャンツールを導入しても、手動で実行しているうちは形骸化します。私たちはGitHub Actionsを利用して、「脆弱なイメージがレジストリにプッシュされるのを物理的に防ぐ」ガードレールを構築しました。
CIでの自動スキャンの構成
ビルドパイプラインの最後にTrivyを組み込み、以下の条件で実行しています。
- 重要度によるフィルタリング:
HIGHおよびCRITICALが1件でもあれば、ビルドを失敗させ、マージをブロックします。 - イメージのキャッシュ利用: 毎回データベースをダウンロードするとCIが遅くなるため、GitHub Actionsのキャッシュ機能を使い、数秒でスキャンが完了するように最適化しています。
- name: Cache Trivy DB
uses: actions/cache@v4
with:
path: .cache/trivy
key: ${{ runner.os }}-trivy-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-trivy-
- name: Run Trivy vulnerability scanner
uses: aquasec/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'table'
exit-code: '1'
severity: 'HIGH,CRITICAL'
cache-dir: .cache/trivy
「開発を止めない」ための対応要否の仕分け:.trivyignore
「修正パッチがまだ存在しない脆弱性」や「実行環境において悪用が不可能な脆弱性」でビルドが止まってしまうと、開発スピードが著しく低下します。これを回避するために、.trivyignore による明示的な管理を行っています。
運用のルール:
-
- 無視(ignore)する場合は、必ずその理由(なぜ安全と言えるのか)をコメントに残す。
-
- 修正パッチが出次第、ignoreリストから外す。
具体的なファイル記述例
# --- OS Package Noise (False Positives) ---
# libsqlite3: No patch available. No impact; Go binary built without CGO.
CVE-2023-3618
# --- Non-exploitable in Runtime ---
# openssl: Risk mitigated; Go's crypto/tls is used for all external
# traffic instead of the system openssl.
CVE-2024-0727
単に無視するだけではセキュリティホールを見逃すリスクがあるため、以下のルールを導入しました。
- 「なぜ無視してよいか」を必ずコメント: 数ヶ月後の自分や、別の担当者が読んでも判断根拠(「パッチ未提供」「利用箇所なし」等)がわかるようにします。
- 有効期限やチケット番号を添える: 「修正パッチが提供されたら対応する」というタスクを GitHub Issues 等に作成し、そのリンクをコメントに記載します。
- Distroless との相乗効果: 標準イメージではこの
.trivyignoreが数十行〜百数行に肥大化しがちですが、Distroless であればそもそも OS パッケージがほぼ無いため、このリストを最小限(数件程度)に保つことができます。
導入後に直面した壁と回避策
Distroless、特に最小構成の static イメージを採用すると、標準イメージでは当たり前だった「インフラの基本部品」が欠落していることに気づきます。これらは Multi-stage build の COPY コマンドで、ビルドステージから安全に持ち込むことが可能です。
タイムゾーン問題(JSTがずれる)
Distroless 内には /usr/share/zoneinfo が存在しないため、Go のプログラム内で time.LoadLocation("Asia/Tokyo") を実行してもエラーになります。そのため、ビルドステージ(Debian等)からタイムゾーンデータをコピーします。
FROM golang:1.22-bookworm AS builder
# ... (Build process) ...
FROM gcr.io/distroless/static-debian12
# Copy timezone data for localized time handling
COPY /usr/share/zoneinfo /usr/share/zoneinfo
# Set default timezone to JST
ENV TZ=Asia/Tokyo
CA証明書問題(外部API通信の失敗)
外部の HTTPS API にリクエストを投げる際、ルート証明書(CA certificates)がないと SSL/TLS エラーで通信に失敗します。
解決策: 同様に、ビルドステージの最新の証明書をコピーします。
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
まとめ
「頑張る」運用の限界
従来のように、検知された大量のOS脆弱性に対して「一つずつパッチを当てる」「一つずつ無視リストに入れる」というアプローチは、いわば「穴の空いたバケツで水を汲む」ような努力でした。この「頑張り」は、開発チームの疲弊と、本質的なリスク(アプリ層の脆弱性)への見逃しを招きます。
「削る」という戦略的選択
Distrolessを採用し、OSレベルの管理を思い切って「捨てる」ことで、私たちの関心は以下のように変化しました。
- Before: 未使用のOSライブラリの脆弱性に怯え、トリアージに追われる日々。
- After: アプリケーション層(
go.mod等)の脆弱性対応という、「本当に自分たちが責任を持つべき領域」だけに100%集中できる環境。
インフラエンジニアの役割:見えないガードレール
今回構築した「Trivy × CI統合 × Distroless」の仕組みは、開発者に対して「セキュリティを意識しろ」と強要するものではありません。
インフラエンジニアが「脆弱なものがそもそも混入できない、かつノイズに邪魔されないガードレール」を敷くことで、開発者は意識せずとも安全な道を歩めるようになります。開発者の負担を減らしながら、組織全体の安全性をボトムアップさせることが可能です。
もし、日々の脆弱性アラートに疲弊しているのなら、まずはイメージを「削る」ことから始めてみるのはいかがでしょうか。