AWS CloudFormationスタックの出力をAnsibleで取得し、アプリに渡す方法

初めまして。2025年にSCSKに入社した新人の大原悠利と申します。現在、所属部署の伝統であるクラウド研修に参加しています。

今回はその研修の中で、自分が特に苦戦した「AWS CloudFormationの出力をAnsibleで利用する方法」についてご紹介します。この記事が、同じように悩む方の助けになれば嬉しいです。

 

実装する要件

概要

AWS CloudFormationと構成管理ツール(Ansible)を用いて、Amazon EC2上にアプリケーションを自動デプロイします。
プライベートネットワーク環境のPCからEC2にアクセスし、Webページが表示されることを確認します。

 

Ansibleとは?
Ansible® は、プロビジョニング、構成管理、アプリケーションのデプロイメント、オーケストレーション、その他多くの IT プロセスを自動化する、オープンソースの IT 自動化ツールまたは自動化エンジンのこと
参照:Ansible (アンシブル) とは?をわかりやすく解説
 

アーキテクチャ

本構成は以下の2つの観点で示します。

アーキテクチャ全体像

 

ユーザーはSCSK PCからRoute53を介してELBにアクセスし、プライベートサブネット内のEC2に接続します。
EC2はElastiCacheと連携し、HTTPS通信はCertificate Managerで管理します。

 

EC2インスタンス詳細

EC2にはJavaアプリケーションをホストし、Ansibleでデプロイを自動化します。
S3からアプリケーションを取得し、Systems Managerで運用管理、IAMで権限を制御します。

 

苦戦したところ

細かい要件はいろいろありますが、実装するにあたり特に苦戦した要件は次の1点です。

CloudFormationで取得したElastiCacheのエンドポイントを、Ansible経由でJVM引数としてアプリに渡す。

この要件を満たすために、私はAnsibleの amazon.aws.cloudformation_info モジュールを使う方法が良いと考えました。

 

amazon.aws.cloudformation_infoモジュールとは

Ansible公式ドキュメントには、以下のように記載されています。

Obtain information about an AWS CloudFormation stack
(AWS CloudFormationスタックに関する情報の取得)

参照:amazon.aws.cloudformation_info module – Obtain information about an AWS CloudFormation stack — Ansible Community Documentation

つまり、amazon.aws.cloudformation_infoモジュール(以降 CloudFormation_infoモジュール)を使えば、CloudFormationスタックの情報(Outputsなど)をAnsible上で取得できるということです。

 

使用例(抜粋)

- name: Get summary information about a stack
  amazon.aws.cloudformation_info:  # amazon.aws.cloudformation_infoモジュールを使用
    stack_name: my-cloudformation-stack  # 取得したいCloudFormationスタック名
  register: output  # 実行結果をoutput変数に格納

Outputsを取得するには以下のようにします。

- set_fact:
  stack_name: my-awesome-stack  # 対象のCloudFormationスタック名を指定

- amazon.aws.cloudformation_info:  # amazon.aws.cloudformation_infoモジュールを使用
    stack_name: "{{ stack_name }}"  # stack_name変数を参照して情報を取得し、my_stack変数に格納
  register: my_stack

- debug:
  msg: "{{ my_stack.cloudformation[stack_name].stack_outputs }}"  # my_stack.cloudformation[stack_name].stack_outputsでOutputsを取得

CloudFormation_infoモジュールの戻り値では、Outputsはstack_outputsキーに格納されます。例えば、registermy_stackとした場合、my_stack.cloudformation[stack_name].stack_outputs.ElastiCacheEndpointで取得できます。

このように、CloudFormation_infoモジュールを使えば、CloudFormationのOutputsをAnsibleで取得できることがわかります。

 

実装手順

実際には、CloudFormationのOutputsセクションンの設定とAnsible Playbookの設定を行いました。

CloudFormationのOutputs設定

まず、CloudFormationテンプレートの Outputs セクションにElastiCacheのエンドポイントを記述します。

Outputs:
  ElastiCacheEndpoint:
    Value: !GetAtt ElastiCacheCluster.ConfigurationEndpoint.Address  # ElastiCacheのエンドポイントを取得
    Export:
      Name: ElastiCacheEndpoint  # CloudFormation_infoモジュールで参照するための名前

 

Ansible Playbookの設定

次に、Ansibleでこの出力を取得するために、以下のようなPlaybookを作成しました。

