2022年8月27日土曜日

AnsibleでESXi上に仮想マシンを構築する

これまでAnsibleで、LinuxやWindows Serverの初期構築と単体テストを実施してきた。今回は、Ansibleを使ってESXi上に仮想マシンを構築するPlaybookを作ってみたので紹介する。なお、私の環境の問題でESXiを用いているが、少し修正すればvCenter Server経由でも実行することができるだろう。

今までのAnsible関連記事

環境

コントロールノードのLinuxのOSは、AlmaLinuxにて構築している。実行対象のESXiは、ESXi 6.7となる。なお、ESXiの無償ライセンスではPlaybookを実行できないため注意する。

  • コントロールノード
    • OS : AlmaLinux 8.5
    • Ansible : ansible [core 2.13.1]
  • Ansible実行対象サーバ
    • OS : ESXi 6.7

Ansibleコントロールノードでの設定

1. pyVmomiをインストール

PythonでESXiやvCenter Serverの管理をできるようpyVmomiをインストールする。これはpipを使ってインストールすればよい。

# pip install pyvmomi

2. vSphere Automation Python SDKをインストール

pyVmomiとは別に、vSphere Automation Python SDKもインストールを行う。こちらは、gitを使う必要があるので、gitをインストールしたのち、pipにてインストールを行う。

# dnf install git -y
# pip install --upgrade git+https://github.com/vmware/vsphere-automation-sdk-python.git
Collecting git+https://github.com/vmware/vsphere-automation-sdk-python.git
  Cloning https://github.com/vmware/vsphere-automation-sdk-python.git to /tmp/pip-req-build-qjay0ffh
  Running command git clone --filter=blob:none --quiet https://github.com/vmware/vsphere-automation-sdk-python.git /tmp/pip-req-build-qjay0ffh
  Resolved https://github.com/vmware/vsphere-automation-sdk-python.git to commit 4bad74ad6be25a41fef7f0bfaa13a17b25039d25
  Preparing metadata (setup.py) ... done

~(中略)~

Installing collected packages: lxml, pyOpenSSL, vapi-runtime, vapi-common-client, vapi-client-bindings, vmc-draas-client-bindings, vmc-client-bindings, nsx-vmc-policy-python-sdk, nsx-vmc-aws-integration-python-sdk, nsx-python-sdk, nsx-policy-python-sdk, vSphere-Automation-SDK
  Running setup.py install for vSphere-Automation-SDK ... done
Successfully installed lxml-4.9.1 nsx-policy-python-sdk-3.1.5.0.0 nsx-python-sdk-3.1.5.0.0 nsx-vmc-aws-integration-python-sdk-3.1.5.0.0 nsx-vmc-policy-python-sdk-3.1.5.0.0 pyOpenSSL-22.0.0 vSphere-Automation-SDK-1.78.0 vapi-client-bindings-3.8.0 vapi-common-client-2.34.0 vapi-runtime-2.34.0 vmc-client-bindings-1.60.0 vmc-draas-client-bindings-1.19.0
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

3. 動作確認

これだけでESXiやvCenter Serverに対してAnsible実行ができるようになる。テスト用として、ESXiの情報を取得するPlaybookを作成したので、実際にPlaybookを実行して、問題なく実行できることを確認してみよう。

インベントリーファイル

hostsというファイル名で以下の通り1台のESXiを記載したインベントリーファイルを作成した。

[vmware_servers]
esxi01 ansible_host=192.168.1.1

Playbook

test_esxi_connection.ymlというファイル名で以下の通りPlaybookを作成した。ユーザ名、パスワードなどの変数は、環境に合わせて修正すること。

---
- name: Test esxi connection
  gather_facts: false
  hosts: vmware_servers

  vars:
    ansible_connection: local
    ansible_python_interpreter: /usr/bin/python3.8
    esxi_username: root
    esxi_password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      37316332366436396537613265366334323663646463306163353665323836636362323334313763
      ~(中略)~
      3730

  tasks:
    - name: Get vmware host facts
      community.vmware.vmware_host_facts:
        hostname: "{{ inventory_hostname }}"
        username: "{{ esxi_username }}"
        password: "{{ esxi_password }}"
        validate_certs: false
      register: result

    - name: Show vmware host facts
      ansible.builtin.debug:
        msg: "{{ result }}"

実行結果

ok=2となることが確認できればOKとなる。

# ansible-playbook -i hosts test_esxi_connection.yml 

PLAY [Create vmware vm] ********************************************************************************************

TASK [Get vmware host facts] ***************************************************************************************
ok: [esxi01]

TASK [Show vmware host facts] **************************************************************************************
ok: [esxi01] => {
    "msg": {
        "ansible_facts": {
            "ansible_all_ipv4_addresses": [
                "192.168.1.1",
                "192.168.2.1"
            ],
            "ansible_bios_date": "2018-07-12T00:00:00+00:00",
            "ansible_bios_version": "P3.00",
            "ansible_datastore": [
                {
                    "free": "326.41 GB",
                    "name": "Datastore_01",
                    "total": "458.25 GB"
                },
                {
                    "free": "641.69 GB",
                    "name": "Datastore_02",
                    "total": "931.25 GB"
                },
                {
                    "free": "584.54 GB",
                    "name": "nfs_01",
                    "total": "1.94 TB"
                }
            ],
            "ansible_distribution": "VMware ESXi",
            "ansible_distribution_build": "18828794",
            "ansible_distribution_version": "6.7.0",
            "ansible_hostname": "esxi01",
            "ansible_in_maintenance_mode": false,
            "ansible_interfaces": [
                "vmk0",
                "vmk1"
            ],
            "ansible_memfree_mb": 6108,
            "ansible_memtotal_mb": 32420,
            "ansible_os_type": "vmnix-x86",
            "ansible_processor": "Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz",
            "ansible_processor_cores": 6,
            "ansible_processor_count": 1,
            "ansible_processor_vcpus": 6,
            "ansible_product_name": "To Be Filled By O.E.M.",
            "ansible_product_serial": "To Be Filled By O.E.M.",
            "ansible_system_vendor": "To Be Filled By O.E.M.",
            "ansible_uptime": 13418913,
            "ansible_uuid": "8cc28570-1edb-0000-0000-000000000000",
            "ansible_vmk0": {
                "device": "vmk0",
                "ipv4": {
                    "address": "192.168.1.1",
                    "netmask": "255.255.255.0"
                },
                "macaddress": "70:85:c2:8c:xx:xx",
                "mtu": 1500
            },
            "ansible_vmk1": {
                "device": "vmk1",
                "ipv4": {
                    "address": "192.168.2.1",
                    "netmask": "255.255.255.0"
                },
                "macaddress": "00:50:56:64:xx:xx",
                "mtu": 1500
            },
            "cluster": null,
            "host_date_time": {
                "date": "2022-08-21",
                "day": "21",
                "epoch": "1661038759",
                "hour": "08",
                "iso8601": "2022-08-21T08:39:19Z",
                "iso8601_basic": "20220821T083919210083",
                "iso8601_basic_short": "20220821T083919",
                "iso8601_micro": "2022-08-21T08:39:19.210083Z",
                "minute": "39",
                "month": "08",
                "second": "19",
                "time": "08:39:19",
                "tz": "UTC",
                "tz_offset": "+0000",
                "weekday": "日曜日",
                "weekday_number": "0",
                "weeknumber": "33",
                "year": "2022"
            },
            "vsan_cluster_uuid": null,
            "vsan_health": "unknown",
            "vsan_node_uuid": null
        },
        "changed": false,
        "failed": false
    }
}

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

ESXi上に仮想マシンを作成するPlaybook

Playbookのファイルの記載内容は大きく以下のように構成される。

設定項目 説明
hosts 実行対象のサーバを記載する。サーバのリストは別ファイルで作成し、そのファイルの中で実行する対象のサーバやグループを指定する。
vars 変数を記載する。ここで記載した変数は{{ 変数名 }}という形で参照できる。また、変数は配列して複数の要素を持たせることもできる。
tasks Ansibleで実行するメインの処理を記載する。
handlers Ansibleでメイン処理実行状況に応じて実行する事後処理を記載する。例えば、設定ファイル更新がされた場合 (実行結果がchangedの場合) は設定反映のためにサービス再起動を行うといった使い方をする。今回のPlaybookでは未使用となる。

