てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] ansible-doc コマンドで Collection のモジュールのドキュメントを表示する

■ はじめに

Ansible 2.9 から Ansible Collection という新しいコンテンツ配布形式が始まりました。(Ansible 2.8では実験的サポート)

ロールのほかにも、モジュールやプラグインを含めて配布できるのが特徴です。

Collection でインストールするものは、Ansible 本家リポジトリ ansible/ansible の管理下ではないので、公式ドキュメントには掲載されません。

そのため、ansible-doc コマンドを利用して表示することになりますが、モジュール名の指定方法が標準モジュールとは若干異なり、名前空間を含めて指定します。

この記事では簡単な例をもとに説明します。

  • 検証環境
    • Ansible 2.9.1


■ 基本書式

標準モジュールでは、 ansible-doc モジュール名 でモジュールのドキュメントを表示できます。Collection モジュールでは、モジュール名の代わりに FQCN でモジュール名指定します。

ansible-doc FQCN

FQCN というのは、Fully Qualified Collection Name の略です。Collection の名前空間、Collection名、モジュール名をドットでつなげたものです。(本当にモジュール名まで含めるのかちょっと自信なし)

たとえば、fragmentedpacket.netbox_modules という Collection 内の netbox_manufacturer モジュールのドキュメントを表示する場合は、以下のコマンドを実行します。

ansible-doc fragmentedpacket.netbox_modules.netbox_manufacturer


■ おためし

例として、fragmentedpacket.netbox_modules という Collection 内の netbox_manufacturer モジュールのドキュメントを表示してみます。

ここでは、対象の Collection のインストールから始めます。

$ ansible-galaxy collection install fragmentedpacket.netbox_modules
Process install dependency map
Starting collection install process
Installing 'fragmentedpacket.netbox_modules:0.1.0' to '/home/ansible/.ansible/collections/ansible_collections/fragmentedpacket/netbox_modules'

netbox_manufacturer モジュールのドキュメントを表示します。

$ ansible-doc fragmentedpacket.netbox_modules.netbox_manufacturer
> NETBOX_MANUFACTURER    (/home/asible/.ansible/collections/ansible_collections/fragmentedpacket/netbox_modules/plugins/modules/netbox_manufacturer.py)

        Creates or removes manufacturers from Netbox

  * This module is maintained by The Ansible Community
OPTIONS (= is mandatory):

- data
        Defines the manufacturer configuration
        [Default: (null)]
        suboptions:
          name:
            description:
            - The name of the manufacturer
            required: true
        

= netbox_token
        The token created within Netbox to authorize API access


= netbox_url
        URL of the Netbox instance resolvable by Ansible control host


- state
        Use `present' or `absent' for adding or removing.
        (Choices: absent, present)[Default: present]

- validate_certs
        If `no', SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates.
        [Default: yes]
        type: bool


NOTES:
      * Tags should be defined as a YAML list
      * This should be ran with connection `local' and hosts `localhost'


REQUIREMENTS:  pynetbox

AUTHOR: Mikhail Yohman (@FragmentedPacket)
        METADATA:
          status:
          - preview
          supported_by: community
        

EXAMPLES:

- name: "Test Netbox modules"
  connection: local
  hosts: localhost
  gather_facts: False

  tasks:
    - name: Create manufacturer within Netbox with only required information
      netbox_manufacturer:
        netbox_url: http://netbox.local
        netbox_token: thisIsMyToken
        data:
          name: Test Manufacturer
        state: present

    - name: Delete manufacturer within netbox
      netbox_manufacturer:
        netbox_url: http://netbox.local
        netbox_token: thisIsMyToken
        data:
          name: Test Manufacturer
        state: absent


RETURN VALUES:

manufacturer:
  description: Serialized object as created or already existent within Netbox
  returned: success (when I(state=present))
  type: dict
msg:
  description: Message indicating failure or info about what has been achieved
  returned: always
  type: str

無事に表示できました。

netbox_tokennetbox_url が必須オプションで、その他にも datastate などのオプションがあることが確認できます。その他、EXAMPLES も確認できます。

