アプリケーションの開発環境を Ansible でつくる

こんにちは。アプリケーションエンジニアの id:aereal です。

この記事ははてなエンジニアアドベントカレンダー2014の1日目です。

今日はアプリケーションの開発環境を作成する手順を Ansible でコードとして表現し自動化する取り組みとその背景について簡単に紹介します。

前提

この記事で扱うアプリケーションは Perl と JavaScript で書かれた中規模の Web アプリケーションです。

アプリケーションを開発するチームのエンジニアとデザイナすべてが Mac OS X を使っています。手元で開発する際には VM などを動かさずに OS X でアプリケーションを起動させます。

また、開発やデプロイなどにおいて SOCKS プロキシを通してアクセスする必要のあるサーバが存在します。

開発環境の構築手順を始めとしたドキュメントは Redmine の Wiki にまとめられています。

これまでの開発環境の構築

前述の通り Redmine の Wiki に手順がドキュメントとしてまとまっているので、これに書いてある通りにコマンドを実行したりファイルをダウンロードして配置することで開発環境の構築を進めます。

追加でインストールすべきミドルウェアが増えたり、設定を変更する必要が生じた場合は、Wiki のドキュメントを更新し適宜チームメンバーに周知する、というような手順をとります。

ドキュメントは自然文で書かれており、それをプログラムとして実行したり、あるいは (自動化された) テストが存在するわけではありませんから、ドキュメントと現状が一致しているかどうかは、ドキュメントの通りに手順を実行してみてうまくいくか (= アプリケーションが起動するかどうか) を確かめる、というところまで至って始めてわかります。

ドキュメントが正しいかどうかの検証がこれだけ手間ではメンテナンスが滞るのは目に見えており、実際に新しくチームにメンバーが加わり躓く度に更新する、という運用になっていました。

環境構築はほとんど単純作業である一方で、手順に依存関係があり先に必要な手順を完遂させないと後の手順が実行できないという厄介な性質があります。そのため「(一度では) 成功しないかもしれない手順」を実行する際には様子を見る必要がありブロックされます。

このブロックされる時間は積もれば無視できない大きさになります。ブロックされているその時間をサービスやコードの概観の説明を受けたり、よりサービスの開発にとって有益な時間として使うことができれば望ましいであろうということは明らかです。

こうした現状から、検証が容易である実行可能なコードとして開発環境の構築を記述することを決めました。

Ansible とは

Ansible とはどのようなものか、簡単に説明すると:

Ansible is the simplest way to automate IT.

Ansible is Simple IT Automation

……と公式サイトにあります。

端的に表現すると構成管理ツール (Configuration Management Tools) としても使えるソフトウェアです。

構成管理ツールとは:

Chef や Puppet は「プロビジョニングフレームワーク」とも呼ばれているが、以下の議論をより厳密にするために、Lee Thompson 氏による Velocity 2010 での Provisioning Toolchain というタイトルのプレゼン に基づき、プロビジョニングを以下の3つのレイヤーにわけて考える。(イメージしやすいように、それぞれに該当するツールやサービスを当て込んでみた。)

  • Orchestration
    • Fabric, Capistrano, MCollective
  • Configuration
    • Puppet, Chef, AWS OpsWorks
  • Bootstrapping
    • Kickstart, Cobbler, OpenStack, AWS

下の方から見ていくと、Bootstrapping はいわゆる OS インストールにあたる領域。Configuration はミドルウェアレベルまでの設定、Orchestration はアプリケーションデプロイなどを行って、個別のシステムをひとつのサービスとして協調動作させる、といった感じか。

インフラ系技術の流れ - Gosuke Miyashita

……という定義における Configuration 層を担うソフトウェアであるとします。

なぜ Ansible か

昨年の Hatena Engineer Seminar #2 にて筆者は Vagrant と Chef でつくるはてなブックマークの開発環境 というタイトルで発表しました。

Chef ではなく Ansible を採用したのはなぜか、簡潔に表現すると「Chef を採用するメリットが薄い環境である」ということになります。

Chef を採用した大きなモチベーションとして、開発環境を本番環境と同じ OS に揃えることで既存の Chef レシピを流用できることが期待できる、というものがありました。

しかし今回対象となるアプリケーションはローカルの OS X で実行する想定です。つまり既存の Chef レシピを流用することが現実的ではなくなりました。

そのため、改めてツールを選択することとなり、結果として Ansible を選択しました。

いくつかある理由で最も大きな理由として、Ansible でセットアップする対象のマシンに要求する依存が少ないことです。

sshd が起動していればよいという点は、セットアップ対象のマシンにも Ruby で書かれたクライアントをインストールする必要のある Chef に対して大きく優位であり魅力的であると評価しました。

Ansible で自動化する設定

実際に Ansible でどんな設定を自動化したのかという話題ですが、一言で言うならば「開発に必要な設定すべて」です。

もう少し砕いて詳細を見てみると:

  • システム全体の設定 (/etc 以下にあるような設定)
  • ネットワークの設定
    • SOCKS プロキシ
    • proxy.pac
  • Homebrew によるパッケージのインストール
  • CPAN モジュールのインストール

……のように大きく分けられます。これらのうちいくつかについて実践的なやりかたを紹介します。

システム全体の設定

システム全体の設定はユーザをまたいで共有されており、現実的には OS のアップデートにより変更される可能性があります。

