てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] 「ポート管理表+パラメータ表+Jinja2テンプレートから、L2SWのConfigを自動生成してみた」を Ansible で

■ はじめに

以下の記事(以下、元記事)を拝見し、Ansible でもできるかなと思って試してみました。

qiita.com

開発中の Ansible 2.8 で導入予定の read_csv モジュールを利用しています。現在(2019/04/02)安定版の Ansible 2.7 系では利用できませんのでご注意ください。read_csv モジュール を利用しない方法もできると思いますが、少し複雑になるでしょう。

  • Ansible devel バージョン (2019/04/02時点)


■ 用意するもの

ポート管理表

元記事と同じものです。

  • port_list_hqaccess1.csv
port_no,speed,duplex,mode,vlan,portfast,status,description
FastEthernet0/1,auto,auto,access,100,o,x,To PC1
FastEthernet0/2,auto,auto,access,100,o,x,To PC2
FastEthernet0/3,auto,auto,access,100,o,x,
FastEthernet0/4,auto,auto,access,100,o,x,
FastEthernet0/5,auto,auto,access,100,o,x,To PC3
FastEthernet0/6,auto,auto,access,100,o,x,To PC4
FastEthernet0/7,auto,auto,access,100,o,x,
FastEthernet0/8,auto,auto,access,100,o,x,
GigabitEthernet0/1,1000,full,access,100,o,x,To hqdist1 Gi0/0/1
GigabitEthernet0/1,1000,full,access,100,o,x,To hqdist2 Gi0/0/1

パラメータ表

元記事と同じものです。

  • parameter_list_hqaccess1.csv
hostname,hardware,secret,username,password,vlan_num,vlan_desc,ip_address,subnet,default_gw,ntp_server
hqdist1,catalyst2960,test,test,cisco,100,<< Server Segment >>,192.168.100.47,255.255.255.0,192.168.100.150,192.168.100.44

Jinja2

ほぼ、元記事と同じものです。

Ansibleで試すと改行が入ってほしいところに入らなかったので、少し調整しました。

  • catalyst2960_template.j2

クリックして展開する

!
no service pad
service timestamps debug datetime localtime
service timestamps log datetime localtime
service password-encryption
!
hostname {{ hostname }}
!
no logging console
enable secret {{ secret }}
!
username {{ username }} privilege 15 password {{ password }}
clock timezone JST 9
ip subnet-zero
no ip domain-lookup
ip domain-name {{ hostname }}
ip ssh version 2
!
spanning-tree mode pvst
no spanning-tree optimize bpdu transmission
spanning-tree extend system-id
!
!
{% for item in interfaces %}
interface {{ item.port_no }}
{% if item.description != '' %}
 description << {{ item.description }} >>
{% endif %}
{% if item.mode == 'access' %}
 switchport access {{ item.vlan }}
 switchport mode access
{% elif item.mode == 'trunk' %}
 switchport mode trunk
{% endif %}
{% if item.duplex != 'auto' %}
 duplex {{ item.duplex }}
{% endif %}
{% if item.speed != 'auto' %}
 speed {{ item.speed }}
{% endif %}
{% if item.status == 'x' %}
 shutdown
{% endif %}
{% if item.portfast == 'o' %}
 spanning-tree portfast
{% endif %}
!
{% endfor %}
!
interface Vlan1
 no ip address
 no ip route-cache
 shutdown
!
interface Vlan{{ vlan_num }}
 description {{ vlan_desc }}
 ip address {{ ip_address }} {{ subnet }}
 no ip route-cache
!
ip default-gateway {{ default_gw }}
no ip http server
no ip http secure-server
!
logging 192.168.100.107
snmp-server community C1sc0 RO
snmp-server host 192.168.100.107 C1sc0 
banner login ^C
============NOTICE==============
| This is test device for demo |
================================
^C
!
line con 0
line vty 0 4
 login local
line vty 5 15
 login local
!
ntp server {{ ntp_server }}
!
crypto key generate rsa modulus 2048
!
end

Playbook

元記事の config_generation.py に相当する Playbook です。

name: adjust..(略)... の2つのタスクは、元記事の Jinja2 テンプレートの変数構造に合わせるための調整です。テンプレート側を変更すれば、これらのタスクは不要になります。特に、adjust parameter variables のほうは、やや乱暴な調整をしているので、テンプレート側で調整したほうが良いかもしれません。

また、今回の要件では、Ansible を実行しているホストのローカルで動けばよいので、対象ホストは localhost、コネクション方式は local を指定しています。

  • build_template.yml