なお、Collection のインストール先や読み込み先は COLLECTIONS_PATHS という設定に基づきます。デフォルトは ~/.ansible/collections/usr/share/ansible/collections です。


■ まとめ

Collection モジュールのドキュメントは ansible-doc FQCN で表示できることをご紹介しました。

これから Collection を含めたコンテンツ配信が活性化されると、Collection を利用し、ドキュメントを表示する機会も増えるかもしれませんので、表示方法を覚えておくと良さそうです。

[Ansible] check モードの実行ログに [CHECK MODE] という印をつける方法

■ はじめに

Ansible には、Playbook 実行時に予めどのような変更がせれるかを、実際に変更を加えることなくチェックできる、check モード (Dry-run)という機能があります。

これまでは、通常実行時も check モード実行時も、実行ログ上は特に変わりはありませんでした。

Ansible 2.9 から、デフォルトのコールバックプラグインに、check モードで実行されたタスクにのログに印を付けられるようになりました。

この記事では、簡単な例をもとにして説明します。


■ 設定

ansible.cfg環境変数の、2通りの設定方法があります。

ansible.cfg

ansible.cfg で設定する場合は、[defaults] セクションに以下のように指定します。

[defaults]
check_mode_markers = yes

環境変数

ansible.cfg で設定する場合は、以下のように指定します。

 export ANSIBLE_CHECK_MODE_MARKERS=yes


■ 実行例1: --check オプション付加時

ansible-playbook コマンドに --check オプションを付けて Playbook 全体をチェックモードで実行する場合です。

  • Playbook
- hosts: all
  gather_facts: no

  tasks:
    - name: debug test 1
      debug:
        msg: debug test 1
    - name: debug test 2
      debug:
        msg: debug test 2
  • 実行
$ ansible-playbook -i localhost, check_test.yml --check

DRY RUN *********************************************************************************

PLAY [all] [CHECK MODE] *****************************************************************

TASK [debug test 1] [CHECK MODE] ********************************************************
ok: [localhost] => {
    "msg": "debug test 1"
}

TASK [debug test 2] [CHECK MODE] ********************************************************
ok: [localhost] => {
    "msg": "debug test 2"
}

PLAY RECAP ******************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

冒頭に DRY RUN、各 Play、各タスクに CHECK MODE と表示されました。


■ 実行例2: タスクに check_mode: yes 付加時

タスクに check_mode: yes を付けて、特定のタスクだけチェックモードで実行する場合です。

  • Playbook
- hosts: all
  gather_facts: no

  tasks:
    - name: debug test 1
      debug:
        msg: debug test 1
    - name: debug test 2
      debug:
        msg: debug test 2
      check_mode: yes         # ポイント
  • 実行
$ ansible-playbook -i localhost, check_test.yml

PLAY [all] ******************************************************************************

TASK [debug test 1] *********************************************************************
ok: [localhost] => {
    "msg": "debug test 1"
}

TASK [debug test 2] [CHECK MODE] ********************************************************
ok: [localhost] => {
    "msg": "debug test 2"
}

PLAY RECAP ******************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

check_mode: yes を指定したタスクのみ CHECK MODE と表示されました。


■ 参考: check_mode_markers を有効にしなかった場合

通常はこのようなログになります。

$ ansible-playbook -i localhost, check_test.yml --check

PLAY [all] ******************************************************************************

TASK [debug test 1] *********************************************************************
ok: [localhost] => {
    "msg": "debug test 1"
}

TASK [debug test 2] *********************************************************************
ok: [localhost] => {
    "msg": "debug test 2"
}

PLAY RECAP ******************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


■ まとめ

デフォルトのコールバックプラグインに、check モードで実行されたタスクにのログに印を付ける設定のご紹介をしました。

Playbook 実行ログ上、check モードで実行したことを区別したいときには便利だと思います。

[Ansible] Ansible 2.9 からデフォルトでネットワークの fact も収集される

■ はじめに

fact といえば、サーバー系モジュールの場合は、gather_facts: yes(デフォルト)で収集されましたが、ネットワークモジュールの場合は事情が異なりました

具体的には、gather_facts: yes では、Ansible コントロールノード自身の fact が収集されていました。ネットワーク機器側の fact を収集するには ios_facts モジュールや、junos_facts モジュールなど、プラットフォーム個別の *_facts モジュールを使う必要がありました。

