Ansibleでファイルコピーするときのプレフィックスをカスタマイズする


きっかけ

lineinfileモジュールを使っていたけど、backup=yesで作ってくれるバックアップファイル名がイマイチ気に入らない...
ということで、自由にプレフィックスを設定できる、かつ楽に設定できる方法をいくつか考えてみました。

構成

■コントロールノード(ansibleコマンドを実行するホスト)
・CentOS 8.0.1905
・Ansible 2.9.2
■ターゲットノード(ansibleで構成管理されるホスト)
・CentOS 7.6.1810

デフォルトのプレフィックスを確認

そもそもどんなプレフィックスをつけてくれるかを確認します。
とりあえず、今回バックアップ対象のファイルを無駄にAnsibleを使って作成します。

[ansible@controll ~]$ ansible host1 -m file -a "name=test.txt state=touch"
host1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "test.txt",
    "gid": 1000,
    "group": "ansible",
    "mode": "0664",
    "owner": "ansible",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 0,
    "state": "file",
    "uid": 1000
}

[ansible@target ~]$ ls
test.txt

デフォルトのプレフィックスを確認します。
lineinfileモジュールのbackup=yesオプションを指定し、テキトーに編集します。

[ansible@controll ~]$ ansible host1 -m lineinfile -a "dest=test.txt line='foo' backup=yes"
host1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "backup": "test.txt.4031.2019-12-23@21:29:44~",
    "changed": true,
    "msg": "line added"
}

[ansible@target ~]$ ls
test.txt test.txt.4031.2019-12-23@21:29:44~

デフォルトで作成されるプレフィックスは、「オリジナルファイル名.pid(?).YYYY-mm-dd@HH:MM:ss~」のようです。
でも、@とか:が使われているのは個人的にイヤです。

場面ごとに独自のプレフィックスを設定してみる

固定のプレフィックス

一時的にバックアップ取って、作業が終わったら消す場面など。
.bakでいいじゃーんって時はcopyモジュールが一番楽だと思います。

[ansible@controll ~]$ ansible host1 -m copy -a "src=test.txt dest=test.txt.bak remote_src=yes"
host1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "./test.txt.bak",
    "gid": 1000,
    "group": "ansible",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "mode": "0664",
    "owner": "ansible",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 0,
    "src": "test.txt",
    "state": "file",
    "uid": 1000
}

[ansible@target ~]$ ls
test.txt  test.txt.4031.2019-12-23@21:29:44~  test.txt.bak

test.txt.bakができました。
注意点は、remote_user=yesを付けること。
srcの宛先はデフォルトでコントロールノードを向いているので、これをターゲットノードに向かせるために必要なパラメータです。

時刻をプレフィックスに入れる

.YYYYmmddくらいで十分な場合とか、@とか:使いたくねー、という場合に。
現在時刻を格納している変数があると思いきや、Ansibleにはなさそうですね。
ということで、処理の中でdateコマンドを実行して取得することにします。コピーはcopyモジュールで。

[ansible@controll ~]$ ansible host1 -m copy -a "src=test.txt remote_src=yes dest=test.txt.{{ lookup('pipe',
'date +%Y%m%d') }}"
host1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "dest": "./test.txt.20191223",
    "gid": 1000,
    "group": "ansible",
    "md5sum": "d3b07384d113edec49eaa6238ad5ff00",
    "mode": "0664",
    "owner": "ansible",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 4,
    "src": "test.txt",
    "state": "file",
    "uid": 1000
}

[ansible@target ~]$ ls
test.txt  test.txt.20191223  test.txt.4031.2019-12-23@21:29:44~  test.txt.bak

test.txt.20191223ができました。
lookupプラグインの第一引数にpipeを指定すれば、第二引数のコマンド実行結果を{{ }}に置換できます。
これは便利。第二引数はコントロールサーバの環境で実行される点に注意しましょう。時刻同期はしっかりと!

ホスト名と日付を合成

最後に証跡取得目的でバックアップを取りたい場合など。
情報取得はfetchモジュールを使えば取ってこれるんですが、何も考えずにやるとこうなります。

[ansible@controll ~]$ ansible host1 -m fetch -a "src=test.txt dest=/home/ansible"
host1 | CHANGED => {
    "changed": true,
    "checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "dest": "/home/ansible/host1/test.txt",
    "md5sum": "d3b07384d113edec49eaa6238ad5ff00",
    "remote_checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "remote_md5sum": null
}

[ansible@controll ~]$ ls /home/ansible
host1
[ansible@controll ~]$ ls -R /home/ansible/host1
test.txt

destで指定した/home/ansibleの下にhost1/test.txtができました。
fetchモジュールの基本動作は、「destで指定したディレクトリ + ホスト名ディレクトリ + ファイル(のフルパス)」という階層構造でファイルが保存されます。ファイルの上書き合戦が起こらないというすばらっ!な機能ですが、個人的にもうちょびっとスッキリさせたいです。
ということで、fetchモジュールのflatオプションとここまでのやり方をフル活用してみます。

[ansible@controll ~]$ ansible host1 -m fetch -a "src=test.txt dest=/home/ansible/test.txt.{{ inventory_host
name }}_{{ lookup('pipe','date +%Y%m%d' )}} flat=yes"
host1 | CHANGED => {
    "changed": true,
    "checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "dest": "/home/ansible/test.txt.host1_20191223",
    "md5sum": "d3b07384d113edec49eaa6238ad5ff00",
    "remote_checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "remote_md5sum": null
}
[ansible@controll ~]$ ls /home/ansible
host1  test.txt.host1_20191223

inventory_hostname変数は現在実行中のターゲットノード名が格納されています。
flatオプションはdestで指定したディレクトリ直下にsrc指定のファイルを配置しますが、工夫しないと上書き合戦が起こります。
ホスト名ごとに分けられればファイル名はバラバラになるため上書きされないはずです。
念のためもう1台ホストを追加して動作確認してみましょう(上のfetchで取ってきたファイルは消しておきました)。

[ansible@controll ~]$ ansible all -m fetch -a "src=test.txt dest=/home/ansible/test.txt.{{ inventory_hostname }}_{{ lookup('pipe','date +%Y%m%d' )}} flat=yes"
host1 | CHANGED => {
    "changed": true,
    "checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "dest": "/home/ansible/test.txt.host1_20191223",
    "md5sum": "d3b07384d113edec49eaa6238ad5ff00",
    "remote_checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "remote_md5sum": null
}
host2 | CHANGED => {
    "changed": true,
    "checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "dest": "/home/ansible/test.txt.host2_20191223",
    "md5sum": "d3b07384d113edec49eaa6238ad5ff00",
    "remote_checksum": "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15",
    "remote_md5sum": null
}

[ansible@controll ~]$ ls /home/ansible
host1  test.txt.host1_20191223  test.txt.host2_20191223

test.txt.host1_20191223とtest.txt.host2_20191223が作成されました。大丈夫そうです!

まとめ

今回はansibleコマンドで実行しましたが、playbookでも十分利用できそうなものが簡単に書けそうです。
シンプルな範囲でかゆいところにも手が届くJinja2すごい。