Aller au contenu principal
友田 陽大
Go & Echo in production
Go
Echo
アーキテクチャ設計
可観測性
セキュリティ
コスト最適化

Echo 本番デプロイ完全ガイド:マルチステージ Docker・distroless・サーバータイムアウト・グレースフルシャットダウンで無停止運用する

Go Echo(v5)を本番にデプロイする実装ガイド。12-factor の環境変数設定、CGO_ENABLED=0 のマルチステージ Docker と distroless/nonroot、StartConfig.BeforeServeFunc による http.Server タイムアウト(v5でTimeoutミドルウェア廃止)、SIGTERM を取りこぼさないグレースフルシャットダウン、liveness/readiness ヘルスチェック、ECS Fargate / Cloud Run の作法、シークレット管理、コスト最適化までを実コードで解説します。

Published
Reading time
9 min read
Author
友田 陽大
Partager

「ローカルでは動く」と「本番で無停止運用できる」の間には、深い谷があります。デプロイのたびに 502 が出る、巨大リクエストでメモリが溢れる、遅いクライアントが接続を握り続ける(slowloris)、コンテナイメージに認証情報が焼かれている——これらはデプロイ設計の不備であって、アプリのバグではありません。

この記事は、Go Echo 本番運用ガイドのデプロイ編です。Go の「単一バイナリ」という強みを最大限に活かし、極小・安全・無停止なデプロイを、Echo v5 の正確な API で実装します。

この記事のルール:Echo の API は 公式ドキュメント / ソース(v5・2026年6月時点) に基づきます。特に v5 は middleware.Timeout が廃止され、サーバータイムアウトの設定方法が変わっています(第3章)。シークレット(DB URL・署名鍵)は環境変数/シークレットマネージャ前提で、コードにもイメージにも絶対に焼き込みません。


1. 設定は 12-factor で:環境変数から読む

設定値(ポート・DB URL・ログレベル・鍵)は環境変数から読みます。これにより、同じイメージを dev/staging/prod でビルドし直さずに使い回せます。

type Config struct {
	Port        string
	DatabaseURL string
	JWTKey      []byte
	LogLevel    string
}

func LoadConfig() (*Config, error) {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080" // 既定値(Cloud Run は PORT を注入してくる)
	}
	dsn := os.Getenv("DATABASE_URL")
	if dsn == "" {
		return nil, errors.New("DATABASE_URL is required") // 起動時に fail-fast
	}
	return &Config{
		Port:        port,
		DatabaseURL: dsn,
		JWTKey:      []byte(os.Getenv("JWT_SIGNING_KEY")),
		LogLevel:    os.Getenv("LOG_LEVEL"),
	}, nil
}

必須設定が無ければ**起動時に即エラー(fail-fast)**にします。「動き始めてから設定不足で落ちる」より、「起動を拒否する」方が遥かに安全です。


2. マルチステージ Docker:極小・最小権限イメージ

Go の単一バイナリを活かし、ビルド環境と実行環境を分離します。実行イメージは distroless(シェル・パッケージマネージャなし)+ nonroot

# --- build stage ---
FROM golang:1.25 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download                       # 依存だけ先に取りレイヤキャッシュを効かせる
COPY . .
# CGO 無効で静的リンク、-trimpath とシンボル除去で軽量化・再現性向上
RUN CGO_ENABLED=0 GOOS=linux go build \
      -trimpath -ldflags="-s -w" \
      -o /app ./cmd/server

# --- runtime stage ---
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /app /app
USER nonroot:nonroot                       # 非 root 実行(最小権限)
EXPOSE 8080
ENTRYPOINT ["/app"]
設定効果
CGO_ENABLED=0静的リンク。distroless/static で動く・libc 依存を断つ
-trimpathバイナリからローカルパスを除去(情報漏洩防止・再現性)
-ldflags="-s -w"デバッグ情報除去でサイズ削減
distroless/staticシェルもパッケージも無い=攻撃面が最小
nonroot侵害時の被害を限定(最小権限の原則)
依存を先に COPYレイヤキャッシュでビルド高速化(コスト効率)

コスト最適化:本番ターゲットが arm64(AWS Graviton / Cloud Run)なら、GOARCH=arm64 でビルドして arm 系インスタンスに載せると、同等性能で単価が安くなります。Go のクロスコンパイルは容易なので、マルチアーキビルドの恩恵を受けやすい言語です。


3. サーバータイムアウト:v5 は StartConfig.BeforeServeFunc で設定する

