AWS Systems ManagerでAnsible実行を行う前提でのPlaybook構成検討

# はじめに
今担当しているシステムでは、`AWS Identity Center`を使い、キーペアを持たない運用を行っておりますが、`Ansible`をEC2インスタンスに実行する場合、`Ansible`の仕組み上、`SSH`を行う必要があることから、`Ansible`を使うためにはキーペアを持たなくてはならないと思っていました。

しかし、`AWS Systems Manager Run Command`を使用すれば、キーペアを使わずに対象のEC2インスタンスに`Ansible`を実行できるため、今回は`AWS Systems Manager Run Command`を使ってキーペアなしで`Ansible`を実行する際の最適なディレクトリレイアウトについて検討してみようと思います。

# AWS Systems Manager Run CommandでのAnsible実行
[以前の記事](https://qiita.com/sakai00kou/items/5604b94dd2c87922538f)で`AWS Systems Manager`からの`Ansible`実行を試しましたが、`Systems Manager Run Command`で`Ansible`を実行する場合、`Run Command`で指定するターゲットで対象を指定することから、インベントリファイルによる制御は行えず、ターゲット全て`localhost`で実行されます。

そのため、ベストプラクティス構成の`Playbook`を実行しようとしても、ホストは`localhost`一択となるため、`group_vars`や`hosts_vars`による変数切り替えができず、単純に実行する`Playbook`を切り替えるだけではサーバ種別ごとに変数を分けることが難しいです。

そのような特徴を持つ`Run Command`による`Ansible`を実行する上で、なるべくベストプラクティスの構成を変えずに、あまり修正すること無く`Run Command`によるPlaybook実行も、通常のインベントリファイル指定でのPlaybook実行もできるようにしてみたいと思います。

以下、以前紹介した記事。

- [AnsibleをAWS Systems Managerから実行するCI/CDを構築する。（その１：Systems ManagerからのAnsible実行）](https://qiita.com/sakai00kou/items/5604b94dd2c87922538f)

# Ansibleのベストプラクティス構成
`Ansible`の公式でベストプラクティスのディレクトリレイアウトとして紹介されている構成は以下となります。

- [ベストプラクティス](https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_best_practices.html#playbooks-best-practices)

```bash:Ansibleベストプラクティスディレクトリレイアウト
.
├── production # inventory file for production servers staging # inventory file for staging environment
├── group_vars/
│   └── group1.yml # here we assign variables to particular groups group2.yml
├── host_vars/
│   └── hostname1.yml # here we assign variables to particular systems hostname2.yml
├── library/ # if any custom modules, put them here (optional) module_utils/ # if any custom module_utils to support modules, put them here (optional) filter_plugins/ # if any custom filter plugins, put them here (optional)
├── site.yml # master playbook webservers.yml # playbook for webserver tier dbservers.yml # playbook for dbserver tier
└── roles/
    ├── common/ # this hierarchy represents a “role”
    │   ├── tasks/ #
    │   │   └── main.yml # <– tasks file can include smaller files if warranted
    │   ├── handlers/ #
    │   │   └── main.yml # <– handlers file
    │   ├── templates/ # <– files for use with the template resource
    │   │   └── ntp.conf.j2 # <——- templates end in .j2
    │   ├── files/ #
    │   │   └── bar.txt # <– files for use with the copy resource foo.sh # <– script files for use with the script resource
    │   ├── vars/ #
    │   │   └── main.yml # <– variables associated with this role
    │   ├── defaults/ #
    │   │   └── main.yml # <– default lower priority variables for this role
    │   ├── meta/ #
    │   │   └── main.yml # <– role dependencies
    │   └── library/ # roles can also include custom modules module_utils/ # roles can also include custom module_utils lookup_plugins/ # or other types of plugins, like lookup in this case
    └── webtier/ # same kind of structure as “common” was above, done for the webtier role monitoring/ # “” fooapp/ # “”
```

上記例で言うと、普通に`Playbook`を実行する場合には、`production`というインベントリファイルを指定して`site.yml`を実行することで、インベントリファイルに記載されているグループ名（`group1`）とホスト名（`hostname1`）に該当する変数が`group_vars`、`host_vars`から呼び出され`site.yml`に記載されている実行ロール名（`common`や`webtier`）が実行される流れとなります。

また、`site.yml`は各サーバごとの処理を記載した`Playbook`を`import_playbook`で読み込む程度の処理しか行わないというのがベストプラクティスとなるため、実際に実行するロールを指定する`Playbook`は以下でいうと`webservers.yml`や`dbservers.yml`となります。

```yml:site.yml
---
# file: site.yml
- import_playbook: webservers.yml
- import_playbook: dbservers.yml
```

# 私が考えたAWS Systems Manager Run Commandで実行する場合のベストプラクティス構成
詳細は後述しますが、以下は俗に言う「俺が考えた最強の○○」

```bash:私が考えたAWS Systems Manager Run Commandで実行する場合のディレクトリレイアウト
.
├── group_vars/
│   └── group1.yml
├── ssm_webservers.yml
├── ssm_dbservers.yml
└── roles/
    ├── common/
    │   ├── tasks/
    │   │   └── main.yml
    │   ├── handlers/
    │   │   └── main.yml
    │   ├── templates/
    │   │   └── ntp.conf.j2
    │   ├── files/
    │   │   └── bar.txt
    │   ├── vars/
    │   │   └── main.yml
    │   ├── defaults/
    │   │   └── main.yml
    │   ├── meta/
    │   │   └── main.yml
    │   └── library/
    └── webtier/
```

`Run Command`での実行だけではなく、普通に`Playbook`を実行する場合でもあまり構成を変えずに実行したかったため、基本的なディレクトリレイアウトはベストプラクティスの構成とほぼ同じです。

インベントリファイルは`Run Command`では使えないため除外、`site.yml`も`Run Command`で実行する場合はインポートする意味がないので除外、`host_vars`もホスト単位での変数指定は`Run Command`では難しいため、こちらも除外。

`group_vars`はそのままでは全てのホストを示す`all.yml`しか読み込んでくれないため、`host_vars`と同じようにあまり意味がないのですが、`Playbook`にパス指定して読み込ませるようにすることで、既存の`group_vars`変数ファイルを使い回せることが分かったので、そのまま使用します。

# Run Commandで実行する場合に用意するファイル
上記のディレクトリレイアウトで`Run Command`で実行する場合に準備するファイルについて以下で見ていきます。

`site.yml`がない代わりに`site.yml`から読み込まれるサーバ種別ごとの`Playbook`を`Run Command`用に準備します。

`Playbook`内の`hosts`の指定は、`localhost`しか指定できないので`localhost`は固定として、`vars_files`で`group_vars`に存在するグループ用変数を`Ansible`ディレクトリからの相対パス指定で読み込みます。

こうすることで、`group_vars`の変数ファイルは既存のまま使用することができるので`Run Command`用に準備するファイルは`Playbook`ファイルとなる`ssm_webserver.yml`や`ssm_dbserver.yml`のみで済むことになります。

```yml:ssm_webserver.yml
---
- become: true
  hosts: localhost
  vars_files:
    - group_vars/group1.yml
  roles:
    - common
```

# Systems Manager Run CommandによるAnsibleの実行
`Systems Manager Run Command`で実行する場合は、ターゲットを`Run Command`の画面から指定して、`Playbook File`で`ssm_webserver.yml`や`ssm_dbserver.yml`を指定すれば動きます。

ターゲットは複数指定できても、ターゲットに対して実行される`Playbook`ファイルをターゲットごとに分けることはできないので、もし実行する`Playbook`ファイルを分けたい場合は、別々に実行しましょう。

# 注意点
`Run Command`による`Ansible`実行は、実行するたびに`Ansible`の`Playbook`などを毎回ダウンロードする仕組みとなります。

そのため、`roles`の`files`などにインストール用パッケージを置いたりするようなことは行わず、S3等からダウンロードするようにした上で、`roles`実行する場合も条件式等を使い、毎回インストール用パッケージ等がダウンロードされないようにするのが良いかと思います。

- インストール用パッケージ等のサイズが大きいファイルはAnsibleのディレクトリには置かずに別のストレージに置く
- Playbookの条件式や実行rolesで制御してサイズの大きいファイルがダウンロードされないようにする

# おわりに
`Playbook`ファイルで`vars_files`で変数ファイルを指定することで既存の`Ansible`にあまり手を入れること無く使い回すことができました。

１台毎に別々の`Playbook`ファイルを準備すれば、もちろん`host_vars`でのホスト用変数の呼び出しもできますし、多分`Playbook`ファイル内で条件式等書いて１台に絞り込むような処理が書ければ、既存のAnsibleはそのままで操作感変わらずに実行することもできるかと思います。

`Ansible`を実行するためだけにキーペア運用を行っている方は、今回の記事を見てキーペアを消してみてはいかがでしょうか。