てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] ansible-galaxy collection list で site-packages 配下の Collection も表示させるワンライナー

これは、Ansible Advent Calendar 2020 (Adventar版) の 22日目の記事です。

はじめに

pip install ansible で Ansible 2.10 をインストールすると、2.9 まで標準で含まれたモジュールに概ね相当する Collection がセットでインストールされます。

Python の環境下 (例: ~/envs/venv/lib/python3.X/site-packages/ansible_collections) に配置されます。

このバージョンの Collection がインストールされているか調べる場合は、ansible-galaxy collection list コマンドを利用しますが、デフォルトだと ~/.ansible/collections/ansible_collections/ 配下を検索しにいくため、site-packages 方面を検索してくれません。

-p ~/envs/venv/lib/python3.X/site-packages/ansible_collections のように検索先を直接指定してもよいのですが、Ansible 公式ドキュメントにもう少し汎用的な方法(だたし長い)があったのでご紹介します。

  • 環境
    • ansible 2.10.4
    • ansible-base 2.10.4

[2021/01/23 追記] 2.10.5 で ansible-galaxy collection list を普通に実行したところ、site-packages配下も検索してくれるようになっていました。 2.10.5 changelog から引用

Fix ansible-galaxy collection list to show collections in site-packages (https://github.com/ansible/ansible/issues/70147)


site-packages 配下の Collection も検索する方法

site-packages 方面を検索しない動作は、Porting Guide に Know Issue として掲載されてます。

docs.ansible.com

対処方法として、以下のようなコマンドが紹介されています。

COLLECTION_INSTALL=$(python -c 'import ansible, os.path ; print("%s/../ansible_collections" % os.path.dirname(ansible.__file__))') ansible-galaxy collection list -p "$COLLECTION_INSTALL"

試してみる

普通に ansible-galaxy collection list を実行した場合は、 ~/.ansible/collections/ansible_collections/ のみを検索します。

(a210) [root@centos7 work]# ansible-galaxy collection list
# /root/.ansible/collections/ansible_collections
Collection    Version
------------- -------
ansible.utils 1.0.1  

今度は、 Porting Guide にあった site-packages 配下の Collection も検索するコマンドを実行します。すると ~/.ansible/collections/ansible_collections/ に加えて、site-packages も検索します。

(a210) [root@centos7 work]# COLLECTION_INSTALL=$(python -c 'import ansible, os.path ; print("%s/../ansible_collections" % os.path.dirname(ansible.__file__))') ansible-galaxy collection list -p "$COLLECTION_INSTALL"

# /root/.ansible/collections/ansible_collections
Collection    Version
------------- -------
ansible.utils 1.0.1  

# /root/envs/a210/lib/python3.6/site-packages/ansible_collections
Collection                Version
------------------------- -------
amazon.aws                1.2.1  
ansible.netcommon         1.4.1  
ansible.posix             1.1.1  
ansible.windows           1.2.0  
arista.eos                1.2.0  
awx.awx                   14.1.0 
azure.azcollection        1.2.0  
check_point.mgmt          1.0.6  
chocolatey.chocolatey     1.0.2  
cisco.aci                 1.1.1  
cisco.asa                 1.0.4  
cisco.intersight          1.0.8  
cisco.ios                 1.2.1  
cisco.iosxr               1.2.0  
cisco.meraki              2.1.2  
cisco.mso                 1.0.1  
cisco.nxos                1.3.1  
cisco.ucs                 1.6.0  
cloudscale_ch.cloud       1.3.0  
community.aws             1.2.1  
community.azure           1.0.0  
community.crypto          1.3.0  
community.digitalocean    1.0.0  
community.docker          1.0.0  
community.general         1.3.0  
community.grafana         1.1.0  
community.hrobot          1.0.0  
community.kubernetes      1.1.1  
community.libvirt         1.0.0  
community.mongodb         1.1.1  
community.mysql           1.1.1  
community.network         1.3.0  
community.okd             1.0.1  
community.postgresql      1.0.0  
community.proxysql        1.0.0  
community.rabbitmq        1.0.1  
community.routeros        1.0.0  
community.skydive         1.0.0  
community.vmware          1.4.0  
community.windows         1.1.0  
community.zabbix          1.1.0  
containers.podman         1.3.2  
cyberark.conjur           1.0.7  
cyberark.pas              1.0.5  
dellemc.os10              1.0.2  
dellemc.os6               1.0.4  
dellemc.os9               1.0.3  
f5networks.f5_modules     1.6.0  
fortinet.fortimanager     1.0.5  
fortinet.fortios          1.0.15 
frr.frr                   1.0.3  
gluster.gluster           1.0.1  
google.cloud              1.0.1  
hetzner.hcloud            1.2.0  
ibm.qradar                1.0.3  
infinidat.infinibox       1.2.3  
junipernetworks.junos     1.2.1  
mellanox.onyx             1.0.0  
netapp.aws                20.9.0 
netapp.elementsw          20.11.0
netapp.ontap              20.11.0
netapp_eseries.santricity 1.1.0  
netbox.netbox             1.1.0  
ngine_io.cloudstack       1.1.0  
ngine_io.exoscale         1.0.0  
ngine_io.vultr            1.0.0  
openstack.cloud           1.2.0  
openvswitch.openvswitch   1.1.0  
ovirt.ovirt               1.2.3  
purestorage.flasharray    1.5.0  
purestorage.flashblade    1.4.0  
servicenow.servicenow     1.0.3  
splunk.es                 1.0.2  
theforeman.foreman        1.4.0  
vyos.vyos                 1.1.0  
wti.remote                1.0.1  
(a210) [root@centos7 work]# 

おわりに

機能として site-packeges を検索するようにする動きもあるようです。

github.com

[2021/01/23 追記] 2.10.5 で ansible-galaxy collection list を普通に実行したところ、site-packages配下も検索してくれるようになっていました。

[Ansible] Playbook 実行結果ログを JSON で出力して jq で抽出する

これは Ansible Advent Calendar 2020 (Qiita版) の 21日目の記事です。

■ はじめに

Ansible には callback plugin という仕組みがあり、結果出力の形式を変更できます。

JSON Callback Plugin もあり、Playbook の実行結果を JSON で得て、jq などと組み合わせて必要な箇所だけ切り取って表示できます。

この記事では、簡単なサンプルでご紹介します。

  • 動作確認環境
    • ansible 2.9.14

前提とする Playbook

以下の Playbook で試すこととします。Cisco IOS のネットワーク機器に show ip route コマンドを実行するだけです。

---
- hosts: ios1
  gather_facts: false

  tasks:
    - name: show ip route
      ios_command:
        commands:
          - show ip route

まずは普通に

比較用に普通実行、つまりデフォルトの callback plugin で実行した場合は以下のようになります。

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

PLAY [ios1] ***********************************************************************************************************************

TASK [show ip route] **********************************************************************************************************************
ok: [ios1]

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

デバッグ表示用のタスクはないので、ただ ok と表示されるだけです。

json callback plugin の場合

今度は ANSIBLE_STDOUT_CALLBACK=json を指定して、実行します。stdoutstdout_lines に実行結果が含まれていることが分かります。

全出力

まず、jq で絞らず、そのまま出力する方法です。

$ ANSIBLE_STDOUT_CALLBACK=json ansible-playbook -i ../inventory.ini iproute.yml
{
    "custom_stats": {},
    "global_custom_stats": {},
    "plays": [
        {
            "play": {
                "duration": {
                    "end": "2020-12-20T12:16:22.379916Z",
                    "start": "2020-12-20T12:16:04.563589Z"
                },
                "id": "acde4800-1122-37cb-6431-00000000002c",
                "name": "ios1"
            },
            "tasks": [
                {
                    "hosts": {
                        "ios1": {
                            "_ansible_no_log": false,
                            "action": "ios_command",
                            "changed": false,
                            "invocation": {
                                "module_args": {
                                    "auth_pass": null,
                                    "authorize": null,
                                    "commands": [
                                        "show ip route"
                                    ],
                                    "host": null,
                                    "interval": 1,
                                    "match": "all",
                                    "password": null,
                                    "port": null,
                                    "provider": null,
                                    "retries": 10,
                                    "ssh_keyfile": null,
                                    "timeout": null,
                                    "username": null,
                                    "wait_for": null
                                }
                            },
                            "stdout": [
                                "Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP\n       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area \n       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2\n       E1 - OSPF external type 1, E2 - OSPF external type 2\n       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2\n       ia - IS-IS inter area, * - candidate default, U - per-user static route\n       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP\n       a - application route\n       + - replicated route, % - next hop override, p - overrides from PfR\n\nGateway of last resort is 10.10.20.254 to network 0.0.0.0\n\nS*    0.0.0.0/0 [1/0] via 10.10.20.254, GigabitEthernet1\n      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks\nC        10.10.20.0/24 is directly connected, GigabitEthernet1\nL        10.10.20.48/32 is directly connected, GigabitEthernet1"
                            ],
                            "stdout_lines": [
                                [
                                    "Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP",
                                    "       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area ",
                                    "       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2",
                                    "       E1 - OSPF external type 1, E2 - OSPF external type 2",
                                    "       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2",
                                    "       ia - IS-IS inter area, * - candidate default, U - per-user static route",
                                    "       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP",
                                    "       a - application route",
                                    "       + - replicated route, % - next hop override, p - overrides from PfR",
                                    "",
                                    "Gateway of last resort is 10.10.20.254 to network 0.0.0.0",
                                    "",
                                    "S*    0.0.0.0/0 [1/0] via 10.10.20.254, GigabitEthernet1",
                                    "      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks",
                                    "C        10.10.20.0/24 is directly connected, GigabitEthernet1",
                                    "L        10.10.20.48/32 is directly connected, GigabitEthernet1"
                                ]
                            ]
                        }
                    },
                    "task": {
                        "duration": {
                            "end": "2020-12-20T12:16:22.379916Z",
                            "start": "2020-12-20T12:16:04.580088Z"
                        },
                        "id": "acde4800-1122-37cb-6431-00000000002e",
                        "name": "show ip route"
                    }
                }
            ]
        }
    ],
    "stats": {
        "ios1": {
            "changed": 0,
            "failures": 0,
            "ignored": 0,
            "ok": 1,
            "rescued": 0,
            "skipped": 0,
            "unreachable": 0
        }
    }
}

jq で絞る

今度は jq と組み合わせて結果の一部のみを表示する方法です。 ここでは、1つめのタスクのホスト ios1 の実行結果の stdout_lines[0] をターゲットにします。

 % ANSIBLE_STDOUT_CALLBACK=json ansible-playbook -i ../inventory.ini iproute.yml | jq ".plays[0].tasks[0].hosts.ios1.stdout_lines[0]" 
[
  "Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP",
  "       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area ",
  "       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2",
  "       E1 - OSPF external type 1, E2 - OSPF external type 2",
  "       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2",
  "       ia - IS-IS inter area, * - candidate default, U - per-user static route",
  "       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP",
  "       a - application route",
  "       + - replicated route, % - next hop override, p - overrides from PfR",
  "",
  "Gateway of last resort is 10.10.20.254 to network 0.0.0.0",
  "",
  "S*    0.0.0.0/0 [1/0] via 10.10.20.254, GigabitEthernet1",
  "      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks",
  "C        10.10.20.0/24 is directly connected, GigabitEthernet1",
  "L        10.10.20.48/32 is directly connected, GigabitEthernet1"
]

抽出されました。

callback plugin の指定方法

今回は、環境変数で callback plugin を指定しましたが、ansible.cfg でも指定できます。

詳細は、公式ドキュメントをご参照ください。

docs.ansible.com

おわりに

Playbook の実行結果を抽出したり、プログラム的に処理させたいときは json callback plugin は便利だと思います。

参考

rheb.hatenablog.com

tekunabe.hatenablog.jp

[Ansible] コントロールノードから ICMP ping を実行する(ただしcommandモジュール)

これは、Ansible Advent Calendar 2020 (Adventar版) の 21日目の記事です。

はじめに

Ansible には、ping モジュールという接続確認モジュールがあります。

コントロールノードからターゲットノードへの SSH 接続 + Python 実行までを確認します(ICMPではありません)。

参考: 【Ansible】ping 系モジュールまとめ(ping/win_ping/ios_ping/nxos_ping/net_ping) - てくなべ (tekunabe)

一方で、コントロールノードからターゲットノードへ ICMP による ping を実行するモジュールはないようです。

やり方を考えてみたのですが、コントロールノード上で command モジュールで ping コマンドを実行する方法鹿思いつきませんでした。

その方法をご紹介します。

[Ansible コントロールノード] ----- ICMP ping -----> [ターゲットノード]
  • 環境
    • ansible 2.9.14

インベントリ

[sv]
192.168.1.1
192.168.1.2

Playbook

---
- hosts: sv
  gather_facts: false
  connection: local

  tasks:
    - name: execute ping
      command: "ping {{ inventory_hostname }} -c 4" 
      register: res_ping

    - name: debug ping result
      debug:
        msg: "{{ res_ping.stdout_lines[] }}"

connection: local なり、delegate_to: localhost なりの指定がないと、ターゲットノードからの ping コマンド実行になってしまいます(本記事の目的であるコントロールノードからではなく)。

異常系や結果の見やすさを考えると改良の余地がありそうですが、基本はこのような感じです。今回は ping を 4回実行することにします(-c 4)。

実行

Playbook を実行します。結果が2個目のタスクで表示されます。

$ ansible-playbook -i inventory.ini ping.yml 

PLAY [sv] **********************************************************************************************************

TASK [ping] ********************************************************************************************************
changed: [192.168.1.2]
changed: [192.168.1.1]

TASK [debug ping result] *******************************************************************************************
ok: [192.168.1.1] => {
    "msg": [
        "PING 192.168.1.1 (192.168.1.1): 56 data bytes",
        "64 bytes from 192.168.1.1: icmp_seq=0 ttl=64 time=1.161 ms",
        "64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.312 ms",
        "64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.549 ms",
        "64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=0.966 ms",
        "",
        "--- 192.168.1.1 ping statistics ---",
        "4 packets transmitted, 4 packets received, 0.0% packet loss",
        "round-trip min/avg/max/stddev = 0.966/1.247/1.549/0.213 ms"
    ]
}
ok: [192.168.1.2] => {
    "msg": [
        "PING 192.168.1.2 (192.168.1.2): 56 data bytes",
        "64 bytes from 192.168.1.2: icmp_seq=0 ttl=64 time=1.007 ms",
        "64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=1.146 ms",
        "64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=1.305 ms",
        "64 bytes from 192.168.1.2: icmp_seq=3 ttl=64 time=2.198 ms",
        "",
        "--- 192.168.1.2 ping statistics ---",
        "4 packets transmitted, 4 packets received, 0.0% packet loss",
        "round-trip min/avg/max/stddev = 1.007/1.414/2.198/0.465 ms"
    ]
}

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

ad-hoc コマンドの場合

Playbook を作成せずに、ad-hoc に ansible コマンドで済ませる場合は以下のようにします。

$ ansible -i inventory.ini sv -m command -a "ping {{ inventory_hostname }} -c 4" -c local
192.168.1.2 | CHANGED | rc=0 >>
PING 192.168.1.2 (192.168.1.2): 56 data bytes
64 bytes from 192.168.1.2: icmp_seq=0 ttl=64 time=1.211 ms
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=2.546 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=3.181 ms
64 bytes from 192.168.1.2: icmp_seq=3 ttl=64 time=1.948 ms

--- 192.168.1.2 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 1.211/2.221/3.181/0.728 ms
192.168.1.1 | CHANGED | rc=0 >>
PING 192.168.1.1 (192.168.1.1): 56 data bytes
64 bytes from 192.168.1.1: icmp_seq=0 ttl=64 time=1.235 ms
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=2.254 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=1.933 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=2.341 ms

--- 192.168.1.1 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 1.235/1.941/2.341/0.435 ms

おわりに

もし コントロールノードからターゲットノードへ ICMP による ping を実行するモジュールがあれば、あったらぜひ教えていただきたいです。

[Ansible] Jinja2 で同じ値のリピートを生成する

これは、Ansible Advent Calendar 2020(Adventor版)20日目の記事です。

はじめに

少しタイトルがわかりにくいかもしれませんが、例えばこうです。

  • abc という文字列を 3回繰り返し abcabcabc にする
  • [1, 2, 3] というリストを 3回繰り返し [1, 2, 3, 1, 2, 3, 1, 2, 3] にする

動作確認環境

  • Ansible 2.9.14
  • Jinja2 2.11.2

演算子 * を利用してリピート

言われてみればそりゃそうだという感じかもしれませんが、* を使います。

Jinja2 のドキュメントにも記載があります。 https://jinja.palletsprojects.com/en/master/templates/#math:titile

This can also be used to repeat a string multiple times. {{ '=' * 80 }} would print a bar of 80 equal signs.

おためし

---
- hosts: all
  gather_facts: false

  tasks:
    - name: repeat test1
      debug:
        msg: "{{ 'abc' * 3 }}"

    - name: repeat test2
      debug:
        msg: "{{ [1, 2, 3] * 3 }}"

    - name: repeat test3
      debug:
        msg: "{{ '-' * 20 }}"

実行結果

TASK [repeat test1] *********************************************
ok: [localhost] => {
    "msg": "abcabcabc"
}

TASK [repeat test2] *********************************************
ok: [localhost] => {
    "msg": [
        1,
        2,
        3,
        1,
        2,
        3,
        1,
        2,
        3
    ]
}

TASK [repeat test3] *********************************************
ok: [localhost] => {
    "msg": "------------------------------"
}

おわりに

Python 的にあれが使えるから Ansible (Jinja2)でも使えるかも」のと思いながら、Jinja2 のドキュメントを見ると新しい発見があるかもしれません。

[Ansible] 「つまずき Ansible 【Part23】AWX をコマンドなどで操作する」ふりかえり

はじめに

2020/12/19 に、YouTube Live で「つまずき Ansible 【Part23】AWX をコマンドなどで操作する」という配信をしました。

実際に作業しながら(ときには)エラーと戦って進めるシリーズです。

今回は、AWX を画面以外で操作する方法を扱いしました。

tekunabe.connpass.com

  • 環境
    • AWX 15.0.0


動画

www.youtube.com

  • 0:00 イントロダクション
  • 2:57 API を叩く
  • 14:05 awx コマンドで操作
  • 25:10 Ansible(Playbook) で操作
  • 40:28 おわりに

■ やったこと

API を直接叩く

AWX には API が備わっているのでそれを直接叩く方法です。

URL に ユーザー名とパスワードを直接埋め込む場合。ここではジョブテンプレートの一覧を取得。

今回は主に VS CodeREST Client という拡張を利用しました。

GET http://admin@password@192.168.1.141/api/v2/job_templates/ HTTP/1.1
Content-Type: application/json

トークンを利用する場合。

GET http://192.168.1.141/api/v2/job_templates/ HTTP/1.1
Content-Type: application/json
Authorization: Bearer XXXXXXX_DUMMY_TOKEN_XXXXXXX

なお、トークンは AWX の各ユーザーの画面の「トークン」から作成できる。

f:id:akira6592:20201219213232p:plain:w300
トークン画面

追加する場合は、POST を利用して、リクエストボディを含めます。

POST http://192.168.1.141/api/v2/job_templates/ HTTP/1.1
Content-Type: application/json
Authorization: Bearer XXXXXXX_DUMMY_TOKEN_XXXXXXX

{
    "name": "jt_show999999999",
    "description": "",
    "job_type": "run",
    "inventory": 2,
    "project": 8,
    "playbook": "show2.yml"
}

すでにあるものをもう一度作成しようとすると、エラーになる。

JobTemplate with this (organization, name) combination already exists.

API リファレンスは Ansible Tower のものを参照しました。

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

awx コマンド

API をラップしたようなツールです。

インストール

pip install awx

接続、認証情報の設定。

export TOWER_HOST=http://192.168.1.141
export TOWER_OAUTH_TOKEN=XXXXXXX_DUMMY_TOKEN_XXXXXXX
export TOWER_VERIFY_SSL=False

設定の確認。

awx config

ジョブテンプレートのリストの取得。

awx job_templates list

-f オプションによるフォーマットの指定。

awx job_templates list -f human

id name              
== ================= 
7  Demo Job Template 
9  jt_show           
15 jt_show999999999  

ジョブテンプレートの作成。インベントリなどは ID だけなく、名前でも指定可能。

awx job_templates create --name jt_show1111111 --project 8 --playbook show2.yml --inventory inv_sandbox

Ansible

awx.awx collection の tower_* モジュールで操作可能です。

インベントリファイル。

[awx]
awx01 ansible_host=192.168.1.141

Playbook。

---
- hosts: awx
  gather_facts: false
  connection: local

  environment:
    TOWER_HOST: "http://{{ ansible_host}}"
    TOWER_OAUTH_TOKEN: XXXXXXX_DUMMY_TOKEN_XXXXXXX

  tasks:
    - name: create jt
      awx.awx.tower_job_template:
        name: "jt99999999999999"
        inventory: inv_sandbox
        project: pj_tower-sample-nw
        playbook: show2.yml

Playbook 実行。

ansible-playbook -i inventory.ini jt.yml

なお、connection: local を忘れた場合は以下のエラーが発生。AWX に SSH 接続を試みようとして以下のエラーに。今回は Ansible コントロールノードから AWX の API を叩く形なので、SSH 接続は不要。

fatal: [awx01]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).", "unreachable": true}

