はじめに

サーバや開発環境 (以下,インフラ) を手順書に従って構築することは手間がかかる.少なくない時間を費やす必要がある上に,ヒューマンエラーをはじめとした各種トラブルが発生する恐れもある.インフラ構築は基礎を勉強する上では役に立つが,肝心のアプリの開発や研究に割くための時間が圧迫されることは歓迎できない.

そこで Infrastructure as Code を導入する.インフラをコードで管理して構築作業を自動化し,あわせてテストを行うことでインフラが正しく構築されることを確認する. Infrastructure as Code により想定する環境をコマンド一撃で構築できるようになるため,たとえばチームメンバの開発環境をまったく同一に揃えることや,環境の作成と破棄を繰り返すといったことが手軽に行えるようになる.

以下,仮想環境構築ツールとして Vagrant を,インフラ構築を自動化するツール(プロビジョニングツール)として Ansible を,インフラ設定をテストするツールとして Serverspec を用いて Infrastructure as Code を試す.

利用するツール

Vagrant

https://www.vagrantup.com/

Create and configure lightweight, reproducible, and portable development environments.

仮想環境構築ツール (VirtualBox, VMware, etc.) のフロントエンドであり,仮想環境を手軽に扱えるようにしてくれる.コマンド一撃で仮想環境を立ち上げたり破棄したりできるようになる.また,設定ファイルを使い回すことで同一の仮想環境を再現できる.

Ansible

https://www.ansible.com/

Deploy apps. Manage systems. Crush complexity. Ansible is a powerful automation tool that you can learn quickly.

プロビジョニングツールの一つであり, OS やミドルウェアといったインフラの設定をコードで管理できる.コードには対象とするマシンのあるべき状態を記述する.対象のマシンに設定が適用される際には冪等性が確保される.同様のツールに Puppet, Chef, Itamae 等がある.

Serverspec

https://serverspec.org/

With Serverspec, you can write RSpec tests for checking your servers are configured correctly.

インフラの設定をテストするためのツールである.インフラのあるべき状態をコードに記述して実行することで,対象のマシンがコード通りの状態となっているか否かを調べて報告してくれる.インフラ構築後にはテストを実行し,問題なく通過することを確認する. TDD のように先にテストを書き,それからテストを通過するようにインフラ構築用のコードを書く方法もある (テスト駆動インフラ).

ツールのインストール

Vagrant

Download Vagrant からダウンロードしてインストールする.また,バックエンドとして用いる VirtualBox を Downloads – Oracle VM VirtualBox からダウンロードしてインストールする.

Ansible

Homebrew でインストールする.

brew update
brew install ansible

Serverspec

Ruby gem でインストールする.

gem 'serverspec'
bundle

ツールの初期設定

Vagrant

Box ファイルの設定

Vagrant では OS イメージを Box と呼ばれる形式で管理している.最初は Box ファイルが何もインストールされていない.

vagrant box list
There are no installed boxes! Use `vagrant box add` to add some.

有志の方々が様々な OS の Box ファイルを公開しているため,使用したいものをローカルに保存して利用する.本記事では CentOS 6.5 を centos65 というキーでインストールする.

vagrant box add centos65 https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box

CentOS 6.5 (centos65) がインストールされた.

vagrant box list
centos65 (virtualbox, 0)

仮想環境の起動

適当な名前のプロジェクトディレクトリを作成して移動する.

mkdir -p ~/Documents/vagrant/test && cd $_

仮想環境を初期化する (仮想環境の設定を記述した Vagrantfile を作成する).

vagrant init centos65
Vagrant.configure(2) do |config|
  config.vm.box = "centos65" # Box ファイルは centos65 を使用する
  config.vm.network "private_network", ip: "192.168.33.10" # 仮想環境の IP アドレス.ホストとの通信用
  config.vm.provider "virtualbox" do |vb|
    vb.memory = "1024" # 仮想環境のメモリを 1GB とする
  end
end

コマンド一撃で仮想環境が立ち上がる.

vagrant up

立ち上げた仮想環境に SSH で接続する.

vagrant ssh

[vagrant@vagrant-centos65 ~]$ cat /etc/system-release
CentOS release 6.5 (Final)

仮想環境を破棄することもできる.

vagrant destroy

以上, Vagrant を用いることで仮想環境を手軽に作成・破棄できる.

Ansible

Vagrant で立ち上げた仮想環境に Ansible で接続する.

まずは SSH の接続情報を ~/.ssh/config に追記する.

vagrant ssh-config >> ~/.ssh/config
Host vagrant-test
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile ~/Documents/vagrant/test/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

次に Ansible の hosts ファイルを作成し,接続する仮想環境の情報を Ansible に伝える.

mkdir /usr/local/etc/ansible
echo "vagrant-test" > /usr/local/etc/ansible/hosts

Ansible と仮想環境の疎通確認を行う.

ansible vagrant-test -m ping
vagrant-test | success >> {
    "changed": false,
    "ping": "pong"
}

ping に対して無事に pong が返ってきた.

Serverspec

初期化コマンドを実行し,対話形式の質問に応答する.

bundle exec serverspec-init
Select OS type:

  1) UN*X
  2) Windows

Select number: 1

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1