今回はcreate_vmware_vm.ymlというファイル1つにすべての内容を記載して作成した。以下にそれぞれの記載内容を説明する。

hosts : 実行対象のESXiを指定

hostsというファイル名で以下の通り1台のESXiを記載したインベントリーファイルを作成した。

[vmware_servers]
esxi01 ansible_host=192.168.1.1

vars : 変数①(ESXiに接続するための変数)

変数名 内容
ansible_connection ESXiへの接続の場合はlocalhostを使用する。
ansible_python_interpreter Pythonの実行パスを指定する。これを指定しない場合、「The error was: ModuleNotFoundError: No module named 'pyVim'」といったモジュールが見つからないというメッセージが表示され、タスクの実行に失敗する。
esxi_username ESXiへ接続するためのユーザ名を指定する。
esxi_password ESXiへ接続するためのパスワードを指定する。平文で記載するとセキュリティ上よろしくないので、Ansible Vaultを使って暗号化して記載しよう。
  vars:
    ansible_connection: local
    ansible_python_interpreter: /usr/bin/python3.8
    esxi_username: root
    esxi_password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      37316332366436396537613265366334323663646463306163353665323836636362323334313763
      ~(中略)~
      3730

vars : 変数②(仮想マシンのパラメータ)

変数名 内容
vm_name 仮想マシン名を指定する。
vm_annotation 仮想マシンの注釈を指定する。
vm_folder 仮想マシンのフォルダを指定する。ESXiにはフォルダの概念がないが、指定が必須となる。この場合は/を指定する。
vm_guest_id 仮想マシンのOS種別を指定する。OS種別とIDの紐づけはVMwareのドキュメントを参照。今回はother4xLinux64Guestを指定する。
vm_hardware 仮想マシンのハードウェアとして、CPU数とメモリ容量を指定する。
vm_disk 仮想マシンのハードデイスクを指定する。
vm_networks 仮想マシンのNICを指定する。
vm_cdrom 仮想マシンの光学ドライブを指定する。今回はOSのインストールメディアであるISOファイルをマウントさせるため、ISOファイルのパスを指定する。
vm_boot 起動時にEFIを使用し、セキュアブートを有効化するよう指定する。
vm_usb_type USBコントローラとして、USB 2.0のコントローラを指定する。
  vars:
      ~(中略)~
    vm_name: ansible-test
    vm_annotation: "Create Ansible"
    vm_folder: /
    vm_guest_id: other4xLinux64Guest
    vm_hardware:
      num_cpus: 2
      num_cpu_cores_per_socket: 2
      memory_mb: 2048
    vm_disk:
      size_gb: 16
      type: thin
      datastore: Datastore-01
    vm_networks:
      name: Network_01
      device_type: vmxnet3
    vm_cdrom:
      iso_path: '[nfs_01] ISO/AlmaLinux/AlmaLinux-8.5-x86_64-minimal.iso'
    vm_boot:
      boot_firmware: efi
      secure_boot_enabled: true
    vm_usb_type: usb2

tasks : 処理内容

VMwareのAnsibleモジュールでは、毎回実行対象ESXiのホスト名、ユーザ名、パスワード、証明書検証の無効化を指定する必要があるため、以下4行を各タスクに設定するようにしよう。

        hostname: "{{ inventory_hostname }}"
        username: "{{ esxi_username }}"
        password: "{{ esxi_password }}"
        validate_certs: false

各タスクの処理概要を以下に記載する。
※モジュール名はFQCNで記載すると長くなるため名前だけの記載にしているが、PlaybookにはFQCNで記載することを推奨する。

タスク名 モジュール 設定
Create vm vmware_guest 仮想マシンを作成する。作成する仮想マシン名nameを指定し、CPU、メモリ、光学ドライブ、ディスク、ネットワークなどを設定する。ただし、一部起動時の設定やUSBコントローラの追加はできないため、それについては他タスクにて対応する。
Set boot settings vmware_guest_boot_manager 起動時BIOS/EFIの設定を行う。
Set usb controller vmware_guest_controller USBコントローラを追加する。
  tasks:
    # 仮想マシン作成
    - name: Create vm
      community.vmware.vmware_guest:
        hostname: "{{ inventory_hostname }}"
        username: "{{ esxi_username }}"
        password: "{{ esxi_password }}"
        validate_certs: false
        name: "{{ vm_name }}"
        annotation: "{{ vm_annotation }}"
        folder: "{{ vm_folder }}"
        guest_id: "{{ vm_guest_id }}"
        hardware:
          num_cpus: "{{ vm_hardware.num_cpus }}"
          num_cpu_cores_per_socket: "{{ vm_hardware.num_cpu_cores_per_socket }}"
          memory_mb: "{{ vm_hardware.memory_mb }}"
        cdrom:
          - controller_number: 0
            controller_type: sata
            unit_number: 0
            iso_path: "{{ vm_cdrom.iso_path }}"
            type: iso
        disk:
          - size_gb: "{{ vm_disk.size_gb }}"
            type: "{{ vm_disk.type }}"
            datastore: "{{ vm_disk.datastore }}"
        networks:
          - name: "{{ vm_networks.name }}"
            device_type: "{{ vm_networks.device_type }}"
            start_connected: true
        state: present

    # BIOS/EFI設定
    - name: Set boot settings
      community.vmware.vmware_guest_boot_manager:
        hostname: "{{ inventory_hostname }}"
        username: "{{ esxi_username }}"
        password: "{{ esxi_password }}"
        validate_certs: false
        name: "{{ vm_name }}"
        boot_firmware: "{{ vm_boot.boot_firmware }}"
        secure_boot_enabled: "{{ vm_boot.secure_boot_enabled }}"

    # USBコントローラ設定
    - name: Set usb controller
      community.vmware.vmware_guest_controller:
        hostname: "{{ inventory_hostname }}"
        username: "{{ esxi_username }}"
        password: "{{ esxi_password }}"
        validate_certs: false
        name: "{{ vm_name }}"
        controllers:
          - state: present
            type: "{{ vm_usb_type }}"

実行結果

実際にPlaybookを実行した結果を以下に記載する。

# ansible-playbook -i hosts create_vmware_vm.yml 

PLAY [Create vmware vm] *********************************************************************************************

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

TASK [Create vm] ****************************************************************************************************
changed: [esxi01]

TASK [Set boot settings] ********************************************************************************************
changed: [esxi01]

TASK [Set usb controller] *******************************************************************************************
changed: [esxi01]

PLAY RECAP **********************************************************************************************************
esxi01                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

実際にESXi上で作成した仮想マシンを確認すると、設定したパラメータ通りに作成が成功していた。


Ansibleを使ってESXi上に仮想マシンを構築するPlaybookの説明は以上となる。

参照

2022年8月20日土曜日

AnsibleでWindows Serverの初期設定と単体テストを行う

今回は、Ansibleを使ってWindows Serverの初期設定と単体テストを行うPlaybookを作成したので紹介する。作成する中で、Playbookのテクニックについても知見を得られたので、それも併せて記載する。

Windows Serverの初期設定とは、例えば役割と機能の追加や必要なサービスの起動・不要なサービスの停止作業が該当する。また、Windows Serverの場合はActive Directoryのドメインに参加をすることが多いので、その作業もAnsibleにて自動化してみた。

今までのAnsible関連記事

環境

コントロールノードのLinuxのOSは、AlmaLinuxにて構築している。実行対象のWindows Serverは、Windows Server 2019とした。

  • コントロールノード
    • OS : AlmaLinux 8.5
    • Ansible : ansible [core 2.13.1]
  • Ansible実行対象サーバ
    • OS : Windows Server 2019

Playbookの処理概要

今回のPlaybookはWindows Serverの初期構築を行うものとなるが、大きく「構築パート」と「単体テストパート」に分けて作成している。

