初めまして、1月に入社した技術部の安達です!

ChatWorkでは社内で使用する開発環境を簡単に共有・更新できるように、VagrantとAnsibleを使って開発環境を作りました。

社内で環境を配布するためには、

  • VagrantやAnsibleのことをあまり知らない人にも使ってもらう

  • 今後も環境を更新していく

といったことを考えて、セットアップ手順を残したり、テストしやすい状態を維持することは大切だと思います。 今回はその辺りを踏まえて、実際に環境を作るときにどういったテストを行ったのか、どんな落とし穴にハマったのか、というテーマで事例を紹介させていただきます。

環境

Vagrant

  • バージョン: 1.4.2

  • Box: CentOS 6.5 (S3に用意)

  • VertualBox: 4.3.4

  • 共有フォルダ: Vagrantのsynced_folder (NFSは未使用)

Ansible

ベストプラクティスに沿ってプレイブックを作成すると、新しいロールの追加や不要になったロールの切り離しも簡単になります。

どんなテストをやったのか

大きく分けて下の4つのテストを実施しました。それぞれのテストにどんな目的があるか、ちょっとだけ効率よくテストする方法と合わせて紹介します。

  • ロール単位で繰り返し実行するテスト

  • 全体を通してのテスト

  • 再起動するテスト

  • 人柱テスト

ロール単位で繰り返し実行するテスト

まずは粒度の小さいテストについて紹介します。 VagrantとAnsibleで作る開発環境をチームへ配布して運用を開始すると、環境の更新をするたびにプロビジョニングを実行すると思います。

Ansibleのモジュールは冪等性を保つように作られていますが、shellcommandモジュールを使う場合はその限りではありません。 この2つは出来るだけ使用しないように工夫はしますが、一度も使わずに開発環境を作り上げることは難しいと思います。

そこで下のようなshellcommandモジュールを使うロールは、同じロールを2回以上繰り返し実行できるかどうかをテストしました。

例. monitのインストール

この例はあまりオススメできない書き方です。最後のTipsでその理由と改善版の説明します。

タグを指定したプロビジョニング

アクションにタグ付けをし、特定のアクションだけ実行する方と特定のアクションやロールを実行するのが楽になります。

$ ansible-playbook provisioning/site.yml --tags "monit"

タグはテストの効率化だけでなく、運用開始後にも役立つことがあります。

「nginxの設定を変更しましたよ。nginxタグだけ実行してくださいねー。」

全体を通してのテスト

全体を通してプロビジョニングを実行してみると、

  • Ansibleのロールを実行する順番が間違っていた

  • パッケージマネージャーが依存関係の解決が出来なかった

といったような理由で期待通りの結果が得られないことがあります。

全体を通してテストは、Jenkins等CIツールを上手に使ったり、それ専用の環境や端末を用意して定期的に実行すると良いと思います。 できればServerSpecなどのテストツールを導入して、プロビジョニング結果を確認したいものです。

プロビジョニングしたBoxを再起動するテスト

全体を通してのテストを実施して問題が無い場合でも、再起動後に不具合が分かるケースもあります。

例.
  • 再起動したらnginxが起動していなかった

serviceモジュールを使うときにenabledを書き忘れた!なんていうのは稀によくありそうです。

人柱テスト

開発環境を配布する前に、少なくとも一度はチーム内の懐が深い人に、「ドキュメントを見ながら環境をセットアップしてみてもらえませんか?」とお願いしてみましょう。次のような観点からフィードバックが貰えるかもしれません。

  • ドキュメントに過不足が無いか

  • セットアップ手順が複雑ではないか

社内の全員がVagrantやAnsibleに詳しいわけではないので、ドキュメントを残しておくことは大切です。 ドキュメントにはセットアップ手順だけでなく、プロビジョニングに失敗した場合の対処方法や環境のアップデート方法についても書いておきたいところです。

ハマりどころ

共有フォルダがマウントされるタイミングが遅い

立ち上がったBoxに共有フォルダがマウントされるタイミングは、ブート時に実行されるrcスクリプトより遅くなります。 なのでApacheのドキュメントルートを共有フォルダ内に設定している場合は、ドキュメントルートが存在しない状態で起動しようとして失敗してしまいます。

これはApacheに限らない問題で、アプリケーションのデータ保存先やログの保存先として共有フォルダを使う場合は注意する必要があります。

この問題の回避方法を、Apacheを例に3つほど紹介します。

  • VirtualHostのドキュメントルートだけ共有フォルダに設定する方法

  • 共有フォルダがマウントされるタイミングをキャッチする方法

  • Monitでリトライを繰り返す

1.はApache限定ですが、最も簡単な方法だと思います。それと比べて2,3.の方法は少し面倒ですが、Apache以外にも応用が効く方法です。

その1) VirtualHostのドキュメントルートだけ共有フォルダに設定する方法

ドキュメントルートは共有フォルダ以外のフォルダ(デフォルトでも可)に設定し、VirtualHostのドキュメントルートを共有フォルダ上に設定にすることで回避できます。

例. httpd.conf
DocumentRoot "/var/www/public"
例. vhost.conf
DocumentRoot "/vagrant/project/src"

その2) 共有フォルダがマウントされるタイミングをキャッチする方法

