Ansible で Jenkins をインストールする

今更ながら、前から気になっていた構成管理ツールの1つである [Ansible](https://github.com/ansible/ansible) を使ってみた（バージョン 1.7.2）。

どんなものなのか、 [ドキュメント](http://docs.ansible.com/index.html "Ansible Documentation") を見ながら [Jenkins](http://jenkins-ci.org/) をインストールする Playbook を書いてみた。

# 環境構築

## サーバの要件

Ansible は Chef と違って、対象サーバにパッケージをインストールする必要はないが、使用するには以下の要件を満たす必要がある（ [Installation](http://docs.ansible.com/intro_installation.html) より）。

**Control Machine**

Ansible をインストールするマシン。このマシンから対象サーバに対して操作を行う。

* Red Hat, Debian, CentOS, OS X など多くの Linux/UNIX マシン（ Windows はサポートされていない）。
* Python 2.6 以上がインストールされていること。

**Managed Node**

構成管理の対象サーバ。

* Python 2.4 以上がインストールされていること
（ただし Python 2.4 の場合は `python-simplejson` がインストールされていること）。
* Python 3 系には今のところ対応していない。



## 環境の作成

まずはこれらの要件を満たす環境を Vagrant で用意する。
Control Machine と Managed Node の2つの仮想マシンを作成する `Vagrantfile` を以下のように作成した。

ベースとなる box は [Bento](http://opscode.github.io/bento/ "Bento") が提供している CentOS 7 を使用した。
パスワードなしでログインできるように、あらかじめ作っておいた認証鍵をそれぞれのサーバに配置している。 Control Machine には yum で Ansible をインストールする。

また、 Control Machine には 10.0.16.21 、 Managed Node には 10.0.16.31 のプライベートIPをそれぞれ割り当てた。

```ruby:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

# for Virtual Box
BOX_NAME = "opscode-centos-7.0"
BOX_URI = "http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-7.0_chef-provisionerless.box"


$base_script = <<SCRIPT
set -x

vagrant_home=/home/vagrant

mkdir -p ${vagrant_home}/.ssh
cp /vagrant/ssh_key/id_rsa ${vagrant_home}/.ssh/
cp /vagrant/ssh_key/id_rsa.pub ${vagrant_home}/.ssh/

touch ${vagrant_home}/.ssh/authorized_keys
cat /home/vagrant/.ssh/id_rsa.pub >> ${vagrant_home}/.ssh/authorized_keys

chown -R vagrant:vagrant ${vagrant_home}/.ssh
chmod 600 ${vagrant_home}/.ssh/authorized_keys
chmod 600 ${vagrant_home}/.ssh/id_rsa
SCRIPT

$controller_script = <<SCRIPT
set -x

yum -y update
yum -y install epel-release
yum -y --enablerepo=epel install ansible
SCRIPT

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = BOX_NAME
  config.vm.box_url = BOX_URI

  config.vm.define "control-machine" do |controller|
    controller.vm.hostname = "controller"
    controller.vm.network :private_network, ip: "10.0.16.21"

    controller.vm.provision :shell, :inline => $base_script + $controller_script
  end

  config.vm.define "managed-node" do |node|
    node.vm.hostname = "node1"
    node.vm.network :private_network, ip: "10.0.16.31"

    node.vm.provision :shell, :inline => $base_script
  end
end
```

# Inventory の作成

対象サーバを記述するファイルを Inventory と言うようだ（ [Inventory](http://docs.ansible.com/intro_inventory.html) ）。ここはまだちゃんと理解していないので、以下のように単純な記述にとどめておき、 `hosts` という名前で保存した。

```text:host
[master]
10.0.16.31
```

`master` がサーバグループの名前で、同じグループに属するサーバに対して同じ操作ができるようになる。

# Playbook の作成

Ansible では、サーバの設定やインストールするアプリケーションを記述したファイルを Playbook と言い、 YAML で記述する。

Ansible を使わない場合の Jenkins インストールは、

```console
$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
$ sudo rpm --import http://pkg.jenkins-ci.org/redhat-stable/jenkins-ci.org.key
$ sudo yum install jenkins
```

のように行う（ [Installing Jenkins on Red Hat distributions](https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins+on+Red+Hat+distributions) より）。

これと同じことを実現する Playbook を以下のように作成した。

```yaml:jenkins.yml
---
- hosts: master
  sudo: yes
  tasks:
    - name: ensure open JDK 1.6 is at the latest version
      yum: name=java-1.6.0-openjdk state=latest

    - name: get the Jenkins repository
      get_url: url=http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo dest=/etc/yum.repos.d/jenkins.repo

    - name: add the Jenkins repository
      rpm_key: key=http://pkg.jenkins-ci.org/redhat-stable/jenkins-ci.org.key

    - name: ensure jenkins is at the latest version
      yum: name=jenkins state=latest

    - name: ensure jenkins is running and enabled
      service: name=jenkins state=running enabled=yes
```

タスクは書いた順番に実行されるそうだ（ [Intro to Playbooks - Tasks list](http://docs.ansible.com/playbooks_intro.html#tasks-list) ）。

まずはじめに、 open JDK をインストールするタスクを書いた。 yum によるインストールは、 [yumモジュール](http://docs.ansible.com/yum_module.html "yum") を使うと簡単に実現できるらしい。 `name` にパッケージ名を指定し、 `state` にどのような状態であるべきか `present`, `latest`, `absent` のいずれかを指定する。省略するとデフォルトの `present` となるようだ。

つぎの3行が Jenkins のインストールである。
Ansible を使わない場合に実行するコマンドがそれぞれのタスクに対応する。

まずは [get_urlモジュール](http://docs.ansible.com/get_url_module.html "get_url") でリポジトリを取得し、 [rpm_keyモジュール](http://docs.ansible.com/rpm_key_module.html "rpm_key") でリポジトリを登録する。その後、再びyumモジュールで Jenkins をインストールする。

最後に、サービスが起動していることを [serviceモジュール](http://docs.ansible.com/service_module.html "service") を使って確認している。

# Playbook の実行

Control Machine で以下のコマンドを実行すると、 Managed Node に Jenkins がインストールされ、使えるようになる。

```console
$ ansible-playbook -i hosts jenkins.yml
```

ブラウザから http://10.0.16.31:8080 へアクセスすると、無事 Jenkins の画面が表示された。

# 所感

この程度だと、シェルスクリプトを書くのとあまり変わりはないのでメリットがわかりにくかったが、複数サーバへ同じ操作をいっぺんに行えるのは便利そうと感じた。複数のサーバへアプリをデプロイするようなもう少し複雑な例も試してみたい。

Inventory や Playbook の書き方もまだきちんと理解できていないので、この辺も色々といじってみよう。