構築パートでAnsibleのモジュールを用いてWindows Serverの各種設定や役割と機能の追加などを行う。

単体テストパートでは、構築パートで実施した設定項目と一対一となるように設定確認を行う。しかし本来、Ansibleは冪等性 (処理を何回行っても必ず同じ結果となる性質) があるため、構築パートでAnsibleの処理が成功したことを確認できれば必ず正しい設定が反映されているはずであり、わざわざ単体テストは不要と感じるかもしれない。

ただし、再度設定値確認として単体テストを行うことで、Ansibleでは処理が成功していても何らかの理由で設定が反映されていない可能性を排除できることや、Ansibleの構築処理とは別に設定変更をさせずに単体テストを行うことができるため品質向上を図ることができると考える。

また、基本方針として、単体テストパートの処理を構築パートで利用したモジュールや処理と同じにしてしまうと、万が一設定変更処理自体に問題があった場合に検知ができない可能性があることから、単体テストパートでは別の処理内容 (別のコマンド) にて設定内容を確認をするようにPlaybookを作成した。

Playbookの処理詳細

Playbookのファイルの記載内容は大きく以下のように構成される。

設定項目 説明
hosts 実行対象のサーバを記載する。サーバのリストは別ファイルで作成し、そのファイルの中で実行する対象のサーバやグループを指定する。
vars 変数を記載する。ここで記載した変数は{{ 変数名 }}という形で参照できる。また、変数は配列して複数の要素を持たせることもできる。
tasks Ansibleで実行するメインの処理を記載する。
handlers Ansibleでメイン処理実行状況に応じて実行する事後処理を記載する。例えば、設定ファイル更新がされた場合 (実行結果がchangedの場合) は設定反映のためにサービス再起動を行うといった使い方をする。今回のPlaybookではHotfixを保存したNASマウント成功時のみアンマウントを行うよう、NASアンマウントのタスクを定義する。

図(saikei)

Ansibleのベストプラクティスでは、これらのファイルを分割して決められたディレクトリに沿って配置するルールとなっている。今回のPlaybookも以下の通り、ベストプラクティスの配置で作成した。

.
├── staging                                     # インベントリファイル (テスト環境)
├── site.yml                                    # Playbookを呼び出すファイルとして利用
├── setup_widnows_server.yml                    # Playbook本体 (実行対象のロールを定義)
├── group_vars
│   ├── all.yml                                 # 全体共通変数
│   └── windows_servers.yml                     # Windows Server用変数
└── roles
    └── setup_windows_server                    # ロール (Windows Server初期設定)
        ├── tasks
        │   ├── main.yml                        # タスクを呼び出すファイルとして利用
        │   ├── build.yml                       # タスク本体 (Windows Server初期設定)
        │   └── unit_test.yml                   # タスク本体 (Windows Server単体テスト)
        └── handlers
             └── main.yml                        # ハンドラーのタスク本体

それでは、実際のファイルの内容を具体的に見ていこう。

site.yml : Playbookを呼び出すファイルとして利用

site.ymlでは、今後の拡張性を考慮してimport_playbookを用いてPlaybook本体を呼び出す構成とする。当然だが、直接呼出し先のPlaybookの内容を記載しても動作する。

---
- import_playbook: setup_windows_server.yml

setup_windows_server.yml : Playbook本体 (実行対象のロールを定義)

Playbook本体では、実行対象のロールを定義する。今回実行するロールは1つとなる。

  • setup_windows_server : Windows Serverの初期設定と単体テストを行うロール

実行するロールに対して、hostsgather_factsといった内容を定義する。なお、タグ情報をtagsで付与しているが、これはPlaybook実行時に特定のロールだけ実行したい場合に利用する。

---
- name: Setup windows server
  hosts: windows_servers
  gather_facts: true

  roles:
    - role: setup_windows_server
      tags: setup_windows_server

group_vars/all.yml : 全体共通変数

実行対象サーバ全台で共通して利用する変数を定義する。今回は、Windows Serverに接続するためのユーザや接続方式にWinRMを利用することなどを変数として定義した。

変数名 内容
ansible_user Ansible実行用ユーザを指定。
ansible_password Ansible実行用ユーザのパスワードを指定。パスワードはAnsible Vaultにて暗号化されたものを記載している。
ansible_port Ansibleが接続する際に使用するポート番号を指定。WinRMを使用するため、5986を使用する。
ansible_connection Ansibleが接続に使用するプロトコルを指定する。WinRMを指定する。
ansible_winrm_server_cert_validation WinRM接続時にサーバの証明書検証を無効化する。
---
ansible_user: ansibleuser
ansible_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  33663331343562653639326336376337643964303935613838333166363362343063313038303164
  ~(中略)~
  64313233396132366361336434376264376361666235633534353631383938616134
ansible_port: 5986
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore

group_vars/windows_servers.yml : Windows Server用変数

Windows Server用の変数を定義する。

変数名 内容
win_local_user 作成するローカルユーザ名とパスワードを設定する。今回はlocaladminとansibleuserという2つのユーザを作成する。
win_hostfix_list インストールするHotfixを指定する。
win_feature_install_list インストールする役割と機能を指定する。例として今回はWindows Serverバックアップのインストールを行う。
win_service_disable_list 無効化するサービスを指定する。
win_service_enable_list 有効化するサービスを指定する。
win_domain 参加するドメイン名と、ドメイン参加時に使用するドメインユーザ、パスワードを指定する。
win_eventlog イベントログの保存容量とイベントログが最大値に達した際の動作を指定する。
---
win_local_user:
  - name: localadmin
    password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      64343535356239656637336163333664313530636237316138326531346130383832663664316238
      ~(中略)~
      3133323736313336610a363262343231366163653561356637386130326364383436396538383935
      3732
  - name: ansibleuser
    password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      33663331343562653639326336376337643964303935613838333166363362343063313038303164
      ~(中略)~
      64313233396132366361336434376264376361666235633534353631383938616134

win_hostfix_list:
  - name: kb5005112
    path: C:\temp\windows10.0-kb5005112-x64_81d09dc6978520e1a6d44b3b15567667f83eba2c.msu

win_feature_install_list:
  - Windows-Server-Backup

win_service_disable_list:
  - AxInstSV
  - ALG
  - bthserv

win_service_enable_list:
  - W32Time

win_domain:
  name: test.local
  user: join_domain@test.local
  password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      30343031393862363564306338633233613961396136653161333661353365636336386263353038
      ~(中略)~
      64393264613537313230626364343564663539643166653739383833623164663365

win_eventlog:
  max_size: 20480KB
  retention: OverwriteAsNeeded

roles/setup_windows_server/tasks/main.yml : タスクを呼び出すファイルとして利用

各ロール内のmain.ymlでは、今後の拡張性を考慮してinclude_tasksを用いて別のファイルに記載したタスクを呼び出す構成とする。当然だが、直接呼出し先のタスクの内容を記載しても動作する。

ここでもタグ情報をtagsで付与しているが、これはPlaybook実行時に特定のタスクだけ実行したい場合に利用する。

---
# Build
- block:
  - name: Include build tasks
    include_tasks: file=build.yml
  tags: windows_build

# Unit test
- block:
  - name: Include unit test tasks
    include_tasks: file=unit_test.yml
  tags: windows_unit_test

roles/setup_windows_server/tasks/build.yml : タスク本体 (Windows Server初期設定)

Windows Serverの初期設定では以下タスクを実行する。大きくはcommunity.windowsansible.windowsの2種類のモジュールを用いる。それぞれのモジュールの詳細は、以下を参照すること。

