🅰️

Ansible AWX のリソースを Terraform で作る

2024/08/22に公開

この記事を書いている時点で awx 公式や community が管理する terraform provider はない状態ですが、以下の awx provider を使えばある程度の AWX リソースが作成できます。

https://registry.terraform.io/providers/denouche/awx/latest/docs

個人所有のリポジトリですが今までの総ダウンロード数が ~ 190 k 程度となっているため利用ユーザーは結構多いことが伺えます。
ただ resource や data の説明の多くは TBD となっているため、パラメータで不明な部分は適宜 tower の API reference と照らし合わせて見ていく必要があります。

https://docs.ansible.com/ansible-tower/latest/html/towerapi/api_ref.html

上記の terraform provider を使って awx で ansible playbook を実行するために必要なリソースが作成できるか検証してみます。

前準備

検証用に AWX Operator を使って k8s クラスタ上に awx を構築します。ただ動作確認だけであれば docker で構築した awx でも特に問題ないかと思います。

operator は helm chart でインストールできますが、検証時点では以下のドキュメントが表示されない状態となっていました。

https://ansible.readthedocs.io/projects/awx-operator/en/latest/installation/helm-install-on-existing-cluster.html

また、つい最近のバージョンでは helm repository の URL が変更されたようです。

https://github.com/ansible/awx-operator/issues/1940

検証時には以下の url で operator がインストールできました。

helm repo add awx-operator https://ansible-community.github.io/awx-operator-helm/
helm repo update
helm install -n awx --create-namespace awx-operator awx-operator/awx-operator

operator pod が起動した後は AWX カスタムリソースを作成する ことで awx を構築できます。基本的にはデフォルト設定でいいですが、後の検証で使用する playbook を自己証明書を使用した self-hosted の Gitlab などに保存している場合、リポジトリから clone する際に証明書の検証に失敗します。そのため Trusting a Custom Certificate Authority を参照に独自の CA 証明書を AWX に取り込みます。
まず独自 CA 証明書 (ca.crt) を secret に保存。

kubectl create secret generic awx-custom-certs \
  --from-file=bundle-ca.crt=./ca.crt

これを使用する awx リソースを作成。

---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx
  namespace: awx
spec:
  service_type: nodeport
  bundle_cacert_secret: awx-custom-certs

これで awx 関連の pod が作成されます。task が awx 本体、web が webUI, postgres がデータベースという構成になっています。

$ k get pod
NAME                                               READY   STATUS      RESTARTS   AGE
awx-migration-24.6.1-mmmgb                         0/1     Completed   0          44h
awx-operator-controller-manager-748c67f659-949wr   2/2     Running     0          44h
awx-postgres-15-0                                  1/1     Running     0          44h
awx-task-7dfdc4cff6-brdck                          4/4     Running     0          44h
awx-web-6b89978556-2z2dg                           3/3     Running     0          44h

同時に作成される awx-service から webUI へのアクセスや api 実行が行えます。

NAME                                              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
awx-operator-controller-manager-metrics-service   ClusterIP   10.96.35.57    <none>        8443/TCP       45h
awx-postgres-15                                   ClusterIP   None           <none>        5432/TCP       44h
awx-service                                       NodePort    10.98.47.230   <none>        80:31301/TCP   44h

初期ユーザーのユーザー名は admin, パスワードは secret から取得できます。

kubectl get secret awx-admin-password -o jsonpath="{.data.password}" | base64 --decode ; echo

上記の service に対して /api/v2/ で各種 API を実行できます。

$ curl -Ssu "admin:password"  http://10.98.47.230/api/v2/  | yq -P

ping: /api/v2/ping/
instances: /api/v2/instances/
instance_groups: /api/v2/instance_groups/
receptor_addresses: /api/v2/receptor_addresses/
config: /api/v2/config/
settings: /api/v2/settings/
...

awx リソース作成

上記で awx の構築が完了したので、こっちく直後のまっさらな状態から terraform でリソースを作成していきます。ここでは簡単な例として、 self-hosted の gitlab リポジトリに保存している ansible playbook や inventory を awx に登録して awx 上から実行できるようにしていきます。

具体的な内容として、postgres のバックアップを作成、保存する処理を ansible playbook で実行するようなユースケースを考えます。これを awx 上から自動で定期実行するような環境を構築することを目標とします。
Gitlab リポジトリ上のファイル構成は以下、こちらは後々に参照します。

.
├── README.md
├── ansible.cfg
├── inventory.yml
├── remote_copy_backup.yml
├── remote_create_backup.yml
├── requirements.txt
├── requirements.yml

接続先の指定

https://registry.terraform.io/providers/denouche/awx/latest/docs

接続先のホスト URL は provider 内の hostname に指定します。
認証情報は username/password か token が使用できます。構築直後では admin ユーザーしかいないのでそのまま username/password で認証します。

terraform {
  required_providers {
    awx = {
      source  = "denouche/awx"
      version = "0.27.0"
    }
  }
}

provider "awx" {
  hostname = "http://[ip or domain]:[port]"
  username = "admin"
  password = "xxx"
}

organization

organization は awx_organization で管理できます。デフォルトでは Default org が存在しています。大規模なリソースを管理する場合は適切に organization を作成することが推奨されますが、検証ではそのまま Default を利用します。

data "awx_organization" "default_org" {
  name = "Default"
}

credentials

ansible では通常 ssh で対象ノードに接続するため、ssh の認証情報を credential として登録します。

SSH key

awx では ssh の秘密鍵と username を合わせて machine という credential type に保存します。awx_credential_machine がこれに対応しており、秘密鍵の内容を ssh_key_data に指定することで作成できます。秘密鍵の内容の受け渡し方は色々ありますが、ここでは簡単のためローカルに配置した秘密鍵を file で読み込んで受け渡します。

resource "awx_credential_machine" "id-rsa-ubuntu" {
  name            = "id-rsa-ubuntu"
  username        = "ubuntu"
  organization_id = data.awx_organization.default_org.id
  ssh_key_data    = file("./id_rsa")
}

project

project は playbook を保存している git リポジトリに対応します(他のソースも指定できますが)。検証では Gitlab 上に保存した playbook を参照するため以下のパラメータを指定します。

  • scm_type: git
  • scp_url: git リポジトリの URL
  • scm_branch: 参照するブランチ名
resource "awx_project" "postgres-backup" {
  name                 = "postgres-backup-project"
  scm_type             = "git"
  scm_url              = "https://gitlab.ops.com/backup/postgres_backup"
  scm_branch           = "main"
  scm_update_on_launch = true
  scm_clean            = true
  organization_id      = data.awx_organization.default_org.id
}

基本的には 1 repo = 1 project なので、複数の git リポジトリを参照する場合はそれぞれ project を作成します。

Inventory

ローカルで ansible-playbook を実行する際は inventory に接続先のホストや変数などの情報を格納しますが、awx では inventory リソースでこれらの情報を一元管理できます。
リソース作成時に直接ホスト等の情報を指定することもできますが、既に git リポジトリ上に inventory ファイルが存在している場合は inventory_source リソースを利用することでその内容を inventory リソースに取り込むことができます。

inventory_source で内容を取り込む場合、awx_inventory リソースでは単に name を指定するだけで良いです。

resource "awx_inventory" "postgres-backup" {
  name            = "postgres-backup"
  organization_id = data.awx_organization.default_org.id
}

次に git リポジトリ上の inventory を取り込むため、inventory が存在する git リポジトリを参照するための project を作成します。検証では上記で作成した project のリポジトリ内に inventory も存在しているため作成は割愛。

最後に awx_inventory_source リソースで上記の project と inventory を関連付けます。source_path では git リポジトリ上の取り込む inventory ファイルをリポジトリのルートディレクトリからの相対パスで記載します。ここではルートディレクトリ直下に存在する inventory.yml を取り入れます。

resource "awx_inventory_source" "postgres-backup" {
  name              = "postgres-backup-source"
  inventory_id      = awx_inventory.postgres-backup.id
  source            = "scm"
  source_project_id = awx_project.postgres-backup.id
  source_path       = "inventory.yml"
}

terraform apply するとインベントリが作成され、ソース タブに指定した inventory source が追加されています。インベントリを使用するジョブ等の実行時に自動で git リポジトリと同期されますが、作成時点では自動で同期されないようです。

手動で同期を実行すると指定した inventory.yml の内容が取り込まれ、ファイル内で定義されているグループ、ホスト、変数等が自動で追加されます。

Job template

project で指定した git リポジトリ上の playbook を実行するには awx_job_templateジョブテンプレートを作成します。
基本的には 1 playbook = 1 job_template に対応します。

resource "awx_job_template" "postgres-backup-1" {
  name           = "postgres-backup-upload"
  job_type       = "run"
  inventory_id   = awx_inventory.postgres-backup.id
  project_id     = awx_project.postgres-backup.id
  playbook       = "remote_create_backup.yml"
  become_enabled = false
}

playbook を実行する際の対象 node への ssh 認証情報は awx_job_template_credential で関連付けます。

resource "awx_job_template_credential" "postgres-backup-1" {
  job_template_id = awx_job_template.postgres-backup-1.id
  credential_id   = awx_credential_machine.id-rsa-ubuntu.id
}

なお、project と job template を同時に作成する際に Error: Unable to create JobTemplate のエラーが出ることがあります。

│ Error: Unable to create JobTemplate
│
│   with awx_job_template.postgres-backup-1,
│   on main.tf line 67, in resource "awx_job_template" "postgres-backup-1":
│   67: resource "awx_job_template" "postgres-backup-1" {
│
│ JobTemplate with name postgres-backup-uploda in the project id 30, failed to create Errors:
│ - playbook: [Playbook not found for project.]

project を設定したジョブテンプレートを作成する際、awx 側ではプロジェクトに指定した git rリポジトリの情報を取得 → リポジトリ内の playbook を取得する処理を行っているようなのですが、terraform provider ではこの処理を待機する部分が微妙らしく、playbook を取得する処理が完了する前にジョブテンプレートを作成しようとして Playbook not found for project のエラーとなるようです。
この場合は少し時間を置いてから再度 terraform apply すればエラーが解消されます。

Job workflow template

複数の job template を一連のワークフローとして実行するにはジョブワークフローテンプレートを使います。
まず上記で作成した postgres-backup-1 テンプレートの後に実行するジョブテンプレートを以下で作成します。

resource "awx_job_template" "postgres-backup-2" {
  name           = "postgres-backup-download"
  job_type       = "run"
  inventory_id   = awx_inventory.postgres-backup.id
  project_id     = awx_project.postgres-backup.id
  playbook       = "remote_copy_backup.yml"
  become_enabled = false
}

webUI からワークフローを作成する際はジョブの依存関係を視覚的に記述できますが、api や terraform で作成する際は awx_workflow_job_template_node リソース等でジョブの実行関係を記述します。まず awx_workflow_job_template でワークフローテンプレートを定義し、その中で最初のジョブテンプレートを実行するための node を awx_workflow_job_template_node で指定します。

  • workflow_job_template_id: ワークフローテンプレートの id を指定。
  • unified_job_template_id: ワークフローで最初に実行するジョブテンプレートの id を指定。
resource "awx_workflow_job_template" "postgres-backup-workflow" {
  name            = "postgres-backup-workflow"
  organization_id = data.awx_organization.default_org.id
  inventory_id    = awx_inventory.postgres-backup.id
}

resource "awx_workflow_job_template_node" "upload" {
  workflow_job_template_id = awx_workflow_job_template.postgres-backup-workflow.id
  unified_job_template_id  = awx_job_template.postgres-backup-1.id
  inventory_id             = awx_inventory.postgres-backup.id
  identifier               = "postgres-backup-upload"
}

次に上記のジョブが成功した際に別のジョブを実行するために awx_workflow_job_template_node_success リソースを定義します。この中で workflow_job_template_node_id に上記のノード id を指定することで、このジョブテンプレートがどのジョブの後に実行されるかを制御しています。

resource "awx_workflow_job_template_node_success" "copy" {
  workflow_job_template_id      = awx_workflow_job_template.postgres-backup-workflow.id
  unified_job_template_id       = awx_job_template.postgres-backup-2.id
  workflow_job_template_node_id = awx_workflow_job_template_node.upload.id
  inventory_id                  = awx_inventory.postgres-backup.id
  identifier                    = "postgres-backup-copy"
}

今回はジョブテンプレート成功時に別のジョブをテンプレートを実行するように条件を指定していますが、失敗したときに実行する際は workflow_job_template_node_failure、成功、失敗に関わらず常に実行する際は workflow_job_template_node_always が使用できます。

これをデプロイして UI で確認すると、想定通り 2 つのジョブテンプレートを直列で実行するワークフローテンプレートが作成されます。

スケジュール実行

ジョブテンプレートやワークフロージョブテンプレートはスケジュール設定することで cron のように定期的に実行することができます。
awx_schedule がこれに対応しており、例えば上記で作成したワークフローを 6 時間毎に定期実行する場合は以下のように指定します。

resource "awx_workflow_job_template_schedule" "postgres-backup" {
  workflow_job_template_id = awx_workflow_job_template.postgres-backup-workflow.id
  name                     = "postgres-backup-schedule"
  rrule                    = "DTSTART:20240822T000000Z RRULE:INTERVAL=6;FREQ=HOURLY"
  unified_job_template_id  = awx_workflow_job_template.postgres-backup-workflow.id
}

スケジュールの間隔等は rrule パラメータで指定します。
Tower API Reference にいくつか指定例が記載されているのでこちらを参照。

POST requests to this resource must include a proper rrule value following
a particular format and conforming to subset of allowed rules.

The following lists the expected format and details of our rrules:

DTSTART is required and must follow the following format: DTSTART:YYYYMMDDTHHMMSSZ
DTSTART is expected to be in UTC
INTERVAL is required
SECONDLY is not supported
TZID is not supported
RRULE must precede the rule statements
BYDAY is supported but not BYDAY with a numerical prefix
BYYEARDAY and BYWEEKNO are not supported
Only one rrule statement per schedule is supported
COUNT must be < 1000
Here are some example rrules:

"DTSTART:20500331T055000Z RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5"
"DTSTART:20240331T075000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1"
"DTSTART:20140331T075000Z RRULE:FREQ=MINUTELY;INTERVAL=1;UNTIL=20230401T075000Z"
"DTSTART:20140331T075000Z RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR"
"DTSTART:20140331T075000Z RRULE:FREQ=WEEKLY;INTERVAL=5;BYDAY=MO"
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=6"
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYSETPOS=4;BYDAY=SU"
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYSETPOS=-1;BYDAY=MO,TU,WE,TH,FR"
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYSETPOS=-1;BYDAY=MO,TU,WE,TH,FR,SA,SU"
"DTSTART:20140331T075000Z RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4;BYMONTHDAY=1"
"DTSTART:20140331T075000Z RRULE:FREQ=YEARLY;INTERVAL=1;BYSETPOS=-1;BYMONTH=8;BYDAY=SU"
"DTSTART:20140331T075000Z RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20230401T075000Z;BYDAY=MO,WE,FR"
"DTSTART:20140331T075000Z RRULE:FREQ=HOURLY;INTERVAL=1;UNTIL=20230610T075000Z"

デプロイすると 6 時間毎に実行がスケジュールされていることが確認できます。

slack 通知

ワークフローが成功したら slack に通知するようにしたいので notifications リソースも作成します。api の方ではジョブテンプレートやワークフローに通知を設定できますが、terraform provider の方では現状 awx_job_template_notification_... のリソースしかないようなので、ここではワークフローの最後に実行されるジョブテンプレートに通知を設定することでワークフローの成功・失敗を判断します。

slack 通知を行うには awx_notification_templatenotification_type: slack に設定した通知テンプレートを作成します。事前に slack 側で app の設定 やチャンネルへのインストールが必要。

resource "awx_notification_template" "job-workflow-notification" {
  name                       = "job-workflow-notification"
  notification_type          = "slack"
  organization_id            = data.awx_organization.default_org.id
  notification_configuration = file("./slack.json")
}

通知先チャンネルの設定などは notification_configuration に json 形式で指定します。
token は Bot User OAuth Token (xoxb- で始まる文字列)を指定します。

slack.json
{
  "channels": [
    "#ansible-awx"
  ],
  "token": "xoxb-xxx",
  "hex_color": "#2EFF2E"
}

作成した通知テンプレートをジョブテンプレートに関連付けるには awx_job_template_notification_template_success を設定します。success はジョブの成功に slack 通知されますが、その他にジョブ開始時に通知する started, 失敗時に通知する failed リソースもあります。

resource "awx_job_template_notification_template_success" "success" {
  job_template_id          = awx_job_template.postgres-backup-2.id
  notification_template_id = awx_notification_template.job-workflow-notification.id
}

作成するとジョブテンプレートの通知タブに上記で作成した通知テンプレートが設定されていることが確認できます。

ワークフローを実行して成功すると以下のようなメッセージが slack に通知されます。

デフォルトのメッセージはやや淡白ですが、通知テンプレートの設定をカスタマイズすることでメッセージの内容やフォーマットを変更できます。詳細は 23.5. Create custom notifications を参照。
また、メッセージ内のドメイン名もデフォルト設定の towerhost となっていますが 23.7. Configure the towerhost hostname for notifications で変更できます。


まとめ

実際に terraform provider によって Gitlab リポジトリ上に存在する backup 作成用の playbook を awx 上で定期的に実行し、実行結果を slack に通知する構成が terraform で作れることが確認できました。
まだ対応していない awx 側のリソースやドキュメントの説明が不足している部分も多いので、適宜 API reference の説明を参照したり、API や Ansible の awx collection を使ってリソース作成が必要になる場面もあります。現時点ですべてのリソースを管理できるわけではないですが、terraform を使い慣れている場合は terraform で IaC 管理できるので便利そうです(そもそも awx リソースを terraform で管理する機会があまりないような気もしますが)。

関連リンク

Discussion