てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] インベントリファイルを YAML 形式で書く

■ はじめに

Ansible のターゲットホストの情報を定義するインベントリファイルは、INI 形式の他にも YAML 形式でも定義できす。

この記事では、2つの例をとって ini と YAML を比較しながら YAML でのインベントリファイルの書き方を説明します。

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


■ 簡単な例

はずは簡単な例です。1つのグループに2つのホストが所属し、ホスト個別の変数、グループ共通の変数を定義します。

ini の場合

ini で書く場合は以下のような例です。

[junos]
junos01 ansible_host=172.16.1.1
junos02 ansible_host=172.16.1.2

[junos:vars]
ansible_network_os=junos
ansible_connection=netconf

ansible-inventory コマンドの --graph オプションでは、グループとホストの関係が以下のように表示さます。

$ ansible-inventory -i inventory01.ini --graph
@all:
  |--@junos:
  |  |--junos01
  |  |--junos02
  |--@ungrouped:

YAML の場合

YAML で書く場合は以下のようになります。

all:
  children:
    junos:   # junos グループ
      hosts: # junos グループのホスト
        junos01:
          ansible_host: 172.16.1.1  # ホスト変数
        junos02:
          ansible_host: 172.16.1.2  # ホスト変数
      vars: # junos グループ変数
        ansible_network_os: junos
        ansible_connection: netconf

以下のような特徴があります。

  • すべてのホストや、グループが暗黙的に所属する all グループをトップに定義する
  • グループの定義の配下には、所属させたいホストを hosts 、所属させたい子グループを children で定義する
  • ホスト変数は、ホスト名配下に定義する
  • グループ変数は vars 配下に定義する

対応

f:id:akira6592:20190310163459p:plain
ini と YAML の対応


■ 少し複雑な例

次に、簡単な例にもうひと工夫した少し複雑な例です。 先ほどとは、junos という一つのグループのみでしたが、同じレベルに ios グループを加えます。さらにこれら2つのグループの親グループ router も定義します。

別途、どのグループにも所属しないホスト web01 も定義します。

ini の場合

ini で書く場合は以下のような例です。

web01 ansible_host=127.16.0.1

[router:children]
junos
ios

[junos]
junos01 ansible_host=172.16.1.1
junos02 ansible_host=172.16.1.2

[junos:vars]
ansible_network_os=junos
ansible_connection=netconf

[ios]
ios01 ansible_host=172.16.2.1
ios02 ansible_host=172.16.2.22

[ios:vars]
ansible_network_os=ios
ansible_connection=network_cli

ansible-inventory コマンドの --graph オプションでは、グループとホストの関係が以下のように表示さます。

$ ansible-inventory -i inventory02.ini --graph
@all:
  |--@router:
  |  |--@ios:
  |  |  |--ios01
  |  |  |--ios02
  |  |--@junos:
  |  |  |--junos01
  |  |  |--junos02
  |--@ungrouped:
  |  |--web01

YAML の場合

YAML で書く場合は以下のようになります。

all:
  hosts:  # グループに所属しないホストの情報(ungroupedグループ所属と同義)
    web01: # ホスト web01
      ansible_host: 172.16.0.1          # ホスト変数
  children:
    router: # router グループ
      children:
        junos:  # junos グループ(routerの子グループ)
          hosts: # junos グループのホスト
            junos01:
              ansible_host: 172.16.1.1  # ホスト変数
            junos02:
              ansible_host: 172.16.1.2  # ホスト変数
          vars: # junos グループの変数
            ansible_network_os: junos
            ansible_connection: netconf
        ios:  # ios グループ(routerの子グループ)
          hosts:  # ios グループのホスト
            ios01:
              ansible_host: 172.16.2.1  # ホスト変数
            ios02:
              ansible_host: 172.16.2.2  # ホスト変数
          vars:  # ios グループの変数
            ansible_network_os: ios
            ansible_connection: network_cli

前述の簡単な例に加えて、以下のような特徴があります。

  • どのグループに所属しない(ungrouped)ホストは、all 配下の hosts に定義する
  • router グループの配下に子グループ junosios を定義する


■ 補足

注意点: 型判定が微妙に異なる

変数の値の型判定が ini と YAML で異なります。

例えば、ini 内の True は真偽値と判定されますが、 true も文字列です。一方で、YAML 内では Truetrue も真偽値です。パース処理に違いによるものです。

