実践!Ansibleベストプラクティス(前編)

今回はAnsibleを本格運用した際のイメージを掴むためにAnsibleのベストプラクティスを参考に実際に試してみたいと思います。

実践のお題はWordPressとします。WordPressのセットアップを通してベストプラクティスのイメージを掴んでいただければと思います。

準備

ローカルマシンに作業ディレクトリを作り、その中でAnsibleのベストプラクティスに則ったプレイブックを作っていきます。MacもしくはLinuxなどで試してみてください。

$ mkdir try-ansible-best-practices
$ cd try-ansible-best-practices

ウェブサーバとDBサーバを別個に立てますので、さくらのクラウドでサーバを二台立てておきます。OSはCentOS 6.6を利用します。サーバ作成時にrootでのsshの接続に必要となる公開鍵も忘れずに登録してください。

ディレクトリレイアウトの確認

手を動かす前にベストプラクティスで推奨されているディレクトリレイアウトをざっと俯瞰してみましょう。

Ansibleのベストプラクティスの内容を引用しつつ見ていきます。

インベントリファイル

まずはインベントリファイルです。本番環境やステージング環境など、環境ごとにサーバのホストとグループをまとめます。

production                # inventory file for production servers
stage                     # inventory file for stage environment

次にグループやホストごとの変数を定義するファイルがあります。

group_vars/
   group1                 # here we assign variables to particular groups
   group2                 # ""
host_vars/
   hostname1              # if systems need specific variables, put them here
   hostname2              # ""

グループやホストはインベントリファイルに記述したものとなります。これらの変数はインベントリファイルに含めることもできますが、ベストプラクティスでは別個のファイルに切り出すようになっています。

トップレベルのプレイブック

マスタープレイブックとしてsite.ymlがあります。

site.yml                  # master playbook

そしてその配下にサーバの役割ごとにロールをまとめたプレイブックを作ります。

webservers.yml            # playbook for webserver tier
dbservers.yml             # playbook for dbserver tier

これらのファイルはロールをインクルードするだけに留め、ここに細かいタスクを書いていくことはしません。

ロール

ロールはプレイブックの一連のタスク、ハンドラ、ファイル、テンプレートなどをまとめたものです。Chefで言うクックブックのようなものでしょうか。

具体的なセットアップのタスクはロールに書いていきます。

roles/
    common/               # this hierarchy represents a "role"
        tasks/            #
            main.yml      #  <-- tasks file can include smaller files if warranted
        handlers/         #
            main.yml      #  <-- handlers file
        templates/        #  <-- files for use with the template resource
            ntp.conf.j2   #  <------- templates end in .j2
        files/            #
            bar.txt       #  <-- files for use with the copy resource
            foo.sh        #  <-- script files for use with the script resource
        vars/             #
            main.yml      #  <-- variables associated with this role
        defaults/         #
            main.yml      #  <-- default lower priority variables for this role
        meta/             #
            main.yml      #  <-- role dependencies

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

適切に切り分けられたロールはプレイブックの再利用性も高めてくれます。Ansibleを本格的に運用していくためには必須の概念と言えるでしょう。

インベントリファイルの作成

さて、ではここからは実際に手を動かしていきましょう。

開発環境のサーバと言うことでインベントリファイル名はproductionでもなくstageでもなくdevelopmentとしてみます。

ベストプラクティスに従ったインベントリファイルでは地域と役割ごとのグループを作って、それを役割ごと地域ごとのグループに束ねます。

複数リージョンにまたがったサーバ構築というのはほとんどのサービスには不要かと思いますが、従っておいてマイナスはないので、倣っておきましょう。

development

IPアドレスは実際に作成したサーバのIPアドレスに置き換えてください。

[ishikari-2-webservers]
xxx.xxx.xxx.xxx

[ishikari-2-dbservers]
yyy.yyy.yyy.yyy

# webservers in all geos
[webservers:children]
ishikari-2-webservers

# dbservers in all geos
[dbservers:children]
ishikari-2-dbservers

# everything in the ishikari-2 geo
[ishikari-2:children]
ishikari-2-webservers
ishikari-2-dbservers

どのリージョンを使っているか分かりやすくなるのがいいですね。

それぞれのグループごとにプレイブックを適用できるので、ウェブサーバのみ、DBサーバのみ、特定リージョンのサーバのみを更新するといったことも簡単にできます。

トップレベルのプレイブックの作成

次にトップレベルのプレイブックを作成していきましょう。

まずはマスタープレイブックのsite.ymlです。

site.yml

---
- include: webservers.yml
- include: dbservers.yml

