mito’s blog

IT技術メインの雑記。思い立ったが吉日。

[Ansible][AWS] ParameterStoreモジュールでユーザとパスワードを登録する

はじめに

community.awsにあるssm_parameterモジュールで、ユーザとパスワードを登録します。

community.aws.ssm_parameter module – Manage key-value pairs in AWS Systems Manager Parameter Store — Ansible Documentation


AWS Systems Manager(SSM)パラメータストアは名前にシークレットと付かないし、同系統のサービスでシークレットマネージャーがあるためセキュリティは強くないイメージを持ってしまいますが、そんなことはありません。
オプションには string_type があり、平文のString StringList、暗号化のSecureStringと選択できます。


環境

  • ansible core: 2.13.7
  • community.aws: 5.0.0

5.0.0以降ののモジュール名はcommunity.aws.ssm_parameterで、それより前はcommunity.aws.aws_ssm_parameter_storeです。
使い方は特に変わりありません。


パラメータストアにユーザとパスワードを登録する

パラメータストアは、1組のkey:value(またはlist)が登録できます。
複数のkey:valueが登録できるシークレートマネージャとは異なります。

Playbook

2組のkey:valueを登録するので、それぞれモジュールを実行します。
また、パラメータストアの値はlookupプラグインで取得できます。

---
- hosts: localhost
  gather_facts: no
  
  tasks:
    - name: Create key/value pair in AWS SSM parameter store
      community.aws.ssm_parameter:
        name: "web_user"
        string_type: "String"          # ユーザの種別はStringを指定
        value: "admin"

    - name: Create key/value pair in AWS SSM parameter store
      community.aws.ssm_parameter:
        name: "web_password"
        string_type: "SecureString"    # パスワードの種別はSecureStringを指定
        value: "oioioioi"

    - debug:
        msg: "{{ lookup('amazon.aws.aws_ssm', item) }}"  # lookupプラグインを使って呼び出す
      loop:
        - "web_user"
        - "web_password"


実行ログ

想定通りに登録されました。

$ ansible-playbook add_paramater.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************************

TASK [Create key/value pair in AWS SSM parameter store] ***********************************
changed: [localhost]

TASK [Create key/value pair in AWS SSM parameter store] ***********************************
changed: [localhost]

TASK [debug] ******************************************************************************
ok: [localhost] => (item=web_user) => {
    "msg": "admin"
}
ok: [localhost] => (item=web_password) => {
    "msg": "oioioioi"
}

PLAY RECAP ********************************************************************************
localhost : ok=3  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   

$ 





「/」で階層構造を表示する

nameの先頭に「/」を付ける必要があります。
複数のkey:valueをまとめられるシークレットマネージャーと比べると見づらい部分もありますが、
先頭にシステム名を入れスラッシュで区切ると、登録数が増えても幾分か見やすくなります。

Playbook
---
- hosts: localhost
  gather_facts: no
  
  tasks:
    - name: Create key/value pair in AWS SSM parameter store
      community.aws.ssm_parameter:
        name: "/web/user"             # 先頭に必ず「/」を入れる
        string_type: "String"
        value: "admin"

    - name: Create key/value pair in AWS SSM parameter store
      community.aws.ssm_parameter:
        name: "/web/password"         # 先頭に必ず「/」を入れ
        string_type: "SecureString"
        value: "oioioioi"

    - debug:
        msg: "{{ lookup('amazon.aws.aws_ssm', item) }}"
      loop:
        - "/web/user"
        - "/web/password"


実行ログ

無事に取得できました。

$ ansible-playbook add_paramater.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] **************************************************************************

TASK [Create key/value pair in AWS SSM parameter store] ***********************************
changed: [localhost]

TASK [Create key/value pair in AWS SSM parameter store] ***********************************
changed: [localhost]

TASK [debug] ******************************************************************************
ok: [localhost] => (item=/web/user) => {
    "msg": "admin"
}
ok: [localhost] => (item=/web/password) => {
    "msg": "oioioioi"
}

PLAY RECAP ********************************************************************************
localhost : ok=3  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

$ 


