てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] 最初の文字だけ大文字に変換する capitalize フィルター

capitalize フィルターは、最初の文字だけ大文字に変換するものです。

Ansible の公式ドキュメントの Filter ページには載っていませんが、Jinja2 のドキュメントに載っています。

以下に、使用例を紹介します。

---
- hosts: localhost
  gather_facts: false

  tasks:
    - debug:
        msg:
          - "{{ 'abc' | capitalize }}"               # Abc
          - "{{ 'ABC' | capitalize }}"               # Abc
          - "{{ 'this is a kingyo.' | capitalize }}" # This is a kingyo.
          - "{{ 'THIS IS A KINGYO.' | capitalize }}" # This is a kingyo.

ネットワーク設定自動化で、いつ、何を確認するか(JANOG45 の Fastly さんの発表から学ぶ)

はじめに

JANOG45 Meeting in Sapporo で、Fastly の土屋さんから「急成長を支えるFastlyスケーラブル・グローバルネットワーク」という発表がありました。

www.janog.gr.jp

シンプルさを追及した設計や、自動化の話があって大変興味深いものでした。

なかでも私が注目したのが、設定自動化の作業フロー内で「いつ、何を確認するか」です。(該当スライドは P15〜、動画は 22:30〜)

この記事では、Fastly さんの事例とそこから学んだことをまとめます。主に、実機に設定を反映する前の段階にフォーカスします。

なお、当日は残念ながらプログラムに参加できず、資料アーカイブ動画(2020/03/09 12:00 までの期間限定公開)togetter を拝見しました。


■ Fastly さんの場合

発表内の設定自動化部分を中心にまとめます。発表の内容をベースにしていますが、若干自己解釈が含まれています。

自動化の背景

  • 1年に10拠点くらい増える、数年後に倍になっているかもしれない
  • とはいえ、ネットワークエンジニアを倍に増やすわけにはいかない
  • スケーラブルにするために自動化
  • トポロジーコンフィギュレーションがシンプル、標準化しやすいので自動化もしやすい

自動化していること

Network Configuration Workflow

  • 新しくピアを張る設定作業の作業フローの話
  • 大まかな工程は、Day0 は作業準備期間、Day1 は作業日当日

f:id:akira6592:20200219164550p:plain
急成長を支えるFastlyスケーラブルグローバルネットワーク(P15)から引用

【Day0】 作業準備期間

  • 作業者が手元で作業情報をアップデートし、Pull Request を出す
    • 作業情報例
      • どのスイッチ、ポートでどことピアを張るか、IPアドレス設定、BGPのパラメーター
  • CI テスト が実行される
    • システムが確認すること
      • バリデーションテスト
  • 承認者が Pull Request の内容をレビューして問題が無ければデータストアにマージする
    • 人が確認すること
      • 設定の意図が正しいこと
      • 正しいPOPであること、クラッシュ(既知の不具合?)しないこと
  • Ansibly (Ansible の内製ラッパーツール)で Dry-run を実行
    • パラメーターファイルをもとにコンフィグを生成
    • そのコンフィグを機器に流す、ただし commit しない
    • システムが確認すること
      • ファイルに問題がないか
      • 設定に矛盾がないか
      • ちゃんと投入できるコンフィグレーションか
        • 実際の機器に(commitせずに)投入することで確認できる

【Day1】 作業日当日

  • 準備段階で正しいコンフィグができているので、あとはコマンドを叩いて、実際にピアやキャッシュを設定する
  • 設定後はネットワークの正常性(トラフィック変化、意図しないアラートがでていないか、など)を人が確認


■ 自動化において、いつ、何を確認するか

前項で、Fastly さんでの事例をまとめました。次は、そこから学んだことや、今までなんとなく頭の中で考えていたことを書き連ねます。(事例とは直接関係ありません)

観点

何のための確認か

  • いつ
  • だれ(なにが)が
  • 何を確認すれば
  • 何を防げるのか

