Edited at

Ansibleのタスクを適度に待たせて適切なタイミングで実行したい

この記事は #インフラ勉強会 Advent Calendar 10日目の記事です

https://adventar.org/calendars/3022#list-2018-12-10

技術系の話をしようかエモい系の話をしようかと迷いましたが、

エモい話で1枚書き切るほどネタが出てこなさそうだし、

気の利いたインフラエンジニアのための〇〇もパッとは浮かばないので、

フツーに最近よく使ってるAnsibleについて書きます。(なのでQiitaにしました)

一応最後にポエムにならない程度にインフラ勉強会のことについて書きました。


概要


Ansibleのタスクを適度に待たせて適切なタイミングで実行したい


というタイトルはざっくりしてしまっていますが、各モジュールの例で具体的にどういうことをしたいか書いています。

適度なタイミング、というのは色々ありますが、特に3つ目の取得した値が特定の値になるのを待つが今回お伝えしたいことです。

最初はshell/commandモジュールでsleep 60とか書いていたのですが、

60秒というマジックナンバーの根拠は??と言われるとないので答えに詰まりますし、

指定時間以上にかかったら事故るし逆に時間を伸ばしすぎて早く処理が終わってたら時間がもったいない、

とイケてないのでなんとか適切なタイミングで実行できるようにしたいと思いました。


特定の時間待つ/手動で再開を指示する

pauseモジュールを使います。名前の通り、待つモジュールです。

じゃあsleepコマンドと一緒じゃん、と思いましたが、

使ってみると意外といろいろできたので紹介します。

プレイブックはこんな感じにしました。


play.yml

- hosts: all

connection: local
gather_facts: false
tasks:
- name: pause
pause:
seconds: 5
register: result
- name: show result
debug:
var: result

普通に実行してみると

$ ansible-playbook -i localhost, play.yml

PLAY [all] **********************************************************************************************************

TASK [pause] ********************************************************************************************************
Pausing for 5 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [localhost]

TASK [show result] **************************************************************************************************
ok: [localhost] => {
"result": {
"changed": false,
"delta": 5,
"echo": true,
"failed": false,
"rc": 0,
"start": "2018-12-09 20:32:19.363536",
"stderr": "",
"stdout": "Paused for 5.0 seconds",
"stop": "2018-12-09 20:32:24.364480",
"user_input": ""
}
}

PLAY RECAP **********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

resultにあるように5秒待って次のタスクが実行されているようです。

そして出力に(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)とあるように

Ctrl+Cを押してみると、以下のように割り込みをかけられます。


'C'continueの場合

TASK [pause] ********************************************************************************************************

Pausing for 5 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
Press 'C' to continue the play or 'A' to abort
ok: [localhost]

TASK [show result] **************************************************************************************************
ok: [localhost] => {
"result": {
"changed": false,
"delta": 13,
"echo": true,
"failed": false,
"rc": 0,
"start": "2018-12-09 20:32:45.888910",
"stderr": "",
"stdout": "Paused for 13.95 seconds",
"stop": "2018-12-09 20:32:59.837578",
"user_input": ""
}
}

PLAY RECAP **********************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0


Ctrl+Cで割り込んだ場合は、本来何秒待つ設定だったかは関係なくなり、

すぐcを押せば即次のタスクが実行されますし、逆に設定以上に待たせてから実行させることもできます。

aを押せば中断になります。


'A'abortの場合

TASK [pause] ********************************************************************************************************

Pausing for 5 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
Press 'C' to continue the play or 'A' to abort
fatal: [localhost]: FAILED! => {"msg": "user requested abort!"}

NO MORE HOSTS LEFT **************************************************************************************************

PLAY RECAP **********************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1


ちなみに冒頭のプレイブックでは待機する秒数を指定していましたが、時間指定は必須ではありません。

その場合は、

TASK [pause] ********************************************************************************************************

[pause]
Press enter to continue, Ctrl+C to interrupt:

となり、Enterで次のタスク実行されます(Ctrl+Cの挙動は上と同じです)

ちなみに私自身はターミナルからではなく、JenkinsのPipelineで実行しているので、

プレイブックを分割して、Jenkinsのinputで別作業の完了待ちをしています。


ポートの応答を待つ

wait_forモジュール

個人的には待機系の方法として一番よく使われているんじゃないかと思ってる方法です。

主な使用用途としてはssh(22)やhttp(80)などのポートが応答あるまで待つ、というものです。

クラウドや仮想環境では仮想マシンの立ち上げタスクが完了したばかりではsshはまだ起動しておらず、

きちんと待機させないと後続のAnsibleタスクにコケることが多いのでよく使われているかと思います。

- name: wake up ec2

ec2:
key_name: mykey
instance_type: t2.micro
image: ami-123456
wait: yes
group: webserver
count: 1
vpc_subnet_id: subnet-123456
assign_public_ip: yes
register: ec2