備考

値を登録するだけであれば、パラメータストアは値の履歴が残り、課金されるまでの上限も余裕があるため、シークレットマネージャーよりも使いやすいかもしれません。
また、CloudFormationではSecureStringは作成できないようです。

[Ansible][AWS] SecretsManagerモジュールでユーザとパスワードを登録する(更新2023.02.13)

はじめに

community.awsにあるsecretsmanager_secretモジュールで、ユーザとパスワードを登録します。

community.aws.secretsmanager_secret module – Manage secrets stored in AWS Secrets Manager — Ansible Documentation



環境

  • ansible core: 2.13.7
  • community.aws: 5.0.0

community.awsの最新版は、Ansible Core 2.11.0以前のバージョンをサポートしていません。

Ansible version compatibility Tested with the Ansible Core 2.12, and 2.13 releases, and the current development version of Ansible. Ansible Core versions before 2.11.0 are not supported. In particular, Ansible Core 2.10 and Ansible 2.9 are not supported.


AWS Secrets Managerにユーザとパスワードを登録する

Playbook

ほぼ公式ドキュメント記載のサンプル通りです。
2つのkey:valueを登録するので、json_secretオプションを指定します。
また、ローテーションは指定していないので無効のままです。

注意点として、SecretsManagerのシークレット名は完全に削除されるまで最低7日間の待機期間が必要です。
7日以内に同じシークレット名を再作成することはできないので、短期間で再作成するならユニークな文字列を付与することになります。

---
- hosts: localhost
  gather_facts: no
  
  tasks:
    - name: Add Secrets Manager
      community.aws.secretsmanager_secret:
        name: "web"
        state: present
        secret_type: 'string'
        # json_secretは、登録するJSON形式のデータを指定します。
        # secretと排他関係にあります。secretは、文字列またはバイナリ データを登録するときに指定します。
        json_secret: '{"user":"ec2-user", "password":"12345678"}'


実行ログ

想定通りに登録されました。

 $ ansible-playbook add_secretmanager.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ************************************************************

TASK [Add Secrets Manager] **************************************************
changed: [localhost]

PLAY RECAP ******************************************************************
localhost : ok=1  changed=1  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   

$ 




AWS Secrets Managerからユーザ、パスワードを取得する

Playbook

登録したなら取り出します。
lookupプラグインを使い、シークレット名を指定します。

---
- hosts: localhost
  gather_facts: no
  
  tasks:
    - debug:
        msg: "{{ lookup('amazon.aws.aws_secret', 'web') }}"


実行ログ

さらっと取得できました。

 $ ansible-playbook get_secretmanager.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ***********************************************************

TASK [debug] ***************************************************************
ok: [localhost] => {
    "msg": {
        "password": "12345678",
        "user": "ec2-user"
    }
}

PLAY RECAP *****************************************************************
localhost : ok=1  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   

$ 


※2023.02.13 追加

AWS Secrets Managerのパスワードを更新する

登録済みのパスワードを更新します。
シークレットに複数のkey:valueが登録されている場合、一部のみの更新・追加はできません。
変更が無いkey:valueは削除され、更新したkey:value、追加したkey:valueのみになります。
そのため、パスワードの更新とともにユーザを再登録します。

Playbook

登録したパスワードを更新します。
ユーザは変更ありませんが、シークレットから取得します。

---
- hosts: localhost
  gather_facts: no
  
  tasks:
    - debug:
        msg: "{{ lookup('amazon.aws.aws_secret', 'web') }}"
      register: result

    - name: change password for Secrets Manager
      community.aws.secretsmanager_secret:
        name: "web"
        state: present
        secret_type: 'string'
        json_secret: "{{ new }}"
      vars:
        new:
          user: "{{ result.msg.user }}"
          password: "oioioioi"    # 更新するパスワード

    - debug:
        msg: "{{ lookup('amazon.aws.aws_secret', 'web') }}"


実行ログ

無事、パスワードが更新できました。

$ ansible-playbook update_secretmanager.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ***********************************************************