タスク名 設定
Set timezone community.windows.win_timezoneモジュールを用いてタイムゾーンを設定する。
Create local users ansible.windows.win_userモジュールを用いて、ローカルユーザの作成を行い初期パスワードを設定する。ユーザ作成済みであってもパスワードが異なる場合は、パスワードを設定しなおしてくれる。グループはAdministratorsグループに所属させ、パスワードの有効期限は無期限とする。また、Ansible実行ログにパスワード情報が記録されることを防ぐため、no_log: trueを設定する。
Mount nfs volume Hotfixを保存したNASをマウントする。アンマウントに成功した場合は、Playbook実行後、ハンドラーにてアンマウントを行うため、notifyにてタスク名を指定する。AnsibleコントロールノードにてNASを一度だけマウントするよう、run_once: trueを設定する。
Copy hotfix files Hotfixをコピーする。ファイルコピーにはansible.windows.win_copyモジュールを用いる。
Install hotfix community.windows.win_hotfixモジュールを用いてコピーしたHotfixを適用する。
Set hostname ansible.windows.win_hostnameモジュールを用いてホスト名を設定する。変更が発生した場合は再起動を行う。
Install windows feature ansible.windows.win_featureモジュールを用いてWindowsの役割と機能の追加を行う。
Disable service ansible.windows.win_serviceを用いてサービスを無効化する。
Enable service ansible.windows.win_serviceを用いてサービスを有効化する。
Disable ipv6 of network adapter community.windows.win_net_adapter_featureモジュールを用いてネットワークアダプタのIPv6の設定を無効化する。
Join domain ansible.windows.win_domain_membership用いてActive Directoryドメインに参加する。参加後にOS再起動を行う。
Set eventlog settings community.windows.win_eventlog用いて、イベントログのログ容量とイベントログが最大値に達した際の動作を設定する。対象のイベントログは、システム、アプリケーション、セキュリティとする。
---
# タイムゾーン設定
- name: Set timezone
  community.windows.win_timezone:
    timezone: Tokyo Standard Time

# ローカルユーザ設定
# パスワード情報表示を抑制するためno_logを付与
- name: Create local users
  ansible.windows.win_user:
    name: "{{ item.name }}"
    fullname: ""
    password: "{{ item.password }}"
    password_never_expires: true
    groups:
      - Administrators
    state: present
  loop: "{{ win_local_user }}"
  no_log: true

# Hotfix適用 (再起動は後続処理で併せて実施)
- name: Mount nfs volume
  ansible.posix.mount:
    src: "192.168.1.1:/public/Windows Server 2019/2022-07_hotfix"
    path: /ansible_mnt
    opts: ro
    boot: false
    state: mounted
    fstype: nfs
  delegate_to: localhost
  run_once: true
  notify: Unmount nfs volume

- name: Copy hotfix files
  ansible.windows.win_copy:
    src: /ansible_mnt/
    dest: C:\Temp

- name: Install hotfix
  community.windows.win_hotfix:
    hotfix_kb: "{{ item.name }}"
    source: "{{ item.path }}"
    state: present
  register: result
  loop: "{{ win_hostfix_list }}"

# ホスト名設定&再起動
- name: Set hostname
  ansible.windows.win_hostname:
    name: "{{ inventory_hostname }}"
  register: result

- name: Reboot after changed hostname
  ansible.windows.win_reboot:
  changed_when: false
  when: result.reboot_required

- name: Wait for reboot
  ansible.builtin.wait_for_connection:
    delay: 5
    timeout: 120
  when: result.reboot_required

# 役割と機能の追加
- name: Install windows feature
  ansible.windows.win_feature:
    name: "{{ win_feature_install_list }}"
    state: present

# サービス無効化
- name: Disable service
  ansible.windows.win_service:
    name: "{{ item }}"
    start_mode: disabled
  loop: "{{ win_service_disable_list }}"

# サービス有効化
- name: Enable service
  ansible.windows.win_service:
    name: "{{ item }}"
    start_mode: auto
  loop: "{{ win_service_enable_list }}"

# インタフェースのIPv6無効化
- name: Disable ipv6 of network adapter
  community.windows.win_net_adapter_feature:
    interface: Ethernet0
    component_id: ms_tcpip6
    state: disabled

# ドメイン参加&OS再起動
- name: Join domain
  ansible.windows.win_domain_membership:
    dns_domain_name: "{{ win_domain.name }}"
    domain_admin_user: "{{ win_domain.user }}"
    domain_admin_password: "{{ win_domain.password }}"
    state: domain
  register: result

- name: Reboot after joined domain
  ansible.windows.win_reboot:
  changed_when: false
  when: result.reboot_required

- name: Wait for reboot
  ansible.builtin.wait_for_connection:
    delay: 5
    timeout: 120
  when: result.reboot_required

# イベントログ設定
# maximum_sizeは64KBの倍数で設定
- name: Set eventlog settings
  community.windows.win_eventlog:
    name: "{{ item }}"
    maximum_size: "{{ win_eventlog.max_size }}"
    overflow_action: "{{ win_eventlog.retention }}"
    state: present
  loop:
    - System
    - Application
    - Security

roles/setup_windows_server/tasks/unit_test.yml : タスク本体 (Windows Server単体テスト)

Windows Serverの単体テストでは以下タスクを実行する。ansible.windows.win_shellモジュールを用いてPowerShellによる設定値確認を行うよう構成した。

タスク名 設定
Test timezone タイムゾーンを確認する。
Test local users ユーザが存在することを確認する。
Test hotfix Hotfixの適用状態を確認する。
Test hostname ホスト名を確認する。
Test install windows features Windowsの役割と機能のインストール状態を確認する。
Test disable service サービスを無効化状態を確認する。
Test enable service サービスを有効化状態を確認する。
Test disable ipv6 ネットワークアダプタのIPv6の設定を無効化されていることを確認する。
Test ntp sync NTPサーバとの時刻同期の状態を確認する。w32tmコマンドでは正確に同期をできていることの表示がされないことから、最終正常同期時刻がテスト日時と同一(「時」まで一致)であることを確認することで、時刻同期の正常性を確認する。
Test domain 所属しているドメイン名を確認する。
Test domain (Check DNS lookup) 自分自身のホスト名がDNSにて名前解決できることを確認する。
Test eventlog settings (MaxSize) イベントログのログ容量を確認する。
Test eventlog settings (Retention) イベントログが最大値に達した際の動作を確認する。レジストリ値を確認し、イベントを上書きする:0、上書きしない:4294967295と値と設定されることを判定する。
---
# タイムゾーン確認
- name: Test timezone
  ansible.windows.win_shell: |
    Get-TimeZone | Where-Object { $_.Id -eq "Tokyo Standard Time" }
  changed_when: false
  register: result
  failed_when: result.stdout == ""

# ローカルユーザ確認
- name: Test local users
  ansible.windows.win_shell: |
    Get-LocalUser | Select-Object Name, Enabled, Description | Where-Object { $_.Name -eq "{{ item.name }}" }
  changed_when: false
  register: result
  failed_when: result.stdout == ""
  loop: "{{ win_local_user }}"
  no_log: true

# Hotfix適用確認
- name: Test hotfix
  ansible.windows.win_shell: |
    Get-HotFix | Where-Object { $_.HotFixID -eq "{{ item.name }}" }
  changed_when: false
  register: result
  failed_when: result.stdout == ""
  loop: "{{ win_hostfix_list }}"

# ホスト名確認
- name: Test hostname
  ansible.windows.win_shell: cmd /C hostname | Select-String "{{ inventory_hostname }}"
  changed_when: false
  register: result
  failed_when: result.stdout == ""

# 役割と機能の確認
- name: Test install windows features
  ansible.windows.win_shell: |
    Get-WindowsFeature | Select-Object Name, Installed | Where-Object { $_.Name -eq "{{ item }}" -and $_.Installed -eq $true }
  loop: "{{ win_feature_install_list }}"
  changed_when: false
  register: result
  failed_when: result.stdout == ""

# サービス無効化確認
- name: Test disable service
  ansible.windows.win_shell: |
    Get-Service | Select-Object Name, StartType, Status | Where-Object { $_.Name -eq "{{ item }}" -and $_.StartType -eq "Disabled" }
  loop: "{{ win_service_disable_list }}"
  changed_when: false
  register: result
  failed_when: result.stdout == ""

# サービス有効化確認
- name: Test enable service
  ansible.windows.win_shell: |
    Get-Service | Select-Object Name, StartType, Status | Where-Object { $_.Name -eq "{{ item }}" -and $_.StartType -eq "Automatic" }
  loop: "{{ win_service_enable_list }}"
  changed_when: false
  register: result
  failed_when: result.stdout == ""