- hosts: localhost
  gather_facts: no
  connection: local
  
  vars:
    TEMPLATE: ./catalyst2960_template.j2
    PARAMETER_LIST: ./parameter_list_hqaccess1.csv
    PORT_LIST: ./port_list_hqaccess1.csv
    CONFIG_FILENAME: ./config_hqaccess1.txt
  
  tasks:
    - name: read interfaces
      read_csv:
        path: "{{ PORT_LIST }}"
      register: interfaces_temp
    
    - name: read params
      read_csv:
        path: "{{ PARAMETER_LIST }}"
      register: params_temp

    - name: adjust interface variable
      set_fact:
        interfaces: "{{ interfaces_temp.list }}"

    - name: adjust parameter variables
      set_fact:
        "{{ item.key }}": "{{ item.value }}"
      loop: "{{ lookup('dict', params_temp.list[0]) }}"

    - name: build template
      template:
        src: "{{ TEMPLATE }}"
        dest: "{{ CONFIG_FILENAME }}"


■ 実行

Playbookを実行します。インベントリファイルは用意せず、-i オプションでは -i localhost, のよううに host_list の形式で指定しています。

$ ansible-playbook -i localhost, build_template.yml

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

TASK [read  interfaces] ***********************************************************************************************************
ok: [localhost]

TASK [read params] ***********************************************************************************************************
ok: [localhost]

TASK [adjust interface variable] ***************************************************************************************
ok: [localhost]

TASK [adjust parameter variables] **************************************************************************************
ok: [localhost] => (item={'key': u'username', 'value': u'test'})
ok: [localhost] => (item={'key': u'subnet', 'value': u'255.255.255.0'})
ok: [localhost] => (item={'key': u'hostname', 'value': u'hqdist1'})
ok: [localhost] => (item={'key': u'secret', 'value': u'test'})
ok: [localhost] => (item={'key': u'default_gw', 'value': u'192.168.100.150'})
ok: [localhost] => (item={'key': u'hardware', 'value': u'catalyst2960'})
ok: [localhost] => (item={'key': u'vlan_num', 'value': u'100'})
ok: [localhost] => (item={'key': u'vlan_desc', 'value': u'<< Server Segment >>'})
ok: [localhost] => (item={'key': u'ntp_server', 'value': u'192.168.100.44'})
ok: [localhost] => (item={'key': u'password', 'value': u'cisco'})
ok: [localhost] => (item={'key': u'ip_address', 'value': u'192.168.100.47'})

TASK [build template] **************************************************************************************************
changed: [localhost]

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

できあがったコンフィグファイルです。

クリックして展開する

!
no service pad
service timestamps debug datetime localtime
service timestamps log datetime localtime
service password-encryption
!
hostname hqdist1
!
no logging console
enable secret test
!
username test privilege 15 password cisco
clock timezone JST 9
ip subnet-zero
no ip domain-lookup
ip domain-name hqdist1
ip ssh version 2
!
spanning-tree mode pvst
no spanning-tree optimize bpdu transmission
spanning-tree extend system-id
!
!
interface FastEthernet0/1
 description << To PC1 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/2
 description << To PC2 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/3
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/4
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/5
 description << To PC3 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/6
 description << To PC4 >>
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/7
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface FastEthernet0/8
 switchport access 100
 switchport mode access
 shutdown
 spanning-tree portfast
!
interface GigabitEthernet0/1
 description << To hqdist1 Gi0/0/1 >>
 switchport access 100
 switchport mode access
 duplex full
 speed 1000
 shutdown
 spanning-tree portfast
!
interface GigabitEthernet0/1
 description << To hqdist2 Gi0/0/1 >>
 switchport access 100
 switchport mode access
 duplex full
 speed 1000
 shutdown
 spanning-tree portfast
!
!
interface Vlan1
 no ip address
 no ip route-cache
 shutdown
!
interface Vlan100
 description << Server Segment >>
 ip address 192.168.100.47 255.255.255.0
 no ip route-cache
!
ip default-gateway 192.168.100.150
no ip http server
no ip http secure-server
!
logging 192.168.100.107
snmp-server community C1sc0 RO
snmp-server host 192.168.100.107 C1sc0 
banner login ^C
============NOTICE==============
| This is test device for demo |
================================
^C
!
line con 0
line vty 0 4
 login local
line vty 5 15
 login local
!
ntp server 192.168.100.44
!
crypto key generate rsa modulus 2048
!
end


■ まとめ

Ansible と Jinja2 を利用して、パラメータとなる CSV からコンフィグを生成させることができました。 Ansible が備える変数管理の仕組みを活用すれば、もっと応用ができるようになると思います。

今回のように、Jinja2 内の制御文があまり複雑でない場合や、他の処理を組み合わせなくて良い場合は Ansible でもよさそうです。一方で、もっと準備に複雑な処理を必要とする場合は、元記事のように Python などで書くのが良いと思います。

