はじめてのAnsible #5 システム変数Fact編

前回はテンプレートエンジンを使ったファイルの作成方法についてご紹介しました。今回はAnsibleが内部で用意しているグローバル変数Factについてまとめていきます。

Factの一覧を確認する

Ansibleにはシステム全体で利用するグローバル変数のような物が用意されており、必要に応じてPlaybookなどから利用することができます。これをFactと呼びます。

Factに何が設定されているかは次のコマンドですべて表示することができます。serversの部分はInventoryファイルに記載したグループ名を指定してください。実際に実行すると非常に長大なリストがJSON形式で表示されます。

$ ansible servers -m setup
example.com | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.0.1"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::7c20:5cff:fe3d:4efd"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "08/24/2006",
        "ansible_bios_version": "4.2.amazon",
()

FactはPlaybookからも参照できます。これを利用して例えばOSの種類ごとに実行するタスクを変更するなど細かい制御を行うことが可能になります。

Factの利用方法

Playbookの「条件」に利用する

Factにはansible_factsという名前のハッシュを参照することでアクセスできます。お手軽すぎてちょっと怖いですねw 以下はOSのdistributionがAmazonLinuxであればshellモジュールを実行するタスクの例になります。

- hosts: servers
  remote_user: ansibleman
  tasks:
    - name: write date if AmazonLinux
      shell: date >> ansible_fact.log
      when: ansible_facts["distribution"] == "Amazon"

Factの一覧を表示するコマンドではキーがansible_distributionとなっていましたが、最初の接頭詞ansible_は削除し残った文字列を利用します。

$ ansible servers -m setup | grep distribution
        "ansible_distribution": "Amazon",

Playbookの「変数」に利用する

もちろんFactは変数の中でも利用できます。次の例ではファイルをコピーする際に、ディストリビューション毎に設定ファイルを切り替える処理に利用しています。

- hosts: servers
  remote_user: ansibleman
  vars:
    path: "conf/{{ ansible_facts[\"distribution\"] }}.conf"
  tasks:
    - name: write date if AmazonLinux
      copy:
        src: "{{ path }}"
        dest: ~/foo.conf

ファイル構成は以下の通り。confディレクトリの下にdistribution毎にファイルを用意してやります。

~/📁 ansible1st
    📒 ansible.cfg
    📒 playbook.yml
   📁 inventory
      📒 production
   📁 conf
        📒 Amazon.conf  ★NEW
        📒 CentOS.conf  ★NEW
        📒 RedHat.conf  ★NEW

ちなみにFactへのアクセスはJavaScriptのようにansible_facts.distributionとドットで区切る形式でも同じ意味になります。変数の一部として利用する際にはこちらの方がエスケープしなくて良いので使いやすいですね。お好みの書き方でどうぞ。

  vars:
    path: "conf/{{ ansible_facts.distribution }}.conf"

templateで利用する

前回まとめたテンプレートの中でも利用できます。

Playbookの方は特別なことをする必要はありません。

- hosts: servers
  remote_user: ansibleman
  tasks:
    - name: include fact variable
      template:
        src: foo.txt.j2
        dest: ~/foo.txt

Jinja2側の変数にそのままansible_factsを書くことができます。

このサーバのディストリビューションは{{ ansible_facts.distribution }}です。

実際に実行すると埋め込まれているのがわかりますね。

$ ansible-playbook playbook.yml
$ ssh ansibleman@exmaple.com

[exmaple.com] $ cat foo.txt
このサーバのディストリビューションはAmazonです。

Local Factsを設定する

管理対象のサーバへ事前にFactを定義したファイルを配置しておき、それをPlaybook上から呼び出すことができます。これをLocal Factsと呼びます。例えば特定のサーバだけ特別な処理をしたいといった場合のトリガーに使うなど、アイデア次第で色々と活用できると思います。

公式ドキュメントによると、設置場所やフォーマットは以下の通り。