何を確認するか

  • 構文
    • コンフィグが構文的に正しいこと
    • 機械的に判断するのが得意
    • 実際の機器に投入して commit 寸前でとめてチェックするのが精度が高い
      • 人がそれらしく作ったコンフィグでも実際の機器に投入するとエラーになることがある
      • 別途コンフィグの構文チェックができる機器もある(Cumulus + Ansible の例
    • コンフィグ投入で即反映するタイプ(commit がない)機器では難しい
      • 独自のロジックを作りこんで、事前にチェックはできる
      • ただ、精度はチェックロジックの実装依存になる
  • 設計
    • 設計が正しいこと
    • 人が判断するのが得意
      • というより機械が苦手
      • 機械的に判断できることは限定的、または相応の作りこみが必要
  • 振る舞い
    • 各種 show コマンド、ping、traceroute コマンドで状態確認
      • 機械的に判断するためには、コマンド結果を構造化データ(JSONなど)で取得できるのが良い
    • 実機に設定を反映した後のタイミング
      • Batfish のようなツールで、ある程度事前にチェックできることも

いつ、何をチェックするか(できるか)

想定フロー

以下のようフローを想定します。

f:id:akira6592:20200219210530p:plain
想定フロー

方針

基本的には以下のような方針になるのかなと考えています。

  • 設計の正しさは、人が確認
  • 構文の正しさは、機械(自動化システムやネットワーク機器)が確認

フェーズごとの確認のしやすさ

フェーズ 主体 設計チェック 構文チェック
(1) パラメーター・コンフィグ作成 【◯】 設計に集中してパラメーターを作成できる。ただし作成者の思い込みや、環境不備によるミスは気が付きにくい。 【△】 作成者の環境依存。ローカルの開発環境で lint 的なものを動かしていれば有効
(2) CI 機械 【△】 設計意図をくみ取ったり、ネットワーク全体としての妥当性を判断するのは難しく、作り込みが必要。コンフィグの場合は、予めパースする必要がある 【◯】 スキーマチェック。コンフィグの場合は、予めパースする必要がある
(3) レビュー 【◯】 作成者の思い込みよるミスを防ぎやすい 【△】 机上チェックになりがちで難しい
(4) Dry-run 機械 【×】 ネットワーク機器自身に設計意図は判断できない 【◯】 機器が実際に解釈した結果を得られる

補足

上記でいう「パラメーター」は、設定値を構造化データフォーマットで定義したものを指します。機器に投入できるコンフィグを生成する元ネタになります。

interfaces:
  - name: GigabitEthernet1/0/1
    description: hogehoge
    enalbed: true

一方の「コンフィグ」とは機器に投入できるコンフィグそのものを示します。

interface GigabitEthernet1/0/1
  description hogehoge
  no shutdown


さいごに

1つの事例をきっかけにして、いつ、何を確認するかをまとめました。

うまく整理、言語化できていないところもあるかと思いますので、お気づきの点があれば@akira6592 までコメントいただければ幸いです。

これを書いている時に、人が得意なこと・苦手なこと、機械が得意なこと・苦手なことを少し深堀りしたくなったので、まとめたら別途記事を書きます。

参考

www.intentionet.com tekunabe.hatenablog.jp

その他参加した JANGO45 のプログラム

tekunabe.hatenablog.jp

[Ansible] 見やすい json に整形するには to_nice_json フィルターが nice で便利

はじめに

Ansible には、テキストデータの内容を整形するための多数のフィルターがあります。

json 形式に変換するフィルターもいくつかあり、そのなかで to_nice_json フィルターは、人にとって見やすく整形するのにとても便利です。

公式ドキュメントでは出力例の記載がないので、イマイチありがたみが分からないかもしれません。

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

f:id:akira6592:20200215182502p:plain:w400
こんなイメージ


サンプル

Playbook の作成

処理内容は以下の通りです。

  • uri モジュールで、Google Public DNSDNS-over-HTTPS (DoH)を利用して www.ansible.com の Aレコードのデータを JSON で取得
  • 結果を、整形なし、to_nice_yaml フィルターで整形、のそれぞれのパターンでファイルに出力
- hosts: localhost
  gather_facts: no
  connection: local

  tasks:
    - name: get json
      uri:
        url:  https://dns.google.com/resolve?name=www.ansible.com&type=A
      register: result

    - name: raw json
      copy:
        content: "{{ result }}"
        dest: raw.json       # 整形なしで出力
    
    - name: nice json
      copy:
        content: "{{ result | to_nice_json }}"
        dest: nice_json.json  # to_nice_json 適用結果を出力

Playbook 実行

Playbook を実行します。

$ ansible-playbook -i localhost, nice.yml 

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

TASK [get json] ***************************************************************************************************
ok: [localhost]

TASK [raw json] ***************************************************************************************************
changed: [localhost]

TASK [nice json] **************************************************************************************************
changed: [localhost]

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

特に整形せずに出力した raw.json は以下のようになります。データフォーマットとしては正しいですが、改行がないので見にくいですね。

{"redirected": false, "url": "https://dns.google.com/resolve?name=www.ansible.com&type=A", "status": 200, "strict_transport_security": "max-age=31536000; includeSubDomains; preload", "access_control_allow_origin": "*", "date": "Sat, 15 Feb 2020 08:48:47 GMT", "expires": "Sat, 15 Feb 2020 08:48:47 GMT", "cache_control": "private, max-age=119", "content_type": "application/x-javascript; charset=UTF-8", "server": "HTTP server (unknown)", "x_xss_protection": "0", "x_frame_options": "SAMEORIGIN", "alt_svc": "quic=\":443\"; ma=2592000; v=\"46,43\",h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000", "accept_ranges": "none", "vary": "Accept-Encoding", "connection": "close", "cookies_string": "", "cookies": {}, "msg": "OK (unknown bytes)", "elapsed": 0, "changed": false, "json": {"Status": 0, "TC": false, "RD": true, "RA": true, "AD": false, "CD": false, "Question": [{"name": "www.ansible.com.", "type": 1}], "Answer": [{"name": "www.ansible.com.", "type": 5, "TTL": 299, "data": "330046g46.secure0032.hubspot.net."}, {"name": "330046g46.secure0032.hubspot.net.", "type": 5, "TTL": 299, "data": "330046.group46.sites.hubspot.net."}, {"name": "330046.group46.sites.hubspot.net.", "type": 5, "TTL": 119, "data": "group46.sites.hscoscdn40.net."}, {"name": "group46.sites.hscoscdn40.net.", "type": 1, "TTL": 299, "data": "104.17.114.180"}, {"name": "group46.sites.hscoscdn40.net.", "type": 1, "TTL": 299, "data": "104.17.115.180"}, {"name": "group46.sites.hscoscdn40.net.", "type": 1, "TTL": 299, "data": "104.17.112.180"}, {"name": "group46.sites.hscoscdn40.net.", "type": 1, "TTL": 299, "data": "104.17.116.180"}, {"name": "group46.sites.hscoscdn40.net.", "type": 1, "TTL": 299, "data": "104.17.113.180"}], "Comment": "Response from 173.245.59.182."}, "failed": false}

一方、to_nice_json フィルターを適用した nice_json.json は以下のようになります。見やすいです。

{
    "accept_ranges": "none",
    "access_control_allow_origin": "*",
    "alt_svc": "quic=\":443\"; ma=2592000; v=\"46,43\",h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000",
    "cache_control": "private, max-age=119",
    "changed": false,
    "connection": "close",
    "content_type": "application/x-javascript; charset=UTF-8",
    "cookies": {},
    "cookies_string": "",
    "date": "Sat, 15 Feb 2020 08:48:47 GMT",
    "elapsed": 0,
    "expires": "Sat, 15 Feb 2020 08:48:47 GMT",
    "failed": false,
    "json": {
        "AD": false,
        "Answer": [
            {
                "TTL": 299,
                "data": "330046g46.secure0032.hubspot.net.",
                "name": "www.ansible.com.",
                "type": 5
            },
            {
                "TTL": 299,
                "data": "330046.group46.sites.hubspot.net.",
                "name": "330046g46.secure0032.hubspot.net.",
                "type": 5
            },
            {
                "TTL": 119,
                "data": "group46.sites.hscoscdn40.net.",
                "name": "330046.group46.sites.hubspot.net.",
                "type": 5
            },
            {
                "TTL": 299,
                "data": "104.17.114.180",
                "name": "group46.sites.hscoscdn40.net.",
                "type": 1
            },
            {
                "TTL": 299,
                "data": "104.17.115.180",
                "name": "group46.sites.hscoscdn40.net.",
                "type": 1
            },
            {
                "TTL": 299,
                "data": "104.17.112.180",
                "name": "group46.sites.hscoscdn40.net.",
                "type": 1
            },
            {
                "TTL": 299,
                "data": "104.17.116.180",
                "name": "group46.sites.hscoscdn40.net.",
                "type": 1
            },
            {
                "TTL": 299,
                "data": "104.17.113.180",
                "name": "group46.sites.hscoscdn40.net.",
                "type": 1
            }
        ],
        "CD": false,
        "Comment": "Response from 173.245.59.182.",
        "Question": [
            {
                "name": "www.ansible.com.",
                "type": 1
            }
        ],
        "RA": true,
        "RD": true,
        "Status": 0,
        "TC": false
    },
    "msg": "OK (unknown bytes)",
    "redirected": false,
    "server": "HTTP server (unknown)",
    "status": 200,
    "strict_transport_security": "max-age=31536000; includeSubDomains; preload",
    "url": "https://dns.google.com/resolve?name=www.ansible.com&type=A",
    "vary": "Accept-Encoding",
    "x_frame_options": "SAMEORIGIN",
    "x_xss_protection": "0"
}

応用編

to_nice_json フィルターはインデント数の指定もできます。デフォルトは4個です。

例えば、2個にする場合は、以下のように指定します。

        content: "{{ result | to_nice_json(indent=2) }}"

結果

{
  "accept_ranges": "none",
  "access_control_allow_origin": "*",
  "alt_svc": "quic=\":443\"; ma=2592000; v=\"46,43\",h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000",
  "cache_control": "private, max-age=119",
  "changed": false,
  // ...(略)...
}


さいごに

json 以外にも yaml 関連、その他様々なフィルターが用意されているので、たまに眺めてみると欲しかったフィルターが見つかるかもしれません。

docs.ansible.com

[Ansible] パターン表記したインベントリの確認は ansible-inventory コマンドが便利

はじめに

Ansible のインベントリファイルでは、効率よく定義するためにパターン表記ができます。

例えば、

web_[1:3]

web_1
web_2
web_3

と同じです。

この様にパターン表記を利用した場合、うまく解釈されるかどうか事前に確認したくなるのではないでしょうか。

ansible-inventory コマンドを利用すると確認できます。この記事では、簡単なサンプルをご紹介します。

  • 動作確認環境
    • Ansible 2.9.4

サンプル

インベントリの用意

いろいろなパターン表記を含めたインベントリファイルを用意します。

  • inventory.ini
[web]
web_[1:3]
web_[01:03]
10.0.0.[101:103]

[db]
db_[a:c]
db_[X:Z]

[general]
gen_[a:b][01:02]

ansible-inventory コマンドの実行

ansible-inventory コマンドの -i オプションでインベントリファイルを指定し、--list オプションでリスト表示を指定します。

$ ansible-inventory -i inventory.ini --list
{
    "_meta": {
        "hostvars": {}
    },
    "all": {
        "children": [
            "db",
            "general",
            "ungrouped",
            "web"
        ]
    },
    "db": {
        "hosts": [
            "db_X",
            "db_Y",
            "db_Z",
            "db_a",
            "db_b",
            "db_c"
        ]
    },
    "general": {
        "hosts": [
            "gen_a01",
            "gen_a02",
            "gen_b01",
            "gen_b02"
        ]
    },
    "web": {
        "hosts": [
            "10.0.0.101",
            "10.0.0.102",
            "10.0.0.103",
            "web_01",
            "web_02",
            "web_03",
            "web_1",
            "web_2",
            "web_3"
        ]
    }
}

色々なパターン表記が展開されたことが分かります。


さいごに

ansible-inventory コマンドは他にも、グループの親子関係を表示する --graph オプションがあったり、ダイナミックインベントリを指定する機能もあります。

Playbook を用意、実行する前に意図したインベントリかどうか確認するのにとても便利です。

参考

[Ansible] set_fact の利用を減らしたい時に考えること

はじめに

set_fact モジュールは、Playbook 内のタスクとして、新たな変数を定義できるモジュールです。

便利といえば便利なのですが、プログラムでいう変数の代入のようなことをタスクで実現させるには、少々大げさに感じることがあります。 set_fact モジュールにしかできない変数の定義方法もありますが、簡単な定義であれば他の方法でも定義できます。

この記事では、他の方法を検討するためのポイントをご紹介します。

前提 (set_fact を利用する場合)

以下のような Playbook をベースに考えます。set_fact モジュールで、test_var 変数を定義し、その値を debug モジュールで表示するという簡単なものです。

- hosts: localhost
  gather_facts: no

  tasks:
    - name: set_fact
      set_fact:
        test_var: hogehoge

    - name: debug test
      debug:
        msg: "{{ test_var }}"


■ Play レベルの vars でよいのではないか

set_fact モジュールではなく、Play レベルvars で定義する方法です。

- hosts: localhost
  gather_facts: no

  vars:
    test_var: hogehoge
    
  tasks:
    - name: debug test
      debug:
        msg: "{{ test_var }}"

タスクの数も減ります。Playbook の実行ログは以下のとおりです。

$ ansible-playbook -i localhsot, test_vars.yml 

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

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

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

ただし、set_fact モジュールのように、ループとは併用できません。


■ Task レベルの vars でよいのではないか

set_fact モジュールではなく、Task レベルvars で定義する方法です。

- hosts: localhost
  gather_facts: no

  tasks:
    - name: debug test
      debug:
        msg: "{{ test_var }}"
      vars:
        test_var: hogehoge

実行ログは、前述のPlay レベルの vars で定義した場合と同じです。

ただし、スコープはこのタスクの範囲なので、他のタスクへの使いまわしはできません。逆にスコープを閉じ込めたい場合はメリットになるかも知れません。

[Ansible] OSPF 設定後にネイバーが張られるまで待つ(until の活用)

はじめに

Ansible には、指定した条件を満たすまでタスクを繰り返す until というループ機能があります。

until を利用すると、ネットワーク機器に設定を投入した後に、期待した状態になるまで待つ、といった処理を Playbook で実現できます。

この記事では、Cisco IOS の機器に OSPF の設定をして、ネイバーが張られるまで待つ Playbook をご紹介します。

検証環境


■ 想定環境

以下のように、2 台の Cisco IOS のネットワーク機器の環境を想定します。 2台はお互いに GigabitEthernet2 で接続されています。インターフェースの IP アドレスは設定済みですが、OSPF はまだ設定していません。

f:id:akira6592:20200209113500p:plain
2台のCisco IOS でOSPFを設定する


■ 環境の準備

ネイバーが張られるまで待つには、show ip ospf neighbor コマンドの結果を確認します。コマンドの結果を正規表現などでマッチさせる方法では少々危ういので、結果をパースして確認することにします。

パースするために TextFSM というライブラリをインストールします。

  • TextFSM のインストール
pip intall textfsm

また、TextFSM ではパースするために各コマンドごとにテンプレート準備する必要があります。 ありがたいことに、networktocode/ntc-templates というリポジトリで、様々なベンダーのコマンドのテンプレートが作られています。今回は、Cisco IOSshow ip ospf neighbor コマンドの結果をパースしたいので、cisco_ios_show_ip_ospf_neighbor.textfsm を利用します。ダウンロードして templates ディレクトリに配置します。

(もちろんnetworktocode/ntc-templatesリポジトリをまるごとcloneなどしても構いません。今回は上記の構成を前提に説明します。)

ファイル構成

ファイルの構成は以下のようにします。

.
├── configure_ospf.yml # Playbook (後述)
├── group_vars
│   └── ios.yml   # 接続情報を定義(詳細省略)
├── inventory.ini # 2台をグループ ios で定義(詳細省略)
└── templates
    └── cisco_ios_show_ip_ospf_neighbor.textfsm # 先程ダウンロードしたもの


■ サンプル Playbook の作成

処理を定義する Playbook を以下の通り作成します(説明は後述)。

- hosts: ios
  gather_facts: no

  tasks:
    - name: 1. configure OSPF
      ios_config:
        lines:
          - network 10.0.1.0 0.0.0.255 area 0
        parents:
          - router ospf 1

    - name: 2. check neighbor
      ios_command:
        commands:
          - show ip ospf neighbor
      register: result
      vars:
        parsed: "{{ result.stdout[0] | parse_cli_textfsm('./templates/cisco_ios_show_ip_ospf_neighbor.textfsm') }}" 
      until: "'FULL' in (parsed | selectattr('INTERFACE', '==', 'GigabitEthernet2') | list | first ).STATE"
      delay: 10
      retries: 6

    - name: 3. debug status
      debug:
        msg: "{{ result.stdout_lines[0] }}"

以下、各タスクについて説明します。

1. configure OSPF

ios_config モジュールを利用して、OSPF のコンフィグを投入します。

具体的には、parentslines オプションの指定により以下のコンフィグを投入します。

router ospf 1
  network 10.0.1.0 0.0.0.255 area 0

2. check neighbor

このタスクが一番のポイントです。ネイバーが張られるまで待ちます。少し複雑ですが、以下の流れで処理します。

(1) show コマンドの実行

ios_command モジュールを利用して、show ip ospf neighbor コマンドを実行し、結果を変数 result に格納します。 この時点での result.stdout[0] の内容は以下の通りです。(ios1 側の例)

Neighbor ID     Pri   State           Dead Time   Address         Interface
2.2.2.2           1   2WAY/DROTHER    00:00:39    10.0.1.2        GigabitEthernet2

(2) コマンド実行結果のパース

vars ディレクティブで、コマンド結果変数 result をパースして、変数 parsed に格納する。パースは、内部で TextFSM を呼び出す parse_cli_textfsm フィルター を利用します。対応するパーステンプレートファイルは、「環境の準備」でダウンロードしたものを指定します。

この時点での parsed の内容は以下の通りです。(ios1 側の例)

[
 {
     "ADDRESS": "10.0.1.2",
     "DEAD_TIME": "00:00:38",
     "INTERFACE": "GigabitEthernet2",
     "NEIGHBOR_ID": "2.2.2.2",
     "PRIORITY": "1",
     "STATE": "2WAY/DROTHER"
  }
]

なお、このタスクの vars ディレクティブで定義した変数 parsed のスコープは、あくまでこのタスクの範囲なのでご注意ください。

参考:

ネットワーク機器のコマンド結果をパースする parse_cli_textfsm フィルタープラグインを試す (Ansible 2.4新機能) - てくなべ (tekunabe)

(3) 繰り返し条件

until で、このタスクの終了条件を指定します。ここでは、パースしたコマンド実行結果である parsed 内の STATEFULL という文字列が含まれていたら終了とします。

今回の場合はネイバーは1つだけなので、以下のように「parsed の最初の要素の STATE」という指定方法でも構いません。

      until: "'FULL' in (parsed.0.STATE)"

ですが、拡張性を持たせるため、INTERFACEGigabitEthernet2 であるという条件にして、対象のネイバーを抽出します。

      until: "'FULL' in (parsed | selectattr('INTERFACE', '==', 'GigabitEthernet2') | list | first ).STATE"

until の動作を決めるリトライ間隔は delay 、リトライ回数を retries で指定します。終了条件を満たすまで(1)に戻って繰り返しこのタスクを実行します。一定回数繰り返しているうちにネイバーが張られ、STATEFULL/DRFULL/BDR などに変わります。そして、ループを終了し、次のタスクに移ります。

3. debug status

デバッグとして、show ip ospf neighbor コマンドの実行結果を表示します。

Playbook の各タスクの説明は以上です。次は Playbook の実行です。


■ Playbook 実行

Playbook を実行します。

$ ansible-playbook -i inventory.ini configure_ospf.yml 
PLAY [ios] *******************************************************************************************************

TASK [1. configure OSPF] *****************************************************************************************
changed: [ios1]
changed: [ios2]

TASK [2. check neighbor] *****************************************************************************************
FAILED - RETRYING: 2. check neighbor (6 retries left).  # 10秒ごとに繰り返しチェック中
FAILED - RETRYING: 2. check neighbor (6 retries left).
FAILED - RETRYING: 2. check neighbor (5 retries left).
FAILED - RETRYING: 2. check neighbor (5 retries left).
FAILED - RETRYING: 2. check neighbor (4 retries left).
FAILED - RETRYING: 2. check neighbor (4 retries left).
FAILED - RETRYING: 2. check neighbor (3 retries left).
FAILED - RETRYING: 2. check neighbor (3 retries left).
ok: [ios1]    # ネイバーが張れた
ok: [ios2]    # ネイバーが張れた

TASK [3. debug status] *******************************************************************************************
ok: [ios2] => {
    "msg": [
        "Neighbor ID     Pri   State           Dead Time   Address         Interface",
        "1.1.1.1           1   FULL/BDR        00:00:38    10.0.1.1        GigabitEthernet2"
    ]
}
ok: [ios1] => {
    "msg": [
        "Neighbor ID     Pri   State           Dead Time   Address         Interface",
        "2.2.2.2           1   FULL/DR         00:00:37    10.0.1.2        GigabitEthernet2"
    ]
}

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

ネイバーが張られるまでタスク 2. check neighbor を繰り返し実行し、張られたら次のたタスク3. debug status に進んだことが分かります。

ネイバーがはれないと・・

何らかの問題があり、OSPF のネイバーが張れないと、タスク 2. check neighbor がエラーになって終了します。


■ さいごに

この記事では、Cisco IOS の機器に OSPF の設定をして、ネイバーが張られるまで待つ Playbook をご紹介しました。

このように、show コマンドを実効するモジュールと、until を組み合わせると、期待した状態まで待つ、といったことが Playbook で実現できます。

ネットワーク機器への作業は、OSPF の他にも設定してから待つ必要がある作業もあるので、until は有効ではないでしょうか。

[Ansible] 変数名を変数で指定する

はじめに

vars lookup pluginは、変数名を引数にして変数の値を取得できます。

この性質を利用すると、変数名を変数で指定して、その値を取得できます。

簡単な例でご紹介します。

サンプル Playbook

値を取得したい変数名を、var_name 変数の値で指定します。(以下の場合 var1

1つめのタスクでは、単純に var1 の値を表示します。 2つめのタスクでは、loop で変数名の一部を可変にして、var1, var2, var3 の値を表示します。

- hosts: localhost
  gather_facts: no

  vars:
    var_name: var1  # 変数名を指定する変数
    var1: value1    # 取得対象の変数その1
    var2: value2    # 取得対象の変数その2
    var3: value3    # 取得対象の変数その3
    
  tasks:
    - name: lookup plugin
      debug:
        msg: "{{ lookup('vars', var_name) }}"   # 変数 var1 の指定と等価

    - name: using loop
      debug:
        msg: "{{ lookup('vars', 'var' + (item | string)) }}" # ループを利用して変数 var1, var2, var3 を指定 
      loop:
        - 1
        - 2
        - 3

Playbook 実行

Playbook を実行します。

$ ansible-playbook -i localhost, var.yml 

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

TASK [lookup plugin] **********************************************************************************************
ok: [localhost] => {
    "msg": "value1"
}

TASK [using loop] *************************************************************************************************
ok: [localhost] => (item=1) => {
    "msg": "value1"
}
ok: [localhost] => (item=2) => {
    "msg": "value2"
}
ok: [localhost] => (item=3) => {
    "msg": "value3"
}

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

参考

When should I use {{ }}? Also, how to interpolate variables or dynamic variable names

docs.ansible.com

別解

How do I access a variable of the first host in a group?

docs.ansible.com