[Ansible] read_csv モジュールの基本的な使い方(CSVファイルをリストやディクショナリとして読み込む)

■ はじめに

2019年5月リリース予定の Ansible 2.8 では、CSV ファイルをリストやディクショナリとして読み込む read_csv モジュール が導入されます。カンマ区切りのファイルだけでなく、別のデリミタ(;など)を指定することもできます。

この記事では、 read_csv モジュールの公式ドキュメントに記載されている使用例をベースにして、使い方を説明します。

なお、公式ドキュメントの使用例は、Playbook 単位ではなくtask 単位で記載されています。この記事では Playbook 単位で例示します。

似たものとして、csvfile lookup プラグインがあります。csvfile lookup プラグインが「ローカルのCSVファイル」の「指定条件の値を取得」するのに対して、read_csv モジュールは、「リモート(またはローカル)のCSVファイル」の「全体をリストまたはディクショナリとして取得」します。

動作確認環境

目次


■ 単純にCSVファイルを読み込んでリストにする

CSV ファイル

以下のヘッダー付き CSV ファイルを利用します。

name,uid,gid
dag,500,500
jeroen,501,500

Playbook

単純にCSVファイルを読み込んでリストにするシンプルな Playbook です。

- hosts: testsv
  gather_facts: no

  tasks:
    - name: Read users from CSV file and return a list
      read_csv:
        path: users.csv
      register: users
      delegate_to: localhost  # ここを省略するとリモートのファイルを参照

    - name: debug csv
      debug:
        msg: "{{ users }}"
  • read_csv タスク
    • path オプション
      • 読み込む CSV ファイルのパスを指定します。(必須)
    • delegate_to ディレクティブ
      • delegate_to: localhost という指定で、リモートではなくローカル(ansible-playbook コマンド自体を実行しているマシン)上の path を参照します。省略すると、リモートの path を参照します。
  • debug タスク
    • msg オプション
      • リストとして取得した CSV ファイルの内容を単純に全部出力します。

実行ログ

Playbook を実行します。

$  ansible-playbook -i ../inventory csv_simple.yml 

PLAY [testsv] *******************************************************************************************

TASK [Read users from CSV file and return a list] *******************************************************
ok: [testsv]

TASK [debug csv] ****************************************************************************************
ok: [testsv] => {
    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        }, 
        "changed": false, 
        "dict": {}, 
        "failed": false, 
        "list": [
            {
                "gid": "500", 
                "name": "dag", 
                "uid": "500"
            }, 
            {
                "gid": "500", 
                "name": "jeroen", 
                "uid": "501"
            }
        ]
    }
}

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

debug タスクの出力により、CSV ファイルの内容がリストとして、users.list に格納されていることが分かります。各リスト内の要素は CSV ファイルの1行目のヘッダー gid,name,uid が、キーとなるディクショナリの構造になります。

特定の値を取得する場合、例えば、1番目(0オリジン)の name であれば、"{{ users.list.1.name }}" と指定すすると、jeroen という値を取得できます。

他、SQL の where 句のような条件で抽出するには selectattrを使用します。例えば、gid500 以上の name のリストであれば、

"{{ `users.list | selectattr('gid', '==', '500') | map(attribute='name') | list }}"

と指定すると ["dag", "jeroen"]という値を取得できます。


CSV ファイルを読み込んでディクショナリにする

CSV ファイル

先ほどと同じく、以下のヘッダー付き CSV ファイルを利用します。

name,uid,gid
dag,500,500
jeroen,501,500

Playbook

CSV ファイルを読み込んでディクショナリにする Playbook です。

- hosts: testsv
  gather_facts: no

  tasks:
    - name: Read users from CSV file and return a list
      read_csv:
        path: users.csv
        key: name
      register: users
      delegate_to: localhost  # ここを省略するとリモートのファイルを参照

    - name: debug csv
      debug:
        msg: "{{ users }}"
  • read_csv タスク
    • key オプション
      • ディクショナリとして取得する際のキーを指定します。
  • debug タスク
    • msg オプション
      • ディクショナリとして取得した CSV ファイルの内容を単純に全部出力します。

実行ログ

Playbook を実行します。

$  ansible-playbook -i ../inventory csv_dict.yml 

PLAY [testsv] *******************************************************************************************

TASK [Read users from CSV file and return a list] *******************************************************
ok: [testsv]

TASK [debug csv] ****************************************************************************************
ok: [testsv] => {
    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        }, 
        "changed": false, 
        "dict": {
            "dag": {
                "gid": "500", 
                "name": "dag", 
                "uid": "500"
            }, 
            "jeroen": {
                "gid": "500", 
                "name": "jeroen", 
                "uid": "501"
            }
        }, 
        "failed": false, 
        "list": []
    }
}

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

