MENU
category

CloudFormationのパラメータをEC2 UserDataで使う方法|Service Catalogでも利用可能

CloudFormationでEC2を作成するとき、スタック作成時に入力したパラメータをUserDataのシェルスクリプトへ渡せます。基本形はFn::Base64とFn::Subの組み合わせです。AWS Service CatalogのCloudFormation製品でも、同じテンプレート記法を利用できます。

この記事の要点

  • Fn::SubでCloudFormationパラメータをUserData内へ展開できる
  • UserDataはFn::Base64でBase64エンコードする
  • Service Catalog製品でもCloudFormationテンプレートの仕様は同じ
  • UserDataの上限はBase64エンコード前で16KB
  • パスワードやAPIキーをUserDataへ埋め込まない
  • 秘密値はEC2のIAMロールを使って起動後に取得する
目次

Fn::Subでパラメータを渡す基本形

次の例では、スタック作成時に指定した環境名とアプリケーション名をUserDataへ渡します。

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  ApplicationEnvironment:
    Type: String
    Default: dev
    AllowedValues: [dev, staging, prod]

  ApplicationName:
    Type: String
    Default: sample-app

  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

Resources:
  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref LatestAmiId
      InstanceType: t3.micro
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            mkdir -p /etc/sample-app
            cat > /etc/sample-app/app.conf <<'EOF'
            APP_NAME=${ApplicationName}
            APP_ENV=${ApplicationEnvironment}
            STACK_NAME=${AWS::StackName}
            AWS_REGION=${AWS::Region}
            EOF

Fn::Subは、${ApplicationName}のようなテンプレートパラメータを実際の値へ置換します。${AWS::StackName}や${AWS::Region}などの擬似パラメータも利用できます。

Fn::Subで参照できる値

書き方取得する値
${ParameterName}Parametersセクションの値
${LogicalId}リソースに対するRefの戻り値
${LogicalId.Attribute}Fn::GetAttで取得できる属性
${AWS::Region}擬似パラメータ
変数マップ別の組み込み関数で求めた値

シェル側で${VAR}をそのまま使いたい場合はCloudFormationの置換と衝突します。文字列として残すには${!VAR}と記述します。起動テンプレート内では、さらにバックスラッシュを付けます。

変数マップを使う書き方

UserData:
  Fn::Base64:
    Fn::Sub:
      - |
        #!/bin/bash
        echo "BUCKET_NAME=${BucketName}" > /etc/app.env
        echo "ENVIRONMENT=${EnvironmentName}" >> /etc/app.env
      - BucketName: !Ref AppBucket
        EnvironmentName: !Ref ApplicationEnvironment

参照する値に別名を付けたい場合や、Fn::FindInMapなどの結果を埋め込みたい場合は、Fn::Subの変数マップを使います。

Service Catalogでも同じ記法を使える

AWS Service CatalogのCloudFormation製品は、プロビジョニングアーティファクトとしてCloudFormationテンプレートを使用します。製品起動時の入力値はテンプレートのParametersへ渡されるため、UserData内でのFn::SubやRefの使い方も通常のCloudFormationスタックと同じです。

テンプレート制約や起動制約を設定している場合、利用者が選択できる値や、リソースを作成するIAMロールは制約の影響を受けます。EC2が起動後にAWS APIへアクセスする権限は、EC2へ付与したインスタンスプロファイルで決まります。

秘密情報をUserDataへ直接渡さない

UserDataにパスワード、APIキー、データベース認証情報を埋め込む設計は避けます。CloudFormationパラメータへNoEcho: trueを指定しても、値をUserDataへ展開すればEC2側へ平文で渡されます。NoEchoは画面表示をマスクする機能であり、秘密値を安全に保管する仕組みではありません。

CloudFormationの公式ドキュメントでは、Secrets ManagerやSSM Parameter Storeの安全な値への動的参照は、EC2のUserDataプロパティではサポートされていません。テンプレートへ動的参照を書けば安全にUserDataへ展開できる、という前提では設計できません。

安全な取得フロー

  1. 秘密情報をSecrets ManagerまたはParameter StoreのSecureStringへ保存する
  2. EC2へIAMロールとインスタンスプロファイルを付与する
  3. 対象の秘密情報だけを取得できる最小権限ポリシーを設定する
  4. UserDataには秘密値ではなく、シークレット名やパラメータ名だけを渡す
  5. 起動後のスクリプトやアプリケーションからAWS CLIまたはSDKで値を取得する
  6. 取得した値をログへ出力しない
secret_json=$(aws secretsmanager get-secret-value 
  --secret-id "$SECRET_ID" 
  --query SecretString 
  --output text 
  --region "$AWS_REGION")

# secret_jsonをログへ出力しない
# 必要な設定処理へ安全に渡す

UserData利用時の注意点

  • サイズ:Base64エンコード前で16KBが上限
  • 実行タイミング:一般的なLinuxのcloud-initでは初回起動時に実行され、再起動のたびに実行されるとは限らない
  • 更新影響:AWS::EC2::InstanceのUserData更新は、ルートがEBSの場合に再起動を伴う
  • 失敗検知:cfn-signalとCreationPolicyを使い、初期化失敗をスタックへ返す
  • ログ:/var/log/cloud-init-output.logなどを確認し、秘密値を出力しない
  • 長い処理:大きなスクリプトはS3や構成管理へ分離し、UserDataは取得と起動に絞る

よくあるエラー

症状主な原因と対処
テンプレートの構文エラー!Base64 !Subを同じ行に連結せず、どちらかを長形式にする
シェル変数が空になる${VAR}をCloudFormationが置換している。${!VAR}を使う
UserDataが実行されないshebang、改行、cloud-initの状態とログを確認する
Service Catalog経由でAWS API呼び出しに失敗製品の起動ロールとEC2インスタンスロールを分けて確認する
秘密値を取得できないIAM、KMS権限、VPC内の通信経路を確認する

まとめ

CloudFormationの入力パラメータは、Fn::Base64とFn::Subを使ってEC2 UserDataへ渡せます。この方法はAWS Service CatalogのCloudFormation製品でも共通です。秘密値はUserDataへ直接展開せず、EC2のIAMロールを使って起動後にSecrets ManagerやParameter Storeから取得してください。短い初期化はUserData、複雑な構成管理はcfn-initなどへ分けると、失敗原因を追いやすくなります。

参考資料

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次