# インタフェースのIPv6無効化確認
- name: Test disable ipv6
  ansible.windows.win_shell: |
    Get-NetAdapterBinding | Where-Object { $_.ComponentID -eq "ms_tcpip6" -and $_.Enabled -eq $false }
  changed_when: false
  register: result
  failed_when: result.stdout == ""

# 時刻同期確認
# 最終正常同期時刻がテスト日時と同一(「時」まで一致)であることを確認
- name: Test ntp sync
  ansible.windows.win_shell: w32tm /query /status | Select-String ((Get-Date).ToString("yyyy/MM/dd H"))
  changed_when: false
  register: result
  failed_when: result.stdout == ""
  until: result.stdout != ""
  retries: 6
  delay: 20

# ドメイン参加確認
- name: Test domain
  ansible.windows.win_shell: |
    Get-WmiObject Win32_ComputerSystem | Select-Object Name, Domain | Where-Object { $_.Domain -eq "{{ win_domain.name }}" }
  changed_when: false
  register: result
  failed_when: result.stdout == ""

- name: Test domain (Check DNS lookup)
  ansible.windows.win_shell: |
    Resolve-DnsName (Get-WmiObject Win32_ComputerSystem).Name
  changed_when: false
  register: result

# イベントログ設定確認
- name: Test eventlog settings (MaxSize)
  ansible.windows.win_shell: |
    Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\{{ item }}' | `
    Select-Object PrimaryModule, MaxSize, Retention | Where-Object { $_.MaxSize -eq {{ win_eventlog.max_size }} }
  changed_when: false
  register: result
  failed_when: result.stdout == ""
  loop:
    - System
    - Application
    - Security

# イベントを上書きする:0、上書きしない:4294967295
- name: Test eventlog settings (Retention)
  ansible.windows.win_shell: |
    if("{{ win_eventlog.retention }}" -eq "OverwriteAsNeeded"){
      Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\{{ item }}' | `
      Select-Object PrimaryModule, MaxSize, Retention | Where-Object { $_.Retention -eq 0 }
    }else{
      Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\{{ item }}' | `
      Select-Object PrimaryModule, MaxSize, Retention | Where-Object { $_.Retention -eq 4294967295 }
    }
  changed_when: false
  register: result
  failed_when: result.stdout == ""
  loop:
    - System
    - Application
    - Security

roles/setup_windows_server/handlers/main.yml : ハンドラーのタスク本体

ハンドラーでは、Hotfix適用後のNASアンマウントを行う。

タスク名 設定
Unmount nfs volume Hotfixを保存したNASをアンマウントする。AnsibleコントロールノードにてNASを一度だけアンマウントするよう、run_once: trueを設定する。
---
- name: Unmount nfs volume
  ansible.posix.mount:
    path: /ansible_mnt
    state: absent
  delegate_to: localhost
  run_once: true

ファイルの内容の説明は以上となる。このPlaybookを実際に実行してみよう。

Playbook実行結果

全処理実行

Playbookの実行結果を以下に記載する。

# ansible-playbook -i staging site.yml 

PLAY [Setup windows server] ***********************************************************************************************************************

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

TASK [setup_windows_server : Include build tasks] *************************************************************************************************
included: /root/ansible/setup_windows_server/roles/setup_windows_server/tasks/build.yml for t1194w219

TASK [setup_windows_server : Set timezone] ********************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Create local users] **************************************************************************************************
changed: [t1194w219] => (item=None)
ok: [t1194w219] => (item=None)
changed: [t1194w219]

TASK [setup_windows_server : Mount nfs volume] ****************************************************************************************************
ok: [t1194w219 -> localhost]

TASK [setup_windows_server : Copy hotfix files] ***************************************************************************************************
changed: [t1194w219]

TASK [setup_windows_server : Install hotfix] ******************************************************************************************************
changed: [t1194w219] => (item={'name': 'kb5005112', 'path': 'C:\\temp\\windows10.0-kb5005112-x64_81d09dc6978520e1a6d44b3b15567667f83eba2c.msu'})

TASK [setup_windows_server : Set hostname] ********************************************************************************************************
changed: [t1194w219]

TASK [setup_windows_server : Reboot after changed hostname] ***************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Wait for reboot] *****************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Install windows feature] *********************************************************************************************
changed: [t1194w219]

TASK [setup_windows_server : Disable service] *****************************************************************************************************
changed: [t1194w219] => (item=AxInstSV)

TASK [setup_windows_server : Enable service] ******************************************************************************************************
ok: [t1194w219] => (item=W32Time)

TASK [setup_windows_server : Disable ipv6 of network adapter] *************************************************************************************
changed: [t1194w219]

TASK [setup_windows_server : Join domain] *********************************************************************************************************
changed: [t1194w219]

TASK [setup_windows_server : Reboot after joined domain] ******************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Wait for reboot] *****************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Set eventlog settings] ***********************************************************************************************
ok: [t1194w219] => (item=System)
ok: [t1194w219] => (item=Application)
ok: [t1194w219] => (item=Security)

TASK [setup_windows_server : Include unit test tasks] *********************************************************************************************
included: /root/ansible/setup_windows_server/roles/setup_windows_server/tasks/unit_test.yml for t1194w219

TASK [setup_windows_server : Test timezone] *******************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Test local users] ****************************************************************************************************
ok: [t1194w219] => (item=None)
ok: [t1194w219] => (item=None)
ok: [t1194w219]

TASK [setup_windows_server : Test hotfix] *********************************************************************************************************
ok: [t1194w219] => (item={'name': 'kb5005112', 'path': 'C:\\temp\\windows10.0-kb5005112-x64_81d09dc6978520e1a6d44b3b15567667f83eba2c.msu'})

TASK [setup_windows_server : Test hostname] *******************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Test install windows features] ***************************************************************************************
ok: [t1194w219] => (item=Windows-Server-Backup)

TASK [setup_windows_server : Test disable service] ************************************************************************************************
ok: [t1194w219] => (item=AxInstSV)

TASK [setup_windows_server : Test enable service] *************************************************************************************************
ok: [t1194w219] => (item=W32Time)

TASK [setup_windows_server : Test disable ipv6] ***************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Test ntp sync] *******************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Test domain] *********************************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Test domain (Check DNS lookup)] **************************************************************************************
ok: [t1194w219]

TASK [setup_windows_server : Test eventlog settings (MaxSize)] ************************************************************************************
ok: [t1194w219] => (item=System)
ok: [t1194w219] => (item=Application)
ok: [t1194w219] => (item=Security)

TASK [setup_windows_server : Test eventlog settings (Retention)] **********************************************************************************
ok: [t1194w219] => (item=System)
ok: [t1194w219] => (item=Application)
ok: [t1194w219] => (item=Security)

PLAY RECAP ****************************************************************************************************************************************
t1194w219                  : ok=32   changed=8    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
2022年8月17日水曜日

yum (dnf) でインストールメディアやローカルに保存されているrpmを適用する方法

ローカルのディレクトリにあるrpmについて、yum (RHEL 8以降はdnf) の機能を用いて依存関係を考慮して適用したいことがある。ということで、過去の記事でローカルのディレクトリをリポジトリ登録する方法を記載したが、そんなことはしなくても、もっと簡単な方法があった。

方法としては2つあるため、以下に紹介する。

インストールメディアに含まれるパッケージをリポジトリ登録する

RHELのインストールメディアには、インストール時に追加可能なパッケージが含まれている。インストールメディアマウント後に、その領域をリポジトリとして認識させることで、あたかも通常のyumを使っているときのようにインストールが可能となる。

まず、リポジトリを登録するため、/etc/yum.repos.d/dvd.repoというファイルを作成する。

なお、RHEL 8.6で確認したところ、name=[リポジトリ名]の設定がない場合、「レポジトリ 'dvd-BaseOS' idを使用した設定内に見つかりません。」といったエラーが発生するようになったため、設定追加をした。

RHEL 7の場合

# cat << EOF > /etc/yum.repos.d/dvd.repo
[dvd]
baseurl=file:///media/
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
EOF

