AnsibleとVagrantで開発環境を構築する

AnsibleChefPuppetと同様に冪等性(べきとうせい)に配慮した構成管理ツールです。YAMLで記述したプレイブックのファイルが1つあれば動き、SSHさえ繋がれば対象サーバーにクライアントは不要、といったシンプルさが支持され、近年ユーザーを増やしています。

そのシンプルさは仮想マシンを利用した開発環境の構築にもうってつけに思えます。と言うことで今回はAnsibleをVagrantのプロビジョナーに使って開発環境を構築しました。

Ansibleの公式サイト
Ansibleの公式サイト

Ansibleのインストール

Ansibleはコントロールマシンに入っていればよく、セットアップ対象のサーバにはAnsibleのクライアントなどは不要です。SSHで接続さえできればOKです。今回のケースでは開発マシンのMacをコントロールマシンとし、Vagrantによる仮想マシンをセットアップ対象とします。

Ansible自体のインストールは多くの環境向けにパッケージが提供されているので特に難しいことはありません。Mac OSXではMacportsやHomebrewでインストールが可能となっています。

$ sudo port install ansible
または
$ brew install ansible

現時点でAnsibleの最新バージョンは1.9.1です。以下の手順はこのバージョンで進めていきます。他の環境でのインストールは公式ドキュメントを参照してください。
Pythonのパッケージマネージャであるpipで入れることもできます。

なおWindowsをコントロールマシンにしたい場合はCygwin上で動かすことができるようですが、きちんと動かすのは多少面倒なので別途LinuxのコントロールマシンをVagrantで立ててAnsibleを動かす方法を取るのがいいかもしれません。

Vagrantの準備

Vagrantのバージョンは最新の1.7.2を使います。VirtualBoxのバージョンは5.0です。準備ができていなければそれぞれのサイトよりダウンロードしてインストールしてください。

まず作業用のディレクトリを切ります。

$ mkdir ansible_vagrant
$ cd ansible_vagrant
$ mkdir provisioning

Vagrantfileの作成

次の内容でVagrantfileを作成します。

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "chef/debian-7.4"

  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "provisioning/site.yml"
    ansible.inventory_path = "provisioning/hosts"
    ansible.limit = 'all'
  end
end

ボックスにはchef/debian–7.4を使います。
Chefでのプロビジョニングに便利なChef入りのボックスですが、今回はAnsibleを試すのでChefは使いません。

provisioning/hostsの作成

Ansibleの対象サーバとして仮想マシンのIPアドレスを記述します。

[vagrants]
192.168.33.10

vagrantsはグループ名です。任意の名前を付けることができます。本格的なインフラ構築に使うなら「web_servers」や「db_servers」といったものになるでしょう。

provisioning/site.ymlの作成

プレイブックをYAMLで作成します。

---
- hosts: vagrants
  sudo: true
  user: vagrant
  tasks:
    - name: install packages zsh
      apt: name=zsh update_cache=yes

各項目の説明です。

  • hosts:provisioning/hostsで記述したグループ名
  • user:プレイブックを実行するユーザ
  • tasks:セットアップ内容を記述

このプレイブックで指定するタスクは「zshをインストールする」です。

tasksの項目は以下のようになります。

  • name:単なるラベル。タスクについて分かりやすい説明を自由に書きます。
  • apt:モジュールの指定

aptという名前で分かる通り、DebianやUbuntsのパッケージマネージャであるAPTでパッケージを扱うものになります。RHELやCentOS向けにはyumモジュールがありますので同様の指定ができます。

Ansibleには多数のモジュールが用意されており、これらのモジュールを使うことでシンプルなYAMLで複雑なセットアップを実行することができます。欲しい機能を実現するモジュールが見つからなければ自作も可能です。

vagrant up

ファイルの構造は現時点で次のようになっています。

├── Vagrantfile
└── provisioning
    ├── hosts
    └── site.yml

では仮想マシンを立ち上げます。

$ vagrant up

