はじめに
Red Hat でコンサルタントをしている id:nanodayo です。
タイトル通り、Ansible Playbook で極端にループが遅くなった話となります。
※実案件での知見ですが、案件特有の情報を載せない形であればOKと、お客様の許可を得て記事にしています。
Ansible - Qiita Advent Calendar 2024 - Qiita の12/21 の記事になります(1日遅れ)
結論(わかったこと)
- include_tasks や flatten を伴うループは、ループ数が多いと極端に遅くなる(測定環境では500ループで5h程度)
- ループ数100では 3min 程度で実行完了する。(単純な比例ではない)
なお、根本原因は調べておらず、あくまで事象の紹介のみとなります。
背景: 案件で受けた Playbook のパフォーマンス改善の相談
- ユーザを作成するPlaybook(Role)実行に時間がかかっている(ユーザ数は500くらい・時間は5時間くらい)
- 実行中、AAPホストのCPU利用率が高くなっている。
- LDAPなど、ユーザ認証関連での通信はない状態
やったこと
ソフトウェアレベルで再現環境を構築
RPM, pip でバージョン指定でパッケージをインストールし、同一バージョンのソフトウェアが入っている環境を構築しました。(手順は割愛)
他、仮想マシンで環境を構築しましたが、お客様の環境と同等スペックのVMを作成しました。(メモリ・コア数)
お客様と同じ Playbook (Role) で測定
顧客情報のため、詳細は割愛しますが、Role は以下のような作りになります。
- input 情報は作成したいユーザのリスト(user 用の辞書構造のリスト)
- 内部構造として、loop 分の中で include_tasks を用いている。(上記のリストを flatten=1 して指定)
特徴を元に chatGPTに書かせたものが以下になります。
. ├── main.yml ├── create_user.yml └── vars.yml
- playbook (実際は role の tasks/main.yml がこのような内容でした)
--- - name: Create users based on the given list hosts: localhost vars_files: - vars.yml # ユーザリストを外部のvarsファイルから読み込む tasks: - name: Loop through users and create them include_tasks: create_user.yml loop: "{{ users|flatten=1 }}" loop_control: loop_var: user
- create_user.yml
--- - name: Create a user user: name: "{{ user.name }}" # ユーザ名 state: present # ユーザが存在することを確認 password: "{{ user.password }}" # パスワード (プレーンテキスト) shell: "{{ user.shell | default('/bin/bash') }}" # デフォルトのシェル comment: "{{ user.comment | default('No comment') }}" # コメント create_home: "{{ user.create_home | default(true) }}" # ホームディレクトリを作成するか
- 想定データ(vars)
--- users: - name: "alice" password: "alicepassword" comment: "Alice User" shell: "/bin/zsh" - name: "bob" password: "bobpassword" comment: "Bob User" - name: "charlie" password: "charliepassword" comment: "Charlie User" create_home: false
ダミーデータで測定
ダミーデータで100ユーザ分の vars で実行したものの、Job 実行にかかった時間は 3分程度でした。
当時は「これを5倍にしても15分だしなぁ」と、何か別の差異があると考え、データ量は考慮しておりませんでした。
再現度合いを上げるためにパスワード文字列を複雑にする等の試行錯誤をしましたが、実行時間に大きな影響は出ませんでした。
お客様の実データで測定
データ構造の複雑さに起因しているのではと考え、実データ(vars)を共有いただいて測定したところ、500ユーザの作成に5時間かかる事象が確認できました。
測定の小ネタ
profile_tasks を設定
どの task に時間がかかっているかを特定するために設定。 参考: AnsibleのCallback Pluginをためしてみよう - 赤帽エンジニアブログ
task の timestamp を確認
Role の構造上、include_tasks を経由して全ての task が実行されているため、profile_tasks での出力は include_tasks の時間のみが表示され、内訳を確認するに至りませんでした。 以下のように、被疑箇所となる候補が複数あったため、task 実行時のタイムスタンプが手がかりとなりました
- include_tasks に時間がかかっている
- vault で暗号化したパスワードの復号化
- user の作成処理そのものに時間がかかっている
事象が再現した際は、タイムスタンプと、ログの表示のされ方から、include_tasks に極端に時間がかかっていることが確認できました。
リアルタイムに眺めていたら、以下のような具合に、include_tasks に入ったところで何10分もそのまま でした。
多数のループと相性の悪かった記述
構造上、include_tasks や flatten を使わない形に修正できたので、その上で測定すると以下のようになりました。
include_tasks あり | include_tasks なし | |
---|---|---|
flatten=1 あり | 遅い | 遅い |
flatten=1 なし | 遅い | 速い |
include_tasks or flatten のどちらかがあると、今回のデータ量では実行時間が5時間近くになりました。
調べてないこと
深掘りの余地のあるトピックですが、根本原因は調べていません。
例えば、以下は深掘りの余地がありそうです。
- 「極端に遅くなるループ数」のしきい値はどのくらいか(100から500の間にあるはず)
- 上記の数は何に起因するか(ハードウェアスペックで変わるか)
今回はユーザの作成で発覚しましたが、一定数のループ+(flatten or include_tasks) で確認できた事象ですので、task としては debug module でも確認可能と思います。
反響があれば調べるかもしれません。それでは Happy Automation。