FORCIA CUBEフォルシアの情報を多面的に発信するブログ

AnsibleとAnsistranoを導入した話

2019.12.12

アドベントカレンダー2019 開発事例

FORCIAアドベントカレンダー2019 12日目の記事です。

2019年アドベントカレンダーの第12日目の記事を担当します、エンジニアの滋野井です。

フォルシアでサーバの環境構築※1Ansibleを、アプリケーションなどのリリースにAnsistranoを導入した際の話を書いていこうと思います。

※1 「サーバの環境構築」とはOSレイヤーの設定をしたり、ミドルウェアをセットアップしたりすることを指します。

なお、この記事ではAnsibleやAnsistranoが何かという話はなく、ある程度知っていることを前提に記事を書いております。ご注意をば。

サーバの構築にAnsibleを導入した話

導入までの話

フォルシアでは、2016年頃までサーバに環境を構築する際にはシェルスクリプトを利用し、ミドルウェアをソースからビルドすることでサーバの基礎部分を構築していました。

非常によくできたシェルスクリプトではありましたが、

  • OSやミドルウェアのバージョンによる分岐やエラー時の考慮等を行うことでスクリプトの規模が大きく、また理解に時間を要する状態になっていた
  • 途中で何かしらの理由で失敗した際に、失敗した処理以外の部分にも堪能でなければ再実行をどのように行うかがわからなかった
  • 再実行に際してはある程度処理を実行しないようにスクリプトを変更する必要があった

など、利用しようと思ったときの障壁が少し高い状態にありました。

一方で、社内にVMを自由に立てられる環境を用意し、サービス環境を再現した各種テストやCIの実施等を容易にできるようにする流れがあり、実際のサーバの環境を行うだけでなく容易に実際の環境と同じものを構築できるようにする必要性が高まっておりました。

それらを受けて、シェルスクリプトに代わる新しい形で環境を構築できるようにしようという機運が高まり、当時流行っていたAnsibleを利用して環境構築をできるようにしようという話になりました。

導入にあたって工夫したこと

まず、ミドルウェアのビルドのための各種処理(ソースダウンロード ~ ビルド)や利用しないdaemonの停止などの、実施事項はアプリケーション(サービス)間では基本的にかわらないため、DBのみを持つサーバがあることやPostgreSQLは導入するがpg_bigmは導入しないといった事例にも対応できるように、各ミドルウェアやextensionといった単位でAnsibleのroleに分割して作成しました。

加えて、導入するミドルウェアのバージョンや導入先のパスや停止するdaemonの対象など、roleの利用に際してアプリケーション間で異なり得るものについては、varsとして変数化し、フォルシア標準としての各変数のdefaultの設定値を記載したvarsファイルを用意しました。

共通のrole daemonの(tasks/main.yml) イメージとdefaultのvarsを記載したファイルのイメージ

- name: stop daemon
  service:
    enable: no
    state: stopped
    name: "{{ item }}"
  loop: "{{ daemon }}"
middleware_version: 1
daemon:
  - crond

これにより、環境構築を行いたい人は

  • 対象サーバに必要なroleを選びplaybookに記載
    • その際にdefaultのvarsが書いたファイルを読み込んでおく
  • defaultのvarsと異なる値を設定したいもののみをvarsファイルとしてdefaultのvarsファイルの後に読み込む

という2つを実施するだけで、Ansibleのmoduleの知識などなくとも、構築ができるようになりました。

利用者が作成するplaybookとvarsファイルのイメージ

- hosts: all

  vars_files:
    # defaultのvars
    - (path to)/default-vars.yml
    # 利用者のvars
    - (path to)/user-vars.yml

  roles:
    # 共通のroleを読み込む
    # 利用者はroleの名前を見て名前の部分だけをかえれば良い
    - role: (path to)/common-roles/middleware
    - role: (path to)/common-roles/daemon
# 利用者が上書きするもののみを設定
middleware_version: 3

リリースにAnsistranoを導入した話

導入までの話

フォルシアでのリリースの方式は各アプリケーション(サービス)によって若干異なりますが、大きく分けて2つの方式がありました。

  • フォルシア独自のツールを利用してリリースを行う
  • repositoryをサーバ上に配置しpushによってリリースを行う

前者のツールは、webアプリケーションで、ブラウザ上からサーバ上とローカル上のファイルでdiffを持つものがひと目でわかったり、サーバ間でもdiffを持つものが判別できたり、果てはそのdiffを画面上で参照できたりと非常によくできたツールではありましたが、

  • アプリケーションを動かすまでの設定が少し大変である
  • 細かく設定できる分、設定ファイルに反映するすべてのファイルの記載をする必要があるため、特に最初の設定ファイルの作成に非常に苦労する
  • (利用者のモラル的な問題だが)ファイルごとの反映ができるため、repositoryの特定のrevisionとサーバ上の状態とが一致しないという事象が発生する
    • 今どの状態が反映されているのかがわからなくなる