CentOSのrcスクリプトはUpstartによってrunlevelイベントをトリガーとして実行されます。しかしながらrunlevelイベントのタイミングではVagrantの共有フォルダがマウントされていません。 vagrantは共有フォルダをマウントしたタイミングでvagrant-mountedというイベントを発行するので、UpstartでこのイベントをキャッチしたタイミングでApacheを起動するように調整しました。

例. /etc/init/httpd.conf

その3) Monitでサービス監視し、Apacheが成功するまでリトライを繰り返す

泥臭い方法ではありますが、Monitを使っている場合は共有フォルダがマウントされApacheが動くまでリトライを繰り返す方法もあります。

例. httpd.mon

api.github.comへのリクエスト上限に達しちゃう問題

PHPのComposerを実行するロールを、テストのために繰り返し実行するとあっという間にapi.github.comへのリクエスト上限に達します。同一のIPアドレスからのアクセス上限が60回なので、同じオフィスのチームに配布して一度にプロビジョニングを実行すると同様に上限に達すると思います。

解決方法

api.github.comへの認証されていないリクエストは60回/hが上限となっていますが、アクセストークンを使用すれば1時間に5,000回まで上限があがります。

Composerの設定ファイルにGithubのoauthのアクセストークンを記載しておくことでこの問題を解決しました。 下のような設定ファイルを用意して、Composerを実行するユーザのホームディレクトリに設定ファイルを配置しています。

例. ~/.composer

Tips

最後にVagrantとAnsibleと上手に付き合っていくためのちょっとしたヒントを紹介します。

テストを簡単に実行する設定

テストではVagrantのBoxを作ったり壊したりを繰り返すので、host_key_checking=Falseを設定しておいて、公開鍵のフィンガープリントチェックをパスしてしまいます。 その他ホストファイルの場所やロールの場所などを毎回オプションとして書くのは煩わしいので、設定ファイルに書いています。

例. ansible.cfg

Vagrantfileにも同様にansibleのオプションを書いておきます。

例. Vagrantfile

Vagrant

(CentOS6の場合) ネットワークの遅延を無くすために

VagrantにおいてBoxのネットワークをNATモードに設定したとき、DNSプロキシを使う設定にするとネットワークが異常に遅くなる場合があります。どうやらCentOS6のboxを使っている状況で、IPv6でDNSプロキシへリクエストを送ろうとするとタイムアウトになることが原因のようです。

具体的には下のIssueなどでご確認ください。 (https://github.com/mitchellh/vagrant/issues/1172)

対策

Vagrantfileに下記2行を追加することで解決されます。

Ansible

shell, commandモジュールの使い方

実行結果のok, changed, failedを定義してあげる

何も気にせずshellcommandモジュールを多用すると、changedが大量発生します。

プロビジョニングが完了したときに変更があったかどうかが分からなくなるので、アクションごとのokchangedfailedを定義してあげると良いと思います。 条件式を使って後述のアクションをスキップすることもでき、プロビジョニングの高速化にも繋がります。

例.Jenkinsの再起動を待機

下の例は、Jenkinsの再起動を待機するアクションですが、5秒おきに20回アクセスし、HTTPのステータスコードを毎回チェックしています。 100秒以内にステータスコード200が返ってくればok、返ってこなければfailedとしています。

Jenkinsはjenkins-cliを使ってジョブを作ったり、プラグインを入れたりしています。 これはモジュールを作った方が良いかもしれないなー、と思っています。

例. monitのインストール(改良版)

すでにテストの章で紹介した例では、Monitがインストールされているかどうかに関わらず、プロビジョニングを実行するたびにファイルのダウンロードや展開が実行されてしまいます。下の例のようにファイルのダウンロードよりも先に「monitがすでにインストールされているか」をチェックし、インストールされている場合はスキップするように書くと良いです。

(get_urlのアクションはダウンロードしたファイルのハッシュ値(md5)をもってchangedかどうかを判定しています。)

追加情報 (2014/02/19)

get_urlモジュールは、destをファイル名まで指定するとダウンロード前にファイルの存在確認をしてくれるようです。

また圧縮ファイルの展開は、バージョン1.4から追加されたunarchiveモジュールで置き換え出来るようです。

unarchive

shirouさん、情報提供ありがとうございます。

テストに時間がかかる

ロールが増えるごとにテスト時間が長くなる上、依存関係が原因でプロビジョニングに失敗する可能性も高くなります。

プロジェクト開始直後は予想以上にスムーズに開発が進みますが、終盤にかけてテストをする時間が増えます。複数端末を使うなどしてテスト時間を減らしたり、テスト中にドキュメントを充実させるなどして時間を無駄にしないよう務めたいところです。

まとめ

今回はチャットワーク社内でVagrantとAnsibleで開発環境を構築し、共有した経験をもとにテストやハマりどころを紹介してみました。 実際にチームで開発環境を共有することになると下のようなことを考える必要があると思います。

  • 開発環境でアプリケーションを動作させるための設定

  • データベースのユーザー作成、マイグレーションやシード

これらのセットアップが最もテストや作り込みに工夫と時間が必要になると思いますので、本記事がその手間を少しでも減らすための参考になればいいなと思っております。