現場で使うansible

弊社でのansible活用例について記事にしてみました。
本記事は弊社での運用例なので、どの組織にも適用できるかは分りませんが、何かの参考になれば幸いです。

目次

リポジトリの作り方

最初に、ansibleのリポジトリをどのような方針で作っているかを書きます。

2種類のリポジトリを作成する

まず作業のためにansibleのplaybookを保存するリポジトリを作ります。
弊社では以下2種類のリポジトリを作っています
(弊社ではGitを使用しています)

  • serverリポジトリ
  • roleリポジトリ

serverリポジトリはあるサーバをセットアップするための実体です。
これは以下のような名前だったりします(ここでは便宜上すべてのリポジトリ名の前にansible_をつけています)

リポジトリ例

  • ansible_hoge-server ・・・hoge-serverのセットアップ手順
  • ansible_huga_app-server・・・huga_appを動かすためのセットアップ手順
  • ansible_convert_piyo・・・piyoデータをコンバートするための手順

serverリポジトリは作業だったり、手順だったりします。ansibleを実行する際はこのプレイブックを実行することになります。

roleリポジトリはroleという単位で再利用可能にしたリポジトリで、serverリポジトリにgit submoduleを利用することで取り込み利用します
roleの使い方についてはansible-galaxyと同様です。

またここでは説明しませんが、Moduleを格納するlibraryディレクトリもroleリポジトリ同様submoduleで取り込みます。

利用例

例を挙げます今hoge-serverというサーバの設定をしたいと思います
hoge-serverでは以下の設定をすることとします

  • hoge-userを作る
  • hoge-user権限でrbenvをインストールする
  • mysqlをインストールして、hogedbというデータベース、ユーザを作る

serverリポジトリとroleリポジトリは以下が存在するとします

https://github.com/search?q=user%3Ayaasita+ansible

  • serverリポジトリ
    • yaasita/ansible_hoge-server
  • roleリポジトリ
    • yaasita/ansible_role_rbenv
    • yaasita/ansible_role_mysql

リポジトリ画像

まず、serverリポジトリを作成します

git clone https://github.com/yaasita/ansible_hoge-server.git
cd ansible_hoge-server
# 必要なファイルを作成します
mkdir group_vars roles
touch ansible_hosts site.yml group_vars/all

次に再利用できそうなroleを取り込みます

cd roles
git submodule add https://github.com/yaasita/ansible_role_mysql.git mysql
git submodule add https://github.com/yaasita/ansible_role_rbenv.git rbenv

rbenvのインストールとmysqlのセットアップは既存roleを使いまわせば出来そうですが、hoge-userの作成は既存のroleではできそうにありません。(ansible-galaxyを探せばありますが、ここではrbenv,mysql以外のroleは無いと想定しています)
そのためhoge-serverリポジトリに固有のroleを作っておきます

ansible-galaxy init hoge

またrole内defaultディレクトリに存在する変数は上書きできるのでgroup_vars/allで設定します

cd ../
vi group_vars/all

group_vars/allの設定は以下のようにしました

# mysql
mysql_dbname: hogedb
mysql_dbuser: hogedb
mysql_dbpass: hogedb

# rbenv
rbenv_user: hoge-user
# versionはroleデフォルトのものを使う

作業後こんな感じの構成になるはずです
(すいませんplaybookの細かい書き方は割愛します 作成後のリポジトリはこちらを参照下さい)
リポジトリツリー画像

あとは実行します

ansible-playbook -i ansible_hosts site.yml

ここまでが一連の流れです

ポイントは以下の通りです

  • roleリポジトリ、serverリポジトリを分けて利用する側利用される側を明確にする
  • roleリポジトリの取り込みはgit submoduleを利用する
  • roleで変更可能な変数はroleリポジトリのdefaultsに記述し、serverリポジトリのgroup_vars/allで設定する
  • roleで対応できないような固有の設定はrole/hoge等のroleをserverリポジトリに直接作る

git submoduleは特定コミットへの参照を保持しているのでバージョンロックと同じ効果があります。
このため、roleが非互換なアップデートにより急に動かなくなったという事態を避けることができます。

