複数のWindowsサーバからUpdateログをAnsibleで収集する

# こんなことやりたい
テスト・開発、準本番、本番環境のサーバ(50台弱)に月次で当てているWindowsパッチの適用後ログを一気に収集したい
ansibleコマンド一発で「収集」～「3ファイルにまとめる」～「ansibleホスト(ローカルPC)へ保存」までやってもらう
Windowsのモジュールも知れたので備忘録
# 環境
・リモートサーバ：Windows server2012
・ホストサーバ：Windows10上のUbuntu20.04(WSL)

　(winrmとかのWindowsのansibleにかかる初期設定は割愛)

# roles機能を使っていきます
### ディレクトリ構成

```
win_patch/
　　inventory.ini
　　site.yml
　　dev.yml
　　stg.yml
　　prd.yml
　　roles/
　　　└── getlog/
           └── tasks/
　　　　　　      └── main.yml
```

### ファイルの中身

```ini:inventory.ini
[all_WIN2012_dev]
ホスト名 hostname=xxxxx
ホスト名 hostname=xxxxx
～～～中略～～～

[all_WIN2012_dev:vars]
ansible_ssh_user=xxxxx
ansible_ssh_pass=xxxxx
ansible_port=5986
～～～中略～～～
file_name=dev

[all_WIN2012_stg]
ホスト名 hostname=xxxxx
ホスト名 hostname=xxxxx
～～～中略～～～

[all_WIN2012_stg:vars]
ansible_ssh_user=xxxxx
ansible_ssh_pass=xxxxx
ansible_port=5986
～～～中略～～～
file_name=stg

[all_WIN2012_prd]
ホスト名 hostname=xxxxx
ホスト名 hostname=xxxxx
～～～中略～～～

[all_WIN2012_prd:vars]
ansible_ssh_user=xxxxx
ansible_ssh_pass=xxxxx
ansible_port=5986
～～～中略～～～
file_name=prd

[all:vars]
date="{{ lookup('pipe','date \"+%Y %m\"') }}"
timestamp="{{ lookup('pipe','date \"+%Y%m%d\"') }}"
log_dist="/var/log/wsus_{{ file_name }}_{{ time_stamp }}.log"
output_dir="/mnt/d/logs"
win_patch_shell='Get-EventLog system -Source Microsoft-Windows-WindowsUpdateClient -after "{{ date }}"|select-object MachineName,TimeWritten,Message|ft -wrap'
```
file_nameのとこだけ作業環境ごとに変えられればいいので基本はallグループで変数定義。
/mnt/dはWSLとローカルPCのマウントポイント。最終的にここにログを吐き出したい。

ちなみにfile_nameのとこ最初environmentってやってたら変数定義できなかった。
予約済み変数だからユーザは使っちゃいけないらしい。へー。

```yaml:site.yml
- include: dev.yml
- include: stg.yml
- include: prd.yml
```

playbookはこれだけ。

```yaml:dev.yml
- hosts: all_WIN2012_dev
  gather_facts: false
  roles:
    - getlog
```

各環境ごとのYAML。 
対象ホストが変わるだけで読み込むrolesは一緒なので他2つは割愛。

```yaml:win_patch/roles/getlog/tasks/main.yml
- name: get WindowsUpdatelog
  win_shell: "{{ win_patch_shell }}"
  register: log_output

- name: create log file
  shell: 'echo "{{ item }}" >> {{ log_dist }}'
  with_items:
    - "{{ log_output.stdout_lines }}"
  delegate_to: localhost

- name: copy logfile to local
  command: cp {{ log_dist }} {{ output_dir }}
  delegate_to: localhost
  run_once: true
```

取得したログを変数に代入、stdout_linesにより改行させる。
echoの後ろに変数直接展開したら改行うまくされなかった。with_items使うとループみたいなことするらしい。知らんけど。
delegate_toでlocalhostにタスク実行を委任。これでLinuxコマンドでファイル操作できる。
echoは各ホスト分を書き込むけど/var/logから/mnt/d/logsへのファイルコピーは１回でいいのでrun_onceつけとく。

# 実行してみた

```
ansible-playbook -i inventory.ini site.yml
```

### 結果
/mnt/d/logs配下にwsus_[dev, stg, prd]_202201.logの3ファイルできた！
ansibleコマンドの結果はめちゃ長いし別にファイル出来ればそれでいいので載せない。
「インストールの成功」でgrepして手元の対象サーバ一覧とdiffさせるtasks作れば確認も楽だなとかはAnsible疲れたから思いつかなかったことにする。年明けたばっかだしね。
### ん？
よくみるとログファイルの中身が思ってたのと違う。
ホストごとに抽出したログが書かれているかと思いきや、並列処理してるから[create log file]の部分でそれぞれ到達順に勝手に書き込んじゃってる(=ホストごとのログ内容重なっちゃてるし順番めちゃくちゃ)。

この後grepなりawkなりsortなりすれば揃うのかもしれないけどちょっとそれはあれなので修正する。
# 修正
するんだけど特定のタスクだけ1ホストずつの実行にするやり方がわからない。
delegate_toにホストグループ入れてrun_onceつけてぐるぐる回すやり方は出てくるんだけど、今回ホストごとに実行したいタスクはそもそもdelegate_toはlocalhostにしちゃってる。

なので思いついた力技で無理やり実装。
### 修正内容

```yaml:site.yml
- include: dev.yml
- include: stg.yml
- include: prd.yml
- include: local.yml
```

```yaml:dev.yml
- hosts: all_WIN2012_dev
  gather_facts: false
  roles: 
    - getlog
  serial: 1
```

※stg.yml、prd.ymlも同様

```yaml:win_patch/roles/tasks/main.yml
- name: get WindowsUpdatelog
  win_shell: "{{ win_patch_shell }}"
  register: log_output

- name: create log file
  shell: 'echo "{{ item }}" >> {{ log_dist }}'
  with_items:
    - "{{ log_output.stdout_lines }}"
  delegate_to: localhost

```

```yaml:local.yml
- hosts: localhost
  gather_facts: false
  connection: local
  tasks:

    - name: find logfile
      find:
        paths: /var/log
        patterns: wsus*
        file_type: file
      register: find_result

    - name: copy log file to local PC
      shell: cp "{{ item.path }}" {{ output_dist }}
      with_items:
        - "{{ find_result.files }}"
```

簡単に言うとmain.ymlの中でplayを分け、ローカル上でのファイルコピーのタスクをlocal.ymlとして外出しした。

main.ymlでログの抽出、/var/logへの書き込みを行うが、serial: 1としてホストごと実行させる。

/var/logから/mnt/d/logsへのコピーはローカルの話なのでplayを分ける。
3ファイルあってワイルドカード使いたかったので、findモジュールでパターンマッチさせて変数へ代入し、shellモジュールへ受け渡し(変数展開するときにpathを指定しないとmsgやらrcやら結果が全部展開されてエラーになる)。
# 修正後結果
できた...(意気消沈)
1ホストずつやるからやっぱり時間がかかりますね。

# まとめ
・win_shellはshellのWindows版なんだなって思った。
・タスクは共通なのでrolesを読み込ませるYAMLを複数に分けてみた。できるんですね。
・対象ホストが複数あるときのansibleの挙動の制御が難しい。モジュールやらプラグインやら改めてansible学習しないとね(自戒)。
・特定のタスクだけ実行順制御するやり方でスマートなのあれば教えてください。。。