If a remotely managed system has an /etc/ansible/facts.d directory, any files in this directory ending in .fact, can be JSON, INI, or executable files returning JSON, and these can supply local facts in Ansible. An alternate directory can be specified using the fact_path play keyword.

  • /etc/ansible/facts.dの下にファイルを配置する
  • ファイル形式はJSONかINI
  • ファイルの拡張子は.fact

では試しにやってみましょう。

サーバ側にfactファイルを用意

対象のサーバにログインし、所定の場所にファイルを置きます。今回はINI形式にしましたが、JSONでももちろん大丈夫。

$ ssh katsube@example.com

[example.com] $ sudo mkdir -p /etc/ansible/facts.d
[example.com] $ sudo vi /etc/ansible/facts.d/preferences.fact
[general]
message = Hiya, Georgie!

/etc/ansible/facts.d/ディレクトリ配下に複数のファイルを配置した場合、拡張子が.factになっていればすべて読みこんでくれます。何らかの理由でファイルを分割したい場合でも対応可能です。

パスを変更するには?

Playbook上でfact_pathの項目を追加するだけでOKです。

- hosts: servers
  fact_path: ~/fact.d/

Playbookから参照する

Local Factの場合はansible_localを参照します。

- hosts: servers
  remote_user: ansibleman
  tasks:
    - name: include local fact variable
      shell: echo "{{ ansible_local.preferences.general.message }}" >> ansible-fact.log

何となく想像がつくと思いますが、ansible_local[ファイル名][グループ][項目]という具合に指定してやります。

実行する

実行すると、無事に事前に設定した値を取得することができました。

$ ansible-playbook playbook.yml
$ ssh ansibleman@exmaple.com

[exmaple.com] $ cat ansible-fact.log
Hiya, Georgie!

繰り返しになりますがLocal Factsの定義ファイルはPlaybookを実行するPCではなく、管理対象のサーバに置く必要があることに注意してください。

その他

Factを無効にする

Factを利用しないことが最初からわかっている場合はgather_factsnoを設定することで無効化することができます。

- hosts: servers
  remote_user: ansibleman
  gather_facts: no
  tasks:
    - name: foobar
      shell: ls

AnsibleがFactに情報を収集する処理はそれなりに時間がかかります。大量のサーバ群に対して実行するようなケースで、予め環境情報が判明しているような場合はOFFにした方が実行速度を上げることが期待できます。

Fact一覧

最後にansible servers -m setupの実行結果を掲載します。ここではAmazonLinuxに対して実行しています。