chef/debian–7.4を使うのが初めてならボックスのダウンロードに少し時間がかかります。

うまくいけば仮想マシンが立ち上がった後にAnsibleによるプロビジョニングが走りzshが入ります。

==> default: Running provisioner: ansible...

PLAY [servers] **************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [192.168.33.10]

TASK: [install packages zsh] ************************************************** 
changed: [192.168.33.10]

PLAY RECAP ******************************************************************** 
192.168.33.10              : ok=2    changed=1    unreachable=0    failed=0

vagrant ssh して確認するとzshが使えるようになっているのが確認できるはずです。

$ zsh --version
zsh --version
zsh 4.3.17 (x86_64-unknown-linux-gnu)

ここで最後の行のchanged=1に着目してみましょう。この時点でもう一度同じプロビジョニングを適用してみます。

$ vagrant provision

==> default: Running provisioner: ansible...

PLAY [servers] **************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [192.168.33.10]

TASK: [install packages zsh] ************************************************** 
ok: [192.168.33.10]

PLAY RECAP ******************************************************************** 
192.168.33.10              : ok=2    changed=0    unreachable=0    failed=0

changed=0になっていますね。プロビジョニングを何度実行しても同じ状態になる、冪等性の確保がうまくいっていることが確認できます。

rbenvとRubyのインストール

zshを入れただけではさすがに開発環境とは言えないので、Rubyを入れてみます。

provisioning/site.ymlの編集

以下のプレイブックはRubyのビルドに必要なパッケージを入れた後、rbenvを使ってRubyを入れて、Bundlerのインストールまでを行います。ちょっと長いですが、全文記載します。

---
- hosts: vagrants
  sudo: true
  user: vagrant
  vars:
    vagrant_home: "/home/vagrant" 
  tasks:
    - name: install packages build-essential
      apt: name=build-essential update_cache=yes

    - name: install packages git  
      apt: name=git update_cache=yes

    - name: install packages git-core
      apt: name=git-core update_cache=yes

    - name: install packages libssl-dev
      apt: name=libssl-dev update_cache=yes

    - name: install packages libqt4-dev
      apt: name=libqt4-dev update_cache=yes

    - name: install packages libc6-dev
      apt: name=libc6-dev update_cache=yes

    - name: install packages automake
      apt: name=automake update_cache=yes

    - name: install packages libtool
      apt: name=libtool update_cache=yes

    - name: install packages libyaml-dev
      apt: name=libyaml-dev update_cache=yes

    - name: install packages zlib1g
      apt: name=zlib1g update_cache=yes

    - name: install packages zlib1g-dev
      apt: name=zlib1g-dev update_cache=yes

    - name: install packages openssl
      apt: name=openssl update_cache=yes

    - name: install packages libssl-dev
      apt: name=libssl-dev update_cache=yes

    - name: install packages libreadline-dev
      apt: name=libreadline-dev update_cache=yes

    - name: install packages libxml2-dev
      apt: name=libxml2-dev update_cache=yes

    - name: install packages libxslt1-dev
      apt: name=libxslt1-dev update_cache=yes

    - name: install packages libncurses5-dev
      apt: name=libncurses5-dev update_cache=yes

    - name: install packages pkg-config
      apt: name=pkg-config update_cache=yes

    - name: install packages chrpath
      apt: name=chrpath update_cache=yes

    - name: install packages libfontconfig1-dev
      apt: name=libfontconfig1-dev update_cache=yes

    - name: install packages libxft-dev
      apt: name=libxft-dev update_cache=yes

    - name: install rbenv
      git: repo=https://github.com/sstephenson/rbenv.git
           dest={{ vagrant_home }}/.rbenv
           version=master

    - name: add rbenv path to bash_profile
      copy: src=files/rbenv.sh dest=/etc/profile.d/rbenv.sh
      # copy: content="export PATH=\"/home/vagrant/.rbenv/bin:$PATH\"\neval \"$(rbenv init -)\"" dest=/etc/profile.d/rbenv.sh

    - name: install ruby_build
      git: repo=https://github.com/sstephenson/ruby-build.git
           dest={{ vagrant_home }}/.rbenv/plugins/ruby-build
           version=master

    - name: install ruby 2.1.5
      shell: "export RBENV_ROOT={{ vagrant_home }}/.rbenv; export PATH=$RBENV_ROOT/bin:$PATH; echo N | rbenv install 2.1.5; rbenv global 2.1.5"
      args:
        creates: "{{ vagrant_home }}/.rbenv/versions/2.1.5/"

    - name: install bundler
      shell: "{{ vagrant_home }}/.rbenv/shims/gem install bundler"
      args:
        creates: "{{ vagrant_home }}/.rbenv/shims/bundle"

    - name: change ~/.rbenv owner to vagrant
      file: path={{ vagrant_home }}/.rbenv state=directory owner=vagrant group=vagrant recurse=yes

