2022年9月24日土曜日

PostgreSQLをストリーミングレプリケーションで構成する (PostgreSQL 10.x版)

PostgreSQLは「ストリーミングレプリケーション」と呼ばれる構成を取ることで、DBの冗長構成を取ることができる。

ストリーミングレプリケーションの仕組みはこうだ。まず、構成は「マスター」と呼ばれる1台のDB更新・参照が可能なDBと、1台以上の参照のみ可能な「スタンバイ」で構成される。マスターにてDBの更新がされる際に出力される更新ログ「WAL (Write Ahead Log)」をスタンバイに転送し、DBに反映する。これによって、マスターとスタンバイの持つDBの内容が同一となるよう動作する。

今回、実際にPostgreSQLをインストールしてストリーミングレプリケーションを構成してみたので、その手順を記載する。

本記事ではPostgreSQL 10.xの手順を記載している。PostgreSQL 15.x版の手順は以下URLを参照すること。

環境

OSはRHEL 8を利用し、ディストリビューションでパッケージ提供されているPostgreSQLのバージョンをそのまま利用する。

  • OS : Red Hat Enterprise Linux 8.2
  • PostgreSQL : 10.6

マスターとスタンバイの2台にPostgreSQLをインストールし、ストリーミングレプリケーションを構成する。ホスト名やIPアドレスは以下図を参照いただきたい。

今回はマスターとスタンバイ両方で実施する作業と、片方のみで実施する作業がある。そのため、本記事で記載するプロンプトを以下の通り記載し、作業対象が判別できるようにした。

プロンプト 説明
[Master/Standby] マスターとスタンバイ両方で実施
[Master] マスターのみ実施
[Standby] スタンバイのみ実施

事前作業として、検証目的なので、firewalldとSELinuxは無効化しておこう。

[Master/Standby]# systemctl stop firewalld
[Master/Standby]# systemctl disable firewalld
[Master/Standby]# sed -ie 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
[Master/Standby]# reboot

ストリーミングレプリケーション構成手順概要

PostgreSQLでストリーミングレプリケーションを構成する手順の概要を以下に図示する。

①PostgreSQLインストール・初期セットアップ

1. dnfでPostgreSQLをインストール

まずは、PostgreSQLをインストールする。postgresql-serverがPostgreSQLのDBエンジン本体となり、依存関係でクライアントツール (posgresqlパッケージ) もインストールされる。それ以外に、各種管理用のスクリプトなどが含まれるpostgresql-contribパッケージをインストールしておく。

[Master/Standby]# dnf install postgresql-server postgresql-contrib -y

~(中略)~

依存関係が解決しました。
================================================================================
 パッケージ         Arch   バージョン                       リポジトリー  サイズ
================================================================================
インストール中:
 postgresql-contrib x86_64 10.6-1.module+el8+2469+5ecd5aae  dvd-AppStream 805 k
 postgresql-server  x86_64 10.6-1.module+el8+2469+5ecd5aae  dvd-AppStream 5.1 M
依存関係のインストール中:
 libpq              x86_64 12.1-3.el8                       dvd-AppStream 195 k
 perl-Carp          noarch 1.42-396.el8                     dvd-BaseOS     30 k
 perl-Exporter      noarch 5.72-396.el8                     dvd-BaseOS     34 k
 perl-libs          x86_64 4:5.26.3-416.el8                 dvd-BaseOS    1.6 M
 postgresql         x86_64 10.6-1.module+el8+2469+5ecd5aae  dvd-AppStream 1.5 M
 uuid               x86_64 1.6.2-42.el8                     dvd-AppStream  63 k
モジュールストリームの有効化中:
 postgresql                10

トランザクションの概要
================================================================================
インストール  8 パッケージ

~(以下略)~

2. postgresユーザーのパスワードを変更

PostgreSQLは原則postgresユーザーにて操作する。パッケージインストール後にpostgresユーザーのパスワードを設定しておこう。

[Master/Standby]# passwd postgres

3. 初期セットアップ

DBの初期データを作成するため、DB初期セットアップコマンドを実行する。/var/lib/pgsql/dataに初期状態のDBが作成される。

