てくなべ (tekunabe)

ansible / network / automation

直前に実行したコマンドの最後の引数を取得する方法3つ

はじめに

よく忘れてしまうのですが、直前に実行したコマンドの最後の引数を取得する方法があります。

例えば、

mkdir hoge

したあとに、hogecd したい時に便利です。

最近、tiwtter でいくつか方法があることを知りました。

忘れる自信があるので書き留めておきます

動作確認環境: CentOS 8 / bash

その1: $_

[vagrant@stumble stumble]$ mkdir hoge
[vagrant@stumble stumble]$ cd $_
[vagrant@stumble hoge]$ pwd
/vagrant/stumble/hoge

その2: !$

[vagrant@stumble stumble]$ mkdir hoge2
[vagrant@stumble stumble]$ cd !$
cd hoge2
[vagrant@stumble hoge2]$ pwd
/vagrant/stumble/hoge2

その3: ESC .

[vagrant@stumble stumble]$ mkdir hoge
[vagrant@stumble stumble]$ cd hoge
[vagrant@stumble hoge3]$ pwd
/vagrant/stumble/hoge

この方法だけ、ログだとわかりにくいので動画で補足します。

挙動としてはこちらが一番好みです。

[Ansible] つまずきながら進める Ansible 【Part3】ふりかえり

はじめに

2020/05/30 に、YouTube Live でつまずいきながら進める Ansible 【Part3】という配信をしました。 実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

前回までは、Ansible のインストールとインベントリファイルの作成、Cisco IOS の機器に show コマンドを実行する Playbook を作りました。

今回は、設定編として、スタティックルートを追加する Playbook を作成しました。

つまずいたエラーと原因、対処をふりかえります。

動画

www.youtube.com


ios_static_route の実行

2回目も changed になってしまった。

1回目の実行で設定が入れば、2回目以降は、ok となるはずのところ、 changed になってしまった。

原因

以下のように mask: 0.0.0.00 と指定していたため

    - name: set route
      ios_static_route:
        prefix: 0.0.0.0
        mask: 0.0.0.00        # ここ
        next_hop: 192.168.1.1

これにより

ip route 0.0.0.0 0.0.0.00 192.168.1.1

が実行される。

しかし、コンフィグ上はあくまでも 0.0.0.0 となる。

ip route 0.0.0.0 0.0.0.0 192.168.1.1

そのため、再度 Playbook を実行し、

ip route 0.0.0.0 0.0.0.00 192.168.1.1

を実行するかどうかの判断の時に、文字列比較の結果「まだ無い設定だ」と判断され、再度実行される。

結局は、コンフィグとしては、

ip route 0.0.0.0 0.0.0.0 192.168.1.1

に落ち着くので、無意味で混乱を招く changed だった。

対処

mask: 0.0.0.0.00mask: 0.0.0.0.0 に修正。

再実行したところ ok になった。

「show running-config」で表示される形式に合わせるのがポイント。

    • 省略しない(shut ではなく shutdonw など)
    • 大文字小文字はあわせる(GigabitEthernet など)
    • スペースを入れすぎない

参考: Ansible Network FAQ — Ansible Documentation

実行されたコマンドがなんなのか気になって仕方がない

モジュールのオプションで指定した値が、実際にどのようなコマンドになって実行されたかが気になる。

対処

ios_static_route モジュールの戻り値commands の中身を確認する。

- hosts: rt01
  
  tasks:
    - name: set route
      ios_static_route:
        prefix: 0.0.0.0
        mask: 0.0.0.0
        next_hop: 192.168.1.1
      register: result   # 結果を変数 result に保存
    
    - name: debug
      debug:
        msg: "{{ result.commands }}"  # 表示

実行結果抜粋

TASK [set route] ***************************************************************************************
changed: [rt01]

TASK [debug] *******************************************************************************************
ok: [rt01] => {
    "msg": [
        "ip route 0.0.0.0 0.0.0.0 192.168.1.1"
    ]
}

ためしたところ、実行され「た」コマンドのよう。すでに設定されていて、実行する必要がなかった場合は空となる。

TASK [debug] *******************************************************************************************
ok: [rt01] => {
    "msg": []
}


Part 4 にむけて

次回の Part 4 では、Playbook のちょっとした改善や、別の設定変更をやってみたいと思います。

[Ansible] ios_config モジュールで save_when: modified 指定時に常に changed になる原因

