公式ベストプラクティスを参考に、Ansibleを1から学んでつくってみました

初めまして。山﨑です。
社会人歴は長いのですがSE歴は短く、その短いSE時代にもIDEに頼りっぱなしだったため、入社当初には、早速ローカル環境の構築に1日以上を費やす事態に陥りました。
もちろんインフラなど未知の世界でしたが、そんな私がゼロベースでAnsibleに取り組み、ある程度形にすることができたので、その経験をお伝えしたいと思います。

はじめに

最近、私の所属するチームでは Amazon EC2 Auto Scaling の導入が行われました。
Auto Scalingとは、トリガーとなる起動済みサーバの負荷が高まると、あらかじめ設定したAMIを元に自動的にスケーリングしてくれるサービスです。
そこで、起動済みサーバとAMIの設定を同一に保つことが必要になります。

今回私は、Ansibleを用いて起動済みサーバに設定を適用したのち、Packerを用いて同一の設定を反映したAMIを作成する、という一連のフローを自動化する業務を担当しました。
その内容を2回に分けてお伝えしたいと思います。
今回は、起動済みサーバに設定を反映するAnsibleを、公式のベストプラクティスを参考に作ってみたので、その過程で得た情報をまとめます。

目次

  • 本記事の主旨を3行でまとめてみると
  • 環境について
    • バージョン
    • サーバ構成
  • 成果物
    • ディレクトリ構成
      • 公式ベストプラクティスの説明
      • 考慮したポイント
    • 設定について
      • インベントリファイル
      • gruop_vars
      • playbooks
  • その他、Ansibleやってて便利だなと感じたTipsのご紹介
    • ローカル環境にグループを適用させたいとき
    • Ansibleの複数のバージョンを同居させたいとき
  • 参考にしたサイト
  • 最後に

本記事を3行でまとめてみると

  • 公式が提示するAnsibleベストプラクティスに則ったディレクトリ構成を考えました。
  • 複数のグループをグループでまとめる機能を利用して、変数やタスクを一元的にまとめるようにしました。
  • その他、Ansibleで便利だなと感じたTipsなども共有します。

環境について

バージョン

サーバ構成

  • 4種類の異なるロールを持つサーバがあります。
  • サーバ環境は2種類用意されています。 f:id:AdwaysEngineerBlog:20190411194000p:plain
ロール(役割)
- web           # WEBページを管理するサーバ
- worker-001    # バックグラウンド処理を非同期に担うサーバ。worker-002とは異なる役割を担う
- worker-002    # バックグラウンド処理を非同期に担うサーバ。worker-001とは異なる役割を担う
- batch         # バッチ処理を管理するサーバ
ステージ(環境)
- production   # 本番環境
- staging      # ステージング環境

成果物

最終的には下記のようになりました。

ディレクトリ構成

.
├── ansible.cfg
├── group_vars          # 変数定義ディレクトリ。名称は`group_vars`固定。グループ名でディレクトリやファイルを作成することができる。ファイルはYAML形式
│   ├── all             # グループを問わず参照されるファイル。ディレクトリの場合、配下のファイルはファイル名に関わらず参照される。
│   │   ├── main
│   │   └── usergroup
│   ├── web             # `web` グループ用の変数ファイル。対象ホストが`web`グループに属している場合、参照される。
│   ├── worker          # `worker` グループ用の変数ファイル。対象ホストが`worker`グループに属している場合、参照される。
│   ├── worker-001
│   ├── worker-002
│   ├── batch
│   ├── production      # `production` グループ用の変数ファイル。対象ホストが`production`グループに属している場合、参照される。
│   └── staging         # `staging` グループ用の変数ファイル。対象ホストが`staging`グループに属している場合、参照される。
├── playbooks           # タスク定義ディレクトリ。名称はなんでも良い。
│   ├── roles           # ちっちゃいplaybook。playbookにモジュールとして読み込むことで実行される。
│   │   ├── common
│   │   │   └── tasks         # タスク記述ファイルを格納するディレクトリ。main.ymlのみ自動で読み込まれる。
│   │   │       └── main.yml
│   │   ├── role1
│   │   │   ├── handlers
│   │   │   │   └── main.yml
│   │   │   └── tasks
│   │   │       └── main.yml
│   │   ├── role2
│   │   │   └── tasks
│   │   │       └── main.yml
...
│   ├── web.yml         # `web`グループに実行するタスク一覧。同一階層に属するrolesから必要なタスクを適宜読み込む。
│   ├── worker.yml      # `worker`グループに実行するタスク一覧。`worker`グループの2つの子グループ単位で作成した別playbookをインポートしている。
│   ├── worker-001.yml  # `worker-001`グループに実行するタスク一覧。同一階層に属するrolesから必要なタスクを適宜読み込む。
│   ├── worker-002.yml  # `worker-002`グループに実行するタスク一覧。同一階層に属するrolesから必要なタスクを適宜読み込む。
│   ├── batch.yml
│   └── site.yml        # すべてのplaybookをインポートし、順次実行する。
├── production          # production(本番)環境用のインベントリファイル。gruop_varsディレクトリと同一階層に配置する必要がある。
├── staging             # staging(ステージング)環境用のインベントリファイル。gruop_varsディレクトリと同一階層に配置する必要がある。
└── templates
    ├── logrotate.j2
    └── nginx.conf.j2

実行コマンド

$ ansible-playbook playbooks/worker-001.yml -i staging

