Ansibleおじさん化
ここ半年ほど、やたらとAnsibleばかり書いています。プロダクトコードは全く書いておらず、完全にAnsibleおじさんと化しています。
主に環境構築を行うためのPlaybook/Roleを作成しています。
色々やる間に何回か大きな方向転換を行い、なんとなく軌道に乗ってきた感があるので、一度自分の考えを整理するのと合わせて書いてみます。
以降では、以下のような前提に立って記載しています。
- Ansible 2.4
- Ansibleを実行するhostはAmazon linux
ディレクトリ構成
ディレクトリ構成は、基本的に Ansibleのベストプラクティス に従っています。ただ、厳密にやると辛いので、実際には以下のようにしています。
├── ansible.cfg
├── decrypt.sh
├── encrypt.sh
├── hosts
├── playbooks
│ ├── common.yml
│ ├── ap-server.yml
│ ├── database.yml
│ ├── deployer.yml
│ └── batch.yml
├── requirements.txt
├── roles
├── run-test.py
├── site.yml
└── test
playbooksに置いているplaybookは、全てsite.ymlにinclude_playbookされており、実際に実行する場合は以下のようになります。
$ ansible-playbook -i [対象ホスト] site.yml
また、原則として全部のtaskにtagを設定しているので、 -t
を利用して、特定のtask/roleだけ実行できるようにしています。
group_varsの戦略
今いるプロジェクトは、本番・検証環境がかなり多いという特性があります。そのため、一ファイルに全部書く、というのは結構厳しいです。
現状は以下のような構造にしています。(hosts以下は前述と一緒です)
├── ansible.cfg
├── decrypt.sh
├── encrypt.sh
├── hosts
│ ├── prod
│ │ ├── prod1
│ │ ├── prod2
│ │ ├── prod2
│ │ └── prod2
│ └── verify
│ ├── verify1
│ ├── veirfy2
│ └── verify3
├── playbooks
│ ├── common.yml
│ ├── ap-server.yml
│ ├── database.yml
│ ├── deployer.yml
│ └── batch.yml
├── requirements.txt
├── roles
├── run-test.py
├── site.yml
└── test
こういう構成にすることで、以下のような効果を狙っています。
- 環境毎のinventoryを管理できる
- 環境毎、group毎の変数管理が(ある程度)わかりやすい
- 似た環境でもあえて分けることで、環境毎に変更内容が独立させられる
- group_vars以下がいい感じに肥大していくのとトレードオフですが。。。
vaultを使う
Ansible自体はそれなりに利用していましたが、ansible-vaultは利用したことがありませんでした。ただ、今回は利用しないとむしろ管理が面倒になりそうでした。
- 環境が多い上に、諸事情でRDBのパスワードをAnsibleで持っておく必要がある
- SSHでのアクセスが公開鍵ではなく、パスワード認証(・・・)
- いくつか作成するユーザーのパスワードを保存する必要がある
ansible-vaultの対象ファイルを増やしすぎると、変更時とかに面倒+時間がかかるので、以下のように(しようと)しています。
# secrets.yml
---
secrets:
user_secret_key: hogehoge
# main.yml
user_secret_key: '{{secrets.user_secret_key}}'
roleのdefaultsに全部のvariableを書く
roleの内、何らかのvariableを渡す必要がある場合、 必ず defaults/main.yml にデフォルト値を記載するようにしています。
- roleで利用する変数は
defaults/main.yml
を見ればわかる -
defaults/main.yml
に説明を書くことで、将来のドキュメントにもなる
後、roleで利用するvariableは、 必ずrole名をprefixにする というルールにしています。基本、metaで依存を設定しているroleを除いて、変数のoverrideを防ぐ目的でこんなルールにしています。
プロジェクト外で共有するようなroleでは無いため、わざと暗黙的な依存を作らないように、できるだけそれぞれで独立するようにしています。無論、実質的に依存が存在するroleもありますが、それはplaybookで明示的に記載することでカバーしています。
ansible-playbookはJenkins(とか)から実行させる
Ansible自体の実行は、個々人のPCからは行わないようにしています。(role/playbookの作成中は当然除く)
ではどうするか、ということでみんな大好きJenkinsおじさんの登場です。Jenkinsにansible-vaultのpassword fileを配置し、Githubから対象のリポジトリをpullしてansible-playbookを実行させるような感じにしています。また、ビルドパラメータとPipelineを使って、以下のようなことをできるようにしています。
- stage(本番・検証)を選択できるように
- 対象の環境を選択できるように
- 実行するtagを指定できる。Pipelineのinputを使って、実行対象のtask一覧を確認してから実行させるようにしている
基本みんなWindows PCということもあり、変に辛い思いをするくらいなら、Ansibleの実行はJenkins!とすることで、誰でも実行できる+実行履歴を見られるという効果がありました。
もっとちゃんとやるんであれば、Ansible Towerなどを導入したほうがいいんではないか・・・とは思います。ただ、現状は環境構築くらいしか利用していないので、これくらいでもなんとかなっています。
Dockerでroleのテストを行う
Ansibleでは、公式でdockerのconnectionを行うためのpluginが用意されているので、これを利用して、Docker + Amazon linux imageでテストを実行できるようにしています。
---
- hosts: "[コンテナの名前]"
connection: docker
gather_facts: False
roles:
- role: ...
Dockerコンテナは、事前に起動しておく必要があります。dockerモジュールを利用すれば、テスト用のplaybookだけで完結すると思いますが、後始末とかが面倒なので、スクリプトで実行しています。
こんな感じでroleを実行した後、 Testinfra を利用してテストケースを流せるようなスクリプトを作成しています。
実際にやってみると、結構ちゃんとテストできます。Ansible自体がべき等であることを求めてくるので、テストもさっくりと書くことができます。
ちなみに、 Serverspec ではなくTestinfraを利用しているのは、現状Rubyを利用するのがNGになっているためです。(PythonはOK)
改善の道のりは長い
それなりに色々とやっている感じですが、個人的にはまだまだ満足していません。というか、Roleの書き方に統一感がないとか、暗黙知になってしまっている部分が多いので、そのへんの改善が必要です。
しかし、環境構築をAnsibleに全振りし、Jenkinsから実行できるようにしたおかげで、
- 誰かがいないと動かせない、ということが激減
- 新しい環境が増えても、group_varsだけ確認すればOK
という感じになりました。プロジェクトの特性とアプリの作り上、環境構築がやたらと多いので、これは助かった感じになっています。
しかし、まだ手作業のぬくもりが残っている部分があるので、そういった部分も巻き取っていきたいと思います。
参考サイト
Laying out roles, inventories and playbooks