2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカル環境変数ファイルに依存していたデプロイフローからの卒業

Last updated at Posted at 2025-01-29

現状の何が問題か

前提のリポジトリ構成

現在担当しているプロダクトは複数のフロントエンドとAPIサーバーのバックエンドが、下記のように単一のリポジトリで構成されています。

root 
├─ フロントA
├─ フロントB
├─ フロントC
├─ バックエンド
└─ 環境変数ファイル #.gitignore対象

このような構成の中で「フロント」「バックエンド」どの環境からも利用される共通の環境変数ファイルが1つある状態です。

この構成自体には問題はないのですが、デプロイフローには解決しなければいけない課題がありました。

解決しなければいけない課題

その課題というのが、各環境をデプロイするときにローカルの環境変数ファイルの値を元にデプロイが行われているということです。
ローカルの環境変数ファイルではdevelopmentの値だけではなくstagingおよびproductionの値も管理していました。
そしてこのローカルの環境変数ファイルのproductionの値を元にデプロイを行う、というフローになっていました。
以下イメージ図です。

バックエンドのデプロイフローイメージ

Image from Gyazo

各フロントエンドのデプロイフローイメージ

Image from Gyazo

ローカル環境変数ファイルのproductionの値を元にデプロイを行う ということで、デプロイを行う開発者間で環境変数ファイルを一意に保たないといけないという問題がありました。
もちろん秘匿情報が含まれているので環境変数ファイルはGit管理をできず、アナログな方法で一貫性を保っていました。
実際のやり取りがこちら。
Image from Gyazo

いつまでもこの手法に頼っていられないということで、デプロイフローの改善を行いました。

改善方針

達成したい要件

  • ローカルでproductionの環境変数を管理をせずにSecrets Managerで一元管理をしたい
  • 環境変数に追加や変更があった場合はSecrets Managerを修正するだけにしたい
  • S3に秘匿情報を含んだファイルがアップロードされているがこれをやめたい
  • 既存バックエンドのデプロイ構成にはあまり手を入れたくない

上記を踏まえて諸々検討して下記のようなデプロイフローに落ち着きました。

改善後バックエンドについて

改善後バックエンドのデプロイフローイメージ

Image from Gyazo

詳細

CodeBuild上にてSecrets Managerから値を取得するようにしました。
取得した値をどのように使うかが悩みポイントでしたが、元の実装にてenv.jsが存在することを前提に作り込まれていたので、CodeBuild上で動的にenv.jsを作成してビルドにて使用するようにしました。
buildspec.ymlからシェルを起動して実現しています。

# フロントエンドとバックエンド用のそれぞれのシークレットから値を取得
echo "Fetching environment variables from '$SECRETS_NAME_BACK'"
BACK_RAW_ENV_CONTENT=$(aws secretsmanager get-secret-value --secret-id $SECRETS_NAME_BACK --query SecretString --output text)
# Secrets Managerに保管されている値が変な形になっていたらエラーを起こして中断
echo "$BACK_RAW_ENV_CONTENT" | jq -e .
if [[ $? -ne 0 ]]; then
    echo "Error: '$SECRETS_NAME_BACK' contains invalid JSON"
    exit 1
fi

echo "Fetching environment variables from '$SECRETS_NAME_FRONT'"
FRONT_RAW_ENV_CONTENT=$(aws secretsmanager get-secret-value --secret-id $SECRETS_NAME_FRONT --query SecretString --output text)
# Secrets Managerに保管されている値が変な形になっていたらエラーを起こして中断
echo "$FRONT_RAW_ENV_CONTENT" | jq -e .
if [[ $? -ne 0 ]]; then
    echo "Error: '$SECRETS_NAME_FRONT' contains invalid JSON"
    exit 1
fi

# バックエンドとフロントエンドのシークレットを統合
echo "Merging back-end and front-end environment variables"
COMBINED_RAW_ENV_CONTENT=$(echo $BACK_RAW_ENV_CONTENT $FRONT_RAW_ENV_CONTENT | jq -s '.[0] * .[1]')