site.ymlはこのように極めてシンプルに保ちます。

そしてsite.ymlから読み込むグループごとのプレイブックを同じくトップレベルのディレクトリに作成します。

webservers.yml

---
- hosts: webservers
  roles:
    - common

dbservers.yml

---
- hosts: dbservers
  roles:
    - common

これらのファイルも極めてシンプルな内容に保ちます。

事始めとして基本的な設定を行うということでcommonというロールを読み込むようにしました。
今はwebservers.ymlもdbservers.ymlもほぼ同じ内容のファイルになっていますが、追々各グループごとのロールを読み込むようにしていきますので、しばらくお待ちください。

commonロールの作成

全てのサーバに共通する処理ということでcommonというロールを作っていきます。

以下のような基本的なユーザの追加やセキュリティ設定を行うことを目的としたロールです。

  • 管理ユーザーの作成
  • sshdの設定
  • iptablesの設定

ベストプラクティスに従ってrolesディレクトリを作成し、その配下にcommonというロール名でディレクトリを切り、更にその下にディレクトリやファイルを作成していきましょう。

管理ユーザーの追加

まずは管理ユーザの追加です。

運用を開始した後もrootアカウントを日常的に使い続けるのは良くありませんので、sudo可能な管理者アカウントを別に作ります。

roles/common/tasks/main.yml

userモジュールを用いてユーザを作成するタスクを書いていきます。

単にuserモジュールでユーザを作成するだけではなく、少しアレンジを加えて変数を用いることで複数ユーザーの追加を楽に行うことができるようにしてみました。

またユーザーの追加に加えて公開鍵認証のためのauthorized_keysの設定とsudoの設定も行っています。

- name: users exist
  user: name={{item.name}} state=present password={{item.password}} groups={{item.groups}}
  with_items: users
  tags: users

- name: ~/.ssh for users exsit
  file: path="/home/{{item.name}}/.ssh" state=directory owner={{item.name}} group={{item.name}} mode=0700
  with_items: users
  tags: users

- name: authorized keys is deployed
  copy: src="authorized_keys_for_{{item.name}}" dest="/home/{{item.name}}/.ssh/authorized_keys" owner={{item.name}} group={{item.name}} mode=0600
  with_items: users
  tags: users

- name: sudo configured
  copy: src="sudoers" dest="/etc/sudoers" owner=root group=root mode=0440

これらのタスクに必要な変数定義やファイルの作成を続いて行いましょう。

group_vars/all

タスクで使用した変数は全てのサーバに共通のグループ変数として定義します。

そのためにgroup_varsディレクトリを作り、配下にallという名前でファイルを作成し以下の内容とします。

users:
  - { name: moongift,  password: "$1$Id9Bdq7D$B7bNBQO.MaZP.XKXm.Hbl.", groups: "wheel" }

パスワードは以下のようにハッシュ化したものを使います。hogehogeは実際に使うパスワードに置き換えてください。

openssl passwd -1 hogehoge

roles/common/files/sudoers

サーバ上に置くsudoersファイルを準備します。今回は実際のサーバ上から/etc/sudoersをscpでコピーしてきたものを編集します。

105行目のコメントアウトを外してwheelに所属するユーザーがsudoできるようにします。

## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)   ALL

なおlineinfileモジュールでサーバ上の既存のファイルを変更するようにする事もできますが、今回は手元に用意した設定ファイルをアップロードするアプローチをとります。

roles/common/files/authorized_keys_for_moongift

公開鍵認証のための公開鍵を含んだauthorized_keysを準備します。

cat ~/.ssh/id_rsa_for_moongift.pub > roles/common/files/authorized_keys_for_moongift

公開鍵の内容をそのままコピーすればOKです。

適用

これでプレイブックにユーザ追加の機能が備わりました。以下のコマンドで適用してみましょう。

ansible-playbook -i development site.yml -u root --private-key=~/.ssh/id_rsa_for_ansible_bp_root

インベントリファイルとしてdevelopmentを、プレイブックとしてsite.ymlを指定しており、各サーバにはrootで接続し、秘密鍵はサーバ作成時に登録した公開鍵と対になるものを使っています。

うまく適用できましたか?うまくいったら追加したユーザでログインしてsudoができることを確認してみてください。

sshdの設定

ユーザが追加できたら次はsshdの設定です。パスワード認証を無効にして公開鍵認証のみとします。

roles/common/files/sshd_config

配置するsshdの設定ファイルを作成します。sudoの設定の時と同じように実際のサーバ上から/etc/ssh/sshd_configをコピーしてきて編集します。

