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にシークレットを保存し、ファイルマウントだけで保護できると考える
担当者の対応手順
config.yamlから実際のシークレットを削除してください。config.yaml.exampleにはキー名と形式の例だけを残してください。- 実際の
config.yamlを.gitignoreに追加してください。追跡済みならGitの追跡からも外す必要があります。.gitignoreだけでは過去のコミットの値は消えません。 - 環境変数方式では、
os.Getenv()や設定ローダーで値を読んでください。 - ファイル方式では、
/etc/app/config.yamlなど実行時のパスから読んでください。 - Kubernetesでは、運用に合った環境変数注入またはSecretボリュームを選んでください。
- 以前の値は露出したものとして扱い、無効化して再発行してください。
担当者への案内例
- 「Goの
config.yamlから実際のシークレットを削除し、config.yaml.exampleには形式の例だけを残してください。」 - 「環境変数を使える場合は
os.Getenv()で読み、ファイルが必須なら実行時に/etc/app/config.yamlへマウントしてください。」 - 「
config.yamlがコミット済みなら、シークレットは露出したものとして直ちにローテーションしてください。」
追加の確認事項
config.yaml.exampleに本番用の値がコピーされていないか。- ローダーが空のシークレットを許容していないか。
- デバッグログに設定オブジェクト全体を出していないか。
- 本番用の
config.yamlがDockerイメージやConfigMapに含まれていないか。