# バックエンドで使う秘匿情報なども全て含めたenv.jsを生成
echo "Generating env.js"
cat <<EOF > env.js
module.exports = function () {
  const env = JSON.parse(\`$COMBINED_RAW_ENV_CONTENT\`)
  console.log("Loaded env config for back-end.")
  console.log({ env })
  return env
}
EOF

バックエンドではDBの接続情報などの秘匿情報を含んだ状態のenv.jsが必要なので、Secrets Managerから取得した値をすべてenv.jsに展開しています。
このenv.jsを後のdockerビルドに使用しています。

一方、この後に行われるフロントエンド用の環境変数ファイルもこの段階で作成し、S3にアップロードしておきます。
フロントエンド用の環境変数ファイルには秘匿情報は不要なので、フロントエンド用の環境変数を保存したSecrets Managerを元にenvForFront.jsを作成します。

# フロントエンドで使う秘匿情報などは含んでいない envForFront.js を生成
echo "Generating envForFront.js"
cat <<EOF > envForFront.js
module.exports = function () {
  const env = JSON.parse(\`$FRONT_RAW_ENV_CONTENT\`)
  console.log("Loaded env config for front-end.")
  console.log({ env })
  return env
}
EOF

# フロントデプロイ時に利用するのでS3にenvForFront.jsをアップロード
echo "Uploading envForFront.js to S3"
aws s3 cp ./envForFront.js s3://$ENV_FILES_BUCKET/envForFront.js

フロントエンドデプロイ時にenvForFront.jsを作成することも考えられます。
しかしフロントが複数存在するのでenvForFront.jsを作成する箇所に一工夫必要なこと、現状のプロダクトのデプロイ性質上、環境変数が追加される場合はほぼ間違いなくバックエンドもデプロイが必要になるので、この方式を取りました。

改善後フロントエンドについて

改善後フロントエンドのデプロイフローイメージ

Image from Gyazo

詳細

今までローカルでビルドを行ってS3に成果物をアップロードしていましたが、それをCodeBuild上で行うようにしました。
バックエンドのデプロイ時に作成したenvForFront.jsをフロント側のCodeBuildでダウンロードしてビルドに使用しています。

buildspec.yml
phases:
  install:
    commands:
      - echo 'Fetching envForFront.js from S3 bucket'
      - aws s3 cp s3://$S3_ENV_FILES_BUCKET/envForFront.js ./envForFront.js

CodeBuildのビルドプロジェクトはフロントの数用意していますが、buildspec.ymlは共通のものを使っています。
CodeBuildに埋め込んだ環境変数や条件分岐を利用することで、共通化を図っています。
※例 各フロントに合わせてビルドコマンドを変更しています

buildspec.yml
  build:
    commands:
      - echo 'Building the application'
      - if [[ $FRONT_DIRECTORY == "front-a" ]]; then yarn generate; elif [[ $FRONT_DIRECTORY == "front-b" ]]; then ./node_modules/.bin/webpack --mode production; else NODE_ENV=$NODE_ENV yarn build; fi

複数のフロントエンドを一括でデプロイ

おまけとして今までのフロント毎のデプロイの他に、以下のようなシェルをローカルに用意することで、複数のフロントエンドを一括でデプロイできるようにしました。

deployAllFront.sh
#!/bin/bash

# 必須環境変数のチェック
if [[ "$AWS_PROFILE" == "" ]] || [[ "$PROJECT_NAMES" == "" ]]; then
  echo "必須の環境変数が設定されていません。"
  echo "AWS_PROFILE: $AWS_PROFILE"
  echo "PROJECT_NAMES: $PROJECT_NAMES"
  exit 1
fi

# 各プロジェクトのビルドを開始
for PROJECT_NAME in $PROJECT_NAMES; do
  echo "Starting deploy for project: $PROJECT_NAME"
  aws codebuild start-build --project-name "$PROJECT_NAME" --region ap-northeast-1 --profile "$AWS_PROFILE" --no-cli-pager

  if [[ $? -ne 0 ]]; then
    echo "Failed to start build for project: $PROJECT_NAME"
    exit 1
  fi
done

echo "All fronts deploy started successfully."

PROJECT_NAMESの変数で渡された複数のCodeBuildプロジェクトをデプロイしていきます。

起動コマンドはpackage.jsonにscriptsとして用意したこちら yarn deployAllFront:prod

pacage.json

{
  "scripts": {
    "deployAllFront:prod": "AWS_PROFILE=hoge PROJECT_NAMES='front-a front-b front-c' ./deployAllFront.sh"
  }
}

勉強になったAWSリソースの挙動

Secrets ManagerはJSONで定義できる

今までSecrets ManagerはコンソールのUIから打ち込んでいましたが、これだと数値や真偽値が文字列型として保管されてしまいました。

Image from Gyazo

こちらはプレーンテキストタブからJSONを編集することで解決しました。
環境変数が大量にあるときはUIから一つずつ入力するのが手間なので、JSONで一括定義できるのは非常に便利でした。(知らんかった)

Image from Gyazo

ただ、こちらプレーンテキストをコンソールから直接編集すると、誤った形のJSONを保存してしまうなどオペレーションミスを引き起こす運用上の懸念がありました。
下記にてこちらを解決できるようなシェルツールを作成しています。

CodeBuildでインストールした依存関係はキャッシュできる

フロントエンドをCodeBuildでビルドするようになったので、依存関係をインストールする必要がありました。
CodeBuildはインスタンスが変わるので毎回初回インストール扱いとなり、パッケージのインストールyarnに時間がかかっていました。
S3にキャッシュを保存することで速度改善を試みています。

※インストール自体は速くなりますがS3のキャッシュをダウンロードするのに多少時間がかかります。

設定方法

buildspec.ymlにキャッシュ対象を記載する

buildspec.yml
cache:
  paths:
    - 'node_modules/**/*'
    - 'yarn.lock'

CodeBuildの設定で保存先バケットを設定する
Image from Gyazo

CodeBuildのビルドが完了すると設定したバケットにキャッシュが保存される
Image from Gyazo

キャッシュ結果の比較

◆before

S3からのキャッシュダウンロード 0秒
yarnでのパッケージインストール 127秒
Image from Gyazo
合計 127秒

◆after

S3からのキャッシュダウンロード 61秒
Image from Gyazo
yarnでのパッケージインストール 0秒
Image from Gyazo
合計 61秒

ということで、127秒→61秒と66秒の短縮になりました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?