今回は、awx.awx collection が 14.1.0 (ansilbe==2.10.4に付属)で、接続対象の AWX が 15.0.0 だったため、以下のバージョン不一致の警告が表示された。処理自体は正常にできた模様。

[WARNING]: You are running collection version 14.1.0 but connecting to tower version 15.0.0

なので、

ansible-galaxy collection install awx.awx:15.0.0 -f

として バージョンを合わせたところ警告は出なくなった。

また、同じ Playbook を実行したら、 changed ではなく ok になった。


おわりに

Web UI とそれ以外の操作応報をうまく使い分けていきたいと思います。


Part24 にむけて

以下のネタを検討中です。気が向いたものをやります。

  • Ansible 2.10 関連
  • connection: local ななにか
  • ansible.cfg
  • Jinja2、フィルター
  • Windows
  • ESXi で VM作成
  • cli_parse モジュール(Part15 の続き)

[Ansible] ACI モジュールの認証情報を環境変数で指定してPlaybookをすっきりさせる

これは、Ansible Advent Calendar 2020 (Qiita版)の19日目の記事です。

はじめに

Ansible には Cisco ACI の操作にも対応していて、モジュールが多数あります。

内部的には APICAPI を利用しますが、httpapi コネクションプラグインには対応していません。 そのため、基本的には各タスクごとに接続先や認証情報を指定する必要があり、Playbook が冗長になりがちになります。

