Ansibleとシェルの対比1

最近なにかと見かけることの多くなった Ansible。色々と動きを調べてみた。

とはいっても、本家に良ドキュメントもあるし、日本語のリソースもあちこちで見かけるようになったので、ここでは「シェルスクリプトの代わりに使ってみたら」という切り口で調べたことをまとめてみる。

バージョンに関して

動作確認には、CentOS 6.5 を使用した。Ansible は、この文章を書いている時点(2014年9月3日)で CentOS 6.5 向けにパッケージ提供されている最新バージョン “1.7.1.el6” を導入して検証した。

1. Ansibleって何?

Ansibleは構成管理ツール(CMS)とかデプロイツールとか色々な呼び方がされているけれど、ここでは即物的に次のようなツールだと捉えている。
  1. ソフトウェアのインストールや設定などのマシン操作手順
  2. スクリプト化 して
  3. 多数のマシン
  4. 一括で流し込める ツール
シンプルさを大切にしており、わずかな準備で即利用でき、その一方で適用範囲はかなり広い汎用ツールだと思う。
その強力さは、私の感覚的にはBashやAwkに近い。

1.1. 適用例

以下は本家で挙げられている例。もっと色々な使い方もできるかも?
  • 構成管理
    マシンとソフトウェアの対応を、グループやロールで、一覧形式にわかりやすく管理
  • ローリングアップデート
    多数のマシンのソフトウェアを、例えばマシン10台づつ、順次アップデートしていく
  • ソフトウェア配布の自動化
    継続的インテグレーションでの環境構築、テスト、ステージング、本番投入まで一本化
  • Orchestration
    AWS上の仮想リソースから、サービスに必要なマシン構成・インベントリを自動管理
  • Dockerの構築・配布
    軽量コンテナ Docker のイメージ構築から配布管理まで、簡単にスクリプト化

1.2. 人気度 - Googleトレンド

Googleトレンドを見ると、2012年春頃から、順調に検索数が伸びている。
個人的には、今後、「クラウド時代のシェルスクリプト」と呼べるような、システムエンジニアの必須スキルになっていくのかな、という気もする。

1.3. 開発モデル、ビジネスモデル

開発モデルとして GitHubでのOSS開発を行っており、だれでも無料で利用できる。現在のGitHubのライセンス表示はGPLv3。
ビジネスモデルとして、UIツールやコンサルを有償で提供している。

2. 事前準備

2.1. インストール

とっても簡単。
制御マシン(マシン管理サーバ)にだけ、Ansibleをインストールする。それで終わり。
ターゲットマシンにはAnsibleのインストールが不要
例えば Cent OS 系なら、EPELにrpmパッケージがあるから、yum で持ってくればよい。
こんなかんじ。
# rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# yum install ansible
試しに、CentOS 6.5 を Basic Server タイプでインストールし、上記コマンドを実行したら、依存関係含めて 9個のパッケージがインストールされた。
# yum install ansible

... 途中省略 ...

Dependencies Resolved

================================================================================
 Package                Arch         Version                Repository     Size
================================================================================
Installing:
 ansible                noarch       1.7-1.el6              epel          874 k
Installing for dependencies:
 PyYAML                 x86_64       3.10-3.el6             epel          157 k
 libyaml                x86_64       0.1.6-1.el6            epel           52 k
 python-babel           noarch       0.9.4-5.1.el6          base          1.4 M
 python-crypto2.6       x86_64       2.6.1-1.el6            epel          530 k
 python-httplib2        noarch       0.7.7-1.el6            epel           70 k
 python-jinja2          x86_64       2.2.1-2.el6_5          updates       466 k
 python-keyczar         noarch       0.71c-1.el6            epel          219 k
 python-pyasn1          noarch       0.0.12a-1.el6          base           70 k

Transaction Summary
================================================================================
Install       9 Package(s)

Total download size: 3.8 M
Installed size: 15 M
Is this ok [y/N]: y
見ての通り、 本体は874kbと小さい!