git subtreeを使わない理由は、serverリポジトリで作業しているときにroleもちょっと修正することが多いため、コミットした後でも修正したことがgit statusで分りやすい & pushしやすい(と思う)のでsubmoduleを使用しています。
ここら辺はお好みでOKかと思います。

要は「再利用しやすい仕組みを作る」「再利用する単位(role)がバージョンアップしても困らないようにする」ことを実現できれば、手段については組織に合わせて修正して頂ければ良いかと思います。

playbook作成規約

規約というほどのことでもないですが以下の規約に則ってplaybookを作成するようにしています。

READMEに以下のことを書く

  • 実行すると何が起こるか
  • どこで動くか(サポートするプラットフォーム)
  • 設定できる変数と意味
  • playbookの依存関係

サンプル

設定できる変数はroleリポジトリのdefaultsに書く

変数を上書きしにくくなるのでなるべくvarsディレクトリはなるべく使わないようにしています

roleリポジトリの変数は role名 + _(アンダーバー)で始める

こんな感じに設定します

# rbenv
rbenv_user: yamasita
rbenv_install_ruby_version: 2.1.2
rbenv_default_ruby_version: 2.1.2

serverリポジトリのgroup_vars/allで一括して設定するため、どのroleに対する設定なのか分るようにしています

運用時のコツ

運用していて気を付けていることについて書いておきます。
これはansibleに限ったことでは無いのですが(chef,puppetでも発生する可能性はあります)、ついでなので書いておきます。

ansibleで設定したサーバをどう管理するか

弊社ではansibleで設定したサーバはansible以外での変更は絶対禁止!というような運用はしていません。
想定する環境にもよるのでしょうが、手で変更した方が良いと判断できる場合はそちらでサーバ設定を変更することはあります。

「設定変更はansibleでのみ許可する」という運用だと構成情報がコードとして表されるメリットがありますが、以下の点に注意して下さい。

今、ansible-app(ver1)というserverリポジトリを適用したapp01というサーバがあります。

以下のような状態になります

ansible1

この時、ansible-app(ver1)を修正してansible-app(ver2)にしてまた適用したとします

ansible2

さてこの構成と同じ設定のapp02を作ろうとします

ansible3

この結果,app02はapp01と同じ状態になるとは限りません。
(パッケージのバージョンが異なる可能性については考慮していません(説明は後述))

具体例を示すと、以下のようなタスクだったとします

ansible-app(ver1)

- name: mysql関連必要パッケージ
  action: apt name={{item}}
  with_items:
    - mysql-server
    - python-mysqldb
    - libmysqld-dev

- name: DB作成
  mysql_db: name={{ mysql_dbname }} state=present encoding=utf8

ansible-app(ver2)

# DB2作成を追加 ###################
- name: DB2作成
  mysql_db: name={{ mysql_dbname2 }} state=present encoding=utf8
###################################

- name: mysql関連必要パッケージ
  action: apt name={{item}}
  with_items:
    - mysql-server
    - python-mysqldb
    - libmysqld-dev
- name: DB1作成
  mysql_db: name={{ mysql_dbname }} state=present encoding=utf8

この場合、ver1 => ver2の順で適用した場合はうまく行きますがいきなりver2を実行してもうまく動きません(mysqlをインストールされていない状態でDBを作成しようとするため)

これは極端な例なので、「DB2の作成を最後にすればいいだけだろう」と思うのですが、複雑なplaybookになると初期状態から作成する場合の考慮を欠いたplaybookを書いてしまうことがあります。
(この手のツールの理想はサーバのあるべき状態を記述すれば、その構成に収束するという触れ込みですが、順序を意識しないといけない設定もあるので、中々そうは行かないのが実状だと思います)

対策として

  • serverリポジトリを修正する場合は、必ず最後のtaskに追記するようにする
  • serverリポジトリの修正はしないで、構成変更ごとに新しいserverリポジトリを作成する

というような方法があります。
弊社では後者の方法を取っています。
そのため、以下のように対象サーバ+日付+作業概要のリポジトリが作業ごとにつくられています

