Ansible で task の実行結果の json を dict オブジェクトとして後続の処理で利用する

全国の Ansible 派のみなさん、こんにちは。
Chef より Ansible 派、インフラストラクチャー部の沼沢です。

Ansible を利用する際に、task の実行結果を register に入れて後続の task で利用したりしますよね。
自分は AWS の構築に Ansible を利用することも多いのですが、例えば以下のように、aws ec2 describe-instances の実行結果を register で変数に代入して使うというのはよくあることです。

- tasks:
  - name: numacchi インスタンスの Instance ID 取得
    shell: >
      aws ec2 describe-instances \
        --region ap-northeast-1 \
        --filters Name=tag:Name,Values="numacchi" \
        --query 'Reservations[].Instances[].InstanceId' \
        --output text
    changed_when: False
    register: instance_id
  - name: EIP 付与
    ec2_eip:
      region: ap-northeast-1
      device_id: "{{ instance_id.stdout }}"

上記のように、Instance ID だけを取りたいならこれで良いのですが、後続の処理で Instance ID や VPC ID、Subnet ID 等、必要な情報が複数ある場合、都度 discribe-instances を実行して1つずつ取得するのは非常に効率が悪いし、何より美しくないですよね。

できれば、1回の task の実行で必要な情報を全て取得して、取得した json を dict オブジェクトとして使いたいですよね。

そこで今回は、set_fact というモジュールを利用してこの課題を解決する方法をご紹介したいと思います。

set_fact モジュールとは、task 内で変数をセットするモジュールです。
set_fact - Set host facts from a task — Ansible Documentation

実例

では早速、例を使ってご紹介します。
以下は、既存のインスタンス (Name タグの値が “numacchi” のインスタンス) と同じ Subnet に同じスペックのインスタンスをもう1台構築するという例を実現する Ansible です。

- tasks:
  - name: EC2 Instance 情報取得
    shell: >
      aws ec2 describe-instances \
        --region ap-northeast-1 \
        --filters Name=tag:Name,Values="numacchi" \
        --query 'Reservations[].Instances[].{"Name": Tags[?Key==`Name`]|[0].Value,"SubnetId": SubnetId,"ImageId": ImageId,"VolumeId": BlockDeviceMappings[0].Ebs.VolumeId,"InstanceType": InstanceType,"InstanceProfile": IamInstanceProfile.Arn,"KeyName": KeyName,"SecurityGroups": SecurityGroups[].GroupName}|[0]'
    changed_when: False
    register: ec2_info
  - set_fact:
      instance: "{{ ec2_info.stdout }}"
  - name: EBS 情報取得
    shell: >
      aws ec2 describe-volumes \
        --region ap-northeast-1 \
        --volume-ids "{{ instance.VolumeId }}" \
        --query 'Volumes[].{"DeviceName": Attachments[0].Device, "VolumeType": VolumeType, "Size": Size}|[0]'
    changed_when: False
    register: ebs_info
  - set_fact:
      ebs: "{{ ebs_info.stdout }}"
  - name: "{{ instance.Name }} インスタンス追加作成"
    ec2:
      region: ap-northeast-1
      vpc_subnet_id: "{{ instance.SubnetId }}"
      state: present
      image: "{{ instance.ImageId }}"
      instance_type: "{{ instance.InstanceType }}"
      instance_profile_name: "{{ instance.InstanceProfile | regex_replace('.*/', '') }}"
      key_name: "{{ instance.KeyName }}"
      volumes:
        - device_name: "{{ ebs.DeviceName }}"
          volume_type: "{{ ebs.VolumeType }}"
          volume_size: "{{ ebs.Size }}"
      group: "{{ instance.SecurityGroups }}"
      instance_tags:
        Name: "{{ instance.Name }}"

解説

例の Ansible は以下のことを実施しています。

  1. ec2 モジュール実行に必要な情報を、aws ec2 describe-instances で取得
  2. 取得した結果を set_fact モジュールで “instance” という変数に代入
  3. EBS の情報も必要なので、aws ec2 describe-volumes でそれぞれ取得
  4. 取得した結果を set_fact モジュールで “ebs” という変数に代入
  5. 取得した情報をもとに、ec2 モジュールでインスタンスを Launch

今回のポイントは、set_fact モジュールで register の出力結果の json 文字列を渡すことで、dict オブジェクトとして扱えるようにしている という点です。

“EC2 Instance 情報取得” task では、以下の情報を取得する CLI を実行しています。

  • Name (Name タグの Value)
  • SubnetId
  • ImageId (AMI の ID)
  • VolumeId (EBS の ID)
  • InstanceType
  • InstanceProfile (IAM ロールの ARN)
  • KeyName (KeyPair の名前)
  • SecurityGroups (アタッチされている Security Group のリスト)

取得される JSON は以下のような形です。

{
  "Name": "numacchi",
  "SubnetId": "subnet-xxxxxxxx",
  "ImageId": "ami-xxxxxxxx",
  "VolumeId": "vol-xxxxxxxxxxxxxxxxx",
  "InstanceType": "t2.micro",
  "InstanceProfile": "arn:aws:iam::xxxxxxxxxxxx:instance-profile/numacchi",
  "KeyName": "xxxxxxxx",
  "SecurityGroups": [
    "securitygroup1",
    "securitygroup2"
  ]
}

上記の json 文字列を set_fact で instance という変数に代入し、これを次の “EBS 情報取得” task で、"{{ instance.VolumeId }}" というように呼び出して利用しています。

同じ要領で、"EBS 情報取得" task で情報を取得し、最後に EC2 の Launch 実行の task を実行しています。

おまけ

例として記載した Ansible 内の AWS CLI の --query オプションでは、出力結果をうまいこと整形する、結構凝った使い方をしてい(ると思い)ます。
この --query オプションの使い方も、参考になれば幸いです。

まとめ

実は自分自身、task 内で何回も同じ CLI を叩いていたので、他に良いやり方が無いか調査や検証をしてこのやり方に至ったという経緯がありました。
今回は AWS CLI の実行結果を例にご紹介しましたが、もちろん、json 文字列の場合はいつでも使えますので、是非ご活用してみてはいかがでしょうか。