debug タスクの出力により、CSV ファイルの内容がディクショナリとして、users.dict に格納されていることが分かります。key オプションで指定した name にあたる値がキーとなる構造になります。

特定の値を取得する場合、例えば namedaguid の場合、"{{ users.dict.dag.uid }}" と指定すると、500 という値を取得できます。


■ ヘッダーのないセミコロン区切りのファイルを読み込んでリストにする

カンマでなく、別のデリミタの場合です。

ファイル

以下のヘッダーのないセミコロン区切りのファイルを利用します。

dag;500;500
jeroen;501;500

Playbook

ヘッダーのないセミコロン区切りのファイルを読み込んでリストにする Playbook です。

- hosts: testsv
  gather_facts: no

  tasks:
    - name: Read users from CSV file and return a list
      read_csv:
        path: users_wo_headers.csv
        fieldnames: name,uid,gid
        delimiter: ';'
      register: users
      delegate_to: localhost  # ここを省略するとリモートのファイルを参照

    - name: debug csv
      debug:
        msg: "{{ users }}"
  • read_csv タスク
    • fieldnames オプション
      • ヘッダーのないファイルの場合にそれぞれのカラムにつけヘッダー名です。
    • delimiter オプション
      • 区切り文字となるデリミタを指定します。今回の読み込みファイルは ; 区切りなので、delimiter: ';' を指定します。
  • debug タスク
    • msg オプション
      • リストとして取得したファイルの内容を単純に全部出力します。

実行ログ

Playbook を実行します。

$  ansible-playbook -i ../inventory csv_3.yml 

PLAY [testsv] *******************************************************************************************

TASK [Read users from CSV file and return a list] *******************************************************
ok: [testsv]

TASK [debug csv] ****************************************************************************************
ok: [testsv] => {
    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        }, 
        "changed": false, 
        "dict": {}, 
        "failed": false, 
        "list": [
            {
                "gid": "500", 
                "name": "dag", 
                "uid": "500"
            }, 
            {
                "gid": "500", 
                "name": "jeroen", 
                "uid": "501"
            }
        ]
    }
}

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


debug タスクの出力により、ヘッダーのない ; 区切りのファイルの内容がリストとして、users.list に格納されていることが分かります。各リスト内の要素は fieldnames オプションで指定した、gid,name,uid が、キーとなるディクショナリの構造になります。


■ まとめ

公式ドキュメントの使用例をベースにしてread_csv モジュール の使い方を説明しました。

他に「こんなことできるかな?」と気になる事がありましたら、公式ドキュメントで詳細をご確認ください。

docs.ansible.com

また、read_csv モジュールは Files modules に分類されています。Files modules には、他にも、指定した正規表現にマッチするすべての文字列を置換する replace や、行ではなくブロック単位で編集する blockinfile などのモジュールがあります。詳細は Files modules の一覧からご確認ください。

[Ansible] ファイルや変数内の改行ごとに分割してリストにする splitlines() の使い方

■ はじめに

ファイルの内容や変数に改行が含まれている場合、1行ごとにリストにしてループなどの処理をしたい場合があります。そんなときに利用できるのが、 splitlines() です。split() が引数で指定した文字で分割するのに対して、splitlines() は改行で分割します。

Python をご存知の方であれば、str.splitlines()をイメージしていただけるとわかりやすいと思います。

この記事では簡単な例をもとに使い方をご紹介します。

  • 動作確認環境: Ansible 2.3.0, 2.7.8


準備

読み込む利用するテキストファイル

今回は、以下のテキストファイルを利用します。改行コードは LF です。CR/LFでも問題ありません。

  • local.txt
111
222
333

Playbook

以下のような Playbook を利用します。file lookup pluginで、ローカル(リモートではなく)のファイルをの内容を取得し、splitlines() で改行ごとのリストに変換しています。そのリストでループして、1行ごとに debug モジュールで標準出力させています。

- hosts: testsv
  gather_facts: no

  tasks:
    - name: local file debug
      debug: 
        msg: "{{ item }}"
      with_list: "{{ lookup('file', '/vagrant/local.txt').splitlines() }}"
  • 応用 Tips
    • ローカルではなく、リモートのテキストファイルの内容を利用したい場合は slurp モジュールを利用してください。
    • テキストファイルの内容ではなく、変数の値を利用したい場合は with_list: "{{ myvars.splitlines() }}" などに読み替えてください。
    • Ansible 2.5 以降では、with_list の代わりに loop キーワードも利用できます。

実行結果

それでは Playbook を実行します。