Support環境

Ansibleのドキュメント Installation に情報がある。表にするとこんな感じに。
制御マシン(管理サーバ) ターゲットマシン
対応OS Red Hat, Debian, CentOS, OSX, BSD(Windowsは不可) 基本なんでもOK
必須PKG Python2.6 Python2.4以降
Pythonベースのツールなので、制御マシン(管理サーバ)とターゲットマシンの両方にPythonが必要。
裏を返すと Pythonされあれば良い という話でもある。

細かな注意点

  • ターゲットマシンのPythonが2.4の場合、追加でpython-simplejsonも必要。Ansibleのrawモードを使って、Ansible自身で最初にpython-simple.json を送り込めば良い。
  • ターゲットマシンでSELinuxを有効にしている場合、一部機能(ディレクトリ作成などのファイル系機能)を使うのにlibselinux-pythonも必要。それら機能を利用する前に、Ansibleを使いインストールすればよい。
  • 現時点で Python3.x には未対応。

2.2. SSHログインの設定

Ansibleでの制御マシンとターゲットマシンの間の通信は、基本、SSH(pluggableな仕組みで切り替えも可)。
Ansibleでターゲットマシンにログインする度にパスワード入力しないで済むよう、公開鍵認証を設定しておく(代わりにパスワード入力を促す--ask-pass オプションも利用可)。
例えば、制御マシン controller 上の ansibleman というユーザから、ターゲットマシン 192.168.170.64 の root ユーザに直ログインできるよう、公開鍵を設定してみる。

最初に鍵を生成。今回はパスフレーズを省略。プロンプトに対して改行で先に進む。
[ansibleman@controller ~]$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ansibleman/.ssh/id_rsa):
Created directory '/home/ansibleman/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ansibleman/.ssh/id_rsa.
Your public key has been saved in /home/ansibleman/.ssh/id_rsa.pub.
The key fingerprint is:

... 途中省略 ...
ターゲットマシンのrootユーザに公開鍵をコピー。この1回だけは、rootのログインパスワードで認証してログインする。
[ansibleman@controller ~]$ ssh-copy-id root@192.168.170.64
The authenticity of host '192.168.170.64 (192.168.170.64)' can't be established.
RSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.170.64' (RSA) to the list of known hosts.
root@192.168.170.64's password:
Now try logging into the machine, with "ssh 'root@192.168.170.64'", and check in:

  .ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.
パスワードなしでログインできることを確認。
[ansibleman@controller ~]$ ssh root@192.168.170.64 whoami
root

OpenSSH vs. Paramiko

Ansibleは、内部にSSHクライアントのPython実装 Paramikoを抱えており、OS環境にSSHが無くても動作できる。
しかし、もし最近のOpenSSHを利用できる環境なら、Ansibleは自動的にOpenSSHを利用し、より高速動作する。
例えば、最近のOpenSSHでは ControlPersist 機能があり、同一経路のSSHコネクションを束ねたり、コネクションを張りっぱなしにするなど可能。大規模環境では効果大の模様。
ここでは制御マシンに CentOS 6.5 を想定しているので、OpenSSHのバージョンは 5.3p1 と古く(この記事を書いている時点の最新は6.6)、AnsibleのSSH通信にはParamikoが使われている。

Root権限について

Ansible自体の動作に、Root権限は不要。
これからやろうとしているタスクに、ターゲットマシン上のRoot権限が不要なら、ターゲットマシンの一般ユーザに公開鍵を設定すれば良い。そういう意味でも気楽に使える。
また、ターゲットマシン上の一般ユーザに sudo 設定していれば、Ansible で必要なときだけRoot権限に昇格することは簡単にできる。リモートコマンド実行時にsudoを指示できるし、sudoパスワードを入力するオプション--ask-sudo-passもある。

2.3. Inventory ファイル