PasswordAuthentication no

としてください。

roles/common/tasks/main.yml

ユーザ追加のタスクが書かれたtasks/main.ymlに以下を追記します。

- name: sshd is configured
  notify:
    - restart sshd
  copy: src="sshd_config" dest="/etc/ssh/sshd_config" owner=root group=root mode=0600
  tags: sshd

- name: sshd is enabled and started
  service: name=sshd state=running enabled=yes
  tags: sshd

notifyでrestart sshdを呼び出しているのに注意してください。これはハンドラとして定義する必要があります。

ちなみにsshd.ymlのようなファイルを別に作ってmain.ymlからインクルードしてもいいのですが、早期に細分化すると見通しが悪くなるので同じファイルに書いていきます。長くなってきたら、その時に分ければいいでしょう。

roles/common/handlers/main.yml

sshdを再起動するためのハンドラは次のように作成します。

---
- name: restart sshd
  service: name=sshd state=restarted

適用

再度適用です。

ansible-playbook -i development site.yml -u root --private-key=~/.ssh/id_rsa_for_ansible_bp_root

うまくいけばsshdの設定ファイルが更新され、sshdが再起動します。これでパスワード認証は無効になりひとつセキュアになりました。

ちなみにうまくいかない場合はファイルを修正して調整する必要がありますが、動作の確認のために全てのタスクが走るのは少し時間がかかり煩わしいです。そこで役に立つのが、タグを指定してそのタグが付けられたタスクのみを実行するという方法です。

ansible-playbook -i development site.yml -u root --private-key=~/.ssh/id_rsa_for_ansible_bp_root --tag=sshd

これを見越して今までに定義したタスクにもtagsでタグを振ってあります。プレイブックを構築する際には特に便利ですので、是非覚えておいてください。

iptablesの設定

最後にiptablesの設定です。

roles/common/tasks/main.yml

引き続きtasks/main.ymlに追記します。

テンプレートとグループ変数を使って各サーバに必要なポートだけを許可するようにしました。

- name: iptables is enabled and started
  service: name=iptables state=running enabled=yes
  tags: iptables

- name: iptables is configured
  template: src="iptables" dest="/etc/sysconfig/iptables" owner=root group=root mode=0600
  notify:
    - restart iptables
  tags: iptables

sshdの時と同じように、設定ファイルが変更されたらサービスを再起動するよう、notifyでハンドラを指定しています。

roles/common/handlers/main.yml

iptablesを再起動するハンドラです。

restart sshdの下に以下を追加します。

- name: restart iptables
  service: name=iptables state=restarted

roles/common/templates/iptables

iptablesのルールファイルのテンプレートです。

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
{% for rule in iptables.accept_tcp_rules %}
-A INPUT {% if "source" in rule %}-s {{rule.source}}{% endif %} -m state --state NEW -m tcp -p tcp --dport {{ rule.port }} -j ACCEPT
{% endfor %}
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

テンプレートエンジンはJinja2です。

group_vars/webservers

接続を許可するポートを変数で定義します。

ウェブサーバはsshとhttpのみを許可します。

iptables:
  accept_tcp_rules:
    - { port: 22 }
    - { port: 80 }

group_vars/dbservers

DBサーバはsshとウェブサーバーからのMySQLの接続のみを許可します。

iptables:
  accept_tcp_rules:
    - { port: 22 }
    - { port: 3306, source: xxx.xxx.xxx.xxx }

このように変数をうまく使うと同じプレイブックやテンプレートからグループ個別、ホスト個別の設定を行うことができます。

適用

三度目の適用です。

ansible-playbook -i development site.yml -u root --private-key=~/.ssh/id_rsa_for_ansible_bp_root

各サーバにログインしてiptablesの設定を確認してみてください。意図したとおりの設定になっているでしょうか?

現在のファイル構成

try-ansible-best-practices
├── development
├── site.yml
├── dbservers.yml
├── webservers.yml
├── group_vars
│   ├── all
│   ├── dbservers
│   └── webservers
└── roles
    └── common
        ├── files
        │   ├── authorized_keys_for_moongift
        │   ├── sshd_config
        │   └── sudoers
        ├── handlers
        │   └── main.yml
        ├── tasks
        │   └── main.yml
        └── templates
            └── iptables

次回は

今回は運用ユーザの追加と基本的なセキュリティ設定をしてみました。

次回はウェブサーバとDBサーバそれぞれの設定を行ってWordPressが動作するところまでいきたいと思います。ぜひご覧ください。

Ansibleのベストプラクティス