zaki work log

作業ログやら生活ログやらなんやら

[Ansible / AAP / AWX] AnsibleでAAPリソースの作成を自動化する

本エントリは「エーピーコミュニケーションズ Advent Calendar 2023」の24日目のエントリです。クリスマスイブ🎄は一切関係ないネタです。

Ansible Automation PlatformのAutomation Controller(旧Ansible Tower / 以下AAP)AWXGUIでAnsibleを実行することができるアプリケーションですが、率直に言ってマウス使って操作…とくにプロジェクトやテンプレート作ったりするのって億劫じゃないでしょうか?私は嫌ですやりたくありません(直球)
この記事ではそんなGUI操作が必要なAAPやAWXの操作をAnsibleで自動化するためのタスクの実装について紹介します。

接続方法

サーバーアドレス

最初に戸惑いがちなところですが、抑えるべきポイントは「タスクが実行されるホスト(hosts指定のホスト)から見たAAPのアドレスを指定する」です。
指定の方法は2通りあり、環境変数CONTROLLER_HOSTで指定するか、タスクごとに各モジュールのcontroller_hostパラメタで指定です。
基本的には1台のAAPに対して様々なリソースを作成していくパターンが多いので、その場合は環境変数をプレイに指定すれば各タスクの記述が減ってplaybookがスッキリします。本記事では環境変数で指定している前提でタスク実装の紹介していきます。

認証情報

これもサーバーアドレスと同様に、環境変数もしくは、タスクごとに各モジュールのcontroller_usernamecontroller_passwordで指定します。パスワード認証でなくトークンを使う場合は環境変数CONTROLLER_OAUTH_TOKENかパラメタcontroller_oauthtokenを使います。
通常はサーバーアドレスとセットで指定することになると思います。

運用時の操作であれば適切なロールを設定したユーザーのトークンを使うのが良いですが、AAP/AWXインストールから初期構築までをすべて自動化するのであればadminとそのパスワード認証を指定することになります。
(ゼロの状態からトークンでの認証ってできない…よね?)

実装例

ローカル実行でリモートのAAPへ接続する場合

おそらく一番よくあるパターン。
localコネクションプラグインでタスクはローカル実行しつつ、CONTROLLER_HOSTでAAPサーバーを指定する構成。

---
- hosts: localhost
  environment:
    CONTROLLER_HOST: 192.168.0.79
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: curry_tabetai

  tasks:
    # ...

また、AAPの場合はHTTPS接続でポートも443/TCPなのでアドレスのみ指定すればOKですが、AXWは構成によっては(Kubernetesからの公開方法によっては)HTTPだったりポート番号もデフォルト以外の場合もあります。
その場合は、httpから記述すればOK
(この場合はCONTROLLER_VERIFY_SSLは無くても動く)

- hosts: localhost
  environment:
    CONTROLLER_HOST: http://192.168.0.75:8080
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: ...

タスク実行ノードを中継サーバーにする場合

Ansibleを実行するノードからAAPへ疎通がない場合などの構成。
hostsで指定する対象ノードから見たAAPのアドレスをCONTROLLER_HOSTに指定。

---
- hosts: server
  environment:
    CONTROLLER_HOST: 172.29.1.21
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: curry_tabetai

  tasks:
    # ...

AnsibleのマネージドノードとしてAAPのホストに直接接続

aapに指定しているサーバーにSSH接続し、そこからlocalhostで接続する、という構成。
複数のAAPサーバーがあり、設定内容が同一であれば、この構成ならまとめて処理できます。

- hosts: aap
  environment:
    CONTROLLER_HOST: localhost
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: curry_tabetai

  tasks:
    # ...

AWXの場合はkubectlコネクションプラグイン使用

SSH接続する構成が取れるのはAAPの場合で、AWXの場合はコンテナ内でSSHは動いていないため、同様の構成をとる場合はkubectlコネクションプラグインを使う必要があります。

- hosts: awx
  environment:
    CONTROLLER_HOST: http://localhost:8052
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: ...

※ 以下は2023年12月時点での内容。AWXはたまにコンテナ構成が変わるので、その場合は指定する対象が変化するかも。おそらく「web」に対して接続すればよいと思うけど…

HTTPであることと、接続先のポートが8052なのがポイント(これはServiceリソースのtargetPortで確認できます)

インベントリファイルは以下のようになります。
この例はAWXはawxネームスペースにawx-demoの名前でデプロイされています。対象はWebのPodでここではawx-demo-web-68b54f6bdf-mx55sというPod名です。 (いくつかのコンテナで構成されていますがwebのコンテナに接続します)

[awx]
awx ansible_kubectl_pod=awx-demo-web-68b54f6bdf-mx55s ansible_kubectl_namespace=awx ansible_kubectl_container=awx-demo-web

[all:vars]
ansible_connection=kubectl

kubectlを実行したときのpodとserviceの状態は以下の通り。

$ kubectl get pod,svc -n awx
NAME                                                   READY   STATUS    RESTARTS   AGE
pod/awx-operator-controller-manager-76b545976d-jlcts   2/2     Running   0          16h
pod/awx-demo-postgres-13-0                             1/1     Running   0          16h
pod/awx-demo-task-5ff94fb4d9-65zdt                     4/4     Running   0          16h
pod/awx-demo-web-68b54f6bdf-mx55s                      3/3     Running   0          15h

NAME                                                      TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
service/awx-operator-controller-manager-metrics-service   ClusterIP      10.43.167.210   <none>         8443/TCP         16h
service/awx-demo-postgres-13                              ClusterIP      None            <none>         5432/TCP         16h
service/awx-demo-service                                  LoadBalancer   10.43.194.190   192.168.0.75   8080:30448/TCP   16h

kubectlコネクションプラグインについては以下参照。

zaki-hmkc.hatenablog.com

パスワードでなくトークンを使用する場合

ここまではCONTROLLER_PASSWORDを使ってパスワードを指定した例でしたが、トークン(パーソナルアクセストークン)の場合は以下の通り。

  environment:
    CONTROLLER_HOST: 192.168.0.79
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: zaki
    CONTROLLER_OAUTH_TOKEN: ....... (ユーザーメニューの「トークン」で作成するトークン文字列)

リソース作成

サブスクリプション

awx.awx.licenseモジュールを使用します。
これはAWXになくAAPのみ。
実行ノード上にzipのマニフェストファイルを配置しておき、パスを指定します。

  - name: set the license
    awx.awx.license:
      manifest: manifest.zip

マニフェストファイルの作成方法は以下参照。

zaki-hmkc.hatenablog.com

認証情報

awx.awx.credentialモジュールを使用します。
よく使うものを紹介。

ソースコントロール

Gitリポジトリにアクセスするための認証情報で、主にプロジェクトの更新時に使用。

- name: create git repository credential
  awx.awx.credential:
    name: git-repository-credential
    organization: Default
    credential_type: Source Control
    inputs:
      username: username
      password: password

公開鍵認証で秘密鍵を登録したい場合は、passwordでなくssh_key_dataを使用して、鍵ファイルの内容を指定します。

    inputs:
      username: zaki
      ssh_key_data: '{{ lookup("file", "~/.ssh/id_rsa") }}'

マシン

Ansible実行時のホストに対するSSH認証情報として使用。

- name: create ssh credential
  awx.awx.credential:
    name: server
    organization: Default
    credential_type: Machine
    inputs:
      username: username
      password: password

SSH秘密鍵を登録するのであれば前述ソースコントロールの場合と同様にssh_key_dataを使用します。

コンテナレジストリ

認証が必要なコンテナレジストリで使用。
例えばDocker Hubへのログインであればdocker.ioを指定します。

- name: create registry credential
  awx.awx.credential:
    name: dockerhub credential
    organization: Default
    credential_type: Container Registry
    inputs:
      host: docker.io
      username: username
      password: password

カスタム認証情報タイプ

認証情報タイプの作成

プリセットの認証情報の型で足りない場合にカスタム認証情報タイプを作成する場合、awx.awx.credential_typeモジュールを使用します。 以下は「ホスト名」「ユーザー名」「パスワード」の3値を一つの情報として定義する例。