上記の場合、グループはall, staging, worker, worker-001が適用されます。

公式ベストプラクティスの説明

  • production, stagingといったステージ別にインベントリファイルを作成します。
  • web, worker-001, worker-002といったロール別にplaybookを作成します。
  • group_varsディレクトリの配下に、グループ名でファイルやディレクトリを用意します。
    • group_varsディレクトリは、インベントリファイルと同一階層(inventory_dir)に配置される必要があります。
  • rolesの使用が推奨されています。rolesとは、playbookでモジュールとして扱える、playbookの小単位です。
    • rolesディレクトリは、playbookファイルと同一階層に配置される必要があります。

考慮したポイント

  • 変数はすべて1ディレクトリにまとめてグループ単位で管理することにしたため、ルートディレクトリ直下にgroup_varsディレクトリを作成しました。host_varsやvarsはもちろん、role/varsも一切作成していません。
    • これは、環境別・ロール別に複合的に変数を管理する必要があり、それ以上の分散管理が難しいためです。
      また、role/varsはgroup_varsより優先されるなど、Ansibleの変数の優先順位によっては予期せぬエラーが発生する可能性も高いと感じました。
  • 複数のグループに共通で用いられる変数は、当該グループをさらにグルーピングして、group_varsにて扱えるようにしました。
    • 変数のスコープを明示できるだけでなく、共通のタスクが1つのplayobookで並列実行できるようになります。
  • group_varsを一元管理したいので、インベントリファイルは階層を切らず、group_varsと同一のルートディレクトリ直下に置きました。
# 下記は、group_varsが参照できない。
.
├── group_vars
├── hosts
│   ├── production
│   ├── staging
  • ステージをグループとしてインベントリファイルに設定しました。
    • group_varsで、ステージ別の変数を管理することができます。
    • playbookの中でも、ステージ別にタスクを分岐させることができます。whenステートメント、およびマジック変数group_namesを用います。
# worker.yml
---
- hosts: worker
  roles:
    - { role: role3, when: "'worker-001' in group_names" }  # グループが`worker-001`の場合のみ実行される
    - { role: role4, when: "'worker-002' in group_names" }  # グループが`worker-002`の場合のみ実行される
    - { role: roke5, when: "'production' in group_names" }  # production環境にのみ実行される
  • templatesはroleのディレクトリではなく、ルートディレクトリ配下に置いて一元管理することにしました。

設定について

インベントリファイル

  • [production:children]で、ステージとロールの各グループをホストに適用しています。

例) production

[web]
10.1.11.111

[worker-001]
10.1.21.121
10.1.21.122

[worker-002]
10.1.21.123

[batch]
10.1.31.131

[worker:children]
worker-001
worker-002

[production:children]
web
worker
batch

group_vars

例) production

---
stage: production
env:
  mysql:
    host: production.hogehoge.com
    user: hogehoge
    password: hogehoge

※各ステージにはstage変数を定義しています。これは、次項に取り上げるPackerにて用いるためです。

例) worker
workerグループは2つの子グループworker-001, worker-002を含むので、対象ホストがこの2つのグループのいずれかに属する場合、下記の変数が参照されます。

---
resque_scheduler:
  pid_file: '/var/run/sample/resque-scheduler.pid'

playbooks

例) worker-001.yml

---
- hosts: worker-001
  roles:
    - common
    - role1
    ...

例) worker-002.yml

---
- hosts: worker-002
  roles:
    - common
    - role2
    ...

例) worker.yml
hosts: worker-001hosts: worker-002に実行するタスクをまとめたplaybookです。

---
- import_playbook: worker-001.yml
- import_playbook: worker-002.yml

その他、Ansibleやってて便利だなと感じたTipsのご紹介

ローカル環境にグループを適用させたいとき

playbookにhosts: localhostと設定するだけです。(Ansible1.6.2以降)

---
- hosts: localhost
  roles
    - role1
    ...

実行コマンド

$ ansible-playbook local-playbook.yml   # -i オプション不要

ただ、この場合inventory_dirinventory_fileといったインベントリ関連のマジック変数は使用できなくなります。

Ansibleの接続設定(ansible_connection)のデフォルトはsshですが、hosts: localhostのときは、暗黙的にansible_connection=localで接続してくれます。
ただし、インベントリファイルにてグループ管理したい場合は、明示的に設定する必要があります。

# インベントリファイル
[local]
localhost ansible_connection=local

または、playbook内で指定します。

---
- hosts: local
  connection: local
  roles
    - role1
    ...

Ansibleの複数のバージョンを同居させたいとき

Ansibleのバージョン切り替えには、vertualenv + Virtualenvwrapper を用いました。
インストールだけで簡単に導入でき、下記コマンドで簡単に複数の環境を行き来できるのでとても楽でした。

$ workon ansible-2.7.8        # ansible-2.7.8 をインストールした環境に移動
(ansible-2.7.8)$ deactivate   # ansible-2.7.8 をインストールした環境を抜ける
$ 

参考にしたサイト

下記サイトを大変参考にさせていただきました。ありがとうございます!

AWS再入門2018 Amazon EC2 Auto Scaling編
Ansible inventoryパターン
Ansibleを使い出す前に押さえておきたかったディレクトリ構成のベストプラクティス

最後に

次回は、Packerを用いて、今回作成したAnsibleの設定をそのままAMIに反映するしくみについて書きたいと思います。