LoginSignup
56
57

More than 5 years have passed since last update.

Ansibleを使って雑にMacの環境構築を自動化する

Posted at

TL;DR

これ読めばとりあえず出来る。

この記事では、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で入れるようにしたらunlinklinkは手作業でしないといけないみたいな事があった。

AnsibleでMacの環境構築・管理をするのは、新規購入したマシンの初期の環境構築のみに使用するか、頑張って頑張るみたいな感じになる。

あとは、テストをTravisCIでやってる人多いっぽいんだけど、遅くて結構辛かったのでTravis使わない事にしてる。

それを踏まえて雑に自動化する。

準備

Vagrantを入れる

Download Vagrant - Vagrant

VagrantからYosemite起動できるようにする

OSX - OS X YosemiteのVagrant Boxを作る - Qiita

これそのままやる。

Vagrantfile書く

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入れる

Serverspec - Home

gem install serverspecか、bundler使うなど任意の方法で入れてください。

serverspec-init

serverspec入れたらserverspec-initというコマンドが使えるようになっているはずなので呼ぶ。

選択肢は、UN*XSSHを選ぶ。

テストをホスト間で共有できるようにする

serverspec-initが生成してくれるテストは、ホストごとに分かれているので、同じテストを使いまわすのが少し怠い。

なので、テストをホスト間で共有できるようにする。

ファイルを移動する

spec/[host]/sample_spec.rbspec/base/sample_spec.rbに移動する。baseは後で使う名前なので変えない。

Rakefileを変更する

コピペで動くはず

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に書くことはこれだけ。

playbook-vagrant.yaml
- hosts: all
  roles:
    - role: base
  vars_files:
    - vars.yaml

Command Line Tools入れる

Homebrew入れるのにCommand Line Tools必須なので。

roles/base/tasks/main.yaml
- 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入れる

roles/base/tasks/main.yaml
- 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

テスト書く

spec/base/all_spec.rb
describe file('/usr/local/bin/brew') do
  it { should be_executable }
end

Homebrewでいろいろ入れる

brew update

roles/base/tasks/main.yaml
- homebrew: update_homebrew=yes

brew-cask使えるようにする

roles/base/tasks/main.yaml
- homebrew_tap: tap=caskroom/cask state=present
- homebrew: name=brew-cask

その他tapしたいの指定する

vars.yaml
homebrew:
  tap:
    - homebrew/versions
roles/base/tasks/main.yaml
- homebrew_tap: tap={{ item }} state=present
  with_items: homebrew.tap

with_itemsにyamlのarrayを指定すると、{{ item }}に各要素が展開されてループみたいな感じになる。

更に雑にやりたい場合は、vars.yaml書かないでこんなかんじでもいい。

roles/base/tasks/main.yaml
- homebrew_tap: tap={{ item }} state=present
  with_items:
    - homebrew/versions

caskでいろいろ入れる

vars.yaml
homebrew:
  cask:
    - virtualbox
    - vagrant
roles/base/tasks/main.yaml
- homebrew_cask: name={{ item }}
  with_items: homebrew.cask

brewでいろいろ入れる

vars.yaml
homebrew:
  formula:
    - git
roles/base/tasks/main.yaml
- homebrew: name={{ item }}
  with_items: homebrew.formula

テスト書く

spec/base/all_spec.rb
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を作る

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/spec_helper.rb
# 元の内容を spec_helper_ssh.rb に移動する
if ENV['TARGET_HOST'] == 'localhost'
  require 'spec_helper_exec'
else
  require 'spec_helper_ssh'
end
spec/spec_helper_exec.rb
require 'serverspec'

set :backend, :exec
spec/spec_helper_ssh.rb
# 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'
56
57
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
57