Ansible で Vagrant にプロビジョニングする

スポンサーリンク

以前設計した 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 に沿って書いていく。

まずは 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 に置くには、registerdelegate_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 ファイルが存在する場合は一旦停止してから起動する必要がある。

このようなときは、registerwhen を用いれば良い。まず 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 等を用いて、プロビジョニング後の状態のテストを行ないたい。

コメント

タイトルとURLをコピーしました