Ansibleでは、ターゲットマシンを列挙した Inventory というデータベースだけは、事前に準備しなければならない。
といっても、お気楽に使う分には簡単なテキストファイルひとつでOK。
例えば、ターゲットマシン 192.168.170.64 に root でSSHログインしたいなら、Inventoryファイルは1行だけでOK。
[ansibleman@controller ~]$ cat inventory
192.168.170.64  ansible_ssh_user=root
ansible_ssh_user は省略可能なパラメータ。Inventoryファイルに書く代わりに、Ansibleのコマンド実行時に、引数 “-u root” で渡しても良い。
もう一台ターゲットマシン 192.168.170.65 があり、こちらのマシンには testuser1 でログインするなら、Inventoryファイルは2行でOK。
[ansibleman@controller ~]$ cat inventory
192.168.170.64  ansible_ssh_user=root
192.168.170.65  ansible_ssh_user=testuser1

Inventoryファイルの Parameter 一覧

Inventoryに書けるパラメータは、ansible_ssh_user 以外にも色々ある。
例えば、localhost に対して、SSHを使わずAnsibleでコマンド実行したいなら、connection=local を渡す。SSHを経由しないので公開鍵の設定も不要だし、AnsibleはSSHの設定を見に行かない。
[ansibleman@controller ~]$ cat inventory
localhost ansible_connection=local

高度なInventory - Group定義など

Inventoryファイルでは、ターゲットマシンのグループを定義して、名前をつけられる。
Ansibleのグループでは、グループ間に包含関係をもたせたり、一つのマシンを複数グループに所属させる定義も書ける。また、マシンやグループに対して変数を定義し、その値をリモートコマンドに渡すことができる。
もし、外部DBに既にマシン一覧を持っているなら、新たにInventoryファイルを作成しなくても、その外部DBを直接参照することができる。

3. 使ってみる

前置きは以上なので、早速、使ってみる。

3.1. SSHリモートコマンドの代わりに使ってみる

以下のSSHリモートコマンドを、Ansibleでやってみる。
[ansibleman@controller ~]$ ssh root@192.168.170.64 date
Tue Sep  2 04:09:19 JST 2014
こんな感じになる。
[ansibleman@controller ~]$ ansible -i ./inventory all -m command -a date
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:15:53 JST 2014

コマンドラインオプションの説明

オプション 説明 省略時
-i ./inventory Inventoryファイルを指定 /etc/ansible/hostsを読む
all Inventoryからターゲットマシンを抽出する host-pattern を指定:”all” は全マシンの意味 (省略不可)
-m command モジュールを指定 -m command
-a date モジュールへの引数 引数なし
Ansibleは -i で指定しなければ、Inventoryファイルとして/etc/ansible/hosts を参照する。/etc/ansible/hosts に 192.168.170.64 ansible_ssh_user=root の1行が書いてあれば、-i を省略してもよかった。
[ansibleman@controller ~]$ cat /etc/ansible/hosts
192.168.170.64  ansible_ssh_user=root

[ansibleman@controller ~]$ ansible all -m command -a date
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:23:10 JST 2014
しかし、普通は /etc/ansible/hosts に書き込むにはroot権限が必要なので、敷居は少し高いかもしれない。

host-pattern の説明

host-pattern は、Inventoryファイルからターゲットマシンを選別するためのサーチキーワードの役割を果たす。
今回は、Inventoryファイルにマシン1台(192.168.170.64)しかないなら、allと書いても、192.168.170.64 と書いても、192.168.170.* と書いても同じ結果となる。
[ansibleman@controller ~]$ ansible 192.168.170.64 -a date
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:30:31 JST 2014

[ansibleman@controller ~]$ ansible 192.168.170.* -a date
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:30:42 JST 2014

[ansibleman@controller ~]$ ansible *64 -a date
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:30:57 JST 2014
host-pattern で選ばれるターゲットマシンが意図したものになっているかは、実際にターゲットマシン上でコマンドを実行せずとも確認できる。
[ansibleman@controller ~]$  ansible all --list-hosts
    192.168.170.64
