Ansible Rolesを使ってplaybookのメンテナンス性を上げる

### はじめに

[前回のansible構築](https://qiita.com/kazuharu2022/items/e94fdc9363076fc80535)から結構時間が経ってしまいましたが、前回とりあえず動くplaybookを作成したところから、今後の検証環境を構築する上で、必要となる構成管理を簡単にするために、Rolesを使って再利用がしやすく変更に強いPlaybookに分割していきます。

そして私が今回ansibleを使っているのはslurmの検証環境構築用ですが、実際の本番環境に適用するためのplaybookとしても利用出来るように記述することも可能です。実際この考えでplaybookを作っていないとあとから本番環境に適用するときに、検証環境と本番環境の違いでシステムが動かないなんて事が発生します。

### Rolesとは

通常playbookファイルのtasksに以下のようにタスクを羅列して書いていくと、どんどん可読性が落ち、変更に強くするために、変数なんかをこの段階で使ってしまうと何がなんだか分からなくなってしまいます。

Ansbile Rolesを使う事で、playbookの中から、さらにplaybookを呼び出す事が出来ます。この仕組みのお陰で複数のタスクを一つのRole(グループ）としてまとめてplaybookに書く事が可能です。

ただし何でもかんでもRolesで分割したら良いモノでも無いです。一応RHEL系Debian系の違いを吸収出来るようなplabookも記述可能ですが、ansible以前のシステムコンセプトと役割がしっかりしていないと、メンテナンスが難しいplaybookになってしまいます。

Rolesはlaravel,strusts,django,flaskなど開発系フレームワークと同様に、ルールが規定されています。このルールを知らないと想定と違う挙動で悩まされるので、最低限のルールは理解して置きましょう。

:::note warn
ここ最近RHEL系のcentos9から有償版と無償版の完全互換性が失われた事で、有償ソフトウェアの対応OSからcentos及びrocky linuxが外れるような事が発生しています。　実際は動くのですがサポートを受けられなくなるので、新しくubuntu Suse LinuxにOSを変更する動きがあります。このときに、複数OSに対応出来るansibleを作るのも一つの手ですし、今後使わなくなるOS用に対応するplaybookを書く意味は無いため、新しくplaybookを書くのもありです。ただRolesを適切に記述していたら、どちらにせよ変更が少なく再利用出来ます。
:::

:::note info
今回の検証環境ではOSはRHEL系のrocky linux8.8で統一しているのでOSの違いを吸収する書き方はしません。
:::

### Rolesの最低限のルール

ansibleはRolesでは、以下のディレクトリ名及びファイル名を前提としています。
詳細は、[ansibleの公式ドキュメント](https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_reuse_roles.html)を参照してください。私の方で最低限のルールはここにピックアップします。

Rolesでは以下のディレクトリ名であることが前提です（使わないディレクトリがなくても問題無い）
```text
- tasks      ・・・ これは説明するまでもなくtaskに関する記述を記載します
- handlers   ・・・ taskの実行がされた事をトリガーとして動作する処理（主に再起動など）を記述する
- defaults   ・・・ 基本的な変数を設定する
- vars       ・・・ 条件によって変数の値を定義しているファイルを配置する
- files      ・・・ taskで配布するファイルを配置
- templates  ・・・ 変数によってファイル内容を置換するファイルを配置
- meta       ・・・ 今回の対象外
```
そしてRolesでは各ディレクトリの直下にある```main.yml```ファイルが読み込まれます。

最後に変数の定義は至る所で出来るため、どこが一番優先されるかは確認して置いてください。
意図通りに動かなかった原因の一つに、この変数の優先順位を考えずに設定している事が挙げられます。

[公式に優先順について記載されています](https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_variables.html?highlight=variable%20precedence#ansible-variable-precedence)

### まずは分割とその準備

まずは大まかに
- セキュリティの無効化
- NISクライアントの設定
- NFSクライアントの設定

で分割してみます。slurmのインストール部分は省きます。

まずはansible-playbookを実行する際に指定するplaybookファイル：site.ymlを作成します。

ここではインストール対象を```slurm01```にしました。
```yaml:site.yml
--- 
  - hosts: slurm
    roles:
      - disable_security
```
etc/hostsファイルの中は以下の通りです。（nnn.nnn.nnn.nnnはIPアドレス）
```text
[slurm]
nnn.nnn.nnn.nnn ansible_ssh_user=root ansible_ssh_private_key_file=/root/.ssh/id_ed25519
```

site.ymlが保存されているディレクトリ直下でディレクトリの作成を実施
セキュリティの無効化では以下のディレクトリのみ作成します。
```bash
mkdir -p roles/disable_security 
mkdir -p roles/disable_security/tasks
mkdir -p roles/disable_security/handlers
```
ここではファイアウォールとselinuxを無効化にしています。

:::note warn
私自身の考えでは検証環境以外でも不特定多数にサービスを提供していない限りはselinuxは無効化で問題無いと考えています。今は色んな箇所でセキュリティの仕組みを入れられるが故に、似たようなセキュリティを多数設定しすぎてメンテナンス性が極端に落ちてしまいます。ここら辺は境界線モデルにするのかEDRにするのかなどコンセプト次第ですが端末系はEDR・サーバー系は境界線モデルでも十分なのでは？と思っています（パブリックサービスなどは別）
:::

```yaml:roles/disable_security/tasks/main.yml
---
- name: Disable and stop firewalld
  systemd:
    name: firewalld
    enabled: false
    state: stopped

- name: Disable SELinux
  selinux:
    state: disabled
  notify: Reboot the system
```

```yaml:roles/disable_security/handlers/main.yml
---
- name: Reboot the system
  reboot:
    msg: "Rebooting after disabling SELinux"
    reboot_timeout: 300  # タイムアウトは必要に応じて調整
```

ではこの段階で一度実行してみます。
![スクリーンショット 2024-09-29 14.10.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3856269/f84a7adb-45f9-7753-59c5-fc7f76f35112.png)

ちゃんとハンドラーが動作して、rebootも掛かりました。

### nis_clientロールの追加
では同じ要領でnisクライアントの方もやっています。

```yaml:site.yml
--- 
  - hosts: slurm
    roles:
      - disable_security
      - nis_client
```

nis_clientロール用のディレクトリも作成します。
```bash
mkdir -p roles/nis_client
mkdir -p roles/nis_client/tasks
mkdir -p roles/nis_client/handlers
```

NISクライアントの設定を行う際には、NISのドメイン名とNISサーバーを指定します。
これは検証環境と本番環境で異なるので、変数化して置いた方が便利です。
教科書的に言えば```roles/nis_client/defaults/main.yml```に変数の定義をしておくのですが、
こういう奥まった所に記載していると正直忘れます。私としてお勧めなのが、hostsファイルに追記して置いた方が、検証用と本番用の設定を切替えが簡単です（この段階では）。

```yaml:roles/nis_client/tasks/main.yml
---
- name: Install NIS and related packages
  dnf:
    name:
      - ypbind
      - rpcbind
      - oddjob-mkhomedir
    state: present

- name: Set NIS domain name
  command: ypdomainname '{{ NISDOMAIN }}'

- name: Add NISDOMAIN to /etc/sysconfig/network
  lineinfile:
    path: /etc/sysconfig/network
    line: "NISDOMAIN={{ NISDOMAIN }}"
    create: yes

- name: Add NIS server to /etc/yp.conf
  lineinfile:
    path: /etc/yp.conf
    line: "domain {{ NISDOMAIN }} server {{ NISSERVER }}"
    create: yes

- name: Select NIS profile with authselect
  command: authselect select nis --force

- name: Enable mkhomedir with authselect
  command: authselect enable-feature with-mkhomedir
  notify: Enable and restart NIS-related services

```

追加部分だけ抜粋しました。

```text:etc/hosts
[slurm:vars]
NISDOMAIN=<nisドメイン名>
NISSERVER=<nisサーバー名 or IP>
```

NIS関連のパッケージをインストールし、設定も終わった段階でハンドラーを呼び出しています。

```yaml:roles/nis_client/handlers/main.yml
- name: Enable and restart NIS-related services
  systemd:
    name: "{{ item }}"
    enabled: true
    state: restarted
  loop:
    - rpcbind
    - ypbind
    - nis-domainname
    - oddjobd
```
再度実行
```bash
ansible-playbook -i etc/hosts site.yml
```

###　検証環境と本番環境の違いをansibleで吸収

最後に検証系と本番系の違いをansibleで表現するために、検証系はhostsファイルを/etc/hostsに配布し、本番系ではhostsを配布しない挙動を入れてみます。

```text
[slurm:vars]
NISDOMAIN=<nisドメイン名>
NISSERVER=<nisサーバー名 or IP>
HOST_ENVIRONMENT=TEST

```
hosts.testはfilesに配置しておく。
```yaml
- name: Overwrite /etc/hosts if HOST_ENVIRONMENT is TEST
  copy:
    src: hosts.test
    dest: /etc/hosts
    backup: yes  
  when: HOST_ENVIRONMENT == "TEST"
````
この状態で一度動く事を確認してください。
この状態のままでもいいですが、一応varディレクトリを使ったやり方も記載します。

まずはhostsの変数はHOST_ENVIRONMENTのみにします。
```text
[slurm:vars]
HOST_ENVIRONMENT=TEST
```
site.ymlの設定に、変数ファイルの読み込みを指定します。```vars_files```

```yaml:site.yml
---
- name: Manage slurm nodes
  hosts: slurm
  vars_files:
    - "vars/{{ HOST_ENVIRONMENT }}/main.yml"
  become: true 
  roles:
    - disable_security
    - nis_client
    
```
検証(TEST)環境に関係する変数は以下の場所に設定します。

```bash
mkdir -p roles/nis_client/vars/TEST
```

ここではイコール（＝）からコロン（：）に変更になっている事に注意してください
```yaml:roles/nis_client/vars/TEST/main.yml
NISDOMAIN:<nisドメイン名>
NISSERVER:<nisサーバー名 or IP>
```

これで実行しても挙動は変わらないハズです。
このように環境毎に違う変数をあらかじめ変数ファイルとして定義しておき、読み込むやり方も出来ます。

ただ多数の変数や環境がない場合は、素直にhostsファイルに記述することをお勧めします

残りのNFSや他のも同じ感じなので、記述はスキップします。