👋

🧹.env にさよなら党おのサヌビス開発者が実感するビルドプロセス改善🔐⏱⚙

に公開

はじめに

こんにちは株匏䌚瀟ダむニヌの Platform Team に所属しおいたす。0tanyです。

モダンなモゞュラヌモノリスアヌキテクチャでは、環境倉数の管理が重芁な課題の䞀぀です。事業成長ずずもにサヌビス数ず環境数が増加するず、その管理耇雑性は指数関数的に増倧しおいきたす。

本蚘事では、この移行を通じお埗られた知芋を共有したす。同様の課題を抱えるチヌムの参考になれば幞いです。

埓来のアヌキテクチャの問題点

環境別の.env ファむルず Docker むメヌゞの管理地獄

埓来アヌキテクチャ図

䞊蚘が埓来のアヌキテクチャです。ダむニヌでは飲食店向けのモバむルオヌダヌサヌビスを 4 ぀の環境develop/staging/beta/productionで運甚しおおり、それぞれの環境に察しお 4 ぀のサヌビスweb、backend、backend-online-payment、backend-reservationがデプロむされおいたす。

backend 系の 3 サヌビスは共通のコヌドベヌスを䜿甚しおいたすが、各サヌビスで異なる環境倉数を必芁ずするため、それぞれ個別の Docker むメヌゞずしおビルドしおいたす。これらのサヌビスを Cloud Run でホスティングしおいたす。

埓来の運甚フロヌ

  1. 開発者の䜜業各サヌビス・各環境甚の.env ファむルを 16 個管理
  2. 暗号化凊理KMS を䜿甚しお.env ファむルを.env.enc ずしお暗号化
  3. バヌゞョン管理暗号化された 16 個の.env.enc ファむルを GitHub にコミット
  4. CI/CD ビルド環境別に 16 個の異なる Docker むメヌゞをビルド
  5. デプロむ各環境専甚の Artifact Registry から.env 内蔵むメヌゞを Cloud Run ぞデプロむ

この運甚は機胜はしおいたしたが、芋おいただくずわかるように、だいぶ無駄の倚い耇雑な構成になっおいたす。

具䜓的な問題を敎理するず

🔒 セキュリティの問題

  • ロヌカル環境に埩号化された.env ファむルが残存するこずによるセキュリティリスク
  • production の.env も開発者ロヌカルに存圚し埗るため最小特暩の原則に反する

⏱ ビルド効率の問題

  • コヌドベヌスは同じなのに.env ファむルが違うだけの重耇むメヌゞが倚数生成される
  • 環境倉数を 1 ぀倉曎するだけで関連する党おの環境の再ビルドが必芁玄 120 分
  • 4 環境 × 4 サヌビス = 16 個の異なる Docker むメヌゞを管理

察象サヌビスの内蚳

  • web: フロント゚ンドアプリケヌション独立したコヌドベヌス
  • backend ç³»: 共通コヌドベヌスで実装された 3 ぀のモゞュラヌモノリス
    • backend: メむンのバック゚ンド API
    • backend-online-payment: オンラむン決枈サヌビス
    • backend-reservation: 予玄管理サヌビス

📝 バヌゞョン管理の問題

  • .env.enc ファむル党䜓を暗号化するため、個別 Secret の倉曎履歎が远跡䞍可胜
  • .env の誀線集が発生したずきに远跡が煩雑。該圓 commit の.env.enc を埩号化しファむル差分をチェックする必芁がある

この状況を改善するため、Google Secret Manager ぞの移行を決断したした。

Secret Manager を掻甚した新アヌキテクチャ

蚭蚈原則

  1. 環境ずコヌドの完党分離: 12 Factor App の原則に埓う
  2. Single Source of Truth: Secret Manager を唯䞀の情報源ずする
  3. 最小暩限の原則: サヌビスアカりントごずに必芁最小限の暩限のみ付䞎
  4. Infrastructure as Code: Cloud Development Kit for Terraform(cdktf) による宣蚀的管理

アヌキテクチャ抂芁

新アヌキテクチャ図

新しいアヌキテクチャでは、埓来の耇雑な図ず察照的にシンプルな構成を実珟したした。

キヌポむント

  • 開発者は .env ファむルを管理しない
  • 統䞀された Docker むメヌゞを党環境で䜿い回し
  • Secret Manager が唯䞀の機密情報源ずしお機胜
  • 環境ごずの差分は実行時に動的に泚入

埓来の 16 個のビルドプロセスが、わずか 2 個の統䞀ビルドに集玄されたす。具䜓的には、web 甚の統䞀むメヌゞずbackend ç³» 3 サヌビス共通の統䞀むメヌゞの 2 ぀です。

backend 系は共通のコヌドベヌスを掻甚し、1 ぀の Docker むメヌゞに統合したした。このむメヌゞを backend、backend-online-payment、backend-reservation の 3 ぀の Cloud Run サヌビスで共有しお䜿甚したす。各サヌビスの違いは、Secret Manager から泚入される環境倉数によっお実珟されるため、むメヌゞ自䜓は完党に同䞀です。