はじめに

Cisco IOS 機器に設定コンフィグを流し込む、ios_config モジュールには、コンフィグの保存(copy running-config startup-config)する条件を指定する、save_whenをいうオプションがあります。

ここで、modified を指定すると「running-config と startup-config に差分があるときだけ copy する」という動作になります。

ところが先日、差分がないであろうタイミングでも毎回 changed になってしまう現象に遭遇しました。always ではないのに。

その原因を調べました。結果としては、よく見たら差分がありました。

  • 動作環境
  • Ansible 2.9.9
  • Cisco IOS XE Software, Version 17.01.01

現象

たとえば、以下のようなタスクを実行したときです。

    - name: save_when test
      ios_config:
        save_when: modified

1回目の実行で、コンフィグ差分があれば changed (保存した)になるのは納得できるのですが、続いて実行したときも毎回 changed (保存した)になりました。

TASK [save_when test] ********************************************************************
changed: [rt01]

※この例で、linessrc オプションの指定がないことからも分かるに、コンフィグを投入しなくても、コンフィグ保存の動作が起こるとすると changed になります。そのため「このモジュールに冪等性がなく、毎回コンフィグを投入してるのではないか」という印象になるかもしれませんがそうではありません。linessrc オプションが指定され場合は、指定されたコンフィグがすでにあるかないかを事前に調べてから投入の有無を決定します。このあたりは注意点があります。

原因

よく見たら、コンフィグ保存直後にも関わらず、running-config と startup-config に差分がありました。

意味合い的には変わらないでしょうが、証明書情報をコンフィグ内に埋め込んでいるか、NVRAM 内のファイルを参照するかの違いでした。

  • running-config
...(略)...
crypto pki certificate chain TP-self-signed-1813660109
 certificate self-signed 01
  30820330 30820218 A0030201 02020101 300D0609 2A864886 F70D0101 05050030 
...(略)...
  • startup-config
...(略)...
crypto pki certificate chain TP-self-signed-1813660109
 certificate self-signed 01 nvram:IOS-Self-Sig#1.cer
...(略)...

コンフィグによっては、他の箇所が原因になることも有り得そうです。

補足

なお、running-configと startup-config は他にも出力が異なる箇所があります。

たとえば、冒頭部分です。

  • running-config
rt01#sh running-config 
Building configuration...

Current configuration : 4356 bytes
!
! Last configuration change at 14:19:27 UTC Thu May 28 2020 by ec2-user
!
version 16.12
...(略)...
  • startup-config
rt01#sh startup-config 
Using 2439 out of 33554432 bytes
!
! Last configuration change at 14:05:56 UTC Thu May 28 2020
!
version 16.12
...(略)...

このあたりは、比較対象外にする文字列として、Ansible 內部で定義されているようです。

github.com

他にも、 ! で始まる行も比較前に除外されます。

たとえば、

Building configuration...

Current configuration : 4344 bytes
!
! Last configuration change at 14:05:56 UTC Thu May 28 2020
!
version 16.12
service timestamps debug datetime msec
...(略)...

version 16.12
service timestamps debug datetime msec
...(略)...

に、予め整形されます。

おわりに

最初は linessrc オプションと併用していたときに毎回 chagned になってしまったため、コンフィグの書き方などに気を取られていました。

今回のようなケースで困る場合は、modified ではなく changed を検討すると良さそうです。

[Ansible/AWX] ジョブテンプレートやワークフロージョブテンプレートを一括削除するワンライナー

はじめに

awx コマンドを利用すると、API を通じて Ansible Tower / AWX 上の 様々な操作ができます。

少し組み合わせて、ジョブテンプレートやワークフロージョブテンプレートを一括削除するワンライナーをご紹介します。

awx コマンドのインストールや接続情報の設定については、以下の記事を参照してください。

tekunabe.hatenablog.jp

また、json をフィルターする関係で jq をインストールしておく必要があります。

  • 動作確認環境
    • AWX 11.0.0
    • awx コマンド 11.2.0

ジョブテンプレートの一括削除

awx job_template listname 一覧を取得して、各 nameawx job_template delete します。

for jt in $(awx job_template list -f jq --filter ".results[] | .name"); do awx job_template delete ${jt}; done

ワークフロージョブテンプレートの一括削除