TASK [debug] ***************************************************************
ok: [localhost] => {
    "msg": {
        "password": "12345678",
        "user": "ec2-user"
    }
}

TASK [change password for Secrets Manager] *********************************
changed: [localhost]

TASK [debug] ***************************************************************
ok: [localhost] => {
    "msg": {
        "password": "oioioioi",
        "user": "ec2-user"
    }
}

PLAY RECAP *****************************************************************
localhost : ok=3  changed=1  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   

$ 


AWS Secrets Managerのパスワード更新に失敗するパターン

シークレットに複数のkey:valueが登録されている状態で、パスワードのみ登録(更新)してみます。

Playbook

json_secretに、パスワードのみを指定します。

---
- hosts: localhost
  gather_facts: no
  
  tasks:
    - debug:
        msg: "{{ lookup('amazon.aws.aws_secret', 'web') }}"
      register: result

    - name: change password for Secrets Manager
      community.aws.secretsmanager_secret:
        name: "web"
        state: present
        secret_type: 'string'
        json_secret: "{{ new }}"
      vars:
        new:
          password: "oioioioi"    # 更新するパスワード

    - debug:
        msg: "{{ lookup('amazon.aws.aws_secret', 'web') }}" 


実行ログ

ユーザは削除され、更新したパスワードのみが残ります。

$ ansible-playbook update_secretsmanager.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ***********************************************************

TASK [debug] ***************************************************************
ok: [localhost] => {
    "msg": {
        "password": "12345678",
        "user": "ec2-user"
    }
}

TASK [change password for Secrets Manager] *********************************
changed: [localhost]

TASK [debug] ***************************************************************
ok: [localhost] => {
    "msg": {
        "password": "oioioioi"
    }
}

PLAY RECAP *****************************************************************
localhost : ok=3  changed=1  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   

$ 


備考

AnsibleでのSecretsManagerは、AWS CLIコマンドから見るに値の更新はできなさそうで、すべての値を再登録しかなさそう。

自社のアドベントカレンダーを企画し、無事に完遂した話

はじめに

終わってみれば、ご協力もあり無事に完遂しましたが、社内(特に他部署と)の交流不足が大きかったと思います。
他部署でちらほらブログを書いていた方が数名いたので何とかなるでしょと思っていましたが、そもそも会話したことがないという状況でした。完全に勢いのみで開催しました。


準備

アドベントカレンダーについての詳細をGoogleドキュメントにまとめ、社内に公開しました。

  • アドベントカレンダーとは
  • アドベントカレンダーを開催する理由
    • 「社外にアウトプットする人が増えたら良いな、外から見た弊社が格好よくうつるんじゃないか」と、アウトプットする人を増やすいい機会だと思ったからです。
    • アウトプットは良いぞ
  • 投稿までの流れ
  • 投稿内容のご相談
    • ○○番煎じとか需要とか気にしないでいいです、毎年同じ内容の雑誌も出てますなどと話しました
  • 社内外へのPRとして私がすること
    • 投稿だけじゃなくツイートもします


周知

slackでどーんと告知した後、上司や他部署の役職者にも相談しました。
順序逆のほうがやりやすかったかもしれないけど、謎の強制力が働くので(気持ち的に)難しいところがあります。


記事一覧

並べてみると、投稿があった分野は多岐にわたっていました。