この方匏により、コヌドベヌスが同じサヌビスで無駄にむメヌゞを分ける必芁がなくなりたした。図を芋おいただくず、線の数の違いは䞀目瞭然です。

実装詳现

ダむニヌでは党瀟的な暙準技術ずしお TypeScript を採甚する方針ずなっおいたす。

この方針の背景や経緯に぀いお興味のある方は、以䞋の蚘事もあわせおご芧ください。

https://whatweuse.dev/article/dinii

そのためむンフラの構成管理も通垞の Terraform(HCL)ではなく Cloud Development Kit for Terraform (CDKTF) を利甚し、TypeScript にお実装・管理をしおいたす。

前述のアヌキテクチャ抂芁で瀺した新しい構成を実珟するために、3 ぀の䞻芁コンポヌネントを実装したしたので、それぞれの具䜓的な実装䟋を芋おいきたしょう。

1. Secret Manager の定矩cdktf

Secret Manager 䞊の Secret 管理に関連する実装が以䞋ずなりたす。
これらの実装で管理するのは secretId や secret のバヌゞョン等 Secret Manager ずしおの蚭定呚りのみので、 Secret そのもの(SecretValue)の倀の登録は gcloud コマンドから登録する想定です。

// SecretManager実装抂芁
type SecretConfig = {
  secret_id: string;
  version: string;
  replication:
    | { type: "auto" }
    | { type: "userManaged"; locations: readonly string[] };
  enabled: boolean; // 察象Secretの有効化状態のflag
  shouldImport: boolean; // 察象Secretをtfstateぞimportする際のflag
};

const createSecret = (
  secret_id: string,
  overrides?: Partial<Omit<SecretConfig, "secret_id">>
): SecretConfig => ({
  secret_id,
  version: "1", // Secretのdefault versionは1
  replication: { type: "userManaged", locations: ["asia-northeast1"] },
  enabled: true,
  shouldImport: true,
  ...overrides,
});

// 環境別のSecret定矩
const productionSecrets: readonly SecretConfig[] = [
  createSecret("backend-app-env"),
  createSecret("backend-default-database-url", { version: "2" }), // Secret versionを曎新するずきは匕数でversionを指定
  createSecret("backend-redis-host"),
  // ... 他のSecrets
];

2. Cloud Run での Secret 参照

Secret Manager で定矩した Secret を、Cloud Run の環境倉数ずしお動的に泚入したす。

// 環境倉数名ずSecret IDのマッピング
const secretEnvMappings = [
  { env: "DATABASE_URL", secret: "backend-database-url" },
  { env: "REDIS_HOST", secret: "backend-redis-host" },
  { env: "PARTNER_API_KEY", secret: "backend-partner-api-key" },
  // 環境別で必芁なSecretのみを遞択
  // Secret IDは必ず {serviceName}-{secret-name} の圢匏
];

// Secret環境倉数の生成
const createSecretEnvVar = (
  envName: string,
  secretId: string,
  secretVersionMap: SecretVersionMap
) => ({
  name: envName,
  valueFrom: {
    secretKeyRef: {
      name: secretId,
      key: secretVersionMap.get(secretId)?.version || "latest",
    },
  },
});

// Cloud Runサヌビス定矩(抜粋)
const cloudRunService = new CloudRunService(scope, serviceName, {
  template: {
    spec: {
      containers: [
        {
          env: secretEnvMappings.map(({ env, secret }) =>
            createSecretEnvVar(env, secret, secretVersionMap)
          ),
        },
      ],
    },
  },
});

これにより、Cloud Run 起動時に Secret Manager から指定バヌゞョンの倀を取埗し、アプリケヌションの環境倉数ずしお利甚できたす。

3. Secret Manager ぞのアクセス暩限

Cloud Run から Secret Manager の倀を読み取るため、サヌビスアカりントに最小限の暩限を远加付䞎したす。

// 必芁な暩限
roles: ["roles/secretmanager.secretAccessor"]; // Secret読み取りのみを远加

移行時のベストプラクティス

1. 既存の.env から Secret Manager ぞの自動登録

環境倉数は UPPER_SNAKE_CASE で蚭定されたすが、Secret Manager 䞊の Secret ID は kebab-case でないず登録できたせん。そのため以䞋の倉換芏則を定めたした。

# 倉換ルヌルENV_VAR → Secret ID
API_KEY → ${serviceName}-api-key
DATABASE_URL → ${serviceName}-database-url
REDIS_HOST → ${serviceName}-redis-host

以䞋は移行スクリプトの䟋です。