Ansible 2.9 では、gather_facts: yes(デフォルト)でネットワーク機器側の fact が収集されるようになりました。逆にAnsible コントロールノード自身の fact は収集されなくなりました。

この記事では、IOS を対象にして、簡単な例をもとに説明します。

※ ログのみやすさのため、CALLBACK PLUGIN を YAML に変更しています。

  • 環境
    • Ansible 2.9.0


■ デフォルトではコンフィグも収集される

デフォルトで、gather_facts: yes なので、そのまま試します。debug モジュールで、fact の名前空間である、ansible_facts をまるごと出力します。

  • Playbook
- hosts: ios

  tasks:
    - name: debug facts
      debug:
        var: ansible_facts
  • 実行

この場合、ios_facts モジュールでいうと gather_subset: all で fact を収集します。 つまり、下記のように コンフィグも収集 されます。

$ ansible-playbook -i ../inventory.ini fact_test.yml 

PLAY [ios] ******************************************************************************

TASK [Gathering Facts] ******************************************************************
[WARNING]: Ignoring timeout(10) for ios_facts

ok: [ios1]

TASK [debug facts] **********************************************************************
ok: [ios1] => 
  ansible_facts:
    _facts_gathered: true
    discovered_interpreter_python: /usr/bin/python
    net_all_ipv4_addresses:
    - 172.16.100.1
    - 10.10.20.48
    net_all_ipv6_addresses: []
    net_api: cliconf
    net_config: |-
      !
      ! Last configuration change at 12:35:22 UTC Sun Nov 24 2019 by developer
      !
      version 16.11
      service timestamps debug datetime msec
      service timestamps log datetime msec
      service call-home
      platform qfp utilization monitor load 80
      no platform punt-keepalive disable-kernel-core

      ...(略)...

      end
    net_filesystems:
    - 'bootflash:'
    net_filesystems_info:
      'bootflash:':
        spacefree_kb: 5793276.0
        spacetotal_kb: 7712692.0
    net_gather_network_resources: []
    net_gather_subset:
    - hardware
    - default
    - interfaces
    - config
    net_hostname: csr1000v-1
    net_image: bootflash:packages.conf
    net_interfaces:
      GigabitEthernet1:
        bandwidth: 1000000
        description: MANAGEMENT INTERFACE - DON'T TOUCH ME
        duplex: Full
        ipv4:
        - address: 10.10.20.48
          subnet: '24'
        lineprotocol: up
        macaddress: 0050.56bb.e99c
        mediatype: Virtual
        mtu: 1500
        operstatus: up
        type: CSR vNIC

      ...(略)...

    net_iostype: IOS-XE
    net_memfree_mb: 2074004.25
    net_memtotal_mb: 2378439.3125
    net_model: CSR1000V
    net_neighbors: {}
    net_python_version: 2.7.10
    net_serialnum: XXXXXXXXXXX
    net_system: ios
    net_version: 16.11.01a
    network_resources: {}

PLAY RECAP ******************************************************************************
ios1    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


gather_facts: yes のまま収集対象を絞るには

先ほどの例は gather_subset: all 相当だったので、不要な情報もあったかもしれません。そもそも fact 収集が不要であれば gather_facts: no にしますが、gather_facts: yes のまま収集対象を制限したいケースもあるかと思います。

module_defaultsを利用して、ios_facts モジュールの gather_subset オプションを指定することで必要な情報に制限できます。

・・・と思って試したのですが、うまく制限できませんでした。

公式ブログにも掲載されている方法ですが、微妙に試し方が誤っているのかもしれません。

うまく制限できなかったものの、ひとまずログとして残しておきます。

  • Playbook
- hosts: ios
  module_defaults:   # 暗黙的に実行される ios_facts のオプションを指定しておく
    ios_facts:
      gather_subset: "!config"   # コンフィグを ! で除外
 
  tasks:
    - name: debug facts
      debug:
        var: ansible_facts
  • 実行
$ ansible-playbook -i ../inventory.ini fact_test.yml 

PLAY [ios] ***********************************************************************