構成変更のためのserverリポジトリ例

  • ansible_hoge-server_20141210_create_db2
  • ansible_hoge-server_20141215_install_redis

もちろん、しっかりとテストできる環境があり、いきなりver2を実行してもver1 => ver2と実行した状態と差異がないことを保証できるならそちらでも構わないと思います。
(deployのたびにコンテナを入れ替える等、immutableな方はこちらの方がいいかもしれません)

いずれにせよ、ansibleで一度構築したサーバの構成管理をどう運用していくかはチーム内で話し合って方針を決めた方が良いと思います

汎用的にしようと無理をしない

汎用的なroleを作成しようとすると以下のようになることがあります。

- name: debianでlocal設定
  include: locale-debian.yml
  when: ansible_distribution == "Debian"

- name: Ubuntuでlocal設定
  include: locale-ubuntu.yml
  when: ansible_distribution == "Ubuntu"

- name: Redhat設定
  include: redhat.yml
  when: ansible_os_family == "RedHat"

この程度ならまだマシなのですがこのroleにwindowsや他のUNIXが入ってくると条件分岐がさらに複雑になります。

複数の環境を想定しようとすると、可読性が悪くなり、テストの手間も増える可能性があります。複雑なroleで更新の手間がかかるなら、思い切って別ブランチか別リポジトリにした方が良いです。

例えば、CeontOS5はこれから増えることもなさそうなので、ブランチを切ってそちらで対応してもらうといった方針をとる場合があります。
branch

設定ファイルについても同様です。
例えばpostfixのmain.cfの設定を全て、ansible側で設定できるようにすることは大変です。
以下のように全ての項目をansibleで設定できるようにするのは現実的ではありません。

smtpd_banner = {{ postfix_smtpd_banner }}
biff = {{ postfix_biff }}
append_dot_mydomain = {{ postfix_append_dot_mydomain }}
readme_directory = {{ postfix_readme_directory }}
smtpd_tls_cert_file= {{ postfix_smtpd_tls_cert_file }}
smtpd_tls_key_file= {{ postfix_smtpd_tls_key_file }}
smtpd_use_tls= {{ postfix_smtpd_use_tls }}
smtpd_tls_session_cache_database = {{ postfix_smtpd_tls_session_cache_database }}
smtp_tls_session_cache_database = {{ postfix_smtp_tls_session_cache_database }}
myhostname = {{ postfix_myhostname }}
alias_maps = {{ postfix_alias_maps }}
alias_database = {{ postfix_alias_database }}
myorigin = {{ postfix_myorigin }}
mydestination = {{ postfix_mydestination }}
virtual_alias_maps = {{ postfix_virtual_alias_maps }}
relayhost = {{ postfix_relayhost }}
mailbox_size_limit = {{ postfix_mailbox_size_limit }}
recipient_delimiter = {{ postfix_recipient_delimiter }}
home_mailbox = {{ postfix_home_mailbox }}
canonical_classes = {{ postfix_canonical_classes }}
relayhost = {{ postfix_relayhost }}
smtp_use_tls = {{ postfix_smtp_use_tls }}
smtp_sasl_auth_enable = {{ postfix_smtp_sasl_auth_enable }}
smtp_sasl_password_maps = {{ postfix_smtp_sasl_password_maps }}
smtp_sasl_tls_security_options = {{ postfix_smtp_sasl_tls_security_options }}
smtp_sasl_mechanism_filter = {{ postfix_smtp_sasl_mechanism_filter }}
inet_protocols = {{ postfix_inet_protocols }}
relay_transport = {{ postfix_relay_transport }}
...以下省略

あまり変更しない設定は、決め打ちのtemplateでもいいでしょう。
特殊な設定の場合、serverリポジトリ固有のroleにfiles/main.cfとして作成しそのままリモートへコピーするという方法もあります。

このように、汎用的にすると使いまわしが効くので便利になりますが、開発の手間が増えるため、どこかで妥協した方が良さそうです

roleの切り出し方について