YAML のアンカー、エイリアス、マージを利用すれば冗長さはある程度抑えられますが、YAML に慣れてないと初見で読み解けなかったり、マージ自体はタスクごとに指定が必要という点は残ります。

一方で、cisco.aci collection の version 1.1.0で、環境変数によって接続先や認証情報を指定できるようになりました。 この機能により、各タスクごとに接続先や認証情報を指定する必要がなくなりました。

この記事では、簡単なサンプルでご紹介します、

  • 動作確認環境
    • Ansible 2.10.3 / 2.9.15
    • cisco.aci collection 1.1.0
      • 2020/12/19 現在の最新は 2.0.0 です
      • Ansible 2.9 系でも利用可能
    • DevNet Sandox ACI Version 4.1(1k)


環境変数名とモジュールオプション名の対応

接続先や認証情報を指定する環境変数は、モジュールの各オプションに対応する形で用意されています。対応は以下のとおりです。

項目 環境変数 モジュールのオプション 備考
ホスト名 ACI_HOST host
ポート ACI_PORT port
ユーザー名 ACI_USERNAME, ANSIBLE_NET_USERNAME username
パスワード ACI_PASSWORD, ANSIBLE_NET_PASSWORD password
証明書名 ACI_CERTIFICATE_NAME certificate_name 署名ベース認証でのみ利用
秘密鍵のファイル名 ACI_PRIVATE_KEY private_key 署名ベース認証でのみ利用 
証明書検証の有無 ACI_VALIDATE_CERTS validate_certs
SSL/TLSの利用 ACI_USE_SSL use_ssl
プロキシ利用の有無 ACI_USE_PROXY use_proxy
タイムアウト ACI_TIMEOUT timeout