一気にタスクを並べましたが、実際はひとつひとつ試しながら書き上げていきます。タスクに適したモジュールが見つからなければshellcommandによるコマンド実行もできます。ただしその場合は冪等性の面倒を自分でみなければなりませんので注意が必要です。

このプレイブックではvarsで変数を定義しています。定義した変数はプレイブックの中で使えますので、繰り返し出てくるパスなどはこのように変数にまとめると便利です。

vars:
  vagrant_home: "/home/vagrant"

なおパッケージのインストールは以下のようにwith_itemsを使ってDRYに見通しよく書くこともできます。

- name: install packages
  apt: name={{item}} update_cache=yes
  with_items:
    - build-essential
    - git
      :

が、まとめて書くとパッケージ名のtypoなど、ミスした場合にどこで間違えたのか分からなくなるので、このプレイブックではあえて冗長にしています。動くことが確認できたら短くまとめてもいいでしょう。

provisioning/files/rbenv.shの作成

開発環境に設置するファイルを作っておきます。

export PATH="/home/vagrant/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

このファイルは以下のようにcopyモジュールで好きな場所に設置できます。

- name: add rbenv path to bash_profile
  copy: src=files/rbenv.sh dest=/etc/profile.d/rbenv.sh

ちょっとした内容のファイルであればコントロールマシン上ではなく、プレイブックの中で内容を組み立てることも可能です。

- name: add rbenv path to bash_profile
  copy: content="export PATH=\"/home/vagrant/.rbenv/bin:$PATH\"\neval \"$(rbenv init -)\"" dest=/etc/profile.d/rbenv.sh

vagrant provision

この時点でのファイルの構造は以下の通りです。

├── Vagrantfile
└── provisioning
    ├── files
    │   └── rbenv.sh
    ├── hosts
    └── site.yml

では、再びvagrant provisionを実行しましょう。

$ vagrant provision

これで少し待てばRubyが使える開発マシンのできあがりです。

きちんと冪等性に配慮したプレイブックとなっているので、繰り返しvagrant provitionしてもchanged=0になるはずです。

終わりに

以上、簡単ながらAnsibleのさわりを体験してみました。このシンプルさは素晴らしいですね。

Chefでは単純なことをするにもクックブックを作成してレシピを書いて、とやや冗長な手順が必要で、使い慣れている人でも億劫だったりします(一方で複雑な環境を作る際にサードパーティのクックブックや過去に作ったクックブックが使えるという利点もありますが)。

Ansibleの場合は基本hostsとsite.xmlと合わせて二つのファイルだけで済むので気軽です。個人的には、なかなか感触が良かったので、今後も開発環境や検証環境の構築に使っていこうと思っています。

ただ本格的なインフラ構築をしようとすると、ここまで単純にとはいかず、もう少し考えるべきことが増えてきます。その際には公式ドキュメントのベストプラクティスを参考にするといいようですが、それでもAnsibleらしいシンプルさを維持できるかは不明です。

機会があれば実際に動くサービスのインフラ構築に使ってみたいところです。皆さんもぜひ試してください。

Ansible is Simple IT Automation