この記事はAnsible Advent Calendar 2016 - Qiitaの24日目の記事になります。
Ansibleで個人開発環境を構築して、一通り使ってみて感じたことを残しておきます。
基本的に自分のメモ程度なので、何かあったらコメントお願い致します。
ベスト・プラクティス
取り敢えず読んでおきます。
更新されているので、たまに読み返すと新しい情報が追加されていることがあります。
Best Practices — Ansible Documentation
ディレクトリ構成
ベスト・プラクティスで下記2つが紹介されています。
- Directory Layout
- Alternative Directory Layout
qiitaでも別解として話題に上がっています。
どれがいいかはプロジェクト次第です。
作り始める前にどれがいいか検討すべきですが、決められなければ、
とりあえず、ベスト・プラクティスのDirectory Layoutで始めてみてあとで構成変えるでもいいと思います。
ちゃんと設計する
最初からロールを分割しすぎない
「早すぎる最適化」はよくないです。
ロール名を変数名の接頭辞にするのが慣例なので、
タスクを別のロールに移動するとか考えるとリファクタリングコストがそこそこかかります。
同じミドルウェアのタスクでも分割したほうがいいタスクがある
例えば、MySQLのロールの中で
をすべて一つのロールでやらないほうがいいかもしれないです。
Playbookにした時に、ユーザーの作成とインストールまで行って、
何か別のタスクを実行した後に、データベースの作成という流れにしたいときに困ります。
共通で使う変数
各ロールやPlaybookで共通で使用したい変数名の命名規則は最初に決めておいたほうがいいです
後から変更するとそこそこリファクタリングコストがかかります。
部分実行しやすいようにする
Playbookの開発中に毎回毎回すべてのタスクを実行していては効率が悪いです。
--start-at-task
や --tags
を使えるようにしておくと再実行しやすいので時短につながります。
なので、タスクやロールにtagはつけておいたほうがいいです。
上記オプションをつけて実行するとき、pre_task
は実行されないっぽいので注意が必要です。
冪等性をどこまで担保するか
なんでもかんでも冪等性を担保しようとすると辛い場面が出てきます。
ドライラン対策
確認のために -C
をつけて実行することがあると思います。
しかし、一部のモジュール(commandやshell)はこのドライランに対応していないです。
冪等性を担保するなら自分でどうにかする必要があります。
不要になったタスクをどこまで面倒みるか
例えば、Vagrantで各自の開発環境を用意するとして、そこに流すPlaybookの場合を想定します。
前のバージョンでは作っていたけど、いらなくなったディレクトリが出てきたとき、次のバージョンで削除するタスクを書いておくのか?
という問題が出てきます。
前のバージョンを実行していたメンバーが次のバージョンを実行したときに
そのいらなくなったディレクトリを削除するためだけに タスクを残しておくと本来必要のないコードが残っている状態になります。
コードの見通しが悪くなるので、タスク自体消したほうがいいですが、いつ消すかはちゃんと考えておかないとメンバーに迷惑がかかることもあります。
モジュール一覧を一通り眺めてみる
意外と細かいことをやってくれるモジュールもあるのでcommand
モジュール使う前に、
Ansibleのドキュメントのモジュール一覧を見てみるとよいです。
apt_key
とか apt_repository
とかcommand
でやりがちなモジュールもあったりします。
Vagrant環境での注意
ディレクトリのオーナー、パーミッション
Ansibleの問題ではなくVagrant(VirtualBox)の使い方の問題です。
ホストOSのパーミッションはゲストOSでは変えられないことを念頭に置きましょう。
ディレクトリのオーナーやパーミッションを変えるようなタスクを作るとき
ホストOSとの共有ディレクトリは、Vagrant側から流したPlaybookでは変更できません。
Ansibleの実行結果はchengedとなりますが、実際には変わりません。
個人開発環境を作るときなんかは、ソースは共有ディレクトリに置くことが多いと思うので
その辺意識しておかないと、なんで変わってないんだとなってはまってしまうので注意です。
例えば、ソースを置くディレクトリは、 wwwをオーナーにしたい場合を考えます。
Vagrantfileでディレクトリ共有の設定でwwwを指定すれば、ゲストOS側でそのオーナーになります。
しかし、そこに指定するユーザーは、Playbook流す前は存在しない場合、
1つのPlaybookだけで解決しようとするとなかなか難しい状況に遭遇します。
ユーザーやグループの追加はプレイブック分けたほうがいいかもしれません。
vagrant up
だけで環境整うと嬉しいけど、そこまでいくにはいろいろ考えないといけないことが多いです。
saharaを使う
Playbookの開発を行うときにVagrantを使うなら sahara
というプラグインを使うとよいです。
Playbookを流しては、失敗して、修正し、もう一度流す。みたいなことを繰り返します。
ある程度動作が固まってきたときに、ゲストOSをまっさらな状態にしてから流し直すことをやると思います。
そんなときは sahara
が便利です。
他にも同じようなことができるプラグインはありますが、
ゲストOSの状態をバージョン管理したいとかでなければsahara
で十分です。
Ansibleの流し方を考える
Vagrantでは、ゲストOSに対してAnsibleを実行する方法が幾つかあります。
Vagrantのプロビジョナを使う場合は、Shell
, Ansible
, Ansible Local
のどれかを使うことになるでしょう。
どれがいいかはプロジェクト次第だと思うので、一概に言えません。
私は、 Shell
を使用して、Ansibleのインストールのみを行い、
Playbookの実行はVagrantに入って、手動でやる方法を取りました。
Playbookの開発中は何度も実行するし、どう変わったかの確認もするため、結局sshするからです。
そもそも開発メンバーの中に、Windowsの人がいたので、Ansible
は選択できませんでした。
テストコード
Ansibleに限った話ではないですが、考えておいたほうがいいです。
Ansibleのタスクに対してテストコードを書く場合、今ならServerSpecを選択することが多いと思います。
そこまで大きなプロジェクトでないなら、書かないほうがコストやすいことのほうが多いかもしれないと感じました。
書くなら書き始める時期もちゃんと検討しましょう。
実行ユーザー
- remote_user
- become_user
はちゃんと運用時のことを想定して考えておきましょう。
Vagrant環境だとvagrantだけでいろいろいけちゃいますが、Vagrant以外の環境のこともちゃんと考えないと後が大変です。
他のサーバーにsshするとき(git cloneなど)にどの秘密鍵を使うのか、
その秘密鍵はどうやって管理するのか
はたまた、エージェント転送で解決していくのかも先にある程度考えておくといいと思います。
becomeの単位
Playbook単位でbecome: Trueとするかタスク単位で切り替えて行くのかは
上記実行ユーザーと一緒に考えておきましょう。
変数の分割
apache_document_root:
name: www
owner: www
group: www
mode: 0755
みたいなハッシュが defaults
にあったときに
apache_document_root.name
だけ上書きしたい時に困ります。
Ansibleはデフォルトでハッシュを置換するので、別のところで
apache_document_root:
name: foo
とかできないです。 ansible.cfg
で置換ではなくマージに変えることもできますが、
全体的な影響(自分が中身をよく知らないロールとか)を考えたときに、他は大丈夫なのかという問題が出てきます。
あと、Ansibleをよく知らないメンバーが、マージがデフォルトだと思ってしまうこともあるかもしれません。
ansible-galaxyで管理するロールの配置場所
これは正解が分かりませんでした。
前提として下記の条件があったとします。
デフォルトだと、システムグローバルに置かれますが、それはしたくないです。
それぞれのプロジェクトで、同じロールの違うバージョンを使う可能性があるからです。
require.yml
で、個別の名前をつければ対応できると思いますが、しっくりこないです。
結局、下記のような運用にしました。
ansible.cfg
の中でインストール場所に./roles
を指定する- require.ymlの中で、インストールするの名前を
role-xxx
というような命名規則にする .gitignore
でroles/role-*
追加して、galaxyで管理しているロールは除外する
Ansibleのソースを読む
Ansibleのエラーメッセージはあまり親切ではないので、よく分からないことが多いです。
どうにもならないエラーに出会ったら、Ansibleのソースを読んでみることをおすすめします。
homebrewで入れたなら下記辺りに入っているはずです。
/usr/local/Cellar/ansible/2.1.0.0/libexec/lib/python2.7/site-packages/ansible/modules
ここに core
と extras
というディレクトリがあり、それぞれの中にカテゴライズされたモジュールがあります。
モジュールが core
か extras
のどっちに属するかは公式ドキュメントに書いてあります。
例えば、gitモジュールだったら、ここの下の方に This is a Core Module
と記載されています。
もしくは、モジュール一覧で (E)
がついていれば、 extras
扱い
gitモジュールは Source Control Modules
にカテゴライズされているので
/usr/local/Cellar/ansible/2.1.0.0/libexec/lib/python2.7/site-packages/ansible/modules/core/source_control/git.py
がソースになります。
Tips
jinja2のフィルターを活用する
例えば、標準出力の整形には from_json
や from_ini
が結構便利です。
自分で冪等性を担保したいときなど、一旦設定を情報などを確認すると思うのですが、
標準出力に出た内容をAnsible(jinja2)で使えるハッシュに直すときなどによく使いました。
group_varsはイベントリファイルの書き方で意図しない動きになる
- group_vars
- web.yml
- db.yml
- batch.yml
というような構成になっているときにそれぞれで、 common_main_user
という変数を定義しているとします。
かつ、hosts(イベントリファイル)の中で、下記のようにすべて同じものを指している場合
[web]
localhost
[db]
localhost
[batch]
localhost
ansible-playook -i hosts db.yml
と実行しても、 多分、期待通りに動かないでしょう。
common_main_user
の中身が db.yml
に指定したものとは限らないからです。
group_vars
の中を探しに行く際に、 マシン名をベースに、最初に見つかったグループを取得するようです。
この例だと、多分、 batch.yml
に指定したものが入ってくると思います。
Vagrantで開発しているときなどは、全部を localhost
などにしがちですが、これをやるとハマることがあります。
意図したとおりに動かしたい場合、
開発中なら一旦他のグループのlocalhost
をコメントアウトしておくとか、xip.io
を使うとかしたほうがいいと思います。
blockinfile便利
tipsでもなんでもないですが、よく存在を忘れるのでメモ。
blockinfile
モジュールを使うとファイルへの追記などが楽になります。
使うときは marker
を指定しておいたほうが無難です。
LinuxユーザーのIDの固定はそれなりに考える必要がある
データベースサーバーでマスタースレーブ構成を取りたいときは、サーバーを複数台用意すると思います。
その時、それぞれのサーバーでユーザーのIDを固定化しておきたいという要望があったとします。
user
モジュールでID指定をすれば固定できますが、よく考えてから固定化するしたほうがいいです。
sudo apt-get install mysql-server
などとパッケージ管理からインストールすると
mysqlのユーザー、グループも一緒に作られます。そして、mysqlのサービスも起動された状態になります。
ユーザーのIDを変更するには、そのユーザーでサービスが起動していてはいけないので、一旦サービスを止める必要があります。
なので、例えばこんな感じのタスクになる。
- apt: name=mysql-server state=present
- service: name=mysql-server state=stopped
- user: name=mysql uid=9999
本番環境でこんなのは流せないです。
先にユーザーを作ってしまうという手もありますが、
例えば、redis-server
は、redisというユーザーがすでに存在するとaptによるインストールが失敗します。
なので、ユーザーのIDの固定化はタスクとかロールとかがごちゃごちゃしがちになるので、本当に必要かどうか考えたほうがいいです。
Ansibleはshellでコマンドを流しているわけではない
Ansibleを触り始めた頃に思い込みをしていたので、メモとして残しておきます。
Ansibleはsshで他のサーバーに接続してタスクを実行していきますが、各タスクのshellのコマンドを実行するわけではないです。
例えば、gitのクローンは
git clone git@github.com/foo/bar.git
をshell上で実行しているのではなく、上記を行うpythonのスクリプトを送り込んで、そのスクリプトを実行しています。
エラーが起こったときなど、実際どのようなコマンドが発行されたかをつい知りたくなりますが、
コマンド履歴などに残っているようなshellのコマンドを探そうとしても見つかりません。