Ansible検証環境に特化したVagrantを使ったVM構築 - 複数VM・VM間ssh公開鍵認証設定・共有フォルダ

# はじめに

Ansibleの検証環境を、手元のVirtualBox上にVagrantを使って構築する。
Vagrantを使うとVMの作成は簡単にできるが、Ansibleの検証環境ということで、以下の条件を追加

- 一つのVagrantfileで複数VM作成
- VM間で固定IPアドレスでネットワークがつながる
- VM間のsshアクセスのため同じ秘密鍵・公開鍵を設定
- AnsibleのPlaybookなどをホストOSから簡単に編集するための共有フォルダ設定

Ansibleのインストールも含めてもいいけど、まぁそこは1台なので手動で。(対象VMに`shell`でインストールしてもいいけど)

- [Vagrantとは？についてはこちら](https://qiita.com/zaki-lknr/items/5a8f49123a26ba4af671)

# Vagrantfile

急いでいる人向けに、完成品のVagrantfileをまず置いておきます。
ホストOS上のAnsible資材は`~/src/ansible`にあります。

```ruby:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"

  # shared folder
  config.vm.synced_folder "~/src/ansible", "/ansible", mount_options: ['dmode=755', 'fmode=644']

  # common ssh-private-key
  config.ssh.insert_key = false
  config.ssh.private_key_path = "~/.vagrant.d/insecure_private_key"

  # copy to private-key
  config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/.ssh/id_rsa"
  config.vm.provision "shell", privileged: false, inline: <<-SHELL
    chmod 600 $HOME/.ssh/id_rsa
  SHELL

  config.vm.define "ansible-controller" do |ansible_controller|
    ansible_controller.vm.network "private_network", ip: "192.168.244.120"
    # OS hostname
    ansible_controller.vm.hostname = "ansible-controller.localhost"
    ansible_controller.vm.provider "virtualbox" do |vb|
      # Customize the amount of memory on the VM:
      vb.memory = "1024"
      # VM name
      vb.name = "ansible-controller"
    end
  end

  config.vm.define "ansible-node01" do |ansible_node01|
    ansible_node01.vm.network "private_network", ip: "192.168.244.121"
    # OS hostname
    ansible_node01.vm.hostname = "ansible-node01.localhost"
    ansible_node01.vm.provider "virtualbox" do |vb|
      # Customize the amount of memory on the VM:
      vb.memory = "1024"
      # VM name
      vb.name = "ansible-node01"
    end
  end

  config.vm.define "ansible-node02" do |ansible_node01|
    ansible_node01.vm.network "private_network", ip: "192.168.244.122"
    # OS hostname
    ansible_node01.vm.hostname = "ansible-node02.localhost"
    ansible_node01.vm.provider "virtualbox" do |vb|
      # Customize the amount of memory on the VM:
      vb.memory = "1024"
      # VM name
      vb.name = "ansible-node02"
    end
  end
end
```

| コマンド                              | 動作                         |
| --------------------------------- | -------------------------- |
| `vagrant up`                      | 全VM起動                      |
| `vagrant halt`                    | 全VM停止                      |
| `vagrant destroy`                 | 全VM削除                      |
| `vagrant reload`                  | 全VM Vagrantfile読みなおし       |
| `vagrant ssh ansible-controller`  | ansible-controllerへsshアクセス |
| `vagrant up ansible-controller`   | ansible-controllerのみ起動     |
| `vagrant halt ansible-controller` | ansible-controllerのみ停止     |

概要図

![vagrant for ansible.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/69b2ca4c-9b90-e34d-5a88-ddbd250fafe0.png)

---

以下解説

# 1ファイル複数VMの設定

- [Multi-Machine - Vagrant by HashiCorp](https://www.vagrantup.com/docs/multi-machine/index.html)
- [[Vagrant] Vagrant で複数のVM を立ち上げて、お互いに通信できるようにするには [VirtualBox] - Qiita](https://qiita.com/sho7650/items/cf5a586713f0aec86dc0)

```ruby
  config.vm.define "ansible-controller" do |ansible_controller|
    ansible_controller.vm.network "private_network", ip: "192.168.244.120"
    # OS hostname
    ansible_controller.vm.hostname = "ansible-controller.localhost"
    ansible_controller.vm.provider "virtualbox" do |vb|
      # Customize the amount of memory on the VM:
      vb.memory = "1024"
      # VM name
      vb.name = "ansible-controller"
    end
  end

  config.vm.define "ansible-node01" do |ansible_node01|
    ansible_node01.vm.network "private_network", ip: "192.168.244.121"
    # OS hostname
    ansible_node01.vm.hostname = "ansible-node01.localhost"
    ansible_node01.vm.provider "virtualbox" do |vb|
      # Customize the amount of memory on the VM:
      vb.memory = "1024"
      # VM name
      vb.name = "ansible-node01"
    end
  end
```

だいたいこの部分。(node02は省略)
Rubyの読み書きができずに、初めは既存部分からコピペでうまくいかなかったけど、

```ruby
  config.vm.define "ansible-controller" do |ansible_controller|
```

に対して

```diff
- config.vm.network
+ ansible_controller.vm.network
```

と、定義した`ansible_controller`で設定を定義してやる必要がある。
これをVMの個数分(そして、ホスト名やIPアドレスなどのVM毎に設定が異なる内容に対して)繰り返す。

各VMで共通の定義については、VM毎の設定に書かずに既存の定義部分のままでよい。

```ruby
  config.vm.box = "centos/7"
```

ベースになるBoxの種類とかね。

# VM間で固定IPアドレスでネットワークがつながる

デフォルトの(インターネットアクセスもできる)NAT用のアドレスだと、VM間でpingは通るけど、IPアドレスが固定ではないので検証には不向き。
ついでにホストOSからもアクセスできないし。

ということでホストオンリーアダプタを使って同セグメントの固定IPアドレスを設定する。

- [Vagrantを使ったVirtualBoxへのVM構築 - Qiita](https://qiita.com/zaki-lknr/items/5a8f49123a26ba4af671#ip%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9)
- [Ubuntu 18.04(on VirtualBox) ホストOSからの通信 - Qiita](https://qiita.com/zaki-lknr/items/a762051d5d26627be068)

これはVM毎に設定する。
この部分。

```ruby
  config.vm.define "ansible-controller" do |ansible_controller|
    ansible_controller.vm.network "private_network", ip: "192.168.244.120"
    #[...]
  end

  config.vm.define "ansible-node01" do |ansible_node01|
    ansible_node01.vm.network "private_network", ip: "192.168.244.121"
    #[...]
  end

  config.vm.define "ansible-node02" do |ansible_node01|
    ansible_node01.vm.network "private_network", ip: "192.168.244.122"
    #[...]
  end
```

# VM間のsshアクセスのため同じ秘密鍵・公開鍵を設定

あきらめて手動で設定したろかーと最も苦労した部分。。

Vagrantのssh関連は特に指定がない場合は、VM毎に秘密鍵・公開鍵が作成され、ホストOSからの`vagrant ssh [hostname]`実行によるsshアクセスはVM毎の公開鍵で行われる。
そのため、この公開鍵ではVM間の公開鍵認証には使えない。
また、秘密鍵はVM内には保持されず、ホストOS上のみに配置される。

作成・使用されるssh鍵は、VM実行中に以下のコマンドで確認できる。

```
zaki@mascarpone% vagrant ssh-config
Host ansible-controller
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/zaki/src/vagrant/ansible/.vagrant/machines/ansible-controller/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

Host ansible-node01
  HostName 127.0.0.1
  User vagrant
  Port 2200
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/zaki/src/vagrant/ansible/.vagrant/machines/ansible-node01/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

zaki@mascarpone%
```

`IdentityFile`に(`vagrant ssh`で)使用する秘密鍵のパスが表示され、VM毎に異なるのがわかる。

そこでまずは、「各VMで同じ秘密鍵・公開鍵を使うための設定」を行う。

```ruby
  config.ssh.insert_key = false
```

この`config.ssh.insert_key`を`false`にする設定によって、「VM毎にsshの秘密鍵・公開鍵を作成・設定する」という動作が無くなる。

次に、`config.ssh.private_key_path`で、替わりの秘密鍵を指定する。(ちなみにデフォルトで`~/.vagrant.d/insecure_private_key`の鍵が使用される)
実はVagrantをインストールすると、ホームディレクトリ以下`~/.vagrant.d/insecure_private_key`にパスフレーズなしの共通設定用の鍵が生成されている。今回はこれを使う。
もちろん`ssh-keygen`でパスフレーズなしの鍵を別途作成してそれを使っても良い。

```ruby
  config.ssh.private_key_path = "~/.vagrant.d/insecure_private_key"
```

この設定の状態で`ssh-config`を確認すると、各VMで同じ秘密鍵・公開鍵を使用する設定になる。

```
zaki@mascarpone% vagrant ssh-config
Host ansible-controller
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/zaki/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL

Host ansible-node01
  HostName 127.0.0.1
  User vagrant
  Port 2200
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/zaki/.vagrant.d/insecure_private_key
  IdentitiesOnly yes
  LogLevel FATAL

zaki@mascarpone%
```

ただし、このままでは、ホストOS→ゲストOS間のsshアクセスで使用するssh鍵が、各ゲストOSで同じになっただけで、各ゲストOS上にこの秘密鍵がまだないため、ゲストOS同士で公開鍵認証ができない。(秘密鍵がホストOS上にしかないため)

そこで、このホストOS上の秘密鍵を、作成したVM上にコピーする処理を追加する。

```ruby
  config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/.ssh/id_rsa"
```

ホストOS上にあるファイルをゲストOSにコピーするには[File Provisioner](https://www.vagrantup.com/docs/provisioning/file.html)を使用する。
(大項目に[Push](https://www.vagrantup.com/docs/push/)なんてあるから、こっち使ってできると思い込んでいろいろやってもうまくいかなかったのであきらめかけたけど、別の機能だった…)
これでホストOS上の`source: "~/.vagrant.d/insecure_private_key"`のファイルを、ゲストOSの`destination: "/home/vagrant/.ssh/id_rsa"`へ配布できる。

ただしこれだけだと、mode設定が維持できず他者にもread権限がついてしまってssh実行時にエラーとなるため、手動で権限設定を追加する。
File Provisionで指定できればいいのによくわからなかったので、shellで。

```ruby
  config.vm.provision "shell", privileged: false, inline: <<-SHELL
    chmod 600 $HOME/.ssh/id_rsa
  SHELL
```

これで各VMで同じ秘密鍵・公開鍵を使いつつ、VM内に秘密鍵の配布まで設定できる。

# AnsibleのPlaybookなどをホストOSから簡単に編集するための共有フォルダ設定

Ansibleの資材はホストOSの`~/src/ansible`にあるとする。
この部分。

```ruby
  # shared folder
  config.vm.synced_folder "~/src/ansible", "/ansible", mount_options: ['dmode=755', 'fmode=644']
```

ホストOSの`~/src/ansible`を、ゲストOSの`/ansible`にマウントする。
オプションを何もつけない場合、ファイルやディレクトリが777のフルオープン状態になり、Ansibleの実行で警告が出力されるなど検証に差し支える。

```
 [WARNING] Ansible is being run in a world writable directory (/ansible/practice), ignoring it as an ansible.cfg source. For more information see https://docs.ansible.com/ansible/devel/reference_appendices/config.html#cfg-in-world-writable-dir
```

- [vagrant で synced_folder の権限変更 - Qiita](https://qiita.com/8mamo10/items/ba21bd4e21a0e18efeef)
- [Basic Usage - Synced Folders - Vagrant by HashiCorp](https://www.vagrantup.com/docs/synced-folders/basic_usage.html)

で、共有フォルダ設定自体もすんなりとはいかなかったけど、こちらはエラーメッセージでググればすぐに答えにたどり着いたので問題なかった。
初実行時はこんなエラーが出力された。

```
==> default: Mounting shared folders...
    default: /vagrant_data => C:/Users/zaki/src/vagrant/data
Vagrant was unable to mount VirtualBox shared folders. This is usually
because the filesystem "vboxsf" is not available. This filesystem is
made available via the VirtualBox Guest Additions and kernel module.
Please verify that these guest additions are properly installed in the
guest. This is not a bug in Vagrant and is usually caused by a faulty
Vagrant box. For context, the command attempted was:

mount -t vboxsf -o uid=1000,gid=1000 vagrant_data /vagrant_data

The error output from the command was:

mount: unknown filesystem type 'vboxsf'
```

調べた感じだと、vboxsfでマウントするためのGuest Additionsが古いのが原因っぽい。
Vagrantのプラグインvagrant-vbguestを入れておくと、VM起動時によしなにしてくれるらしい。

[Vagrantで共有フォルダのマウントに失敗するときの対処方法 - Qiita](https://qiita.com/ozawan/items/9751dcfd9bd4c470cd82)

ので、設定する。

## プラグインインストール

```
zaki@mascarpone% vagrant plugin install vagrant-vbguest
Installing the 'vagrant-vbguest' plugin. This can take a few minutes...
Installed the plugin 'vagrant-vbguest (0.18.0)'!
zaki@mascarpone% vagrant plugin list
vagrant-disksize (0.1.3, global)
vagrant-vbguest (0.18.0, global)
```

## 現在のGuest Additionsの確認

```
zaki@mascarpone% vagrant vbguest --status
[default] No Virtualbox Guest Additions installation found.
```

なんと入っていなかった。

## Guest Additionsのインストール(更新)

`vagrant vbguest`と引数・オプションなしで実行する。

```
zaki@mascarpone% vagrant vbguest
[default] No Virtualbox Guest Additions installation found.
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: ty1.mirror.newmediaexpress.com
 * extras: ty1.mirror.newmediaexpress.com
 * updates: ty1.mirror.newmediaexpress.com
 :
 :
Dependencies Resolved

================================================================================
 Package                   Arch      Version                   Repository  Size
================================================================================
Installing:
 gcc                       x86_64    4.8.5-36.el7_6.2          updates     16 M
 kernel-devel              x86_64    3.10.0-957.5.1.el7        updates     17 M
 kernel-devel              x86_64    3.10.0-957.21.3.el7       updates     17 M
 perl                      x86_64    4:5.16.3-294.el7_6        updates    8.0 M
:
:
```

インストール処理が始まる。

```
:
Dependency Updated:
  glibc.x86_64 0:2.17-260.el7_6.5     glibc-common.x86_64 0:2.17-260.el7_6.5
  libgcc.x86_64 0:4.8.5-36.el7_6.2    libgomp.x86_64 0:4.8.5-36.el7_6.2

Complete!
Copy iso file C:\Program Files\Oracle\VirtualBox\VBoxGuestAdditions.iso into the box /tmp/VBoxGuestAdditions.iso
Mounting Virtualbox Guest Additions ISO to: /mnt
mount: /dev/loop0 is write-protected, mounting read-only
Installing Virtualbox Guest Additions 5.2.26 - guest version is unknown
Verifying archive integrity... All good.
Uncompressing VirtualBox 5.2.26 Guest Additions for Linux........
VirtualBox Guest Additions installer
Copying additional installer modules ...
Installing additional modules ...
VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel
modules.  This may take a while.
VirtualBox Guest Additions: To build modules for other installed kernels, run
VirtualBox Guest Additions:   /sbin/rcvboxadd quicksetup <version>
VirtualBox Guest Additions: Building the modules for kernel
3.10.0-957.5.1.el7.x86_64.
VirtualBox Guest Additions: Starting.
Redirecting to /bin/systemctl start vboxadd.service
Redirecting to /bin/systemctl start vboxadd-service.service
Unmounting Virtualbox Guest Additions ISO from: /mnt
```

```
zaki@mascarpone% vagrant vbguest --status
[default] GuestAdditions 5.2.26 running --- OK.
zaki@mascarpone%
```

これで`vagrant reload`すれば、共有フォルダ設定が有効になる。