どちらが良いか

Ansible としては、YAML のイベントリファイルのほうが新しい機能です。 既存の ini のイベントリファルがあるのであれば、無理に YAML に移行する必要も無いと思います。Playbook も インベントリも YAML に統一したい、という事情がある場合は YAML に移行することを検討されるのがよいのではないでしょうか。

また、イベントリファル内で、複雑な変数(リストやディクショナリなど)を定義したい場合は YAML の方が適しています。

既存の ini のイベントリファイルを YAML にした場合の雰囲気を掴むには

既存の ini のイベントリファルがあって、それを YMAL で書く場合の雰囲気を掴むには以下のコマンドを利用できます。

ansible-inventory -i インベントリファイル名 --list --yaml

例えば以下の ini のイベントリファルがあったとします。

[junos]
junos01 ansible_host=172.16.1.1
junos02 ansible_host=172.16.1.2

[junos:vars]
ansible_network_os=junos
ansible_connection=netconf

出力結果は以下のようになります。

all:
  children:
    junos:
      hosts:
        junos01:
          ansible_connection: netconf
          ansible_host: 172.16.1.1
          ansible_network_os: junos
        junos02:
          ansible_connection: netconf
          ansible_host: 172.16.1.2
          ansible_network_os: junos
    ungrouped: {}

厳密には、インベントリファイルの形式変換用のコマンドではなく、インベントリファイルを解析して、各ホストにどのような変数が適用されるかなどを確認するコマンドです。例えばグループ変数は、実際に所属するホストの変数に展開されて表示されます。

ini と YAML 間の一致を確認する方法

ini から YAML に以降した場合、正しく書き換えできたかが気になると思います。その場合は、以下の2コマンドの出力結果が同じであることを確認します。

  • ansible-inventory -i iniのインベントリファイル名 --list
  • ansible-inventory -i YAMLのインベントリファイル名 --list

お好みで --yaml オプションを加えても大丈夫です。


■ まとめ

YAML 形式のインベントリファイルの書き方をご紹介しました。必要に応じて使い分けるときの参考にしていただければ幸いです。

参考

docs.ansible.com

qiita.com

[Ansible] 環境変数を取得する ansible_env.hoge と lookup("env", "hoge") の違い

■ はじめに

Ansible には 環境変数を取得するための方法として、ansible_env 配下の変数を参照する方法と、 lookup("env", "hoge") のように loopkup プラグインを利用する方法があります。これらは性質が大きく異なります。

この記事では簡単な例で動作を確認します。

■ 違いは?

ansible_env.hoge lookup("env", "hoge")
取得場所 リモートホスト(ターゲット) ローカルホスト(ansible-playbookコマンド実行ホスト)
取得タイミング gather_facts: yes (デフォルト)などによる setup 実行時 参照時(のはず)
取得可能な環境変数 setup モジュールに依存 すべて

■ 動作確認

環境

Ansible 2.7.8

Playbook

- hosts: linux  # taget1 、target2 定義済みグループ
  gather_facts: yes   # デフォルトはyes。no の場合は ansible_env 自体が定義されない

  tasks:  
    - name: debug lookup("env", "PWD") 
      debug:
        var: lookup("env", "PWD") 

    - name: debug ansible_env.PWD
      debug:
        var: ansible_env.PWD

    - name: debug ansible_env
      debug:
        var: ansible_env       # ansible_env 全体を表示

実行

$ echo $PWD # ローカルホスト上の 環境変数 `PWD` をあらかじめ確認。
/vagrant/blog/env
$ ansible-playbook -i inventory env.yml    # Playbook 実行

PLAY [linux] *************************************************************************************

TASK [Gathering Facts] ***************************************************************************
ok: [target2]
ok: [target1]

TASK [debug lookup("env", "PWD")] ****************************************************************
ok: [target1] => {
    "lookup(\"env\", \"PWD\")": "/vagrant/blog/env"
}
ok: [target2] => {
    "lookup(\"env\", \"PWD\")": "/vagrant/blog/env"
}

TASK [debug ansible_env.PWD] *********************************************************************
ok: [target1] => {
    "ansible_env.PWD": "/home/vagrant"
}
ok: [target2] => {
    "ansible_env.PWD": "/home/testuser"
}