host-pattern は、ansibleのコマンドラインオプション --limit でも指定でき、2重にターゲットマシンを絞り込むことができる。
host-pattern は、globみたいなワイルドカード “*” だけでなく、正規表現や、簡単な集合演算みたいな、ターゲットマシンの高度な選別もできる。
詳しい説明はこちら。
host-pattern は、柔軟にマシン選別できるので、わりとゆるい感じでInventoryを作っても運用に困らないかもしれない。運用より、論理的なグループ分けやホスト名の命名の方が大切かもしれない。DBかWebか、どのデータセンタにあるのか等。
本番サイトとテストサイトみたいに、絶対に取り違えてはいけないようなターゲットマシンの選別には、host-pattern を使うのではなく、Inventory自体を分ける方がBest Practiceとのこと。例えば、こんな感じに。
[ansibleman@controller ~]$  ansible -i ./test-site webservers -a start-app1
[ansibleman@controller ~]$  ansible -i ./production-site webservers -a start-app1

Ansibleモジュール: -m commandなど

Ansibleモジュールは、プラグインの形になっており、Ansibleと外界であるOS環境をつなぐ ”糊の役目” を果たす。
例えば、commandモジュールでは、内部でPythonのsubprocess.Popen を利用し、ターゲットマシン上の外部コマンドを1つ fork&exec する。この機能のおかげで、Ansibleをリモートシェル(RSHやSSH)として利用することができる。
予め組み込まれているAnsibleモジュールは、Linuxでは普通 /usr/share/ansible に格納されている。場所は設定ファイル /etc/ansible/ansible.cfg の library に指定がある。
大概の事はcommandモジュールだけでもできてしまうけれど、典型的な作業については250個近い組み込みモジュールが用意されているので、そちらを利用したほうがよい。そうすることで、異常系が簡単になり本来やりたいことに集中できるし、移植性や冪等性(何度実行しても同じ結果となる)もよくなる。
例えば、こんな作業には組み込みのAnsibleモジュールが用意されている。
  • 制御マシンとターゲットマシン間でファイルをコピー:scpやrsyncのように
  • ターゲットマシンで設定ファイルを編集:sedやシェルのhere documentのように
  • ターゲットマシンでCron設定
  • ターゲットマシンでサービスの起動設定:serviceコマンドのように
  • メールを送る:sendmailのように
  • パッケージのインストール: apt, yum, pip, gem など
  • リモートレポジトリからファイル取得: gitやsubversionなど
  • ファイルシステムの作成やマウント
一方、自分で作った独自サービスのAPIを、Ansibleから直接制御したいのであれば、比較的簡単に、新たなAnsibleモジュールを書き下すこともできる。
モジュール指定のない場合は、command モジュールが既定値となっている。Ansibleを単にリモートコマンドとして使いたいだけなら、モジュール指定は省略してよい。
[ansibleman@controller ~]$ ansible all -a date
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:23:10 JST 2014
モジュールのより詳しい説明はこちらから。

Output

AnsibleコマンドのOutputは、単なるSSHと比べると若干情報が多い。その理由は、元々、多数マシン上での同時実行を前提としているため。
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:23:10 JST 2014
一行目は、a. Ansibleが処理を実行したマシン(host-patternで選ばれたマシンの内の1台)、b. モジュールの実行成否、c. Return Code(0: 正常)。
二行目以降が、commandモジュールが実行したコマンドの出力。
結果はjson形式でも得ることもできる。-t <directory>で出力ディレクトリを指定してこんな感じに。
[ansibleman@controller ~]$ ansible -i ./inventory all -a date -t output
192.168.170.64 | success | rc=0 >>
Fri Sep  5 23:08:29 JST 2014
生成されたjsonファイルはこちら。
[ansibleman@controller ~]$ cat output/192.168.170.64
{
    "changed": true,
    "cmd": [
        "date"
    ],
    "delta": "0:00:00.002909",
    "end": "2014-09-05 23:08:29.950988",
    "rc": 0,
    "start": "2014-09-05 23:08:29.948079",
    "stderr": "",
    "stdout": "Fri Sep  5 23:08:29 JST 2014"
}
デフォルトでは、syslogにも出力がある。
Sep  5 23:08:29 controller ansible-command: Invoked with executable=None shell=False args=date removes=None creates=None chdir=None

