実践!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が動作するところまでいきたいと思います。ぜひご覧ください。