サービスごとに分散してしまった Ansible のコードを共通化する

f:id:tkhttty:20190410092459g:plain

こんにちは!インフラチームの高畑です!

ついに新年度を迎えぼく自身も入社 2 年目となってしまいました。

後輩も入社してきたのでより一層気合い入れて業務に取り組む必要性をひしひしと感じています。

さて、今回はウィルゲートの各サービスを陰ながら支えている Ansible を共通化したお話をいたします!

これまでの Ansible リポジトリ運用

ウィルゲートではこれまで、サービスが新規で立ち上がるたびに Ansible リポジトリを作成してサーバ構成を行ってきました。

f:id:tkhttty:20190405161459p:plain
Ansible リポジトリ一覧

各サービスごとに LDAP の設定や SSH の設定、Webサーバの設定などを Ansible で行っているのですが、使いまわせる role についてもそれぞれで用意しており一つ変更が入れば全てのリポジトリを修正しなければならない状態となっていました。

これらの問題を解決するため、 Ansible のコードを共通化して運用コストを下げていこうと動き出しました。

Ansible 共通化に向けて行ったこと

まず Ansible を共通化するにあたり、 Bitbucket 上で共通のリポジトリを作成しました。

f:id:tkhttty:20190405145026p:plain
Ansibleリポジトリの README.md

リポジトリディレクトリ構造は以下のような形にしています。

.
├── README.md
├── all.yml
├── ansible.cfg
├── bitbucket-pipelines.yml
├── requirements.txt
├── site.yml
└── roles   # 全サーバ共通の role
│   ├── hostname
│   ├── ldap
│   ├── ssh
│   ├── sudoers
│   ├── swap
│   ├── timezone
│   └── yum
└── milly       # サービス毎にディレクトリを作成
    └── roles       # 各サービス毎の固有の role
    |   ├── nginx
    |   └── firewalld
    |   :
    |   :
    ├── production   # 各サービス毎のインベントリファイル
    └── testing
    :
    :

各サービスはこのリポジトリのなかでディレクトリを作成し、それぞれ固有の role を用意しています。 共通化できる role については上の階層でまとめており、各サービスそれぞれで all.yml を読み込むようにすることで共通化を実現しています。

---

# import common playbook
- import_playbook: ../all.yml

# import service specific playbooks
- import_playbook: ./frontservers.yml

また、これまで Ansible の実行コマンドをそれぞれ叩いていたのですが、新しくジョインした人やこれまで Ansible に触れていなかった人などコマンドを一々覚えるのも大変だったので対話型のシェルを用意しました(Makefile で用意する方法もあったのですが、引数をとったりするのにはあまり向いていないため今回はシェルにしました)。

$ sh ansible.sh
サーバ構成変更のため Ansible を発射します。心の準備は大丈夫ですか?
対象のサービスを選択してください
    1) sample
    2) xxxx
    3) xxxx

選択してください (数字を入力): 1
sample の Ansible を実行します
Ansible を実行する対象のホストグループ名を入力してください: sampleservers
sampleservers に対して Ansible を実行します
本番環境ですか? (yes or no): yes
Dry-Run を実行しますか? (yes or no): yes
Dry-Run を実行します
実行結果に問題がない場合には再度シェルを実行して Ansible を発射してください

Ansible を実行するユーザ名を入力してください: hogehogeuser
hogehogeuser さんが Ansible を実行します
鍵認証ですか?(yes or no): no
Ansible 実行の際にログインパスワードを入力してください
ansible-vault で暗号化しているものはありますか?(yes or no): no
発射の準備は整いましたが心の準備はできていますか? (yes or no): yes

これにより、 Ansible に触ったことがない人でも手軽に Ansible を実行することができるようになったため、少しですが運用の手間を省くことができました。

今後は Ansible Tower や OSS である AWX を利用することも考えています。

このリポジトリの扱いについて

今回、このリポジトリを作成するにあたり、 ansible-lint を導入しました。

github.com

Bitbucket Pipelines を利用して ansible-lint を実行しており、チェック項目に引っかかった場合は以下のようにメッセージが出力されるようになっています。

/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:25: [E401] Git checkouts must contain explicit version
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:30: [E401] Git checkouts must contain explicit version
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:35: [E401] Git checkouts must contain explicit version
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:41: [E401] Git checkouts must contain explicit version
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:46: [E301] Commands should not change things if nothing needs doing
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:57: [E303] tar used in place of unarchive module
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:61: [E201] Trailing whitespace
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:63: [E301] Commands should not change things if nothing needs doing
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:129: [E301] Commands should not change things if nothing needs doing
/opt/atlassian/pipelines/agent/build/milly/roles/nginx/tasks/api/build.yml:134: [E301] Commands should not change things if nothing needs doing

このテストが通っていて且つインフラチーム内の最低 1 人からの承認がないとプルリクエストのマージができない設定にしてあるため、最低限の品質は担保できるのではないでしょうか。

Ansible 共通化を行ってみて

今回このように共通化を行ってみて、下記のように CentOSAmazon Linux などによる OS の違いをなるべく吸収するような Playbook の作成を心がけるようになりました。

- name: set hostname
  hostname:
    name: "{{ inventory_hostname }}"

# Amazon Linuxであれば /etc/sysconfig/network を修正
- name: set hostname
  lineinfile:
    dest: "/etc/sysconfig/network"
    state: present
    regexp: "^HOSTNAME"
    line: "HOSTNAME={{ inventory_hostname }}"
  when:
    - ansible_distribution == "Amazon"

また、共通化した role を各サービスで読み込むだけで済むようになったため、各サービス固有の role 作成に集中できるようになりました。 LDAP の設定などにもし変更が入った場合にも 1 箇所だけ修正すれば済むようになったため、運用コスト的にも良い方向に進んでいると感じています。

特に一番大きかったことが、弊社としての Ansible ベストプラクティスを用意することができたかなという点です。

これまでガイドラインなどもない状態での Ansible 運用であったため、サービス毎に品質がバラバラだったりしていました。

今回の共通化をきっかけにそれぞれの品質を一定にできればと思っています。

現在それぞれの Ansible をこのリポジトリに移行している最中であるため、より気合いを入れて取り組んでいきたいと思います!