firewalldを切り忘れていたために、httpアクセスが通らずに3時間ほど悩んだ。 しかもIEが「DNSエラー」と表示してくれたおかげで、名前解決を疑って遠回りしてしまった。 自分への戒めのために、Ansibleでfirewalldを無効化する方法を残しておく。
- name: firewalldの無効化 systemd: name: firewalld state: stopped enabled: false
ディレクトリのコピーが正常終了したかどうかを、ファイルのダイジェストを元に確認したかった。 コマンドの組み立てに、いくらかの調べものと試行錯誤をしたので、メモしておく、 結果的に、コマンドは以下に落ち着いた。
$ find -type f -print0 | sort -z | xargs -0 sha1sum
ハッシュ関数はsha1sumを利用した。 md5sumでもsha256sumでもよかったが、sha256sumだと実行時間が体感でsha1sumの倍ぐらいあったということで、sha1sumにしておいた。
執筆時点で、ファイル破損の検出にどのハッシュ関数を使うのがスタンダードなのかはよく分からない。 参考までに、CentOSのダウンロードサイトではsha1sumとsha256sumの計算結果がアップロードされていた。 また、Ubuntuのダウンロードサイトではmd5sum、sha1sum、sha256sumの計算結果がアップロードされていた。
xargsで空白および記号を含むファイル名を正しく取り扱えるように、データ(ここではファイルのパス)の区切りを空白や改行ではなく、NUL (\0)にする。 パイプによる連結を想定しているコマンドは、セパレータをNULにするオプションを備えているものが多いように思う。 ここで利用しているコマンドとオプションは以下。
コマンド | オプション |
---|---|
find | -print0 |
sort | -z |
xargs | -0 |
CentOS7でSELinuxを無効化するPlaybookを書くのが一苦労だったので、試行錯誤の結果を記録しておく。
CentOS 7.4をyum updateで最新化して、ansibleをインストールした環境。
$ uname -srvm Linux 3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018 x86_64 $ cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) $ ansible-playbook --version | grep -E '^ansible-playbook' ansible-playbook 2.4.2.0 $ python --version Python 2.7.5
CentOS 7.4をインストールした直後の環境。
$ uname -srvm Linux 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 $ cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) $ python --version Python 2.7.5
ここではselinux-disablerというロールを作成した。 tasks/main.ymlは以下の通り。
- name: (1) SELinux用のPythonモジュールをインストール yum: name=libselinux-python state=installed - name: (2) SELinuxの無効化 selinux: state=disabled register: selinux - name: (3) SSHのポート番号の取得 set_fact: ssh_port: "{{ hostvars[inventory_hostname].ansible_port if 'ansible_port' in hostvars[inventory_hostname] else 22 }}" when: selinux.reboot_required - name: (4) マシンのリブート shell: "sleep 2 && reboot" async: 1 poll: 0 when: selinux.reboot_required - name: (5) マシンの停止を待ち合わせ local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=stopped when: selinux.reboot_required - name: (6) マシンの起動を待ち合わせ local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=started when: selinux.reboot_required
- name: (1) SELinux用のPythonモジュールをインストール yum: name=libselinux-python state=installed
モジュールselinuxを利用するためには、事前にlibselinux-pythonのインストールが必要。
- name: (2) SELinuxの無効化 selinux: state=disabled register: selinux
モジュールselinuxを利用してSELinuxを無効化。 以前は冪等性が正しく実装されていなかったのか、ググるとコマンドgetenforceの実行結果を元にモジュールselinuxを実行するかどうかを判断してるサンプルが多数引っかかる。 例えば以下のような感じ。
- name: SELinuxの状態を取得 shell: getenforce changed_when: False register: selinux_state - name: SELinuxの無効化 selinux: state=disabled when: selinux_state.stdout != 'Disabled'
ドキュメント的には、Ansible 2.4からモジュールselinuxのreturn valuesについての説明が加わっているので、このあたりで整備されたと思われる。
なお、リブートが必要かどうかはモジュールselinuxの戻り値のreboot_requiredを調べると分かる。
- name: (3) SSHのポート番号の取得 set_fact: ssh_port: "{{ hostvars[inventory_hostname].ansible_port if 'ansible_port' in hostvars[inventory_hostname] else 22 }}" when: selinux.reboot_required
ここは、単に好き嫌いの問題。 ハードコーディングが気にならないのであれば、わざわざSSHのポート番号をひねくり出さなくても、wait_forでの待ち合わせで以下のように「port=22」にしておけばよい。
- name: マシンの起動を待ち合わせ local_action: wait_for host={{ inventory_hostname }} port=22 state=started
ネット上にあるサンプルでは、上記のようにwait_forの待ち合わせでSSHのポート22がハードコーディングされているものが殆どだった。 ハードコーディングが嫌いなので標準の変数がないか探したところ、ansible_portがそれに該当することが分かった。 さらに、ansible_portはhostvars経由で参照するようなので、以下のように試してみた。
- name: マシンの起動を待ち合わせ local_action: wait_for host={{ inventory_hostname }} port={{ hostvars[inventory_hostname].ansible_port }} state=started
しかし、この変数は以下のようにインベントリで明示的に指定している場合は使えるのだが、そうでない場合は「未定義」となり、参照箇所でエラーが発生する。
[server] 192.168.8.3 ansible_port=22
なんで適切なデフォルト値で変数を準備してくれないんだよ、と思って調べてみると、以下の報告があった。
結論は、バグではなく仕様と言い切っている。
but if you want to ensure a variable value is always available for use in templates and stuff, you'll need to set it yourself- the connection plugins don't have a way to smuggle out the value they used.
文意を崩さないように心がけつつ意訳。
もし、テンプレートでの利用か何かの都合でansible_portの値が必ず存在していてほしいのであれば、自分自身でansible_portの値を明示的に設定しておく必要がある。 なぜならば、現在のところコネクションプラグインが利用している内部的なデフォルト値を外に出す方法は準備されていないからだ。
つまり、SSHのプラグインでは22がデフォルトで、WinRMのプラグインでは5986がデフォルトだが、これらのプラグインがデフォルト値をansible_portなどに書き出す仕組みは現状実装されていないので諦めてくれ、ということらしい。 仕方がないので、以下のようにした。
set_fact: ssh_port: "{{ ansible_portが存在するならその値、存在しなければ22 }}"
最終的に、Jinja2 (Python)で書くと以下のようになった。
set_fact: ssh_port: "{{ hostvars[inventory_hostname].ansible_port if 'ansible_port' in hostvars[inventory_hostname] else 22 }}"
- name: (4) マシンのリブート shell: "sleep 2 && reboot" async: 1 poll: 0 when: selinux.reboot_required
コマンドrebootを利用してリブートする。
ただし、単純にrebootを実行するとSSHの切断が検知されてplaybookの実行に失敗する。 そのためasyncを利用して非同期実行を行う。 その際には(結果確認の?)ポーリングをオフにするために、pollに0を指定しておく。
また、rebootと同時に実行しているsleepは何らかのタイミング調整だが、詳細は把握しきれていない。 asyncの値とsleepの引数の値には何か制約がありそうな気がする。 例えば、async < sleepでなければならない、等。 試しにsleepを削除したところ、SSHの切断が検知されてplaybookの実行が失敗した。
詳細はそのうち整理しよう。
- name: (5) マシンの停止を待ち合わせ local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=stopped when: selinux.reboot_required
SSHのポートがクローズされることで、マシンが停止したと判断している。 ググって見つかるリブートのサンプルには、起動の待ち合わせを遅延実行することで、停止の待ち合わせを省略しているものもある。 例えば、以下のように。
- name: マシンのリブート shell: "sleep 2 && reboot" async: 1 poll: 0 - name: マシンの起動を待ち合わせ (delay=30により、30秒後から待ち合わせ開始) local_action: wait_for host={{ inventory_hostname }} port=22 delay=30 state=started
停止の待ち合わせが本当に必要かどうかはよく分からないが、delayの値をうまく決められないので、今回は丁寧に停止の待ち合わせをしておくことにする。
- name: (6) マシンの起動を待ち合わせ local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=started when: selinux.reboot_required
SSHのポートがオープンされることで、マシンが起動したと判断している。
以下の(2)で、モジュールselinuxがSELinuxを無効化している。 ただしその反映にはリブートが必要なので、後続の(3)〜(6)のリブート処理を実行している。
PLAY [server] ****************************************************************** TASK [Gathering Facts] ********************************************************* ok: [192.168.8.3] TASK [selinux-disabler : (1) SELinux用のPythonモジュールをインストール] ********************** ok: [192.168.8.3] TASK [selinux-disabler : (2) SELinuxの無効化] ************************************** [WARNING]: SELinux state change will take effect next reboot changed: [192.168.8.3] TASK [selinux-disabler : (3) SSHのポート番号の取得] ************************************* ok: [192.168.8.3] TASK [selinux-disabler : (4) マシンのリブート] ***************************************** changed: [192.168.8.3] TASK [selinux-disabler : (5) マシンの停止を待ち合わせ] ************************************* ok: [192.168.8.3 -> localhost] TASK [selinux-disabler : (6) マシンの起動を待ち合わせ] ************************************* ok: [192.168.8.3 -> localhost] PLAY RECAP ********************************************************************* 192.168.8.3 : ok=7 changed=2 unreachable=0 failed=0
以下の(2)で、モジュールselinuxの冪等性の検査が正しく行われて、結果がokになっている。 またリブートは不要なので、後続の(3)〜(6)のリブート処理をスキップしている。
PLAY [server] ****************************************************************** TASK [Gathering Facts] ********************************************************* ok: [192.168.8.3] TASK [selinux-disabler : (1) SELinux用のPythonモジュールをインストール] ********************** ok: [192.168.8.3] TASK [selinux-disabler : (2) SELinuxの無効化] ************************************** ok: [192.168.8.3] TASK [selinux-disabler : (3) SSHのポート番号の取得] ************************************* skipping: [192.168.8.3] TASK [selinux-disabler : (4) マシンのリブート] ***************************************** skipping: [192.168.8.3] TASK [selinux-disabler : (5) マシンの停止を待ち合わせ] ************************************* skipping: [192.168.8.3] TASK [selinux-disabler : (6) マシンの起動を待ち合わせ] ************************************* skipping: [192.168.8.3] PLAY RECAP ********************************************************************* 192.168.8.3 : ok=3 changed=0 unreachable=0 failed=0
SELinuxを無効化した後にすぐにリブートするかどうかは要件次第なので、場合によってはハンドラにリブート処理を書いたほうがよいと思われる。
コンテキストメニューのスクリーンショットなどは[Alt]+[Print Screen]ではうまく取れないので、ツールを探してみた。 Windows 7から、標準で「Snipping Tool」というツールがある模様。
開発者向け情報
.NET視点でのAPI (コマンドレット関連?)
MSDNの公式ブログ
TechNetの公式ブログ
ベストプラクティスなど
PythonのYAML (PyYAML) の調べものをする際の情報源を集めておく。 デファクトの割には、どの情報が正しいのか(最新なのか)が分かりにくかったので。
現在の本家?
過去の本家? まだ全部はGitHubに移行しきれていない?
正式ドキュメント?
Python本家による概説
Python YAML package documentation
PyYAMLの使い方