各モジュールの説明ページ(例: aci_tenant モジュール)にも説明があります。

優先度はモジュールのオプションのほうが高く、モジュールのオプションが指定されていなければ環境変数を参照する仕様です。

(補足) 2種類の認証方式

aci_* モジュールは、パスワードベース、署名ベースの2種類の認証方式があります。

パスワードベース認証方式は環境面の手間が少ない反面、DDoS 攻撃と誤認されやすいデメリットがあります。

一方、署名ベース認証方式は証明書や鍵を用意して APIC に登録するといった準備が必要ですが、DDoS 攻撃と誤認されにくいメリットがあります。

この記事でご紹介するサンプルは、準備が簡単なパスワードベース認証方式を利用した Playbook です。必要に応じて読み替えてください。


サンプル Playbook

テナント test_tenant01 と、 Application Profile test_ap01EPG test_epg01 を作成する Playbook です。

環境変数で一括指定

環境変数を指定するenvironment キーワードは、Play レベルで指定します。Task レベルでも指定できますが、ここではなるべく冗長さを排除するために、 Play レベルで指定します。

また、各パラーメーターの実際の値は、別途変数定義ファイルで定義済みのものとします。

---
- hosts: apic
  gather_facts: no
  connection: local

  environment:  # 環境変数による接続先や認証情報の指定
    ACI_HOST: "{{ ansible_host }}"
    ACI_USERNAME: "{{ username }}"
    ACI_PASSWORD: "{{ password }}"
    ACI_VALIDATE_CERTS: false

  tasks:
    - name: create tenant
      cisco.aci.aci_tenant:
        tenant: test_tenant01
        state: present

    - name: create ap
      cisco.aci.aci_ap:
        tenant: test_tenant01
        ap: test_ap01
        state: present

    - name: create epg
      cisco.aci.aci_epg:
        tenant: test_tenant01
        ap: test_ap01
        epg: test_epg01
        state: present