[Master/Standby]# postgresql-setup initdb
WARNING: using obsoleted argument syntax, try --help
WARNING: arguments transformed to: postgresql-setup --initdb --unit postgresql
 * Initializing database in '/var/lib/pgsql/data'
 * Initialized, logs are in /var/lib/pgsql/initdb_postgresql.log

4. DBを起動

一度このタイミングでDBが問題なく起動することを確認しておこう。

[Master/Standby]# systemctl start postgresql
[Master/Standby]# systemctl enable postgresql
Created symlink /etc/systemd/system/multi-user.target.wants/postgresql.service  → /usr/lib/systemd/system/postgresql.service.

②マスターのDB初期設定

1. マスターにてWALアーカイブログディレクトリ作成

ストリーミングレプリケーションを構成するため、事前にWALアーカイブログを保存するディレクトリを作成する。今回は、/var/lib/pgsql/data/archivedirを保存場所とした。

[Master]$ su - postgres
[Master]$ mkdir /var/lib/pgsql/data/archivedir
[Master]$ chmod 700 /var/lib/pgsql/data/archivedir
[Master]$ ls -ld /var/lib/pgsql/data/archivedir
drwx------ 2 postgres postgres 6  6月  5 17:06 /var/lib/pgsql/data/archivedir

2. マスターにてレプリケーション用のDBユーザーを作成

レプリケーション用のDBユーザーを作成する。今回はdbreplというユーザーを作成した。

[Master]$ psql
postgres=# \du
                                               ロール一覧
 ロール名 |                                     属性                                     | 所属グループ
----------+------------------------------------------------------------------------------+--------------
 postgres | スーパーユーザー, ロール作成可, DB作成可, レプリケーション可, RLS のバイパス | {}

postgres=# create user dbrepl replication login encrypted password 'P@ssw0rd';
CREATE ROLE
postgres=# \du
                                               ロール一覧
 ロール名 |                                     属性                                     | 所属グループ
----------+------------------------------------------------------------------------------+--------------
 dbrepl   | レプリケーション可                                                           | {}
 postgres | スーパーユーザー, ロール作成可, DB作成可, レプリケーション可, RLS のバイパス | {}

postgres=# \q

3. マスターにて各種設定を実施

次に、マスターにてDBのレプリケーションに必要な各種設定を実施する。設定前に、念のため変更前の設定ファイルをバックアップしておこう。

[Master]$ cp /var/lib/pgsql/data/postgresql.conf ~/postgresql.conf.org
[Master]$ cp /var/lib/pgsql/data/pg_hba.conf ~/pg_hba.conf.org

postgresql.conf

PostgreSQLの各種設定を変更するため、postgresql.confを更新する。以下に設定内容の概要を記載する。

設定項目 設定内容
listen_addresses DBが接続を受け付けるIPアドレスを指定する。今回はすべてのインタフェースにて許可するため* (all)を設定する。
wal_level replicaで指定する。
synchronous_commit DBに更新があった際に、どこまで更新が反映されたら応答を返すかを設定する。例えば、localの場合はマスターでWALがディスクに記録されたら応答を返す。onの場合はスタンバイに転送されたWALがディスクに記録されたのちに応答を返す。今回は、onで設定する。
archive_mode スタンバイのレプリケーションが長期間停止した際に、マスター側のWALが削除された場合は、WALアーカイブログを使う可能性があるため、WALアーカイブログを有効にする。ストリーミングレプリケーションの場合はalwaysに設定すればよさそうだ。
archive_command WALアーカイブログを出力時に、WALをコピーするためのコマンドを指定する。
max_wal_senders スタンバイから接続を許可する接続数の最大値を設定する。スタンバイ1台の構成であるため、10に設定すれば十分となる。
wal_keep_segments ストリーミングレプリケーション用に保持するWALのファイル数。今回は決め打ちで30とした。
synchronous_standby_names スタンバイのリストを記載する。スタンバイを識別する名称は、後述するスタンバイのrecovery.confファイルで設定したapplication_nameとなる。今回はフェイルオーバー時の設定簡素化のため、* (all) で設定する。
hot_standby ストリーミングレプリケーションを有効にするためonに設定する。
lc_messages PostgreSQLのエラーメッセージを日本語から英語に変更する場合は、Cで設定する。この変更は任意である。
[Master]$ cd $PGDATA
[Master]$ vi postgresql.conf
listen_addresses = '*'
 ↑★すべてのインタフェースにて接続を受け付ける