3.2. Behind the scene: 裏で何をやっているの?

Ansibleの引数に -v を3つ付けると、裏でやっていることを観察できる。
導通確認にも便利かも?
[ansibleman@controller ~]$ ansible -i ./inventory all -a date -vvv
<192.168.170.64> ESTABLISH CONNECTION FOR USER: root on PORT 22 TO 192.168.170.64
<192.168.170.64> REMOTE_MODULE command date
<192.168.170.64> EXEC /bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1409600225.49-71777331256732 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1409600225.49-71777331256732 && echo $HOME/.ansible/tmp/ansible-tmp-1409600225.49-71777331256732'
<192.168.170.64> PUT /tmp/tmpJXAAw2 TO /root/.ansible/tmp/ansible-tmp-1409600225.49-71777331256732/command
<192.168.170.64> EXEC /bin/sh -c 'LANG=C LC_CTYPE=C /usr/bin/python /root/.ansible/tmp/ansible-tmp-1409600225.49-71777331256732/command; rm -rf /root/.ansible/tmp/ansible-tmp-1409600225.49-71777331256732/ >/dev/null 2>&1'
192.168.170.64 | success | rc=0 >>
Tue Sep  2 04:37:05 JST 2014
上のログを詳しく見ると、ターゲットマシン192.168.170.64にSSHでログイン後、作業ディレクトリを準備(mkdir, chmod)し、設定ファイルのremote_tmpなどから生成したパス $HOME/.ansible/tmp/ansible-tmp-1409600225.49-71777331256732/command へ Python スクリプトを送り込み(PUT)、実行している様子(LANG=C LC_CTYPE=C /usr/bin/python)が見えている。
送り込んでいるスクリプトは、-m で指定したモジュール command をテンプレートとして生成している。テンプレートの実体は、制御マシンの/usr/share/ansible/commands/command にある。
ansible -i ./inventory all -a "sleep 60" などとして、Ansibleの実行中にターゲットマシンの中を覗くと、さらに色々と詳しい動きを見ることも可能。
Ansibleは、このように「ターゲットマシンにAnsibleのインストールも不要、Pythonさえあれば良い」という構造を作り出している。

3.3. scpの代わりに使ってみる

ファイル配布

「scpで制御マシンからターゲットマシンにファイルをコピーする」という処理を単純に行うと、こんな感じになる。
[ansibleman@controller ~]$ echo hello > testdata.txt
[ansibleman@controller ~]$ scp testdata.txt root@192.168.170.64:
testdata.txt                                  100%    6     0.0KB/s   00:00
一方、Ansibleでやると、こんな感じに。-m copyでcopyモジュールを利用している。
[ansibleman@controller ~]$ echo hello > testdata.txt
[ansibleman@controller ~]$ ansible -i ./inventory all -m copy -a "src=testdata.txt dest=/root/testdata.txt"
192.168.170.64 | success >> {
    "changed": true,
    "dest": "/root/testdata.txt",
    "gid": 0,
    "group": "root",
    "md5sum": "b1946ac92492d2347c6235b4d2611184",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:admin_home_t:s0",
    "size": 6,
    "src": "/root/.ansible/tmp/ansible-tmp-1410014817.94-232935578383127/source",
    "state": "file",
    "uid": 0
}
“changed”: や “md5sum”: が示唆する通り、同じファイルを複数回コピーすると、2回目以降は実際のコピーは発生しない。しかるに、不運にもコピーの最中にターゲットマシンがフリーズ/クラッシュした場合もコピー先ファイルの中身は壊れにくく、安全性が高い。
コピー先に既にファイルがあれば上書きしないというオプション(force=no)もある。
Ansibleの処理を、大体等価なシェルで書き表すと以下の様になる。
[ansibleman@controller ~]$ scp testdata.txt root@192.168.170.64:tmpfile
testdata.txt                                  100%    6     0.0KB/s   00:00
[ansibleman@controller ~]$ scp copy.sh root@192.168.170.64:
copy.sh                                       100% 1012     1.0KB/s   00:00
[ansibleman@controller ~]$ ssh root@192.168.170.64 ./copy.sh tmpfile testdata.txt
"changed": true
"dest": testdata.txt
"md5sum": b1946ac92492d2347c6235b4d2611184
"src": tmpfile
[ansibleman@controller ~]$ ssh root@192.168.170.64 rm -f tmpfile copy.sh
ここでのシェルスクリプト copy.sh が、Ansibleのcopyモジュールに対応しており、次のような処理をしている(実際はPythonスクリプト)。
#!/bin/bash
# Usage: copy.sh <srcfile> <destfile> [force]