どこまでをroleにすれば効率的になるかは、一概に言うことはできませんが、
roleへの切り出しは後でやった方がいいと思っています。

使えそうなroleが既にあるならその使用を検討すべきですが、無いならとりあえず、serverリポジトリ固有ロールに全部書いてしまい、後から検討するほうがうまくいく場合が多かったです。
(roleへの切り出しとplaybookの開発を同時平行でやると混乱するためです。他の人はうまくいくかもしれません)

弊社ではansible_role_mysql等のアプリケーション単位にroleを作成することが多いですが、ansible_role_bps-commonといった具合にどのサーバでも適用する会社共通設定を行うroleも存在します。

サーバのコピーに使わない

検証のためにあるサーバと同じ環境を用意したい場合にansibleのplaybookで同じ構成を再現するより丸ごとコピーした方が良い場合が多かったです。

以下はopensslをインストールする例です

- apt: pkg=openssl state=installed

この場合、時間が経ってからこのタスクを実行しても同じ結果になりません。opensslのバージョンが上がっている可能性があるためです。
セキュリティアップデート等のバージョンまで考えると完全に同じにするのは難しいです。
(特定のバージョンを入れる方法もありますが、煩雑になります)

また、該当のサーバが運用されているうちに変化するデータ(データベースやアプリにより作成されるファイル)を持っていた場合は別途コピーする必要が出てきます。

ansibleは当時の構築手順を再現することはできますが、それは運用されたサーバとまったく同じになるとは限りません。
あるサーバのコピーをとりたいなら、ansibleで行うよりスナップショットやファイルのバックアップからサーバを復元したほうが、早くて確実な場合が多かったです。
(余談ですが、弊社では検証環境を作る場合はバックアップファイルを丸ごとtarで固め,Dockerイメージにして検証しています)

スナップショット等が使えない、バックアップが存在しない、データサイズが巨大すぎる
等の事情がある場合は仕方がないかもしれませんが、
最初に検討すべきはスナップショットやバックアップファイルからの復元だと考えています。

よく分からないroleは実行しない

個人的にansibleのplaybookはその他構成管理ツールに比べ読みやすいと思っています。
ですので、ansible-galaxyのコードや他人が書いたコード等を利用する場合は必ずtaskに目を通した方が良いと思います。
よく分からないroleを実行すると後で管理できなくなります。

ansible-galaxyには多様なplaybookが登録されていますが、自分の欲しいplaybookが無い場合、現在の運用にそぐわない場合は参考程度にとどめておき、自分で作った方が良い場合もあります。

まとめ

弊社でのansible活用事例について記事にしました

組織やサーバの規模によっては大げさ過ぎたり、考慮が足りない部分もあるかとは思いますが、何かの参考になれば幸いです。
どのような運用方法にするかは偉い人が言ったベストプラクティスをただ鵜呑みにするのではなく、メリット、デメリットを考慮の上採用する方が良いかもしれません。

ansibleの導入によって弊社では以下のメリットがありました。

  • 構築手順書が不要になった
  • 手作業が減ったことによるミスがなくなった
  • roleによるサーバ設定作業の再利用がされるようになったので全体的に環境構築にかける工数が削減された
  • (その他、構成管理ツールに比べて)コードを書いてくれるようになった(気がする)
  • (なぜか楽しい)

デメリットはansibleの構文を覚えなければいけないことだと思いますが、とりあえずshellモジュールさえ使えればすぐにでも使えるので敷居は低いのかなと感じています。
また、ansibleはsshd(とPython)さえ入っていれば使えるのであまりansibleを使わない方との共同作業もうまくできたと思っています。
自社の運用事例や、もっと良い運用方法をご存知でしたら是非教えて頂きたいです。

以上です。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

yamasita

東京電機大学工学部→3年間某SIerにて銀行システムの開発→bpsに入社

yamasitaの書いた記事

インフラ
検証環境の作り方

2014年08月14日

週刊Railsウォッチ

インフラ

Rubyスタイルガイドを読む

BigBinary記事より

ActiveSupport探訪シリーズ