- name: Get ElastiCache endpoint from CloudFormation
  amazon.aws.cloudformation_info:  # amazon.aws.cloudformation_infoモジュールを使用
    stack_name: my-app-stack  # 子スタックの名前(仮にmy-app-stackとおく)
    region: 'ap-northeast-1'  # リージョンの指定
  register: cf_output  # 実行結果を変数に格納

- name: Set ElastiCache endpoint as a fact
  set_fact:
    elasticache_endpoint: "{{ cf_output.cloudformation['my-app-stack'].stack_outputs.ElastiCacheEndpoint }}"  # CloudFormation Outputsで定義したElastiCacheEndpointを取得

この elasticache_endpointをJVM引数としてアプリに渡すことで、動的にエンドポイントを設定することができると予想しました。

しかし結果はうまくいきませんでした。

 

なぜうまくいかなかったか

子スタック名がわからなかった

CloudFormation_infoモジュールを使うには、対象のスタック名が必要です。親スタックの名前は作成時に指定できるため問題ありませんが、ネストされた子スタックはCloudFormationが自動で名前を生成するため、事前に把握するのが難しく、結果としてCloudFormationのOutputsを取得できませんでした。

 

Amazon Linux 2の環境制約

Amazon Linux 2では、デフォルトで古いバージョンのAnsibleやbotocoreがインストールされているため、使用要件に満たさずCloudFormation_infoモジュールが正常に動作しませんでした。研修の要件でAMIを変更できなかったため、ここも大きな制約でした。

実際にCloudFormation_infoモジュールを使うには、以下の環境が必要です

Ansible-core:2.13.9以上
Ansible amazon awsコレクション: 10.1.2以上
Python:3.6以上
boto3:1.34.0以上
botocore:1.34.0以上

研修環境ではAmazon Linux 2を使用していたため、これらのバージョンが古く、モジュールがうまく動作しませんでした。

参考:研修環境(Amazon Linux2の設定)
Ansible-core:2.9.25
Ansible amazon awsコレクション:未インストール
Python:3.7.16
boto3:未インストール
botocore:1.18.6

そこで、対策としてEC2インスタンスにログインして環境を修正しました。その後、再度Ansible Playbookを実行してアプリをデプロイしました。

 

対策

Ansible Playbookに子スタック名を直接記入

一度スタックを作成した後に、直接マネジメントコンソール上で確認したスタック名をAnsible Playbook内に記載しました。

- name: Get ElastiCache endpoint from CloudFormation
  amazon.aws.cloudformation_info:  # amazon.aws.cloudformation_infoモジュールを使用
    stack_name: hrd-d0-cfs-y-oohara-ApplicationquickstartStack-1902NZ480MKZ8  # 子スタックの名前
    region: 'ap-northeast-1'  # リージョンの指定
  register: cf_output  # 実行結果を変数に格納

- name: Set ElastiCache endpoint as a fact
  set_fact:
    elasticache_endpoint: "{{ cf_output.cloudformation['hrd-d0-cfs-y-oohara-ApplicationquickstartStack-1902NZ480MKZ8'].stack_outputs.ElastiCacheEndpoint }}"  # CloudFormation Outputsで定義したElastiCacheEndpointを取得

 

各ソフトウェアのアップデート

CloudFormation_infoモジュールの使用要件を満たすために、EC2インスタンスへ接続し古いAnsibleを削除し、各ソフトウェア (Ansible, Python3, boto3, botocore, Ansible amazon awsコレクション)をインストールしました。その後、Ansible Playbookを実行しました。

sudo yum remove ansible  # 古いAnsibleを削除
sudo yum install python3 -y  # Python3をインストール
sudo python3 -m pip install ansible boto3 botocore  # 最新のAnsibleとAWS SDK(boto3, botocore)をpipでグローバルインストール
ansible-galaxy collection install amazon.aws  # Ansible amazon awsコレクションのインストール 
ansible-playbook -c local /home/ssm-user/ansible/web_ui_startup.yml  # AnsibleのPlaybookをローカルモードで実行

改善した結果、Webページが表示されることを確認することができました。

 

別解(ペアのyabuさんの方法)

実際に研修中は、自分の方法では実装がかないませんでした。研修ではペアで作業しており、ペアのyabuさんが別のアプローチでこの問題を解決してくれました。自分とは考え方が違っていて、非常に勉強になったのでご紹介します。

