大規模Ansibleスクリプト開発における小ネタ

こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の相楽です。
最近、仕事で多量の仮想マシンやコンテナのセットアップすることが多くなり、構成管理ツールであるAnsibleをよく利用するようになりました:muscle_tone2:
Ansibleは、宣言的にホストの構成を記述してインストールなどの構築作業を自動化できるツールで、Qiitaにも説明記事が色々あります（「[Ansibleとは何か　構成管理ツールの目的〜Ansible導入まで最速で理解する](https://qiita.com/Brutus/items/1894629105d61f4854bc)」など)。
Ansibleを使うことでソフトウェアのセットアップがだいぶ楽になりましたが、rolesの数が50個を超えたあたりからAnsibleスクリプトの開発(デバッグ)が大変になってきました。そこで、私がAnsibleスクリプトの開発で使っている小ネタ？をご紹介させて頂きます。

# 前提
- Ansibleのディレクトリ構成は[公式のベストプラクティス](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html#directory-layout)に従って、playbookをroleに分割してタスクを記載しています(下図のような感じ)。

 ```
├── hosts
├── playbook.yml
└── roles
    ├── apache
    │   └── tasks
    │       └── main.yml
    ├── nginx
    │   └── tasks
    │       └── main.yml
    ...
 ```

# やりたいこと
Ansibleスクリプトをトライアンドエラーで開発するときには、`--start-at`オプションで指定タスクから処理を開始したり、`--tags`オプションでタグを付けたタスクのみを処理したりしながら開発をしています。
たとえば、下記のようなタスクがある場合、`--start-at`ではタスク名として`Install Nginx via RPM`や`Enable Nginx service by systemctl`を指定します。また、`--tags`ではタグ名として`install`や`setup`、両方指定する場合は`install,setup`などと指定します。

```yml
- name: Install Nginx by RPM 
  yum:
    name: nginx
  tags: install

- name: Enable Nginx service by systemctl
  systemd:
    name: nginx
    enabled: yes
  tags: setup
```

例えば、指定の方法はこんなかんじです。

```bash
ansible-playbook -i hosts playbook.yml --start-at="Enable Nginx service by systemctl" --tags="setup"
```

タスク名はAnsibleの実行ログにも表示されるため可読性高く書くべきだと思うのですが、長くすると`--start-at`オプションで指定する際にタイプミスがよく発生して苦労してしまいました。
また、タグ名も複数指定する際に入力漏れがあったりして目的のタスクが実行されていないことに気づかずハマってしまうこともありました。
そこで、既存の便利なツールを組み合わせて、タスク名やタグ名を手入力ではなく選択できるようにします。

## タスク名をインタラクティブに選択して実行する

下記のGIF動画をご覧ください。
![task.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548961/df7bfa50-e82d-c4ab-b626-aaf667866ce5.gif)

`start-at`オプションに`$(select_task)`を指定すると、`ロール名: タスク名`のリストからインタラクティブに絞り込んでタスク名を選択でき、`$(select_task)`を選択したタスク名と置き換えてAnsibleを実行できます:blush:

## タグ名をインタラクティブに選択して実行する

下記のGIF動画をご覧ください。
![tags.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548961/4de037c8-2218-8d51-6d12-ac8a75baf137.gif)

`tags`オプションに`$(select_tags)`を指定すると、`ロール名: タグ名`のリストからインタラクティブに絞り込んでタグ名を複数選択でき、選択したタグのみAnsibleを実行できます :blush:

# やっていること
下記のワンライナー処理を呼び出す関数を定義することで実現しています。高速検索ツール[The Silver Searcher](https://github.com/ggreer/the_silver_searcher)とインタラクティブフィルタ[peco](https://github.com/peco/peco)のおかげで簡単に実現できました。

```bash
# タスク名選択
select_task() { ag --nogroup --yaml '^\- name' ./roles | sed 's/^[^\/]\+\/\([^\/]\+\)\/.*[0-9]\+:- name:\(.*\)/\1: \2/' | peco | sed -e 's/^.\+: *\(.\+\)/\1/'; }

# タグ名選択
select_tags() { ag --nogroup --yaml '^  tags' ./roles | sed 's/^[^\/]\+\/\([^\/]\+\)\/.*[0-9]\+:  tags: *\(.\+\)/\1: \2/' | sort | uniq | peco | sed 's/^.\+: *\(.\+\) */\1/g' | tr '\n' , | sed 's/,$//'; }
```

**1. The Silver Searcherで検索**
まず、Silver Searcher(以下、ag)でrolesのファイルからタスク名を抽出しています。ポイントは`--nogroup`オプションで、これをつけると各検索結果の前にファイルパスを付与した出力形式になります。今回はファイルパスに含まれるロール名を取得したかったので必須となりました。

**2. sedで整形**
ユーザには`ロール名: タスク名`や`ロール名: タグ名`のリストから選択させたいので、sedを使ってagの出力結果を整形しています。なお、sedはGNU版(Linuxとか)とBSD版(Macとか)とで動作が若干異なります。上記はGNU版で、BSD版では以下のようになります。

```bash
# タスク名選択
select_task() { ag --nogroup --yaml '^\- name' ./roles | sed -E 's/^[^\/]+\/([^\/]+)\/.*[0-9]+:- name:(.*)/\1: \2/' | peco | sed -E 's/^.+: *(.+)/\1/';}

# タグ名選択
select_tags() { ag --nogroup --yaml '^  tags' ./roles/ | sed -E 's/^[^\/]+\/([^\/]+)\/.*[0-9]+:  tags: *(.+)/\1: \2/g' | sort | uniq | peco | sed -E 's/^.+: *(.+) */\1/g' | tr '\n' , | sed -E 's/,$//'; }
```

**3. pecoでインタラクティブフィルタ化**
sedで整形した結果をpecoに渡しています。これだけでpecoが素晴らしいUIを提供してくれます。pecoでなくfzfなどでも行けると思いますが、私が普段使っているのでpecoを利用しました。

**4. sed/trで整形**
pecoは標準では選択した項目をそのまま出力します。そのため、整形しないとロール名も一緒に出力されるためタスク名やタグ名としては使えません。pecoの[カスタムフィルタ](https://github.com/peco/peco#customfilter)を自作すれば好きなように出力内容を変更できますが、今回はpecoには手を加えずに出力結果をsedやtrで整形することにしました。
タグ名として複数選択した場合は複数行として出力されるので、Ansibleの複数タグ指定方式(カンマ区切り)に合わせるようにtrとsedで対応しています。

# まとめ

既存の便利なツールを組み合わせて、Ansibleのタスク名やタグ名をインタラクティブに指定できるワンライナーをご紹介しました。
余談となりますが、コマンドをつないでより便利なことを実現できるパイプのようなUNIX哲学はサービスコンピューティングに通じるところがあると個人的に考えています。「一つのことを、うまくやれ」なんかはまさに現在のマイクロサービスアーキテクチャと同じ思想で面白いですよね。そんなわけでこれからもワンライナーを楽しんでいきたいと思います。


※文中に記載されている会社名、商品名は各社の商標または、登録商標です。