AnsibleのSSHエラー「data could not be sent to remote host」をSSH接続が途中で切れる場合に修正する

intermediate🔧 Ansible2026-05-16| Ansible 2.9以降、Linux(Ubuntu 20.04/22.04、CentOS 7/8、RHEL 8/9)、OpenSSH 7.x以降

Error Message

fatal: [host]: FAILED! => {"msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh"}
#ansible#ssh#接続#ネットワーク#パイプライン

状況

プレイブックは正常に開始されます — ホストの到達確認が通過し、タスクが実行され始めます — そして途中のどこかで、すべてが以下のエラーとともに停止します:

fatal: [webserver-01]: FAILED! => {"msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh"}

ホストは起動しています。今この瞬間、手動でSSH接続できます。しかしAnsibleは処理を完了できません。これが厄介なところです — これは到達不可能なホストのエラーではありません。接続は実行途中で切断されたのです。

なぜこれが起こるのか

AnsibleはControlMasterを通じてSSH接続を多重化します — 複数のタスク操作で共有される1つのマスターソケットです。長時間実行されるタスクがSSHパイプラインを停止させると(アイドルタイムアウト、ファイアウォールのRSTパケット、バッファオーバーフロー)、Ansibleがまだ生きていると思っている間に、基盤となるソケットが死にます。その死んだソケットへの次の書き込みがこのエラーを引き起こします。

よくある原因:

  • ファイアウォールまたはNATデバイスがアイドル状態のTCP接続を切断する — AWSセキュリティグループ、GCPファイアウォールルール、または非アクティブ状態が350〜600秒続いた後にリセットする企業プロキシで一般的
  • キープアライブトラフィックなしにSSHサーバーのClientAliveIntervalより長く実行されるタスク
  • SSHパイプバッファを圧迫する大量のデータ転送
  • セッション中のリモートホストのDNS解決失敗
  • リモートホストのsshdがリソース制限に達して接続を切断する
  • 長いプレイブック内のタスク間でAnsibleのControlMasterソケットが古くなる

クイックフィックス — 今すぐプレイブックを動かす

ControlMasterの持続を無効にし、キープアライブを追加します。プレイブックまたはインベントリに以下を追加してください:

# In your playbook
- hosts: all
  vars:
    ansible_ssh_extra_args: '-o ServerAliveInterval=30 -o ServerAliveCountMax=10 -o ControlMaster=no'

またはインベントリでホストごとに設定します:

[webservers]
webserver-01 ansible_ssh_extra_args="-o ServerAliveInterval=30 -o ServerAliveCountMax=10 -o ControlMaster=no"

そして最初からやり直す代わりに、失敗した箇所から再実行します:

ansible-playbook site.yml --start-at-task="The task that failed" -v

ServerAliveInterval=30は30秒ごとにキープアライブを送信します。アイドル接続を切断するほとんどのファイアウォールは少なくとも60秒待つため、これによりソケットが維持されます。ControlMaster=noはタスクごとに新しいSSH接続を強制し、前回のクラッシュから残っている古いソケットをバイパスします。

根本原因の診断

恒久的な修正を確定する前に、どの原因に該当するかを特定してください。

キープアライブ / ファイアウォールの問題かどうか確認する

詳細なSSHデバッグでプレイブックを実行します:

ANSIBLE_SSH_ARGS="-vvv" ansible-playbook site.yml 2>&1 | grep -E "(debug|channel|packet|timeout|Broken)"

出力にchannel X: open failedまたはBroken pipeが表示される場合、パイプが切断されています。Connection timed outはネットワークまたは上流のファイアウォールの問題を示しています。

SSHキープアライブの動作を手動でテストする

ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=10 user@host "sleep 300 && echo done"

このコマンドはSSHセッションを5分間何もしない状態で開いたままにします — 長いタスク中にAnsibleが行うこととまったく同じです。ここでは成功するがプレイブックでは失敗する場合、以下のSSH設定修正が答えです。ここでも切断される場合は、より深いネットワーク問題を追う必要があります。

リモートホストのsshdログを確認する

sudo journalctl -u sshd --since "30 minutes ago" | grep -E "(disconnect|timeout|error)"
# or on older systems:
grep -i "disconnect\|timeout\|error" /var/log/auth.log | tail -50

