Go config.yaml

Goではconfig.yamlとconfig.yaml.exampleを併用する構成がよくあります。ただし、本番用のシークレットを含む実際のconfig.yamlまでコミットしてしまうことがあります。サービスの構成に合わせて、環境変数を読む方式と、実行時に安全に供給するファイルを使う方式を選んでください。

このガイドを使う場面

  • リポジトリにconfig.yamlとconfig.yaml.exampleがある場合
  • Goアプリケーションが起動時にYAML設定を読む場合
  • database.password、api.token、jwt.secretなどがconfig.yamlに直接記載されている場合

推奨する構成

text
config.yaml.example   # 샘플 설정만 포함, Git 커밋 가능
config.yaml           # 실제 운영값, Git 커밋 금지
.gitignore            # config.yaml 포함

例

yaml
server:
  port: 8080

database:
  host: prod-db.internal
  user: app_user
  password: super-secret-password

jwt:
  secret: hardcoded-jwt-secret

Pattern A: 環境変数による注入

環境変数を利用できるサービスでは、単純な構成で使用できます。

このパターンを選ぶ場面

  • シークレットの数が比較的少なく、環境変数で管理できる場合
  • デプロイシステムが環境変数の注入を提供する場合
  • 既存の設定ファイル構造を維持する必要がない場合

config.yaml.example

yaml
server:
  port: 8080

database:
  host: ${DB_HOST}
  user: ${DB_USER}
  password: ${DB_PASSWORD}

jwt:
  secret: ${JWT_SECRET}

Goのコード例

このコードは環境変数を直接読み取ります。yaml.Unmarshalは${ENV_VAR}を自動展開しないため、上のYAMLを読むローダーでは別途置換処理が必要です。

go
package config

import (
	"fmt"
	"os"
)

type Config struct {
	DBPassword string
	JWTSecret  string
}

func Load() (*Config, error) {
	cfg := &Config{
		DBPassword: os.Getenv("DB_PASSWORD"),
		JWTSecret:  os.Getenv("JWT_SECRET"),
	}

	if cfg.DBPassword == "" {
		return nil, fmt.Errorf("DB_PASSWORD is not configured")
	}
	if cfg.JWTSecret == "" {
		return nil, fmt.Errorf("JWT_SECRET is not configured")
	}

	return cfg, nil
}

Pattern B: マウントしたconfig.yamlの注入

アプリケーションがconfig.yamlを読む必要がある場合は、形式を維持し、実際のファイルを/etc/app/config.yamlなどに実行時にマウントしてください。

このパターンを選ぶ場面

  • アプリケーションがファイルによる設定読み込みに依存している場合
  • 階層化されたYAML構造を維持する必要がある場合
  • Kubernetes Secretボリュームや安全なファイル配布を運用標準としている場合

リポジトリに置くファイル:config.yaml.example

yaml
server:
  port: 8080

database:
  host: prod-db.internal
  user: app_user
  password: "<runtime-provided>"

jwt:
  secret: "<runtime-provided>"

実行時のファイルパス例

text
/etc/app/config.yaml

Goのコード例

go
package config

import (
	"fmt"
	"os"

	"gopkg.in/yaml.v3"
)

type FileConfig struct {
	Database struct {
		Password string `yaml:"password"`
	} `yaml:"database"`
	JWT struct {
		Secret string `yaml:"secret"`
	} `yaml:"jwt"`
}

func LoadFromFile(path string) (*FileConfig, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read config file: %w", err)
	}

	var cfg FileConfig
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parse config file: %w", err)
	}

	if cfg.Database.Password == "" {
		return nil, fmt.Errorf("database.password is not configured")
	}
	if cfg.JWT.Secret == "" {
		return nil, fmt.Errorf("jwt.secret is not configured")
	}

	return &cfg, nil
}

ファイルがない場合や必須のシークレットが空の場合は、直ちに処理を中止してください。

Goサービスでconfig.yamlが必須の場合

  • 実際のconfig.yamlはコミットしないでください。
  • リポジトリにはconfig.yaml.exampleだけを置いてください。
  • 実際のファイルは、Kubernetes Secretボリューム、CSI/Vault Agent、安全なファイル配布で実行時に供給してください。
  • 本番用のconfig.yamlをDockerイメージに含めないでください。
  • ファイル権限を最小限にし、誰でも読める状態を避けてください。
  • ファイルや必須のシークレットがなければ、起動を中止してください。
  • 設定ダンプ、panic、デバッグログではシークレットをマスキングしてください。
  • ローテーション時のファイル更新と再読み込み・再起動を運用手順に記載してください。

開発者の作業

  • サービスに合った環境変数方式またはファイル方式を選んでください。
  • os.Getenv()を使う処理やファイルローダーで、必須のシークレットがなければ処理を中止してください。
  • 設定オブジェクト全体をログに出さないでください。
  • ファイル方式では実行時のパスを文書化し、開発用シークレットのハードコードを削除してください。

インフラ・プラットフォーム担当者の作業

  • Pattern Aでは、デプロイ時の環境変数注入を構成してください。
  • Pattern Bでは、/etc/app/config.yamlなどの標準パスでファイルを供給してください。
  • Dockerイメージ、ConfigMap、リポジトリのコミットで本番ファイルを配布しないでください。
  • ローテーションと再デプロイ・再読み込みの手順を運用文書に含めてください。

検証方法

  • config.yaml.exampleに本番用のシークレットがないことを確認してください。
  • 実際のconfig.yamlがGitで追跡されていないことを確認してください。
  • ファイルや環境変数が欠けると、アプリケーションが起動に失敗することを確認してください。
  • ログ、panic、デバッグ出力にシークレットが出ないことを確認してください。

よくある誤り

  • 開発の便宜のため、本番用シークレット入りのconfig.yamlをコミットする
  • ファイルをマウントする構成でも、実ファイルをDockerイメージに含める
  • 空のシークレットを許容し、不正な状態で起動する
  • ConfigMapにシークレットを保存し、ファイルマウントだけで保護できると考える

担当者の対応手順

  1. config.yamlから実際のシークレットを削除してください。
  2. config.yaml.exampleにはキー名と形式の例だけを残してください。
  3. 実際のconfig.yamlを.gitignoreに追加してください。追跡済みならGitの追跡からも外す必要があります。.gitignoreだけでは過去のコミットの値は消えません。
  4. 環境変数方式では、os.Getenv()や設定ローダーで値を読んでください。
  5. ファイル方式では、/etc/app/config.yamlなど実行時のパスから読んでください。
  6. Kubernetesでは、運用に合った環境変数注入またはSecretボリュームを選んでください。
  7. 以前の値は露出したものとして扱い、無効化して再発行してください。

担当者への案内例

  • 「Goのconfig.yamlから実際のシークレットを削除し、config.yaml.exampleには形式の例だけを残してください。」
  • 「環境変数を使える場合はos.Getenv()で読み、ファイルが必須なら実行時に/etc/app/config.yamlへマウントしてください。」
  • 「config.yamlがコミット済みなら、シークレットは露出したものとして直ちにローテーションしてください。」

追加の確認事項

  • config.yaml.exampleに本番用の値がコピーされていないか。
  • ローダーが空のシークレットを許容していないか。
  • デバッグログに設定オブジェクト全体を出していないか。
  • 本番用のconfig.yamlがDockerイメージやConfigMapに含まれていないか。

関連ドキュメント