- name: Wait 300 seconds for port 22
wait_for:
port: 22
host: '{{ ec2.instances.0.public_ip }}'

ちなみにec2モジュールのwaitはあくまでEC2のステート変化(この場合はrunning)を待つもののようです。

wait_forモジュールの使い方はもう一つあり、ファイルの監視が可能です。

ポート待ちの場合はportパラメータを入れましたが、ファイルの場合はpathパラメータを設定します。

(両者は排他的なので同時に設定することはできません)

また、search_regexで正規表現を設定でき、ファイルならファイルの中身、ポートなら応答メッセージの中身のマッチングまでやってくれます。

ちなみに調べていたときに初めて知ったのですが、

wait_for_connectionモジュールというAnsibleのためのsshポート待ちに特化したモジュールもありました。

使ってみたことはないですが、Ansible2.3から追加されたモジュールのようなので知名度が低いのかもしれませんね。


取得した値が特定の値になるのを待つ

前置きが長くなりましたが、今回のメインはこちらです。

Do-Until Loopsというループ機構を使用します。

例では、AMIの取得が完了するまで待って次のタスク(EC2のTerminateなど)に進みたい、ということをやっています。

最初はAnsibleでの書き方がわからずシェルスクリプトでwhile文でも書こうと思ったのですが、

なんかうまい方法ないかなーと調べているうちに見つけました。

こちらもプレイブックを見てもらったほうがわかりやすいと思います。

- name: create AMI

ec2_ami:
instance_id: i-xxxxxx
register: ami

- name: wait for finish creating AMI
ec2_ami_facts:
image_ids: "{{ ami.ami_id }}"
register: result
until: "{{ result.images.0.state }}" == "available"
retries: 100
delay: 10

2つ目のタスクのuntilの箇所にあるように、

条件式を書くことで結果がTrueになるまでタスクを繰り返してくれる、というものになります。

固有のモジュールではなく、with_itemsloopのような繰り返しの制御機構なので、

様々なモジュールのタスクと組み合わせることができます。

特に今回のような****_factsモジュールやurlモジュールなどでAPIをPOSTした後にGETで完了待ちするなど、使い所は結構ありそうです。

retriesのデフォルト値が1なので、有限の回数を指定しなければいけないのがちょっと辛いですが、

untilの条件をしっかり書いておけば大きすぎる数値を入れても基本的には問題ないと思います。

ちなみにec2_amiモジュールにもwaitがあり、これをyesにしてあげれば要件を満たせそうですが、

以下のようなエラーが出てしまったのでuntilのような待ち方を調べた、というのが経緯だったりします。

https://github.com/ansible/ansible/issues/40303

(ちなみにAmazonLinux1ではこのエラーが出たけど、WSLのUbuntu18.04ではエラーが出なかったりで何かしらのバージョン依存の問題かも?)


まとめ

例を見て、カンのいい人は「これPackerで良くね?」と思われるかと思います。

はい、最初はPackerで書いてたんですよ…でもね・・・あとはお察しください。

Ansibleのモジュールレベルの機能は調べてくるとポンポン出てくるの学習しやすいのですが、

Ansibleの動きを制御する方法の知識ってイマイチ増えてないなあ、

と今回の課題にぶち当たったときに感じました。

人間勉強している気になっても、なかなか問題が起きないと新しいものには目が行かないものですね。


インフラ勉強会について

せっかくなのでポエムにならない程度にインフラ勉強会について書きたいと思います。

インフラ勉強会に参加したのは確か今年の1月半ばぐらいだったはず(記憶怪しい)なのでもう約1年経つのですが、

オフラインの勉強会にはそれ以前からちょくちょく参加していました。

しかし、登壇することはない聞き専でしたし、コミュ力もお察しなので懇親会でもあまり交流できていませんでした。

一応何らかのユーザグループではありますが、登壇者や他の参加者と "壁" みたいなものを感じていたんだと思います。

それに対してインフラ勉強会は、


  • セッション中にリスナー同士で会話できる

  • その流れで登壇者への質問もしやすい

  • セッション以外の時間も様々なトピックや雑談で会話してる

  • 開催ペースがDaily(オフラインだとハイペースでもMonthly)

という状況もあって、色んな人が初登壇に挑戦しやすい流れができていて、私もその流れに乗ることができました。

インフラ勉強会の初期の段階では入れたのも良かったと思いますが、

オフラインの勉強会で強く感じていた"壁"がない!というのがインフラ勉強会の最大の強みだと私は感じてます。

最近ちょっとリアルイソガシイであまり参加できてませんが、1周年イベントもありますし、

コミュニティに貢献!とまではいかないまでも、またちょくちょく顔を出したいと思っています。

以上です!

明日のカレンダーはanchor_cableさんです!