インジェクター{{ 変数名 }}という文字列を設定するため、rawを使ってJinja2のテンプレート処理を無効にしているのがポイント。

  - name: create credential type for host
    awx.awx.credential_type:
      name: host-auth-info
      kind: cloud
      inputs:
        fields:
          - id: username
            type: string
            label: username
          - id: password
            type: string
            label: password
            secret: true
          - id: hostname
            type: string
            label: hostname
      injectors:
        extra_vars:
          auth_username: "{% raw %}{{ username }}{% endraw %}"  # "{{ uaesrname }}"
          auth_password: "{% raw %}{{ password }}{% endraw %}"  # "{{ password }}"
          auth_hostname: "{% raw %}{{ hostname }}{% endraw %}"  # "{{ hostname }}"

ちなみにkindは必須項目でドキュメントを見るとcloudnetが指定できると書かれているが、どこに作用しているかは不明。。あまり影響しなさそうな気がしますが。。

Note that only cloud and net can be used for creating credential types.

上記の自作認証情報タイプの認証情報

前述host-auth-infoの認証情報タイプを使った認証情報を作成するには、credential_typeに認証情報タイプ名を指定します。

  - name: create my credential
    awx.awx.credential:
      name: my-credential
      organization: Default
      credential_type: host-auth-info
      inputs:
        hostname: nijigasaki.example.org
        username: zaki
        password: tokimeki

ジョブ関連

インベントリとホスト

awx.awx.inventoryモジュールを使用します。
ホスト作成時にインベントリの指定があるため、まずインベントリを作成してからホストを作成します。

インベントリ作成

- name: create inventory
  awx.awx.inventory:
    name: my inventory
    organization: Default

ホスト作成

awx.awx.hostモジュールを使用します。
inventoryに作成済みインベントリを指定して、そこに属するホストを登録します。
ホスト変数の指定がある場合はvariablesで指定します。

- name: add host to inventory
  awx.awx.host:
    name: "{{ item.name }}"
    inventory: my inventory
    variables:
      ansible_host: "{{ item.addr }}"
  loop:
    - name: server1
      addr: 192.168.0.71
    - name: server2
      addr: 192.168.0.72

プロジェクト作成

awx.awx.projectモジュールを使用します。
認証が不要なGitリポジトリとしてhttps://github.com/zaki-lknr/ansible-sample.gitを指定するなら以下。

  - name: create project
    awx.awx.project:
      name: sample project
      organization: Default
      scm_type: git
      scm_url: https://github.com/zaki-lknr/ansible-sample.git
      scm_update_on_launch: false

認証が必要なリポジトリの場合はcredentialパラメタに作成済みの認証情報名を指定します。

      credential: git-repository-credential

実行環境

awx.awx.execution_environmentモジュールを使用してコンテナレジストリ上のEEコンテナを指定します。
認証が必要な場合は、credentialに「credential_type=Container Registry」を指定して作ったクレデンシャルリソースを指定します。
pullにはイメージpullポリシーを指定します。

- name: create ee setting
  awx.awx.execution_environment:
    name: Network Automation Execution Environment
    image: docker.io/zakihmkc/ansible-ee:1.2.3
    pull: missing
    credential: dockerhub credential

ジョブテンプレート

ジョブテンプレートを作成するには、awx.awx.job_templateモジュールを使用してここまで掲載したプロジェクトやインベントリ、認証情報、EEなどを指定します。
playbookにはリポジトリに存在するプレイブックのファイル名を指定する必要があります。(存在しないとエラー)

- name: create job template
  awx.awx.job_template:
    name: sample job template
    job_type: run
    project: sample project
    organization: Default
    inventory: my inventory
    credentials:
    - server1
    - my-credential
    playbook: path/to/playbook.yml
    verbosity: 0
    execution_environment: my-image

ワークフローテンプレート…は長いのでまた別途 m__m

awx.awx.workflow_job_templateを使って定義します。
schema以下に実行したジョブテンプレートを記述して、relatedに成功時・失敗時などの次のジョブを指定します。それぞれのジョブテンプレートはidentifierでタスク定義内で使用するIDを割り振ってそのIDを指定します。

スケジュール

