Ansibleのローカル実行

##概要
Ansibleによるsshを使用しないローカル実行には、以下の2つの指定が必要。

- 接続方法: local
- 宛先: localhost
  - インベントリーが空でなければ必須では無い

それらは、以下などで指定可能。

- Playbook
- コマンド引数
- インベントリー


##検証環境

- macOS 10.15.4
- ansible 2.9.4
- phthon 3.8.2 (pyenv環境)


#例1a: Playbookでの指定 (全てのtaskがlocal)

##インベントリー: 無し

##例1a: 接続先をlocalhostのみで指定

接続方法を指定しなくとも、相手がlocalhostの場合には、接続方法はlocalが選択される。

```yml:site.yml

---
- hosts: localhost      # 接続先(localhostは、インベントリーになくとも指定可能)
  gather_facts: false
  tasks:

  - shell: hostname
    register: r_hostname
  - debug: { var: r_hostname.stdout }


  - debug: { var: ansible_connection }
  - debug: { var: ansible_playbook_python }
  - debug: { var: ansible_python_interpreter }
```


##コマンドパラメーターと実行結果

パラメーターはplaybook名のみ

```
$ ansible-playbook site.yml 
[WARNING]: No inventory was parsed, only implicit localhost is available

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not
match 'all'


PLAY [localhost] *************************************************************************************************

TASK [shell] *****************************************************************************************************
changed: [localhost]

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "r_hostname.stdout": "hogehoge"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_connection": "local"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_playbook_python": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_python_interpreter": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

PLAY RECAP *******************************************************************************************************
localhost                  : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

- インベントリーが無いというワーニングが出力される
- 自動的に "ansible_connection": "local" が設定される
- 自動的に "ansible_python_interpreter" に、ansible_playbook_python と同じ値が設定される
- ansible-playbook を、-i localhost, -c local として実行すると、接続方法は正しく local となるが、ansible_python_interpreter が定義されていないので、以下の様なワーニングが出力されることになる。

```
[WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/bin/python, but
future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
```

# 例1b: Playbookでの指定 (一部のtaskがlocalhostのみ対象)


## 例1b-1: 接続方法をtask単位で delegate_to: localhost にて指定

インベントリーは他の接続先が存在する想定

```yml:site.yml
---
- hosts: all               # 宛先 インベントリーにおけるデフォルトのallグループ
  gather_facts: false
  tasks:

  - shell: hostname
    register: r_hostname
    delegate_to: localhost  # 接続方法
    #run_once: true         # 各ノード分だけ実行する必要がないならコメントイン

  - debug: { var: r_hostname.stdout }
    delegate_to: localhost  # 接続方法 (debugモジュールなので意味はないが)
    #run_once: true         # 各ノード分だけ実行する必要がないならコメントイン


  - debug: { var: ansible_playbook_python }
    run_once: true
  - debug: { var: hostvars['localhost'].ansible_connection }
    run_once: true
  - debug: { var: hostvars['localhost'].ansible_python_interpreter }
    run_once: true
```

##例1b-2: 接続方法をタスクのブロックに対して指定


```yml:site.yml
---
- hosts: all               # 宛先 インベントリーにおけるデフォルトのallグループ
  gather_facts: false
  tasks:

  - delegate_to: localhost  # blockに対して接続方法を指定
    block:                  # blockでまとめて 

    - shell: hostname
      register: r_hostname
      #run_once: true       # 各ノード分だけ実行する必要がないならコメントイン

    - debug: { var: r_hostname.stdout }
      #run_once: true       # 各ノード分だけ実行する必要がないならコメントイン

    # blockはここまで

  - debug: { var: ansible_playbook_python }
    run_once: true
  - debug: { var: hostvars['localhost'].ansible_connection }
    run_once: true
  - debug: { var: hostvars['localhost'].ansible_python_interpreter }
    run_once: true
```



## コマンドパラメーターと実行結果


```
$ ansible-playbook -i inv site.yml

PLAY [all] *******************************************************************************************************

TASK [shell] *****************************************************************************************************
changed: [host1 -> localhost]             # <= 実行ホストの変更が [host1 -> localhost] の様に表示される
changed: [host2 -> localhost]             # <= run_once: trueが付いていれば1ノードのみで実行

TASK [debug] *****************************************************************************************************
ok: [host2 -> localhost] => {
    "r_hostname.stdout": "hogehoge"
}
ok: [host1 -> localhost] => {
    "r_hostname.stdout": "hogehoge"
}

TASK [debug] *****************************************************************************************************
ok: [host1] => {                          # <= run_once: trueの為、1ノードのみで実行
    "ansible_playbook_python": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

TASK [debug] *****************************************************************************************************
ok: [host1] => {
    "hostvars['localhost'].ansible_connection": "local"  # <= local接続が設定されている
}

TASK [debug] *****************************************************************************************************
ok: [host1] => {
    "hostvars['localhost'].ansible_python_interpreter": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8" # <= 使用されるpythonもコントローラー側の pyton(ansible_playbook_python) と同じものが使用される。
}

PLAY RECAP *******************************************************************************************************
host1                      : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```


#例1c: Playbookでの指定 (localとその他のhostが同時に対象)

インベントリーには、他の接続先とlocalhostが含まれる想定


```:インベントリー
$ cat inv
localhost
host1 ansible_connection=ssh ansible_user=xxxx ansible_ssh_private_key_file="xxxx/id_rsa" ansible_host=xxxx ansible_port=nnnn ansible_python_interpreter=/usr/bin/python
```

ここでlocalhost ansible_connection=local ansible_python_interpreter=xxxx などとすることも可能。(例3) 

```yaml:site.yml
- hosts: all
  gather_facts: false
  pre_tasks:                                     # tasksの先頭でも良い。
    - when: inventory_hostname == 'localhost'    # 相手がlocalhostの場合
      set_fact:
        ansible_connection: local                # 接続方法をlocalに
        ansible_python_interpreter: '{{ ansible_playbook_python }}' # 接続先のpythonをコントラーのpythonに設定
  tasks:
    - shell: hostname
      register: r
    - debug: var=r.stdout

    - debug: var=ansible_playbook_python
    - debug: var=ansible_connection
    - debug: var=ansible_python_interpreter
    - debug: var=hostvars[inventory_hostname].ansible_connection
    - debug: var=hostvars[inventory_hostname].ansible_python_interpreter
```



## コマンドパラメーターと実行結果

```
$ ansible-playbook -i inv site.yml 

PLAY [all] *******************************************************************************************************

TASK [set_fact] **************************************************************************************************
skipping: [host1]
ok: [localhost]               # <= ここでlocalhostのansible_connectionなどを設定

TASK [shell] *****************************************************************************************************
changed: [localhost]
changed: [host1]

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "r.stdout": "hogehoge"
}
ok: [host1] => {
    "r.stdout": "host1"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_playbook_python": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}
ok: [host1] => {
    "ansible_playbook_python": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {      # <= もしansible_connectionを未設定の場合、sshとなる
    "ansible_connection": "local"
}
ok: [host1] => {
    "ansible_connection": "ssh"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {      # <= localhostのpythonは、set_factにてコントーラーのpythonに合わせている
    "ansible_python_interpreter": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}
ok: [host1] => {
    "ansible_python_interpreter": "/usr/bin/python"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {     # <= もしansible_connectionを未設定の場合、VARIABLE IS NOT DEFINED!となる
    "hostvars[inventory_hostname].ansible_connection": "local"
}
ok: [host1] => {
    "hostvars[inventory_hostname].ansible_connection": "ssh"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {     # <= もしansible_python_interpreterを未設定の場合、VARIABLE IS NOT DEFINED!となる
    "hostvars[inventory_hostname].ansible_python_interpreter": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}
ok: [host1] => {
    "hostvars[inventory_hostname].ansible_python_interpreter": "/usr/bin/python"
}

PLAY RECAP *******************************************************************************************************
host1                      : ok=7    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
localhost                  : ok=8    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

```


# 例2: パラメーターによる指定

## インベントリー: 無し

## Playbook

宛先は汎用的に、デフォルトで存在する all group を指定
接続方法指定は行わない。

```yml:site.yml

---
- hosts: all              
  gather_facts: false
  tasks:
  - shell: hostname
    register: r
  - debug: var=r.stdout

  - debug: var=ansible_playbook_python
  - debug: var=ansible_connection
  - debug: var=ansible_python_interpreter
  - debug: var=hostvars[inventory_hostname].ansible_connection
  - debug: var=hostvars[inventory_hostname].ansible_python_interpreter
```

##コマンドパラメーターと実行結果

- --connection=local (もしくは -c local) を使用して接続方法を指定
- --inventory=localhost, (もしくは -i localhost,) などを使用して接続を指定
    - この例では、直接接続先を指定しているが、ファイル指定でも良い。直接指定の場合、接続先が1つのみであっても、","を付与する必要がある。
    - 接続方法としてlocalを指定する為、実際はインベントリーは空でさえなければ良い


```
$ ansible-playbook -i localhost, -c local site.yml

PLAY [all] *******************************************************************************************************

TASK [shell] *****************************************************************************************************
[WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/bin/python, but
future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.

changed: [localhost]

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "r.stdout": "hogehoge"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_playbook_python": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_connection": "local"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_python_interpreter": "VARIABLE IS NOT DEFINED!"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "hostvars[inventory_hostname].ansible_connection": "VARIABLE IS NOT DEFINED!"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "hostvars[inventory_hostname].ansible_python_interpreter": "VARIABLE IS NOT DEFINED!"
}

PLAY RECAP *******************************************************************************************************
localhost                  : ok=7    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

- Ansible 2.9 から、ansible_python_interpreter 未指定のワーニングが出力される様になっている
    - [WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/bin/python, but
future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
- 対象がlocalhostのみならば、ansible-playbookに、-e ansible_python_interpreter=xxxxとして指定することが可能。localhost以外も対象となるならば、例1の様にplaybook内でset_factを使用するか、例3の様にインベントリーで指定する対応が現実的でしょう。










#例3: インベントリーを使用しての指定



##例3-1: INI書式

```ini:hosts
localhost ansible_connection=local ansible_python_interpreter=/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8
```

##例3-2: YAML書式

```ini:hosts
all:
  children:
    ungrouped:
      hosts:
        localhost:
          ansible_connection: local
          ansible_python_interpreter: /usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8
```

これは、例3-1のファイルを用いて、ansible-inventory -i hosts --list --yaml により生成可能

##例3-3: JSON書式



```ini:hosts
{
  "all": {
    "children": {
      "ungrouped": {
        "hosts": {
          "localhost": {
            "ansible_connection": "local",
            "ansible_python_interpreter": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
          }
        }
      }
    }
  }
}
```

これは、例3-1のファイルを用いて、ansible-inventory -i hosts --list  --yaml  | yq . などにより生成可能 (要 yq などyamlをjson化するコマンド/スクリプト)

ちなみに、ダイナミックインベントリーで返される必要があるJSONのフォーマットとは、ansible-inventory -i hosts --list により生成可能。(ややフォーマットが異なる)

## Playbook

宛先は汎用的にall指定
接続方法指定は行わない。

```yml:site.yml
---
- hosts: all              
  gather_facts: false
  tasks:
  - shell: hostname
    register: r
  - debug: var=r.stdout

  - debug: var=ansible_playbook_python
  - debug: var=ansible_connection
  - debug: var=ansible_python_interpreter
  - debug: var=hostvars[inventory_hostname].ansible_connection
  - debug: var=hostvars[inventory_hostname].ansible_python_interpreter
```

## コマンドパラメーターと実行結果

--inventory-file=hosts (もしくは -i hosts か --inventory=hosts) にてインベントリーファイルを指定
例3-1のINI書式、3-2のYAML書式、3-3のJSON書式、いずれも使用可能。
また、ダイナミックインベントリ用JSONを返す実行可能スクリプトなどを指定しても良い。

```
$ ansible-playbook -i hosts site.yml 

PLAY [all] *******************************************************************************************************

TASK [shell] *****************************************************************************************************
changed: [localhost]

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "r.stdout": "hogehoge"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_playbook_python": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_connection": "local"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "ansible_python_interpreter": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "hostvars[inventory_hostname].ansible_connection": "local"
}

TASK [debug] *****************************************************************************************************
ok: [localhost] => {
    "hostvars[inventory_hostname].ansible_python_interpreter": "/usr/local/Cellar/ansible/2.9.4_1/libexec/bin/python3.8"
}

PLAY RECAP *******************************************************************************************************
localhost                  : ok=7    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```








#参考:

- [Local Playbooks](https://docs.ansible.com/ansible/latest/user_guide/playbooks_delegation.html#local-playbooks)
- [Ansibleチートシート（実行周り）](https://qiita.com/unarist/items/39f5510f95c752c10df1)
- [[Ansible] sshで繋がずにlocalhostで実行する](https://qiita.com/hnakamur/items/c7c4f7277c07a14a4a59)
- [\[Ansible\] playをまたいだ変数保持をdelegate_toとfact変数で行う](https://zaki-hmkc.hatenablog.com/entry/2020/04/15/083708)
- [\[Ansible\] 他のホスト変数を参照、設定する（hostvars、delegate_to/delegate_facts）](https://tekunabe.hatenablog.jp/entry/2020/04/17/ansible_beyond_host_vars)