2016.09.28

外部接続を絞られているあなたのための Ansistrano を使ったデプロイ環境構築

Pocket

次世代システム研究室の DevOps ネタ担当の M.Y. です。

最近、セキュリティ要件の厳しい企業ネットワーク内での、デプロイ環境の構築をお手伝いする機会がありました。具体的には、デプロイ用のサーバ1台以外は外部接続できないという環境でした。

その際に Ansistrano というツールを使ってデプロイ環境を構築したのですが、このノウハウは他でも使えそうなので、今回ご紹介します。

制約の厳しい企業ネットワークの例

最近私がお手伝いした案件では、以下のような要件と制約がありました。
  • 外部接続を強く制限されたネットワーク内で、新たに開発したバッチ処理を実行したい
  • Java/Scala で開発されたバッチを実行するために、このネットワークにあるバッチサーバに jar ファイルなどをデプロイしたい
  • jar のビルドに必要な GitLab サーバおよび Maven リポジトリは、このネットワークの外にある
  • ネットワーク外へのアクセスはビルド&デプロイ用のサーバ1台(以下、デプロイサーバ)しか許可されていない
外部接続を強く制限されたネットワーク

Ansistrano とは?

デプロイ自動化ツールとしては Capistrano が有名ですが、Ansistrano はこの Capistrano と同様の動作を提供する Ansible role(配布・再利用可能な Ansible playbook の部品)です。

Ansistrano の提供する機能は、Capistrano と比べてどうなのか? その答えは、Ansistrano ページ の “History” の節で簡潔に説明されています。以下はその抄訳です。

歴史

Capistrano はサーバ自動化ツールで、現在はバージョン3である。元々バージョン2.0は Rails アプリケーションのデプロイを考えて作られていたが、プラグインを追加することで Rails 以外のデプロイや、異なるデプロイ戦略などなどにも対応できた。私は Capistrano v2 を愛用していたし、プラグインをたくさん書いた。

Capistrano 2 は偉大なツールだったし、今でも問題なく動く。しかし、v2 はもうメンテされておらず、オリジナルのチームは v3 を開発している。この新バージョンは v2 と同じ機能を提供しておらず、強力さと拡張性にかけている。加えて、Ansible のような新しいツールが、アプリケーションをより簡単にデプロイできるようになっている。

そこで私は、v2 はメンテナンスされておらず、v3 は十分な機能を持たない Capistrano を使うのをやめた。Capistrano でできることのすべては、Ansible でできる。もしあなたが他の代替案を探しているなら、Fabric や Chef Solo をチェックすると良い。

今回の件でも、私も最初は Capistrano v3 を検討したのですが、Capistrano 3 では deploy_via :copy に相当する機能(まさに今回の案件で欲しかった)がなくなった、という情報をあちこちで見かけてがっかりしたりしました……。

その後 Capistrano v3 と Ansistrano を比べてみて、確かにこの開発者が言うように Ansible & Ansistrano で十分に感じました。主な理由は以下の3点です。Ansistrano の具体的な使い方を交えつつ、説明します。

  • 理由1. deploy/rollback機能:Capistrano の基本である deploy/rollback 機能を十分に備えている。
  • 理由2. Ansible との親和性:OS の設定などに Ansible を使っている場合、Capistrano を使うとデプロイツール(とその学習コスト)が増えてしまう。Ansistrano を使うなら Ansible のみ覚えればよい。
  • 理由3. カスタマイズ可能:独自の処理を差し込むための機能を備えている。Ansible role として実装されているので、動作を理解しやすい。

理由1. deploy/rollback機能

Capistrano と同様に、Ansistrano でコードをデプロイすると、以下のようなディレクトリが作られます。/target_dir/current は、常に最新のプログラムへのシンボリックリンクになります。
target_dir/
  current -> /target_dir/releases/20160913013644Z へのシンボリックリンク
  releases/
    20160909061440Z/
    20160912030815Z/
    20160913013644Z/
      log -> ../../shared/log へのシンボリックリンク
      tmp -> ../../shared/tmp へのシンボリックリンク
  shared/
    log/
    tmp/
また、Ansistrano のロールバック機能を実行すると、releases ディレクトリ以下に残っているバックアップを使い、以下のようにロールバックします。最新だった 20160913013644Z ディレクトリは削除されます。これだけの機能があれば、デプロイには十分と判断しました。
target_dir/
  current -> /target_dir/releases/20160912030815Z へのシンボリックリンク
  releases/
    20160909061440Z/
    20160912030815Z/
      log -> ../../shared/log へのシンボリックリンク
      tmp -> ../../shared/tmp へのシンボリックリンク
  shared/
    log/
    tmp/

理由2. Ansible との親和性

Ansistrano を使うために必要な設定は、”ansistrano_” から始まる変数の設定と、ロールの追加だけです。