作成済みジョブテンプレートを定期実行させたい場合はawx.awx.scheduleモジュールを使って作成します。
スケジュール定義はrruleに記述しますが、生で書くのは若干複雑というほどでもないけどハードコーディングせざるを得なくなるため、awx.awx.schedule_rruleset lookupプラグインを使用すると管理しやすいです。
ただしschedule_rrulesetを使用するにはPythonpytzパッケージが必要なのでpipでインストールしておく必要があります。

- name: create schedule
  awx.awx.schedule:
    name: sample job template schedule
    unified_job_template: sample job template
    rrule: "{{ query('awx.awx.schedule_rruleset', start_time, rules=rrules, timezone='Asia/Tokyo') }}"
  vars:
    rrules:
      - frequency: 'day'
        interval: 1
    # 現在日(リソース作成日)の12時を基準に"%Y-%m-%d HH:MM:SS"書式の文字列作成
    start_time: "{{ now(false, '%Y-%m-%d') }} 12:00:00"

あとschedule_rruleもあって、どちらを使っても以下の警告が出て解決できなかった(運用では実際はこっち使ってたりするけど)
メッセージの感じからplaybookの書きっぷりじゃなくてプラグインの内部実装の方に修正が必要そうな感じがしなくもないけど…詳細不明。

[DEPRECATION WARNING]: The lookup plugin 'awx.awx.schedule_rruleset' was expected to return a list,
got '<class 'str'>' instead. The lookup plugin 'awx.awx.schedule_rruleset' needs to be changed to return a list. 
This will be an error in Ansible 2.18. This feature will be removed in version 2.18.
Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

…なんかここまで書いてるとlookup plugin使わず生で書いてもよいかもしれない。。

その他

設定

設定についてはawx.awx.settingsモジュールを使用します。
インベントリファイルへの指定でできそうだけどわからなかった「ベースURLの指定」をセットするには以下の通り。
自動化を実行するだけであればこの設定は特にイジらなくても大丈夫だけど、ベースURLは「通知」を行う場合のメッセージ内のURLに使われるので、使い方によってはきちんと設定しておきたい値。

- name: update base url
  awx.awx.settings:
    name: TOWER_URL_BASE
    value: "https://ansible.example.org"

他には、例えばAWX_ISOLATION_SHOW_PATHSに空リストを設定するには以下。

- name: unset isolation path (workaround)
  awx.awx.settings:
    name: AWX_ISOLATION_SHOW_PATHS
    value: []

access.redhat.com

ジョブの起動

ジョブを起動するにはawx.awx.job_launchモジュールを使用します。
job_templateにジョブテンプレート名を指定すればOK

- name: launch sample job template
  awx.awx.job_launch:
    job_template: sample job template

通知のテスト

モジュールはなさそうなので、uriを使ってAPIを直接叩きます。
URLに含める必要がある通知ID(以下例でのnotify_id)は事前に知っておく必要があります。

- name: notification test
  # モジュールが無さそうなのでRESTで実行
  ansible.builtin.uri:
    method: POST
    url: "https://ansible.example.org/api/v2/notification_templates/{{ notify_id }}/test/"
    url_username: admin
    url_password: "{{ password }}"
    validate_certs: false
    force_basic_auth: true
    status_code: [200, 201, 202]  # ドキュメントは201となってるが実際は202が返る

ほんとは先に「AAP/AWXの結果をSlackに通知する設定」という記事を書いておきたかったのに…いつか書きます。

docs.ansible.com


環境

  • Ansible実行元
  • 自動化対象
    • AAP Controller 4.4.8 (on RHEL9 / AAP 2.4.3)
    • AWX 23.4.0 (on K3s 1.28.4)

弊社では各展示会に出展する際に「ネットワーク自動化デモ」を実施しています。

automation.ap-com.co.jp

本記事で紹介したタスクはこのデモ環境を構築するときに使用しており、環境の構築をAnsibleで自動化しています。

(以下懺悔) 1年以上前からこの構築を行っててAnsible使った自動構築の情報としては持っていたので「いつかブログでまとめよう…」と思いつつもずっと保留してる間に、チームメンバーが何人も「AnsibleでAAPリソース作成の自動化」を行う機会があって、でもわかりやすい情報共有ができずに「デモの自動構築のソース参考になるよ」とリポジトリの場所を教えることしかできなかったという機会損失が今年は何度もあり、本当に申し訳ありませんでした。(これが言いたかった)