このエントリーをはてなブックマークに追加

ansibleを使ってみる

Chefが猛烈な勢いで流行り始めている今日このごろですが、似たようなものは世の中にいくつもあります。今日はその中の一つ、Ansibleを使ってみます。

書いていたらやたらと長くなったので何回か続きます。

軽くご紹介

インストールの前にどのようなツールなのかを軽く説明します。マシンの設定を自動で行なってくれる、というツールなのはChefと同じです。

そのポリシーはgithubのページに書かれています。

  • シンプルな設定
  • 最初から超速くて並列
  • サーバーやデーモンとかいらない。今あるsshdだけあればいい
  • クライアント側になにもいらない
  • モジュールは「どんな」言語でも書ける
  • 超強力な分散スクリプトを書くためのイケてるAPI
  • rootじゃなくても便利に使える
  • 今までで一番使える設定管理システム

さて、では見て行きましょう。

インストール

ansibleはpythonで書かれています。実行する側には2.6以上が、実行される側には2.4以上が必要です。

他にも

  • paramiko
  • PyYAML
  • jinja2

が必要ですが、そのあたりはpipがめんどうみてくれるので、

% pip install ansible

でOKです。

使ってみる

% echo "127.0.0.1" > ~/ansible_hosts
% export ANSIBLE_HOSTS=~/ansible_hosts
% ansible all -m ping --ask-pass
SSH password:
127.0.0.1 | success >> {
    "changed": false,
    "ping": "pong"
}

と出ればOKです。これは、

  1. ansbileの対象となるホストを設定
  2. -m pingを実行

という感じです。

対象となるホストを設定

/etc/ansible/hosts を次のように書きます。これはiniファイル形式です。グループ化しておくといいですね。/etcに書けなくても、さっきのようにANSBLE_HOSTS環境変数を設定したり -i で指定できるので問題ないです。

mail.example.com

[servers]
foo.example.com
bar.example.com

[webservers]
web[01:50].example.com    <-- web01から50まで指定

[databases]
db-[a:f].example.com  <-- こういうのもあり

実際にはホストやグループごとに個別に変数を設定できたりと、結構柔軟に書けます。

使い方

以上のように設定して、実際の実行は以下のようにします。

% ansible <対象> -m <モジュール名> -a <引数>

例
% ansible webservers -m service -a "name=httpd state=restarted"

さて、では、これから実際の使い方の例を並べてみます。

# webサーバーを10並列でrebootかける
% ansible webservers -a "/sbin/reboot" -f 10
# sudoで実行
% ansible atlanta -a "/usr/bin/foo" -u username --sudo
# shellモジュールを経由して実行
% ansible raleigh -m shell -a 'echo $TERM'

モジュールについてはあとで説明します。

ファイルを扱う

# 手元の/etc/hostsを各サーバーの/tmp/hostsにコピー
% ansible webservers -m copy -a "src=/etc/hosts dest=/tmp/hosts"
# 各サーバーのa.txtのownerを変更
% ansible webservers -m file -a "dest=/srv/foo/a.txt mode=600 owner=mdehaan"

パッケージ

# yumでinstall
% ansible webservers -m yum -a "name=acme state=installed"
# version指定
% ansible webservers -m yum -a "name=acme-1.5 state=installed"
# 最新指定
% ansible webservers -m yum -a "name=acme state=latest"
# インストールされていないこと
% ansible webservers -m yum -a "name=acme state=removed"

モジュール

ここまでで、ansbileの基礎的な使い方はわかったと思います。そして、 -m で指定するモジュールというものがあることも。

モジュールはchefで言うところの「Resource」に当たるものです。結構いっぱい用意されており、ここにまとまっています。

モジュールは自分で作成することもできます。

  • 標準入力でオプションを受け取る
  • 標準出力で実行結果を返す(出力形式は基本JSONで。ただし、key=value を空白でつなげたものでも)

ということさえ守っていれば、言語はなんでも大丈夫です。ただし、「冪等性」は守ったほうがいいでしょう。既存のモジュールは冪等性があります。

詳しくはこちらをご覧ください。

playbook

モジュールは単一の機能だけを提供するものですので、これだけでは自動構成できません。モジュールを組み合わせて、一連の動作を設定する必要があります。これを書くのが playbook と呼ばれるものです。

以下にapacheを設定する例を書きます。

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  user: root
  tasks:
  - name: apacheを最新に
    action: yum pkg=httpd state=latest
  - name: apacheの設定ファイルを書き出す
    action: template src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - apache再起動
  - name: apacheが起動していることを確認
    action: service name=httpd state=started
  handlers:
    - name: apache再起動
      action: service name=httpd state=restarted

これを実行するのには、 apache.yamlというファイル名で保存したとすると、

% ansible-playbook playbook.yml -f 10

とします。この例では10並列で実行しています。

感じがつかめたところで、順に説明していきます。

playbookの設定

- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  user: root

hostsは実行するホストですね。先ほど ansible コマンドでも指定しましたね。(ここが分離されてると共有しやすくなるんだけどな)

varsは使用する変数です。playbookの中で $http_port と指定したり、templateモジュールのjinja2で使われたりします。

tasks設定

tasks:
- name: apacheを最新に
  action: yum pkg=httpd state=latest