RHEL 8の場合

# cat << EOF > /etc/yum.repos.d/dvd.repo
[dvd-BaseOS]
name=dvd-BaseOS
baseurl=file:///media/BaseOS/
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release

[dvd-AppStream]
name=dvd-AppStream
baseurl=file:///media/AppStream/
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
EOF
これだけでyumdnf実行時に、インストールメディアに含まれるパッケージを参照することができる。

ただし、他にリポジトリが設定されている場合、そのリポジトリに対してインターネット経由で接続を試みてしまうため、--disablerepo=* --enablerepo=dvd*を指定し、インストールメディアのみ参照するようコマンドを実行しよう。

例として、dnfを用いてethtoolをインストールした際の実行結果を記載する。
# dnf --disablerepo=* --enablerepo=dvd* install ethtool
dvd-BaseOS                                      243 MB/s | 2.6 MB     00:00
dvd-AppStream                                   312 MB/s | 7.9 MB     00:00
パッケージ ethtool-2:5.8-7.el8.x86_64 は既にインストールされています。
依存関係が解決しました。
================================================================================
 パッケージ      Arch           バージョン             リポジトリー       サイズ
================================================================================
アップグレード:
 ethtool         x86_64         2:5.13-1.el8           dvd-BaseOS         218 k

トランザクションの概要
================================================================================
アップグレード  1 パッケージ

~(以下略)~

yum localinstallを使う

yumにローカルディレクトリのパッケージを一括でインストールする機能がある。rpmを配置したディレクトリに移動し、yum localinstall *コマンドを実行すればよく、サブスクリプションの設定も不要でインストールすることができる。
# yum localinstall *
読み込んだプラグイン:product-id, security, subscription-manager
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
ローカルパッケージ処理の設定をしています
ConsoleKit-0.4.1-6.el6.x86_64.rpm を調べています: ConsoleKit-0.4.1-6.el6.x86_64
次のリポジトリーへの更新として ConsoleKit-0.4.1-6.el6.x86_64.rpm を設定します: ConsoleKit-0.4.1-3.el6.x86_64
ConsoleKit-libs-0.4.1-6.el6.x86_64.rpm を調べています: ConsoleKit-libs-0.4.1-6.el6.x86_64
次のリポジトリーへの更新として ConsoleKit-libs-0.4.1-6.el6.x86_64.rpm を設定します: ConsoleKit-libs-0.4.1-3.el6.x86_64
Red_Hat_Enterprise_Linux-Release_Notes-6-en-US-8-2.el6.noarch.rpm を調べています: Red_Hat_Enterprise_Linux-Release_Notes-6-en-US-8-2.el6.noarch

~(中略)~


トランザクションの要約
================================================================================
インストール         3 パッケージ
アップグレード     250 パッケージ

合計容量: 899 M
これでいいですか? [y/N] y   ←★ここでyを押せばインストール&更新が開始
ちなみに、似たようなものにyum localupdateというものもあるのだが、なぜかうまくいかなかった。yum localinstallでもきちんとインストールとアップグレードを実施できるので、特に問題ないので詳しくは調べていない。
# yum localupdate *
読み込んだプラグイン:product-id, security, subscription-manager
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
ローカルパッケージ処理の設定をしています
ConsoleKit-0.4.1-6.el6.x86_64.rpm を調べています: ConsoleKit-0.4.1-6.el6.x86_64
次のリポジトリーへの更新として ConsoleKit-0.4.1-6.el6.x86_64.rpm を設定します: ConsoleKit-0.4.1-3.el6.x86_64
ConsoleKit-libs-0.4.1-6.el6.x86_64.rpm を調べています: ConsoleKit-libs-0.4.1-6.el6.x86_64
次のリポジトリーへの更新として ConsoleKit-libs-0.4.1-6.el6.x86_64.rpm を設定します: ConsoleKit-libs-0.4.1-3.el6.x86_64
Red_Hat_Enterprise_Linux-Release_Notes-6-en-US-8-2.el6.noarch.rpm を調べています: Red_Hat_Enterprise_Linux-Release_Notes-6-en-US-8-2.el6.noarch

~(中略)~

--> 依存性解決を終了しました。
エラー: パッケージ: 1:java-1.7.0-openjdk-1.7.0.101-2.6.6.4.el6_8.x86_64 (/java-1.7.0-openjdk-1.7.0.101-2.6.6.4.el6_8.x86_64)
             要求: libsctp.so.1(VERS_1)(64bit)
エラー: パッケージ: 1:java-1.7.0-openjdk-1.7.0.101-2.6.6.4.el6_8.x86_64 (/java-1.7.0-openjdk-1.7.0.101-2.6.6.4.el6_8.x86_64)
             要求: libsctp.so.1()(64bit)
エラー: パッケージ: 1:java-1.7.0-openjdk-1.7.0.101-2.6.6.4.el6_8.x86_64 (/java-1.7.0-openjdk-1.7.0.101-2.6.6.4.el6_8.x86_64)
             要求: libpcsclite.so.1()(64bit)
 問題を回避するために --skip-broken を用いることができません
 これらを試行できます: rpm -Va --nofiles --nodigest

更新履歴

  • 2016/5/15 新規作成
  • 2021/7/18 インストールメディアをレポジトリに登録する手順を追加。その他全体的に体裁を修正
  • 2021/9/11 dvd.repoにRHEL 8のパターンも追加
  • 2022/8/17 RHEL 8のdvd.repoにnameの設定を追加。実行結果をdnfの実行結果に修正
2022年8月13日土曜日

AnsibleからWindows Serverに接続するための事前作業

AnsibleはLinuxサーバに対してはSSHを使用して接続するが、Windows ServerのようにデフォルトではSSH接続ができないOSの場合は当然SSHによる処理実行はできない。しかし、SSHは使えなくともAnsibleから接続する手段は用意されており、Windows Serverの場合はWinRMを用いて接続することができる。

本記事では、AnsibleからWindows Serverに接続し処理を実行できるようにするための事前作業を記載する。

なお本記事では触れないが、最近のWindows OSではOpenSSHサーバのインストールが可能となっており、Ansible 2.8以降ではSSHによる接続もできるようだ。Windows Server 2019にOpenSSHをインストールする方法は以下別記事を参照いただきたい。

今までのAnsible関連記事

環境

コントロールノードのLinuxのOSは、AlmaLinuxにて構築している。実行対象のWindows Serverは、Windows Server 2019とした。

  • コントロールノード
    • OS : AlmaLinux 8.5
    • Ansible : ansible [core 2.13.1]
  • Ansible実行対象サーバ
    • OS : Windows Server 2019
    • IPアドレス : 192.168.1.194

AnsibleによるWindows Server接続手順

1. Ansibleコントロールノードにpywinrmをインストール

前述の通りAnsibleは、Windows Serverに対してWinRMによる接続を行うため、WinRMにて接続を行うために必要となるPythonのモジュールであるpywinrmをインストールしておく。

[Ansibleコントロールノード]
# pip3 install pywinrm

2. WinRM設定用スクリプトをダウンロード

実行対象サーバのWindows ServerでWinRMを有効化するためのスクリプトConfigureRemotingForAnsible.ps1を以下URLからダウンロードする。

3. WinRM設定用スクリプトを実行

PowerShellのプロンプトを「管理者として実行する」で開き、まずは現在のWinRMの設定を確認する。初期状態ではポート5985のListenerが存在しているだけとなっている。

[実行対象サーバ]
PS> winrm enumerate winrm/config/Listener
Listener
    Address = *
    Transport = HTTP
    Port = 5985
    Hostname
    Enabled = true
    URLPrefix = wsman
    CertificateThumbprint
    ListeningOn = 127.0.0.1, 192.168.11.194, ::1

先ほどダウンロードしたWinRM設定用スクリプトを実行し、最後に「OK」と表示されることを確認する。処理は10秒程度で完了する。

[実行対象サーバ]
PS> powershell.exe -ExecutionPolicy ByPass -File .\ConfigureRemotingForAnsible.ps1
Self-signed SSL certificate generated; thumbprint: 274532XXXXA792D9D89EF21XXXXD541B542BB5EF