awx workflow_job_templates listname 一覧を取得して、各 nameawx workflow_job_templates delete します。

for wf in $(awx workflow_job_templates list -f jq --filter ".results[] | .name"); do awx workflow_job_templates delete ${wf}; done 

補足: 一覧を確認したい場合

削除する前に、一覧を確認したい場合は、以下のように実行します。

  • 例1
$ awx job_template list -f jq --filter ".results[] | .name"
jt_01_show
jt_02_debug
jt_03_debug
  • 例2
$ awx job_template list -f human
id name        
== =========== 
28 jt_01_show  
26 jt_02_debug 
27 jt_03_debug 

ワークフロージョブテンプレートの場合は、job_templateworkflow_job_templates に読み替えてください。

おわりに

両方とも、特に確認やログ出力などなく削除するため、十分にご注意ください。

jq のフィルターを工夫すると、特定の条件にマッチしたものだけ削除といった応用もできると思います。

[Ansible] コマンド版で使用している Playbook を Ansible Tower / AWX に載せる前にチェックしたいポイント

はじめに

Ansible Tower / AWX は、GUI からジョブという実行単位を経由して、Playbook を実行します。

そのため、CLI からの対話的な操作は受け付けられません。

Ansible Tower / AWX に載せる前に他の方法に変える必要があります。

vars_prompt による対話的な変数設定は Survey へ

vars_prompt、は CLI からの対話的な入力を必要とします。Ansible Tower / AWX ではそのままでは利用できません。

その代わりに、Survey という機能で、GUI で変数の設定ができます。

参考: 26. Best Practices — Ansible Tower User Guide v3.7.0

無期限 の pause は使わない

pause という、処理を止めるモジュールがあります。

CLI からは止まってる途中でも Ctrl+C で強制終了できますが、Ansible Tower / AWX ではできません。

止める必要がある場合は、タイムアウトを設定します。

参考: 26. Best Practices — Ansible Tower User Guide v3.7.0

Playbook (ジョブ)を分割してもよいのであれば、2つのジョブの間に、期限なしの Approval Node を挟むワークフローにしても良いともいます。(Ansible Tower 3.6 以降)

参考: How to add approval steps to Ansible Tower workflows

Playbook Debugger

Playbook Debugger は、Playbook の実行を途中でとめて、変数の値を表示、変更したりできるデバッガです。 これも CLI からの入力を必要とします。

あくまで、Debugger は CLI から使うという役割分担となります。

[Ansible] つまずきながら進める Ansible 【Part2】ふりかえり

はじめに

2020/05/23 に、YouTube Live でつまずいきながら進める Ansible 【Part2】という配信をしました。 実際に作業しながらエラーと戦って進めるシリーズです。

前回の Part1(動画ふりかえりブログ)では、Ansible のインストール、インベントリファイルの作成、簡単な Playbook の作成を行いました。

Playbook は Cisco IOS の機器の、show ip route コマンドの実行コマンドを、ファイルに保存するというものでした。しかし、まだ余計な情報が多かったり、思った形式になっていませんでした。

また、いきなり Playbook を作成してしまいましたが、その前に疎通確認のステップをしたほうが良かったように思います。

Part 2 では、疎通確認と、Playbook の改善をしました。

つまずいたエラーと原因、対処をふりかえります。

動画

www.youtube.com


■ 疎通確認

ping モジュールを疎通確認として利用できない

ping モジュールは、ターゲットノード(接続先)で Python が利用できるか確認するモジュールです。

よく ansible コマンド(アドホックコマンド)と併用して、Ansible としての疎通確認(ICMP ではなく、ssh + python)に利用されます。

Cisco IOSping モジュールを利用したところ、接続情報が誤っているのにもかかからず ok となってしまった。

$ ansible -i inventory.ini all -m ping
rt01 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

原因

Ansible 2.9 からの仕様。

ネットワーク機器向けのコネクションプラグイン利用時に ping モジュールを使っても、ターゲットノードに接続しにいかないため、接続確認として機能しない。

参考: [Ansible] ネットワーク機器に接続する条件はバージョンやコネクションブラグインによって異なる - てくなべ (tekunabe)

対処

ping モジュールではなく、実際ににネットワーク機器に接続しに行くモジュールを利用する。