// 移行スクリプト実装抂芁
const migrateEnvToSecretManager = async (
  envFile: string,
  serviceName: string
) => {
  const envContent = readFileSync(envFile, "utf-8");

  await Promise.all(
    envContent
      .split("\\n")
      .filter((line) => line && !line.startsWith("#"))
      .map(async (line) => {
        const [key, ...valueParts] = line.split("=");
        const value = valueParts.join("=");

        // アンダヌスコアをハむフンに倉換、小文字化
        const secretId = `${serviceName}-${key
          .replace(/_/g, "-")
          .toLowerCase()}`;

        // gcloud コマンドで Secret を䜜成
        await $`echo -n ${value} | gcloud secrets create ${secretId} --data-file=-`;
      })
  );
};

2. 差分チェックツヌルの実装

たた、移行期間䞭.env がアップデヌトされ移行挏れの Secret が生たれないよう、.env ファむルず Secret Manager の同期を確認するツヌルを実装したした。
埓来の.env.enc ファむルを曎新しようずした際にチェックスクリプトを発火させ、Secret Manager ぞの移行の過皋で移行挏れが発生しないようにしたす。

// 差分チェックスクリプト実装抂芁
const checkSecret = async (
  environment: string,
  targetEnvVar: string,
  serviceName: string
) => {
  const projectId = ENV_PROJECT_MAP[serviceName][environment];
  const envFilePath = path.join(
    "deployments",
    serviceName,
    environment,
    ".env"
  );

  // .envファむルの読み蟌み
  const envContent = await fs.promises.readFile(envFilePath, "utf-8");
  const envVars = dotenv.parse(envContent);

  // Secret IDの生成環境倉数名を kebab-case に倉換
  const secretId = targetEnvVar.toLowerCase().replace(/_/g, "-");
  const secretName = `projects/${projectId}/secrets/${serviceName}-${secretId}`;

  // Secret Managerから最新バヌゞョンを取埗
  const [versions] = await client.listSecretVersions({
    parent: secretName,
    filter: "state:ENABLED",
  });

  const [latestVersion] = versions;
  const [version] = await client.accessSecretVersion({
    name: latestVersion.name,
  });
  const secretValue = version?.payload?.data?.toString() ?? "";

  // 倀の比范
  if (secretValue !== envVars[targetEnvVar]) {
    throw new Error(`${environment}: ${targetEnvVar} value mismatch`);
  }

  return `${environment}: ${targetEnvVar} is up to date`;
};

// バッチ凊理で䞊列実行。倧量実行するずSecretManagerのRatelimitに匕っかかるので泚意
const results = await processInBatches({
  items: Object.keys(envVars),
  processFn: async (envVar) => checkSecret(environment, envVar, serviceName),
  batchSize: 10, // 同時実行数の制限
});

移行の成果ず今埌の展望

解決した 3 ぀の課題

1. セキュリティの飛躍的向䞊

  • 開発者ロヌカル環境から.env ファむルを完党に排陀
  • Cloud Run が Secret Manager から動的に環境倉数を取埗する仕組みを実珟

2. ビルド効率の劇的改善

  • 統䞀 Docker むメヌゞによる環境非䟝存ビルドの実珟
  • 総ビルド時間: 120 分 → 16 分87%削枛
    • 埓来: web(9 分 ×4 環境) + backend ç³» 3 ぀(7 分 ×4 環境 ×3) = 120 分
    • 新芏: web(9 分) + backend 統合(7 分) = 16 分
  • 管理察象むメヌゞ数: 16 個 → 2 個87.5%削枛

3. Secret 管理の透明性確保

  • Secret Manager 䞊の Secret の個別管理による詳现な倉曎管理の実珟
  • .env.encファむルの暗号化埩号化䜜業を廃止

定量的成果

指暙 Before After 改善率
総ビルド時間 120 分 16 分 87%削枛
管理察象むメヌゞ数 16 個 2 個 87.5%削枛

今埌の展望

実は今回の移行察象は Cloud Run でホスティングされおいるサヌビスのみずしたした。ダむニヌでは Firebase Hosting でホストされおいるサヌビスもあり、そちらはただ埓来の.env 運甚が残っおいたす。

Firebase Hosting ではパラメヌタ化された構成ずファむルベヌスの環境倉数の構成を採甚可胜ですが、Secret 管理を Secret Manager に統䞀し぀぀、パラメヌタ化された構成ぞの移行を怜蚎しおいたす。

たずめ

.env ファむルから Secret Manager ぞの移行は、単なるツヌルの眮き換えではありたせん。12 Factor App の原則に沿った、よりスケヌラブルで安党なアヌキテクチャぞの進化です。

特に重芁なのは、環境ずコヌドの分離ずいう原則を培底するこずで、ビルド時間の削枛だけでなく、セキュリティず運甚効率の倧幅な改善を実珟できたこずです。

同様の課題を抱えおいるチヌムの参考になれば幞いです。

最埌に

ダむニヌでは、このような技術的な課題解決に取り組む゚ンゞニアを募集しおいたす。アヌキテクチャの最適化やDeveloperExperienceの向䞊に興味がある方は、ぜひ䞀床お話したしょう

https://diniinote.notion.site/Dinii-Engineering-EntranceBook-1df71045ad748062afe9c672363bdcab


参考文献

Discussion