$ ansible-playbook -i inventory splitlinestest.yml 

PLAY [testsv] ******************************************************************

TASK [local file debug] ********************************************************
ok: [testsv] => (item=111) => {
    "msg": "111"
}
ok: [testsv] => (item=222) => {
    "msg": "222"
}
ok: [testsv] => (item=333) => {
    "msg": "333"
}

PLAY RECAP *********************************************************************
testsv                     : ok=1    changed=0    unreachable=0    failed=0 

無事に local.txt の1行ごとのループができました。

■ まとめ

splitlines() で、改行ごとに分割してリストにできることを確認しました。

[Ansible] グループAとBの両方に所属しているホストを対象にする方法

■ はじめに

Ansible では、管理対象のホストをグループ化してインベントリファイルに定義できます。Playbook 内にグループを指定すると、指定したグループに所属するホストのみが対象になります。

では「グループAとBに両方に所属しているホスト」を対象にしたい場合はどのようにしたらよいでしょうか。 例えば以下のようなインベントリファイルがある場合、host02host03 を対象にしたい、というケースです。

[group_a]
host01
host02
host03

[group_b]
host02
host03
host04

この記事では、両方に所属しているホストを対象にする2つの方法を、簡単な例とともにご紹介します。

  • 動作確認環境: Ansible 2.3.0, 2.7.9


■ 方法1: play の hosts ディレクティブにグループを and 条件で指定する

Pkabbook内(のplay)の hosts ディレクティブには、複数の対象を and 条件で指定できます。この機能を利用する方法です。

参考: https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html

  • playbook
- hosts: group_a:&group_b
  gather_facts: no

  tasks:
    - name: debug test
      debug:
        msg: "I am {{ inventory_hostname }}."

上記のように group_a:&group_b と指定することで、group_a に所属、かつ group_b にも所属、という指定になります。

  • 実行例
$ ansible-playbook -i inventory test.yml

PLAY [group_a:&group_b] ****************************************************************

TASK [debug test] **********************************************************************
ok: [host02] => {
    "msg": "I am host02."
}
ok: [host03] => {
    "msg": "I am host03."
}

PLAY RECAP ******************************************************************************
host02                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0
host03                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0

両グループに所属している、host02host03 が対象となりました。

なお、--list-hosts オプションを付けると実処理をせずに対象ホストを確認できます。

$ ansible-playbook -i inventory test.yml --list-hosts

playbook: test.yml

  play #1 (group_a:&group_b): group_a:&group_b  TAGS: []
    pattern: [u'group_a:&group_b']
    hosts (2):
      host02
      host03


■ 方法2: -l オプションと組み合わせる

ansible-playbook コマンドには、対象ホストを絞り込む -l または --limit オプションがあります。この機能と、hosts ディレクティブを併用する方法です。

参考: https://docs.ansible.com/ansible/latest/cli/ansible-playbook.html#cmdoption-ansible-playbook-l

  • playbook
- hosts: group_a
  gather_facts: no

  tasks:
    - name: debug test
      debug:
        msg: "I am {{ inventory_hostname }}."

Playbook としては group_a を対象としています。

  • 実行例
$ ansible-playbook -i inventory test.yml -l group_b

PLAY [group_a] ***************************************************************************

TASK [debug test] ************************************************************************
ok: [host02] => {
    "msg": "I am host02."
}
ok: [host03] => {
    "msg": "I am host03."
}

PLAY RECAP *******************************************************************************
host02                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0
host03                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0

-l group_b で絞ることによって、結果的に両グループに所属している host02host03 が対象となりました。

--list-hosts オプションでも確認してみます。

$ ansible-playbook -i inventory test.yml -l group_b --list-hosts

playbook: test.yml

  play #1 (group_a): group_a    TAGS: []
    pattern: [u'group_a']
    hosts (2):
      host02
      host03


■ 方法3: when で group_names で判断する [2019/10/16 追記]

Play 単位ではなく、when が効く ロールや block 、タスクなどで使える方法です。

- hosts: all
  roles:
    - role: test
      when: "'group_a' in group_names and 'group_b' in group_names"
  tasks:
    - debug:
msg: In group_a and group_b
      when: "'group_a' in group_names and 'group_b' in group_names"

@satoh_fumiyasu さんにアイディアいただきました。ありがとうございます。


■ まとめ

複数のグループに所属しているホストを対象にする方法を2つご紹介しました。 ただ、インベントリとして新たにグループを定義したほうが、メンテナンス性が良いケースもあるかもしれません。必要に応じて使い分けるのが良いと思います。

[Ansible] Playbook(YAML)のための vim のインデント関連設定

■ はじめに