TASK [Gathering Facts] *******************************************************************
[WARNING]: Ignoring timeout(10) for ios_facts

ok: [ios1]

TASK [debug facts] **********************************************************************
ok: [ios1] => 
  ansible_facts:
    _facts_gathered: true
    discovered_interpreter_python: /usr/bin/python
    net_all_ipv4_addresses:
    - 172.16.100.1
    - 10.10.20.48
    net_all_ipv6_addresses: []
    net_api: cliconf
    net_config: |-
      !
      ! Last configuration change at 13:11:27 UTC Sun Nov 24 2019 by developer
      !
      version 16.11
      service timestamps debug datetime msec
      service timestamps log datetime msec
      service call-home
      platform qfp utilization monitor load 80
      no platform punt-keepalive disable-kernel-core

      ...(略)...

      end
    net_filesystems:
    - 'bootflash:'
    net_filesystems_info:
      'bootflash:':
        spacefree_kb: 5793252.0
        spacetotal_kb: 7712692.0
    net_gather_network_resources: []
    net_gather_subset:
    - hardware
    - default
    - interfaces
    - config
    net_hostname: csr1000v-1
    net_image: bootflash:packages.conf
    net_interfaces:
      GigabitEthernet1:
        bandwidth: 1000000
        description: MANAGEMENT INTERFACE - DON'T TOUCH ME
        duplex: Full
        ipv4:
        - address: 10.10.20.48
          subnet: '24'
        lineprotocol: up
        macaddress: 0050.56bb.e99c
        mediatype: Virtual
        mtu: 1500
        operstatus: up
        type: CSR vNIC

        ...(略)...

    net_iostype: IOS-XE
    net_memfree_mb: 2074004.25
    net_memtotal_mb: 2378439.3125
    net_model: CSR1000V
    net_neighbors: {}
    net_python_version: 2.7.10
    net_serialnum: XXXXXXXXXXX
    net_system: ios
    net_version: 16.11.01a
    network_resources: {}

PLAY RECAP ******************************************************************************
ios1       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

ということで、意図せずコンフィグも収集されてしまいました。module_defaults で指定した内容が反映されていないように見えます。なにか分かったら更新します。

[2019/11/24追記] ansible.cfgDEFAULT_GATHER_SUBSETに設定したら効きましが、影響範囲が広すぎますね。。


■ まとめ

  • Ansible 2.8 までは ios_facts モジュールなど、プラットフォーム個別の fact 収集モジュールで fact を収集していた
  • Ansible 2.9 でネットワークモジュールも gather_facts: yes (デフォルト) で fact が収集されるようになった
  • *_facts モジュールでいう、gather_subset: all 相当なので、コンフィグも収集される
  • 対象を制限るには module_defaults を利用すれば、よい・・はず・・・だが、今回の試し方ではうまくいかなかった

参考

[Ansible] role の vars ディレクトリ配下に複数の変数ファイルを配置してまとめて読み込む方法

■ はじめに

role を構成する際、vars ディレクトリ配下に main.yml を作成して、変数を読み込ませます。

普通に考えると、vars/main.yml という1つのファイルしか読み込めなさそうですが、複数のファイルに分割しておきたいこともあるかもしれません。

公式ドキュメントに載っていたわけではありませんが、vars ディレクトリ配下にさらに main ディレクト を用意して、その中に変数ファイルを置いておくと、まとめて読み込んでくれました。

「試したらできた」というレベルではありますが、この記事では簡単な例を元にして説明します。

  • 環境
    • Ansible 2.9.0

※ 将来仕様が変更されるかもしれませんのでご了承ください。あまりコードを追えていないのですが、おそらく vars 読み込み時に、find_vars_files メソッドallow_dir=True で呼ばれるからではないかと思います。


■ ファイル・ディレクトリ構成

以下のようなファイル、ディレクトリ構成とします。

|--roles
|  |--testrole
|  |  |--tasks
|  |  |  |--main.yml    # ロールのタスク
|  |  |--vars
|  |  |  |--main        # 変数ファイルをまとめるディレクトリ
|  |  |  |  |--var1.yml # 読み込みたい変数ファイル1
|  |  |  |  |--var2.yml # 読み込みたい変数ファイル2
|--site.yml    # ロールを呼び出す Playbook