(比較用)タスクごとに指定した場合

比較のために、普通にタスクごとに接続先や認証情報を指定した場合の Playbook も掲載します。

このように、基本的には各タスクに hostusername などの接続先や認証情報の指定が必要です。

---
- hosts: apic
  gather_facts: no
  connection: local

  tasks:
    - name: create tenant
      cisco.aci.aci_tenant:
        host: "{{ ansible_host }}"  # タスクごとに指定する場合
        username: "{{ username }}"  # タスクごとに指定する場合
        password: "{{ password }}"  # タスクごとに指定する場合
        validate_certs: no          # タスクごとに指定する場合
        tenant: test_tenant01
        state: present

    - name: create ap
      cisco.aci.aci_ap:
        host: "{{ ansible_host }}"  # タスクごとに指定する場合
        username: "{{ username }}"  # タスクごとに指定する場合
        password: "{{ password }}"  # タスクごとに指定する場合
        validate_certs: no          # タスクごとに指定する場合
        tenant: test_tenant01
        ap: test_ap01
        state: present

    - name: create epg
      cisco.aci.aci_epg:
        host: "{{ ansible_host }}"  # タスクごとに指定する場合
        username: "{{ username }}"  # タスクごとに指定する場合
        password: "{{ password }}"  # タスクごとに指定する場合
        validate_certs: no          # タスクごとに指定する場合
        tenant: test_tenant01
        ap: test_ap01
        epg: test_epg01
        state: present

この場合、少し Playbook が長くなってしまいます。


実行結果

Play 単位の環境変数で接続先や認証情報を指定した Playbook を実行します。

$ ansible-playbook -i inventory.ini aci_test.yml         
PLAY [apic] ******************************************************************************************************

TASK [create tenant] *********************************************************************************************
changed: [apic01]

TASK [create ap] *************************************************************************************************
changed: [apic01]

TASK [create epg] ************************************************************************************************
changed: [apic01]

PLAY RECAP *******************************************************************************************************
apic01                     : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

$

無事に接続、認証も成功し、処理が正常に完了しました。

f:id:akira6592:20201205183254p:plain
作成されたもの


まとめ

Play 単位の環境変数で、接続先や認証情報を指定することで、各タスクがスッキリする Playbook をご紹介しました。

タスクごとに接続先や認証情報を指定することを避けたい場合は、この方法を検討してみてはいかがでしょうか。

[Ansible] Ansible 2.10 の次は 3.0.0 (Community Package として)

これは Ansible Advent Calendar 2020 (Adventar版) の 14日目の記事です。

はじめに

現在、コミュニティ版としての 最新安定版は Ansible 2.10 系です。

もともとのロードマップでは、Ansible 2.10 系 の次は Ansible 2.11 系でした。ですが先月、2.10 系の次は 3.0.0 になる予定、との情報が入ってきまた。

[2021/04/27 追記] ansible-core 2.11 がリリースされました。こちらは Ansible Community Package の コアの位置するもので、ansible-base 2.10 の後継です。関係については公式ブログの記事をご参照ください。

簡単にですが、この記事では Ansible 3.0.0 のリリースについての情報をまとめます。

Ansible 3.0.0 ってなに?

Network to Code の Slack や同僚きっかけで、以下の投稿を知りました。

www.reddit.com

Twitter アカウント @ansible でもこの投稿について触れられていました。

ポイント

ポイントは以下の通り。

公式ドキュメントの devel ブランチのロードマップにも反映されました。

docs.ansible.com

また、Migrating to Ansible Collections というウェビナーにも Ansible Project 3.0.x (pip)という言葉が登場していました (12:41ころ)。

www.ansible.com

今後の情報を追うために

予定は予定ですので、何かしら変更されるかもしれません。

今後の情報は以下のあたりを追っておくと分かるかも知れません。

私自身もまだ情報を収集中です。もし修正や追加すべき情報がありましたら @akira6592 までお知らせいただけると幸いです。