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上に構築します。事前準備
VirtualBoxとVagrantをインストールして、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に接続できていることが確認できます。
環境の破棄
構築した環境は破棄するまで残り続けてディスク容量を使うので、ローカルで以下のコマンドを実行して環境を破棄します。$ 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は簡単な構成から作り始める
参考リンク
次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。皆さんのご応募をお待ちしています。