wxf                 : http://schemas.xmlsoap.org/ws/2004/09/transfer
a                   : http://schemas.xmlsoap.org/ws/2004/08/addressing
w                   : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
lang                : ja-JP
Address             : http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
ReferenceParameters : ReferenceParameters

OK   ←★「OK」を確認する

実行後、再度WinRMの設定を確認すると、ポート5986のListenerが追加されているはずだ。

[実行対象サーバ]
PS> winrm enumerate winrm/config/Listener
Listener
    Address = *
    Transport = HTTP
    Port = 5985
    Hostname
    Enabled = true
    URLPrefix = wsman
    CertificateThumbprint
    ListeningOn = 127.0.0.1, 192.168.11.194, ::1

Listener          ←★追加されている
    Address = *
    Transport = HTTPS
    Port = 5986
    Hostname = T1194W219
    Enabled = true
    URLPrefix = wsman
    CertificateThumbprint = 274532XXXXA792D9D89EF21XXXXD541B542BB5EF
    ListeningOn = 127.0.0.1, 192.168.11.194, ::1

4. Ansible実行用ユーザを作成しパスワードを設定

実行対象サーバに対して、Ansible実行用ユーザを作成する。また、WinRMではパスワード認証にて接続することから、パスワードの設定も行う。なお、Ansibleからの処理実行時には特権が必要となることから、ローカルのAdministratorsグループに所属させておく。

[実行対象サーバ]
PS> $ansible_pass = "[任意のパスワード]"
PS> New-LocalUser -Name "ansibleuser" -PasswordNeverExpires -Password (ConvertTo-SecureString "${ansible_pass}" -AsPlainText -Force)
PS> Add-LocalGroupMember -Group "Administrators" -Member "ansibleuser"

5. インベントリファイルの作成

Ansible実行時の実行対象サーバを定義するインベントリファイルをhostsというファイルで作成する。対象がLinuxサーバの場合は、実行時に利用するユーザとパスワード情報だけ指定すれば問題なかったが、Windowsの場合は、WinRMによる接続であることを明示的に指定するため、以下3点を変数で指定する。

  • 接続ポート番号 (ansible_port)
  • 接続方式 (ansible_connection)
  • 接続時の証明書の検証を無視する (ansible_winrm_server_cert_validation)

また、Ansible接続用ユーザのパスワードが平文で書かれてしまうという問題がある。パスワードを平文でインベントリファイルやPlaybookに記載しないためのツールとして、「Ansible Vault」があるが、こちらについては別記事で使用方法を紹介することとしたい。

[Ansibleコントロールノード]
# cd ~
# cat << 'EOF' > hosts
[windows_servers]
t1194w219 ansible_host=192.168.11.194

[windows_servers:vars]
ansible_user=ansibleuser
ansible_password=[ansibleuserのパスワード]
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
EOF

6. Ansibleコントロールノードからの接続確認

これでAnsibleを実行するための準備は整った。Ansibleコントロールノードからwin_pingモジュールを用いて接続確認をしてみよう。結果としてpongが返ってくればOKとなる。

[Ansibleコントロールノード]
# ansible -i hosts -m win_ping t1194w219
t1194w219 | SUCCESS => {
    "changed": false,
    "ping": "pong"   ★pongが返ってくればOK
}

以上で、AnsibleからWindows Serverに接続するための事前作業は完了となる。次回以降の記事で、実際にWindows Serverに対してPlaybookを実行してみたいと思う。

参考

2022年8月6日土曜日

Ansibleのベストプラクティスのディレクトリ構成でPlaybookを実行する

AnsibleはPlaybookを1つのファイルで作成することもできるが、その場合処理が多く複雑になるほど、一つのファイルの情報量が膨大となり、ファイルの可読性や再利用が難しくなるという問題がある。そのため、Ansibleではベストプラクティスとして、ディレクトリを分割し、Playbookをvars、tasksなどで分割して配置することを定めている。

今までは1つのファイルでPlaybookを作成したが、今回は、Ansibleのベストプラクティスのディレクトリ構成にてPlaybookを実行できるよう構成してみる。

今までのAnsible関連記事

環境

コントロールノード及び実行対象サーバのLinuxのOSは、AlmaLinuxにて構築している。

  • コントロールノード
    • OS : AlmaLinux 8.5
    • Ansible : ansible [core 2.13.1]
  • Ansible実行対象サーバ
    • OS : AlmaLinux 8.5
    • IPアドレス : 192.168.1.101

今回は前回の記事で作成した、Linuxサーバの初期設定と単体テストを行うPlaybookをAnsibleのベストプラクティスのディレクトリにて再構成する。

Ansibleのベストプラクティスのディレクトリ構成

ベストプラクティスの構成は以下URLに記載されている。

各ディレクトリの説明を以下表に記載する。

ファイル名 説明
production 本番環境用のインベントリファイル。
staging 検証環境用のインベントリファイル。
group_vars/all.yml すべての実行対象サーバが利用する変数。
group_vars/[グループ名].yml インベントリファイルに記載したグループに所属する実行対象サーバが利用する変数。
host_vars/[サーバ名].yml 個別の実行対象サーバが利用する変数。
site.yml Playbook実行の主体。本YAMLファイルから、[任意の処理名].ymlを呼び出す。
[任意の処理名].yml site.ymlから呼び出されて実行される。実行対象のロールを記載する。
roles/[ロール名]/tasks/main.yml Playbookのタスクが記載されたファイル。
roles/[ロール名]/handlers/main.yml Playbookのハンドラーが記載されたファイル。
roles/[ロール名]/files/[任意のファイル名] 実行対象サーバにコピーして利用するための、定型化された設定ファイルなどを配置する。
roles/[ロール名]/vars/main.yml 本ロールのみで有効となる変数。優先度が高く、他箇所で宣言された変数によって上書きされない。
roles/[ロール名]/defaults/main.yml 本ロールのみで有効となる変数。優先度が低く、他箇所で宣言された変数によって上書きされる。

これをもとに、前回作成したsetup_linux_serverのPlaybookのディレクトリ構成は以下の通りとした。不要なディレクトリは作成していないため、比較的シンプルになっている。

また、ロールは以下2種類を配置している。

  • restore_vmware_vm_snapshot : テスト用Linuxサーバを初期化 (スナップショットリストア) を行うロール
  • setup_linux_server : 初期化されたLinuxサーバの初期設定と単体テストを行うロール
.
├── staging                                     # インベントリファイル (テスト環境)
├── site.yml                                    # Playbookを呼び出すファイルとして利用
├── setup_linux_server.yml                      # Playbook本体 (実行対象のロールを定義)
├── group_vars
│   ├── all.yml                                 # 全体共通変数
│   ├── linux_servers.yml                       # Linuxサーバ用変数
│   └── vmware_servers.yml                      # ESXi用変数
└── roles
    ├── restore_vmware_vm_snapshot              # ロール① (スナップショットリストア)
    │   └── tasks
    │       ├── main.yml                        # タスクを呼び出すファイルとして利用
    │       └── restore_vmware_vm_snapshot.yml  # タスク本体 (スナップショットリストア)
    └── setup_linux_server                      # ロール② (Linux初期設定)
        └── tasks
            ├── main.yml                        # タスクを呼び出すファイルとして利用
            ├── build.yml                       # タスク本体 (Linux初期設定)
            └── unit_test.yml                   # タスク本体 (Linux単体テスト)

それでは、実際のファイルの内容を具体的に見ていこう。

各ファイルの内容

Ansibleのベストプラクティスのディレクトリ構成に伴い、ファイル分割や新たなファイル作成が必要となる。マニュアルだけでは実際にどのような内容が記載されているかイメージしにくいと思うので、以下に各ファイルの内容を記載する。参考にしてもらい、Playbookの分割を行えば、同様に動作させることができるだろう。

staging : インベントリファイル (テスト環境)