日付 タイトル
12/1 [AWS Backup] バックアップとリストアを試してみた - mito’s blog
12/2 [Ansible x Terraform] TerraformでKeyPairの作成やAmazon Secrets ManagerにKeyPairを登録し、Ansibleで利用する - mito’s blog
12/3 今の時代だからこそ、アニメーションで伝えよう
12/4 VSCode + Docusaurus で最高のドキュメント作成環境を構築する
12/5 いつの間にかRaspberry Pi Imagerが更に便利になっていました
12/6 Docusaurus で作成するドキュメントはココがいいね!
12/7 Nginx + Docusaurus でドキュメントをコンテナ化してみる
12/8 LaravelとReactでWebSocketを使ったブロードキャストアプリを作ってみる - Qiita
12/9 OpenAPIGeneratorの自動生成におけるコントローラーの生成単位はURLで決まる
12/10 Nginx + OAuth2 Proxy で静的 Web サイトに認証機能を追加してみる
12/11 静的 Web サイトに認証をつけて AWS App Runner でデプロイしてみる
12/12 SwiftUI でサイズクラスに対応する - Qiita
12/13 IndexedDBの概念がわかりづらい件について - Qiita
12/14 Cloud9でseleniumを動かしてみた - Qiita
12/15 seleniumをコンテナ化して動かしてみた - Qiita
12/16 Google Cloud Next '22に参加してクラウドエンジニアの育成について考えた - Qiita
12/17 ML Kit Text Recognition v2で日本語認識Androidアプリ - Qiita
12/18 [Ansible] Assume Roleモジュールを使う - mito’s blog
12/19 [Terraform] 関数を使って、年月日時分秒をユニークな文字列として扱う - mito’s blog
12/20 フロントエンドを知らない一般人がトレンディなフレームワーク SvelteKit を眺める - ローカル環境構築編 -
12/21 GAEの起動/停止をスケジューリングする
12/22 フロントエンドを知らない一般人がトレンディなフレームワーク SvelteKit を眺める - ルーティング編 -
12/23 非技術者がJavaScriptフレームワーク・ライブラリの違いを調べてみた - Qiita
12/24 【対策メモ】AWS認定DevOpsエンジニア-プロフェッショナル試験 - Qiita
12/25 【合格体験記】AWS認定DevOpsエンジニア-プロフェッショナル(DOP-C01) - Qiita


最後に

来年も開催したいなと思います。 順調に埋まるように(それだけじゃないけど)、じわじわと他部署にもアウトプットの芽を埋めてみます。

[Terraform] 関数を使って、年月日時分秒をユニークな文字列として扱う

この記事は、カサレアル Advent Calendar 2022 の19日目のエントリです。

はじめに

ユニークな文字列が欲しかったので、年月日時分秒を取得し文字列にしました。
以下の関数を組み合わせます。


TFファイル

現在の時間2022年12月18日11時26分29秒20221218112629に変換します。

  • tfファイル
locals {
  # timestampでUTCを取得する
  now_utc = timestamp()
  # timeaddで9hを足してJSTに変換する
  now_jst = timeadd(local.now_utc, "9h") 
  # formatdateで取得したい形式に変換する
  datetime = formatdate("YYYYMMDDhhmmss", local.now_jst)
}

output "datetime"{
  value = local.datetime
}


実行結果

$ terraform apply

Changes to Outputs:
  + datetime = (known after apply)

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes


Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

datetime = "20221218112629"
$ 


備考

24時間表記にするため、formatdate関数ではhhを指定します。


[Ansible] Assume Roleモジュールを使う

この記事は、カサレアル Advent Calendar 2022 の18日目のエントリです。

はじめに

最近、AnsibleのAWSコレクションにamazon.awsとcommunity.awsの2つがあることを知りました。
community.awsのほうにAssume Roleモジュールがあったので、それを試してみます。

なお、AssumeRoleってなに?という方は、記憶に残しやすい記事がありますのでご参照ください。


環境

  • ansible core: 2.13.7
  • community.aws: 5.0.0

community.awsの最新版は、Ansible Core 2.11.0以前のバージョンをサポートしていません。

Ansible version compatibility Tested with the Ansible Core 2.12, and 2.13 releases, and the current development version of Ansible. Ansible Core versions before 2.11.0 are not supported. In particular, Ansible Core 2.10 and Ansible 2.9 are not supported.


AssumeRole する Playbook

sts_assume_roleモジュールの実行前後で、ARNやクレデンシャル情報等を出力します。

---
- hosts: localhost
  gather_facts: no

  tasks:
    - shell:
        cmd: aws sts get-caller-identity
      register: result

    - name: before
      debug:
        msg: "{{ result.stdout }}"

    - name: set assume_role
      sts_assume_role:
        role_arn: "arn:aws:iam::*******:role/********"  #引き継ぎたいRoleを指定
        role_session_name: "Test_Role"
      register:  assumed_role

    - name: after
      debug:
        msg: "{{ assumed_role }}"