ということがありました。後者については、前記のようなことは起こりませんが、

  • フォルシア管理でないサーバ(そのようなサーバもある)にrepositoryのcommitの履歴など含めてすべて配置するのは良くない

ということがあり、どのようなサービスでも利用できる新しい方式が求められておりました。

折しもサーバ構築にAnsibleを利用していたこともあり、AnsibleでリリースができるAnsistranoを利用しようという話が持ち上がりました。

導入にあたって工夫したこと

Ansibleでの環境構築と同様に、利用者ができるだけ知識を必要とせず、少し特殊なことをしようとした場合でもvarsの変更により対応できるようにという点を最優先に考え、アプリケーションをリリースするもの、ミドルウェアの設定ファイルをリリースするものなどを分割して記載しようと考えました。

ただ、Ansibleと同じようにできないこととして、Ansistranoはroleとして利用するものであるため、各種リリースを行うものをrole単位で用意してやって、必要なもののみを利用者がplaybookに記載するという方式が取れません。また、アプリケーションによってはリリースする前にビルドするといった前処理を必要とするものがあるため、簡単なplaybookとvarsファイルを定義するだけで利用できるようにするにはどうしたら良いのかという点は悩みどころでした。

最終的には、Ansibleのimport_playbookの機能を活用し、こちらで用意するのをroleではなくplaybookとし、そのplaybookも、localhostに対してビルドなど各種処理を実行するplaybookとそのビルドをしたものを対象のサーバにリリースするplaybookとを分けた上で、その両方のplaybookをimportしたエントリーポイントとなるyamlファイルを用意するという手段を取りました。

また、このplaybookの中でAnsibleでの環境構築のようにdefaultのvarsファイルを読み込んだ後に予め用意した特定のvarsファイルを読み込むようにしておきました。

エントリーポイントとなる(例えばアプリケーションの)ファイルmain.ymlのイメージ(これをimport_playbookする)

import_playbook: local_playbook.yml
import_playbook: deploy_playbook.yml

localhostに対してビルドなどを実行するplaybook

- host: all
  vars_files:
    - (path to)/default-vars.yml
    - (path to)/user-vars.yml

  tasks:
    # localhostに対して実施するtask
    - import_tasks: local_tasks.yml
      delegate_to: 127.0.0.1

deployをAnsistranoを用いて(例えばアプリケーションを)リリースするplaybook

- host: all
  vars_files:
    - (path to)/default-vars.yml
    - (path to)/user-vars.yml
  vars:
    ansistrano_deploy_from: "{{ ansistrano_app_from }}"
    ansistrano_deploy_to: "{{ ansistrano_app_to }}"

  roles:
    - ansistrano.deploy  

これにより、利用者は、

  • 用意されたplaybookをimport_playbookにより読み込むようなplaybookを作成する
    • sample playbookを作っておけばその記載にならって必要なplaybookをimportするのみ
  • 各playbookの中に使われている変数での中でdefaultと異なる変数を特定のパス上に配置したvarsファイルに書き込む

という2つを実施するだけで、AnsibleやAnsistranoの知識がなくてもリリースができるようになりました。

利用者が作成するplaybookファイルのイメージ(varsはAnsibleのときと同じような形になるため省略)

- import_playbook: (path to)/app/main.yml
- import_playbook: (path to)/middleware_setting/main.yml

おわりに

Ansibleを使うというところからAnsistranoも含めて実際にサービスで利用するまでにおよそ3年弱くらい(2016年 ~ 2018年)かかりました。その期間にそればかりをやっていたわけではないですが、皆に利用してもらうためにはどのような仕組みにするのが良いのかを検討することやそれを実装することなど、それなりに時間を要しました。

多くの社員に使ってもらうための布教活動やサポートを行い、2020年を目の前にした今、プロジェクトに関わってない社員たちにも使われ始めております。

まだまだ拡張していったり充実していったりするところはあり、日々改善していっている状態ではありますが、Ansibleを触ったり環境構築をやったりしたことのなかった私でも、様々な社員からのサポートとそのような機会を許容してもらったことで、ここまで形にすることができました。素晴らしい会社です、フォルシア。

今回はAnsibleとAnsistranoを使い始めた経緯や利用方法についてそのエッセンスのみを記載しました。この記事の反響が大きければそれぞれの切片の詳細を語る続編があるかもしれませんが、それはこの記事を読んでいる皆さん(というかFORCIA CUBE編集長?)次第ということで。

この記事を書いた人

滋野井 雅人

2015年新卒入社のエンジニア。大手旅行会社のシステムを担当。
雪でもふらない限り通勤だけだったらコートはいらないと思う。