- name: apacheの設定ファイルを書き出す
  action: template src=/srv/httpd.j2 dest=/etc/httpd.conf
  notify:
  - apache再起動

tasksは実際に実行する動作を指定します。nameはその名前を、actionでモジュールと引数を指定します。

実行順序は、書いてある順番で実行されます。

handlers

handlers:
  - name: apache再起動
    action: service name=httpd state=restarted

tasksの設定中でnotifyというのがありましたが、notifyで指定した動作を受けるのがhandlerです。これにより、

  1. 設定ファイル書き出し
  2. apache再起動

という流れになります。chefのnotify/subscribeと一緒ですね。

なお、handlerは必ず最後に実行されます。つまり、tasksで「apache再起動」が何回も指定されていたとしても、最後に一回だけしか実行されません。

includeでtaskを再利用

一度書いたtaskはincludeで再利用できます。

---
- name: apache再起動
  action: service name=apache state=restarted

とだけ書かれたファイルを用意しておけば、

tasks:
 - include: tasks/foo.yml
 - include: tasks/bar.yml user=shirou

と書けます。2つ目のincludeではuserという変数に別の値を入力しています。これで、 {{user }} となっているところを置き換えられます。

また、handlerでもincludeできます。

playbookの再利用

- name: this is a play at the top level of a file
  hosts: all
  user: root
  tasks:
  - name: say hi
    tags: foo
    action: shell echo "hi..."

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

tasksやhandlerと同じように、playbookそのものもincludeできます。上記例では、load_balancersなどを読み込み、さらに実行するhostsを上書きしています。

role

これら再利用をまとめたのがroleです。

site.yml
webservers.yml
fooservers.yml
roles/
   common/
     files/
     templates/
     tasks/
     handlers/
     vars/
   webservers/
     files/
     templates/
     tasks/
     handlers/
     vars/

というようなファイル構成を作り、以下のようなplaybookを書きます。

---
- hosts: webservers
  roles:
     - common
     - webservers

このplaybookを実行すれば、roles/webserversの中身が設定されます。

rolesは以下のような動作をします。xは上記"webservers"などと読み替えてください。

  • roles/x/tasks/main.yml があればそこに書かれているtaskが自動でinclude
  • roles/x/handlers/main.yml があればそこに書かれているhandlersが自動でinclude
  • roles/x/vars/main.yml があればそこに書かれているvarsが自動でinclude
  • copy taskは、roles/x/files/以下のファイルをパス指定なしで参照できる
  • template taskaは、roles/x/templates以下のファイルをパス指定なしで参照できる

chefで言うところのcookbookが近いですね。

変数を別ファイルに書き出したい

---
- hosts: all
  user: root
  vars:
    favcolor: blue
  vars_files:
    - /vars/external_vars.yml

var_filesとしておけば、変数を外だし出来ます。

OSごとに挙動を変えたい

ansible_os_family という変数がセットされており、それを使います。

例えば、Debian系とCentOSとではapacheのパッケージ名が違いますが、

---
- hosts: all
  user: root
  vars_files:
    - "vars/common.yml"
    - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
  tasks:
  - name: make sure apache is running
    action: service name={{ apache }} state=running

というように書いておき、vars/CentOS.ymlにこう書いておくと、

---
# for vars/CentOS.yml
apache: httpd

CentOSの場合はhttpdが使われることになります。なにも当てはまらなければos_defaults.ymlが使われます。仮にos_defaults.ymlがない場合はエラーとなります。

なお、この機能を使うためにはfacterかohaiを入れる必要があります。

状態に応じて動作を変える

tasks:
  - name: "Debian Familyだったらshutdownする"
    action: command /sbin/shutdown -t now
    when: ansible_os_family == "Debian"

whenを使うとできます。さらに、registerというものがあり、これはactionの出力を受け取ります。この2つを併用すると、こういうこともできます。

tasks:
    - action: template src=/templates/foo.j2 dest=/etc/foo.conf
      register: last_result
    - action: command echo 'ファイルが変更されました'
      when: last_result.changed

この例ではactionのtemplateで変更があったら、echoが実行されます。

FAQ

naiveのsshを使うには

  • “–connection=ssh を引数につけるか
  • export ANSIBLE_TRANSPORT=ssh とする

実行時間が超長いコマンドがあるんだけど

# timeoutを3600秒にしてバックグラウンドで実行
% ansible all -B 3600 -a "/usr/bin/long_running_operation"

127.0.0.1 | success >> {
 "ansible_job_id": "27789765441",
 "results_file": "/home/shirou/.ansible_async/27789765441",
 "started": 1
}

# 得られたjob idを使って状態を得る。jobidは全ホスト同じ
% ansible all -m async_status -a "jid=123456789"

# 60秒ごとにポーリング
% ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff"

まとめ

chefでserverを使うまでもなくchef-soloで十分なレベル(20台ぐらいまで?)であれば、このansibleを試してみる価値はあるかもしれません。

ただし、ansibleは並列実行をサポートしていますので、かなりの台数までまかなえるかもしれません。特に、実行先のホストにはなにも必要ない点はchef-soloの比べてアドバンテージと言えるでしょう。

ということで、実はいままではドキュメントを読んだことしか書いていませんですが、実際にplaybookを使ってみようと思います。

ということで、まて次号!