Vagrant instance y/n: y
Auto-configure Vagrant from Vagrantfile? y/n: y
 + spec/
 + spec/default/
 + spec/default/sample_spec.rb
 + spec/spec_helper.rb
 + Rakefile
 + .rspec

テスト実行に必要なファイルが配置された.

Infrastructure as Code

簡単なテスト駆動インフラ構築により Infrastructure as Code を試す.最初に Serverspec のテストを書き,このテストを通過するように Ansible で対象マシン (Vagrant で立ち上げた CentOS 6.5) の設定を行う.以下, web サーバ (nginx) の立ち上げを例にして記述する.

テスト作成

Serverspec のテストを記述する. Nginx のインストール・有効化・起動と, 80 番ポートの状態をテストする.

vim ~/Documents/vagrant/test/spec/default/nginx_spec.rb
require 'spec_helper'

describe package('nginx') do
  it { should be_installed }
end

describe service('nginx') do
  it { should be_enabled }
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end

テスト実行 (失敗)

対象マシンはまだ何も設定していないので,当然テストは失敗する.

bundle exec rake spec
...
(省略)
...
Failed examples:

rspec ./spec/default/nginx_spec.rb:4 # Package "nginx" should be installed
rspec ./spec/default/nginx_spec.rb:8 # Service "nginx" should be enabled
rspec ./spec/default/nginx_spec.rb:9 # Service "nginx" should be running
rspec ./spec/default/nginx_spec.rb:13 # Port "80" should be listening

インフラ設定

テストを通過するように Ansible で設定を行う. Ansible では playbook と呼ばれるファイルにマシンのあるべき状態を記述する.そして ansible-playbook コマンドを実行することで,対象マシンの設定は playbook に記述された状態に収束する.

Nginx のインストール・有効化・(再)起動と, iptables の設定・有効化・(再)起動を行う.

mkdir ~/Documents/vagrant/test/ansible
vim ~/Documents/vagrant/test/ansible/nginx.yml
- hosts: vagrant-test
  user: vagrant
  sudo: yes
  tasks:
  - name: install nginx
    yum: name=nginx state=latest
    notify: restart nginx

  - name: start nginx
    service: name=nginx enabled=yes state=started

  - name: set iptables
    copy: src=iptables dest=/etc/sysconfig/iptables owner=root group=root mode=0744
    notify: restart iptables

  - name: start iptables
    service: name=iptables enabled=yes state=started

  handlers:
  - name: restart nginx
    service: name=nginx enabled=yes state=restarted

  - name: restart iptables
    service: name=iptables enabled=yes state=restarted

対象のマシンに転送する iptables を作成する.

vim ~/Documents/vagrant/test/ansible/iptables
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

Nginx 関連の設定を行う playbook (+ iptables) が作成できたので,この内容を対象マシンに適用する.

ansible-playbook ansible/nginx.yml

PLAY [vagrant-test] ***********************************************************

GATHERING FACTS ***************************************************************
ok: [vagrant-test]

TASK: [install nginx] *********************************************************
changed: [vagrant-test]

TASK: [start nginx] ***********************************************************
changed: [vagrant-test]

TASK: [set iptables] **********************************************************
changed: [vagrant-test]

TASK: [start iptables] ********************************************************
changed: [vagrant-test]

NOTIFIED: [restart nginx] *****************************************************
changed: [vagrant-test]

NOTIFIED: [restart iptables] **************************************************
changed: [vagrant-test]

PLAY RECAP ********************************************************************
vagrant-test               : ok=7    changed=6    unreachable=0    failed=0

すべての処理が成功した.対象マシンに web ブラウザでアクセスすると,確かに nginx が立ち上がっている.

ここで,先ほどの playbook をもう一度適用してみる.

ansible-playbook ansible/nginx.yml

PLAY [vagrant-test] ***********************************************************

GATHERING FACTS ***************************************************************
ok: [vagrant-test]

TASK: [install nginx] *********************************************************
ok: [vagrant-test]

TASK: [start nginx] ***********************************************************
ok: [vagrant-test]

TASK: [set iptables] **********************************************************
ok: [vagrant-test]

TASK: [start iptables] ********************************************************
ok: [vagrant-test]

PLAY RECAP ********************************************************************
vagrant-test               : ok=5    changed=0    unreachable=0    failed=0

先ほどと異なりすべての処理の結果が changed から ok に変わっている.これは対象サーバがすでに playbook 通りの設定となっており変更の必要がなかったことを示している. Ansible は playbook の記述内容と対象マシンの設定を照らし合わせ,異なる箇所のみ設定を変更する.

テスト実行 (成功)

Ansible で対象マシンの設定を行ったので,改めて Serverspec でテストを行う.

bundle exec rake spec
...
(省略)
...
4 examples, 0 failures

今度は無事にテストを通過した.

おわりに

仮想環境構築ツール (Vagrant), プロビジョニングツール (Ansible), インフラ設定テストツール (Serverspec) を用いて簡単な Infrastructure as Code を試した.インフラ構築を自動化できるだけでなく,コードを参照することでインフラの状態を把握することもできて便利である.本記事では扱わなかったが,上述の一連の流れを継続的インテグレーションすれば,インフラ構築用コードの正しさが常に保証されるようになり,より便利になると思われる.