2016.08.31

1時間でできるオーケストレーション入門


はじめに

こんにちは。次世代システム研究室のT.Tです。

ここのところAnsibleを使って環境を構築する機会が多くあり、そろそろAnsibleにも慣れてきたかなという感触が得られてきました。触れ始めたころから公式のベストプラクティスがあり、その解説や実践例も出揃っていたように思うのですが、その割には有効に使えるようになるまで紆余曲折あり大分手こずったように思います。また、部署内に新しいエンジニアが配属されてきた際に初めてAnsibleを使うというケースも増えている一方で、最初から(ファットな)ベストプラクティスで記述されているplaybookの構成を把握しないといけず、自分と同じように紆余曲折している状況もあるように思います。

そこで、今回はローカルマシン上に簡単なサーバー構成を構築するplaybookの例を使って、ベストプラクティスへの橋渡しとなる構成例を紹介したいと思います。

構築する環境

前回の記事(Golang vs PHP7)で利用したPHP 5.6とPHP 7.0が動作するサーバーとそのサーバーから接続するMySQLのサーバーの構成で構築します。簡素化のため、MySQLのバージョンはデフォルトでインストールされる5.1、PHP環境にはWebフレームワークを載せない構成にします。サーバーはVagrant経由でVirtualBox上に構築します。

事前準備

VirtualBoxVagrantをインストールして、Vagrantの作業ディレクトリとして~/workspace/ansible-playbookを作成します。Windowsの場合は、さらに仮想化支援機構の設定が必要になります。
*本記事ではMacBook ProでVirtualBoxのバージョンは5.0.12、Vagrantのバージョンは1.8.1で動作検証しています。

以下の内容を~/workspace/ansible-playbook/Vagrantfileに保存し、~/workspace/ansible-playbookでvagrant upを実行するとciサーバーから各サーバーにパスワード無しでsshでログイン出来る環境が構築され、Ansibleを実行する環境が整います。
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
  config.vm.box = "centos-6.7"
  config.vm.box_url = "https://atlas.hashicorp.com/bento/boxes/centos-6.7"

  server_configs = [
    {"hostname" => "php56", "ip" => "192.168.33.11", "port" => 2201},
    {"hostname" => "php7", "ip" => "192.168.33.12", "port" => 2202},
    {"hostname" => "db", "ip" => "192.168.33.13", "port" => 2203},
  ]

  script = "
sudo yum update -y --disablerepo=\* --enablerepo=base,updates
sudo yum install -y epel-release
sudo yum install -y --enablerepo=epel ansible
cd ansible-playbook
cp .vagrant/machines/ci/virtualbox/private_key /home/vagrant/.ssh/id_rsa
cp /home/vagrant/.ssh/authorized_keys .vagrant/
sudo tee /home/vagrant/.ssh/config << EOS > /dev/null
StrictHostKeyChecking no
EOS
chmod -R og-rwx /home/vagrant/.ssh
chown -R vagrant.vagrant /home/vagrant/.ssh
"

  config.vm.define :ci do |ci|
    ci.vm.hostname = "ci"
    ci.vm.box = "centos-6.7"
    ci.vm.network :private_network, ip: "192.168.33.10"
    ci.vm.network :forwarded_port, guest: 22, host: 2200, id: "ssh"
    ci.vm.provider "virtualbox" do |v|
      v.customize ["modifyvm", :id, "--memory", "1024"]
      v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
    end
    ci.vm.synced_folder ".", "/home/vagrant/ansible-playbook", owner: "vagrant", group: "vagrant", :create => true, :mount_options => ["fmode=600"]
    ci.vm.provision :shell, inline: script
  end

  server_configs.each do |server_config|
    config.vm.define "#{server_config['hostname']}" do |server|
      server.vm.hostname = server_config['hostname']
      server.vm.box = "centos-6.7"
      server.vm.network :private_network, ip: server_config['ip']
      server.vm.network :forwarded_port, guest: 22, host: server_config['port'], id: "ssh"
      server.vm.provider "virtualbox" do |v|
        v.customize ["modifyvm", :id, "--memory", "2048"]
        v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
      end
      server.vm.synced_folder ".", "/home/vagrant/ansible-playbook", owner: "vagrant", group: "vagrant", :create => true, :mount_options => ["fmode=600"]
      server.vm.provision :shell, inline: "cat ansible-playbook/.vagrant/authorized_keys >> /home/vagrant/.ssh/authorized_keys"
    end
  end
end

playbookの構成

ここから本題に入ります。

inventoryの設定