Playbook の実行結果

sts_assume_roleモジュールにより、ARNや一時的なクレデンシャル情報等が設定されました(ほぼ伏字ですが)。

$ ansible-playbook assume_role.yml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] ********************************************************************

TASK [shell] ************************************************************************
changed: [localhost]

TASK [before] ***********************************************************************
ok: [localhost] => {
    "msg": {
        "Account": "***********",
        "Arn": "arn:aws:iam::***********:user/***********",
        "UserId": "***********"
    }
}

TASK [set assume_role] **************************************************************
changed: [localhost]

TASK [after] ************************************************************************
ok: [localhost] => {
    "msg": {
        "changed": true,
        "failed": false,
        "sts_creds": {
            "access_key": "***********",
            "expiration": "2022-12-17T17:31:46+00:00",
            "secret_key": "**********************",
            "session_token": "*********************************"
        },
        "sts_user": {
            "arn": "arn:aws:sts::***********:assumed-role/***********/Test_Role",
            "assumed_role_id": "***********:Test_Role"
        }
    }
}

PLAY RECAP **************************************************************************
localhost : ok=4  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0   

$ 


以降のタスクでaws_access_key: "{{ assumed_role.sts_creds.access_key }}"のように指定し、AssumeRole後のクレデンシャル情報等を使用します。

[Ansible] ダイナミックインベントリで起動中のインスタンスのIPアドレスを出力する

この記事は、Ansible Advent Calendar 2022 の9日目のエントリです。

はじめに

ダイナミックインベントリを使う時はデフォルトのDNS名をhostsに指定していたのですが、ほかの指定はできないのかと思い、IPアドレスを対象に実施してみました。


インベントリファイル

ファイル名は、仕様でaws_ec2.ymlか***aws_ec2.ymlとする必要があります。

$ cat aws_ec2.yml
---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1
hostnames:
  # 結果をプライベートIPで出力する。パブリックIPは「ip-address」を指定する
  - private-ip-address
include_filters:
  # リストの中に複数の条件をいれると、andでつながる
  - instance-state-name: running
    tag:Name:
    # ワイルドカードが使える
    - "web_*"
compose:
  ansible_host: private_ip_address     # hostsにプライベートIPをしているので、今回は不要

$ 


実行ログ

インスタンスの状況は以下です。

Name インスタンスの状態 プライベートIPアドレス
web_01 実行中 172.31.36.***
web_02 実行中 172.31.32.***
web_03 停止済み 172.31.41.***
weeeeebb!!! 実行中 172.31.16.***


ansible-inventoryコマンドを実行し、確認します。

$ ansible-inventory -i aws_ec2.yml --graph
@all:
  |--@aws_ec2:
  |  |--172.31.32.***
  |  |--172.31.36.***
  |--@ungrouped:
$ 


起動中のweb_01、web_02のプライベートIPアドレスのみ出力できました。


注意

プライベートIPアドレスインスタンスが停止していても割り当てられているので、aws_ec2.ymlのフィルターにあるinstance-state-name : runningが必要です。

aws_ec2.ymlからinstance-state-name : runningを削除した場合、以下のように停止中のインスタンスも表示されます。

$ cat aws_ec2.yml 
---
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-1
hostnames:
  - private-ip-address
include_filters:
  - tag:Name:
    - "web_*"
$ 
$ ansible-inventory -i aws_ec2.yml --graph
@all:
  |--@aws_ec2:
  |  |--172.31.32.***
  |  |--172.31.36.***
  |  |--172.31.41.***
  |--@ungrouped:
$ 


参考

フィルターの条件をandにする方法が分からなかったので助かりました!

フィルターなどの条件に指定できるEC2インスタンスのパラメータは以下のようです。

[Ansible x Terraform] local-execでPlaybookを実行してみた


この記事は、Ansible Advent Calendar 2022 の2日目のエントリです。

はじめに

