11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AnsibleAdvent Calendar 2019

Day 6

Ansibleによる開発をスケールさせるために、公式のベストプラクティスから逸脱した話

Last updated at Posted at 2019-12-05

オンプレとAWSのハイブリット構成で、数千台の仮想マシンを Ansible で構成管理している環境で、実際に経験した問題と、有効だったリファクタリングについて説明します

少し前にTweetした↓の内容を、より詳細に書いています

気がついたら、こんな事態に

社内でサービスの新機能開発プロジェクトが進行している中、インフラエンジニアとして、新機能用のサーバ構築をしていたときのことです

roles/ec2
roles/ec2/create
roles/ec2-create

EC2インスタンスを作成するための roles が乱立していました……

もしやと思いオンプレのコードを確認してみると

roles/create-vm
roles/virt-install
roles/v1.0/kvm/guest/install

やはり同じような roles が乱立していました

仮想マシンの作成だけでなく apache2 nginx mysql などミドルウェアのインストールをするroleも、同じように別名で乱立している状態でした

なぜこんなことに?

AnsibleにおけるRolesとは

まずひとつは、そもそも roles という概念をとらえ損ねていることが原因でしょう

roles というのは言葉どおり役割ですから**「仮想マシン作成」「Apache2をインストール」のような処理の単位で分割するのではなく「Webサーバ」「DBサーバ」**のように、もっと大きな単位で分割すべきものです

公式ドキュメントの例(ユーザガイド - Roles)でも、まさにそのように例示されています

roles を細かく分割すれば、それだけ汎用性、再利用性は高まりますが、中途半端な汎用性しか持ち合わせていないと ほとんど同じ処理だけど、一部分だけ特殊なことをしたい という場合に対応しきれなくなり、仕方なく新しく roles を作成する、ということになりがちです

コードの依存関係

もうひとつは、メンテナンス性の問題です

1つ目とつながる部分もあるのですが、roles の汎用性、再利用性が高すぎると、無数のplaybookからロードされるようになるため、あるroleを変更した際 どのplaybookに、あるいは、どのinventoryに影響するか分からない といった状態になります

また、Ansibleのディレクトリ構成も、依存関係の把握を難しくしている要因の1つだと思います

下記はAnsibleの公式ドキュメントで、ディレクトリ構成のベストプラクティス([ユーザガイド - ディレクトリ構成](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html?highlight=best practice#directory-layout))として紹介されているものの1つを inventory vars modules playbooks roles という単位で分割して記載したものです(見やすさのため一部、省略)

inventory.txt
production                # inventory file for production servers
staging                   # inventory file for staging environment
vars.txt
group_vars/
   group1.yml             # here we assign variables to particular groups
   group2.yml
host_vars/
   hostname1.yml          # here we assign variables to particular systems
   hostname2.yml
modules.txt
library/                  # if any custom modules, put them here (optional)
module_utils/             # if any custom module_utils to support modules, put them here (optional)
filter_plugins/           # if any custom filter plugins, put them here (optional)
playbooks.txt
site.yml                  # master playbook
webservers.yml            # playbook for webserver tier
dbservers.yml             # playbook for dbserver tier
roles.txt
roles/
    common/               # this hierarchy represents a "role"

~ commonの中身は省略 ~

    webtier/              # same kind of structure as "common" was above, done for the webtier role
    monitoring/           # ""
    fooapp/               # ""

この構成では inventory vars playbooks roles のディレクトリが全て同じ階層にあるので、どのplaybookから、どのroles,vars,inventoryが参照されているのか分かりづらくなっています

これらに加えてさらに include_roledependencies でroleの中でroleを呼ぶような処理をしていると、コードに変更を加えた際の影響範囲は、いっそうは分かりづらくなっていきます

こうして、既存のroleのメンテナンスコストが増大し、結局あらたにroleを作成した方が速い、という流れになりがちなのです

ディレクトリ構成を変えてみた

上記で紹介した問題は roles を、汎用的になりすぎないように分割することで、ある程度は解消できると思います

ただそれでも roles - inventory - playbook の依存関係が不明瞭なことには変わりありません

そこで以下のようにディレクトリ構成を変えてみました

regionA/ # オンプレ、AWSなど
  common/ # 一応commonも作る。ただし重複するコードでも、極力commonは利用しない
    roles/
    vars/
  serverA/
    inventory/
    roles/
    playbook1.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    playbook2.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    ...
  serverB/
    inventory/
    roles/
    playbook1.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    playbook2.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    ...
...

regionB/ # オンプレ、AWSなど
  common/ # 一応commonも作る。ただし重複するコードでも、極力commonは利用しない
  serverA/
    inventory/
    roles/
    playbook1.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    playbook2.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    ...
  serverB/
    inventory/
    roles/
    playbook1.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    playbook2.yml (varsはディレクトリ化せず、playbookの中に直接書く)
    ...
...

このディレクトリ構成の特徴

  • serverA serverB というのが Webサーバ とか DBサーバ に対応する概念です。またAWSやオンプレなど、サーバが動作する基盤やネットワークからして異なるものは region としてディレクトリを分割しています
  • region を分ける理由は roles の中でregion判定して条件分岐する(AWSの場合だけ実行するなど)といった記述を避けるためです
  • 他にも、よくあるAnsibleの書き方として CentOS Ubuntu といったディストリビューションごとに分岐処理するようなコードがありますが、これも「serverA は同じディストリビューションであること」という制約を加えれば serverA ディレクトリの中では ディストリビューションを調べるコードを書く必要がなくなります
  • よくよく考えるてみると 「Webサーバ」のroleは「Webサーバ以外」のinventoryやplaybookとひもづける必要はない ということが分かってくるでしょう。なので、関連するroles, playbooks, inventoryは一つのディレクトリにまとめて「他のディレクトリとの依存関係は持たない」とした方が、コード改修時の影響範囲が明確でメンテナンス性が向上します
  • この構成では、極力 common は利用しない方針にします
    例えば serverAserverB の両方にApache2をインストールする、といった重複する処理があった場合にも common は利用せず serverA と serverBそれぞれのディレクトリに同じコードを配置します
    こうしておけば将来的に serverB だけApache2のバージョンを上げたいとか、httpd.confを変更したいなど、サーバ固有のカスタマイズが必要になったときに serverA との 共通部分がないことによって、影響範囲が極小化され、コードの改修がスムーズに進むためです
  • それと vars はディレクトリ化せずにplaybookに直接書いた方が、コードの可読性が高まります(ただしinventory_varsinventory に書いた方がよいです)

他にも嬉しい副作用

  • 複数サーバ間で共有しているコードを極力排除することで、それぞれの構築担当者の書いたコードのconflictが減り、作業の並列性が向上します
  • serverA serverB などのディレクトリ内では、構築担当者が比較的に自由なコーディングができるため、あらたに業務に参加する方の障壁が少なくてすみます
  • たとえばAWSからAzureへ意向するような場合でも region を新しく追加するだけで済み、既存のコードとの依存関係を考える必要がないため、スムーズに進みます

最後に

Ansibleはコードのシンプルさを保つために、高度なプログラミング言語に実装されているような安全機能が実装されておらず、プログラマが注意してコード設計をする必要があります

ですが、それが面白いところでもあります

11
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?