Ansible の Playbook を書いて試すときは、普段 VSCode を使っているため、あまり vim は使っていません。ですが、vim しか使えない環境に置かれたときのために vim で Playbook (YAML) を書くときに便利そうな設定を調べました。

f:id:akira6592:20190321135658g:plain
インデント関連設定をした vim で Playbook をかく

せっかくですのでまとめておきます。


■ .vimrc の中身

今のところ、 .vimrc の中身は以下のようにしています。

set expandtab
set softtabstop=2
set shiftwidth=2
set autoindent

それぞれの設定について簡単に説明します。


set expandtab: Tab キーを押したときに Tab の代わりにスペースを入力する

省略系: set et

通常、Tab キーを押すと tab が入力されます。YAML のインデントには tab ではなくスペースを利用します。 そこで、set expandtab で、Tab キーを押したときに tab の代わりにスペースを入力されうように設定します。


set softtabstop=2: softtab(スペース)の幅をスペース2個にする

省略形: set sts

前述の set expandtab で有効にした Tab キーによるスペース入力の、スペースの数を指定します。 Playbook (YAML)としては、スペースが2個でも4個でも階層的に矛盾がなければ構わないのですが、私はいつもスペース2個なので set softtabstop=2 を設定します。


set shiftwidth=2: インデントレベルの変更時のスペースを2個にする

省略形: set sw=2

>><< などによるインデントレベルの変更時に使うスペースの数です。softtabstop の数と合わせて、set shiftwidth=2を設定します。

f:id:akira6592:20190321135112g:plain
インデントレベルの調整


set autoindent: 自動でインデントする

省略系: set ai

インデントされた状態の行をにカーソルがある状態で改行したときに、次の行で同じインデント位置を保つようにする設定です。

f:id:akira6592:20190321135042g:plain
自動インデント

この設定は少し注意が必要です。手入力時は便利ですが、すでにインデントされたテキストをコピペすると余計なインデントが入ってしまいます。

例えば以下のインデントされたテキストの場合。

- hosts: eos
  gather_facts: no

  tasks:
    - name: test
      eos_command:
        commands:
          - show running-config
      register: result

ペーストすると以下のように余計なインデントが入ってしまいます。これは正しいPlyabookではありません。

- hosts: eos
  gather_facts: no

    tasks:
        - name: test
              eos_command:
                      commands:
                                - show running-config
                                      register: result                                                             

対策は以下のとおりです。(ほかにもあるかもしれません。)

対策1. 自動インデントが不要なときだけ無効(set noautoindent / set noai)に設定する。 対策3. set paste でペーストモードにしてからペーストする。 対策3. そもそも普段は自動インデント無効にしておき、必要な時だけ有効(set autoindent / set ni)に設定する


■ まとめ

vim で Playbook (YAML)を書くときに便利そうな設定をまとめました。YAML では、インデントの数が情報構造に強く関わっているので、特にインデント周りの設定を好みに設定しておくと良いと思いました。

参考

参考にさせていただきました。ありがとうございます。 hatakazu.hatenablog.com

Ansible 公式ドキュメントでは、Ansible vim というプラグインが紹介されています。

docs.ansible.com

[Ansible] オフライン状態でモジュールの詳細をなど調べる方法(ansible-doc/ansible-config)

■ はじめに

Playbook を書いていく過程で、Ansible の公式ドキュメントを閲覧したり、インターネットで検索したりして、調べることが多いと思います。

しかし、もしインターネットにつながっていない状態で調べことをしたい時はどのようにしたらよいでしょうか。

この記事では、オフライン状態(ここではインターネット未接続状態のこと指します)で、Ansible について調べる方法をいくつかご紹介します。対応する公式ドキュメントのページもあわせてご紹介します。

動作確認環境: Ansible 2.7.8


■ モジュール

モジュールの詳細情報を知りたい

モジュール名は分かっているけれど、オプションの名前やサンプルを確認したい時に調べる方法です。

ansible-doc [モジュール名]
  • 実行例: ios_config モジュールの詳細を表示する