TASK [debug ansible_env] *************************************************************************
ok: [target1] => {
    "ansible_env": {
        "HOME": "/home/vagrant", 
        "LANG": "C", 
        "LC_ALL": "C", 
        "LC_MESSAGES": "C", 
        "LESSOPEN": "||/usr/bin/lesspipe.sh %s", 
        "LOGNAME": "vagrant", 
        "LS_COLORS": "rs=0:di=38;(...略...)", 
        "MAIL": "/var/mail/vagrant", 
        "PATH": "/usr/local/bin:/usr/bin", 
        "PWD": "/home/vagrant", 
        "SELINUX_LEVEL_REQUESTED": "", 
        "SELINUX_ROLE_REQUESTED": "", 
        "SELINUX_USE_CURRENT_RANGE": "", 
        "SHELL": "/bin/bash", 
        "SHLVL": "2", 
        "SSH_CLIENT": "172.16.0.9 53026 22", 
        "SSH_CONNECTION": "172.16.0.9 53026 172.16.0.11 22", 
        "SSH_TTY": "/dev/pts/5", 
        "TERM": "xterm-256color", 
        "USER": "vagrant", 
        "XDG_RUNTIME_DIR": "/run/user/1000", 
        "XDG_SESSION_ID": "18", 
        "_": "/usr/bin/python"
    }
}
ok: [target2] => {
    "ansible_env": {
        "HOME": "/home/testuser", 
        "LANG": "ja_JP.UTF-8", 
        "LESSOPEN": "||/usr/bin/lesspipe.sh %s", 
        "LOGNAME": "testuser", 
        "LS_COLORS": "rs=0:di=38;(...略...)", 
        "MAIL": "/var/mail/testuser", 
        "PATH": "/usr/local/bin:/usr/bin", 
        "PWD": "/home/testuser", 
        "SELINUX_LEVEL_REQUESTED": "", 
        "SELINUX_ROLE_REQUESTED": "", 
        "SELINUX_USE_CURRENT_RANGE": "", 
        "SHELL": "/bin/bash", 
        "SHLVL": "2", 
        "SSH_CLIENT": "172.16.0.9 49312 22", 
        "SSH_CONNECTION": "172.16.0.9 49312 172.16.0.12 22", 
        "SSH_TTY": "/dev/pts/1", 
        "TERM": "xterm-256color", 
        "USER": "testuser", 
        "XDG_RUNTIME_DIR": "/run/user/1001", 
        "XDG_SESSION_ID": "20", 
        "_": "/usr/bin/python"
    }
}

PLAY RECAP ***************************************************************************************
target1                    : ok=4    changed=0    unreachable=0    failed=0   
target2                    : ok=4    changed=0    unreachable=0    failed=0 

確認できたこと

上記の Playbook 実行結果から以下のことが分かります。

  • lookup("env", "PWD") の値は、target1target2 向けのいずれのタスクでも /vagrant/blog/env になっています。
    • これらはローカルホスト上の環境変数 PWD の値です。
  • ansible_env.PWD の値は、target1 では /home/vagranttarget2 では /home/testuser となっています。これは各ターゲットに異なるユーザーでログインしたため、ログイン先での PWD も異なっている、という状態です。リモートホストPWD である /vagrant/blog/env とも異なっています。
  • ansible_env 配下には、すべてのローカル環境変数が入るわけではない
    • たとえば、HOSTNAME がない

■ まとめ

環境変数を取得する方法である、ansible_env.hogelookup("env", "hoge") の違いについて確認しました。 主な違いは、リモートのものなのか、ローカルのものなのかでした、

参考

docs.ansible.com

docs.ansible.com

[Ansible] Jinja2 でループインデックス(index/index0)などの特殊変数を利用する

■ はじめに

Ansible というより、Jinja2 の機能ですが for によるループの中で、今何番目のループであるかを示すループインデックスなどの特殊な変数を利用できます。

変数名
loop.index 1 から始まるインデックス
loop.index0 0 から始まるインデックス
loop.irevindex 後ろから数えるインデックス(5要素ある場合は5から)
loop.first 最初のループであれば True、それ以外は False
loop.last 最後のループであれば True、それ以外は False

この記事では簡単な例で動作を確認します。

[2019/08/20 追記] Ansible 2.8 で Ansible の「Extended loop variables」という機能として実装されました。

動作確認環境

  • Ansible 2.7.8
  • CentOS 7.6 (Ansible 側、管理対象ホスト側とも)