{
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "172.26.4.60"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::467:75ff:fe48:c35c"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "08/24/2006",
        "ansible_bios_version": "4.2.amazon",
        "ansible_cmdline": {
            "KEYTABLE": "us",
            "LANG": "en_US.UTF-8",
            "console": "ttyS0",
            "nvme_core.io_timeout": "4294967295",
            "root": "LABEL=/",
            "selinux": "0"
        },
        "ansible_date_time": {
            "date": "2019-11-08",
            "day": "08",
            "epoch": "1573220817",
            "hour": "22",
            "iso8601": "2019-11-08T13:46:57Z",
            "iso8601_basic": "20191108T224657973495",
            "iso8601_basic_short": "20191108T224657",
            "iso8601_micro": "2019-11-08T13:46:57.973578Z",
            "minute": "46",
            "month": "11",
            "second": "57",
            "time": "22:46:57",
            "tz": "JST",
            "tz_offset": "+0900",
            "weekday": "金曜日",
            "weekday_number": "5",
            "weeknumber": "44",
            "year": "2019"
        },
        "ansible_default_ipv4": {
            "address": "172.26.4.60",
            "alias": "eth0",
            "broadcast": "172.26.15.255",
            "gateway": "172.26.0.1",
            "interface": "eth0",
            "macaddress": "06:67:75:48:c3:5c",
            "mtu": 9001,
            "netmask": "255.255.240.0",
            "network": "172.26.0.0",
            "type": "ether"
        },
        "ansible_default_ipv6": {},
        "ansible_device_links": {
            "ids": {},
            "labels": {
                "xvda1": [
                    "\\x2f"
                ]
            },
            "masters": {},
            "uuids": {
                "xvda1": [
                    "f25f5092-0401-4edb-9fac-c57f3c673803"
                ]
            }
        },
        "ansible_devices": {
            "xvda": {
                "holders": [],
                "host": "",
                "links": {
                    "ids": [],
                    "labels": [],
                    "masters": [],
                    "uuids": []
                },
                "model": null,
                "partitions": {
                    "xvda1": {
                        "holders": [],
                        "links": {
                            "ids": [],
                            "labels": [
                                "\\x2f"
                            ],
                            "masters": [],
                            "uuids": [
                                "f25f5092-0401-4edb-9fac-c57f3c673803"
                            ]
                        },
                        "sectors": "167768031",
                        "sectorsize": 512,
                        "size": "80.00 GB",
                        "start": "4096",
                        "uuid": "f25f5092-0401-4edb-9fac-c57f3c673803"
                    }
                },
                "removable": "0",
                "rotational": "0",
                "sas_address": null,
                "sas_device_handle": null,
                "scheduler_mode": "noop",
                "sectors": "167772160",
                "sectorsize": "512",
                "size": "80.00 GB",
                "support_discard": "0",
                "vendor": null,
                "virtual": 1
            }
        },
        "ansible_distribution": "Amazon",
        "ansible_distribution_file_parsed": true,
        "ansible_distribution_file_path": "/etc/system-release",
        "ansible_distribution_file_variety": "Amazon",
        "ansible_distribution_major_version": "2018",
        "ansible_distribution_release": "NA",
        "ansible_distribution_version": "NA",
        "ansible_dns": {
            "nameservers": [
                "172.26.0.2"
            ],
            "options": {
                "attempts": "5",
                "timeout": "2"
            },
            "search": [
                "ap-northeast-1.compute.internal"
            ]
        },
        "ansible_domain": "ap-northeast-1.compute.internal",
        "ansible_effective_group_id": 503,
        "ansible_effective_user_id": 502,
        "ansible_env": {
            "AWS_AUTO_SCALING_HOME": "/opt/aws/apitools/as",
            "AWS_CLOUDWATCH_HOME": "/opt/aws/apitools/mon",
            "AWS_ELB_HOME": "/opt/aws/apitools/elb",
            "AWS_PATH": "/opt/aws",
            "EC2_AMITOOL_HOME": "/opt/aws/amitools/ec2",
            "EC2_HOME": "/opt/aws/apitools/ec2",
            "HOME": "/home/ansibleman",
            "JAVA_HOME": "/usr/lib/jvm/jre",
            "LANG": "ja_JP.UTF-8",
            "LESSOPEN": "||/usr/bin/lesspipe.sh %s",
            "LESS_TERMCAP_mb": "\u001b[01;31m",
            "LESS_TERMCAP_md": "\u001b[01;38;5;208m",
            "LESS_TERMCAP_me": "\u001b[0m",
            "LESS_TERMCAP_se": "\u001b[0m",
            "LESS_TERMCAP_ue": "\u001b[0m",
            "LESS_TERMCAP_us": "\u001b[04;38;5;111m",
            "LOGNAME": "ansibleman",
            "MAIL": "/var/mail/ansibleman",
            "PATH": "/usr/local/bin:/bin:/usr/bin:/opt/aws/bin",
            "PWD": "/home/ansibleman",
            "PYTHON_INSTALL_LAYOUT": "amzn",
            "SHELL": "/bin/bash",
            "SHLVL": "2",
            "SSH_CLIENT": "110.233.244.246 52359 50022",
            "SSH_CONNECTION": "110.233.244.246 52359 172.26.4.60 50022",
            "SSH_TTY": "/dev/pts/1",
            "TERM": "xterm-256color",
            "USER": "ansibleman",
            "_": "/usr/bin/python"
        },
        "ansible_eth0": {
            "active": true,
            "device": "eth0",
            "features": {
                "esp_hw_offload": "off [fixed]",
                "esp_tx_csum_hw_offload": "off [fixed]",
                "fcoe_mtu": "off [fixed]",
                "generic_receive_offload": "on",
                "generic_segmentation_offload": "on",
                "highdma": "off [fixed]",
                "hw_tc_offload": "off [fixed]",
                "l2_fwd_offload": "off [fixed]",
                "large_receive_offload": "off [fixed]",
                "loopback": "off [fixed]",
                "netns_local": "off [fixed]",
                "ntuple_filters": "off [fixed]",
                "receive_hashing": "off [fixed]",
                "rx_all": "off [fixed]",
                "rx_checksumming": "on [fixed]",
                "rx_fcs": "off [fixed]",
                "rx_udp_tunnel_port_offload": "off [fixed]",
                "rx_vlan_filter": "off [fixed]",
                "rx_vlan_offload": "off [fixed]",
                "rx_vlan_stag_filter": "off [fixed]",
                "rx_vlan_stag_hw_parse": "off [fixed]",
                "scatter_gather": "on",
                "tcp_segmentation_offload": "on",
                "tx_checksum_fcoe_crc": "off [fixed]",
                "tx_checksum_ip_generic": "off [fixed]",
                "tx_checksum_ipv4": "on [fixed]",
                "tx_checksum_ipv6": "off [requested on]",
                "tx_checksum_sctp": "off [fixed]",
                "tx_checksumming": "on",
                "tx_esp_segmentation": "off [fixed]",
                "tx_fcoe_segmentation": "off [fixed]",
                "tx_gre_csum_segmentation": "off [fixed]",
                "tx_gre_segmentation": "off [fixed]",
                "tx_gso_partial": "off [fixed]",
                "tx_gso_robust": "on [fixed]",
                "tx_ipxip4_segmentation": "off [fixed]",
                "tx_ipxip6_segmentation": "off [fixed]",
                "tx_lockless": "off [fixed]",
                "tx_nocache_copy": "off",
                "tx_scatter_gather": "on",
                "tx_scatter_gather_fraglist": "off [fixed]",
                "tx_sctp_segmentation": "off [fixed]",
                "tx_tcp6_segmentation": "off [requested on]",
                "tx_tcp_ecn_segmentation": "off [fixed]",
                "tx_tcp_mangleid_segmentation": "off",
                "tx_tcp_segmentation": "on",
                "tx_udp_tnl_csum_segmentation": "off [fixed]",
                "tx_udp_tnl_segmentation": "off [fixed]",
                "tx_vlan_offload": "off [fixed]",
                "tx_vlan_stag_hw_insert": "off [fixed]",
                "udp_fragmentation_offload": "off",
                "vlan_challenged": "off [fixed]"
            },
            "hw_timestamp_filters": [],
            "ipv4": {
                "address": "172.26.4.60",
                "broadcast": "172.26.15.255",
                "netmask": "255.255.240.0",
                "network": "172.26.0.0"
            },
            "ipv6": [
                {
                    "address": "fe80::467:75ff:fe48:c35c",
                    "prefix": "64",
                    "scope": "link"
                }
            ],
            "macaddress": "06:67:75:48:c3:5c",
            "module": "xen_netfront",
            "mtu": 9001,
            "pciid": "vif-0",
            "promisc": false,
            "timestamping": [
                "rx_software",
                "software"
            ],
            "type": "ether"
        },
        "ansible_fibre_channel_wwn": [],
        "ansible_fips": false,
        "ansible_form_factor": "Other",
        "ansible_fqdn": "ip-172-26-4-60.ap-northeast-1.compute.internal",
        "ansible_hostname": "ip-172-26-4-60",
        "ansible_hostnqn": "",
        "ansible_interfaces": [
            "lo",
            "eth0"
        ],
        "ansible_is_chroot": false,
        "ansible_iscsi_iqn": "",
        "ansible_kernel": "4.14.114-82.97.amzn1.x86_64",
        "ansible_lo": {
            "active": true,
            "device": "lo",
            "features": {
                "esp_hw_offload": "off [fixed]",
                "esp_tx_csum_hw_offload": "off [fixed]",
                "fcoe_mtu": "off [fixed]",
                "generic_receive_offload": "on",
                "generic_segmentation_offload": "on",
                "highdma": "on [fixed]",
                "hw_tc_offload": "off [fixed]",
                "l2_fwd_offload": "off [fixed]",
                "large_receive_offload": "off [fixed]",
                "loopback": "on [fixed]",
                "netns_local": "on [fixed]",
                "ntuple_filters": "off [fixed]",
                "receive_hashing": "off [fixed]",
                "rx_all": "off [fixed]",
                "rx_checksumming": "on [fixed]",
                "rx_fcs": "off [fixed]",
                "rx_udp_tunnel_port_offload": "off [fixed]",
                "rx_vlan_filter": "off [fixed]",
                "rx_vlan_offload": "off [fixed]",
                "rx_vlan_stag_filter": "off [fixed]",
                "rx_vlan_stag_hw_parse": "off [fixed]",
                "scatter_gather": "on",
                "tcp_segmentation_offload": "on",
                "tx_checksum_fcoe_crc": "off [fixed]",
                "tx_checksum_ip_generic": "on [fixed]",
                "tx_checksum_ipv4": "off [fixed]",
                "tx_checksum_ipv6": "off [fixed]",
                "tx_checksum_sctp": "on [fixed]",
                "tx_checksumming": "on",
                "tx_esp_segmentation": "off [fixed]",
                "tx_fcoe_segmentation": "off [fixed]",
                "tx_gre_csum_segmentation": "off [fixed]",
                "tx_gre_segmentation": "off [fixed]",
                "tx_gso_partial": "off [fixed]",
                "tx_gso_robust": "off [fixed]",
                "tx_ipxip4_segmentation": "off [fixed]",
                "tx_ipxip6_segmentation": "off [fixed]",
                "tx_lockless": "on [fixed]",
                "tx_nocache_copy": "off [fixed]",
                "tx_scatter_gather": "on [fixed]",
                "tx_scatter_gather_fraglist": "on [fixed]",
                "tx_sctp_segmentation": "on",
                "tx_tcp6_segmentation": "on",
                "tx_tcp_ecn_segmentation": "on",
                "tx_tcp_mangleid_segmentation": "on",
                "tx_tcp_segmentation": "on",
                "tx_udp_tnl_csum_segmentation": "off [fixed]",
                "tx_udp_tnl_segmentation": "off [fixed]",
                "tx_vlan_offload": "off [fixed]",
                "tx_vlan_stag_hw_insert": "off [fixed]",
                "udp_fragmentation_offload": "off",
                "vlan_challenged": "on [fixed]"
            },
            "hw_timestamp_filters": [],
            "ipv4": {
                "address": "127.0.0.1",
                "broadcast": "host",
                "netmask": "255.0.0.0",
                "network": "127.0.0.0"
            },
            "ipv6": [
                {
                    "address": "::1",
                    "prefix": "128",
                    "scope": "host"
                }
            ],
            "mtu": 65536,
            "promisc": false,
            "timestamping": [
                "tx_software",
                "rx_software",
                "software"
            ],
            "type": "loopback"
        },
        "ansible_local": {
            "preferences": {
                "general": {
                    "message": "Hiya, Georgie!"
                }
            }
        },
        "ansible_lsb": {},
        "ansible_machine": "x86_64",
        "ansible_machine_id": "9aa32d5b81fc647aabb1f5835cd420af",
        "ansible_memfree_mb": 344,
        "ansible_memory_mb": {
            "nocache": {
                "free": 3557,
                "used": 388
            },
            "real": {
                "free": 344,
                "total": 3945,
                "used": 3601
            },
            "swap": {
                "cached": 0,
                "free": 1023,
                "total": 1023,
                "used": 0
            }
        },
        "ansible_memtotal_mb": 3945,
        "ansible_mounts": [
            {
                "block_available": 18312080,
                "block_size": 4096,
                "block_total": 20609191,
                "block_used": 2297111,
                "device": "/dev/xvda1",
                "fstype": "ext4",
                "inode_available": 5153095,
                "inode_total": 5242880,
                "inode_used": 89785,
                "mount": "/",
                "options": "rw,noatime,data=ordered",
                "size_available": 75006279680,
                "size_total": 84415246336,
                "uuid": "f25f5092-0401-4edb-9fac-c57f3c673803"
            }
        ],
        "ansible_nodename": "ip-172-26-4-60",
        "ansible_os_family": "RedHat",
        "ansible_pkg_mgr": "dnf",
        "ansible_proc_cmdline": {
            "KEYTABLE": "us",
            "LANG": "en_US.UTF-8",
            "console": [
                "tty1",
                "ttyS0"
            ],
            "nvme_core.io_timeout": "4294967295",
            "root": "LABEL=/",
            "selinux": "0"
        },
        "ansible_processor": [
            "0",
            "GenuineIntel",
            "Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz",
            "1",
            "GenuineIntel",
            "Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz"
        ],
        "ansible_processor_cores": 2,
        "ansible_processor_count": 1,
        "ansible_processor_threads_per_core": 1,
        "ansible_processor_vcpus": 2,
        "ansible_product_name": "HVM domU",
        "ansible_product_serial": "NA",
        "ansible_product_uuid": "NA",
        "ansible_product_version": "4.2.amazon",
        "ansible_python": {
            "executable": "/usr/bin/python",
            "has_sslcontext": true,
            "type": "CPython",
            "version": {
                "major": 2,
                "micro": 16,
                "minor": 7,
                "releaselevel": "final",
                "serial": 0
            },
            "version_info": [
                2,
                7,
                16,
                "final",
                0
            ]
        },
        "ansible_python_version": "2.7.16",
        "ansible_real_group_id": 503,
        "ansible_real_user_id": 502,
        "ansible_selinux": {
            "status": "Missing selinux Python library"
        },
        "ansible_selinux_python_present": false,
        "ansible_service_mgr": "upstart",
        "ansible_ssh_host_key_dsa_public": "(公開鍵)",
        "ansible_ssh_host_key_ecdsa_public": "(公開鍵)",
        "ansible_ssh_host_key_ed25519_public": "(公開鍵)",
        "ansible_ssh_host_key_rsa_public": "(公開鍵)",
        "ansible_swapfree_mb": 1023,
        "ansible_swaptotal_mb": 1023,
        "ansible_system": "Linux",
        "ansible_system_capabilities": [
            ""
        ],
        "ansible_system_capabilities_enforced": "True",
        "ansible_system_vendor": "Xen",
        "ansible_uptime_seconds": 15630159,
        "ansible_user_dir": "/home/ansibleman",
        "ansible_user_gecos": "",
        "ansible_user_gid": 503,
        "ansible_user_id": "ansibleman",
        "ansible_user_shell": "/bin/bash",
        "ansible_user_uid": 502,
        "ansible_userspace_architecture": "x86_64",
        "ansible_userspace_bits": "64",
        "ansible_virtualization_role": "guest",
        "ansible_virtualization_type": "xen",
        "gather_subset": [
            "all"
        ],
        "module_setup": true
    },
    "changed": false
}

参考ページ

Ansible実践ガイド 第3版 (impress top gear)
北⼭ 晋吾 佐藤 学 塚本 正隆 畠中幸司 横地 晃
インプレス (2019-10-18)
売り上げランキング: 17,753