$ ansible-doc ios_config

 IOS_CONFIG    (/home/vagrant/ansible2780/lib/python2.7/site-packages/ansible/modules/network/ios/ios_confi

        Cisco IOS configurations use a simple block indent file syntax for segmenting
        configuration into sections.  This module provides an implementation for
        working with IOS configuration sections in a deterministic way.

(...略...)

EXAMPLES:

- name: configure top level configuration
  ios_config:
    lines: hostname {{ inventory_hostname }}

- name: configure interface settings
  ios_config:
    lines:
      - description test interface
      - ip address 172.31.1.1 255.255.255.0
    parents: interface Ethernet1

(...略...)

公式ドキュメントでは、こちらの ios_config モジュールのページを確認します。

モジュール名の一部からモジュール名を特定したい

モジュール名の一部しか分かっていないけれど、どのモジュールかを特定したい時に調べる方法です。

ansible-doc -l | grep [正規表現]
  • 実行例: ios_ から始まるモジュール名の一覧を表示する
$ ansible-doc -l | grep ^ios_
ios_banner                                           Manage multiline banners on Cisco IOS devices
ios_command                                          Run commands on remote devices running Cisco IOS
ios_config                                           Manage Cisco IOS configuration sections
ios_facts                                            Collect facts from remote devices running Cisco IO...
ios_interface                                        Manage Interface on Cisco IOS network devices
ios_l2_interface                                     Manage Layer-2 interface on Cisco IOS devices.
ios_l3_interface                                     Manage Layer-3 interfaces on Cisco IOS network dev...
ios_linkagg                                          Manage link aggregation groups on Cisco IOS networ...
ios_lldp                                             Manage LLDP configuration on Cisco IOS network dev...
ios_logging                                          Manage logging on network devices
ios_ping                                             Tests reachability using ping from Cisco IOS netwo...
ios_static_route                                     Manage static IP routes on Cisco IOS network devic...
ios_system                                           Manage the system attributes on Cisco IOS devices
ios_user                                             Manage the aggregate of local users on Cisco IOS d...
ios_vlan                                             Manage VLANs on IOS network devices
ios_vrf                                              Manage the collection of VRF definitions on Cisco ...

公式ドキュメントでは、モジュールリストから検索します。

ansible-doc -l のみの場合、モジュールの一覧を表示


■ コマンド

オプションを知りたい

あれするときのオプションは何だったかな?という時に調べる方法です。

$ [特にオプションを付けずにコマンドを実行、または -h オプション]
  • 実行例: ansible-playbook コマンドのヘルプを表示する
$ ansible-playbook -h
Usage: ansible-playbook [options] playbook.yml [playbook2 ...]

Runs Ansible playbooks, executing the defined tasks on the targeted hosts.

Options:
  --ask-vault-pass      ask for vault password
  -C, --check           don't make any changes; instead, try to predict some
                        of the changes that may occur
  -D, --diff            when changing (small) files and templates, show the
                        differences in those files; works great with --check
(...略...)

-h オプションなしで、単に ansible-playbook でも可

公式ドキュメントでは、ansible-playbook コマンド説明ページで確認します。(一覧はこちら


プラグイン

コネクションプラグインの一覧を表示したい

コネクションプラグインsshnetwork_cliなど)ってどんなのがあったかな?という時に調べる方法です。

ansible-doc -t connection -l
  • 実行例
$ ansible-doc -t connection -l
buildah      Interact with an existing buildah container
chroot       Interact with local chroot
docker       Run tasks in docker containers
(...略...)
netconf      Provides a persistent connection using the netconf protocol
network_cli  Use network_cli to run command on network appliances
(...略...)

公式ドキュメントでは、コネクションプラグインのリストを確認します。

コネクションプラグインの詳細を表示したい

コネクションプラグイン名は分かっているけれど、利用される変数名などの詳細を知りたい時に調べる方法です。

ansible-doc -t connection [コネクションプラグイン名]
  • 実行例: netconf コネクションプラグインの詳細を表示する
$ ansible-doc -t connection netconf
> NETCONF    (/home/vagrant/ansible2780/lib/python2.7/site-packages/ansible/plugins/connection/netconf.py)

        This connection plugin provides a connection to remote devices over the SSH
        NETCONF subsystem.  This connection plugin is typically used by network
        devices for sending and receiving RPC calls over NETCONF. Note this connection
        plugin requires ncclient to be installed on the local Ansible controller.

OPTIONS (= is mandatory):

- host
        Specifies the remote device FQDN or IP address to establish the SSH connection
        to.
        [Default: inventory_hostname]
        set_via:
          vars:
          - name: ansible_host
(...略...)

公式ドキュメントでは、netconf コネクションプラグインの説明ページを確認します。


■ 設定

項目名を知りたい

ansible.cfg環境変数で定義する設定項目名を知りたい時に調べる方法です。

ansible-config dump | grep [正規表現]
  • 実行例: timeout が含まれる設定項目名を表示する
$ ansible-config dump | grep -i timeout
CACHE_PLUGIN_TIMEOUT(default) = 86400
DEFAULT_GATHER_TIMEOUT(default) = 10
DEFAULT_TIMEOUT(default) = 10
PERSISTENT_COMMAND_TIMEOUT(default) = 10
PERSISTENT_CONNECT_RETRY_TIMEOUT(default) = 15
PERSISTENT_CONNECT_TIMEOUT(default) = 30

公式ドキュメントでは、設定一覧のページを確認します。


■ fact変数名の一部からfact変数名を特定したい

fact変数名の一部しか分かっていないけれど、どのfact変数かを特定したい時に調べる方法です。

ansible -i [インベントリ] [ターゲット] -m setup -a "filter=[パターン]" 

-m オプションを省略すると、すべてのfact変数の内容が表示されます

  • 実行例: localhost の fact変数で ipv4 が含まれるものを表示
$ ansible -i localhost, all -m setup -a "filter=*ipv4*" -c local
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.17.0.1", 
            "10.0.2.15", 
            "172.16.0.9"
        ], 
        "ansible_default_ipv4": {
            "address": "10.0.2.15", 
            "alias": "eth0", 
            "broadcast": "10.0.2.255", 
            "gateway": "10.0.2.2", 
            "interface": "eth0", 
            "macaddress": "52:54:00:xx:xx:xx", 
            "mtu": 1500, 
            "netmask": "255.255.255.0", 
            "network": "10.0.2.0", 
            "type": "ether"
        }
    }, 
    "changed": false
}