■ 準備

Jiinja2 テンプレート

  • test.j2
{% for u in users %}
# {{ u }} 
- index: {{ loop.index }}
- index0: {{ loop.index0 }}
- revindex: {{ loop.revindex}}
- first: {{ loop.first}}
- last: {{ loop.last}}

{% endfor %}

Playbook

テンプレート test.j2 の内容をもとにして test.txt を生成します。ここでは、変数はvars ディレクティブで指定したものが利用されます。

  • test.yml
- hosts: localhost
  gather_facts: no

  tasks:
    - name: loop test
      template:
        src: test.j2
        dest: test.txt

  vars:
    users:
      - kingyo
      - koi
      - funa
      - tanago
      - oikawa

■ 実行

$ ansible-playbook -i inventory test.yml 

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

TASK [loop test] *********************************************************
ok: [localhost]

PLAY RECAP *************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0   
  • test.txt

生成したファイルの内容です。各種変数の内容が確認できます。

# kingyo 
- index: 1
- index0: 0
- revindex: 5
- first: True
- last: False

# koi 
- index: 2
- index0: 1
- revindex: 4
- first: False
- last: False

# funa 
- index: 3
- index0: 2
- revindex: 3
- first: False
- last: False

# tanago 
- index: 4
- index0: 3
- revindex: 2
- first: False
- last: False

# oikawa 
- index: 5
- index0: 4
- revindex: 1
- first: False
- last: True

■ まとめ

Jinja2 テンプレートのループの中で、ループインデックスなどの特殊な変数を確認しました。 その他の loop.* 変数については Jinja2 のドキュメントを参照してください。

jinja.pocoo.org

なお、これらの変数は Ansilbe 徹底入門をきっかけに知りました。

Ansible徹底入門 クラウド時代の新しい構成管理の実現

Ansible徹底入門 クラウド時代の新しい構成管理の実現

[Ansible] 任意のfactsを定義できる local facts (custom facts) の定義方法

■ はじめに

Ansible では、ホスト名やインターフェースなどのシステムの基本情報を fatcs (ファクト)という単位で収集して管理します。通常、Ansible によって決められた情報が対象になりますが、任意の情報を facts として管理するための local facts (custom facts) という機能もあります。

この記事では、簡単な例を元にして、local facts の定義の方法と確認する方法を説明します。

動作確認環境

  • Ansible 2.3.0, 2.7.8
  • CentOS 7.6 (Ansible 側、管理対象ホスト側とも)


■ local facts の特徴

local とは?

local というのは Ansible 自身を実行するコントロールホストにとっての local ではなく、管理対象のリモートホストにとっての local です。

定義方法

  • リモートホスト/etc/ansible/facts.d/ ディレクトリ配下に配置する
  • フォーマットは ini か json
    • いずれの場合も拡張子は .fact にする
  • ini の場合、セクション(例: [hogehoge])が必須
  • json を返す実行可能なファイルも可
  • yaml は不可

名前空間


■ 動作確認

準備1: リモートホスト上の local facts 定義ファイル

試しに2つの形式で準備しておきます。

json

  • /etc/ansible/facts.d/test_j.fact
{
  "kind": ["wakin", "demekin", "ryukin"]
}

ini 版

  • /etc/ansible/facts.d/test_i.fact
[section1]
custom_name=kingyo
custom_group=fish

準備2: Playbook

facts を収集して ansible_local 配下の facts を debug モジュールで表示する Playbook です。

- hosts: linux
  gather_facts: yes # default

  tasks:
    - name:
      debug:
        msg: "{{ ansible_local }}"

実行ログ

それでは実行します。

$ ansible-playbook -i inventory custom_facts.yml

PLAY [linux] ***********************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [linux1]

TASK [debug] ***********************************************************************************************************
ok: [linux1] => {
    "msg": {
        "test_i": {
            "section1": {
                "custom_group": "fish", 
                "custom_name": "kingyo"
            }
        }, 
        "test_j": {
            "kind": [
                "wakin", 
                "demekin", 
                "ryukin"
            ]
        }
    }
}

PLAY RECAP *************************************************************************************************************
linux1                     : ok=2    changed=0    unreachable=0    failed=0

無事に /etc/ansible/facts.d/test_i.fact/etc/ansible/facts.d/test_j.fact の2つの local facts の内容が確認できました。ファイル名や、ini の場合はセクション名も名前空間に含まれていることが確認できます。