ここが v5 の重要な落とし穴です。 v4 にあった middleware.Timeoutv5 で廃止されました。代わりに、コネクションレベルのタイムアウトを http.Server に設定します。v5 では StartConfig.BeforeServeFunc(サーバー起動直前に *http.Server を触れるコールバック)がその正規の場所です。

sc := echo.StartConfig{
	Address:         ":" + cfg.Port,
	GracefulTimeout: 10 * time.Second,
	BeforeServeFunc: func(s *http.Server) error {
		// slowloris 等の遅延攻撃・リソース占有を防ぐ
		s.ReadHeaderTimeout = 5 * time.Second   // ヘッダ読み取りの上限(最重要)
		s.ReadTimeout = 15 * time.Second        // ボディ含むリクエスト全体
		s.WriteTimeout = 30 * time.Second       // レスポンス書き込み
		s.IdleTimeout = 120 * time.Second       // keep-alive 接続のアイドル上限
		return nil
	},
}
タイムアウト守るもの
ReadHeaderTimeoutslowloris(ヘッダをだらだら送って接続を占有する攻撃)
ReadTimeout巨大/低速なリクエストボディによる占有
WriteTimeout応答を受け取らないクライアントによる占有
IdleTimeoutアイドルな keep-alive 接続の滞留

ハンドラ単位のタイムアウト(重いクエリの打ち切り)は、context.WithTimeout で表現します(DB 層の記事)。サーバーレベル(接続)とハンドラレベル(処理)は別物で、両方を設定します。リクエストボディはBodyLimitでサイズ上限も掛けます。


4. グレースフルシャットダウン:SIGTERM を取りこぼさない

デプロイ・スケールインのたびに、オーケストレータはまず SIGTERM を送ります。これを無視すると処理中のリクエストが切れて 502 になります。v5 は StartConfig{GracefulTimeout}signal.NotifyContext を渡す形に一本化されています。http.ErrServerClosed は正常終了として扱うのが正しい作法です。

func main() {
	cfg, err := LoadConfig()
	if err != nil {
		slog.Error("config error", "error", err)
		os.Exit(1)
	}

	e := buildEcho(cfg) // ルート・ミドルウェア・DI 済みの Echo

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	sc := echo.StartConfig{
		Address:         ":" + cfg.Port,
		GracefulTimeout: 10 * time.Second,
		BeforeServeFunc: applyServerTimeouts, // 第3章
		OnShutdownError: func(err error) {
			e.Logger.Error("graceful shutdown error", "error", err)
		},
	}

	// ErrServerClosed は SIGTERM での正常停止。エラー扱いしない
	if err := sc.Start(ctx, e); err != nil && !errors.Is(err, http.ErrServerClosed) {
		e.Logger.Error("server failed", "error", err)
		os.Exit(1)
	}
	slog.Info("server stopped gracefully")
}

SIGTERM 受信後の流れ:①新規接続の受付停止 → ②GracefulTimeout(既定10秒)の範囲で処理中リクエストの完了を待つ → ③DB プール等のクリーンアップ(defer pool.Close())→ ④終了

致命的な設定整合GracefulTimeout は、オーケストレータの終了猶予より必ず短くします(ECS の stopTimeout、Kubernetes の terminationGracePeriodSeconds 等)。逆だと、片付けの途中で SIGKILL され、結局リクエストが切れます。例:猶予 30 秒なら GracefulTimeout は 10〜20 秒に。


5. ヘルスチェック:liveness と readiness を分ける

オーケストレータはヘルスチェックで「生きているか」「トラフィックを流してよいか」を判断します。この2つを混同しないのが無停止運用の鍵です。

// liveness: プロセスが生きているか(依存はチェックしない。落ちていれば再起動される)
e.GET("/healthz", func(c *echo.Context) error {
	return c.NoContent(http.StatusOK)
})