まず、192.168.33.10のサーバーはci、192.168.33.11のサーバーはPHP 5.6の環境というようにサーバーがどの役割を担うかの分類を決めます。inventoryにはそのように分類したものをグループとして定義します。今回の構成では以下のような設定になり、これを~/workspace/ansible-playbook/hosts.localに保存します。local_ci、local_php56、local_php7、local_dbがグループ名で、このグループごとにroleや独自に定義した変数を適用 することができます。さらに、local:childrenで各グループをlocalという大きなグループに含めています。
[local_ci]
192.168.33.10

[local_php56]
192.168.33.11

[local_php7]
192.168.33.12

[local_db]
192.168.33.13

[local:children]
local_ci
local_php56
local_php7
local_db

roleの取得

ミドルウェア等を構築するためのplaybookのroleはAnsible Galaxyで共有されているものがそのまま使えることも多く、今回の構成であればここから取得できるものだけで構築できてしまいます。~/workspace/ansible-playbook/requirements.ymlに以下の内容を保存して、ansible-galaxy install -p roles -r requirements.ymlを実行するとroleが取得できます。
- src: geerlingguy.repo-epel
- src: geerlingguy.repo-remi
- src: geerlingguy.php
  version: 3.2.2
- src: geerlingguy.mysql
- src: jdauphant.nginx

playbookの設定

playbookにはどのホストにどのAnsibleモジュールをどのように適用するかを定義するために、taskやrole、変数を始め様々な要素を含めることができます。ですが、ここに直接いろいろ書き込むとplaybookの構成を複雑にする一因になりがちです。そのため、ここにはどのホストにどのroleを適用するかを定義するに留めます。以下の内容を~/workspace/ansible-playbook/のsite.yml、webservers.yml、dbservers.ymlに保存します。

~/workspace/ansible-playbook/site.yml
- include: webservers.yml
- include: dbservers.yml
~/workspace/ansible-playbook/webservers.yml
- hosts: local_php56
  remote_user: vagrant
  roles:
    - geerlingguy.repo-epel
    - geerlingguy.repo-remi
    - jdauphant.nginx
    - geerlingguy.php

- hosts: local_php7
  remote_user: vagrant
  roles:
    - geerlingguy.repo-epel
    - geerlingguy.repo-remi
    - jdauphant.nginx
    - geerlingguy.php
~/workspace/ansible-playbook/dbservers.yml
- hosts: local_db
  remote_user: vagrant
  roles:
    - geerlingguy.mysql

変数の定義

playbookに設定したroleには、環境に応じて設定を変更できるようにいろいろな変数が定義されています。変数の意味は各role内のREADME.mdに説明されていますが、変数の値を変更する場合はdefaults/main.ymlからコピーすると使い勝手が良いです(一部の変数はvars以下に定義されています)。

変更する変数は~/workspace/ansible-playbook/group_vars/localに定義します。先ほどのinventoryファイルにlocal:childrenを定義したことで、group_vars/localの値が各グループに適用されるようになっていて、このファイル一つで変数を管理できるようになっています。MySQLのデータベースを作成して、nginxでCGIを動かせるようにして、PHPからMySQLに接続するための変数設定は以下のようになります。これを~/workspace/ansible-playbook/group_vars/localに保存します。
# geerlingguy.mysql
mysql_databases:
  - name: test
    collation: utf8_general_ci
    encoding: utf8
    replicate: 1

mysql_users:
  - name: test
    host: 'localhost'
    password: password
    priv: 'test.*:ALL'
  - name: test
    host: '192.168.33.%'
    password: password
    priv: 'test.*:ALL'

# geerlingguy.php
php_packages:
  - php
  - php-fpm
  - php-opcache
  - php-mysql
php_date_timezone: "Asia/Tokyo"
php_enable_apc: false
php_enable_webserver: true
php_enable_php_fpm: true
php_fpm_pool_user: vagrant
php_fpm_pool_group: vagrant
php_webserver_daemon: nginx

# jdauphant.nginx
nginx_official_repo: True
nginx_user: vagrant
nginx_remove_sites:
  - default
nginx_sites:
  service:
    - listen 80 default_server
    - server_name web
    - root   /var/www/service
    - location ~ \.php$ {
        fastcgi_split_path_info  ^(.+\.php)(.*)$;
        if (-f $document_root$fastcgi_script_name){
          set $fsn $fastcgi_script_name;
        }
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fsn;
        fastcgi_param  PATH_INFO        $fastcgi_path_info;
        fastcgi_param  PATH_TRANSLATED  $document_root$fsn;
      }

PHPのバージョンの切り分け

