Ansibleタスクの再利用性を高めるためのタスクの粒度について

# 概要

掲題をテーマに自社クラウドサービスの運用で培った経験をもとに紹介したいと思います。
あくまでも一つの考え方として参考になれば幸いです。
もし、皆さんの考え方やお勧めがありましたら是非共有して頂けると嬉しいです！

# はじめに

Ansibleを習いたての頃はタスクの再利用性を考慮してできるだけRole化していました。むしろ、再利用性を高めるためにはRole化しかないと思っていた。
しかし、簡単な処理もRole化したことにより、handlers、templates、filesなどがないtasksしかないRoleが大量に出来上がったことでフォルダ構成が深くなり、管理しづらい状況でした。また、とあるRoleの一部のタスクを流用したい時に`import_role`の`tasks_from`や`vars_from`などを使用することでrole内の一部のタスクを再利用できるが、前述のようにフォルダ構成が深くなり、常用的にタスクを再利用しようとした時に検索性が悪い。([ファイル構成](#ファイル構成)を参照)
なので、再利用性を考慮しつつタスクの粒度について以下で紹介したいと思います。

# 結論

1から順にタスクを構成していくと管理しやすくなると思います。
そして、すべてに共通で言えるのが、**横断的にtagsを付与することが大事！**

1. 再利用しないタスクはそのままベタ書き
2. 再利用するタスクはタスクファイルに分割し、`import_tasks`で読み込む
3. いくつかの処理が複数集まって役割として機能するようになったらRole化し、role内のタスクでも `import_tasks` でタスクを読み込む


# 具体例

以下の一連の処理を例に説明したいと思います。
※以下はあくまでも流れを説明するためのコードで、動作確認はしていません。

1. Apacheのインストール
2. Apacheの設定ファイルの展開
3. サービスの起動
4. コンテンツの配置

そのままplaybookにするとこんな感じになります。

```yaml:sample-playbook.yml
---
- hosts: webservers
  remote_user: root
  tasks:
  - name: Apacheの最新版をインストール
    yum:
      name: httpd
      state: latest

  - name: 設定ファイルを展開
    template:
      src: httpd.j2
      dest: /etc/httpd.conf
    notify:
　    - restart apache

  - name: Apacheサービスの起動
    service:
      name: httpd
      state: started
      enabled: yes

 - name: コンテンツの配置
   copy:
     src: hoge_dir
     dest: htdocs

  handlers:
    - name: restart apache
      service:
        name: httpd
        state: restarted
```

では、タスクファイル化するとどうなるかを見ていきましょう！

## 再利用するタスクはタスクファイルに分割し、`import_tasks`で読み込む

実際は以下のように1タスクごとにタスクファイル化はしませんが、
[以下の例](#タスクファイルの例)のように一連のタスクを1タスクファイルにするとわかりやすいと思います。
また、`tasks/httpd_service.yml`の一部のパラメータを変数化（`_httpd_service_state`）したことで、呼び出し時に動的にパラメータを指定することで再利用性をさらに高めることができます。
**タスクファイル＝関数**、**パラメータ＝引数**と捉えると理解しやすいかと思います。ただし、変数名の命名規則は明確にしないと思わぬ上書き事故に見舞われるのでご注意！

```yaml:tasks/httpd_install.yml
---
- name: Apachの最新版をインストール
  yum:
    name: httpd
    state: latest
```

```yaml:tasks/httpd_config.yml
---
- name: 設定ファイルを展開
  template:
    src: /srv/httpd.j2
    dest: /etc/httpd.conf
```

```yaml:tasks/httpd_service.yml
---
- name: Apacheサービスの起動
  service:
    name: httpd
    state: "{{ _httpd_service_state }}"
    enabled: yes
```

```yaml:tasks/httpd_contents.yml
---
- name: コンテンツの配置
  copy:
    src: "{{ _httpd_contents_path }}"
    dest: htdocs
```

上記のタスクファイルを読み込むplaybookは以下のようになります。

```yaml:sample-playbook.yml
---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
    - import_tasks: ./tasks/httpd_install.yml
      tags:
        - httpd_install

    - import_tasks: ./tasks/httpd_config.yml
      tags:
        - httpd_config

    # サービスのstateを動的に指定
    - set_fact:
        _httpd_service_state: "started"

    - import_tasks: ./tasks/httpd_service.yml
      tags:
        - httpd_service
   
    # コンテンツの配置先のパスを動的に指定
    - set_fact:
        _httpd_contents_path: "hoge_dir"
    
    - import_tasks: ./tasks/httpd_contents.yml
      tags:
        - httpd_contents
```

「タスクファイル」＋「パラメータ化」の有効活用の例として、例えばhttpdサービスを停止する処理を追加したい時に、新たにサービス停止するタスクを追加せずに以下のように変数の値を変更するだけで、簡単にタスクの再利用が可能になります。

```yaml:stop_httpd.yml

    # サービスのstateを動的に指定
    - set_fact:
        _httpd_service_state: "stopped"

    - import_tasks: ./tasks/httpd_service.yml
      tags:
        - httpd_service
```

### 単一処理ではないタスクファイルの例

上記のような単一処理ではなく、一連の処理をタスクファイル化した時の例になります。
AWSのEC2インスタンスのidをlistで取得する処理になります。
tag:Roleの値を変数化するとより可用性が高まるでしょう！

```yaml:tasks/describe_ec2_id_list.yml

# EC2インスタンスのタグRole=Webのインスタンスを取得
- ec2_instance_facts:
    filters:
      tag:Role: "Web"
  register: _instance_facts

# レスポンスをリストに整形
- set_fact:
    _instance_ids: >-
      {%- set ids = [] -%}
      {%- for fact in _instance_facts.results -%}
        {%- if fact.instances | length > 0 -%}
          {%- for instance in fact.instances -%}
            {%- set dummy = ids.append(instance.instance_id) -%}
          {%- endfor -%}
        {%- endif -%}
      {%- endfor -%}
      {{ ids }}
```

## いくつかの処理が複数集まって役割として機能するようになったらRole化し、共通のタスクも`import_tasks`で読み込む

role内も基本的にタスクファイルを再利用する！
上記の例では、handlersやtemplatesがありますが、初期段階でtasksしかない場合は、roleを作成せずにplaybookからは`import_tasks`にとどめ、handlersやtemplates、filesが必要になった段階（役割として機能実装が必要になったら）でRole化で良いかと思います。

```yaml:roles/httpd/main.yml
---
- import_tasks: ../tasks/httpd_install.yml
- import_tasks: ../tasks/httpd_config.yml
- import_tasks: ../tasks/httpd_service.yml
- import_tasks: ../tasks/httpd_contents.yml
```

```yaml:sample-playbook.yml
---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
    - import_role:
        name: httpd
      tags:
        - httpd
```

## ファイル構成

前述のRoleの`tasks_from`でタスクを読み込む場合は、各タスクファイル(install.yml)がRole配下になるので検索性が悪い。
また、例えば他のRole(hoge)からhttpdのinstall.ymlを呼び出す時のパス参照（`../.../httpd`）が複雑になる。

一方タスクファイル化するとすべてのタスクファイルがフラットな構成になり、検索性、可視性が良くなる。
ファイル名に**一定の命名規則**を設けることが必要です。
ただし、tasksの配下をさらにフォルダ分けすると検索性が下がるのでお勧めしません。

```
- sample-playbook.yml
- roles
    |- httpd
        |-handlers
        |-tasks
           |- main.yml
           |- install.yml
           |- config.yml
           |- service.yml
           |- contents.yml
        |-templates
    |- hoge

- tasks
    |- httpd_install.yml
    |- httpd_config.yml
    |- httpd_service.yml
    |- httpd_contents.yml
```

# タグ付け

タスクには極力付けして分類することをおすすめします！
既存の処理に一切影響ないので、今すぐにやってもいいと思います！
タグ付けの理由としては、例えば上記の`sample-playbook.yml`のwebコンテンツ配置だけを再実行したい時（コンテンツを更新したい）にコンテンツ更新するPlaybookを再作成するとコードの冗長化になります。なので、こういう時は`--tags="httpd_contents"`を加えるだけでスポット実行ができる！



# import_tasksとinclude_tasksがあるので用法については注意！

import_tasksとinclude_tasks、import_roleとinclude_roleとありますが、動作に違いがありますので違いを理解しょう！
「[Ansible 2.4 で import_tasks/include_tasks に tags を付けるときの注意点](https://muziyoshiz.hatenablog.com/entry/2018/01/15/231213)」をご参照ください！

# コミュニティ

これから始める方なら安心の日本語でコミュニケーションができるので、興味がある方は是非ご参加ください。

- [Slack](https://ansiblejp.slack.com/join/shared_invite/enQtNzQwNTEyNTc2Mjc3LTRmYzBkY2FhM2RhOGIzNjVhYTczMDdiODY0YWFiMjdmMGRkNGJiZDYzN2I4M2NjZDA5MjkxYzU3ZWQyMzFhYjU)･･･日本のユーザ会（800名以上）になります。これから始める方で気軽に質問できるので、是非ご参加ください！
- [connpass](https://ansible-users.connpass.com/)･･･Ansible関連イベントの募集を行っています！