Sửa lỗi Ansible 'SSH Error: data could not be sent to remote host' khi kết nối SSH bị ngắt giữa chừng

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#kết nối#mạng#pipeline

Tình Huống

Playbook của bạn khởi chạy bình thường — kiểm tra kết nối host thành công, các task bắt đầu thực thi — rồi đến một lúc nào đó ở giữa chừng, mọi thứ sụp đổ với lỗi:

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"}

Host vẫn đang hoạt động. Bạn có thể SSH vào thủ công ngay lúc này. Nhưng Ansible không thể hoàn thành những gì đã bắt đầu. Đó là phần khó chịu nhất — đây không phải lỗi host không thể tiếp cận. Kết nối bị ngắt trong quá trình thực thi.

Nguyên Nhân

Ansible ghép kênh các kết nối SSH thông qua ControlMaster — một socket master dùng chung cho nhiều thao tác task. Khi một task chạy lâu làm tắc nghẽn pipeline SSH (idle timeout, firewall gửi gói RST, tràn buffer), socket nền bên dưới chết trong khi Ansible vẫn nghĩ nó đang hoạt động. Lần ghi tiếp theo vào socket chết đó sẽ kích hoạt lỗi này.

Các nguyên nhân phổ biến:

  • Firewall hoặc thiết bị NAT ngắt các kết nối TCP nhàn rỗi — thường gặp với AWS Security Groups, GCP firewall rules, hoặc corporate proxy reset sau 350–600 giây không hoạt động
  • Các task chạy lâu hơn ClientAliveInterval của SSH server mà không có keepalive traffic
  • Truyền dữ liệu lớn làm tràn buffer của SSH pipe
  • Lỗi DNS resolution cho remote host trong phiên làm việc
  • Tiến trình sshd trên remote host chạm giới hạn tài nguyên và ngắt kết nối
  • ControlMaster socket của Ansible bị cũ giữa các task trong một playbook dài

Khắc Phục Nhanh — Chạy Playbook Ngay Bây Giờ

Tắt ControlMaster persistence và thêm keepalive. Thêm phần này vào playbook hoặc inventory của bạn:

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

Hoặc đặt theo từng host trong inventory:

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

Sau đó chạy lại từ điểm thất bại thay vì bắt đầu lại từ đầu:

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

ServerAliveInterval=30 gửi keepalive mỗi 30 giây. Hầu hết các firewall ngắt kết nối nhàn rỗi đều chờ ít nhất 60 giây, nên cấu hình này giữ socket luôn hoạt động. ControlMaster=no buộc tạo kết nối SSH mới cho mỗi task, bỏ qua mọi socket cũ có thể còn sót lại từ lần crash trước.

Chẩn Đoán Nguyên Nhân Gốc Rễ

Trước khi áp dụng bản vá cố định, hãy xác định nguyên nhân nào bạn đang gặp phải.

Kiểm tra xem có phải vấn đề keepalive / firewall không

Chạy playbook với chế độ debug SSH chi tiết:

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

channel X: open failed hoặc Broken pipe trong output nghĩa là pipe đã chết. Connection timed out chỉ ra sự cố mạng hoặc firewall ở tầng trên.

Kiểm tra hành vi SSH keepalive thủ công

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

Lệnh này giữ phiên SSH mở trong 5 phút mà không làm gì hữu ích — chính xác là những gì Ansible làm trong các task chạy lâu. Thành công ở đây nhưng thất bại trong playbook? Cấu hình SSH bên dưới là giải pháp cho bạn. Vẫn ngắt kết nối ở đây? Bạn có vấn đề mạng sâu hơn cần điều tra.

Kiểm tra log sshd trên remote host

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 xuất hiện từ phía server nghĩa là việc ngắt kết nối do server khởi tạo — client không gửi keepalive và sshd đã từ bỏ.

Khắc Phục Vĩnh Viễn — ansible.cfg

Đặt các cấu hình này ở cấp toàn cục để mọi playbook đều được hưởng lợi mà không cần lặp lại ssh args ở khắp nơi:

[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

Giải thích các cài đặt quan trọng:

  • ServerAliveInterval=30 — client gửi keepalive mỗi 30 giây
  • ServerAliveCountMax=10 — chịu đựng tối đa 10 keepalive bị bỏ lỡ (tổng cộng 5 phút) trước khi tuyên bố kết nối chết
  • ControlPersist=60s — kết nối master tồn tại 60 giây sau lần sử dụng cuối, không phải vô thời hạn; giảm đáng kể nguy cơ socket cũ
  • pipelining = True — gộp các thao tác SSH theo từng task, ít round-trip hơn, ít cơ hội kết nối bị ngắt giữa chừng hơn

Nếu bạn gặp gói RST từ firewall (AWS/GCP/Azure)

Firewall của các nhà cung cấp cloud thường reset kết nối nhàn rỗi hơn 350–600 giây. Cấu hình sshd trên remote host để gửi keepalive từ phía server:

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

sudo systemctl reload sshd

Chỉ dựa vào keepalive phía client đặt toàn bộ trách nhiệm lên Ansible. Khi cả hai phía đều gửi keepalive, bên nào cũng có thể phát hiện kết nối chết và không bên nào phải chờ hết timeout.

Nếu pipelining gây vấn đề với sudo

Một số cấu hình sudo yêu cầu TTY, xung đột với pipelining. Gặp lỗi sudo: no tty present sau khi bật? Sửa trên remote host:

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

Xóa Socket ControlMaster Cũ

Playbook bị crash để lại các socket chết phía sau. Những file cũ đó kích hoạt cùng lỗi ở lần chạy tiếp theo dù mạng đã hoạt động bình thường:

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

Hoặc xóa chính xác một socket cụ thể thay vì xóa toàn bộ thư mục:

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

Xác Minh Bản Vá

Sau khi áp dụng các thay đổi trong ansible.cfg, hãy thử với một task chậm có chủ ý trước khi tin tưởng với môi trường production:

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

Nhận được connection survived sau 60 giây nghĩa là keepalive đang hoạt động. Sau đó chạy toàn bộ playbook với logging:

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

Quét output để tìm SSH Error. Không có kết quả nào nghĩa là bản vá đã giữ vững.

Mẹo

Khi lỗi này xuất hiện không nhất quán trên một fleet lớn — một số host thất bại, số khác thì không — vấn đề thường ở cấp subnet. Các host khác nhau đi qua các đường mạng khác nhau với các quy tắc firewall khác nhau. Trước khi truy tìm cấu hình SSH theo từng host, hãy lập bản đồ topology mạng của bạn trước. Tôi dùng Subnet Calculator tại ToolCraft để nhanh chóng xác định các dải CIDR mà các host khác nhau thuộc về và liệu chúng có gặp các chính sách firewall khác nhau không. Chạy hoàn toàn trên trình duyệt, không gửi dữ liệu đến đâu cả.

Với các task chạy lâu như migration database hoặc cài đặt package, hãy cân nhắc dùng asyncpoll thay vì giữ kết nối SSH mở suốt thời gian đó:

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

Ansible kích hoạt task, ngắt kết nối, rồi định kỳ kiểm tra trạng thái hoàn thành. SSH pipe không bao giờ mở đủ lâu để bị ngắt — hoàn toàn tránh được toàn bộ vấn đề này với các thao tác thường xuyên chạy hơn một hoặc hai phút.

Related Error Notes