先ほどの変数設定だけでは、local_php56とlocal_php7のサーバー内で同じPHPのバージョンが使われてしまいます。local_php56とlocal_php7で使いたいPHPのバージョンを指定するために~/workspace/ansible-playbook/group_vars/local_php56と~/workspace/ansible-playbook/group_vars/local_php7にそれぞれ以下の内容を保存します。
~/workspace/ansible-playbook/group_vars/local_php56
#geerlingguy.php
php_enablerepo: "remi-php56,epel"
~/workspace/ansible-playbook/group_vars/local_php7
#geerlingguy.php
php_enablerepo: "remi-php70"

環境構築

必要な準備が出来たので、ローカルの作業ディレクトリからciサーバーにログインしてplaybookを実行します。
$ cd ~/workspace/ansible-playbook
$ vagrant ssh ci
[vagrant@ci ~]$ cd ansible-playbook/
[vagrant@ci ~]$ ansible-playbook -i hosts.local site.yml --user=vagrant --become

動作確認

local_php56とlocal_php7にそれぞれPHP 5.6とPHP 7.0がインストールされていることを確認します。playbookの実行が完了した状態から、ssh 192.168.33.11とssh 192.168.33.12で各サーバーにログインして以下のコマンドを実行して動作確認用のPHPファイルを用意します。
sudo mkdir /var/www/service
sudo tee /var/www/service/index.php << 'EOS' > /dev/null
<?php
$link = mysqli_connect("192.168.33.13", "test", "password", "test");
$result = $link->query("SELECT version()");
$row = $result->fetch_row();
print("Hello MySQL {$row[0]} by PHP " . phpversion());
$result->close();
$link->close();
EOS
ブラウザからhttp://192.168.33.11/index.phpとhttp://192.168.33.12/index.phpにアクセスするとそれぞれ以下のように表示され、各バージョンのPHPがインストールされてMySQLに接続できていることが確認できます。
php56
php7

環境の破棄

構築した環境は破棄するまで残り続けてディスク容量を使うので、ローカルで以下のコマンドを実行して環境を破棄します。
$ vagrant destroy ci php56 php7 db

他の環境への展開

例えば、今回の構成をステージング環境に適用する場合は、webservers.ymlとdbservers.ymlにステージング用のplaybookを追記して、hosts.stagingとgroup_vars/staging、group_vars/staging_php56、group_vars/staging_php7を、それぞれローカル環境用に用意したファイルと同じような内容をステージング環境の設定に置き換えることで構成できます。

今回のような簡単な構成の場合はローカル環境用だけであれば変数管理用のファイルの数は多くありませんが、ステージング環境や本番環境、場合によってはさらに他の環境を追加していくとその分ファイルの数も増えていくので、管理しやすくするためにはある程度まとまった形で変数を管理できるようにしておくことが重要になってきます。今回はgroup_varsの直下にローカル環境用とステージング環境用のファイルを配置する構成になっていますが、それぞれの環境用にファイルをまとめるためのgroup_vars/localとgroup_vars/stagingディレクトリを作ってそこに配置しても同じように動きます。

ベストプラクティスに向けて

今回紹介した内容で、ベストプラクティスの一部を適用することで管理しやすいplaybookの構成の一例を示せたのではないかと思います。実際にサービス等を提供する環境の場合はより複雑な構成になり、ベストプラクティス全体に近い構成に近づいていくと思います。しかし、今回紹介した構成を維持するように意識して拡張を進めていくと、より簡単な構成を保てるのではないかと思います。

また、roleもAnsible Galaxy等から取得できるものだけでは足らず、独自の実装が必要になると思いますが、その際も今回使ったroleの実装を参考にしつつベストプラクティスの構成やAnsibleモジュールの使い方の把握を進めるとよりスムーズになるかと思います。roleは以下の構成で始めるのが理解しやすいと思います。
myapp.example
    tasks/
        main.yml
    templates/
        template.j2
    defaults/
        main.yml

まとめ

今回はAnsibleのplaybookの簡単な構成で環境を構築する一例を取り上げてみました。まとめると以下のようになります。
  • Ansible Galaxyに使えるroleがないか探して使えそうなものは使う
  • playbookにはroleだけ書く
  • 変数はgroup_varsのファイルにある程度まとめて書けるように適切にホストをグループ化する
  • roleは簡単な構成から作り始める
オーケストレーションを実践するにはやらなければならないことはまだたくさんありますが、サウンドミキサーで手軽に音源を操作するような感覚は伝えられたのではないでしょうか。これから環境構築を始めてみようという方の一助になれば幸いです。

参考リンク

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。