$ ansible -i inventory.ini all -m ios_facts
[WARNING]: default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards
rt01 | SUCCESS => {
    "ansible_facts": {
        "ansible_net_all_ipv4_addresses": [
            "192.168.1.11",
         // ...(略)...
        ],
        "ansible_net_system": "ios",
        "ansible_net_version": "15.8(3)M2",
        "ansible_network_resources": {},
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false
}

コマンドは任意だがここでは show version

$ ansible -i inventory.ini all -m ios_command -a "commands='show version'"
rt01 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "stdout": [
        // ...(略)...
    ],
    "stdout_lines": [
        [
            "Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.8(3)M2, RELEASE SOFTWARE (fc2)",
            "Technical Support: http://www.cisco.com/techsupport",
            "Copyright (c) 1986-2019 by Cisco Systems, Inc.",
            "Compiled Thu 28-Mar-19 14:06 by prod_rel_team",
            "",
           // ...(略)...
            "Configuration register is 0x0"
        ]
    ]
}

参考: Network Debug and Troubleshooting Guide — Ansible Documentation

gather_facts モジュールでも可能。

setup モジュールは、ネットワーク機器に接続しにいかないため、疎通確認としては利用不可。


■ コマンド実行結果の整形

コマンド実行結果のファイルに余計な情報がは入ってしまう

{"changed": false, "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 not set\n\n      10.0.0.0/8 is variably subnetted, 3 subnets, 2 masks\nC        10.0.0.0/24 is directly connected, GigabitEthernet0/3\nL        10.0.0.1/32 is directly connected, GigabitEthernet0/3\nC        10.255.255.1/32 is directly connected, Loopback0\n      192.168.1.0/24 is variably subnetted, 2 subnets, 2 masks\nC        192.168.1.0/24 is directly connected, GigabitEthernet0/0\nL        192.168.1.11/32 is directly connected, GigabitEthernet0/0"], "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 not set", "", "      10.0.0.0/8 is variably subnetted, 3 subnets, 2 masks", "C        10.0.0.0/24 is directly connected, GigabitEthernet0/3", "L        10.0.0.1/32 is directly connected, GigabitEthernet0/3", "C        10.255.255.1/32 is directly connected, Loopback0", "      192.168.1.0/24 is variably subnetted, 2 subnets, 2 masks", "C        192.168.1.0/24 is directly connected, GigabitEthernet0/0", "L        192.168.1.11/32 is directly connected, GigabitEthernet0/0"]], "failed": false}

原因

register で保存したコマンド実行結果には、コマンド実行そのもの以外にもメタ情報が含まれるため。

対処