なお、ini 形式にセクションがない場合は、"test_i": "error loading fact - please check content", のようなメッセージが表示され、正常に local facts が読み込めません。

■ まとめ

local facts の特徴や定義方法を説明しました。任意の情報を facts として扱いたい場合は、この方法を参考にしていただければと思います。

参考

docs.ansible.com

[Ansible] file モジュールの基本的な使い方(ファイルやディレクトリの操作)

■ はじめに

Ansible には、ファイル属性の設定やディレクトリの作成などができる file モジュール があります。

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

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

動作確認環境

  • Ansible 2.3.0, 2.7.8
  • CentOS 7.6 (Ansible 側、管理対象ホスト側とも)

目次


■ [例1] 所有者やパーミッションを設定する

Playbook

既存のファイル /etc/foo.conf の所有者、所有グループ、パーミッションを設定する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - file:
        path: /etc/foo.conf
        owner: kingyo
        group: kingyo
        # when specifying mode using octal numbers, add a leading 0
        mode: 0644
  • path オプション
    • 対象ファイルのパスを指定します。(必須)
    • 本タスクの場合、state オプションは指定していないため(デフォルトの state: file 扱い)、ここで指定したファイルは あらかじめ存在している必要があります
    • もし空のファイルを作成する場合は、state: touch を併用します。その場合も、パスの途中に含まれるディレクトリは存在している必要があります。
  • onwer オプション
    • 対象ファイルの所有者を指定します。
  • group オプション
    • 対象ファイルの所有グループを指定します。
  • mode オプション
    • 対象ファイルの mode を指定します。
    • 8進数で指定する場合は、0644 のように 0 から始めるか、'644' のようにクォーテーションで囲う必要があります。
    • u=rw,g=r,o=r のような指定もできます。