今回はAnsibleのTerraformモジュールを使うのではなく、TerraformでEC2インスタンスを作成し、local-execでPlaybookを実行してパッケージをインストールします。
AnsibleのAmazonEC2のインベントリプラグインは使わず、出来る限りTerraformで実装してみます。

Provisioner: local-exec | Terraform | HashiCorp Developer



結論

Playbookを更新しても一度それを実行するnull_resourceが成功していれば、terraform applyでは差異なしと判断され、null_resource(Playbook)は再実行されません。
また、Terrafromが出力するPlaybookのログがきれいに表示されないので、AnsibleからTerraformを呼び出したほうが便利そうです。


環境

  • terraform: v1.3.4
  • ansible core: v2.13.6
  • aws cli: v1.22.34


用意するファイル

  • main.tf
    • 実装内容
      • EC2インスタンスの作成
      • SSHキーの作成
      • セキュリティグループの作成
      • リソースhttpでホストのパブリックIPを取得する
      • リソースlocal_fileでinventory.iniの作成
      • リソースnull_resourceのlocal-execでPlaybookを実行する
  • main.yml
    • 実装内容
      • gitのインストール
  • ansible.cfg
    • 実装内容
      • host_key_checkingの無効


main.tf
provider "aws" {
    region = "ap-northeast-1"
    access_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

# EC2インスタンスの作成
resource "aws_instance" "mito_ec2" {
    ami           = "ami-072bfb8ae2c884cc4"    # Amazon Linux 2
    instance_type = "t2.micro"
    associate_public_ip_address = "true"
    key_name      = aws_key_pair.deployer.key_name
    vpc_security_group_ids = [aws_security_group.sg_mito.id]
    tags = {
        Name = "mito_ec2"
    }
}

# キーペアの作成
resource "tls_private_key" "rsa_4096" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "deployer" {
  key_name   = "mito_key"
  public_key = tls_private_key.rsa_4096.public_key_openssh
  lifecycle {
    ignore_changes  = [key_name]
  }
}

resource "local_file" "private_key_pem" {
  content  = tls_private_key.rsa_4096.private_key_pem
  filename = "/home/ubuntu/.ssh/mito_key.pem"  # ログインユーザに合わせる
}

# セキュリティグループの作成
resource "aws_security_group" "sg_mito" {
  name        = "sg_mito"
  description = "sg_mito"

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["${chomp(data.http.myip.response_body)}/32"]  # ホストのパブリックIPを許可する
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name      = "sg_mito"
  }
}

# 返り値でホストのパブリックIPを取得する
data "http" "myip" {
  url = "http://ipv4.icanhazip.com"
}

# 対象ノードのプライベートIPが記載されたinventory.iniを作成する
resource "local_file" "inventory" {
  content  = aws_instance.mito_ec2.public_ip
  filename = "./inventory.ini"
  file_permission = "0644"
}

# Playbookの実行
resource "null_resource" "playbook" {
  depends_on = [
    aws_instance.mito_ec2  # インスタンス作成後に実行
  ]

  # 対象ノードの起動を待ち、Playbookを実行する
  provisioner "local-exec" {
    interpreter = ["/usr/bin/bash", "-c"]
    command     = <<-EOT
      chmod 600 ${local_file.private_key_pem.filename}
      aws ec2 wait instance-status-ok --instance-ids ${aws_instance.mito_ec2.id}
      ansible-playbook main.yml -i inventory.ini -u ec2-user --private-key=${local_file.private_key_pem.filename}
    EOT
  }
}


main.yml
---
- hosts: all
  gather_facts: no
  become: yes

  tasks:
    - name: install git
      yum:
        name: git
        state: present


ansible.cfg
[defaults]
host_key_checking = False


実行ログ

99行目からPlaybookのログが表示されていますが、ログの順番が変わっています。

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/null from the dependency lock file
- Reusing previous version of hashicorp/tls from the dependency lock file
- Finding latest version of hashicorp/http...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/null v3.2.1
- Using previously-installed hashicorp/tls v4.0.4
- Installing hashicorp/http v3.2.1...
- Installed hashicorp/http v3.2.1 (signed by HashiCorp)
- Using previously-installed hashicorp/aws v4.43.0
- Using previously-installed hashicorp/local v2.2.3