■ 各ファイルの作成

以下のようなファイルを作成します。

Playbook: site.yml

testrole ロールを呼び出すだけの簡単な Playbook です。

---
- hosts: localhost
  gather_facts: no

  roles:
    - role: testrole

ロールのタスク: roles/testroke/tasks/main.yml

変数 var1var2 の値を表示するだけのタスクです。それぞれ別のファイルで定義している変数です。

---
- name: debug var1
  debug:
    var: var1       # roles/testroke/vars/main/var1.yml 内で定義
- name: debug var2
  debug:
    var: var2       # roles/testroke/vars/main/var2.yml 内で定義

変数ファイル: roles/testroke/vars/main/var1.yml

変数 var1 を定義します。

---
var1: var1!!

変数ファイル: roles/testroke/vars/main/var2.yml

変数 var2 を定義します。

---
var2: var2!!

ファイルの作成はここまでです。


■ 実行

Playbook を実行します。

$ ansible-playbook -i localhost, site.yml 

PLAY [localhost] ************************************************************************

TASK [testrole : debug var1] ************************************************************
ok: [localhost] => 
  var1: var1!!

TASK [testrole : debug var2] ************************************************************
ok: [localhost] => 
  var2: var2!!

PLAY RECAP ******************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

roles/testroke/vars/main/ ディレクトリの var1.ymlvar2.yaml でそれぞれ定義した変数 var1var2 の値が表示されまた。

なお、main.ymlmain ディレクトリが存在する場合は、 main.yml 飲み読み込んで、main ディレクトリ配下のファイルは無視されました。


■ まとめ

role の vars ディレクトリ配下にさらに main ディレクト を用意して、その中に変数ファイルを置いておくと、まとめて読み込まれることを確認しました。

前述のように、公式ドキュメントに載っている方法ではありませんが、試したら、という扱いです。

試したが限り、vars/main/ ディレクトリだけでなく、defaults/main/ ディレクトリでも同様に、複数の変数ファイルをまとめて読み込まれました。

なちみに、なぜこの方法を思いついたかというと、 group_vars/グループ名.yml だけでなく、group_vars/グループ名/ファイル1.ymlgroup_vars/グループ名/ファイル2.yml のように、グループ名をディレクトリ化して複数ファイルを読み込む方法をたまたま知っていたためです。

[Ansible] Playbook の書き出しを - hosts か hosts か迷ったら

■ はじめに

このツイートの詳細です。

サンプルの Playbook をコピーして書いているときにはあまり気にならないものの、いざ1から Playbook を書くときに、Plybook の冒頭でいきなり悩んでしまう時期がありました。

- hosts だったか、 hosts だったか、という点です。

(1) こうだったか・・

- hosts: web
  gather_facth_no: no
  #...(略)...

(2) それともこうだっか・・・

hosts: web
gather_facth_no: no
#...(略)...

答えは (1) のハイフンありの - hosts です。

この記事ではその理由を解説します。

  • 環境
    • Ansible 2.9.0


■ Playbook は Play をまとめるもの

Playbook は、1つまたは複数の Play をリスト形式でまとめるものです。

https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#playbook-language-example

Each playbook is composed of one or more ‘plays’ in a list.

おそらく、1つの Playbook の中に 複数の Play を書く機会があまりなくて、この点はあまり意識されないのかなと思います。

Play というのはざっくりいうと、どのターゲットに対して、何をするかという処理の単位です。以下のようなイメージです。

f:id:akira6592:20191124111940p:plain
複数 Play をまとめる Playbook

ちなみに、2つの Play を含む Playbook を実行すると、ログに 2箇所 PLAY が出力されます、

- hosts: localhost      # Play のはじまり
  gather_facts: no

  tasks:
    - name: task1-1
      debug:
        msg: task1-1
    - name: task1-2
      debug:
        msg: task1-2

- hosts: localhost      # Play のはじまり
  gather_facts: no

  tasks:
    - name: task2-1
      debug:
        msg: task2-1
    - name: task2-2
      debug:
        msg: task2-2
  • 実行ログ