大原の考え方
  • CloudFormationのOutputsからElastiCacheのエンドポイントを取得
  • Ansibleでその出力を受け取り、JVM引数としてアプリに渡す

この方法は理論上は正しいのですが、環境の制約(バージョンやスタック名の取得)でうまくいきませんでした。

yabuさんの考え方
  • CloudFormationのUserDataで、EC2インスタンスの環境変数にElastiCacheのエンドポイントを直接出力
  • その環境変数をAnsibleで参照してアプリに渡す
EC2インスタンスのUserData設定
UserData:
  Fn::Base64: !Sub |
    #!/bin/bash
    yum install -y aws-cfn-bootstrap  # CloudFormationのcfn-initやcfn-signalを利用するためにインストール
    echo ENDPOINT=${ElastiCache.RedisEndpoint.Address} | sudo tee -a /etc/environment  # ElastiCacheのRedisエンドポイントを環境変数に設定
    /opt/aws/bin/cfn-init -v \  # CloudFormationのMetadataに基づいて設定を適用
      --stack ${AWS::StackName} \  # 現在のスタック名を指定
      --resource EC2Instance \     # テンプレート内のEC2リソース論理ID
      --region ${AWS::Region} \    # デプロイ先リージョン
      --configsets Default         # 適用するConfigSet名(Ansible Playbookに記載)
Ansible Playbookの設定
- name: Run Spring Boot application
  shell: |
    source /etc/environment \  # ElastiCacheのエンドポイントなど環境変数を利用するため /etc/environmentを読み込み
    java -jar \
      -Dspring.profiles.active=d0 \
      -Dspring.data.redis.host=$ENDPOINT \ # Redis接続先を環境変数から設定
      /home/appuser/my-spring-app.jar

この方法なら、CloudFormation_infoモジュールのバージョン制約を気にする必要がなく、環境変数として扱えるため非常にシンプルです。

 

感想

このコードを見たとき、「なるほど!」と目から鱗でした。自分がモジュールにこだわりすぎていたことに気づき、柔軟な発想の大切さを学びました。この考え方を踏まえると、環境変数に子スタック名を記載すると、CloudFormation_infoモジュールが利用できるのかなと思います。

参考:yabuさんの記事
https://blog.usize-tech.com/aws-certification-five-study-methods/

 

実際のユースケース

今回の研修を通じて、「CloudFormation_infoモジュールを使う方法」と「EC2のUserDataで環境変数として渡す方法」の2つのアプローチを学びました。それぞれのユースケースを整理してみると、使い分けのポイントが見えてきました。そこで、Microsoft Copilotに二つのユースケースの違いを聞いてみました。以下回答になります。

Copilotによる回答
方法  向いているケース  メリット デメリット
CloudFormation_infoモジュール
  • 複数環境で構成管理
  • CI/CDで最新情報反映
  • 最新情報を動的取得
  • 冪等性が高い
  • コード一元管理
  • AWS API呼び出し必須
  • 実行速度やや低下
  • 環境要件あり(Ansible/Python/boto3など)
UserDataで環境変数を渡す方法
  • 単一環境で固定構成
  • AWS認証を避けたい場合
  • AWS認証不要
  • 高速
  • バージョン依存が少ない
  • 更新に弱い
  • 冪等性低い
  • セキュリティリスク

 

🤖 Copilotからの補足
このように、CloudFormation_infoはAnsible中心の構成管理に向いていて、UserDataはインスタンス起動時の初期設定に向いているという違いがあります。

 

この回答を踏まえて、どちらが優れているというよりは、目的に応じて使い分けることが重要だと感じました。

 

最後に

今回の研修では、CloudFormationの出力をAnsibleで取得してアプリに渡すというシンプルな要件に対して、予想以上に多くの課題がありました。環境制約やスタック名の取得など、技術的な壁に直面しましたが、ペアのyabuさんの別解やEC2インスタンスへの直接の対応を通じて、柔軟な発想の大切さを学びました。

この経験から得た教訓は次の2つです。

引き出しが多いと、焦らずに対応できる
バージョンや前提条件は、必ず確認する習慣をつけるべき

新人としてまだまだ未熟ですが、こうした試行錯誤を積み重ねて、少しずつ成長していきたいと思っています。
この記事が、同じように悩んでいる方のヒントになれば幸いです。ここまで読んでいただき、ありがとうございました!

タイトルとURLをコピーしました