wal_level = replica    ←★コメントアウトを外す
synchronous_commit = on  ←★コメントアウトを外す
archive_mode =  always  ←★コメントアウトを外しalwaysに設定
archive_command = 'test ! -f /var/lib/pgsql/data/archivedir/%f && cp %p /var/lib/pgsql/data/archivedir/%f'
 ↑★コメントアウトを外し上記に設定
max_wal_senders = 10    ←★コメントアウトを外す
wal_keep_segments = 30   ←★コメントアウト外し30に設定
synchronous_standby_names = '*' ←★コメントアウトを外し* (all) に設定
hot_standby = on       ←★コメントアウトを外す
lc_messages = 'C'      ←★エラーメッセージを日本語から英語にするため変更

pg_hba.conf

PostgreSQLにスタンバイからの接続許可を行うため、pg_hba.confに追記する。

[Master]$ cd $PGDATA
[Master]$ vi pg_hba.conf
~(中略)~
host    replication     dbrepl          192.168.11.116/32       md5
host    replication     dbrepl          192.168.11.117/32       md5

4. マスターのDB再起動

設定反映するため、DBを再起動する。一部設定はリロードでは反映されないため、必ず再起動を実施しよう。

[Master]$ systemctl restart postgresql
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ====
'postgresql.service'を再起動するには認証が必要です。
Authenticating as: root
Password:
==== AUTHENTICATION COMPLETE ====

③マスターのDBをスタンバイにコピー(ベースバックアップ取得)

1. マスターのDBをスタンバイにコピー(ベースバックアップ取得)

スタンバイはマスターからベースバックアップを取得することで、初期設定と初期データのコピーを行う。コピーはPostgreSQLのバックアップ機能であるベースバックアップを使用する。

ベースバックアップを取得するため、スタンバイのDBを一度停止する。

[Standby]# systemctl stop postgresql

DB初期化時に作成されたデータをすべて削除してから、pg_basebackupコマンドを用いてマスターのベースバックアップを直接コピーする。