$ ansible-playbook -i localhost, play.yml 

PLAY [localhost] ******************************************************************************

TASK [task1-1] **************************************************************************
ok: [localhost] => 
  msg: task1-1

TASK [task1-2] **************************************************************************
ok: [localhost] => 
  msg: task1-2

PLAY [localhost] ************************************************************************

TASK [task2-1] **************************************************************************
ok: [localhost] => 
  msg: task2-1

TASK [task2-2] **************************************************************************
ok: [localhost] => 
  msg: task2-2

PLAY RECAP ******************************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


■ 参考: PLAY にも名前を付けられる

Play の書き出しを - hosts で始める例を見かけることが多いですが、PLAY に指定できる属性として、name もあります。

- name: PLAY1         # Play のはじまり
  hosts: localhost
  gather_facts: no

  tasks:
# ...(略)...

この場合は、以下のように実行ログに Play につけた name が表示されます。

$ ansible-playbook -i localhost, play.yml 

PLAY [PLAY1] ****************************************************************************

TASK [task1-1] **************************************************************************
ok: [localhost] => 
  msg: task1-1

もっといってしまうと、こんなのも文法上はアリです。

- tasks:
    - name: task1-1
      debug:
        msg: task1-1
  name: PLAY1 
  hosts: localhost
  gather_facts: no

とにかく Play をリストで定義することが必須です。


■ まとめ

  • Playbook は、1つまたは複数の Play をリスト形式でまとめるもの
  • そのため - hosts のように - をつけて Play をリスト化するのが正しい

[Ansible] Ansible Tower / AWX の設定済みオブジェクトをエクスポート、編集、インポートする(ワークフロー編)

はじめに

Ansible Tower / AWX で画面から設定を続けていくと、自動化したくなるのではないでしょうか。

これから新たに設定する分は、Ansible で Ansible Tower / AWX の各オブジェクトを管理できる tower_* モジュールを利用するのも手かと思います。

では、設定済みのオブジェクトを再利用して、新たに別オブジェクトを作成するにはどのようにすれば良いでしょうか。

tower_* モジュールの中には、設定済みオブジェクトの情報をエクスポートできる tower_receive モジュールと、そのフォーマットの情報を元にインポートできる、tower_send モジュールがあります(今日知りました)。Ansible 2.8 で追加されたモジュールです。

扱えるオブジェクトは、組織、プロジェクト、インベントリ、など様々ですが、この記事では例としてワークフローを対象とした方法をご紹介します。

  • 環境
    • Ansible 2.9.0
    • AWX 7.0.0


■ 環境の準備

以前の記事にも書きましたが、Ansible 側に ansible-tower-cli をインストールして、 ansible_python_interpreter を調整します。

https://tekunabe.hatenablog.jp/entry/2019/11/124/ansible_towerk_workflow_schema


■ 既存ワークフローのエクスポート

tower_receive モジュールを、copy モジュールを利用して、特定のワークフローの情報をエクスポートします。

エクスポート用 Playbook 作成

以下の Playbook を作成します。

  • wf_exp.yml
- hosts: tower
  gather_facts: no
  connection: local

  vars:
    # ansible-tower-cli をインストールした環境
    ansible_python_interpreter: /Users/akira/envs/venvs/a2900/bin/python
    
  tasks:
    - name: export workflow
      tower_receive:
        tower_host: "{{ ansible_host }}"
        tower_username: "{{ tower_username }}"  # 別途定義した認証情報
        tower_password: "{{ tower_password }}"  # 別途定義した認証情報
        validate_certs: no
        workflow:
          - testwf1
      register: result

    - name: save workflow
      copy:
        content: "{{ result.assets | to_nice_yaml }}"
        dest: wf_data.yml

tower_receive モジュールでは、様々なオブジェクトをエクスポートできます。今回は testwf1 という名前のワークフローのみを対象とするため、workflow オプションを利用して、testwf1 を指定しています。 もし、全ワークフローを対象とする場合は、workflow: all のように指定します。実行結果は変数 result に保存します。

次の copy モジュールのタスクで、result.assets の内容を、dest で指定したファイルに書き出します。

