😸

[Ansible]インベントリプラグインを使ってEC2インスタンス情報を動的に取得する

2021/05/01に公開

本記事では、Ansible のインベントリプラグインを使用してEC2インスタンスを動的に取得する方法について紹介します。

Ansible のインベントリ(ターゲットノードの接続情報等をまとめたファイル)に接続対象のホスト情報を記載するのですが、クラウド環境でEC2インスタンスが頻繁にホストが追加・削除される状況では管理が難しくなります。そこで、Ansible ではクラウドから提供されている API(AWS であれば EC2 API)によってホスト情報を動的に取得し管理するダイナミックインベントリという機能が提供されています。

Ansible 2.9までは GitHub 上の ec2.py をダウンロードして使う方法が主流でしたが、2.10 からはこのファイルが削除されていて使うことができず、インベントリプラグインをつかうことが推奨されています。

Ansible のバージョン

手元の実行環境です。

$ ansible --version
ansible 2.10.8
  config file = /home/ansible/workspace/ansible.cfg
  configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.8/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.8.9 (default, Apr 10 2021, 15:55:09) [GCC 8.3.0]

$ pip list | grep ansible
ansible           3.2.0
ansible-base      2.10.8
ansible-lint      5.0.7

$ pip list | grep boto
boto              2.49.0
boto3             1.17.62
botocore          1.20.62

EC2インスタンス例

今回は ansible-target-node01~05 という EC2 インスタンスが実行されているとします。Application というタグで EC2 インスタンスをグルーピングしています。

Nameタグ Applicationタグ インスタンスタイプ
ansible-target-node01 なし t3a.medium
ansible-target-node02 なし t3a.medium
ansible-target-node03 bar t3a.medium
ansible-target-node04 foo t3a.medium
ansible-target-node05 foo t3.medium

インベントリプラグインの有効化

Amazon EC2 のインベントリプラグイン(aws_ec2)を使う場合、Ansible Galaxy から amazon.aws コレクションをインストールします。

$ ansible-galaxy collection install amazon.aws
$ ansible-galaxy collection list | grep amazon.aws
amazon.aws                    1.4.1

また、aws_ec2 インベントリプラグインを実行するホスト上で boto3 と botocore が必要です。

$ pip install boto3 botocore

インベントリの設定

インベントリファイルですが、ファイル名の末尾を aws_ec2.(yml|yaml) にする必要があります。このインベントリファイルに取得するための設定やフィルタを書いていきます。詳細はドキュメントをご参照ください。

今回は aws_ec2.yml というファイル名で以下のように記載しました。

plugin: aws_ec2
regions:
  - ap-northeast-1

# フィルタの設定
# 起動しているEC2インスタンスのみを対象とする
filters:
  instance-state-name: running

# グルーピングの設定
# タグ毎にEC2インスタンスのグループをまとめる
keyed_groups:
  - key: tags.Application
    prefix: tag_Name_
    separator: ""

# inventory_hostnameの設定項目の優先順位
# 上から優先され、取得できなければ下に下がっていく
# この例だと Nameタグがなければ `ip-address`(パブリックIPv4アドレス)を採用し、
# それも設定されていなければプライベートIPv4アドレスを採用する
hostnames:
  - tag:Name
  - ip-address # パブリックIPv4アドレス
  - private-ip-address

compose:
  # inventory_hostnameを変更せずにansible_hostを変更する場合
  # プライベートIPv4アドレスでホストに接続したい場合
  ansible_host: private_ip_address

  # SSM Session Managerでホストに接続したい場合は以下のようにする
  # ansible_host: instance_id

ansible-inventory コマンドでインベントリの状況を確認できます。@ が接頭辞になっているものはグループ名で、複数の EC2 インスタンスを指定する際に使用できます。インベントリの設定で Application タグの値でグルーピングし、接頭辞に tag_Name_ をつけるようにしました。

$ ansible-inventory -i aws_ec2.yml --graph
@all:
  |--@aws_ec2:
  |  |--ansible-target-node01
  |  |--ansible-target-node02
  |  |--ansible-target-node03
  |  |--ansible-target-node04
  |  |--ansible-target-node05
  |--@tag_Name_bar:
  |  |--ansible-target-node03
  |--@tag_Name_foo:
  |  |--ansible-target-node04
  |  |--ansible-target-node05
  |--@ungrouped:

プレイブック例

今回は簡単な例として、Name タグの値を取得するのみのプレイブックとしました。EC2 インスタンスに SSH 接続するための設定値(ansible_useransible_ssh_private_key_file)は適宜変更してください。

# playbook.yml
---
- name: Example dynamic inventory
  hosts: "{{ target_hosts }}"

  vars:
    ansible_user: ec2-user
    ansible_ssh_private_key_file: ~/.ssh/<KEY PAIR>.pem

  tasks:
    - name: Print Name tag
      debug:
        msg: "{{ tags.Name }}"

target_hosts に Ansible を実行するホスト名を指定します。以下のようにコマンドから渡すことでプレイブック内に変更を加えることなく柔軟に EC2 インスタンスを指定できます。

# Applicationタグ: foo(tag_Name_foo)のEC2インスタンスに対して実行する
$ ansible-playbook -i aws_ec2.yml sample.yml -e "target_hosts=tag_Name_foo"

PLAY [Example dynamic inventory] ***********************************************************************************************

TASK [Print Name tag] **********************************************************************************************************
ok: [ansible-target-node04] => {
    "msg": "ansible-target-node04"
}
ok: [ansible-target-node05] => {
    "msg": "ansible-target-node05"
}

PLAY RECAP *********************************************************************************************************************
ansible-target-node04      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ansible-target-node05      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
# EC2インスタンス ansible-target-node04 のみ実行する
$ ansible-playbook -i inventory/aws_ec2.yml sample.yml -e "target_hosts=ansible-target-node04" 

PLAY [Example dynamic inventory] ***********************************************************************************************

TASK [Print Name tag] **********************************************************************************************************
ok: [ansible-target-node04] => {
    "msg": "ansible-target-node04"
}

PLAY RECAP *********************************************************************************************************************
ansible-target-node04      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

キャッシュ

Cache プラグインを利用してインベントリ情報をキャッシュできます。aws_ec2.yml に以下のように追記します。

# インベントプラグインのキャッシュ有効化
cache: yes
# キャッシュプラグイン。JSON形式のファイルで保持
cache_plugin: jsonfile
# キャッシュの有効期限(1時間)
cache_timeout: 3600
# キャッシュファイルの保存場所
cache_connection: $HOME/.ansible/aws_inventory

ansible.cfg で設定できますが、インベントリプラグイン全体の設定ですので、インベントリプラグイン毎に設定をカスタマイズしたい場合は aws_ec2.yml のようなインベントリファイルに記載します。

[inventory]
cache = yes
cache_plugin = jsonfile
cache_timeout = 3600
cache_connection = $HOME/.ansible/ansible_inventory

注意点としては、Ansible から EC2 インスタンスを作成する場合、キャッシュが更新されず作成した EC2 インスタンスの情報を取得できない可能性があります。

参考

Discussion