result の内容を確認して、必要な箇所である、result.stdout[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 not set

      10.0.0.0/8 is variably subnetted, 3 subnets, 2 masks
C        10.0.0.0/24 is directly connected, GigabitEthernet0/3
L        10.0.0.1/32 is directly connected, GigabitEthernet0/3
C        10.255.255.1/32 is directly connected, Loopback0
      192.168.1.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.1.0/24 is directly connected, GigabitEthernet0/0
L        192.168.1.11/32 is directly connected, GigabitEthernet0/0

調べる際は、Playbook Debugger で試行錯誤できるのが便利。

    - name: save to file
      copy:
        content: "{{ result.stdout[0] }}"
        dest: "show_ip_route_{{ inventory_hostname }}.log"
      debugger: always    # タスク実行時に無条件で Playbook Debugger を起動

Playbook Debugger の使い方は、書籍「Ansible 実践ガイド 第3版」のP368「7-4-4 Playbook Debugger」にも掲載されています(宣伝)。

book.impress.co.jp


■ show コマンドの追加

show running-config コマンドが実行できない

原因

権限不足。 ログインユーザーが一般ユーザーだったため、show ip route は実行できても、how running-config は実行できない。

対処

変数定義ファイル(今回は group_vars/ios.yml)に、権限昇格のための以下の変数を追加。

ansible_become: true 
ansible_become_method: enable
ansible_become_password: secret 

参考: IOS Platform Options — Ansible Documentation


Part 3 にむけて

次回の Part 3 では、以下のアンケートで2番目に多かった、ルーティングの設定をしてみたいと思います。

[Ansible] Ansible Tower 3.7.0 でワークフローノード作成モジュール(awx.awx.tower_workflow_job_template_node)が使えるようになった

はじめに

先日、Ansible Tower 3.7 がリリースされました。

ワークフロージョブテンプレートに対して、ワークフローノードを定義する awx.awx.tower_workflow_job_template_node が、Asnible Tower 3.6 系に対しては使えなかったのですが、3.7 に試したら使えるようになってました。(もともとの使い方がうまくなかったのであればすみません・・)

もともと AWX には使えていたので、同等の実装が入ったのだと思います。

簡単なサンプルで検証します。

利用するファイル

ワークフローを定義する変数ファイルと、それを利用する Playbook です。

  • 変数定義ファイル(抜粋)
workflow_templates:
  - name: wf_show
    desicription: show command workflow
    nodes:
      - identifier: node101   # 内部識別ID
        unified_job_template: jt_show   # 実行するジョブテンプレート
        success_nodes:  # 成功時に進めるノード(緑のリンク)
          - node201
          - node202
        failure_nodes:  # 障害発生時に進めるノード(赤のリンク)
          - node209
      - identifier: node201
        unified_job_template: jt_show
        success_nodes:
          - node301
      - identifier: node202
        unified_job_template: jt_show
        success_nodes:
          - node301
      - identifier: node209
        unified_job_template: jt_show
      - identifier: node301
        unified_job_template: jt_show
  • Playbook(タスク抜粋)
    - name: create workflow templates node
      awx.awx.tower_workflow_job_template_node:
        identifier: "{{ item.1.identifier }}"
        unified_job_template: "{{ item.1.unified_job_template | default(omit) }}"  # 実行するジョブテンプレート
        workflow_job_template: "{{ item.0.name }}"        # 関連付けるワークフロージョブテンプレート名
      loop: "{{ workflow_templates | subelements('nodes') }}"
      loop_control:
        label: "{{ item.1.identifier }}"

Ansible Tower 3.6 までのエラー

3.6 までは、こんなエラーになっていました。(抜粋)

TASK [create workflow templates node] ******************************************
failed: [tower36] (item=node101) => {"ansible_loop_var": "item", "changed": false, "item": [{"allow_simultaneous": true, "desicription": "show command workflow", "extra_vars": {"wf_var1": "hello_wf1"}, "name": "wf_show", "nodes": [{"failure_nodes": ["node209"], "identifier": "node101", "success_nodes": ["node201", "node202"], "unified_job_template": "jt_show"}, {"identifier": "node201", "success_nodes": ["node301"], "unified_job_template": "jt_show"}, {"identifier": "node202", "success_nodes": ["node301"], "unified_job_template": "jt_show"}, {"identifier": "node209", "unified_job_template": "jt_show"}, {"all_parents_must_converge": true, "identifier": "node301", "unified_job_template": "jt_show"}], "survey": {"description": "", "name": "", "spec": [{"default": "new_username", "max": 1024, "min": 0, "question_description": "追加ユーザ名を入力してください", "question_name": "Enter new user name", "required": true, "type": "text", "variable": "new_username"}, {"default": "password", "max": 32, "min": 0, "question_description": "パスワードを入力してください", "question_name": "Enter new user password", "required": true, "type": "password", "variable": "new_password"}, {"choices": "operator\nadministrator", "default": "operator", "question_description": "役割を選択してください", "question_name": "Enter new user role", "required": true, "type": "multiplechoice", "variable": "new_role"}]}, "survey_enabled": true}, {"failure_nodes": ["node209"], "identifier": "node101", "success_nodes": ["node201", "node202"], "unified_job_template": "jt_show"}], "msg": "Got a 400 response when trying to get one from workflow_job_template_nodes, detail: WorkflowJobTemplateNode has no field named 'identifier'"}

おそらく、WorkflowJobTemplateNode has no field named 'identifier' がポイントで、3.6 には API 仕様としてまだこのキーがなかったということだと思います。

Ansible Tower 3.7.0 の場合(正常)

3.7.0 は以下のように正常に実行できました。

TASK [create workflow templates node] ********************************************************************
changed: [tower37] => (item=node101)
changed: [tower37] => (item=node201)
changed: [tower37] => (item=node202)
changed: [tower37] => (item=node209)
changed: [tower37] => (item=node301)

参考までに、作成されたワークフローを掲載します。(実際は、前述のタスクのあとにノード間のリンクを定義するタスクを実行済み)

f:id:akira6592:20200521213953p:plain
作成されたワークフロー

おわりに

Ansible Tower の設定を自動化できる箇所が増えてよかったです。