エクスポート用 Playbook 実行

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini wf_exp.yml 

PLAY [tower] ****************************************************************************

TASK [export workflow] ******************************************************************
ok: [awx1]

TASK [save workflow] ********************************************************************
changed: [awx1]

PLAY RECAP ******************************************************************************
awx1                       : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

wf_data.yml にエクスポートされたワークフロー testwf1 の情報が保存されます。

  • wf_data.yml
-   asset_relation:
        labels: []
        roles:
        -   name: Admin
            team: []
            user: []
        -   name: Execute
            team: []
            user: []
        -   name: Read
            team: []
            user: []
        -   name: Approve
            team: []
            user: []
        schedules: []
        survey_spec: {}
        workflow_nodes:
        -   always_nodes:
            - node3
            failure_nodes: []
            name: node0
            success_nodes: []
            unified_job_name: jt2
            unified_job_type: job
        -   always_nodes: []
            failure_nodes:
            - node2
            name: node1
            success_nodes:
            - node0
            unified_job_name: jt1
            unified_job_type: job
        -   always_nodes:
            - node3
            failure_nodes: []
            name: node2
            success_nodes: []
            unified_job_name: jt9999
            unified_job_type: job
        -   always_nodes: []
            failure_nodes: []
            name: node3
            success_nodes: []
            unified_job_name: jt3
            unified_job_type: job
    asset_type: workflow
    name: testwf1

なお、このワークフローは、GUI 上で確認するとこんな感じです。

f:id:akira6592:20191123205145p:plain
testfw1 のワークフロー定義


■ エクスポートデータの編集

保存したワークフロー testwf1 のエクスポートデータを元に編集します。

ここでは、以下の図のように、ワークフロー内の jt2jt5 に差し替えて、新たなワークフロー testwf2 となるように編集します。

f:id:akira6592:20191123205733p:plain
要件

  • wf_data.yml
-   asset_relation:
        labels: []
        roles:
        -   name: Admin
            team: []
            user: []
        -   name: Execute
            team: []
            user: []
        -   name: Read
            team: []
            user: []
        -   name: Approve
            team: []
            user: []
        schedules: []
        survey_spec: {}
        workflow_nodes:
        -   always_nodes:
            - node3
            failure_nodes: []
            name: node0
            success_nodes: []
            unified_job_name: jt5   # ここ編集
            unified_job_type: job
        -   always_nodes: []
            failure_nodes:
            - node2
            name: node1
            success_nodes:
            - node0
            unified_job_name: jt1
            unified_job_type: job
        -   always_nodes:
            - node3
            failure_nodes: []
            name: node2
            success_nodes: []
            unified_job_name: jt9999
            unified_job_type: job
        -   always_nodes: []
            failure_nodes: []
            name: node3
            success_nodes: []
            unified_job_name: jt3
            unified_job_type: job
    asset_type: workflow
    name: testwf2     # ここ編集

もちろん、エクポートしなくても1からこのフォーマットのファイルを作成してインポートもできます。ここでは、どのようなフォーマットかを確認する意味も込めて一旦エクスポートしています。


■ インポート

tower_send モジュールを利用して、先ほど編集したワークフローのデータを Ansible Tower / AWX にインポートします。

インポート用 Playbook 作成

以下の Playbook を作成します。

  • wf_imp.yml
- hosts: tower
  gather_facts: no
  connection: local

  # ansible-tower-cli をインストールした環境
  vars:
    ansible_python_interpreter: /Users/akira/envs/venvs/a2900/bin/python

  tasks:
    - name: import workflow
      tower_send:
        tower_host: "{{ ansible_host }}"
        tower_username: "{{ tower_username }}"  # 別途定義した認証情報
        tower_password: "{{ tower_password }}"  # 別途定義した認証情報
        validate_certs: no
        files:
          - wf_data.yml

インポート用 Playbook 実行

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini wf_imp.yml 

PLAY [tower] ****************************************************************************

TASK [import workflow] ******************************************************************
changed: [awx1]

PLAY RECAP *******************************************************************************
awx1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

確認

GUI で確認します。

f:id:akira6592:20191123205542p:plain
testwf2 ができた