サーバー側からTimeout, client not respondingが表示される場合、切断はサーバー側から開始されたことを意味します — クライアントがキープアライブを送信しておらず、sshdが諦めたのです。

恒久的な修正 — ansible.cfg

これらをグローバルに設定することで、すべてのプレイブックがSSH引数を繰り返すことなく恩恵を受けられます:

[defaults]
timeout = 30

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30 -o ServerAliveCountMax=10
pipelining = True
control_path_dir = /tmp/.ansible/cp

主要な設定の説明:

  • ServerAliveInterval=30 — クライアントが30秒ごとにキープアライブを送信する
  • ServerAliveCountMax=10 — 接続が切断されたと判断する前に最大10回のキープアライブ欠落を許容する(合計5分)
  • ControlPersist=60s — マスター接続は最後の使用から60秒間生き続け、無期限ではない;古いソケットのリスクを大幅に削減
  • pipelining = True — タスクごとにSSH操作をバッチ処理し、ラウンドトリップを減らし、タスク途中で接続が切断される機会を減らす

ファイアウォールのRSTパケットに悩まされている場合(AWS/GCP/Azure)

クラウドプロバイダーのファイアウォールは通常、350〜600秒以上アイドル状態の接続をリセットします。リモートホストのsshdも自分側からキープアライブを送信するよう設定します:

# /etc/ssh/sshd_config
ClientAliveInterval 30
ClientAliveCountMax 10
TCPKeepAlive yes

sudo systemctl reload sshd

クライアント側のキープアライブだけに頼ると、すべての責任がAnsibleにかかります。両側からキープアライブを送信すれば、どちらの側も切断された接続を検出でき、完全なタイムアウトを待つ必要がありません。

パイプラインがsudoで問題を引き起こす場合

一部のsudo設定ではTTYが必要で、パイプラインと競合します。有効にした後にsudo: no tty presentが表示される場合は、リモートホストで修正します:

# Add to /etc/sudoers via visudo:
Defaults !requiretty

古いControlMasterソケットのクリア

クラッシュしたプレイブックは死んだソケットを残します。それらの古いファイルは、ネットワークが正常に戻った後でも次の実行で同じエラーを引き起こします:

ls /tmp/.ansible/cp/
rm -f /tmp/.ansible/cp/*

またはディレクトリ全体を削除する代わりに、特定のソケットを選択的に終了させます:

ansible all -m ping  # Still failing? Sockets are stale.
ssh -O stop -o ControlPath=/tmp/.ansible/cp/%r@%h:%p user@host

修正の確認

ansible.cfgの変更を適用した後、本番環境で信頼する前に意図的に遅いタスクでテストします:

ansible webservers -m command -a "sleep 60 && echo 'connection survived'"

60秒後にconnection survivedが表示されれば、キープアライブが機能しています。その後、ログを記録しながらフルプレイブックを実行します:

ansible-playbook site.yml -v 2>&1 | tee playbook-run.log

出力でSSH Errorを確認します。ヒットがなければ修正が維持されています。

ヒント

このエラーが大規模なフリート全体で断続的に発生する場合 — 一部のホストは失敗し、他は失敗しない — 問題は通常サブネットレベルです。異なるホストは異なるファイアウォールルールを持つ異なるネットワークパスを経由します。ホストごとのSSH設定を追う前に、まずネットワークトポロジーをマッピングしてください。異なるホストがどのCIDR範囲に属し、異なるファイアウォールポリシーに当たるかを素早く把握するために、ToolCraftのSubnet Calculatorを活用しています。ブラウザのみで動作し、どこにもデータは送信されません。

データベースマイグレーションやパッケージインストールなどの長時間実行タスクでは、SSH接続を常に開いたままにする代わりにasyncpollの使用を検討してください:

- name: Run long database migration
  command: python manage.py migrate
  async: 600    # Allow up to 10 minutes
  poll: 15      # Check every 15 seconds

Ansibleはタスクを起動して切断し、スケジュールに従って完了を確認します。SSHパイプが切断されるほど長く開いたままになることはありません — これにより、定期的に1〜2分以上かかる操作でこの問題全体を回避できます。

Related Error Notes