# srcfile must be copied from the remote side to the same partition of destfile.
srcfile="$1"
destfile="$2"
force="${3:-yes}"

[ -n "$srcfile" -a -n "$destfile" ] || { echo "missing args"; exit 1; }
[ -r "$srcfile" ] || { echo "missing srcfile"; exit 1; }

md5sum_src=`md5sum ${srcfile}|awk '{print $1}'`
md5sum_dest=""

creating="yes"

if [ -r "$destfile" ]; then
        if [ "$force" != "yes" ]; then
                echo "dest file already exists"
                exit
        fi
        md5sum_dest=`md5sum ${destfile}|awk '{print $1}'`
        creating="no"
fi

if [ "$md5sum_src" != "$md5sum_dest" ]; then
        # Need to update the destfile

        if [ -e "$destfile" ]; then
                chmod --reference "$destfile" "$srcfile"
                chown --reference "$destfile" "$srcfile"
        fi
        mv -f "$srcfile" "$destfile"

        if [ "$creating" = "yes" ]; then
                chmod 0666 "$destfile"
        fi
        changed="true"
else
        changed="false"
fi
echo '"changed": '${changed}
echo '"dest": '${destfile}
echo '"md5sum": '${md5sum_src}
echo '"src": '${srcfile}
Copyモジュールには、その他にも、Permission(mode)や所有者(owner)を一緒に設定する、事前にバックアップファイルを作る(backup=yes)、コピー先ファイルを更新する前に任意のValidationコマンドを流して文法チェック等行う、ディレクトリ全体をコピーするなど、色々と便利な機能が揃っている。
ちょっと変わったところでは、Here documentのようにファイル内容を埋め込んで、ターゲットマシンに直接ファイルを作成できる。
[ansibleman@controller ~]$ ansible -i ./inventory all -m copy -a 'content="hello" dest=/root/testdata.txt'

ファイル収集