testwf2 が作成されています。

続いて、ワークフローの定義を確認します。

f:id:akira6592:20191123205823p:plain
testwf2 のワークフロー定義

狙い通り jt5 に差し替わったワークフローになっています。

注意点: 既存オブジェクトの更新はできない

試した限り、既存のワークフローを上書きするようにインポートすることはできませんでした。おそらく、ワークフローの name で存在確認をして、存在すれば何もしないという動作になっているのだと思います。タスクの実行ステータスとしては changed ではなく、ok でした。

tower_workflow_template モジュールのほうを利用すれば、既存ワークフローの設定変更もできます。


■ まとめ

設定済みの ワークフローをエクスポートして、編集して再利用するする方法をご紹介しました。

エクスポートにはtower_receive モジュール、インポートにはtower_send モジュールを利用しました。

これらのモジュールはワークフロー以外にも組織、プロジェクト、インベントリなど様々なオブジェクトを扱えます。

オブジェクトの種類によっては、ちょっとした複製であれば GUI からでもできますが、いっぺんに大量に類似オブジェクトを作成する場合は、これらのモジュールは便利だと思いました。

[Ansible] ロール呼び出しに指定したタグはロールの各タスクに継承される(roles/import_role)

問題

突然ですが、問題です。

以下のようにタグのついた Playbook、role があるとします。

  • tag_playbook.yml
- hosts: localhost
  gather_facts: no

  roles:
    - role: testrole
      tags:
        - tag_parent
  • roles/testrole/tasks/main.yml
- name: task1
  debug:
    msg: "task1"
  tags:
    - tag_parent
    - tag_child1

- name: task2
  debug:
    msg: "task2"
  tags:
    - tag_child2

ansible-plabook コマンドで-t tag_parent を指定して Playbook を実行した場合

$ ansible-playbook -i localhost, tag_playbook.yml  -t tag_parent

どのタスクが実行されるでしょうか。

答えはこちら


























答え

答えは、task1task2 です。(本記事のタイトル通りの挙動です)

  • 実行ログ
$ ansible-playbook -i localhost, tag_playbook.yml  -t tag_parent

PLAY [localhost] ************************************************************************

TASK [testrole : task1] *****************************************************************
ok: [localhost] => {
    "msg": "task1"
}

TASK [testrole : task2] *****************************************************************
ok: [localhost] => {
    "msg": "task2"
}

PLAY RECAP ******************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


解説

tag_playbook.yml では、role を呼び出す指定に、タグ tag_parent を付けています。この場合、呼ばれたロールの 各タスク にもタグ tag_parent が付きます。継承されるようなイメージです。

そのため、呼ばれた role の タスク task2 自身には、タグ tag_parent は付いていなくても実行される結果となります。

https://docs.ansible.com/ansible/latest/user_guide/playbooks_tags.html#tag-inheritance

Adding tags: to a play, or to statically imported tasks and roles, adds those tags to all of the contained tasks. This is referred to as tag inheritance.

タスクへのタグの付き方は、ansible-playbook コマンドの --list-tasks オプションを利用することで確認できます。

$ ansible-playbook -i localhost, tag_playbook.yml --list-tasks

playbook: tag_playbook.yml

  play #1 (localhost): localhost        TAGS: []
    tasks:
      testrole : task1  TAGS: [tag_child1, tag_parent]
      testrole : task2  TAGS: [tag_child2, tag_parent]

上記結果により、role 呼び出し側のタグ tag_parent が継承されて task2 にもつくことが確認できます。

なお、task1 のみの実行を意図して、ansible-playbook コマンドに -t tag_parent を指定するのであれば、ロール呼び出し側 では tag_parent を指定しないのが正しいです。


まとめ

タグの挙動はうっかり忘れがちなものもあると思いますので、たまに公式ドキュメントを見返すのが良さそうです。

docs.ansible.com

※role や import* ではなく、include* のよう動的読み込みの場合は継承されないようです。

Tag inheritance is not applicable to dynamic inclusions such as include_role and include_tasks.

  • 検証環境
    • Ansible 2.9.0

参考

[2021/03/24 追記]

zaki-hmkc.hatenablog.com