本タスクで指定していないその他のオプション例

  • recurse オプション
    • path オプションでディレクトリを指定した場合、操作は再帰的に行うかどうかを指定します。(デフォルト no

実行ログ

ここでは、/etc/foo.conf が 所有者 root、所有者グループ rootパーミッション 664 の状態で Playbook を実行します。

  • リモート側の事前確認
$ sudo ls -al /etc/foo.conf
-rw-rw-r--. 1 root root 6 Mar  3 06:57 /etc/foo.conf
  • Ansible 側
$ ansible-playbook -i inventory file_attr.yml 

PLAY [linux] ******************************************************************************************

TASK [file] *******************************************************************************************
changed: [linux1]

PLAY RECAP ********************************************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   
  • リモート上の事後確認
$ sudo ls -al /etc/foo.conf
-rw-r--r--. 1 kingyo kingyo 6 Mar  3 06:57 /etc/foo.conf

無事に /etc/foo.conf が指定通りの所有者、パーミッションになりました。


■ [例2] ファイルやディレクトリを削除する

Playbook

ファイル /etc/foo.conf をファイルを削除する Playbook です。 公式ドキュメントの資料には載っていませんが、使用する頻度はそこそこあるのではないかと思います。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - file:
        path: /etc/foo.conf
        state: absent
  • path オプション
    • 対象(ここではファイル名)のパスを指定します。(必須)
  • state オプション
    • 対象の種類を指定します。ここでは削除するため state: absent を指定しています。

実行ログ

ここでは、/etc/foo.conf がある状態で Playbook を実行します。

  • Ansible 側
$ ansible-playbook -i inventory file_absent.yml 

PLAY [linux] ***************************************************************************

TASK [file] ****************************************************************************
changed: [linux1]

PLAY RECAP *****************************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   
  • リモート上の事後確認
$ sudo ls /etc/foo.conf
ls: cannot access /etc/foo.conf: No such file or directory

無事に /etc/foo.conf が削除されました。


■ [例3] ディレクトリを作成する

Playbook

ディレクト/etc/some_directory を作成する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - file:
        path: /etc/some_directory
        state: directory
        mode: 0755
  • path オプション
    • 対象(ここではディレクトリ名)のパスを指定します。(必須)
  • state オプション
    • 対象の種類を指定します。ここでは対象がディレクトリのため state: directory を指定してます。
    • パスの途中に存在していないディレクトリが含まれている場合でも再帰的にディレクトリを作成することもできます。(mkdir コマンドの -p オプションのように)
  • mode オプション
    • 対象ファイルの mode を指定します。

実行ログ

ここでは、/etc/some_directory がない状態で Playbook を実行します。

  • Ansible 側
$ ansible-playbook -i inventory file_dir.yml 

PLAY [linux] ******************************************************************************************

TASK [file] *******************************************************************************************
changed: [linux1]

PLAY RECAP ********************************************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   
 
  • リモート上の事後確認
$ ls -al /etc/some_directory/
total 8
drwxr-xr-x.  2 root root 4096 Mar  3 07:14 .
drwxr-xr-x. 88 root root 4096 Mar  3 07:14 .

無事に /etc/some_directory ディレクトリが作成されました。すでにこのディレクトリがあって、パーミッション755 だった場合は特に何もしません。


■ まとめ

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

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

docs.ansible.com

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

[Ansible] template モジュールの基本的な使い方(Jinja2テンプレートからファイル生成)

■ はじめに

Ansible には、Jinja2 というテンプレートエンジンによるテンプレートファイルを利用してファイルを生成してリモートに送れる template モジュール があります。httpd.conf などの設定ファイルを、変数とテンプレートから生成するといった用途に利用できます。

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

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

動作確認環境

  • Ansible 2.3.0, 2.7.8
  • CentOS 7.6 (Ansible 側、管理対象ホスト側とも)

目次


■ [例1] 単純にテンプレートからファイルを生成して送る

Playbook

Jinja2 テンプレートファイル foo.j2 を元にファイルを生成して、リモートに /etc/file.conf として送る Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    # Example from Ansible Playbooks
    - template:
        src: ./foo.j2
        dest: /etc/file.conf
        owner: bin
        group: wheel
        mode: 0644

  vars:
    test_name: Ansible
  • src オプション
    • もととなる Jinja2 テンプレートファイル名を指定します。(必須)
    • 今回指定している ./foo.j2 の内容は後述します。
  • dest オプション
    • 送り先のリモートのファイル名を指定します。(必須)
  • onwer オプション
    • リモートファイルの所有者を指定します。
  • group オプション
    • リモートファイルの所有グループを指定します。
  • mode オプション
    • リモートファイルの mode を指定します。
    • 8進数で指定する場合は、0644 のように 0 から始めるか、'644' のようにクォーテーションで囲う必要があります。
    • u=rw,g=r,o=r のような指定もできます。

使用するテンプレートファイル (./foo.j2)は以下のとおりです。

Hello, {{ test_name }}!

{{ test_name }} には、今回の場合 Playbook 内の vars ディレクティブ で指定した変数 test_name の値が展開されます。

Jinja2 の書式の詳細は、Jinja2 のドキュメントを確認してください。

本タスクで指定していないその他のオプション例

  • backup オプション
    • dest オプションで指定したリモートのファイルがすでに存在する場合に、バックアップをとるかどうか指定するオプションです。(デフォルト no
    • バックアップファイルの名前はタイムスタンプを含むものになります。

実行ログ

ここでは、リモートに /etc/file.conf が存在しない状態で Playbook を実行します。

  • Ansible 側
$ ansible-playbook -i inventory template_simple.yml 

PLAY [linux] ******************************************************************************

TASK [template] ***************************************************************************
changed: [linux1]

PLAY RECAP ********************************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   
  • リモート上の確認
$ cat /etc/file.conf
Hello, Ansible!
$ ls -al /etc/file.conf
-rw-r--r--. 1 bin wheel 16 Mar  3 05:44 /etc/file.conf

無事に /etc/file.conf が生成され、テンプレートで指定した変数 {{ test_name }} も展開されました。 所有者やパーミッションも指定したとおりになりました。

なお、この後もう一度同じ Playbook を実行した場合は、すでに同じ内容、権限 になっていることを検出して、とくに何もしません。そのため、対象ファイルの更新日時も変わりません。


■ [例2] 正しい内容に生成されるかを事前に検証する

Playbook

Jinja2 テンプレートファイル sudoers.j2 を元にファイルを生成して、リモートに /etc/file.conf として送って適用する前に /usr/sbin/visudo -cf %s コマンドでファイルのフォーマットを確認する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    # Copy a new "sudoers" file into place, after passing validation with visudo
    - template:
        src: ./sudoers.j2
        dest: /etc/sudoers
        validate: '/usr/sbin/visudo -cf %s'

  vars:
    wheel_group_name: wheel
  • src オプション
    • もととなる Jinja2 テンプレートファイル名を指定します。(必須)
    • 今回指定している ./sudoers.j2 の内容は後述します。
  • dest オプション
    • 送り先のリモートのファイル名を指定します。(必須)
  • validate オプション
    • 生成したファイルの内容が正しいか検証するためのコマンドを指定します。対象を %s で指定します。
    • ファイルの内容のフォーマットエラーが、システムへ大きく影響するためあらじめ検証たい場合に利用できます。

使用するテンプレートファイル (./sudoers.j2)は以下のとおりです。

(...略...)
%{{ wheel_group_name }}  ALL=(ALL)       ALL
(...略...)

{{ wheel_group_name }} には、今回の場合 Playbook 内の vars ディレクティブ で指定した変数 wheel_group_name の値が展開されます。

実行ログ

  • Ansible 側
$ ansible-playbook -i inventory template_validate.yml            

PLAY [linux] ********************************************************************************************************

TASK [template] *****************************************************************************************************
changed: [linux1]

PLAY RECAP **********************************************************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   
  • リモート上の確認
$ sudo cat /etc/sudoers
(...略...)
%wheel  ALL=(ALL)       ALL
(...略...)

無事に /etc/sudoers が生成され、テンプレートで指定した変数 {{ wheel_group_name }} も展開されました。


■ まとめ

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

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

docs.ansible.com

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

[Ansible] user モジュールの基本的な使い方(ユーザーの作成・削除など)

■ はじめに

Ansible には、ユーザーを管理(作成、削除など)する user モジュール があります。

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

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

動作確認環境

  • Ansible 2.3.0, 2.7.8
  • CentOS 7.6 (Ansible 側、管理対象ホスト側とも)

目次


■ [例1] 単純にユーザーを作成する

Playbook

ユーザー kingyo を作成する Playbook です。公式ドキュメントの使用例にはありませんが、一番シンプルな使い方です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - name: Add the user 'kingyo'
      user:
        name: kingyo
  • name オプション
    • 対象のユーザー名を指定します。(必須)

本タスクで指定していないその他のオプション例

  • state オプション
    • 対象のユーザーが存在する状態(present)にするか、存在しない状態(absent)にするかを指定します。(デフォルト present
    • ここでは state: absent を指定しているため、存在しない状態(存在する場合は削除)という指定になります。

実行ログ

ここでは、kingyo ユーザー未作成の状態で Playbook を実行します。

  • リモート上の事前確認
$ id kingyo 
id: kingyo: no such user  # kingyo ユーザーがまだ存在しない
  • Ansible 側
$ ansible-playbook -i inventory user_add_simple.yml 

PLAY [linux] **************************************************************************************************

TASK [Add the user 'kingyo'] **********************************************************************************
changed: [linux1]

PLAY RECAP ****************************************************************************************************
linux1                     : ok=1    changed=1    unreachable=0    failed=0   
  • リモート上の事後確認
$ id kingyo 
uid=1041(kingyo) gid=1041(kingyo) groups=1041(kingyo)  # kingyo ユーザーが作成された
$ cat /etc/passwd | grep kingyo
kingyo:x:1041:1041::/home/kingyo:/bin/bash

無事に指定した通りに、ユーザー kingyo が作成されました。 groupgroups オプションを指定していなかったため、暗黙的に新たに作成された kingyo グループにプライマリグループとして所属しています。


■ [例2] プライマリグループとユーザーIDを指定してユーザーを作成する

Playbook

作成済みの admin グループにプライマリグループとして所属し、ユーザーID 1040 のユーザー jonhnd を作成する Playbook です

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - name: Add the user 'johnd' with a specific uid and a primary group of 'admin'
      user:
        name: johnd
        comment: John Doe
        uid: 1040
        group: admin
  • name オプション
    • 対象のユーザー名を指定します。(必須)
  • comment オプション
    • /etc/passwd のコメント欄に記入するコメントを指定します。
  • uid オプション
    • 対象のユーザーIDを指定します。
  • group オプション
    • 対象のユーザーが所属するプライマリグループ名を指定します。
    • 似たようなオプションに groups(サブグループの指定)があるので注意してください。

実行ログ

ここでは、johnd ユーザーは未作成、admin グループは作成済みの状態で Playbook を実行します。

$ ansible-playbook -i inventory user_add.yml 

PLAY [linux] **********************************************************************************

TASK [Add the user 'johnd' with a specific uid and a primary group of 'admin'] ****************
changed: [linux1]

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

$ 
$ id johnd
uid=1040(johnd) gid=1002(admin) groups=1002(admin)
$ cat /etc/passwd | grep johnd
johnd:x:1040:1002:John Doe:/home/johnd:/bin/bash    # johnd ユーザーが作成された

無事に指定した通りに、ユーザー johnd が作成されました。


■ [例3] サブグループと使用シェルを指定してユーザーを作成する

Playbook

作成済みの adminsdevelopers グループにサブグループとして所属し、`/bin/bash を使用するユーザー james を作成する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - name: Add the user 'james' with a bash shell, appending the group 'admins' and 'developers' to the user's groups
      user:
        name: james
        shell: /bin/bash
        groups: admins, developers
        append: yes
  • name オプション
    • 対象のユーザー名を指定します。(必須)
  • shell オプション
    • 使用するシェルを指定します。
  • groups オプション
    • 対象のユーザーが所属するサブグループ名を指定します。
    • 複のグループ名を指定できます。Ansible 2.3 からは、カンマ区切りの他にリストも指定できます。
    • 似たようなオプションに group (プライマリグループの指定)があるので注意してください。
  • append オプション
    • groups オプションで指定したグループ名を追加所属させるかどうかを指定します。(デフォルト no
    • append: no を指定(デフォルト)の場合、groups オプションで指定したグループ以外の所属は外れます。

実行ログ

ここでは、james ユーザーは未作成、adminsdevelopers グループは作成済みの状態で Playbook を実行します。

$ ansible-playbook -i inventory user_add_goups.yml 

PLAY [linux] **********************************************************************************

TASK [Add the user 'james' with a bash shell, appending the group 'admins' and 'developers' to the user's groups] ***
changed: [linux1]

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

$ id james
uid=1002(james) gid=1005(james) groups=1005(james),1003(admins),1004(developers)  # james ユーザーが作成された
$ cat /etc/passwd | grep james   
james:x:1002:1005::/home/james:/bin/bash

無事に指定した通りに、ユーザー james が作成されました。


■ [例4] ユーザーを削除する

Playbook

作成済みのユーザー johnd を削除する Playbook です。

- hosts: linux
  gather_facts: no
  become: yes

  tasks:
    - name: Remove the user 'johnd'
      user:
        name: johnd
        state: absent
        remove: yes
  • name オプション
    • 対象のユーザー名を指定します。(必須)
  • state オプション
    • 対象のユーザーが存在する状態(present)にするか、存在しない状態(absent)にするかを指定します。(デフォルト present
  • remove オプション
    • state: absent の指定場合った場合、対象のユーザーに関連づいたディレクトリを削除します。(デフォルト no

実行ログ

$ id johnd                  
uid=1040(johnd) gid=1002(admin) groups=1002(admin) # johnd ユーザーがまだ存在する
$ cat /etc/passwd | grep johnd
johnd:x:1040:1002:John Doe:/home/johnd:/bin/bash
$ sudo ls -al /home/johnd/  
total 20         # johnd のホームディレクトリがまだ存在する
drwx------. 2 1040 admin 4096 Mar  3 01:53 .
drwxr-xr-x. 6 root root  4096 Mar  3 02:08 ..
-rw-r--r--. 1 1040 admin   18 Oct 30 17:07 .bash_logout
-rw-r--r--. 1 1040 admin  193 Oct 30 17:07 .bash_profile
-rw-r--r--. 1 1040 admin  231 Oct 30 17:07 .bashrc

$ ansible-playbook -i inventory user_remove.yml 

PLAY [linux] ********************************************************************************************************************

TASK [Remove the user 'johnd'] **************************************************************************************************
changed: [linux1]

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

$ id johnd   
id: johnd: no such user      # johnd ユーザーが削除された
$ sudo ls -al /home/johnd/   
ls: cannot access /home/johnd/: No such file or directory  # johnd のホームディレクトリも削除された

無事に指定した通りに、ユーザー johnd とホームディレクトリも削除されました。


■ まとめ

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

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

docs.ansible.com

また、user モジュールは System modules に分類されています。System modules には、他にも、グループを管理する group や、cron エントリを管理する cron 、などのモジュールがあります。詳細は System modules の一覧からご確認ください。