先ほどとは逆向きに、ターゲットマシン上のファイルを制御マシンに転送、収集する。
例えば…
[ansibleman@controller ~]$ scp root@192.168.170.64:testdata.txt collectedfile.txt
testdata.txt                                  100%   12     0.0KB/s   00:00
[ansibleman@controller ~]$ cat collectedfile.txt
hello world!
同じことをAnsibleでやると、こんな感じに。fetch モジュールを利用している。
[ansibleman@controller ~]$ ansible -i ./inventory all -m fetch -a 'src=./testdata.txt dest=newfile.txt flat=yes fail_on_missing=yes'
192.168.170.64 | success >> {
    "changed": true,
    "dest": "/home/ansibleman/newfile.txt",
    "md5sum": "b1946ac92492d2347c6235b4d2611184",
    "remote_md5sum": "b1946ac92492d2347c6235b4d2611184"
}
fetchモジュールは元々はログファイルの収集向けに開発されたそうで、ターゲットマシン上にファイルがなくてコピーに失敗しても、既定ではエラーとならない。そのため、fail_on_missing=yesを指定している。
Ansibleは、同じ処理を多数のターゲットマシン上で並列動作させる想定なので、ファイルを制御マシンに集める時も 収集ディレクトリ(dest)/ターゲットマシン名/ファイルパス(src) という形で、整理して格納する。今回は1つのファイルしか収集しなかったので flat=yes により、パスをdestで直接指定するようにした。
本来の姿では、こんな風になる。この例はちょっと縁起が悪いかも知れない。
[ansibleman@controller ~]$ cat inventory
192.168.170.64  ansible_ssh_user=root
192.168.170.65  ansible_ssh_user=root
192.168.170.66  ansible_ssh_user=root
192.168.170.67  ansible_ssh_user=root
192.168.170.68  ansible_ssh_user=root
192.168.170.69  ansible_ssh_user=root

[ansibleman@controller ~]$ ansible -i ./inventory all -m fetch -a 'src=./tombstone.txt  dest=graveyard fail_on_missing=yes'
192.168.170.68 | success >> {
    "changed": true,
    "dest": "/home/ansibleman/graveyard/192.168.170.68/./tombstone.txt",
    "md5sum": "b1946ac92492d2347c6235b4d2611184",
    "remote_md5sum": "b1946ac92492d2347c6235b4d2611184"
}

192.168.170.64 | success >> {
    "changed": true,
    "dest": "/home/ansibleman/graveyard/192.168.170.64/./tombstone.txt",
    "md5sum": "b1946ac92492d2347c6235b4d2611184",
    "remote_md5sum": "b1946ac92492d2347c6235b4d2611184"
}

... 途中省略 ...

192.168.170.69 | success >> {
    "changed": true,
    "dest": "/home/ansibleman/graveyard/192.168.170.69/./tombstone.txt",
    "md5sum": "b1946ac92492d2347c6235b4d2611184",
    "remote_md5sum": "b1946ac92492d2347c6235b4d2611184"
}
fetchでは、パーミッションや所有者は保存されない。こんな感じにansibleを実行した制御マシンのユーザで、デフォルトのパーミッションとなる。
[ansibleman@controller ~]$ find graveyard/ -type f -printf "%p %m %u:%g\n"
graveyard/192.168.170.65/tombstone.txt 664 ansibleman:ansibleman
graveyard/192.168.170.68/tombstone.txt 664 ansibleman:ansibleman
graveyard/192.168.170.67/tombstone.txt 664 ansibleman:ansibleman
graveyard/192.168.170.66/tombstone.txt 664 ansibleman:ansibleman
graveyard/192.168.170.69/tombstone.txt 664 ansibleman:ansibleman
graveyard/192.168.170.64/tombstone.txt 664 ansibleman:ansibleman

スクリプトのリモート実行

ターゲットマシンへファイルコピーする理由が、手元のスクリプトのリモート実行なら、scriptモジュールを利用することができる。
例えば以下のスクリプトをターゲットマシンに送り込んで実行してみる。
[ansibleman@controller ~]$ cat sample-script.sh
#!/bin/bash
echo "hello! I'm `whoami`"
echo "watch out!" >&2
exit 2
シェルでやると、こんな感じ。
[ansibleman@controller ~]$ scp sample-script.sh root@192.168.170.64:
sample-script.sh                              100%   68     0.1KB/s   00:00
[ansibleman@controller ~]$ ssh root@192.168.170.64 ./sample-script.sh
hello! I'm root
watch out!
[ansibleman@controller ~]$ echo $?
2
[ansibleman@controller ~]$ ssh root@192.168.170.64 rm ./sample-script.sh
Ansibleで書くと、こんな感じに。
[ansibleman@controller ~]$ ansible -i ./inventory all -m script -a "./sample-script.sh"
192.168.170.64 | FAILED >> {
    "changed": true,
    "rc": 2,
    "stderr": "watch out!\n",
    "stdout": "hello! I'm root\n"
}
[ansibleman@controller ~]$ echo $?
2
scriptの異常終了(exit 2)も、ちゃんと呼び元に伝わっている。
異常終了させたので、ステータスは”FAILED” になっている。

