TL;DR
- Ansible チュートリアル | Ansible Tutorial in Japanese
- Mac の開発環境構築を自動化する (2015 年初旬編) - t-wadaのブログ
- Macの環境構築をAnsibleでやることにした - Please Drive Faster
これ読めばとりあえず出来る。
この記事では、Homebrewのインストールなども含めて出来る限りのことをAnsibleに任せます。
Vagrantで建てたvmに対してAnsibleのplaybook適用して、Serverspecでテストします。
僕が全部初学者なので雑というのは説明が雑ということです。
AnsibleでMacのプロビジョニングする際の問題点
Ansible、恐らく本来はImmutable InfrastructureとかInfrastructure as Codeと呼ばれるものを実現するためのツールなので、日常的に使用して、状態変化するマシンの環境管理には適していない。
なので、会社と自宅のMacの状態同期に使用するのには適していない。
実際にあった問題なんだけど、事情があってhomebrew/versions/android-ndk-r9d
を使っていたけど、事情が解消されたので普通にbrew install android-ndk
で入れるようにしたらunlink
とlink
は手作業でしないといけないみたいな事があった。
AnsibleでMacの環境構築・管理をするのは、新規購入したマシンの初期の環境構築のみに使用するか、頑張って頑張るみたいな感じになる。
あとは、テストをTravisCIでやってる人多いっぽいんだけど、遅くて結構辛かったのでTravis使わない事にしてる。
それを踏まえて雑に自動化する。
準備
Vagrantを入れる
VagrantからYosemite起動できるようにする
OSX - OS X YosemiteのVagrant Boxを作る - Qiita
これそのままやる。
Vagrantfile書く
Vagrant.configure(2) do |config|
config.ssh.insert_key = false
config.vm.define :yosemite do |node|
node.vm.box = 'osx-yosemite'
node.vm.network :forwarded_port, guest: 22, host: 2001, id: "ssh"
node.vm.network :private_network, ip: "192.168.33.11"
node.vm.synced_folder ".", "/vagrant", type: "nfs", nfs: true
end
end
-
config.ssh.insert_key = false
は、true
だとvmごとにsshキーが生成されて怠いので、怠くないようにする設定 -
config.vm.define :yosemite
は、yosemiteという名前を付けてvm建てられるようにしてる -
node.vm.box = 'osx-yosemite'
は、さっき追加したboxを*:yosemite*で使うということ -
node.vm.network
は察してください -
node.vm.synced_folder
は、vmを動かしてるマシンのフォルダにvmからアクセス出来るようにするやつ。後述するのやらないなら不要
ここまでやったらvagrant up yosemite
でvm起動できるはず(画面とかは見られない)。
Ansible入れる
Ansible is Simple IT Automation
任意の方法で入れる。brew install ansible
が楽。
Serverspec
Serverspec入れる
gem install serverspec
か、bundler使うなど任意の方法で入れてください。
serverspec-init
serverspec入れたらserverspec-init
というコマンドが使えるようになっているはずなので呼ぶ。
選択肢は、UN*X
とSSH
を選ぶ。
テストをホスト間で共有できるようにする
serverspec-init
が生成してくれるテストは、ホストごとに分かれているので、同じテストを使いまわすのが少し怠い。
なので、テストをホスト間で共有できるようにする。
ファイルを移動する
spec/[host]/sample_spec.rbをspec/base/sample_spec.rbに移動する。baseは後で使う名前なので変えない。
Rakefileを変更する
コピペで動くはず
require 'rake'
require 'rspec/core/rake_task'
task :spec => 'spec:all'
task :default => :spec
properties = {
'yosemite' => {
roles: ['base']
},
'localhost' => {
roles: ['base']
},
}
namespace :spec do
task :all => properties.keys.map {|key| 'spec:' + key.split('.')[0] }
properties.keys.each do |key|
desc "Run serverspec to #{key}"
RSpec::Core::RakeTask.new(key.split('.')[0].to_sym) do |t|
ENV['TARGET_HOST'] = key
t.pattern = 'spec/{' + properties[key][:roles].join(',') + '}/*_spec.rb'
end
end
end
Shellでrake -T
するとrake spec:yosemite
とかが出てくるようになってるはず。
rake spec:yosemite
呼んでみるとテスト落ちるはず。require 'spec_helper'
以外全て消そう。
Ansibleいろいろ書く
ファイル作る
- playbook-vagrant.yaml
- vars.yaml
- roles/base/tasks/main.yaml
-
spec/base/all_spec.rb
- sample_specは消す。雑にテストのファイル1つでやるのでもっと良い名前あったら教えて欲しい。
https://gist.github.com/gin0606/25f1b59e8a6602a758b9
全部書いてたらめっちゃ長くなりそうなんでmain.yamlだけgistに上げた。brewとbrew-caskとgemだけ使うならvars.yaml編集するだけで終わる。
playbook-vagrant.yamlに書くことはこれだけ。
- hosts: all
roles:
- role: base
vars_files:
- vars.yaml
Command Line Tools入れる
Homebrew入れるのにCommand Line Tools必須なので。
- name: Command Line Toolsが入っているかの確認
stat: path=/usr/include
register: command_line_tools_dir
- name: Command Line Toolsが入っていなかったらインストールする
shell: |
PLACEHOLDER=/tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress
touch $PLACEHOLDER
PROD=$(softwareupdate -l | grep "\*.*Command Line" | head -n 1 | awk -F"*" '{print $2}' | sed -e 's/^ *//' | tr -d '\n')
softwareupdate -i "${PROD}"
[[ -f $PLACEHOLDER ]] && rm $PLACEHOLDER
when: not command_line_tools_dir.stat.exists
最初にstat
で状態確認してから、次のtaskのwhen
で実行するか否か決めると、2回目の実行時にchangeが少なくなって良い。
シェルからCLT入れるのはdotfiles/install.sh at master · r7kamura/dotfilesから頂きました。
Homebrew入れる
- name: homebrewが入っているかの確認
stat: path=/usr/local/bin/brew
register: brew_command
- name: homebrewが入っていなかったらインストールする
shell: echo '\n' | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
when: not brew_command.stat.exists
テスト書く
describe file('/usr/local/bin/brew') do
it { should be_executable }
end
Homebrewでいろいろ入れる
brew update
- homebrew: update_homebrew=yes
brew-cask使えるようにする
- homebrew_tap: tap=caskroom/cask state=present
- homebrew: name=brew-cask
その他tapしたいの指定する
homebrew:
tap:
- homebrew/versions
- homebrew_tap: tap={{ item }} state=present
with_items: homebrew.tap
with_items
にyamlのarrayを指定すると、{{ item }}
に各要素が展開されてループみたいな感じになる。
更に雑にやりたい場合は、vars.yaml書かないでこんなかんじでもいい。
- homebrew_tap: tap={{ item }} state=present
with_items:
- homebrew/versions
caskでいろいろ入れる
homebrew:
cask:
- virtualbox
- vagrant
- homebrew_cask: name={{ item }}
with_items: homebrew.cask
brewでいろいろ入れる
homebrew:
formula:
- git
- homebrew: name={{ item }}
with_items: homebrew.formula
テスト書く
describe package('git') do
it { should be_installed.by('homebrew') }
end
brewの場合はupdateコマンドで入るversion変わるので書かないほうがいい気がするけど、be_installed.by('homebrew').with_version('2.3.5')
とか書くとversion違うの入ってた場合テストが落ちる
VMに対して適用してみる
VagrantのProvisionerにAnsibleを指定する
最初に作ったVagrantfileを編集する。
diff --git a/Vagrantfile b/Vagrantfile
index 90ff975..d31b4c7 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -13,5 +13,9 @@ Vagrant.configure(2) do |config|
node.vm.network :forwarded_port, guest: 22, host: 2001, id: "ssh"
node.vm.network :private_network, ip: "192.168.33.11"
node.vm.synced_folder ".", "/vagrant", type: "nfs", nfs: true
+
+ node.vm.provision "ansible" do |ansible|
+ ansible.playbook = "playbook-vagrant.yaml"
+ end
end
end
これ書くとvagrant up
したらさっき書いたyamlの内容を適用できる。変更あったらvagrant provision
でプロビジョニング出来る。
適用してみる
vagrant up
すると自動的に適用される。ログは長いのでいろいろ削ってます。
$ vagrant destroy yosemite
$ vagrant up yosemite
Bringing machine 'yosemite' up with 'virtualbox' provider...
==> yosemite: Importing base box 'osx-yosemite'...
# 略
PLAY [all] ********************************************************************
GATHERING FACTS ***************************************************************
ok: [yosemite]
TASK: [base | Command Line Toolsが入っているかの確認] ***************
ok: [yosemite]
TASK: [base | Command Line Toolsが入っていなかったらインストールする] ***
skipping: [yosemite]
TASK: [base | homebrewが入っているかの確認] *************************
ok: [yosemite]
TASK: [base | homebrewが入っていなかったらインストールする] ***
changed: [yosemite]
TASK: [base | homebrew update_homebrew=yes] ***********************************
ok: [yosemite]
TASK: [base | homebrew_tap tap=caskroom/cask state=present] *******************
changed: [yosemite]
TASK: [base | homebrew name=brew-cask] ****************************************
changed: [yosemite]
TASK: [base | homebrew_tap tap={{ item }} state=present] **********************
changed: [yosemite] => (item=homebrew/versions)
TASK: [base | homebrew_cask name={{ item }}] **********************************
changed: [yosemite] => (item=virtualbox)
changed: [yosemite] => (item=vagrant)
TASK: [base | homebrew name={{ item }}] ***************************************
changed: [yosemite] => (item=git)
PLAY RECAP ********************************************************************
yosemite : ok=20 changed=15 unreachable=0 failed=0
もう一度vagrant provision
でansibleで書いた内容適用してみると、changedが減ってるはず。
テストしてみる
rake spec:yosemite
でプロビジョニング後のvmの状態が所望の状態になっているかをテストできる。
テスト落ちたらyaml見なおすか、テスト見直す。
そんな感じであとは頑張れ。
その他
localhostに適用する
playbook-localhost.yamlを作る
- hosts: localhost
connection: local
roles:
- role: base
vars_files:
- vars.yaml
echo 'localhost' >> hosts
ansible-playbook -i hosts playbook-localhost.yaml
というような感じで出来る。
localhostの環境をserverspecでテストする
serverspec-init
で作った状態だとlocalhostのテストが出来ないので、sshとlocalで別の初期化走るようにする。
正しい方法は不明なので知ってる人いたら教えて欲しい。
# 元の内容を spec_helper_ssh.rb に移動する
if ENV['TARGET_HOST'] == 'localhost'
require 'spec_helper_exec'
else
require 'spec_helper_ssh'
end
require 'serverspec'
set :backend, :exec
# spec_helperに書いてあった内容
require 'serverspec'
require 'net/ssh'
require 'tempfile'
set :backend, :ssh
if ENV['ASK_SUDO_PASSWORD']
begin
require 'highline/import'
rescue LoadError
fail "highline is not available. Try installing it."
end
set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
set :sudo_password, ENV['SUDO_PASSWORD']
end
host = ENV['TARGET_HOST']
`vagrant up #{host}`
config = Tempfile.new('', Dir.tmpdir)
config.write(`vagrant ssh-config #{host}`)
config.close
options = Net::SSH::Config.for(host, [config.path])
options[:user] ||= Etc.getlogin
set :host, options[:host_name] || host
set :ssh_options, options
# Disable sudo
set :disable_sudo, true
# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C'
# Set PATH
set :path, '$HOME/.rbenv/shims:$HOME/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin'