こんにちは、弊社サービスのインフラを運用している id:keijiu (ijikeman)です。
今回は、「Ansible実行を1/3に高速化した話」を記載します。
目次
- 目次
- 1. 実行時間の把握
- 2. Ansibleコードチューニング
- 3. Ansible実行環境チューニング
- 3-3. サードパーティ製 Strategy Plugin (Mitogen for Ansible)
- 3-4. 上記実行環境毎の実行速度の計測比較結果
- 4. 高速化の結果
背景
3年ほど前よりAnsible構成管理を推進を開始し、ようやくラクスの各商材のAnsible化の割合が大幅に増えました。
構成管理対象サーバの増加と構成管理範囲が増えるに伴い、Ansibleの実行完了までの時間が大幅に増えていきました。
また、インフラメンバー各自がAnsibleコードを記載し、テスト実行(--check)等をする回数も増えこの「実行時間の長時間化」が問題となりました。
この問題の改善にあたり、調査・検証し、実際に実行時間を大幅に削減した内容を公開させていただきます。
1. 実行時間の把握
まずはAnsibleの全体の実行時間や各タスク毎の実行時間の把握を行い、時間がかかっている処理がどの処理なのかを把握することが重要です。
各タスクの中で時間がかかりすぎているものを把握することで、処理や環境の見直しによって改善できるものがないかを確認します。
1-1. Callbackプラグイン[profile_tasks]を有効にする
各タスクの実行に掛かった実行時間を表示するために、callbackプラグインprofile_tasksを有効化します。
$ /etc/ansible/ansible.cfg --- [defaults] callback_whitelist = profile_tasks ---
もしくは
実行時にANSIBLE_CALLBACK_WHITELIST='profile_tasks'を設定
ANSIBLE_CALLBACK_WHITELIST='profile_tasks' ansible-playbook ...
1-2. CallBack Plugin有効化の確認
下記の様に各タスクの実行時間が表示され、実行完了後には時間がかかったタスクのTOP20までがリスト表示される様になります。
... RUNNING HANDLER [os : Install Epel.repo] ***************************************** Sunday 05 June 2022 19:33:32 -0400 (0:00:01.327) 0:00:01.356 ********** skipping: [192.168.0.1] PLAY RECAP ********************************************************************* 192.168.0.1 : ok=34 changed=1 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0 Thursday 05 May 2022 21:09:47 -0400 (0:00:00.058) 0:00:14.804 ********** =============================================================================== os : Install Epel.repo ------------------------------------------ 1.66s os : Install python module for SELinux -------------------------- 1.12s Gathering Facts ------------------------------------------------- 1.07s
2. Ansibleコードチューニング
2-1. 各タスク実行時間の確認
では、実行時間の把握ができるようになったところで、実際のPlaybookを実行しました。
未セットアップのCentOSに対するPlaybookの実行結果は以下のようになりました。(一部伏字)
圧倒的にパッケージのインストール時間が大半でしたので、まずはパッケージインストール処理のコードを確認してみます。
すると約6割がパッケージインストール処理でした。
Tuesday 07 June 2022 01:14:14 -0400 (0:00:03.472) 0:05:20.110 ********** common : Install Packages --------------------------------------------- 190.71s common : Copy Scripts ------------------------------------------------- 14.21s os : Server Reboot ------------------------------------------------------- 11.57s os : Server Reboot ------------------------------------------------------- 9.78s os : Install python module ----------------------------------------------- 8.90s os : Install NetworkManager -------------------------------------------- 6.51s ...
2-2. パッケージインストール処理の見直し
2-2-1. コードの確認
実コードは記載できませんが、サンプルコードで説明すると以下の様な処理になっていました。
- set_fact: INSTALL_PACKAGES: - NAME: 'httpd' REPO: 'appstream' - NAME: 'httpd-devel' REPO: 'appstream' - NAME: 'httpd-tools' REPO: 'appstream' - NAME: 'httpd-manual' REPO: 'appstream' - name: Install Packages yum: name: "{{ item.NAME }}" state: 'installed' enablerepo: '{{ item.REPO }}' with_items: "{{ INSTALL_PACKAGES }}"
実際にこのコードを実行すると以下のようになります。
パッケージを1つずつインストールしている為、パッケージ対象が増えれば増えるほど毎回yum moduleが呼び出され時間がかかる実装になっています。
--- 実行ログ TASK [test : Install Packages] ************************************************ Friday 06 May 2022 01:34:02 -0400 (0:00:00.024) 0:00:07.367 ************ changed: [192.168.0.1] => (item={'NAME': 'httpd', 'REPO': 'appstream'}) changed: [192.168.0.1] => (item={'NAME': 'httpd-devel', 'REPO': 'appstream'}) changed: [192.168.0.1] => (item={'NAME': 'httpd-tools', 'REPO': 'appstream'}) changed: [192.168.0.1] => (item={'NAME': 'httpd-manual', 'REPO': 'appstream'}) ...
2-2-2. コードの修正
以下ansible公式
ansible.builtin.yum module – Manages packages with the yum package manager — Ansible Documentation
のyum moduleのサンプルコードにもあるようにパッケージ名は配列にて渡すことができます。
- name: Install a list of packages with a list variable yum: name: "{{ packages }}" vars: packages: - httpd - httpd-tools
以下様に書き換えを行う「パッケージ名をリスト形式に変更する」ことで、yumモジュールの実行回数を大幅に削減することができます。
実際にこのサンプルコードでも実行時間を約半分にすることができました。
- set_fact: INSTALL_PACKAGES: - NAME: - 'httpd' - 'httpd-devel' - 'httpd-tools' - 'httpd-manual' REPO: 'appstream' --- 実行ログ TASK [test : Install Packages] ************************************************ Friday 06 May 2022 01:36:19 -0400 (0:00:00.023) 0:00:03.246 ************ changed: [192.168.0.1] => (item={'NAME': ['httpd', 'httpd-devel', 'httpd-tools', 'httpd-manual'], 'REPO': 'appstream'})
2-3. 動的書き換えの見直し
ラクスで使っているansibleコードテンプレートでは、汎用的な処理を別のタスクファイルにして、
「include_role」や「include_tasks」で動的に読み込み書き換えることで、コードの再利用性を高める取り組みを行っております。
その為、繰り返し処理を行うと毎回コードの動的書き換えと実行が行われる為、処理時間が伸びていきます。
2-3-1. コードの確認
以下のように汎用的な処理を別のロールに集めており
$ roles/libraries/tasks/copy.yml --- - name: FILE COPY copy: src: "{{ item.SRC }}" dest: "{{ item.DEST }}" owner: "{{ item.OWNER }}" group: "{{ item.GROUP }}" mode: "{{ item.MODE }}"
呼び出し元からinclude_role等で呼び出して利用しています。
$ roles/test/tasks/main.yml --- - name: Copy files include_role: name: libraries tasks_from: copy.yml with_items: - { SRC: 'test1', DEST: '/tmp/test1', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test2', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test3', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test4', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test5', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test6', OWNER: 'root', GROUP: 'root', MODE: '0644' }
実行すると、毎回copy.ymlが呼ばれる毎に処理が行われる為、毎回TASKが呼ばれます。
TASK [Copy files] ******************** Monday 06 June 2022 02:00:31 -0400 (0:00:01.356) 0:00:01.386 TASK [libraries : FILE COPY] ********* Monday 06 June 2022 02:00:31 -0400 (0:00:00.087) 0:00:01.474 changed: [192.168.0.1] TASK [libraries : FILE COPY] ********** Monday 06 June 2022 02:00:33 -0400 (0:00:01.099) 0:00:02.573 changed: [192.168.0.1] TASK [libraries : FILE COPY] ********** Monday 06 June 2022 02:00:33 -0400 (0:00:00.910) 0:00:03.484 changed: [192.168.0.1] TASK [libraries : FILE COPY] ********** Monday 06 June 2022 02:00:34 -0400 (0:00:00.869) 0:00:04.354 changed: [192.168.0.1] TASK [libraries : FILE COPY] *********** Monday 06 June 2022 02:00:35 -0400 (0:00:00.889) 0:00:05.243 changed: [192.168.0.1] TASK [libraries : FILE COPY] ********* Monday 06 June 2022 02:00:36 -0400 (0:00:00.854) 0:00:06.098 changed: [192.168.0.1] PLAY RECAP ********************************************* 192.168.0.1 : ok=7 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Monday 06 June 2022 02:00:37 -0400 (0:00:00.830) 0:00:06.928 *********** =================================================== Gathering Facts --------------- 1.36s libraries : FILE COPY ---------- 1.10s libraries : FILE COPY ---------- 0.91s libraries : FILE COPY ---------- 0.89s libraries : FILE COPY ---------- 0.87s libraries : FILE COPY ---------- 0.85s libraries : FILE COPY ---------- 0.83s Copy files --------------------- 0.09s
2-3-2. コードの修正
動的書き換え(include_*)を使わずに静的処理に変更します。
$ roles/test/tasks/main.yml --- - name: Copy files copy: src: "{{ item.SRC }}" dest: "{{ item.DEST }}" owner: "{{ item.OWNER }}" group: "{{ item.GROUP }}" mode: "{{ item.MODE }}" with_items: - { SRC: 'test1', DEST: '/tmp/test1', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test2', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test3', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test4', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test5', OWNER: 'root', GROUP: 'root', MODE: '0644' } - { SRC: 'test1', DEST: '/tmp/test6', OWNER: 'root', GROUP: 'root', MODE: '0644' }
Ansibleを実行するとわずかですが、実行時間が短くなりました。
書き換えを行う回数が増えるほど効果は高くなりますが、変更による効果はそれほど高くない為、「コードの再利用性」とどちらがよいかはで使い分ける必要がありそうです。
TASK [test : Copy files] *********************************** Monday 06 June 2022 02:01:26 -0400 (0:00:01.413) 0:00:01.437 *********** changed: [192.168.0.1] => (item={'SRC': 'test1', 'DEST': '/tmp/test1', 'OWNER': 'root', 'GROUP': 'root', 'MODE': '0644'}) changed: [192.168.0.1] => (item={'SRC': 'test1', 'DEST': '/tmp/test2', 'OWNER': 'root', 'GROUP': 'root', 'MODE': '0644'}) changed: [192.168.0.1] => (item={'SRC': 'test1', 'DEST': '/tmp/test3', 'OWNER': 'root', 'GROUP': 'root', 'MODE': '0644'}) changed: [192.168.0.1] => (item={'SRC': 'test1', 'DEST': '/tmp/test4', 'OWNER': 'root', 'GROUP': 'root', 'MODE': '0644'}) changed: [192.168.0.1] => (item={'SRC': 'test1', 'DEST': '/tmp/test5', 'OWNER': 'root', 'GROUP': 'root', 'MODE': '0644'}) changed: [192.168.0.1] => (item={'SRC': 'test1', 'DEST': '/tmp/test6', 'OWNER': 'root', 'GROUP': 'root', 'MODE': '0644'}) PLAY RECAP ********************************************************************* 192.168.0.1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Monday 06 June 2022 02:01:31 -0400 (0:00:05.252) 0:00:06.689 *********** =================================================== test : Copy files --------------------5.25s Gathering Facts -------------------1.41s
3. Ansible実行環境チューニング
3-1. 並列実行数の変更
Ansibleの標準設定では並列で実行するターゲット数が"5"と設定されている為、多くのターゲットに対して実行する場合に時間がかかります。
その為、並列実行数の変更を行います。
FORKS設定はAnsibleの子プロセスを増やす設定の為、増やせば増やすほどCPUへの負荷がかかります。
各CPUの使用率を確認し、不足しているようであればCPU数を増やすことを検討してください。
$ /etc/ansible/ansible.cfg --- [defaults] forks = 5
もしくは
実行時にANSIBLE_FORKS=NUMを設定
ANSIBLE_FORKS=20 ansible-playbook ...
3-2. Pipelining
ansibleのデフォルト設定の場合、各タスクを実行する度に以下の例の様にターゲット側の"~{ansible実行ユーザのホームディレクトリ}/.ansible/"にpythonプログラムの展開を行いAnsibleを実行しています。
その為、ターゲットへのデータ転送時間とこれらのファイルの書き込み等により、各タスクの実行に時間がかかります。
/home/ansible_user/.ansible/ansible-tmp-1651804170.1883245-2652826-75480101479031/ 合計 120 drwx------. 2 root root 31 5月 5 22:28 . drwx------. 3 root root 68 5月 5 22:28 .. -rw-------. 1 root root 119155 5月 5 22:28 AnsiballZ_stat.py
Pipeliningの有効化
Pipeliningを有効化することで、sshのpipeを経由してansibleを実行することで高速化を行うことができます。
$ /etc/ansible/ansible.cfg --- [ssh_connection] pipelining = True
※設定を記載するセクションは[defaults]ではなく[ssh_connection]であることに注意してください
もしくは
実行時にANSIBLE_PIPELINING=True か ANSIBLE_PIPELINING=1を設定
ANSIBLE_PIPELINING=1 ansible-playbook ...
公式ドキュメントAnsible Configuration Settings — Ansible Documentationにもある通り、
requrettyが設定されているユーザでansibleを実行する場合は、sudo設定にて無効にする必要がある為注意が必要です。
Pipelining有効化の確認
Ansible実行時にPipeliningが有効になっているかは、-vvvオプションを付けることで確認することができます。
以下の例の様に"Pipelining is enabled"と出力されていることを確認してください。
... Pipelining is enabled. <192.168.0.1> ESTABLISH SSH CONNECTION FOR USER: ansible_user ...
3-3. サードパーティ製 Strategy Plugin (Mitogen for Ansible)
Mitogen — Mitogen Documentation
Other Toolsとして公式で紹介されているMitogen for Ansibleを使うことで、上記Pipeliningよりも実行速度の高速化を行うことができます。
Mitogenは
- 実行コードのRAMへのキャッシュ
- pipelineと同様ターゲットへの書き込み削減
- 従来のPreforkモデルに加えて、Thread化によるさらなる並列化・高速化
- ネットワークコネクションの再利用等
によりAnsibleの実行を高速化することができます。
Mitogen利用時に気付いた点
Mitogen利用時に以下の事象が確認できましたので、利用するには事前に検証が必要です。
- 現行v0.2.9ではターゲットサーバ側に"/usr/bin/python"が必要(/usr/bin/python3ではダメ)
- ansible 2.10以降には未対応(2022/06/13時点でAnsible v2.10以上に対応するv0.3.xは未リリース 2022/06/13追記)
- ターゲットのpython環境によっては動かないコードがある
- include_roleは未対応
- CPU使用率がPipeliningよりかなり高い
- メモリの使用率が高い
- Pipeliningとの併用はできない
どなたかv2.10以降でmitogen for Ansibleが動くよというコメントをいただきましたが、公式ニュースに記載されているように
- v0.2.xは2.10未満
- v0.3.x系は2.10以上
をサポートという風にバージョンが分かれるようです(2022/06/13追記) Release Notes — Mitogen Documentation
Mitogenの利用方法
■Ansible実行環境にMitogenを設置
$ curl -kL -o https://networkgenomics.com/try/mitogen-0.2.9.tar.gz $ tar zxvf mitogen-0.2.9.tar.gz -C /opt/
■Strategyプラグインとしてmitogenを設定
$ ansible.cfg --- [defaults] strategy_plugins = /opt/mitogen-0.2.9/ansible_mitogen/plugins/strategy
■各playbook毎にstrategyを設定してmitogenを有効化します。(ansible.cfgで全体に適用してもよい)
$ playbook.yml --- - name: Test Playbook strategy: mitogen_linear hosts: test-servers ...
あるPlaybookの実行時間の差を確認したところ、以下様にかなりの差がありました。
Default状態 ... 0:00:22.399 Pipelining ... 0:00:17.744 Mitogen for Ansible ... 0:00:09.333
3-4. 上記実行環境毎の実行速度の計測比較結果
検証としてターゲットサーバ20台に対し、ディレクトリ10個作成する処理を行いました。
それぞれの環境毎に5回実行し、一番早い時間と一番遅い時間を除く3回の平均時間を記しています。
[検証結果]
AnsibleサーバCPU数 | FORK数 | CPU使用率 | 平均実行時間 | コメント |
---|---|---|---|---|
[Default] | 1 | 5 | 20 - 40% | 01:39 | 1 | 10 | 20 - 70% | 01:05 | 1 | 20 | 30 - 100% | 00:42 | CPUが100%となる場合があり処理待ちが発生 | 2 | 5 | 10 - 20% | 01:36 | 1 vCPUでもCPUに余裕があった為、CPU追加効果なし | 2 | 10 | 20 - 40% | 01:01 | 1 vCPUでもCPUに余裕があった為、CPU追加効果なし | 2 | 20 | 15 - 80% | 00:36 | CPU 100%が解消され処理時間が短縮 |
[Pipelining] | 1 | 5 | 30 - 40% | 00:40 | Pipeliningの効果が高いが若干CPU使用率が上昇 | 1 | 10 | 30 - 70% | 00:28 | Pipeliningの効果が高いが若干CPU使用率が上昇 | 1 | 20 | 30 - 100% | 00:21 | Pipeliningの効果が高いが若干CPU使用率が上昇 | 2 | 5 | 15 - 30% | 00:38 | 1 vCPUでもCPUに余裕があった為、CPU追加効果なし | 2 | 10 | 20 - 40% | 00:26 | 1 vCPUでもCPUに余裕があった為、CPU追加効果なし | 2 | 20 | 30 - 50% | 00:17 | CPU 100%が解消され処理時間が短縮 |
[Mitoge] | 1 | 5 | 100% | 00:18 | Pipeliningよりさらに効果が高いがCPU使用率がかなり上昇 | 1 | 10 | 100% | 00:16 | Pipeliningよりさらに効果が高いがCPU使用率がかなり上昇 | 1 | 20 | 100% | 00:16 | Pipeliningよりさらに効果が高いがCPU使用率がかなり上昇 | 2 | 5 | 25 - 50% | 00:19 | CPUに余裕がでたが、CPU追加効果なし | 2 | 10 | 40 - 60% | 00:16 | CPUに余裕がでたが、CPU追加効果なし | 2 | 20 | 30 - 70% | 00:14 | SWAPが発生 | 2+2GBメモリ | 20 | 50% | 00:14 | メモリを追加することでSWAPが解消 |
実行速度検証総括
- Pipelining
- Ansible標準機能として実装されている為、手軽に利用が可能で効果が高い為、標準で利用できるようにしておくとよい。
- Ansible実行側のCPU使用率の上昇が若干見られるが、実行環境の大幅な見直しは必要ない。
- 並列実行数(ANSIBL_FORKS)
- 並列実行数を上げて実行する場合はAnsible実行側のCPU使用率を確認して引き上げる必要がある。
- Mitogen for Ansible
- CPUの使用率が高い為、CPUの割り当て数に注意が必要。
- 並列実行数を増やすAnsible実行サーバのメモリ使用量が上がる為、メモリの割り当てに注意が必要。
- 初期導入作業が必要だが、Pipelineよりも効果が高い。
4. 高速化の結果
最終的に
Mitogen Plugin及び上記コードの改修等を進め
以下の様に
1/3に高速化することができました!!
■改修前 Tuesday 07 June 2022 01:14:14 -0400 (0:00:03.472) 0:05:20.110 **********
■改修後 Tuesday 07 June 2022 01:04:01 -0400 (0:00:02.327) 0:01:34.366 **********
終わり
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.comラクスDevelopers登録フォーム
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com