通常のPlaybookでもインベントリファイルは作成をするが、Ansibleのベストプラクティスでは、以下ファイル名でインベントリファイルを作成する。本Playbookではテスト環境のみ対象となるため、stagingのみ作成した。

  • staging : テスト環境用インベントリファイル
  • production : 本番環境用インベントリファイル
[linux_servers]
server1 ansible_host=192.168.1.101

[vmware_servers]
esxi1 ansible_host=192.168.1.201

site.yml : Playbookを呼び出すファイルとして利用

site.ymlでは、今後の拡張性を考慮してimport_playbookを用いてPlaybook本体を呼び出す構成とする。当然だが、直接呼出し先のPlaybookの内容を記載しても動作する。

---
- import_playbook: setup_linux_server.yml

setup_linux_server.yml : Playbook本体 (実行対象のロールを定義)

Playbook本体では、実行対象のロールを定義する。前述したとおり今回実行するロールは以下2つとなる。

  • restore_vmware_vm_snapshot : テスト用Linuxサーバを初期化 (スナップショットリストア) を行うロール
  • setup_linux_server : 初期化されたLinuxサーバの初期設定と単体テストを行うロール

それぞれのロールに対して、hostsgather_factsといった内容を定義する。なお、タグ情報をtagsで付与しているが、これはPlaybook実行時に特定のロールだけ実行したい場合に利用する。タグを用いたPlaybookの実行方法は後述する。

---
- name: Build test vm
  hosts: vmware_servers
  gather_facts: true

  roles:
    - role: restore_vmware_vm_snapshot
      tags: restore_vmware_vm_snapshot

- name: Setup linux server
  hosts: linux_servers
  gather_facts: true

  roles:
    - role: setup_linux_server
      tags: setup_linux_server

group_vars/all.yml : 全体共通変数

実行対象サーバ全台で共通して利用する変数を定義する。今回は、Linuxサーバに接続するためのユーザやプロキシサーバの環境変数を定義した。

---
ansible_ssh_user: ansibleuser

proxy_env:
  http_proxy: 192.168.1.1:8080
  https_proxy: 192.168.1.1:8080

group_vars/linux_servers.yml : Linuxサーバ用変数

Linuxサーバ用の変数を定義する。これは、もともとのPlaybookのvarsに記載されているものがそのまま記載されている。

---
package_install_list:
  - open-vm-tools
  - rsyslog
  - chrony
  - sos
  - sysstat
  - tcpdump
  - cifs-utils
  - nfs-utils
  - bind-utils

service_disable_list:
  - firewalld

service_enable_list:
  - chronyd

ntp_server: 192.168.1.2

nmcli_ipv4_gateway: 192.168.1.254
nmcli_ipv4_dns: "192.168.1.4"
nmcli_ipv4_dns_search: test.local

roles/setup_linux_server/tasks/main.yml : タスクを呼び出すファイルとして利用

各ロール内のmain.ymlでは、今後の拡張性を考慮してinclude_tasksを用いて別のファイルに記載したタスクを呼び出す構成とする。当然だが、直接呼出し先のタスクの内容を記載しても動作する。

ここでもタグ情報をtagsで付与しているが、これはPlaybook実行時に特定のタスクだけ実行したい場合に利用する。タグを用いたPlaybookの実行方法は後述する。

---
# Build
- block:
  - name: Include build tasks
    include_tasks: file=build.yml
  tags: linux-build

# Unit test
- block:
  - name: Include unit test tasks
    include_tasks: file=unit_test.yml
  tags: linux-unit_test

roles/setup_linux_server/tasks/build.yml : タスク本体 (Linux初期設定)

ここには、もともとのPlaybookのtasksに記載されているLinuxサーバの初期設定個所がそのまま記載されている。

---
- name: Set timezone
  community.general.timezone:
    name: Asia/Tokyo
  become: true

- name: Set network interface
  ansible.builtin.command: nmcli c mod ens192 {{ item }}
  loop:
    - connection.autoconnect yes
    - ipv4.gateway "{{ nmcli_ipv4_gateway }}"
    - ipv4.dns "{{ nmcli_ipv4_dns }}"
    - ipv4.dns-search "{{ nmcli_ipv4_dns_search }}"
  changed_when: false
  become: true

~(以下略)~

roles/setup_linux_server/tasks/unit_test.yml : タスク本体 (Linux単体テスト)

ここには、もともとのPlaybookのtasksに記載されているLinuxサーバの単体テスト個所がそのまま記載されている。

---
- name: Test timezone
  ansible.builtin.shell: set -o pipefail && timedatectl status | grep 'Asia/Tokyo'
  changed_when: false

- name: Test network interface
  ansible.builtin.shell: set -o pipefail && nmcli c show ens192 | grep -E {{ item }}
  loop:
    - '^connection.autoconnect.*yes'
    - '^ipv4.addresses.*{{ ansible_host }}'
    - '^ipv4.gateway.*{{ nmcli_ipv4_gateway }}'
    - '^ipv4.dns.*$(echo "{{ nmcli_ipv4_dns }}" | tr " " ",")'
    - '^ipv4.dns-search.*{{ nmcli_ipv4_dns_search }}'
  changed_when: false
  register: result
  environment:
    LANG: C

~(以下略)~

ファイルの内容の説明は以上となる。このPlaybookを実際に実行してみよう。

Playbook実行結果

全処理実行

Playbookの実行結果を以下に抜粋して記載する。

# ansible-playbook -i staging site.yml 

★ロール「restore_vmware_vm_snapshot」を開始
PLAY [Build test vm] **************************************************************************************************************

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

★ロール「restore_vmware_vm_snapshot」のタスクをmain.ymlから読み込み
TASK [restore_vmware_vm_snapshot : Include build tasks] ***************************************************************************
included: /root/ansible/setup_linux_server/roles/restore_vmware_vm_snapshot/tasks/restore_vmware_vm_snapshot.yml for esxi1

★テスト用Linuxサーバを初期化のタスクを実行
TASK [restore_vmware_vm_snapshot : Restore test vm from snapshot] *****************************************************************
changed: [esxi1]

~(以下略)~

★ロール「setup_linux_server」を開始
PLAY [Setup linux server] *********************************************************************************************************

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

★ロール「setup_linux_server」のLinux初期構築のタスクをmain.ymlから読み込み
TASK [setup_linux_server : Include build tasks] ***********************************************************************************
included: /root/ansible/setup_linux_server/roles/setup_linux_server/tasks/build.yml for server1

★Linux初期構築のタスクを実行
TASK [setup_linux_server : Set timezone] ******************************************************************************************
changed: [server1]

~(以下略)~

★ロール「setup_linux_server」の単体テストのタスクをmain.ymlから読み込み
TASK [setup_linux_server : Include unit test tasks] *******************************************************************************
included: /root/ansible/setup_linux_server/roles/setup_linux_server/tasks/unit_test.yml for server1

★単体テストのタスクを実行
TASK [setup_linux_server : Test timezone] *****************************************************************************************
ok: [server1]

~(以下略)~

PLAY RECAP ************************************************************************************************************************
esxi1                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
server1                  : ok=21   changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

単体テストのみ実行 (タグを指定して実行)

Playbook実行時にtagsにて記載しているタグ名を指定することで、特定の処理のみ実行することが可能となる。ここでは、例として単体テストのみ実行をさせてみる (タグ名:linux-unit_test)。

# ansible-playbook -i staging site.yml -t linux-unit_test

PLAY [Build test vm] **********************************************************************************************

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

★テスト用Linuxサーバを初期化のタスクがスキップされる

PLAY [Setup linux server] *****************************************************************************************

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

★Linux初期構築のタスクがスキップされ、単体テストのタスクが実行される
TASK [setup_linux_server : Include unit test tasks] ***************************************************************
included: /root/ansible/setup_linux_server/roles/setup_linux_server/tasks/unit_test.yml for server1

TASK [setup_linux_server : Test timezone] *************************************************************************
ok: [server1]

~(以下略)~

PLAY RECAP ********************************************************************************************************
esxi1                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
server1                  : ok=9    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

以上の通り、Ansibleのベストプラクティスのディレクトリ構成にてPlaybookを実行することができた。

人気の投稿