例えば以下のような playbook を書いて、
---
- hosts: batch_server
  vars:
    ansistrano_deploy_from: "/source_dir"
    ansistrano_deploy_to: "/target_dir"
    ansistrano_deploy_via: rsync
  roles:
    - carlosbuenosvinos.ansistrano-deploy
以下のようにコマンドを実行すると、目的のファイルをデプロイできます。
$ ansible-playbook -i production deploy-batch.yml
ロールバックの方は、以下のような playbook を書いて、
---
- hosts: batch_server
  vars:
    ansistrano_deploy_to: "/target_dir"
  roles:
    - carlosbuenosvinos.ansistrano-rollback
以下のように実行すると、サーバ上に残っているバックアップを使ってロールバックします。
$ ansible-playbook -i production rollback-batch.yml
単純なデプロイであれば、必要な設定は本当にこれだけです。簡単ですね。

変数の意味は名前が示す通りで、ansistrano_deploy_from はデプロイ元(デプロイサーバ)のディレクトリ、ansistrano_deploy_to はデプロイ先(バッチサーバ)のディレクトリです。

理由3. カスタマイズ可能

今回の場合はデプロイ前に jar のビルドが必要ですが、Ansistrano はこういう処理を差し込むのも簡単です。

Ansistrano の動作は複数のフェーズに分かれており、各フェーズの前後に処理を差し込むことができます。以下の図は、Ansistrano のページ からの引用です。

Ansistrano Flow

差し込む処理は Ansible の task で書くので、Ansible が使える人ならすぐ書けると思います。その具体例は、後ほどご紹介します。

ちなみに、Ansistrano は Capistrano ほどにはドキュメントが充実していませんが、結局は Ansible role なので、YAML ファイルを読めば動作がすぐわかる、というメリットがあります。Ansistrano のインストール先は以下のディレクトリです。
/etc/ansible/roles/carlosbuenosvinos.ansistrano-deploy
/etc/ansible/roles/carlosbuenosvinos.ansistrano-rollback

Ansistrano のインストール方法

Ansible がインストールされている環境であれば、以下のコマンドを実行するだけで Ansistrano がインストールされます。
$ ansible-galaxy install carlosbuenosvinos.ansistrano-deploy carlosbuenosvinos.ansistrano-rollback
ちなみに、外部接続が絞られすぎていて ansible-galaxy すら実行できないという方もご安心ください。これはただの role ファイルなので、他のマシンで ansible-galaxy を実行し、そのマシンの /etc/ansible/roles 以下のファイルをコピーすれば問題なく動きます。

実際の Ansistrano 利用事例

システム構成

システム構成は以下の通りです。バッチサーバに対して、2種類のバッチをデプロイします。各バッチは開発サイクルが異なるため、別々にデプロイできる必要がありました。

Ansitrano example

ソフトウェアのバージョン

今回利用したバージョンは以下の通りです。事例では Ansible 2.0 以降でしか使えない block を使っているので、Ansible 1 系を使っている場合は適宜読み替えてください。
  • Ansible 2.0.1.0
  • ansistrano-deploy 1.10.0
  • ansistrano-rollback 1.4.2

Playbook の設計方針

色々検討した結果、設計方針は以下のようにしました。方針を決めるにあたっては、部署内の別案件での Ansistrano の先行事例も参考にしています。
  • Playbook の名称
    • Ansistrano を使ってデプロイするための playbook の名前は “deploy-{{ アプリ名 }}.yml” に統一
    • Ansistrano を使ってロールバックするための playbook の名前は “rollback-{{ アプリ名 }}.yml” に統一
    • デプロイ以外の設定(crontab 設定など)を行う playbook の名前は “{{ サーバの種別を表す文字列 }}.yml” に統一
  • jar のビルド
    • Playbook を実行するたびに、ビルド用のディレクトリ(workspace)を削除・新規作成
    • デプロイしたいファイルを workscape/{{ プロダクト名 }}/release ディレクトリにまとめて、このディレクトリの中身のみを rsync でバッチサーバにデプロイ

Playbook のディレクトリ構成

私達のチームでは、基本的に Ansible 公式ドキュメントにあるベストプラクティス を踏襲しています。今回はこのベストプラクティスに、ビルド用の “workspace” ディレクトリと、tasks ディレクトリ内の before_update_code.yml を追加しました。

なお、以下のファイル名は、ディレクトリ構成を説明するために付けた架空のもので、実際のものとは若干変えています。
ansible_dir/
  production                    # 本番環境のインベントリファイル
  staging                       # ステージング環境のインベントリファイル

  batch_server.yml              # バッチサーバの設定用 playbook

  deploy-batch1.yml             # バッチ1のデプロイ用 playbook
  deploy-batch2.yml             # バッチ2のデプロイ用 playbook
  rollback-batch1.yml           # バッチ1のロールバック用 playbook
  rollback-batch2.yml           # バッチ2のロールバック用 playbook

  roles/
    batch1/
      tasks/
        before_update_code.yml  # バッチ1のビルド処理
        main.yml                # バッチ1 で必要になる設定のタスク
    batch2/
        before_update_code.yml  # バッチ2のビルド処理
        main.yml                # バッチ2 で必要になる設定のタスク
  workspace/
    batch1/                     # バッチ1のビルド用ディレクトリ(ビルド毎に削除)
    batch2/                     # バッチ2のビルド用ディレクトリ(ビルド毎に削除)