// readiness: トラフィックを受けられるか(DB 等の依存も確認)
e.GET("/readyz", func(c *echo.Context) error {
	ctx, cancel := context.WithTimeout(c.Request().Context(), 2*time.Second)
	defer cancel()
	if err := pool.Ping(ctx); err != nil {
		return c.JSON(http.StatusServiceUnavailable, map[string]string{"db": "down"})
	}
	return c.NoContent(http.StatusOK)
})
  • liveness(/healthz:依存を見ない。DB が一時的に落ちてもプロセスは再起動すべきでない(再起動しても直らない)。
  • readiness(/readyz:依存を見る。DB が落ちている間はトラフィックを流さない(が、プロセスは殺さない)。
  • グレースフル停止との連携:理想は、SIGTERM 受信時にまず readiness を 503 にしてロードバランサからの新規流入を止め、その後に既存リクエストを捌き切ること。

これらのヘルスチェックやRequestLogger(slog)、相関 ID を組み合わせると、本番の可観測性が一段上がります(OpenTelemetry 連携)。


6. ECS Fargate / Cloud Run の作法

Echo は常駐コンテナとして、どのコンテナ基盤にも素直に載ります。プラットフォーム別の要点だけ押さえます。

項目AWS ECS / FargateGoogle Cloud Run
ポートタスク定義のポートマッピングPORT 環境変数を必ず読む
終了シグナルSIGTERMstopTimeout(既定30秒)SIGTERM → 猶予(既定10秒、延長可)
猶予との整合GracefulTimeout < stopTimeoutGracefulTimeout < 終了猶予
クライアント IPALB 経由→ Echo.IPExtractor を設定プロキシ経由→同上
シークレットSecrets Manager / SSM → 環境変数注入Secret Manager → 環境変数/ボリューム
オートスケールTarget Tracking 等リクエスト並行数ベース(ゼロスケール可)

c.RealIP()レート制限や監査ログで使うなら、ロードバランサ背後では Echo.IPExtractor で信頼できる前段を設定しないと、ヘッダ偽装で誤った IP を掴みます。

ECS/Fargate のオートスケールや Blue/Green、コスト最適化はECS Fargate 本番ガイド、Cloud Run のゼロスケール・並行性・課金はAzure Container Apps ガイドのサーバーレスコンテナの考え方とも地続きです。


7. シークレット管理とイメージ衛生

  • イメージに焼かないENV JWT_KEY=....env のコミットは厳禁。鍵はランタイムでシークレットマネージャ → 環境変数注入。
  • .dockerignore.git.env・テスト・ローカル設定をイメージから除外(漏洩・肥大化防止)。
  • イメージスキャン:CI でコンテナ脆弱性スキャン(Trivy 等)をゲートに。golangci-lintgovulncheck(Go の既知脆弱性検査)も合わせる。
  • 鍵レス CI/CD:デプロイ認証は長期キーではなく **OIDC(GitHub Actions → AWS/GCP の一時credential)**で。長期アクセスキーをリポジトリ Secrets に置かない。

このイメージ衛生・鍵レス CI/CD の思想は、AI駆動開発の品質ゲートで扱う「秘密情報・脆弱性スキャンを CI で機械強制する」設計と同じです。


まとめ:無停止デプロイの7原則

  1. 設定は 12-factor で環境変数。必須欠如は起動時 fail-fast。
  2. CGO_ENABLED=0 のマルチステージ + distroless/nonroot で極小・最小権限イメージに。
  3. v5 はサーバータイムアウトを BeforeServeFunc で設定(ReadHeaderTimeout で slowloris 対策)。
  4. StartConfig{GracefulTimeout} + signal.NotifyContextErrServerClosed は正常終了。
  5. GracefulTimeout はオーケストレータ猶予より短く。readiness を先に落として流入を止める。
  6. liveness と readiness を分ける。依存は readiness だけで見る。
  7. シークレットはイメージに焼かず、OIDC の鍵レス CI/CD とイメージスキャンをゲートに。

デプロイ設計は、コードの正しさを**本番の現実(再起動・スケール・攻撃・障害)**に耐えさせる最後の工程です。Echo の単一バイナリ + v5 の StartConfig は、この工程を素直に・正確に組めるように設計されています。クラスタ全体はGo Echo 本番運用ガイドから辿れます。

友田

友田 陽大

Créateur d’un produit lauréat du prix du ministre (METI). Avec TypeScript + Python + AWS, je livre SaaS, transformation numérique industrielle et IA générative (RAG) prête pour la production — des spécifications à l’infrastructure et à l’exploitation, seul.

I can take on the implementation from this article as an engagement

I build Go / Echo backends, from design to production

API design and migration to Echo v5, clean architecture (Controller/UseCase/Repository + DI), middleware and security, centralized error handling, graceful shutdown, and testing/CI. With experience building a clean-architecture backend in Go/Echo + google/wire, I implement APIs that don't fall over, are traceable, and are easy to change.

Disponible en mission au forfait comme en conseil technique. Commencez par un échange gratuit de 30 minutes.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

À lire également