ローカル環境でAnsibleの鍵交換がめんどくさい貴方に送るプラクティス

# はじめに

平成の時分も終わりに近づく中、野分立ち尽くす天災に
人々は翻弄され、お家で過ごすのを余儀なくされる日が多いように思います。[^1]

今日のような一日は、自然とQiitaにたどり着き、
PVが増えるのではないかと勝手に邪推する筆者です。

さて、話は閑話休題。
ローカル環境で、仮想マシン立ててAnsible環境作る際に、
コントロールノードからターゲットノードにSSHの鍵交換をすると思うのですが、
ターゲットノードが多い際の交換を一つ一つ`ssh-copy-id`していくのは
甚だ煩わしいこととお感じではないでしょうか。

はじめに鍵交換をしてしまえば、後はLet's Ansible[^2]するだけなのですが、
めんどくさがりの権化である私はどうもこの作業すら億劫になります。
ローカルマシンで冗長構成のWebサーバなんてのを作って遊んでをするときは
最初が兎にも角にもめんどくさい。

**環境構築こそ、一番ラクに終わらせて後はInfrastructure as Code(IaC)
していくのが本分なのではないでしょうか。**

# Environment

- macOS Mojave 10.14
- Vagrant 2.1.5
- VartualBox 5.2.16 r123759 (Qt5.6.3)
- CentOS7のbox(今回はVagrant Cloudの`geerlingguy/centos7`を使用)

※ご自身の環境に合わせて事前に用意をして下さい

# 仮想環境を立てる

今回の例では、10台のノードをVagrantで立てます。その際、
1台をコントロールノード、残り9台をコントロールノードとして使用します。


任意のディレクトリ下で`init`をしましょう。

```bash
$ vagrant init geerlingguy/centos7
```

作成したディレクトリに`Vagrantfile`が作成されるので、下記のように記述して下さい。

参考URL：[Vagrant Cloud](https://app.vagrantup.com/boxes/search)



```ruby:Vagrantfile
Vagrant.configure("2") do |config|
  config.ssh.insert_key = false
  config.vm.box = "geerlingguy/centos7"

  N = 10
  (1..N).each do |node_id|
    config.vm.define "node#{node_id}" do |server|
      server.vm.hostname = "server#{node_id}"
      server.vm.network "private_network", ip: "192.168.200.#{10+node_id}"
    end
  end
end
```
上記のVagrantfileでは、`192.168.200.0`のプライベートネットワークに連番で、`192.168.200.11`から`192.168.200.20`のIPがそれぞれのノードに振られます。また、同様にそれぞれのSSHする際のノード名は`node1:10`、machine`hostname`は`server1:10`という名前が割り当てられます。


参考URL：[Loop Over VM Definitions](https://www.vagrantup.com/docs/vagrantfile/tips.html#loop-over-vm-definitions)

次に、boxを登録した後、`vagrant up`します。
この例では、事前に`vagrant box add`はする必要がなく、
`vagrant init`した際にboxをpullします。

```bash
# 全てのノードを起動する
$ vagrant up

# コントロールノード(node1)に接続
$ vagrant ssh node1
```

# コントロールノードの設定

## expectで全てのノードに送る

```bash
# ansivbleｒとexepctをnode1にインストールしておく
[vagrant@server1 ~]$ sudo yum -y install ansible expect
```

仮想マシンが10台起動した後、`node1`から`node2:10`を操作する場合、
全てに鍵を送らないといけません。`expect`を使用することで、それぞれのIPアドレスに対する
対話形式のコマンドラインを全て変数で渡して処理することができます。


```bash
#!/bin/sh

export LANG=C
pw="vagrant"

for i in `seq 12 20`
do
    expect -c "
    set timeout 5
    spawn ssh-copy-id -o StrictHostKeyChecking=no -i $HOME/.ssh/id_rsa.pub 192.168.200.$i
    expect \"password:\"
    send \"${pw}\n\"
    expect \"$\"
    exit 0
    "
done
```


詳しくは、@ine1127 様の[Linuxの対話がめんどくさい?そんな時こそ自動化だ！-expect編-](https://qiita.com/ine1127/items/cd6bc91174635016db9b)の記事をお読み下さい。

鍵を作成後、シェルスクリプトを動かしてみましょう。
成功していれば、`Number of key(s) added: 1`の記述がそれぞれのサーバに対して表示されるはずです。

```bash
# 公開鍵を作成 （名前を変更した場合、適宜スクリプトの変更をする）
[vagrant@server1 ~]$ ssh-keygen

# 実行権限を与える
[vagrant@server1 ~]$ chmod 755 copy_ssh_keys.sh

[vagrant@server1 ~]$ ./copy_ssh_keys.sh
```

## Ansibleの実行確認

インベントリは連番であれば上記のように記載できます。

```yaml:inventory.ini
[vm_nodes]
192.168.200.[12:20]
```

最後にAnsibleで意思疎通ができるか`ping`コマンドを送ってみましょう。


```bash

$ ansible vm_nodes -i inventory.ini -m ping

192.168.200.14 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.15 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.13 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.12 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.16 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.17 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.18 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.19 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.200.20 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
```

`SUCCESS`が返ってこれば成功です。
おめでとうございます。


# おわりに

私が思いついた方法として上記の例を取り上げましたが、もっといい方法があるのではないかと個人的に思っております。そういった意味で**ベストプラクティス**ではなく**プラクティス**というタイトルをつけさせていただきました。上記よりも適切な方法があるのでれば、コメントにて教えていただければ幸いでございます。

最後までお読み頂きありがとうございました。

[^1]: 被災された皆様に心よりお見舞い申し上げます。我が実家も同様に。
[^2]: なんとなく言ってみたかった。