やること
- DBサーバとWEBサーバを別々のVMで起動させる
- windows環境でansibleを実行できるようにする(ansible_local)
- inventory配下のhostsファイルをyamlで書く
- ansibleのinventory/hostsファイルを読み込んでローカル環境の設定を適用させる
- playbookはもちろんBestPracticesを踏襲
環境
- Host OS: Windows10 Pro
- Guest OS: Centos6.7 x86_64
- Ansible 2.5.2
- Vagrant 1.9.8
- VirtualBox 5.1.22
動機
Vagrantで複数のVMを立ち上げてansibleを導入しようとした時のことです。windows環境化でansibleを実行するには色々な方法がありますが、vagrantのansible_localが使えそうな雰囲気だったので実装してみましたが、公式の方法がしっくりこなかったので独自に実装してみた話です。
Ansible Parallel Execution
公式で並列処理時の方法があったのでこれを参考に書きました。
N = 3
(1..N).each do |machine_id|
config.vm.define "machine#{machine_id}" do |machine|
machine.vm.hostname = "machine#{machine_id}"
machine.vm.network "private_network", ip: "192.168.77.#{20+machine_id}"
# Only execute once the Ansible provisioner,
# when all the machines are up and ready.
if machine_id == N
machine.vm.provision :ansible do |ansible|
# Disable default limit to connect to all the machines
ansible.limit = "all"
ansible.playbook = "playbook.yml"
end
end
end
end
s/:ansible/:ansible_local/g
に書き換えて起動!
最後に立ち上げたVMにしかplaybookが作動せず、、、原因が良く分からない。
そもそも、ホストOSで呼び出す方法を真似たので良くなかったのかも。
Ansible Parallel Execution from a Guest
ansible_local
はGuest側でansibleを起動するのでこっちの方法を参照するべきでした。
ansible専用のVM(controller)を作ろうと書いてあります。
Vagrant.configure("2") do |config|
config.vm.box = "centos6.7x86_64"
config.vm.define "web" do |machine|
machine.vm.network "private_network", ip: "172.17.177.21"
end
config.vm.define "db" do |machine|
machine.vm.network "private_network", ip: "172.17.177.22"
end
config.vm.define 'controller' do |machine|
machine.vm.network "private_network", ip: "172.17.177.11"
machine.vm.provision :ansible_local do |ansible|
ansible.playbook = "site.yml"
ansible.verbose = true
ansible.install = true
ansible.limit = "all" # or only "nodes" group, etc.
ansible.inventory_path = "inventories/local/hosts"
end
end
end
「うーん、でもansible用のVMまでわざわざ作る必要あるのか?」と思い、もう少しシンプルな作りを目指してみました。
成果物
|-- Vagrantfile
|-- ansible.cfg
`-- playbook
|-- inventories
| |-- production
| | `-- hosts.yml
| |-- staging
| | `-- hosts.yml
| |-- develop
| | `-- hosts.yml
| `-- local
| `-- hosts.yml
|-- roles
| |-- common
| |-- httpd
| `-- postgresql
|-- site.yml
|-- dbservers.yml
`-- webservers.yml
# -*- mode: ruby -*-
# vi: set ft=ruby :
require 'yaml'
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
VAGRANTFILE_API_VERSION = "2"
VAGRANTFILE_DIR = File.dirname(__FILE__)
ANSIBLE_INVENTORY_PATH = 'playbook/inventories/local/hosts.yml'
begin
conf = YAML.load_file(File.join(VAGRANTFILE_DIR, ANSIBLE_INVENTORY_PATH))
rescue Errno::ENOENT => ex
STDERR.puts <<-EOT
File does not exist. "#{ANSIBLE_INVENTORY_PATH}"
EOT
exit;
end
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "centos6.7x86_64"
conf['all']['hosts'].each do |host, value|
config.vm.define host do |machine|
machine.vm.hostname = value['hostname']
machine.vm.network "private_network", ip: value['ip']
# virtualbox
machine.vm.provider "virtualbox" do |vb|
vb.name = value['hostname']
end
# synced_folder
if value.has_key? 'synced_folder' then
value['synced_folder'].each do |folder|
machine.vm.synced_folder File.join(VAGRANTFILE_DIR, folder['host']), folder['guest']
end
end
# ansible
machine.vm.provision :ansible_local do |ansible|
ansible.playbook = "playbook/#{host}.yml"
ansible.inventory_path = ANSIBLE_INVENTORY_PATH
ansible.limit = 'all'
end
end
end
end
---
# file: playbook/inventories/local/hosts.yml
all:
hosts:
webservers:
hostname: carolsendai.co.jp
ip: 192.168.39.90
synced_folder:
- { guest: /var/LOG, host: ./LOG }
- { guest: /usr/local/lib, host: ./lib }
dbservers:
hostname: db.carolsendai.co.jp
ip: 192.168.39.91
---
# file: playbook/webservers.yml
- hosts: webservers
connection: local
become: yes
roles:
- common
- httpd
---
# file: playbook/dbservers.yml
- hosts: dbservers
connection: local
become: yes
roles:
- common
- postgresql
解説
hosts.yml
require 'yaml'
ANSIBLE_INVENTORY_PATH = 'playbook/inventories/local/hosts.yml'
conf = YAML.load_file(File.join(VAGRANTFILE_DIR, ANSIBLE_INVENTORY_PATH))
inventoryのhostsがyaml形式で書ける(ansible 2.1以上)ようになったことでロードが容易になりました。設定も色々記述できて自由度が上がったと思います。
multi-machine
VAGRANTFILE_DIR = File.dirname(__FILE__)
ANSIBLE_INVENTORY_PATH = 'playbook/inventories/local/hosts.yml'
conf['all']['hosts'].each do |host, value|
config.vm.define host do |machine|
machine.vm.hostname = value['hostname']
machine.vm.network "private_network", ip: value['ip']
# virtualbox
machine.vm.provider "virtualbox" do |vb|
vb.name = value['hostname']
end
# synced_folder
if value.has_key? 'synced_folder' then
value['synced_folder'].each do |folder|
machine.vm.synced_folder File.join(VAGRANTFILE_DIR, folder['host']), folder['guest']
end
end
# ansible
machine.vm.provision :ansible_local do |ansible|
ansible.playbook = "playbook/#{host}.yml"
ansible.inventory_path = ANSIBLE_INVENTORY_PATH
ansible.limit = 'all'
end
end
公式のようなN = 3
など記述せず、hosts.yml
の配列分のVMを増やすこともできます。(そこまでする必要があるのかは置いといて)
site.yml
が読み込まれていないのがちょっと残念な作り。