インベントリファイル

デプロイ先のディレクトリは、後述するデプロイ用 playbook と設定用 playbook の両方で使うため、インベントリファイルに記載します。

production
[batch_server]
batch01

[all:vars]
batch1_git_url=...
batch1_path=/target_dir/batch1
batch2_git_url=...
batch2_path=/target_dir/batch2

デプロイ用 playbook

デプロイ用 playbook の deploy-batch1.yml には、Ansistrano 用の変数(ansistrano_ から始まる変数)を記載します。また、Ansistrano の変数とは別に、version 変数(デフォルト値は master)を用意し、–extra-vars でこの変数を上書きすると、任意のブランチをデプロイできるようにしました。

バッチサーバにコードをアップロードする前に jar のビルドを行う必要があります。タイミングとしては Update code phase の前が良いので、jar のビルドを行うタスクファイルを ansistrano_before_update_code_tasks_file に指定します。

deploy-batch1.yml
---
- hosts: batch_server
  vars:
    version: master
    workspace_dir: '{{ playbook_dir }}/workspace/batch1'
    ansistrano_deploy_via: rsync
    ansistrano_deploy_from: '{{ playbook }}/workspace/batch1/release'
    ansistrano_deploy_to: '{{ batch1_path }}'
    ansistrano_before_update_code_tasks_file: '{{ playbook_dir }}/roles/batch1/tasks/before_update_code.yml'
    ansistrano_shared_paths:
      - log
      - tmp
  roles:
    - carlosbuenosvinos.ansistrano-deploy
roles/batch1/tasks/before_update_code.yml
---
- block:
  - name: Remove workspace directory
    file: path={{ workspace_dir }} state=absent

  - name: Checkout batch1
    git: repo={{ batch1_git_url }} dest={{ workspace_dir }} version={{ version }}

  - name: Create release directory
    file: path={{ workspace_dir }}/release state=directory mode=0755

  - name: Build jar
    command: gradle jar chdir={{ workspace_dir }}

  - name: Copy jar to release directory
    copy: src={{ workspace_dir }}/target/libs/batch1.jar dest={{ workspace_dir }}/release

  - name: Copy launcher to release directory
    copy: src={{ workspace_dir }}/bin/launcher.sh dest={{ workspace_dir }}/release

  delegate_to: localhost
  run_once: true
ここでの注意点は、デプロイサーバ上でビルドするために “delegate_to: localhost” を指定すること、およびビルドを1回だけ実行するために “run_once: true” を指定することです。この指定を忘れると、バッチサーバの台数だけビルドが実行されてしまいます。

設定用 playbook

cron などの設定については、複数のバッチの分を1個の playbook にまとめることができます。

batch_server.yml
- hosts: batch_server
  roles:
    - batch1
    - batch2
また、これは Ansistrano の話とは外れるのですが、Ansible 2.0 から cron モジュールに “disable” という便利なオプションが追加されました。disable の値を True に設定すると、そのジョブは crontab 内でコメントアウトされます。

roles/batch1/tasks/main.yml
- name: Set batch1 job
  cron:
    name: Batch1
    minute: 0
    job: '{{ batch1_path }}/launcher.sh'
    state: present
    disabled: '{{ disable_batch1 is defined and disable_batch1 }}'

- name: Set batch2 job
  cron:
    name: Batch2
    minute: 30
    job: '{{ batch2_path }}/launcher.sh'
    state: present
    disabled: '{{ disable_batch2 is defined and disable_batch2 }}'
このようにタスクを書くと、–extra-vars で disable_batch1(または disable_batch2)を True に指定した場合に限り、その cron ジョブを crontab 内でコメントアウトすることができます。例えば、以下を実行すると、両方のバッチがコメントアウトされます。
$ ansible-playbook -i production batch_server.yml --extra-vars="disable_batch1=True disable_batch2=True"
今までも when と state オプションを併用すれば似たようなことはできましたが、その場合は state=present 用のタスクと state=absent 用のタスクを別に用意する必要がありました。disable を使えば1個のタスクにまとめられるので、スッキリ読みやすくなります。

まとめ

今回は外部接続が絞られているときに、Ansistrano を使ってデプロイ環境を構築する方法を、実例をベースにご紹介しました。

Ansitrano は、Ansible に慣れている方であれば、新しい概念を特別に覚える必要もなく、すぐに使い始められるツールです。今回は特殊なネットワーク環境での事例をご紹介しましたが、普通の環境でも使える便利なツールだと思います。Ansible を使っている方は、是非お試しください。

次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

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