■ まとめ

オフライン状態で、Ansible のモジュールやコネクションプラグイン、設定項目について調べる方法をご紹介しました。個人的には意外と調べる方法方があった、という印象を受けました。

もし、インターネットに接続できないシーンで調べごとをしたいときのめに、参考にしていただければ幸いです。

[Ansible] ループにリストの変数を渡すときの(私が)忘れがちな挙動差分

■ はじめに

ループするためにリストの変数を渡すときに、以下の2パターンを特に区別なく使ってしまうことがあります(私が)。

  • pattern1
- name: pattern1
    debug:
    msg: "{{ item }}"
    with_items: "{{ test_list }}"  # ここにリストの変数を渡す
  • pattern2
- name: pattern2
    debug:
    msg: "{{ item }}"
    with_items:
      - "{{ test_list }}"   # ここにリストの変数を渡す

どちらも結果は同じなのですが、内部の挙動が異なります。

この記事では、簡単な例で違いを説明します。


■ 動作確認

Playbook

以下のパターンのタスクを準備します。

pattern 説明
pattern1 with_items に直接リストを指定
pattern2 with_items の最初の要素にリストを指定
pattern3 loop に直接リストを指定
pattern4 loop の最初の要素にリストを指定

この中で一つだけ結果が異なるパターンがあります。

- hosts: localhost
  gather_facts: no

  vars:
    test_list:   # ループに使うリストの変数
      - a
      - b
      - c

  tasks:
    - name: pattern1
      debug:
        msg: "{{ item }}"
      with_items: "{{ test_list }}"
    
    - name: pattern2
      debug:
        msg: "{{ item }}"
      with_items:
        - "{{ test_list }}"

    - name: pattern3
      debug:
        msg: "{{ item }}"
      loop: "{{ test_list }}"

    - name: pattern4
      debug:
        msg: "{{ item }}"
      loop:
        - "{{ test_list }}"

実行結果

実行して確認します。

$ ansible-playbook -i localhost, looplist.yml

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

TASK [pattern1] ************************************************************
ok: [localhost] => (item=a) => {
    "msg": "a"
}
ok: [localhost] => (item=b) => {
    "msg": "b"
}
ok: [localhost] => (item=c) => {
    "msg": "c"
}

TASK [pattern2] ************************************************************
ok: [localhost] => (item=a) => {
    "msg": "a"
}
ok: [localhost] => (item=b) => {
    "msg": "b"
}
ok: [localhost] => (item=c) => {
    "msg": "c"
}

TASK [pattern3] ************************************************************
ok: [localhost] => (item=a) => {
    "msg": "a"
}
ok: [localhost] => (item=b) => {
    "msg": "b"
}
ok: [localhost] => (item=c) => {
    "msg": "c"
}

TASK [pattern4] ************************************************************
ok: [localhost] => (item=[u'a', u'b', u'c']) => {
    "msg": [
        "a",
        "b",
        "c"
    ]
}

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

解説

pattern1 -3 が一次元のリストとしてループしたのに対して、pattern4 だけ二次元のリストとしてループしました。

pattern4 では、リスト0番目の要素に ["a", "b", "c"] というリストが入っている状態です。

patterm 2 も構造上は二次元のリストですが、with_times がループ時に事前に、一次元のリストに flatten します。その結果、pattern1 や 3 と同じ結果になります。


■ まとめ

with_items を利用したループにおいて、同じ結果でも内部の挙動が異なる点をご紹介しました。

loop との違いや、リストが意図した通りに処理されているかは意識したほうが良いと思いました。