Ansibleならローカルのシェルスクリプトをリモートホストで実行できて便利

# はじめに
サーバ移行作業などで、旧サーバでバックアップを取得してダウンロードし、新サーバにアップロードするといった作業がよくあると思います。

通常だとバックアップを取得するためのシェルスクリプトを旧サーバに配置して実行、できたアーカイブファイルを新サーバに直接 or 一度ローカルにダウンロードして転送する流れになるかと思いますが、対象サーバの数が多くなると面倒です。

こういった作業にAnsibleを利用するとかなり楽になりますよ！　というネタです。

今回は**「ローカルにあるスクリプトをリモートホスト(旧サーバ)で実行し、生成されたファイルをローカルにダウンロードし、別のリモートホスト(新サーバ)にアップロードする」**というシナリオをAnsibleでやってみます。

構成管理だけではない、Ansibleの利用方法をご紹介します。

# 概要
- Ansibleの`script`モジュールを利用すると、ローカル上のシェルスクリプトをリモートホストで実行できる
 - その際、`creates:`でファイルの存在をスクリプトの実行条件にできるので、何度も同じホストに対して実行されることはない
- 旧サーバからのファイルダウンロードおよび新サーバへのファイルアップロードは`shell`モジュールで`scp`コマンドをローカル実行する
- 対象サーバはホスト名のみをインベントリに記載
- インベントリのホスト情報に対応した`ssh_config`を用意し、インベントリのホスト情報を`scp`コマンドでも利用する

#前提
- Ansibe2.3系で動作確認しています

#sample

##ディレクトリ構成

```bash:ディレクトリ構成
ansible.cfg
ssh_config
site.yml
inventories/
|--adhoc_work
roles/
|--adhoc_work/
|  |--tasks/
|  |  |--main.yml
|  |--files/
|  |  |--your_script.sh #旧サーバに対して実行するシェルスクリプト
playbooks/
|--adhoc_work.yml
group_vars/
|--adhoc_work.yml
host_vars/
|--sourcehost01.yml
|--sourcehost02.yml
```
## ansible.cfg

`ssh_args = -F ssh_config`とすることで、Ansible実行時にカレントディレクトリの`ssh_config`を読み込むようにします。

```bash:ansible.cfg
[defaults]
hash_behaviour = merge
gathering = false
timeout = 300

[ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
ssh_args = -F ssh_config

[privilege_escalation]
become = True
```

## ssh_config
通常の`.ssh/config`と同様に定義します。
`Host`とインベントリに記載するホスト名を一致させます。

```bash:ssh_config
Host sourcehost01
    HostName WW.WW.WW.WW
    User XXXX
    IdentityFile ~/.ssh/xxxx.pem

Host sourcehost02
    HostName XX.XX.XX.XX
    User XXXX
    IdentityFile ~/.ssh/xxxx.pem

Host desthost01
    HostName YY.YY.YY.YY
    User XXXX
    IdentityFile ~/.ssh/xxxx.pem

Host desthost02
    HostName ZZ.ZZ.ZZ.ZZ
    User XXXX
    IdentityFile ~/.ssh/xxxx.pem
```

## site.yml
```yaml:site.yml
---
- include: playbooks/adhoc_work.yml
```

## inventory
```yaml:inventories/adhoc_work
[adhoc_work]
sourcehost01 #旧サーバ1
sourcehost02 #旧サーバ2
```
## group_vars

インベントリの`[adhoc_work]`グループ全体に適用される変数です。
今回のadhocな処理で共通のパラメータを定義します。
ここでは例としてアーカイブファイルのコピー先ディレクトリを定義しています。

```yaml:group_vars/adhoc_work.yml
---
destination:
  path: /PATH/TO/DEST_DIR  #アーカイブファイルのコピー先ディレクトリ（新サーバ）
```

## host_vars

ホスト固有の変数を定義します。
ここでは例として旧サーバに対応する新サーバのホスト名を定義しています。

```yaml:host_vars/sourcehost01.yml
---
destination:
  host: desthost01 #新サーバ1
```
```yaml:host_vars/sourcehost02.yml
---
destination:
  host: desthost02 #新サーバ2
```

## playbook
```yaml:playbooks/adhoc_work.yml
---
- name: adhoc task
  hosts: adhoc_work
  roles:
    - ../roles/adhoc_work
```

## Role
```yaml:roles/adhoc_work/tasks/main.yml
---
- name: リモートホストに対してローカルにあるシェルスクリプトを実行
  script: your_script.sh
  args:
    creates: /tmp/your_archive.file #アーカイブファイルのパス（旧サーバ）。このファイルが存在した場合、スクリプトは実行されない
  when: not ansible_check_mode

- name: バックアップファイル格納ディレクトリ作成（ローカル）
  file:
    path: "{{ inventory_dir }}/backup"
    state: directory
  delegate_to: localhost
  check_mode: no
  changed_when: no
  become: no

- name: リモートホストからバックアップファイルをダウンロード
  shell: "scp -F {{ inventory_dir }}/../ssh_config {{ inventory_hostname }}:/tmp/your_archive.file {{ inventory_dir }}/backup/{{ inventory_hostname }}_archive.file"
  delegate_to: localhost
  become: no

- name: リモートホストからダウンロードしたバックアップファイルを削除
  file:
    path: /tmp/your_archive.file
    state: absent

- name: バックアップファイルアップロード to 新サーバ
  shell: "scp -F {{ inventory_dir }}/../ssh_config {{ inventory_dir }}/backup/{{ inventory_hostname }}_archive.file {{ destination.host }}:{{ destination.path }}/."
  delegate_to: localhost
  become: no
```

## 実行
旧サーバ1に対して実行

```bash
$ ansible-playbook -i inventories/adhoc_work -l sourcehost01 site.yml
```

インベントリに定義してある旧サーバにまとめて実行

```bash
$ ansible-playbook -i inventories/adhoc_work site.yml
```

# まとめ
Ansibleでの構成管理はハードルが高いという方は、このように運用作業にAnsibleを利用してみるというのはいかがでしょうか。