Ansibleを用いたz/OSカスタマイズ自動化 - (4) 雑多な考察

# はじめに
Ansibleを触り始めて気になったことを少し整理しておきます。

## 関連記事
[Ansibleを用いたz/OSカスタマイズ自動化 - (1) 概要/基本構成](https://qiita.com/tomotagwork/items/44e6c02010c6e9025273)
[Ansibleを用いたz/OSカスタマイズ自動化 - (2) z/OS Core Collectionを試す](https://qiita.com/tomotagwork/items/2625d2d8a4a4aad6960b)
[Ansibleを用いたz/OSカスタマイズ自動化 - (3) Ansible提供モジュールを試す](https://qiita.com/tomotagwork/items/56ed181a209648b1e053)
[Ansibleを用いたz/OSカスタマイズ自動化 - (4) 雑多な考察](https://qiita.com/tomotagwork/items/ffd488a8e6e0f9e3b6d6)

# 手続き型 vs 宣言型
IaC(Infrastructure as Code)の製品では、その構成管理方法について大きく2種類に分類されるようです。1つは手続き型(命令型)と呼ばれるもの、もう1つは宣言型と呼ばれるものです。
手続き型は構成作業の手順をコード化するイメージで、宣言型は構成された結果のあるべき姿を定義するイメージでしょうか。
例えば[Terraform](https://www.terraform.io/)というIaCのためのOSSがあります。これは明確に"宣言型"で構成管理を行うツールと言えます。
一方でAnsibleはどうでしょうか？
Ansibleの公式ドキュメントのIntroductionに以下の記述があります。
[Introduction to Ansible](https://docs.ansible.com/ansible/latest/getting_started/introduction.html)
>Ansible uses simple, human-readable scripts called playbooks to automate your tasks. <font color="red">You declare the desired state of a local or remote system in your playbook.</font> Ansible ensures that the system remains in that state. 

この記述を見ると、基本的なコンセプトとして宣言型を目指しているようにも見えますが、実体としては手続き型と宣言型が混在している(個人的にはどちらかというと手続き型に寄っている)ように感じます。

RedHatのドキュメントでは以下のような記述もあります。
[Ansible とTerraform の違いとは？](https://www.redhat.com/ja/topics/automation/ansible-vs-terraform)
>Ansible は宣言型と手続き型の両方に対応します。多くのモジュールは宣言型で動作しますが、手続き型プログラミングアプローチを使用するモジュールもあります。さらに、条件やループなど、Ansible 言語の一部の構成要素では、手続き型ロジックをユーザーが定義できます。この組み合わせにより、1 つのパラダイムに固執するのではなく、実行する必要があることに集中できる柔軟性が得られます。

ベンダーが提供するモジュール単位でも手続き型か宣言型かで、特性が違っているものがあるようです。今回着目しているIBM z/OS core collectionを振り返ってみてみます。

例えば以下のようなモジュールに着目すると...
- [zos_blockinfile](https://galaxy.ansible.com/ui/repo/published/ibm/ibm_zos_core/content/module/zos_blockinfile/): PDSメンバーやファイルに複数行のブロックの挿入/削除等を行う
    - state: [absent|present] により挿入/削除の制御をを行う
- [zos_lineinfile](https://galaxy.ansible.com/ui/repo/published/ibm/ibm_zos_core/content/module/zos_lineinfile/): PDSメンバーやファイルに一行の文字列の挿入/削除等を行う
    - state: [absent|present] により挿入/削除の制御をを行う
- [zos_mount](https://galaxy.ansible.com/ui/repo/published/ibm/ibm_zos_core/content/module/zos_mount/): ファイルシステムのマウントの制御を行う
    - state: [absent|mounted|unmounted|present|remounted] によりマウント/アンマウント等の制御を行う

いずれもstateというパラメーターを指定することで、結果としてどういう状態にあるべきかを制御するような実装になっています。つまり"宣言型"っぽい使い方を想定したモジュールと言えます。

一方で以下のようなモジュールに着目すると...
- [zos_job_submit](https://galaxy.ansible.com/ui/repo/published/ibm/ibm_zos_core/content/module/zos_job_submit/): JCLをサブミットする
- [zos_operator](https://galaxy.ansible.com/ui/repo/published/ibm/ibm_zos_core/content/module/zos_operator/): MVSコマンドを実行する
- [zos_tso_command](https://galaxy.ansible.com/ui/repo/published/ibm/ibm_zos_core/content/module/zos_tso_command/): TSOコマンドを実行する
- [zos_script](https://galaxy.ansible.com/ui/repo/published/ibm/ibm_zos_core/content/module/zos_script/): USS上でスクリプトを実行する

これらは指定したJCLやコマンドを実行するというもので、処理内容は指定したJCLなりコマンド等に依存します。いずれもなんらかの"処理記述"や"命令"そのものを指定して実行させるものですので、"手続き型"っぽい使い方を想定したモジュールと言えます。

z/OS関連のモジュールはオープン系プラットフォームに比べてまだまだ浸透していないということもあり、モジュールの充実度もだいぶ遅れをとっている印象があります。そのせいか、何かの構成作業を行おうとしてもそれ専用のモジュールが提供されておらず、その操作を行うためのJCLやコマンド実行を組み合わせて実現しなければならない状況が多いように思えます。例えばユーザー作成というのはかなり基本的な構成作業の1つだと思いますが、z/OS Core Collectionにはユーザー作成用のモジュールすら提供されていません。ユーザー作成用のコマンドやJCLなどを組み合わせてPlaybookを実装しなければならないというのが現状です。
このような状況を考えると、少なくともz/OS構成管理においては宣言型で統一してPlaybookを作成するというのは、現時点ではあまり現実的ではないようです。ある程度"手続き型"の書き方で割り切ってのPlaybookでの構成管理を設計/実装していく必要があると考えます。

# 冪等(べきとう)性について

AnsibleやTerraformなどIaCのツールでは冪等(べきとう)性[Idempotence]をどこまで担保すべきか、ということが議論にあがる場合があります。
冪等性とは、ひらたく言うと、同じ操作であれば何度実施しても結果が同一になるという性質のことです。この冪等性というのがなかなかやっかいです。
上の項目でまず宣言型か手続き型かを整理したのは、この管理方法が冪等性に大きく関連すると考えているからです。
宣言型で構成を管理する場合はあるべき姿を定義すればよいので定義と実体の比較がしやすく、冪等性を担保するのが比較的容易と考えられます。一方で手続き型で構成を管理する場合は、1回目の処理をどこまで意識して2回目以降の処理を実装するのかを考慮するのがかなり難しくなります。環境や状況、要件によって正解が一意に決まらない場合もあります。
※Ansibleにおける具体的な冪等性の議論についてはWeb上にもたくさん上がっているのでここでは詳細は割愛します。

[Introduction to Ansible](https://docs.ansible.com/ansible/latest/getting_started/introduction.html)
>**Idempotence and predictability**
When the system is in the state your playbook describes Ansible does not change anything, even if the playbook runs multiple times.

Ansible公式ドキュメントには上のような記載もありますが、現在提供されている多数のモジュール実装、Playbookのサンプルなども踏まえると、この"冪等性を担保する"ということは現実的にはなかなか難しいのではないかと思われます。
z/OS構成管理についても、厳密に冪等性を考慮しだすとそのための制約によりがんじがらめにになってかえって管理がしにくくなってしまう事態にもなりかねません。
複数回実行してもなるべくエラーを起こさずに妥当な処理結果となるように落としどころを見つけてPlaybookを設計/実装していく、という方針で進めるのがよいと考えています。

例えば、Ansibleでz/OS上にUser Catalogを作成することを考えてみます。User Catalogを作成するためのモジュールは提供されていないのでそのためのJCLやコマンドを実行するということになります。宣言型っぽく使わせるように作るのは難しいので手続き型っぽく作ることを考えます。つまり、User Catalog作成用と削除用のPlaybookを別に作成するイメージです。冪等性については2回以上実行しても問題ないように最低限の考慮をします。AnsibleのPlaybookはタスクでエラーが生じると基本的にはそこでPlaybookの実行が止まり後続タスクは実行されません。既にUser Catalogが存在している状態で同じUser Catalogを作成するJCLを実行するとエラーになってしまうので、まずUser Catalogの存在チェックを行い、存在していなかった場合はUser Catalog作成用のJCLをSubmitする、存在していた場合はJCLのSubmitをスキップするというような制御を行います。このくらいの方針で作成するのが妥当なのかなと思います。
※宣言型での実装でより厳密な冪等性を考慮すると、削除や変更の場合の処理をどうするか、Ansible管理外の既に最初から存在していたUser Catalogについてはどう考えるか、といったことを考慮する必要があり、泥沼にハマっていきそうです...

# 機能分割について

最初の記事でも紹介したように、z/OSや一部のミドルウェアについては構成管理に使用できるモジュールをまとめた"コレクション"を提供してくれています。しかし、ユーザー作成に代表されるように基本的な操作についてもモジュール化されていない操作が多く、プリミティブなモジュール(zos_job_submitやzos_operatorなど)を駆使してPlaybookを実装していかなければならないのが現状です。JCLやコマンドを直接叩くということは自由度がかなり高くなるので、Playbookとしての使いやすさやメンテナンスしやすさはPlaybookの作り方に大きく依存します。それこそ冪等性や汎用性など作り方次第でどうにでも変わってしまいます。

本来であれば、例えば"ユーザー管理"のようなよく利用しそうな機能はAnsibleの"モジュール"として実装するのが王道のように思われますが、モジュール作成は少しハードルが高い気がしています(やってみればそうでもないのかもしれませんが)。また、がんばって独自の作り物をしすぎてしまうと公式にモジュールが提供された場合に無駄になってしまうリスクもあります。
Ansibleの世界では汎用的なタスクのまとまりを再利用しやすくするために"role"という仕組みが提供されています。"role"はPlaybook記述の1つのお作法という位置づけなので、"モジュール"の作成よりはハードルが低いと思います。
参考: [Roles](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html)
"role"はhandlerやテスト用の仕組みも含めかなりしっかりした機能分割の仕組みと言えそうです。これ以外にも複数のタスクの塊を別ファイルとして作成しておき、他のPlaybookから再利用するというやり方もあります。こちらはファイル分割のような感じですが、これでもまとまった機能を共通のyamlファイルで管理できるので有用そうです。
いずれも、include_role/import_role、include_tasks/import_tasksというディレクティブでPlaybookにroleやtaskuを取り込むことができます。
参考: [Re-using Ansible artifacts](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse.html)
※`include_*`/`import_*` はそれぞれdynamic reuse/static reuseと呼ばれ細かな挙動の違いがありますが、個人的に大きな差異と考えているのはloopの利用有無です。`include_*`はloopが利用できますが`import_*`は利用不可です。このため`include_*`の方が応用範囲が広いのではないかと思います。

z/OS構成管理についても、汎用的に使いそうなタスクは"role"や独立した"task"として定義し、Playbookからはなるべく汎用的な"role"/"task"を使用するという実装にするのが望ましいと考えます。
ちなみに、z/OS関連のCollectionを使用した各種サンプルのPlaybookが以下に提供されています。
参考: [Github - z_ansible_collections_samples](https://github.com/IBM/z_ansible_collections_samples)
ここに提供されているサンプル内でも汎用的に使えるタスクは"role"として記述されていますので、ここで提供されている"role"をそのまま流用することもできるかもしれません。
ただし、一部のPlaybookはdeprecatedになっていたり、新しいバージョンのcollectionには対応していないものがちらほらあるようですので、あくまでこれらはサンプルとして参照し、自身の環境に合わせて適宜作成/カスタマイズするのがよいのではないかと思います。

# 変数名について

変数名の命名規則については、メジャーなところではキャメル・ケース(ex. userName)、スネーク・ケース(ex. user_name)あたりでしょうか。
どちらを使用するのがよいか悩ましいところですが、なんとなくAnsibleではスネーク・ケースがよく使われていそうだなという感じですかね。
参考:[Creating valid variable names](https://docs.ansible.com/ansible/devel/playbook_guide/playbooks_variables.html#creating-valid-variable-names)
少なくとも`-`(ハイフン)は使用できないのでハイフン区切りのケバブ・ケース (というらしい、はじめて知った...) はNGですね。


# z/OS Core Collectionのバージョンについて

2024年6月に z/OS Core Collection V1.10.0がリリースされました。このバージョンではかなり大きな変更が含まれています。
参考: [IBM z/OS Core Collection V1.10.0- Release Notes](https://ibm.github.io/z_ansible_collections_doc/ibm_zos_core/docs/source/release_notes.html#version-1-10-0)
大きな変更としては、

- ZOAU V13.0が前提となった
- Playbookで指定する多くのパラメーター値について大文字/小文字判定ルールが変更された

これらの変更により、これまで問題無く動作していたPlaybookでも、ZOAUやCollectionのバージョンを上げると動かなくなるものが多数出てくる可能性が高いです。つまり、バージョン間の互換性が担保されていません。
ここまで影響の大きい変更はさすがに珍しいとは思いますが、メインフレームの世界でのバージョン互換性担保の文化からするとかなり割り切った変更が入ることもあるということは認識しておくべきことです。これはオープンな文化の悪い所でもあり、かつ、良い所でもあると考えます(ある意味悪い所は割り切って直すというのはよいですが、ちと今更感は拭えない...)。

ちなみに上の2つ目の大文字/小文字ルール変更は、既存ユーザーにはなかなか受け入れがたい変更ではありますが、背景としてはRedHatの認証を受けるのに必要なものようです。
参考: [Automation Z has never been easier, 1.10.0-beta.1 will deliver ease.](https://community.ibm.com/community/user/ibmz-and-linuxone/blogs/demetrios-dimatos1/2024/05/16/ibm-ansible-zos-core-1100-beta1-has-released?communityKey=ce54fe94-0145-4832-a0ef-4ea81d6062cc)
今後はこういう互換性をガン無視した大きな変更ができるだけ入らないことを祈るばかりです...。