既存のLinuxサーバーでAnsibleを使えるようにするための準備

# 概要

本記事は[Ansible Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ansible)の16日目の記事になります。

Ansibleを使うことを意識せずにセットアップされたLinuxマシンに対して、何をすればAnsible Readyになるかについて説明します。

Ansibleを使ったサーバーやネットワーク構築の情報は豊富だし、「この環境を使えばすぐにAnsible動かせます」という環境も割とあると思うのですが、「今あるこの手元(特にオンプレ)の環境でAnsibleを使い始めるにはどうすればよいか」についての情報が少ないと(私が個人的に)感じたので、その内容についてまとめました。

ですので本記事は「Ansibleを勉強し始めるにはどうすればよいか」ではなく「Ansibleを使ったサーバー構築を始めるときは何を準備すればいいか」という内容になっています。

---

- 「Ansibleを勉強したい！」という方はこちら: [【初心者歓迎】Ansible始めてみたい人はまずはAnsibleもくもく会に参加してみよう！](https://qiita.com/zaki-lknr/items/35669f9d6a3f3393dd06)
- すぐに検証できるAnsible環境がとりあえず手元に欲しい方はこちら: [Ansible検証環境に特化したVagrantを使ったVM構築 - 複数VM・VM間ssh公開鍵認証設定・共有フォルダ](https://qiita.com/zaki-lknr/items/cdf4eac2d2f2020ac7be)

# 構成

CentOSを前提としています。

|ホスト名|IPアドレス|役割|
|---|---|---|
|control-node|192.168.0.140|コントロールノード|
|target-node01|192.168.0.141|ターゲットノード|
|target-node02|192.168.0.142|ターゲットノード|

Ansibleのコマンド(`ansible`や`ansible-playbook`など)を実行するホストをコントロールノード、コントロールノードのAnsibleによって処理される対象のホストをターゲットノードと言います。

- コントロールノードに必要なもの
  - Ansible
  - Python

- ターゲットノードに必要なもの
  - Python
  - SSHサーバー
  - sudoによるrootへの昇格

また本記事では、コントロールノード・ターゲットノードそれぞれ、作業用のユーザ名(非root)と認証用パスワードは全て同一である前提です。

# インストールと設定

## Ansible本体

CentOS7の場合、標準のYumリポジトリだとAnsible 2.4がインストールされますが、ちょっと古すぎます(2019.12時点で最新は2.9)

```console
$ yum info ansible
:
:
利用可能なパッケージ
名前                : ansible
アーキテクチャー    : noarch
バージョン          : 2.4.2.0
リリース            : 2.el7
容量                : 7.6 M
リポジトリー        : extras/7/x86_64
要約                : SSH-based configuration management, deployment, and task

:
:
```

ですので、CentOS7の場合はEPELリポジトリにあるパッケージでインストールすると楽で良いです。

```console
$ sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
$ sudo yum install ansible
```

これで最新安定板がインストールできるはずです。
バージョンを確認するには`ansible --version`を実行します。

```console
[zaki@control-node ~]$ ansible --version
ansible 2.9.1
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/zaki/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Aug  7 2019, 00:51:29) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
[zaki@control-node ~]$
```

RHELやDebian/Ubuntuなどその他のプラットフォームの場合については、[AnsibleのInstallation Guide](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html)に載っています。
また、`pip`を使ってもインストールできます。

インストールができたら簡単な動作確認として、対象でAnsibleが実行できるかを確認する`ping`モジュールをlocalhost宛に実行してみます。

```console
$ ansible localhost -m ping
localhost | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
```

`SUCCESS`と表示されればOKです。
※ ICMPを使ったLinuxの`ping`コマンドと異なり、疎通というよりはAnsibleが実行可能かどうかを確認します。また、`localhost`の場合はSSH接続を要しません

## Python

`yum`などのパッケージマネージャでインストールできます…が、最近のよく使われるディストリビューションであればPythonは初めから入っているはずです。
また、Ansibleのインストール要件にPythonは必要とされるので、Ansibleがインストール済みであればPythonもインストール済みになっているはずです。

```console
$ python --version
```

でPythonのバージョンが確認できます。
※ Python version2 は、2020年1月1日でサポートが終了します。

## SSHアクセス

SSHサーバーもメジャーなディストリビューションであれば、OSインストール直後の状態で起動しているはずです。
Ansibleを使えるようにするには、コントロールノードからターゲットノードへSSHアクセスできる必要があります。(ターゲットノードにSSHサーバーが動いていること)

認証についてはパスワード認証・公開鍵認証どちらでも実行可能ですが、パスワード認証やパスフレーズ付き公開鍵認証の場合は実行のたびにパスワード/パスフレーズの入力が必要になるので、パスフレーズ無しの秘密鍵を設定すると構築の際は手間が無くて便利です。

```console
$ ssh-keygen -t rsa -f $HOME/.ssh/id_rsa -N ""
```

これで`~/.ssh/id_rsa`にパスフレーズ無しの公開鍵が作成されます。
キーペアを作成したら、各ターゲットノードへ公開鍵を配布します。

```console
$ ssh-copy-id zaki@192.168.0.141 # ターゲットノード1
$ ssh-copy-id zaki@192.168.0.142 # ターゲットノード2
```

これで認証無しで各ターゲットノードへSSHアクセスできるはずです。

```console
[zaki@control-node ~]$ ssh 192.168.0.141
[zaki@target-node01 ~]$
```

```console
[zaki@control-node ~]$ ssh 192.168.0.142
[zaki@target-node02 ~]$
```

ここまで設定できれば、2台のターゲットノードに対して`ping`モジュールでアクセスできるはずです。

ターゲットノードを定義した以下の内容のインベントリファイルを作成。

```text:inventory.ini
[nodes]
192.168.0.141
192.168.0.142
```

このインベントリファイルを`-i`で指定し`ansible`を実行。この時、ローカルを対象に実行した際に`localhost`と指定したところを、インベントリの最初の行に書いた`[nodes]`というグループ名を指定します。

```console
$ ansible nodes -i inventory.ini -m ping
192.168.0.142 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
192.168.0.141 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
```

これでコントロールノードからターゲットノードに対してAnsibleが実行できることが確認できました。

パスフレーズ無しの公開鍵設定を行わない場合は、以下のようにすることでAnsibleは実行できます。

|認証方式|Ansibleの操作|
|---|---|
|パスワード認証|`-k`を付与することでパスワード認証のプロンプトを表示する|
|パスフレーズ付き公開鍵認証|自動でパスフレーズの入力プロンプトが表示される|

## sudo設定

さらににもう1点。
Ansibleを使ってサーバー構築する場合、(`yum`によるパッケージインストールなど)大抵の作業はroot権限が必要です。
ターゲットノードにおいて`sudo`がそもそも使用不可能である場合は、対象ユーザに対して(`wheel`グループに追加するなどの)sudoersの設定を行ってください。

また、`sudo`実行時にパスワードが不要な設定にしておけば、Ansible実行時にもパスワード入力が必要なくなるため、手間が無くて便利です。

```text:/etc/sudoers
%wheel  ALL=(ALL)       NOPASSWD: ALL
```

※ `vi`じゃなくて`visudo`を使いましょう

これは全ターゲットノードで実施します。(コントロールノードに対しても処理を行う場合はコントロールノードにも設定)
設定できたら`-b`オプションを追加(デフォルトrootとして動作)してAnsibleを実行します。

```console
$ ansible nodes -i inventory.ini -b -m ping
192.168.0.142 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
192.168.0.141 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
```

エラーにならなければ、ターゲットノード上ではroot権限で処理が実行されます。
といっても`ping`モジュールだとわかりづらいので、もし全ノードで`yum update`してもよいのであれば、以下のコマンドを実行してみましょう。全てのパッケージが更新されます。

```
$ ansible nodes -i inventory.ini -b -m yum -a "name=* state=latest"
```

パスワード無しの`sudo`設定を行わない場合は、実行時に`-K`を付与することでroot権限でAnsibleは実行できます。
パスワード認証用の`-k`と併用可能です。

# 初期構築のAnsibleによる自動化

ここまで手作業でAnsibleのインストールとSSH/sudoの設定について説明しましたが、せっかくAnsibleをインストールするのだから、[じゃがいも](https://twitter.com/angrypotato_jp)に怒られないように自動化できるところは自動化しちゃいましょう。
特に全てのターゲットノードに対して、SSH公開鍵を配布とsudo設定は手作業でポチポチやるものじゃないです。

## 前提

- コントロールノードでのAnsibleのインストール(手動)
- ターゲットノードで作業ユーザが`wheel`グループに所属しておりsudoでroot昇格できること(ただし要パスワード)
- 作業ユーザ名とパスワードは全ノード共通
- パスワード認証でSSH接続でき、Pythonがインストール済みである

それ以外の要件であるパスフレーズ無しSSH公開鍵設定とパスワード無しsudo設定をAnsibleでやります。

## ansible.cfg

ここまで説明してないので、初回SSH実行時の手間を減らすおまじないだと思ってください。

```text:ansible.cfg
[defaults]
host_key_checking = False

[ssh_connection]
ssh_args -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
```

## インベントリ

```text:inventory.ini
[nodes]
192.168.0.141
192.168.0.142
```

※ Ansibleを実行するコントロールノード(192.168.0.140)も構築対象にする場合、それも追加してください。

## プレイブック

```yml:playbook.yml
---
- hosts: localhost
  tasks:
  - name: create directory for ssh keypair
    file:
      path: "{{ lookup('env','HOME') }}/.ssh/"
      state: directory
      mode: 0700

  - name: create ssh privatekey
    openssh_keypair:
      path: "{{ lookup('env','HOME') }}/.ssh/id_rsa"

- hosts: all
  tasks:
  - name: publickey copy to target-nodes
    authorized_key:
      user: "{{ ansible_env.USER }}"
      key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/id_rsa.pub' )}}"

- hosts: all
  become: True
  tasks:
  - name: configure non-password sudo
    copy:
      dest: /etc/sudoers.d/nopass
      mode: 0600
      content: |
        %wheel ALL=(ALL) NOPASSWD: ALL
```

この3つのファイルを同一ディレクトリに置いて、以下のコマンドを実行。

```
$ ansible-playbook -i inventory.ini playbook.yml -kK
```

`-k`でパスワード認証、`-K`でsudoの昇格用パスワード認証がまだ未設定(このAnsibleで設定する)なので、忘れないように。

[`lookup`プラグイン](https://docs.ansible.com/ansible/latest/plugins/lookup.html)などを使って以下のものを外部参照しています。

|変数|内容|
|---|---|
|{{ lookup('env','HOME') }}|実行ユーザのホームディレクトリのパス|
|{{ ansible_env.USER }}|ユーザ名|
|{{ lookup('file', lookup('env', 'HOME') + '/.ssh/id_rsa.pub' )}}|`~/.ssh/id_rsa.pub`のファイルの内容|

以上を踏まえて、このプレイブックの各タスクでは以下の処理が行われます。
(既にパスワード無しで`sudo`できるなど、設定済みのものがあれば削ってください)

- create directory for ssh keypair (ローカル実行)
    - `~/.ssh`ディレクトリを作成
- create ssh privatekey (ローカル実行)
    - SSH秘密鍵キーペアを`~/.ssh/id_rsa`に作成
- publickey copy to target-nodes (リモートアクセス)
    - 全ターゲットノードのauthorized_keyに`~/.ssh/id_rsa.pub`をコピー
- configure non-password sudo (要root権限)
    - 全ターゲットノードのsudoの`NOPASSWD`設定

ね、簡単でしょう？

### (補足)openssh_keypairモジュールについて

[ドキュメント](https://docs.ansible.com/ansible/latest/modules/openssh_keypair_module.html)にも記載がありますが、このモジュールはversion 2.8から使用可能な新しいモジュールです。それ以前のAnsibleバージョンだと使えません。

それ以前のバージョンを使っている場合は、モジュールでは鍵作成ができないので、`shell`モジュールで作成するタスクに置き換えます。

```yml:playbook.yml
  - name: check exists ssh privatekey
    shell: test -f $HOME/.ssh/id_rsa
    register: exist_key
    ignore_errors: True
    check_mode: False
    changed_when: False
  - name: create ssh privatekey
    shell: ssh-keygen -t rsa -f $HOME/.ssh/id_rsa -N ""
    when: exist_key.rc != 0
```

ちょっと長いけど「`~/.ssh/id_rsa`があるかどうかチェック」して「なければ`ssh-keygen`」という2段階になっています。
初回実行時は`~/.ssh/id_rsa`が無いためエラーの表示が出力されますが、`ignore_errors: True`によって無視して処理が継続されます。

…`ssh-keygen`って強制上書きモードないんかな？？

# まとめ

ということで、Ansibleを使ったサーバー構築をするための準備について、具体的な作業手順(マニュアル＆Ansibleによる自動構築)について説明しました。

台数が2,3台であれば手作業でもミスなく頑張れると思いますが、10台20台になってくると絶対に自動化した方が良いです。

ネットワークの自動化やクラウド環境だとまたちょっと変わってくると思いますが、オンプレでのAnsible使ったサーバー構築の自動化はこれで始めることができると思います。

余談だけど、AnsibleのインストールはAnsibleで自動化できないのだろうか。。

---
参考

- [openssh_keypair – Generate OpenSSH private and public keys — Ansible Documentation](https://docs.ansible.com/ansible/latest/modules/openssh_keypair_module.html) ※ version 2.8から
- [authorized_key – Adds or removes an SSH authorized key — Ansible Documentation](https://docs.ansible.com/ansible/latest/modules/authorized_key_module.html)
- [OSSはアルミニウムの翼で飛ぶ: ものぐさ playbook](https://aikotobaha.blogspot.com/2019/11/playbook.html)
- [Ansibleでとりあえず authorized_key を通す - Qiita](https://qiita.com/ono_pm/items/4395433e535b4121fdfb)
- [IT とかその他もろもろ: Ansible の authorized_key モジュールで lookup プラグインを使う時にユーザー名を変数で指定](http://fishrimper.blogspot.com/2015/04/ansible-authorizedkey-lookup.html)