vagrant で ansible を試す

Vagrant 1.2 から ansible がサポートされたということで、気になって試してみた。

まだ自作モジュールとかは作ってなくて、単に vagrant で使えるところまで。

vagrant-ansible

vagrant plugin install ansible で vagrant の provision 機能として ansible が使えるようになる。

これを使うと vagrant に完結するのだが、これがメリットにもデメリットにもなる。 なにか playbook を作って vagrant 上で試そうと思ったら、 ansible-playbook コマンドが使えず、 Vagrantfile で指定した playbook に include してやらないといけない。 しかも ansible に渡せるオプションは vagrant-ansible がサポートしているものだけだ。

なので、開発メンバーに Vagrant ファイルを共有して同じ vm をセットアップするとかの目的にはいいが、 ansible を試すだけなら ansible 単体で起動できたほうがいい。

ansible-vagrant

ansible に標準で搭載されている vagrant モジュール。vagrant-ansible の逆で、 vagrant を ansible から起動する。

これはモジュールなので、 「localhost で vagrant up されている」という状態にしたあと、その vm を inventory (対象ホストの一覧) に追加する。

ただ、やっぱりこれを使うにも、試したい playbook に hosts: localhost で task: vagrant を追加しておかないといけなくて、再利用できる playbook を単体で試したいのにはちょっと面倒。

自力

~/.ansible.cfg に次のような設定をした。

[defaults]
hostfile = ~/.ansible/hosts.d

これで ~/.ansible/hosts.d ディレクトリの中に、 hosts みたいなテキストファイルや、 inventory を生成するスクリプトを置くことが出来る。このスクリプトは --list でホストの一覧を、 --host ホスト名 でそのホストに対する設定を出力する。 ssh のオプションは ansible_ssh_host などで設定できる。

~/.ansible/hosts.d/vagrant:

#!/usr/bin/env python

import sys
import subprocess
import json

def hosts():
    print json.dumps({"vagrant": ["vagrant"]})

def variable():
    output = subprocess.check_output("vagrant ssh-config", shell=True)

    for line in output.splitlines():
        line = line.strip()
        try:
            k, v = line.split(' ', 1)
        except ValueError:
            continue
        k = k.lower()
        if k == 'hostname':
            hostname = v
        elif k == 'port':
            port = v
        elif k == 'user':
            user = v
        elif k == 'identityfile':
            v = v.strip('"')
            identity = v.decode('string_escape')

    print json.dumps({
        'ansible_ssh_host': hostname,
        'ansible_ssh_port': port,
        'ansible_ssh_user': user,
        'ansible_ssh_private_key_file': identity,
        })

if sys.argv[1] == '--host':
    # show host variables.
    variable()
else:
    # show list of hosts.
    hosts()

# ft=python

これで vagrant 経由で ansible を起動したり ansible 経由で vagrant を起動しなくても playbook を試せるようになった。

感想

まだ本格的な構成管理ツールは必要なくて、ふだん fabric + cuisine でやっている初期設定 (パッケージのアップグレードやいくつかのパッケージのインストール) をやってみただけなんだけど、これだけなら fabric の方が楽だ。

@task
def install_devtools():
    packages = '''python-dev libz-dev libbz2-dev libpng-dev vim-nox exuberant-ctags
                  git-core bzr bzrtools build-essential python-distribute python-virtualenv
                  mercurial linux-tools pkg-config libzmq-dev
               '''.split()
    for pkg in packages:
        package_ensure(pkg)

こんな感じの fabfile.py を

---
- hosts: all
  sudo: yes
  tasks:
    - name: update apt
      action: apt pkg=$item state=latest update_cache=yes
      with_items:
        - vim-nox
        - exuberant-ctags
        - build-essential
        - pkg-config
        - libzmq-dev
        - libz-dev
...

という playbook にして、 ansible -l vagrant devel.yml として実行してみたところ、 libz-dev で失敗してしまう。

fabric + cuisine は apt-get install libz-dev を実行するだけなので問題ない (これで充分 idempotent になってる) のに対して、 ansible は現在インストールされているパッケージのバージョンとかまで管理しようとしてしまうので、 virtual package な libz-dev は使えず、本体となる zlib1g-dev を指定しないといけないようだ。

ただし action に apt じゃなくて command を使えば任意の apt-get install を実行できるので、ある程度 fabric 的にも使える。

    action: command /usr/bin/apt-get install -y  $item

とりあえずこんなかんじでお茶を濁して、ちゃんとモジュールを使ったり作ったりは本格的なことをする段階になってから試してみる。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。