rsyncによる多数ファイルの高速転送 - synchronize

copyモジュールは、多数のファイルの転送では、あまり効率がよくない。
主な理由は、ファイルのコピーを1つずつ個別に繰り返すからで、ネットワーク経由でデータを転送している時間(RTT)と、ターゲットマシンでファイルを構築している時間が交互に発生し、片方を実行中にもう片方のリソースが空いてしまうから。ファイルが多数あるなら、本来は工場の組み立てラインの様に、データ転送とファイル構築を流れ作業で同時に動かすことができ、全体のコピー時間を短くできる(Pipelining)。
この辺に気を配っているのが rsync で、ターゲットマシン上に rsync があれば、Ansible からも気軽に利用できる。
SSHを使った以下のコマンドを…
[ansibleman@controller ~]$ rsync --rsh ssh -az --delete /etc/ansible root@192.168.170.64:
ansibleでやるとこんな感じに。rsyncのラッパーとして用意されている synchronizeモジュールを利用。
[ansibleman@controller ~]$ ansible -i ./inventory all -m synchronize -a "src=/etc/ansible dest=./ delete=yes"
192.168.170.64 | success >> {
    "changed": true,
    "cmd": "rsync --delay-updates -FF --compress --delete-after --archive --rsh 'ssh  -o StrictHostKeyChecking=no' --out-format='<<CHANGED>>%i %n%L' \"/etc/ansible\" \"root@192.168.170.64:./\"",
    "msg": "cd+++++++++ ansible/\n<f+++++++++ ansible/ansible.cfg\n",
    "rc": 0,
    "stdout_lines": [
        "cd+++++++++ ansible/",
        "<f+++++++++ ansible/ansible.cfg"
    ]
}
一見すると、commandモジュールからrsyncコマンドを直接コールする場合と比べて、何が良いか分かりにくい。しかし、synchronizeを使えば自動でrsyncの引数にターゲットマシンを渡してくれたり、”—delay-updates” など安全性を高めるオプションを指定してくれる。”src” や “dest” といった引数も他のモジュールと統一され、覚えやすい。こういう配慮ができるのも、Ansibleモジュールを書く一つの理由、良さかもしれない。
fetchのようにターゲットマシンから制御サーバにファイルを転送する場合(=逆方向)、 mode=pull を渡す。
[ansibleman@controller ~]$ ansible -i ./inventory all -m synchronize -a "src=/etc/ansible dest=./ mode=pull"
192.168.170.64 | success >> {
    "changed": true,
    "cmd": "rsync --delay-updates -FF --compress --archive --rsh 'ssh  -o StrictHostKeyChecking=no' --out-format='<<CHANGED>>%i %n%L' \"root@192.168.170.64:/etc/ansible\" \"./\"",
    "msg": "cd+++++++++ ansible/\n>f+++++++++ ansible/ansible.cfg\n",
    "rc": 0,
    "stdout_lines": [
        "cd+++++++++ ansible/",
        ">f+++++++++ ansible/ansible.cfg"
    ]
}
ただし、fetchのようにファイルをマシン名で整理して格納する訳ではないので、多数のターゲットマシンからpullするときには注意が必要。synchronizeのpullモードは、Ansibleのスクリプト(Playbook)で利用する方が良いかもしれない。
つづく。

次回:並列実行の動作制御、他

1 コメント:

Ansibleとシェルの対比1 >>>>> Download Now

>>>>> Download Full

Ansibleとシェルの対比1 >>>>> Download LINK

>>>>> Download Now

Ansibleとシェルの対比1 >>>>> Download Full

>>>>> Download LINK

Reply

コメントを投稿