Contents
サンプルアプリ設計とモジュール管理
最小限の HTTP サーバー構成とモジュール管理の基本方針を示します。ローカルでの実行性と CI/本番での再現性を両立する設計に重点を置いています。
ファイル構成と go.mod 初期化
ここでは推奨するリポジトリ構成と go.mod の初期化手順を示します。CI や Docker キャッシュを意識した配置です。
- cmd/server/main.go
- internal/handler.go
- go.mod
- go.sum
- Dockerfile
- Dockerfile.dev
- docker-compose.yml
- docker-compose.dev.yml
- .dockerignore
- .github/workflows/ci.yml
- Makefile
- README.md
go.mod の初期化例:
|
1 2 3 4 |
git init go mod init github.com/yourorg/sample-go-docker # go 1.21 を go.mod に記載することを推奨 |
go.mod のサンプル:
|
1 2 3 4 5 6 7 8 |
module github.com/yourorg/sample-go-docker go 1.21 require ( github.com/prometheus/client_golang v1.16.0 ) |
簡易サーバーのポイント
サーバー実装では監視・運用を念頭に置いてエンドポイントを用意します。pprof などは本番で無制限に公開しないことが重要です。
注意点の例:
- /healthz を liveness/readiness 用に実装する
- /metrics を Prometheus 用に公開する(スクレイプ元を限定)
- /debug/pprof は認証・ネットワーク制限経由でのみ公開する
- SIGINT/SIGTERM を捕まえてグレースフルシャットダウンを実装する
pprof を安全に扱うための簡単な middleware(例):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func basicAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { u, p, ok := r.BasicAuth() if !ok || !validate(u, p) { w.Header().Set("WWW-Authenticate", `Basic realm="restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) } // 登録例: // mux.Handle("/debug/pprof/", basicAuth(http.HandlerFunc(pprof.Index))) |
テストと CI での検証
ユニットテストと静的解析は CI の必須項目です。ローカルでも go test ./... を習慣化してください。
検証コマンド:
|
1 2 3 4 5 |
go mod tidy go test ./... -v go vet ./... # optional: staticcheck |
CI ではテスト失敗を早期に検出する仕組みを設け、同時に go.sum による依存再現性を保ちます。
Dockerfile とランタイムの選択(distroless を含む)
マルチステージでビルドと実行を分離する方針を示します。distroless を採用する際は CA 証明書やデバッグ、ヘルスチェック実行環境を明示的に用意する必要があります。
推奨マルチステージ Dockerfile の例
ここでは distroless を最終イメージにする例を示します。重要な点はビルダーで CA 証明書を用意し、最終イメージに明示的にコピーすることです。また CGO フラグ名は常に CGO_ENABLED を使います。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# builder stage FROM golang:1.21-alpine3.18 AS builder ARG TARGETOS=linux ARG TARGETARCH=amd64 ARG VERSION=dev ENV CGO_ENABLED=0 \ GOOS=${TARGETOS} \ GOARCH=${TARGETARCH} \ GOCACHE=/root/.cache/go-build WORKDIR /src # 依存のキャッシュを効かせる COPY go.mod go.sum ./ RUN apk add --no-cache git ca-certificates && \ go env -w GOPROXY=https://proxy.golang.org && \ go mod download COPY . . # キャッシュマウントを利用したビルド RUN --mount=type=cache,target=/root/.cache/go-build \ go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" -o /app/server ./cmd/server # オプション: コンテナ内でのヘルスチェック用バイナリをビルド RUN --mount=type=cache,target=/root/.cache/go-build \ go build -trimpath -ldflags="-s -w" -o /app/healthcheck ./cmd/healthcheck # runtime stage FROM gcr.io/distroless/static:nonroot # バイナリをコピー COPY --from=builder /app/server /app/server COPY --from=builder /app/healthcheck /app/healthcheck # CA 証明書をコピー(重要: ランタイムに証明書がないと外部 TLS が失敗します) COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt USER nonroot EXPOSE 8080 ENTRYPOINT ["/app/server"] |
ポイント:
- ビルド時は CGO_ENABLED=0 を利用して静的バイナリ化が可能ならこれを優先します。
- distroless はシェルや curl を持たないため、ヘルスチェックやデバッグ用バイナリをあらかじめ用意してコピーする方法が実務的です。
- 代替として debian-slim 等の軽量な base を選ぶ選択肢もあります(次節参照)。
distroless の運用上の注意と対処
distroless は攻撃面を小さくできますが、運用上のハードルが発生します。代表的な問題と実務的な対処を記します。
- シェル・ツール無しでデバッグが困難: デバッグ用に debian-slim 等の debug イメージを別途用意するか、最小限の静的ツール(busybox 互換、独自の healthcheck バイナリ)をコピーします。
- CA 証明書の欠如: builder で ca-certificates をインストールし、/etc/ssl/certs をランタイムにコピーします。これを怠ると外部 TLS 通信が失敗します。
- docker-compose や healthcheck のコマンドミスマッチ: healthcheck はコンテナ内で実行されます。curl を使う例は distroless では失敗します。後述のように exec 形式で独自バイナリを使うか、Kubernetes の HTTP プローブを活用してください。
- 緊急時の調査: kubectl debug や ephemeral debug コンテナを利用し、問題解析用に同一バイナリを含めた debug イメージで再現する運用を用意します。
代替ベースイメージの選定基準
選定の判断基準は次の通りです。要件に応じて選んでください。
- ネイティブライブラリ(CGO)を使う場合: debian-slim など glibc ベースを選ぶ
- デバッグやオンボードツールが頻繁に必要な場合: debian-slim を選択
- セキュリティ最優先かつツール不要であれば: distroless(ただし上記のコピーや debug 戦略を準備)
ビルド最適化とマルチアーキ(CGO/ldflags/strip/BuildKit)
ビルド時最適化はイメージサイズと起動時間、セキュリティに影響します。特に CGO、-ldflags、strip の扱いと cross-compile の注意点を押さえてください。
CGO とクロスコンパイルの注意点
CGO を使うライブラリは C コンパイラと標準Cライブラリ(glibc/musl)に依存します。クロスコンパイル時は次を考慮してください。
- CGO を不要なら CGO_ENABLED=0 を設定して静的ビルドを目指すと簡単です。
- CGO を使う場合は対象アーキ向けのクロスコンパイラや適切な libc が必要です。単に buildx を使うだけではビルドが成功しないことがあります。
- glibc と musl の違いに注意し、実行時に依存する libc とランタイムベースイメージを一致させてください。
例: 静的ビルド環境でのビルド(CGO 無効)
|
1 2 3 4 5 6 |
DOCKER_BUILDKIT=1 \ docker build --build-arg VERSION=${VERSION} \ --build-arg TARGETOS=linux --build-arg TARGETARCH=amd64 \ -t sample:local . # Dockerfile 内で ENV CGO_ENABLED=0 を設定済み |
buildx とキャッシュ戦略
buildx を使うとマルチプラットフォームビルドとリモートキャッシュが可能です。認証やキャッシュ参照は正しく設定してください。
基本コマンド例:
|
1 2 3 4 5 6 7 8 9 10 |
docker buildx create --name mybuilder --use docker buildx inspect --bootstrap docker buildx build \ --platform linux/amd64,linux/arm64 \ --build-arg VERSION=${VERSION} \ --cache-from=type=registry,ref=ghcr.io/yourorg/sample:cache \ --cache-to=type=registry,mode=max,ref=ghcr.io/yourorg/sample:cache \ -t ghcr.io/yourorg/sample:${TAG} --push -f Dockerfile . |
注意点:
- キャッシュをレジストリに置く場合は認証情報が必要です。
- クロスビルドで CGO が必要な場合は追加のセットアップが要ります(QEMU や cross-compiler)。
バイナリ縮小とデバッグのトレードオフ
- -ldflags="-s -w" と -trimpath でサイズ削減と再現性向上が図れます。
- strip による更なる削減は可能ですが、デバッグ情報が失われます。
- UPX は更なる圧縮が可能ですが、互換性・起動時間の面で注意が必要です。
例: builder ステージで strip を実行する場合(必要に応じて binutils をインストール)
|
1 2 3 |
RUN apk add --no-cache binutils && \ strip /app/server |
ローカル開発・デバッグ・ヘルスチェック・シークレット管理
ローカルでの高速なフィードバックと本番の堅牢なシークレット管理を両立する実務的な構成を示します。ヘルスチェックはコンテナ内で実行される点に注意してください。
docker-compose と Delve を使った開発ワークフロー
開発ではソースをボリュームマウントしホットリロードや Delve によるデバッグを行います。ツールは固定バージョンでインストールします。
Dockerfile.dev(抜粋):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
FROM golang:1.21-bullseye WORKDIR /src RUN apt-get update && \ apt-get install -y ca-certificates git && \ rm -rf /var/lib/apt/lists/* # 固定バージョンでツールをインストール RUN go install github.com/cosmtrek/air@v1.40.0 \ && go install github.com/go-delve/delve/cmd/dlv@v1.12.0 COPY go.mod go.sum ./ RUN go env -w GOPROXY=https://proxy.golang.org && go mod download COPY . . |
docker-compose.dev.yml(抜粋):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
services: app: build: context: . dockerfile: Dockerfile.dev volumes: - ./:/src:delegated working_dir: /src ports: - "8080:8080" - "127.0.0.1:40000:40000" # Delve 用、ループバックにバインド command: ["air", "-c", ".air.toml"] |
Delve を使う場合はローカルのみにバインドするなどアクセス制御を徹底します。
ヘルスチェック実装例(コンテナ内で実行される点に注意)
docker-compose の healthcheck で curl を直接使う例は最小イメージでは失敗します。healthcheck はコンテナ内で実行されるため、コマンドがそのコンテナに存在する必要があります。代替策として小さな Go 製のヘルスチェックバイナリを作り、ランタイムにコピーする方法を推奨します。
簡単なヘルスチェックバイナリ(cmd/healthcheck/main.go):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import ( "net/http" "os" "time" ) func main() { if len(os.Args) < 2 { os.Exit(1) } url := os.Args[1] client := http.Client{Timeout: 2 * time.Second} resp, err := client.Get(url) if err != nil || resp.StatusCode >= 400 { os.Exit(1) } os.Exit(0) } |
docker-compose の healthcheck 例(exec 形式を利用):
|
1 2 3 4 5 6 |
healthcheck: test: ["CMD", "/app/healthcheck", "http://127.0.0.1:8080/healthz"] interval: 30s timeout: 3s retries: 3 |
Kubernetes を使う場合は kubelet の liveness/readiness probe を利用すると、コンテナ内にヘルスチェックツールを入れる必要がなくなります(例は以下)。
Kubernetes liveness/readiness の例:
|
1 2 3 4 5 6 7 8 9 |
livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 2 failureThreshold: 3 |
pprof の安全な公開方法(認証・ネットワーク制限)
pprof はデバッグに有用ですが本番で無制限に公開してはいけません。対策例を示します。
対策候補:
- pprof をローカル専用ポートにバインドし、必要時に SSH トンネルで接続する
- 認証プロキシ(oauth-proxy や nginx の Basic 認証)経由でのみアクセス許可する
- アプリ内で Basic 認証ミドルウェアを適用して制限する(前述の basicAuth 例)
- Bastion 経由でアクセスを限定する
簡易的な Go の BasicAuth ミドルウェアは前節の例を参照してください。ネットワークACL や IAM による保護と合わせて運用してください。
CI/CD と脆弱性スキャン運用(Trivy 等)
CI によるイメージビルドとスキャンの運用設計を紹介します。Trivy は便利ですがインストール方法と運用方針を適切に決めてください。
GitHub Actions での安全な Trivy 利用例
curl | sh のようなインストールはセキュリティと再現性の観点で問題があります。公式の GitHub Action を使うか、公開リリースの署名済みバイナリ+チェックサムで検証してインストールしてください。
公式アクションを使う例(タグは必ず固定する):
|
1 2 3 4 5 6 7 8 |
- name: trivy scan uses: aquasecurity/trivy-action@v0.##.## with: image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }} format: table severity: HIGH,CRITICAL exit-code: 1 |
注: 上記では必ずリリースの固定タグ(例 v0.x.y)に置き換え、リリースノートで署名方法を確認してください。
署名済みバイナリ+チェックサムでのインストール例(手順の例示):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- name: Install Trivy (checksum-verified) run: | TRIVY_VERSION=v0.44.0 ARCH=linux-amd64 URL="https://github.com/aquasecurity/trivy/releases/download/${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_${ARCH}.tar.gz" CHECKSUM_URL="https://github.com/aquasecurity/trivy/releases/download/${TRIVY_VERSION}/SHA256SUMS" curl -fsSL -o trivy.tar.gz "$URL" curl -fsSL -o SHA256SUMS "$CHECKSUM_URL" # 以下はリリースのチェックサム形式に合わせて検証コマンドを調整してください grep "trivy_${TRIVY_VERSION}_${ARCH}.tar.gz" SHA256SUMS | sha256sum -c - tar -xzf trivy.tar.gz trivy sudo mv trivy /usr/local/bin/trivy |
バイナリ名やチェックサムのパスは必ず公式リリースページで確認してください。
脆弱性運用フローと例外管理
自動スキャンは有効ですが運用ポリシーが必要です。実務で使いやすい運用例を示します。
- スキャン結果の扱い: HIGH/CRITICAL は原則 fail。低い程度は警告ログにとどめる運用が一般的。
- 偽陽性(false positive)の扱い: .trivyignore 等で一時的に除外し、必ずトリアージチケットを作成して検証履歴を残す。
- 例外管理: 受け入れられる例外は期限付きでチケット化し、定期的に再評価する。
- 自動化と手動レビューの併用: 自動 fail をトリガーに担当者が調査し、修正のプランを作るワークフローを用意する。
- 監査ログ: スキャン結果・除外ルール・承認履歴は保存しておく。
.trivyignore の簡易例:
|
1 2 3 4 |
# CVE を一時的に除外する例 CVE-2021-1234 CVE-2022-5678 |
バージョン固定と署名の運用指針
- サードパーティーツールはバージョンを固定し、リリースのチェックサムや GPG 署名で検証してください。
- CI のワークフロー内でバイナリをダウンロードする場合、チェックサム検証を必須にします。
- 依存は go.sum 等で固定化し、定期的にアップデートと追跡を行います。
まとめ
Go コンテナ化ではビルドとランタイムを明確に分離し、distroless 利用時は CA 証明書やヘルスチェック実行環境の確保、デバッグ用イメージ戦略が重要です。CI 側では Trivy の導入を安全に行い、偽陽性や例外の運用フローを整備してください。以下に要点をまとめます。
- マルチステージでビルドと実行を分離し、go.mod を先にコピーして依存をキャッシュする。
- CGO は CGO_ENABLED=0 を基本に検討。CGO 必要時はランタイムの libc とツールチェーンを合わせる。
- distroless を使う場合は /etc/ssl/certs を明示的にコピーし、ヘルスチェックバイナリや debug イメージの戦略を用意する。
- docker-compose の healthcheck はコンテナ内で実行される点に注意し、exec 形式や独自バイナリを利用する。
- Trivy は公式アクションか署名済みバイナリ+チェックサムで導入し、false positive 対応や例外管理ルールを整備する。
付録: 推奨する .dockerignore(例)
|
1 2 3 4 5 6 7 8 |
.git .gitignore .env *.log testdata/ node_modules/ .vscode/ |
付録: Makefile による代表的なコマンド(例)
|
1 2 3 4 5 6 7 8 9 10 11 |
.PHONY: build docker-build test build: go build -o bin/server ./cmd/server docker-build: DOCKER_BUILDKIT=1 docker build -t sample:local . test: go test ./... -v |
上記を基にリポジトリ内の Dockerfile、docker-compose、CI ワークフローを点検し、バージョン固定・署名検証・例外運用ルールを整備してください。