あるアプリケーションを開発する際にシステム全体に影響を与えない・与えられないのが理想ですが、現実的に困難な場合もあります。

たとえば hosts ファイルが挙げられます。dnsmasq などを使ってもたらされる複雑さよりも hosts に追記する方がメリットは大きいと考えられます。

hosts ファイルに対して安全にエントリを追加するには、既に該当する行が無い場合のみ追加、としたいところです。Ansible にはまさにそのものの lineinfile というモジュールがあります。

例:

---
# tasks/main.yml
-
  name: Install hosts entries
  lineinfile:
    backup=yes
    dest=/etc/hosts
    regexp="{{ item.hostname }}"
    line="{{ item.ip }}\t{{ item.hostname }}"
  with_items: hosts_record
  sudo: yes
---
# defaults/main.yml
hosts_record:
  -
    ip: 127.0.0.1
    hostname: local.hatena.host

ネットワークの設定

SOCKS プロキシ

SSH のダイナミックポートフォワーディングを利用して SOCKS プロキシに接続する必要があるのですが、SSH の接続を管理するために autossh というツールを使うことにします。

また autossh のプロセス自体を起動・管理するために OS X の launchd を使います。
launchd とは systemd や init と crond の機能を併せ持つようなサービス管理フレームワークです。

ne.jp.hatena.socks-example.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>KeepAlive</key>
    <true/>
    <key>Label</key>
    <string>ne.jp.hatena.socks-example</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/opt/autossh/bin/autossh</string>
      <string>-M</string>
      <string>XXXX</string>
      <string>-N</string>
      <string>-D</string>
      <string>XXX</string>
      <string>-p</string>
      <string>XXX</string>
      <string>-C</string>
      <string>XXX</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>EnvironmentVariables</key>
    <dict>
      <key>AUTOSSH_LOGFILE</key>
      <string>/Users/aereal/Library/Logs/autossh/ne.jp.hatena.socks-example.log</string>
      <key>AUTOSSH_LOGLEVEL</key>
      <string>7</string>
    </dict>
  </dict>
</plist>

このような plist (property list) ファイルを記述し launchd に読み込ませることで OS X の起動時に自動で起動し、死活監視や標準出力をログファイルにリダイレクトさせるといったデーモン管理に必要な諸々を launchd に任せることができます。

Ansible で扱う際には雛形となるファイルを用意し、使う SOCKS プロキシごとの設定 (各ポート番号) を template モジュール に与えています。

例:

tasks/main.yml:

-
  name: Install socks proxy launchd configuration
  template:
    dest="/Users/{{ ansible.user_id }}/Library/LaunchAgents/{{ item.label }}.plist"
    src=files/socks.plist
  with_items: socks_config

files/socks.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>KeepAlive</key>
    <true/>
    <key>Label</key>
    <string>{{ item.label }}</string>
    <key>ProgramArguments</key>
    <array>
      <string>{{ homebrew_root }}/opt/autossh/bin/autossh</string>
      <string>-M</string>
      <string>{{ item.monitoring_port }}</string>
      <string>-N</string>
      <string>-D</string>
      <string>{{ item.dynamic_port }}</string>
      <string>-p</string>
      <string>{{ item.port }}</string>
      <string>-C</string>
      <string>{{ item.host }}</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>EnvironmentVariables</key>
    <dict>
      <key>AUTOSSH_LOGFILE</key>
      <string>{{ logs_directory }}/autossh/{{ item.label }}.log</string>
      <key>AUTOSSH_LOGLEVEL</key>
      <string>7</string>
    </dict>
  </dict>
</plist>

defaults/main.yml:

socks_config:
  -
    label: ne.jp.hatena.socks
    monitoring_port: XXX
    dynamic_port: XXX
    port: XXX
    host: xxx.hatena.host
proxy.pac

proxy.pac (Proxy Auto Configuration) はプロキシの設定をプログラマブルに記述するためのファイルですが、これを正しく配置する必要があります。

OS X ではシステム環境設定 (System Preferences.app) の「ネットワーク (Network.prefpane)」で編集できる設定のほとんどは networksetup というコマンドで参照・編集できます。設定可能な項目の詳細は man に記述されています。

参照すべき proxy.pac を OS X に設定するためには -setautoproxyurl というアクションを指定すればよいです。

networksetup -setautoproxyurl NETWORK_SERVICE PROXY_AUTO_CONFIG_URL

NETWORK_SERVICE はネットワークインターフェースごとの設定に名前を付けて区別したもの、と考えられます。典型的には Wi-Fi です。システム環境設定の「ネットワーク」の左に並ぶ項目が network service です。

PROXY_AUTO_CONFIG_URL は参照すべき proxy.pac の URL です。URL であるという点に注意する必要があります。ローカルのファイルを参照する際には file スキームを指定します。

課題

期待する結果 (= 構成) になっているかどうかを検証する必要があります。つまりテストを書く必要があります。

serverspec のようなツールが既にあり、環境は整っているといえます。

おわり

以上、ごく簡単にはてなのサービス開発におけるアプリケーションの開発環境の構築に関する取り組みを紹介しました。

細かな点で躓いて律速されるようでは、よりよいサービスの開発・素早い改善は望めません。直接的なサービスの開発・改善だけではなく、こうした足回りを整えておくことも長い目では重要です。

はてなでは、様々な視野に立ってサービスの開発・改善に取り組みたいと考えられるエンジニアを募集しています。

採用情報 - 株式会社はてな