[Standby]# su - postgres
[Standby]$ rm -rf /var/lib/pgsql/data/*
[Standby]$ pg_basebackup -D /var/lib/pgsql/data -h 192.168.11.116 -X stream -c fast -U dbrepl -W
パスワード:   ←★DBレプリケーション用のユーザーのパスワードを指定
[Standby]$ echo $?
0        ←★0であることを確認

④スタンバイのDB設定(レプリケーション設定)

1. スタンバイにてレプリケーション設定を実施

ベースバックアップを行うとpostgresql.confpg_hba.confも一緒にスタンバイへコピーされるため、追加で設定は不要となる。スタンバイ独自で設定が必要なファイルは、recovery.confとなる。

recovery.conf

recovery.confにはPostgreSQLをスタンバイとして動作させるための設定を記載する。以下に設定内容の概要を記載する。

設定項目 設定内容
standby_mode スタンバイで動作させるため、onを設定する。
primary_conninfo application_nameは任意の名前でよいが、今回はスタンバイのホスト名を設定する。userpasswordは前の手順で作成したレプリケーション用DBユーザーを指定する。hostportはマスターのIPアドレスとポート番号 (デフォルト5432) を指定する。
recovery_target_timeline ストリーミングレプリケーションの場合はlatestを設定する。
restore_command ストリーミングレプリケーションでWALアーカイブログを用いる場合のコピーコマンドを指定する。今回は、scpコマンドを利用する。scp実行時にパスワードを聞かれないようにするため、後程SSHキーの交換を行っておく。
[Standby]$ cd $PGDATA
[Standby]$ vi recovery.conf
standby_mode = 'on'
primary_conninfo = 'application_name=t1117psgl user=dbrepl password=''P@ssw0rd'' host=192.168.11.116 port=5432'
recovery_target_timeline = 'latest'
restore_command = 'scp -o StrictHostKeyChecking=no 192.168.11.116:$PGDATA/archivedir/%f "%p"'

2. マスターとスタンバイのSSHキーを互いに交換

スタンバイからマスターのアーカイブログを取得する際にscpコマンドを使うため、その際にパスワードを聞かれないよう、SSHキーの交換を行っておく。

[Master]$ ssh-keygen
[Master]$ ssh-copy-id 192.168.11.117

[Standby]$ ssh-keygen
[Standby]$ ssh-copy-id 192.168.11.116

3. スタンバイのDBを起動

以上で準備が整ったので、停止していたスタンバイのDBを起動する。

[Standby]$ systemctl start postgresql
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ====
'postgresql.service'を開始するには認証が必要です。
Authenticating as: root
Password:
==== AUTHENTICATION COMPLETE ====

4. マスターにてレプリケーション状態確認

起動後、マスターにてレプリケーションの状態確認を行う。レプリケーションが開始されると、pg_stat_replicationテーブルにレコードが記載される。もし、なにも表示されない場合は、何らかの理由でレプリケーションが正常にできていないことを意味する。

[Master]$ psql -x -c "SELECT * FROM pg_stat_replication;"
-[ RECORD 1 ]----+------------------------------
pid              | 3486
usesysid         | 16384
usename          | dbrepl
application_name | t1117psgl
client_addr      | 192.168.11.117
client_hostname  |
client_port      | 51458
backend_start    | 2022-06-05 17:37:57.698886+09
backend_xmin     |
state            | streaming
sent_lsn         | 0/7000060
write_lsn        | 0/7000060
flush_lsn        | 0/7000060
replay_lsn       | 0/7000060
write_lag        |
flush_lag        |
replay_lag       |
sync_priority    | 1
sync_state       | sync   ←★syncであることを確認

⑤レプリケーション動作確認

1. マスターにてDB及びテーブルを作成

それでは、実際にマスターにて更新した内容がストリーミングレプリケーションによってスタンバイ側に反映されることを確認しよう。

まず、テスト用DBとしてtestdbを作成する。

[Master]$ psql -l
                                         データベース一覧
   名前    |  所有者  | エンコーディング |  照合順序   | Ctype(変換演算子) |     アクセス権限
-----------+----------+------------------+-------------+-------------------+-----------------------
 postgres  | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       |
 template0 | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       | =c/postgres          +
           |          |                  |             |                   | postgres=CTc/postgres
 template1 | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       | =c/postgres          +
           |          |                  |             |                   | postgres=CTc/postgres
(3 行)

[Master]$ createdb testdb
[Master]$ psql -l
                                         データベース一覧
   名前    |  所有者  | エンコーディング |  照合順序   | Ctype(変換演算子) |     アクセス権限
-----------+----------+------------------+-------------+-------------------+-----------------------
 postgres  | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       |
 template0 | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       | =c/postgres          +
           |          |                  |             |                   | postgres=CTc/postgres
 template1 | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       | =c/postgres          +
           |          |                  |             |                   | postgres=CTc/postgres
 testdb    | postgres | UTF8             | ja_JP.UTF-8 | ja_JP.UTF-8       |
(4 行)

次に、testdbに対してテーブルmytableを作成する。mytableidnameの二つの列を持つシンプルなテーブルとしている。

[Master]$ psql -d testdb
testdb=# \dt
リレーションが見つかりませんでした。
testdb=# create table mytable (id integer primary key, name varchar(20));
CREATE TABLE
testdb=# \dt
             リレーション一覧
 スキーマ |  名前   |    型    |  所有者
----------+---------+----------+----------
 public   | mytable | テーブル | postgres
(1 行)

testdb=# \q

2. スタンバイにレプリケーションされることを確認

作成したテーブルmytableに対して、レコードを3行登録する。

[Master]$ psql -d testdb -c "insert into mytable (id, name) values (1, 'Ichiro');"
[Master]$ psql -d testdb -c "insert into mytable (id, name) values (2, 'Jiro');"
[Master]$ psql -d testdb -c "insert into mytable (id, name) values (3, 'Saburo');"
[Master]$ psql -d testdb -c "select * from mytable;"
 id |  name
----+--------
  1 | Ichiro
  2 | Jiro
  3 | Saburo
(3 行)

スタンバイで確認すると、3行のレコードが存在することを確認できる。

[Standby]$ psql -d testdb -c "select * from mytable;"
 id |  name
----+--------
  1 | Ichiro
  2 | Jiro
  3 | Saburo
(3 行)

なお、スタンバイは参照のみ可能となるため、以下の通りDBの更新を行うとエラーとなり失敗する点に注意しよう。

[Standby]$ psql -d testdb -c "insert into mytable (id, name) values (4, 'Shiro');"
ERROR:  cannot execute INSERT in a read-only transaction

以上で、PostgreSQLをインストールしてストリーミングレプリケーションを構成できた。次回は、マスターとスタンバイのDBの役割を切り替えるフェイルオーバーの手順を記載する。

次回記事↓。

PostgreSQLのDBを手動フェイルオーバーする手順 (ストリーミングレプリケーション構成)

参照

2022年9月10日土曜日

GitLabをインストールする手順

現在、自宅環境ではAnsibleやSeleniumによる環境の自動化対応を進めている。その中で、やはりコードを管理するバージョン管理システム (VCS) が欲しくなり、以前から自宅環境として欲しかったGitLabを構築することにした。

ということで、本記事ではGitLabをAlmaLinuxにインストールする手順を記載する。

環境

GitLabをインストールするOSは、AlmaLinuxを用いる。ただし、Red Hat系のディストリビューションであるCentOSやRocky Linuxなどでも同様の手順で構築できるだろう。

  • OS : AlmaLinux 8.5
  • GitLab : 15.1.2

GitLabをインストールするサーバのスペックはCPU4コア・メモリ8GBとした。公式サイトに記載の最小要件はCPU4コア・メモリ4GBとなるが、メモリを多く消費するようなので余裕をもって8GBで構築している。

ちなみに、当初CPU2コア、メモリ2GBの環境で構築を進めていたが、メモリ不足となりインストールがいつまでも終了しない事態となったことから、少なくとも最小要件は必ず満たすようサーバを用意した方がよさそうだ。

また、今回は手順簡略化のため、SELinuxとfirewalldは停止しておく。

# systemctl stop firewalld
# systemctl disable firewalld
# sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
# reboot

GitLabインストール手順

1. Postfixをインストール

GitLabの機能においてメール送信を利用する場合は、Postfixを使うようだ。本記事ではメール送信までの設定や確認は対象外としているが、将来利用できるようインストールしておく。

# dnf install postfix -y
# systemctl enable postfix
# systemctl start postfix

2. GitLabのリポジトリを登録

GitLabはdnfを用いてインストールするため、GitLabのリポジトリを登録する。リポジトリ登録用のシェルが公開されているので、以下の通りコマンドを実行するだけで、リポジトリ登録が完了する。

# curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  6928  100  6928    0     0  39588      0 --:--:-- --:--:-- --:--:-- 39588
Detected operating system as almalinux/8.
Checking for curl...
Detected curl...
Downloading repository file: https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/config_file.repo?os=almalinux&dist=8&source=script
done.

~(中略)~

完了しました!
Generating yum cache for gitlab_gitlab-ee...
GPG 鍵 0x51312F3F をインポート中:
 Userid     : "GitLab B.V. (package repository signing key) <packages@gitlab.com>"
 Fingerprint: F640 3F65 44A3 8863 DAA0 B6E0 3F01 618A 5131 2F3F
 From       : https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey
GPG 鍵 0xF27EAB47 をインポート中:
 Userid     : "GitLab, Inc. <support@gitlab.com>"
 Fingerprint: DBEF 8977 4DDB 9EB3 7D9F C3A0 3CFC F9BA F27E AB47
 From       : https://packages.gitlab.com/gitlab/gitlab-ee/gpgkey/gitlab-gitlab-ee-3D645A26AB9FBD22.pub.gpg
Generating yum cache for gitlab_gitlab-ee-source...

The repository is setup! You can now install packages.

3. dnfにてGitLabをインストール

リポジトリが登録されているので、dnfにてGitLabをインストールする。GitLab Enterprise Edition (gitlab-ee) という名前になっているが、有償ライセンス登録をしなければ、機能上もライセンス上もGitLab Community Editionと同様の機能のみ利用できるようになっており、特に問題はない。

# dnf install gitlab-ee -y

4. GitLabを初期設定

単純にdnfでインストールしただけでは、初期設定がされておらずGitLabを利用することができないため、初期設定を実施する。

まず、GitLabを公開するURLを定義する。設定ファイルは/etc/gitlab/gitlab.rbとなるので、vi等を用いてexternal_urlの項目を修正しよう。

# vi /etc/gitlab/gitlab.rb
external_url 'http://gitlab.example.com'

設定が完了したら、以下コマンドで設定を反映させる。この処理は数分~数十分程度の時間を要するので、完了まで気長に待とう。もしいつまでも処理が完了しない場合は、CPUやメモリのリソース不足を疑おう。

# gitlab-ctl reconfigure

~(中略)~

Notes:
Default admin account has been configured with following details:
Username: root
Password: You didn't opt-in to print initial root password to STDOUT.
Password stored to /etc/gitlab/initial_root_password. This file will be cleaned up in first reconfigure run after 24 hours.

NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.

gitlab Reconfigured!

初期設定が完了すると、gitlab-runsvdirというサービス名でGitLabが起動しているはずだ。

# systemctl status gitlab-runsvdir
● gitlab-runsvdir.service - GitLab Runit supervision process
   Loaded: loaded (/usr/lib/systemd/system/gitlab-runsvdir.service; enabled; ve>
   Active: active (running) since Thu 2022-07-07 13:51:09 JST; 1 day 20h ago
 Main PID: 3492 (runsvdir)
    Tasks: 431 (limit: 4915)
   Memory: 5.0G
   CGroup: /system.slice/gitlab-runsvdir.service
           tq  3492 runsvdir -P /opt/gitlab/service log: ......................>
           tq  3494 runsv logrotate
           tq  3504 svlogd -tt /var/log/gitlab/logrotate
           tq  3529 runsv redis
           tq  3531 /opt/gitlab/embedded/bin/redis-server unixsocket:/var/opt/g>
           tq  3548 svlogd -tt /var/log/gitlab/redis

~(以下略)~

5. 管理者ユーザ (root) の初期パスワードを確認

管理者ユーザ (root) の初期パスワードは自動生成され、その内容が/etc/gitlab/initial_root_passwordに記載されている。

# cat /etc/gitlab/initial_root_password
# WARNING: This value is valid only in the following conditions
#          1. If provided manually (either via `GITLAB_ROOT_PASSWORD` environment variable or via `gitlab_rails['initial_root_password']` setting in `gitlab.rb`, it was provided before database was seeded for the first time (usually, the first reconfigure run).
#          2. Password hasn't been changed manually, either via UI or via command line.
#
#          If the password shown here doesn't work, you must reset the admin password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.

Password: LEDXXXXqJIZWfXXXXeoZxXXXXNe6XXXXjXFQeJ7ZXXXX

# NOTE: This file will be automatically deleted in the first reconfigure run after 24 hours.

6. GitLabにログイン確認

それではGitLabにログインしてみよう。設定したURLまたはIPアドレスにブラウザでアクセスしてみよう。問題なければ、ログイン画面が表示されるはずだ。

ユーザ名をroot、パスワードは先ほど確認した自動生成パスワードを入力してログインできれば、インストールは成功となる。

最後にパスワードを変更しておこう。右上のユーザのアイコン→「Edit profile」→「Password」を選択し、「New password」に新しいパスワードを設定し「Save password」ボタンを押せば変更できる。

以上で、GitLabをAlmaLinuxにインストールする手順は完了となる。

2022年9月3日土曜日

AnsibleからLinuxサーバに接続するための事前準備をするPlaybookを作ってみた

AnsibleでLinuxサーバに接続する際には、事前作業が必要となる。この事前作業については、以下の記事に記載した。

必要な作業内容は以下フローの通りとなる。

この事前の作業自体も手間なので、Ansibleで自動化できないかと考えてみた。

RHEL系ディストリビューション(RHEL 8以前)では、rootによるSSHログインがデフォルトで有効であり、Ansible用のユーザがなくともrootユーザを用いることでAnsibleから接続することが可能となる。そこで、Ansibleの事前作業はrootユーザにて接続して実行し、事前作業完了後は、Ansible用のユーザにて接続できるようにする。

本記事では、AnsibleからLinuxサーバに接続するための事前準備をするPlaybookを作ってみたので紹介する。

環境

今回の動作検証はAlmaLinux 8に対して実施した。ただし、Red Hat系のディストリビューションであるCentOSやRocky Linuxなどでも同様の手順で構築できるだろう。

  • OS : AlmaLinux 8.5
  • Ansible : ansible [core 2.13.1]

Playbookの説明

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

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

vars

変数は以下表の通り設定する。

変数 説明
ansible_user rootを指定。
ansible_password rootのパスワードを平文またはAnsible Vaultで暗号化して指定する。
os_user.name 作成するAnsible接続用ユーザ名を指定する。今回はansibleuserという名前でユーザを作成する。
os_user.uid ansibleuserを作成する際のUIDを指定する。今回は2001を指定する。
os_user.password Ansible接続用ユーザに指定するパスワードを指定する。パスワードは平文ではなく暗号化されたものを記載する。作成方法は複数あるようだが、ansibleuserユーザを作成済みのサーバがある場合は、そのサーバの/etc/shadowから確認すると手っ取り早い。
control_node_ssh_key 事前にAnsibleコントロールノードにてssh-keygenコマンドで作成したSSH公開鍵である~/.ssh/id_rsa.pubの内容をそのまま指定する。

実際のPlaybookの抜粋を以下に記載する。SSH公開鍵はかなり長い文字列となるが、特に気にならなければ1行で記載しても問題ない。気になる場合は、ダブルクォートで囲んで\を末尾に記載することで、改行して記載することができる。

  vars:
    ansible_user: root
    ansible_password: [rootのパスワード]

    os_user:
      name: ansibleuser
      uid: 2001
      password: '$6$7yLTtwuuOp/T$P06CVjt/yz13CCnS2RDcXXMn/aFDng1n1VWXXnZ7asHR13AOXXqtbEKLprEHphmLjJuOtXXorUYWRprxNhAE2A0'

    control_node_ssh_key: "\
          ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCo/wii2sMw08gntc6OREkVvuuZSpiSUBULmksMdKXAnTqyndaUWDuuzm7hT78GAAAAFCL0/1TlAAAA44QMD5rOtkBQmR\
                    ~(中略)~
          AAAAA1+VbxBBBBy2FATUts9dqFaNpjg9cCt5my3nZe9x8UEXZZiP8ye/7Wev6lHSl2wVU7NPL5N08pCP4qV+MFEIhCcw7T72nAL01W8e4Woz0AwN7QRgKolQzS+z5PuYGFU\
          4LTbEEEEO1eUsOxGFFFFSVHYQM= root@t1025alma"

tasks

タスクは以下表の通りとなる。

タスク名 モジュール 設定
Create ansibleuser user Ansible接続用ユーザを作成。
Add /etc/sudoers lineinfile Ansible接続用ユーザのsudo設定を追加。validatevisudoによる構文チェックをするように構成している。
Copy ssh key lineinfile AnsibleコントロールノードのSSH公開鍵を実行対象サーバのAnsible接続用ユーザのauthorized_keysに追加。
Set sshd setting (PermitRootLogin yes -> no) lineinfile SSHDのPermitRootLoginの設定値を更新し、rootユーザのSSHログインを無効化する。
Check ping by ansible ping module ping Ansible接続用ユーザを用いて、Ansibleより接続ができることを確認する。このタスクはAnsible接続用ユーザで実行する必要があるため、varsを指定しansible_userの値を上書きしている。実行結果は「Show ping result」のタスクで表示する。

実際のPlaybookの記載内容は後述する。

handlers

ハンドラーは以下表の通りとなる。

タスク名 モジュール 設定
Restart sshd service SSHDの設定ファイル修正後にサービスを再起動する。

実際のPlaybookの記載内容は後述する。

Playbook全文

実際のPlaybook全文を以下に記載する。

---
- name: Setup linux ansible settings
  gather_facts: false
  hosts: linux_servers

  vars:
    ansible_user: root
    ansible_password: [rootのパスワード]

    os_user:
      name: ansibleuser
      uid: 2001
      password: '$6$7yLTtwuuOp/T$P06CVjt/yz13CCnS2RDcXXMn/aFDng1n1VWXXnZ7asHR13AOXXqtbEKLprEHphmLjJuOtXXorUYWRprxNhAE2A0'

    control_node_ssh_key: "\
          ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCo/wii2sMw08gntc6OREkVvuuZSpiSUBULmksMdKXAnTqyndaUWDuuzm7hT78GAAAAFCL0/1TlAAAA44QMD5rOtkBQmR\
                    ~(中略)~
          AAAAA1+VbxBBBBy2FATUts9dqFaNpjg9cCt5my3nZe9x8UEXZZiP8ye/7Wev6lHSl2wVU7NPL5N08pCP4qV+MFEIhCcw7T72nAL01W8e4Woz0AwN7QRgKolQzS+z5PuYGFU\
          4LTbEEEEO1eUsOxGFFFFSVHYQM= root@t1025alma"

  tasks:
    # Ansible実行用ユーザ作成
    - name: Create ansibleuser
      ansible.builtin.user:
        name: "{{ os_user.name }}"
        uid: "{{ os_user.uid }}"
        password: "{{ os_user.password }}"
        state: present

    # sudo設定
    - name: Add /etc/sudoers
      ansible.builtin.lineinfile:
        path: /etc/sudoers
        line: '{{ os_user.name }} ALL=(root) NOPASSWD:ALL'
        state: present
        validate: visudo -cf %s

    # SSH公開鍵をコピー
    - name: Copy ssh key
      ansible.builtin.lineinfile:
        path: /home/{{ os_user.name }}/.ssh/authorized_keys
        line: "{{ control_node_ssh_key }}"
        owner: "{{ os_user.name }}"
        group: "{{ os_user.name }}"
        mode: '0600'
        create: true
        state: present

    # rootによるSSHログイン拒否
    - name: Set sshd setting (PermitRootLogin yes -> no)
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PermitRootLogin'
        line: 'PermitRootLogin no'
      become: true
      notify: Restart sshd

    # 接続確認
    - name: Check ping by ansible ping module
      ansible.builtin.ping:
      changed_when: false
      register: result
      vars:
        ansible_user: "{{ os_user.name }}"

    - name: Show ping result
      ansible.builtin.debug:
        msg: "{{ result }}"

  handlers:
    # SSHDサービス再起動
    - name: Restart sshd
      ansible.builtin.service: name=sshd state=restarted
      become: true
      changed_when: false

実行結果

実際にPlaybookを実行した結果は以下となる。Show ping resultにてpongが返ってきていれば問題ない。

# ansible-playbook -i hosts -l t1193alma linux/setup_linux_ansible_settings.yml 

PLAY [Setup linux ansible settings] ********************************************************************************

TASK [Create ansibleuser] ******************************************************************************************
changed: [t1193alma]

TASK [Add /etc/sudoers] ********************************************************************************************
changed: [t1193alma]

TASK [Copy ssh key] ************************************************************************************************
changed: [t1193alma]

TASK [Set sshd setting (PermitRootLogin yes -> no)] ****************************************************************
changed: [t1193alma]

TASK [Check ping by ansible ping module] ***************************************************************************
ok: [t1193alma]

TASK [Show ping result] ********************************************************************************************
ok: [t1193alma] => {
    "msg": {
        "changed": false,
        "failed": false,
        "ping": "pong"
    }
}

RUNNING HANDLER [Restart sshd] *************************************************************************************
ok: [t1193alma]

PLAY RECAP *********************************************************************************************************
t1193alma                  : ok=7    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

なお、本Playbook実行後、rootによるSSHログインは拒否されるようになるため、次回以降は本Playbookは処理を実行することができなくなる。
※厳密には、Playbook実行時にSSHの接続情報がキャッシュされるため、実行後60秒以内であればrootユーザによる本Playbookの再実行は可能となる。

# ansible-playbook -i hosts -l t1193alma linux/setup_linux_ansible_settings.yml 

PLAY [Setup linux ansible settings] **********************************************************************************************************************

TASK [Create ansibleuser] ********************************************************************************************************************************
fatal: [t1193alma]: UNREACHABLE! => {"changed": false, "msg": "Invalid/incorrect password: Permission denied, please try again.", "unreachable": true}

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

以上で、AnsibleからLinuxサーバに接続するための事前準備をするPlaybookの説明は完了となる。

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  

人気の投稿