()
$ 
$ 
$ terraform apply
data.http.myip: Reading...
data.http.myip: Read complete after 0s [id=http://ipv4.icanhazip.com]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.mito_ec2 will be created
  + resource "aws_instance" "mito_ec2" {
      + ami                                  = "ami-072bfb8ae2c884cc4"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)
() 

  # local_file.inventory will be created
  + resource "local_file" "inventory" {
      + content              = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0644"
      + filename             = "./inventory.ini"
      + id                   = (known after apply)
    }

  # local_file.private_key_pem will be created
  + resource "local_file" "private_key_pem" {
      + content              = (sensitive)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "/home/ubuntu/.ssh/mito_key.pem"
      + id                   = (known after apply)
    }

  # null_resource.playbook will be created
  + resource "null_resource" "playbook" {
      + id = (known after apply)
    }

()
Plan: 7 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

tls_private_key.rsa_4096: Creating...
aws_security_group.sg_mito: Creating...
tls_private_key.rsa_4096: Creation complete after 1s [id=6d5549db0XXXXXXXXXXXXXXXXX]
local_file.private_key_pem: Creating...
aws_key_pair.deployer: Creating...
local_file.private_key_pem: Creation complete after 0s [id=9f5e11483XXXXXXXXXXXXXXXXXXXX]
aws_key_pair.deployer: Creation complete after 0s [id=mito_key]
aws_security_group.sg_mito: Creation complete after 3s [id=sg-091fbXXXXXXXXXXXX]
aws_instance.mito_ec2: Creating...
aws_instance.mito_ec2: Still creating... [10s elapsed]
aws_instance.mito_ec2: Still creating... [20s elapsed]
aws_instance.mito_ec2: Creation complete after 21s [id=i-02fb1XXXXXXXXXXXX]
local_file.inventory: Creating...
null_resource.playbook: Creating...
local_file.inventory: Creation complete after 0s [id=fecdbeXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]
null_resource.playbook: Provisioning with 'local-exec'...
null_resource.playbook (local-exec): Executing: ["/usr/bin/bash" "-c" "chmod 600 /home/ubuntu/.ssh/mito_key.pem\naws ec2 wait instance-status-ok --instance-ids i-02fb1149af69d72ce\nansible-playbook main.yml -i inventory.ini -u ec2-user --private-key=/home/ubuntu/.ssh/mito_key.pem\n"]
null_resource.playbook: Still creating... [10s elapsed]
null_resource.playbook: Still creating... [20s elapsed]
null_resource.playbook: Still creating... [30s elapsed]
null_resource.playbook: Still creating... [40s elapsed]
null_resource.playbook: Still creating... [50s elapsed]
null_resource.playbook: Still creating... [1m0s elapsed]
null_resource.playbook: Still creating... [1m10s elapsed]
null_resource.playbook: Still creating... [1m20s elapsed]
null_resource.playbook: Still creating... [1m30s elapsed]
null_resource.playbook: Still creating... [1m40s elapsed]

null_resource.playbook (local-exec): PLAY [all] *********************************************************************

null_resource.playbook (local-exec): TASK [install git] *************************************************************
null_resource.playbook: Still creating... [1m50s elapsed]
null_resource.playbook (local-exec): [WARNING]: Platform linux on host xx.xx.xx.xx is using the discovered Python
null_resource.playbook (local-exec): interpreter at /usr/bin/python3.7, but future installation of another Python
null_resource.playbook (local-exec): interpreter could change the meaning of that path. See
null_resource.playbook (local-exec): https://docs.ansible.com/ansible-
null_resource.playbook (local-exec): core/2.13/reference_appendices/interpreter_discovery.html for more information.
null_resource.playbook (local-exec): changed: [xx.xx.xx.xx]

null_resource.playbook (local-exec): PLAY RECAP *********************************************************************
null_resource.playbook (local-exec): xx.xx.xx.xx  : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

null_resource.playbook: Creation complete after 1m54s [id=6559XXXXXXXXXXXXXX]

Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
$