Tại sao kết nối bị từ chối
Khi Python đưa ra lỗi ConnectionRefusedError: [Errno 111], tập lệnh của bạn về cơ bản đang hét vào một khoảng không vô định. Điều đó có nghĩa là mã của bạn đã kết nối thành công đến địa chỉ IP mục tiêu, nhưng hệ điều hành đích đã gửi lại một phản hồi "Không" dứt khoát. Không có dịch vụ nào đang lắng nghe trên cổng cụ thể đó để nhận yêu cầu của bạn.
Lỗi này khác với lỗi hết thời gian chờ (timeout). Trong trường hợp hết thời gian chờ, yêu cầu của bạn bị thất lạc hoặc bị bỏ qua. Trong trường hợp bị từ chối, việc bác bỏ diễn ra chủ động và ngay lập tức ở cấp độ TCP. Trong khi Linux sử dụng Errno 111, các lập trình viên Windows sẽ thấy WinError 10061 cho cùng một vấn đề này.
Những nguyên nhân phổ biến
Trong 90% trường hợp, lỗi này bắt nguồn từ một trong bốn tình huống sau:
- Dịch vụ không tồn tại: Cơ sở dữ liệu, instance Redis hoặc máy chủ web của bạn thực tế không hoạt động.
- Sai lệch cổng: Bạn đang yêu cầu cổng
5432, nhưng cơ sở dữ liệu của bạn đã được chuyển sang cổng5433vào tuần trước. - Bẫy cô lập: Dịch vụ đang lắng nghe trên
127.0.0.1, nhưng bạn lại đang cố gắng kết nối từ một container khác hoặc một IP bên ngoài. - Chặn chủ động: Một tường lửa như
ufwhoặciptablesđang trực tiếp loại bỏ các gói tin.
Các bước khắc phục
1. Xác nhận dịch vụ đang hoạt động
Đừng giả định rằng các tác vụ chạy nền của bạn đang hoạt động. Hãy bắt đầu bằng cách kiểm tra trạng thái của dịch vụ mục tiêu trực tiếp trên máy chủ.
# Check a system-level service
sudo systemctl status redis-server
# Or for Dockerized environments
docker ps | grep my_database_container
Nếu dịch vụ không hoạt động, hãy khởi động lại nó. Nếu nó liên tục bị treo, hãy kiểm tra nhật ký (logs) tại /var/log/ để tìm ra lỗi tiềm ẩn.
2. Tìm kiếm cổng đang lắng nghe
Một dịch vụ có thể đang "chạy" nhưng không lắng nghe ở nơi bạn mong đợi. Sử dụng công cụ ss để xem tiến trình nào thực sự đang mở cổng.
# Liệt kê tất cả các trình lắng nghe TCP đang hoạt động
sudo ss -tulpn | grep LISTEN
Quét đầu ra để tìm cổng của bạn (ví dụ: :6379 cho Redis). Nếu mã Python của bạn nhắm đến cổng 8080 nhưng ss hiển thị dịch vụ trên cổng 8081, hãy cập nhật chuỗi kết nối của bạn ngay lập tức.
3. Thoát khỏi Localhost (Vấn đề với 127.0.0.1)
Đây là rào cản thường gặp nhất trong các microservices hiện đại. Nếu một dịch vụ liên kết với 127.0.0.1, nó sẽ chỉ giao tiếp với chính nó. Nó sẽ bỏ qua bất kỳ yêu cầu nào từ bên ngoài.
Hãy tưởng tượng tập lệnh Python của bạn nằm trong một container Docker đang cố gắng truy cập Redis tại localhost:6379. Nó sẽ thất bại. Bên trong một container, "localhost" ám chỉ loopback của riêng container đó, không phải máy chủ của bạn hay dịch vụ Redis.
Cách khắc phục: Cấu hình dịch vụ của bạn để lắng nghe trên 0.0.0.0 (tất cả các giao diện). Đối với một máy chủ socket Python tùy chỉnh, hãy cập nhật lệnh gọi bind của bạn:
# Thay vì giới hạn trong loopback:
# sock.bind(('127.0.0.1', 8080))
# Mở rộng ra mạng:
sock.bind(('0.0.0.0', 8080))
4. Kiểm tra quyền hạn cụ thể của cơ sở dữ liệu
Các cơ sở dữ liệu thường có bộ phận kiểm soát nội bộ riêng. Ngay cả khi hệ điều hành cho phép kết nối, DB có thể từ chối dựa trên cấu hình.
- PostgreSQL: Mở
postgresql.confvà đảm bảolisten_addressesđược đặt thành'*'. Sau đó, kiểm trapg_hba.confđể biết quyền hạn IP. - Redis: Kiểm tra
redis.conf. Đảm bảoprotected-modelànonếu bạn đang kết nối qua mạng. - MySQL: Tìm kiếm
bind-address = 127.0.0.1trong tệpmy.cnfcủa bạn và thay đổi nó thành0.0.0.0.
5. Kiểm tra Tường lửa và Nhóm bảo mật
Nếu dịch vụ đang chạy và lắng nghe trên 0.0.0.0 nhưng bạn vẫn gặp lỗi Errno 111, tường lửa có thể là thủ phạm. Trên Linux, hãy cho phép cổng này một cách rõ ràng.
# Mở cổng 8080 trên Ubuntu/Debian
sudo ufw allow 8080/tcp
Nếu bạn triển khai trên AWS hoặc GCP, hãy kiểm tra lại Inbound Security Group Rules (Quy tắc nhóm bảo mật đầu vào). Đảm bảo địa chỉ IP của tập lệnh (hoặc dải VPC) được phép truy cập vào cổng cụ thể đó.
Kiểm tra đường truyền
Trước khi bạn khởi động lại ứng dụng Python, hãy sử dụng một công cụ mạng chuyên dụng. Điều này chứng minh đường truyền thông suốt mà không liên quan đến logic Python.
# Sử dụng netcat để kiểm tra cổng
nc -vz 192.168.1.50 8080
Một kết nối thành công sẽ trả về succeeded!. Nếu nó vẫn báo Connection refused, vấn đề chắc chắn nằm ở cấu hình máy chủ hoặc mạng của bạn, không phải ở mã nguồn.
Mẹo chuyên nghiệp để phòng ngừa
Kết nối mạng qua các mạng con (subnet) hoặc VPC phức tạp thường rất rắc rối. Khi xây dựng một cluster mới, tôi thường sử dụng Công cụ tính toán IP Subnet để lập bản đồ các khối CIDR. Điều này giúp ngăn chặn sự trùng lặp IP và giúp việc viết các quy tắc tường lửa chính xác ngay từ đầu dễ dàng hơn nhiều.
Ngoài ra, hãy tránh việc ghi cứng (hardcoding) localhost. Sử dụng các biến môi trường như DB_HOST. Điều này cho phép ứng dụng của bạn chuyển đổi từ localhost khi phát triển sang db.internal.network trong môi trường production mà không cần thay đổi bất kỳ dòng mã nào.
Lời kết
Errno 111 hiếm khi là lỗi trong logic Python của bạn. Đó là một sự sai lệch về cấu hình. Luôn xác minh rằng dịch vụ đang chạy, đảm bảo nó đang lắng nghe trên giao diện phù hợp (0.0.0.0), và sử dụng nc để kiểm tra đường truyền đã mở trước khi bạn bắt đầu viết lại các tập lệnh Python của mình.

