ControlPersistとPipeliningでAnsibleのパフォーマンスを最大化する
SSH接続の再利用(ControlPersist)とモジュール実行の効率化(Pipelining)を有効にすることで、Ansibleプレイブックのパフォーマンスを大幅に向上させましょう。このガイドでは、特に大規模環境での実行時間を短縮するための重要な洞察と実践的な設定を提供します。より高速で効率的なIT自動化のために、`ansible.cfg`をチューニングする方法を学びます。
ControlPersistとPipeliningでAnsibleのパフォーマンスを最大化する
Ansibleの実行が遅いと、その原因は通常、ネットワーク上で何が起こっているかを観察するまで謎に包まれています。20の小さなタスクを100台のホストに対して実行するプレイブックは、SSHセッションの開始、一時モジュールファイルのコピー、Pythonの実行、出力の収集、接続のクローズに驚くほど時間を費やすことがあります。リモートホストでの作業自体はミリ秒で完了するかもしれませんが、接続のオーバーヘッドが何度も繰り返されます。
この問題に対して、SSH接続の再利用(ControlPersist)とAnsibleのPipeliningという2つの設定が役立つことがよくあります。これらは魔法のスイッチではなく、遅いパッケージミラーや過負荷のデータベース、重い処理を行うタスクを修正するものではありません。しかし、回避可能な通信オーバーヘッドを削減します。これはまさに、多くの小さなタスクからなるプレイブックが時間を無駄にしているポイントです。
まずは現状の痛みを測定する
設定を変更する前に、タイミングを有効にしてプレイブックを一度実行してみましょう。
ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml
そのコールバックがインストールされていない場合は、CIシステムの組み込みタイミング出力を使用するか、コマンドをtimeでラップします。完璧なベンチマークを目指す必要はありません。ベースラインを取得し、遅いタスクが実際の作業なのか、多くのホストで繰り返される小さなタスクなのかを把握することが目的です。
有用なスモークテストとして、代表的なインベントリに対してアドホックなpingを実行します。
time ansible all -m ping
これを2回実行します。接続再利用を設定した後、2回目の実行が大幅に高速化された場合、SSHセットアップのコストが問題の一部であったことが確認できます。
ControlPersistが変えるもの
ControlPersistはOpenSSHの機能です。同じホスト、ユーザー、ポートへの後続のSSHコマンドが再利用できるように、マスターSSH接続を一定時間維持します。Ansibleは通常、タスクごとにSSHを使用するため、接続の多重化により繰り返されるハンドシェイクが排除されます。
実用的なプロジェクトレベルのansible.cfgは次のようになります。
[defaults]
forks = 20
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
ソケットディレクトリをプライベートなパーミッションで作成します。
mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp
ControlMaster=autoは、利用可能なマスター接続があればそれを使用し、なければ新しく作成するようSSHに指示します。ControlPersist=600sは、最後のセッションが終了してから10分間マスター接続を維持します。この値は絶対的なものではありません。短いCIジョブの場合は数分で十分かもしれません。メンテナンスウィンドウ中にプレイブックを繰り返し実行するオペレーターにとっては、10分や15分が快適な場合があります。
ControlPathは人々が考える以上に重要です。多くのシステムではUnixソケットパスに長さ制限があります。深いワークスペースパス内の長いインベントリホスト名は、混乱を招くエラーで多重化を壊す可能性があります。~/.ansible/cpのようにパスを短く保つことで、その種の障害を回避できます。
最近のAnsibleバージョンでは、多くの通常の設定でSSH多重化がデフォルトで有効になっていますが、プロジェクトのansible.cfgに明示的に設定することで、動作の監査が容易になります。アクティブな設定は以下で確認できます。
ansible-config dump --only-changed
Pipeliningが変えるもの
Pipeliningがない場合、Ansibleはモジュールをリモートホストの一時ディレクトリにコピーし、実行し、クリーンアップすることがよくあります。Pipeliningを有効にすると、AnsibleはモジュールコードをSSH接続経由で渡すことができ、一時ファイルの書き込みを減らせます。これにより、ラウンドトリップとリモートファイルシステムの作業が節約されます。
同じファイルで有効にします。
[ssh_connection]
pipelining = True
または、1回の実行でテストします。
ANSIBLE_PIPELINING=True ansible-playbook site.yml
この設定は、プレイブックにfile、lineinfile、template、user、service、短いコマンドタスクなど、多くの小さなモジュールがある場合に最も効果を発揮します。タスクのほとんどがパッケージのインストール、ソフトウェアのビルド、大きなアーティファクトの転送、外部サービスの待機に費やされる場合、その効果は薄れます。
sudo requirettyの罠
Pipeliningの古典的な問題は、sudoersのrequirettyです。一部の古いエンタープライズLinux設定では、sudoにTTYが必要でした。Pipeliningは、Ansibleが非対話的にSSH経由で作業をストリーミングしようとするため、その要件とうまく機能しません。
sudoersを注意深く確認してください。/etc/sudoersを通常のエディタで編集しないでください。visudoを使用します。
sudo visudo
次のようなグローバル行がある場合、
Defaults requiretty
それを削除するか、Ansible自動化ユーザーに対してオーバーライドする必要があるかもしれません。
Defaults:ansible !requiretty
この変更は、組織のセキュリティポリシーに適合する場合にのみ行ってください。最近の多くのディストリビューションでは、requirettyはデフォルトで有効になっていません。
より安全な組み合わせ設定
多くのチームにとって、これは妥当な出発点です。
[defaults]
forks = 20
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 86400
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
pipelining = True
forksは並列性を制御します。パフォーマンスに関連しますが、同じ最適化ではありません。forksを5から20に上げると、コントロールノードとネットワークが処理できる場合に役立ちます。上げすぎると、踏み台ホスト、パッケージリポジトリ、または管理対象ノード自体に過負荷がかかる可能性があります。
ファクトキャッシングは、別の種類の遅さに役立ちます。プレイブックが実行のたびにファクトを収集し、ファクトが毎分最新である必要がない場合、キャッシングは繰り返されるセットアップ作業を削除できます。意図的に使用してください。古いファクトは、現在のメモリ、ディスク、インターフェースデータに依存している場合、混乱を招く可能性があります。
自分を欺かないテスト方法
本番環境に似たサブセットでテストしてください。同じサブネット上の5つのアイドル状態のテストVMでは、踏み台の背後にある100台の混在ホストについて多くを語ることはできません。
変更の前後で同じプレイブックを実行します。コールド比較が必要な場合は、テスト実行の間に既存のコントロールソケットをクリアします。
rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web
次に、ウォーム比較を実行します。
time ansible-playbook site.yml --limit web
小さな繰り返しタスクに費やす秒数が減っているかどうかを確認します。また、障害モードにも注意してください。becomeを使用するタスクが失敗し始めた場合は、Pipelining自体を非難する前にsudo設定を調査してください。
これらの設定がほとんど役に立たない場合
ControlPersistとPipeliningは、リモートでの遅い作業を高速化するわけではありません。apt updateがミラーを待っている場合、接続の再利用は役に立ちません。アプリが接続をドレインするためにサービス再起動が30秒待つ場合、Pipeliningは無関係です。プレイブックが2 GBのアーティファクトをすべてのホストにコピーする場合、アーティファクトの配布、キャッシング、またはローカルパッケージリポジトリに焦点を当ててください。
また、これらは冪等なプレイブック設計に取って代わるものではありません。シェルコマンドを無条件に実行するプレイブックは、依然として時間を無駄にします。現在の状態を検出できるモジュールを使用し、適切な場合はコマンドタスクにcreatesまたはremovesを追加し、プレイで使用しない場合はファクト収集を避けてください。
実用的なアプローチはシンプルです。短くプライベートなコントロールパスでControlPersistを有効にし、sudoポリシーでPipeliningをテストし、forksを徐々に調整し、毎回同じインベントリとワークロードで測定します。多くの実際のAnsible環境では、これらの変更により、繰り返されるオーバーヘッドを隠すのではなく除去するため、動作の遅い実行が許容範囲内になります。
踏み台ホストとジャンプホスト
多くのインベントリは管理対象ノードに直接接続しません。踏み台を経由します。ControlPersistは依然として役立ちますが、コントロールノードから踏み台、踏み台からターゲットへの両方の接続を考慮する必要があります。
一般的なインベントリ変数は次のようになります。
[private]
app01 ansible_host=10.0.10.11
app02 ansible_host=10.0.10.12
[private:vars]
ansible_user=ansible
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'
すべてのタスクが繰り返しジャンプ接続を構築する場合、踏み台はオーバーヘッドの一部になります。コントロールパスを短くプライベートに保ち、踏み台のSSH接続制限に注意してください。10台のホストに対して正常に動作するプレイブックでも、forksを50に上げると小さな踏み台に過負荷がかかる可能性があります。
古いSSHクライアントの場合、ProxyJumpの代わりにProxyCommandが表示されることがあります。
ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'
Ansibleを非難する前に、プレーンなSSH接続をテストしてください。
ssh -o ProxyJump=bastion.example.com [email protected] hostname
それが遅い場合、Ansibleも遅くなります。
実際のプレイブックにおけるPipeliningとbecome
Pipeliningは権限昇格と連携しないという古いアドバイスは、あまりにも広範です。Pipeliningはbecomeと連携できます。一般的な障害は、sudoがTTYを必要とするか、非対話型実行を妨げるポリシーがある場合です。唯一の信頼できる答えは、プレイブックが使用するのと同じbecome設定でテストすることです。
小さなテストプレイで十分です。
- hosts: all
become: true
gather_facts: false
tasks:
- name: Confirm privileged command works
ansible.builtin.command: id
changed_when: false
Pipeliningを有効にして実行します。失敗した場合は、sudoers、認証プロンプト、自動化ユーザーがパスワードプロンプトなしで必要なコマンドを実行できるかどうかを確認してください。大規模な自動化実行におけるパスワードプロンプトは脆弱であり、タイミング測定もノイズが多くなります。
依存関係を考慮したforksの調整
forksを増やすことは、すぐに目に見える変化をもたらすため魅力的です。しかし、新たなボトルネックを生み出す可能性もあります。1つのプレイがすべてのホストで同時にパッケージキャッシュを更新する場合、高いfork数はパッケージミラーに負担をかける可能性があります。すべてのホストが同時に再起動し、同じデータベースに再接続する場合、データベースはその影響を受けます。
測定されたアプローチの方が優れています。
[defaults]
forks = 10
プレイブックを実行します。20を試します。次に30を試します。コントロールノードのCPU、SSHプロセス数、踏み台の負荷、ネットワーク飽和、パッケージリポジトリ、変更中のサービスを監視します。最速の設定は、必ずしも最も並列性が高いものとは限りません。ローリングアプリケーションデプロイメントの場合、可用性を保護するためにプレイブックでserialを使用することもできます。
- hosts: web
serial: 10
forksはAnsibleが一度に実行できる量を制御します。serialはプレイが一度に処理するホスト数を制御します。これらは異なる問題を解決します。
古いコントロールソケットのクリーンアップ
コントロールソケットは通常自動的にクリーンアップされますが、ラップトップがスリープしたり、CIジョブが強制終了されたり、ネットワークパスが変更されたりします。SSHが多重化エラーを報告し始めた場合は、古いソケットを削除してください。
rm -f ~/.ansible/cp/*
これは、それらのソケットに対してアクティブなAnsible実行がない場合に安全です。共有自動化ランナーでは、コントロールソケットを共有書き込み可能ディレクトリに配置しないでください。各自動化ユーザーは独自のプライベートパスを持つ必要があります。
インベントリ設計がパフォーマンスの向上を台無しにする可能性がある
接続のチューニングは役立ちますが、インベントリ設計がすべてを遅くする可能性があります。クラウドAPIをゆっくり呼び出す動的インベントリスクリプト、高価なルックアップを実行するグループ変数、決して触れないホストのファクトを収集するプレイブックは、SSHが開始される前に遅延を追加する可能性があります。
最初のタスクの前に実行が一時停止する場合は、インベントリのロードをプロファイリングしてください。プラグインがサポートしている場合は、動的インベントリをキャッシュします。解析時に高価な変数ルックアップを避けてください。ホストパターンを厳密に保ちます。
ansible-playbook site.yml --limit web:&prod
このコマンドは、webとprodの両方のホストを対象とします。allに対して広範なプレイを実行し、when条件でほとんどのタスクをスキップすると、時間を無駄にし、出力が読みにくくなります。
状態が関連する場合は、より少なく、より明確なタスクを優先する
Ansibleの可読性は重要ですが、過度に小さなタスクは接続オーバーヘッドをより顕著にする可能性があります。1つの設定ファイル内の10の関連行を10の個別のlineinfileタスクで設定する場合、タスクのオーバーヘッドを10回支払い、ロールバックの推論が難しくなります。テンプレートの方が明確で高速な場合があります。
- name: Render application config
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/app.conf
mode: '0644'
notify: restart app
これは、Ansible内で巨大なシェルスクリプトを使用することを推奨するものではありません。適切なレベルで望ましい状態をモデル化することを思い出させるものです。1つの設定ファイルに対して1つのテンプレートは、多くのマイクロ編集よりも優れていることがよくあります。
時期尚早なグローバル変更を避ける
可能な場合は、パフォーマンス設定をプロジェクトのansible.cfgに配置してください。共有コントロールノードで/etc/ansible/ansible.cfgを変更すると、他のチームを驚かせる可能性があります。プロジェクトレベルのファイルは、動作をリポジトリとともに移動させ、CI、ラップトップ、自動化ランナーを同じ設定に近づけます。
Ansibleが使用している設定ファイルを確認します。
ansible --version
出力にはアクティブな設定パスが含まれます。これにより、あるansible.cfgを編集している間にAnsibleが別のものを読み取っているという一般的な間違いを防げます。
Ansibleのチューニングをいつ止めるべきかを知る
接続オーバーヘッドが実行時間の主要な部分でなくなった場合は、SSHのチューニングをやめて、作業自体に注目してください。パッケージキャッシュの更新、コンテナのプル、データベースマイグレーション、サービスのヘルスチェック、クラウドAPI呼び出しは、成熟したプレイブックで支配的になることがよくあります。その時点では、より良い最適化は、ローカルパッケージミラー、アーティファクトキャッシング、より小さなデプロイバッチ、またはすべてのデプロイ実行から1回限りのプロビジョニングを移動することかもしれません。