以前設計した DNS を、Ansible を用いて Vagrant 上にプロビジョニングしてみる。
と言っても Ansible + Vagrant は他の人が書いた記事がたくさんあるので、今回は以下のような点に焦点をあてる。
- Vagrant で複数の VM (master * 1, slave * 2) を扱う
- VM には
private_network
で直接 (NAPT ではなく) アクセスする - VM どうしで ssh 接続させるが、秘密鍵はレポジトリ内に置かない
- 本番環境にも同じ Playbooks でプロビジョニングできるような設計にする
前提環境
以下の環境を前提に話を進める
- Mac
- Vagrant 1.3.5
- VirtualBox 4.3.10
- Ansible 1.6.2
環境の準備
ゲスト OS は Ubuntu 12.04 を使用する。あらかじめ ubuntu-server-12.04-amd64
という名前で Vagrant に追加しておく。
$ vagrant box add ubuntu-server-12.04-amd64 \
http://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box
VM の private_network
のアドレスにパス無しで ssh できるように、Mac の ~/.ssh/config
に次の設定を追記しておく。VM に付けるアドレスは 10.200.19.0/24
とする。
Host 10.200.19.*
User vagrant
IdentityFile ~/.vagrant.d/insecure_private_key
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
VM の準備
Vagrant で VirtualBox の VM を用意する。まずは vagrant init
する。
$ vagrant init ubuntu-server-12.04-amd64
VM を3つ作ってそれぞれに private_network
のアドレスを付けるために、作成された Vagrantfile
を次のように編集する。
$ diff -u Vagrantfile.orig Vagrantfile
--- Vagrantfile.orig 2014-06-01 00:24:02.000000000 +0900
+++ Vagrantfile 2014-06-01 00:32:48.000000000 +0900
@@ -10,7 +10,20 @@
# please see the online documentation at vagrantup.com.
# Every Vagrant virtual environment requires a box to build off of.
- config.vm.box = "ubuntu-server-12.04-amd64"
+ config.vm.define :node1 do |node|
+ node.vm.box = "ubuntu-server-12.04-amd64"
+ node.vm.network :private_network, ip: "10.200.19.10"
+ end
+
+ config.vm.define :node2 do |node|
+ node.vm.box = "ubuntu-server-12.04-amd64"
+ node.vm.network :private_network, ip: "10.200.19.11"
+ end
+
+ config.vm.define :node3 do |node|
+ node.vm.box = "ubuntu-server-12.04-amd64"
+ node.vm.network :private_network, ip: "10.200.19.12"
+ end
VM を起動する。
$ vagrant up
3つ上がったか確認する。
$ vagrant status
Current machine states:
node1 running (virtualbox)
node2 running (virtualbox)
node3 running (virtualbox)
上がったっぽい。
Ansible の準備
VM が用意できたので、いよいよ Ansible を使う。基本的に Best Practices に沿って書いていく。
- Best Practices – Ansible Documentation
まずは inventory ファイルを作成する。場所は stage
ディレクトリを掘ってその下に置く。
$ cat stage/inventory
[master]
10.200.19.10
[slave]
10.200.19.11
10.200.19.12
この時点で VM につなげるか、ping
モジュールで確認しておく。
$ ansible -i stage/inventory all -m ping
10.200.19.10 | success >> {
"changed": false,
"ping": "pong"
}
10.200.19.11 | success >> {
"changed": false,
"ping": "pong"
}
10.200.19.12 | success >> {
"changed": false,
"ping": "pong"
}
pong
が返ってきているので、無事つながっている。
次に変数 (Variables
) を格納するファイルを作成する。ステージングと本番で共通の設定を group_vars/all
に書く。
$ cat group_vars/all
opsuser: dnsops
document_root: /var/www
ステージングと本番で別々の設定は、それぞれ stage/group_vars/all
, production/group_vars/all
に書く。stage の方の内容はこんな感じ。
$ cat stage/group_vars/all
master: 10.200.19.10
allow_axfr_ips: 10.200.19.0/24
nginx_allow_ips:
- 10.200.19.0/24
そして roles/{common,master,slave}
ディレクトリを掘り、さらにその下に以下のようなディレクトリを作成する。
tasks
… 各 role の Playbooks を置くディレクトリfiles
… VM にコピーするためのファイルを置くディレクトリtemplates
… files と似たようなものだが、配置時に変数等を置換する
あとは適宜 Playbooks を書いていく。最終的に次のようなディレクトリ構成になった。
$ tree .
.
├── Vagrantfile
├── group_vars
│ └── all
├── production
│ └── group_vars
├── roles
│ ├── common
│ │ └── tasks
│ │ └── main.yml
│ ├── master
│ │ ├── files
│ │ │ ├── home
│ │ │ │ └── dnsops
│ │ │ │ └── zone_sync
│ │ │ │ └── master_zones.sh
│ │ │ └── var
│ │ │ └── www
│ │ │ └── powerdns-on-rails
│ │ │ └── config
│ │ │ └── database.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── etc
│ │ ├── nginx
│ │ │ └── conf.d
│ │ │ └── powerdns-on-rails.conf.j2
│ │ └── powerdns
│ │ └── pdns.conf.j2
│ └── slave
│ ├── files
│ │ └── etc
│ │ └── bind
│ │ └── named.conf.options
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ └── home
│ └── dnsops
│ └── zone_sync
│ └── zone_sync.sh.j2
├── site.yml
└── stage
├── group_vars
│ └── all
└── inventory
できあがったファイル群は GitHub に置いておいた。
Ansible の実行
できあがった Playbook を実行するには、次のコマンドを叩けば良い。
$ ansible-playbook -i stage/inventory site.yml
本番環境で実行するなら、それ用の inventory ファイル production/inventory
を作成して -i
で指定する。その際の各ホストは、次の要件を満たす必要がある。
- 鍵認証で ssh できること
- パス無しで sudo できること
Tips
今回 Playbooks を作成する上で気づいた点や工夫点をまとめておく。
screen 上での実行
GNU screen の上で Ansible を実行するとうまく動かないことがある。具体的には、初回の ssh 接続が必ず失敗する。ssh の ControlMaster 周りがうまくいっていない模様。
調べてみると、screen の status line に ssh の接続先ホスト名を表示するように設定 (LocalCommand
) していることの影響であることがわかった。これは ~/.ssh/config
を次のように修正することで解決した。
#LocalCommand tty -s && [ ! -z "$STY" ] && echo -ne "\ek"%n"\e\\" > `tty`
LocalCommand tty -s && [ ! -z "$STY" ] && [ "$PYTHONPATH" = "" ] && echo -ne "\ek"%n"\e\\" > `tty`
鍵ペアの作成、配置
slave
ロールのマシンから master
ロールのマシンにパス無しで ssh できるようにするために、鍵を置く必要がある。しかし秘密鍵はレポジトリに入れたくないので、都度作成することにした。
OS のユーザの作成時に鍵ペアを生成するには、user
モジュールで generate_ssh_key=yes
を指定すれば良い。
- name: add user
user: name={{ opsuser }} groups=sudo generate_ssh_key=yes
作成した slave の公開鍵を master に置くには、register
と delegate_to
を使う。まず shell
モジュールで slave の公開鍵を cat
して、変数 (今回は slave_publickey
) に入れる。
- name: get publickey
shell: cat /home/{{ opsuser }}/.ssh/id_rsa.pub
register: slave_publickey
そして delegate_to
で master のホストを指定して、authorized_key
モジュールで公開鍵を登録する。
- name: set publickey to master
authorized_key: user={{ opsuser }}
key="{{ slave_publickey.stdout }}"
delegate_to: "{{ master }}"
これで動的に鍵を配置できる。ただし delegate_to
にはグループが指定できないようなので、鍵を置かれる側 (今回は master) のホストが複数台の場合、この方法は使えない。
条件分岐
冪等性を担保するために、特定の条件下でのみ処理を実行したい場合がある。
例えば今回は ruby の unicorn を起動する処理がある。unicorn には restart 機能がないので、既に動いている、すなわち pid ファイルが存在する場合は一旦停止してから起動する必要がある。
このようなときは、register
と when
を用いれば良い。まず shell
モジュールでファイルがあるかどうか確認し、結果を変数に入れる。
- name: check whether unicorn is running
shell: "[ -f /tmp/powerdns-on-rails.pid ] && echo 'running' || echo ''"
register: unicorn_is_running
そして when
を用いて、変数に値が入っている場合のみ実行する処理を記述する。
- name: stop unicorn
shell: kill -INT `cat /tmp/powerdns-on-rails.pid`
when: (unicorn_is_running.stdout)
まとめ
以上で、Ansible を用いて、サーバのプロビジョニングとアプリケーションの簡易デプロイができた。インベントリファイルと vars ファイルをそれぞれ用意することで、ステージングと本番環境に同じ Playbooks でプロビジョニングできるようにした。
今後は serverspec 等を用いて、プロビジョニング後の状態のテストを行ないたい。
コメント