Hai Hướng Lưu Lượng, Một Hạng Mục
Chào mừng
Hầu hết các sơ đồ kiến trúc hiển thị lưu lượng đi theo một chiều: client ở trên, server ở dưới, mũi tên chỉ xuống. Thực tế, lưu lượng đi theo cả hai chiều.
Ingress: các client bên ngoài tiếp cận các dịch vụ của bạn thông qua đường dẫn này. Một reverse proxy ở biên mạng sẽ kết thúc TLS, định tuyến các yêu cầu & thực thi chính sách truy cập.
Egress: các dịch vụ của bạn tiếp cận các dịch vụ bên ngoài thông qua đường dẫn này. Gọi API của bộ xử lý thanh toán, lấy đích webhook, gửi yêu cầu đến đối tác. Thường là thông qua một forward proxy hoặc NAT gateway với danh sách cho phép.
Nhiều kiến trúc bắt đầu với một hộp duy nhất xử lý cả hai chiều. Nó hoạt động, cho đến ngày nó không còn hoạt động nữa. Chế độ lỗi này tinh vi, chỉ xuất hiện sau khi đủ nhiều dịch vụ nội bộ tồn tại, & dạy một bài học quan trọng về tách biệt mối quan tâm.
Vào cuối bài học này, bạn sẽ hiểu:
- Tại sao ingress & egress đại diện cho các mẫu lưu lượng cơ bản khác nhau với các trục mở rộng khác nhau & các chế độ lỗi khác nhau
- Hairpin NAT & tại sao một proxy cố gắng kết nối đến chính nó lại thất bại
- Sự phân nhánh kiến trúc: một hộp trở thành hai, & mỗi hộp sau đó sở hữu độc quyền điều gì
- Lợi ích cách ly bảo mật: mỗi phía có thể khóa chặt đến các đối tác được phép thực sự của nó
- Cách xác định khi thiết kế một hộp của bạn đã vượt qua ngưỡng mà ở đó sự tách rời là cần thiết
Tại Sao Các Hướng Yêu Cầu Các Công Cụ Khác Nhau
Hai loại tải công việc khác nhau tại một ranh giới mạng
Đặc điểm của lưu lượng truy cập vào (Ingress):
- Được khởi tạo bởi các bên bên ngoài (toàn bộ internet)
- Khối lượng tăng theo quy mô cơ sở người dùng của bạn
- Kết thúc TLS, định tuyến yêu cầu, giới hạn tốc độ theo nguồn
- Các mối quan tâm về phòng thủ nhiều lớp: DDoS, lạm dụng, thu thập dữ liệu (scraping)
- Địa chỉ IP công khai cần chấp nhận kết nối từ bất kỳ ai
Đặc điểm của lưu lượng truy cập ra (Egress):
- Được khởi tạo bởi các dịch vụ của chính bạn (một tập hợp các client đã biết, quy mô nhỏ)
- Khối lượng lưu lượng tăng theo mô hình gọi dịch vụ-đến-dịch vụ & gọi API bên ngoài của bạn
- Cho phép danh sách IP nguồn (source-IP allowlisting) tại các điểm cuối từ xa (bạn có một IP ra cố định mà các đối tác tin tưởng)
- Các mối quan tâm về phòng thủ nhiều lớp (defense-in-depth): đánh cắp dữ liệu, các dịch vụ nội bộ bị xâm nhập gọi ra bên ngoài
- Nên từ chối kết nối từ bất kỳ ai ngoại trừ các dịch vụ của chính bạn
Sự bất đối xứng cốt lõi: ingress chấp nhận lưu lượng từ thế giới; egress chỉ chấp nhận lưu lượng từ các dịch vụ của chính bạn. Đặt chúng trên cùng một máy có nghĩa là máy đó phải đồng thời có thể được truy cập từ thế giới (cho ingress) & chỉ có thể được truy cập từ các dịch vụ của bạn (cho egress). Các quy tắc tường lửa đáp ứng yêu cầu này sẽ gây bất lợi cho yêu cầu kia.
Con đường phát triển: một dự án nhỏ có thể ẩn cả hai sau một IP & một công cụ, vì khối lượng lưu lượng nhỏ & danh sách cho phép IP đối tác (partner-IP-allowlist) ngắn. Khi dự án phát triển, sự ma sát giữa hai vai trò này tăng lên, & một ngày nào đó một chế độ lỗi cụ thể (hairpin NAT) sẽ buộc phải tách rời chúng.
Lỗi buộc phải tách rời
Một câu chuyện sự cố đã được làm sạch
Hãy hình dung một điểm rẽ nhánh kiến trúc thực tế xảy ra trong các cụm máy chủ (fleets) đang vận hành. Các tên dưới đây đã được thay đổi; cấu trúc vấn đề hoàn toàn giống với những gì các đội ngũ kỹ thuật gặp phải trong thực tế.
Một tổ chức vận hành một máy chủ proxy duy nhất tại 203.0.113.5. Máy chủ này xử lý lưu lượng đi vào (ingress, cổng 443 cho người dùng) & lưu lượng đi ra (egress, cổng 1080 SOCKS5 cho các dịch vụ nội bộ gọi ra bên ngoài). Các dịch vụ nội bộ nằm trong các subnet riêng tư & định tuyến tất cả lưu lượng đi ra qua proxy SOCKS5 đó tại 203.0.113.5:1080.
Một trong các dịch vụ được đặt sau cùng địa chỉ 203.0.113.5 là api.example.com. DNS công khai trỏ api.example.com đến 203.0.113.5.
Bây giờ, một dịch vụ nội bộ khác cần gọi đến api.example.com. Đường dẫn đi ra của nó:
1. Dịch vụ nội bộ phân giải api.example.com → 203.0.113.5
2. Dịch vụ nội bộ gửi yêu cầu qua proxy egress SOCKS5 tại 203.0.113.5:1080
3. Proxy cố gắng mở một kết nối từ chính nó đến 203.0.113.5:443
4. Kết nối bị từ chối. Gói tin phải đi ra & đi vào lại cùng một NAT, điều mà hầu hết các bộ chồng mạng (network stacks) đều từ chối. Proxy không thể kết nối đến chính nó thông qua IP công khai của nó.
Đây là hairpin NAT: một gói tin đi ra khỏi NAT & cần đi vào lại cùng một NAT đó để đến đích. Nếu không có hỗ trợ hairpin đặc biệt ở lớp định tuyến, gói tin sẽ bị loại bỏ.
Tại sao vấn đề này chỉ bộc lộ muộn
Trong giai đoạn đầu của dự án, mọi dịch vụ nội bộ đều giao tiếp với các dịch vụ nội bộ khác bằng tên miền riêng (internal-api.local) hoặc không gọi ngược lại vào các dịch vụ công khai của tổ chức mình. Con đường hairpin đơn giản là không tồn tại.
Sau đó, một tính năng mới yêu cầu dịch vụ A gọi api.example.com (một tên miền công khai). Con đường hairpin được kích hoạt. Kết nối bị từ chối. Sự cố xảy ra.
Cách sửa đã vá triệu chứng (buộc trình phân giải DNS trả về IP riêng của api.example.com thay vì IP công khai). Nguyên nhân gốc rễ: một máy chủ duy nhất đang đảm nhận quá nhiều vai trò.
Chia rẽ Kiến trúc
Một hộp trở thành hai
Giải pháp gọn gàng: tách proxy thành hai máy.
Máy chủ Ingress (IP công khai 203.0.113.5):
- Caddy / reverse proxy trên các cổng 80, 443
- Các bản ghi DNS công khai trỏ đến đây
- Chứa api.example.com, app.example.com, v.v.
Máy chủ Egress (IP công khai khác 203.0.113.99):
- SOCKS5 / forward proxy trên cổng 1080
- Tường lửa (Firewall) hạn chế các kết nối đến chỉ cho các địa chỉ IP của subnet nội bộ
- Các dịch vụ nội bộ định tuyến tất cả lưu lượng ra ngoài qua địa chỉ này
Điều này mang lại lợi ích gì:
1. Giải quyết vấn đề Hairpin. Một dịch vụ nội bộ gọi api.example.com sẽ định tuyến lưu lượng ra ngoài qua 203.0.113.99 (egress), sau đó kết nối bình thường đến 203.0.113.5 (ingress, một địa chỉ IP khác). Vòng lặp NAT biến mất vì hai địa chỉ IP này nằm trên các máy khác nhau.
2. Cách ly bảo mật. Tường lửa của máy chủ egress có thể được khóa chặt cho một tập hợp nhỏ các địa chỉ IP nội bộ. Tường lửa của máy chủ ingress vẫn mở cho thế giới bên ngoài. Hai bộ quy tắc riêng biệt, mỗi bộ thể hiện rõ ràng một vai trò.
3. Mở rộng độc lập. Băng thông ingress mở rộng theo số lượng người dùng; băng thông egress mở rộng theo hoạt động của dịch vụ nội bộ. Nâng cấp một bên mà không cần đụng chạm đến bên kia.
4. Cách ly sự cố. Một cấu hình egress sai không còn làm hỏng trang web công khai. Một cuộc tấn công DDoS nhắm vào trang web công khai không còn làm cạn kiệt băng thông egress.
5. Mô hình tư duy rõ ràng hơn. Mỗi máy có một nhiệm vụ. Kỹ sư suy luận về các vấn đề ingress mà không cần nghĩ đến egress, và ngược lại.
Hai Trục, Hai Quyết Định Về Kích Thước
Mở rộng độc lập
Trước khi tách biệt, sự tăng trưởng theo bất kỳ hướng nào cũng gây áp lực lên cùng một máy. Sau khi tách biệt, mỗi hướng có cơ chế cấp phát tài nguyên riêng.
Kích thước Ingress: mở rộng theo số lượng người dùng. Các quyết định về dung lượng nằm ở tầng hướng ra công chúng (nhiều bản sao reverse proxy hơn, VM lớn hơn, CDN đặt phía trước). Ngân sách băng thông được tính toán dựa trên lưu lượng người dùng tại thời điểm cao điểm.
Kích thước Egress: mở rộng theo khối lượng gọi API từ dịch vụ nội bộ ra bên ngoài. Thường bị chi phối bởi việc phân phối webhook, các cuộc gọi đến bộ xử lý thanh toán, hoặc các lần lấy dữ liệu từ bên thứ ba. Ngân sách băng thông được tính toán dựa trên các mẫu gọi nội bộ.
Cách ly sự cố: một cuộc tấn công DDoS nhắm vào ingress công khai không còn tiêu thụ băng thông egress (các cuộc gọi đến bộ xử lý thanh toán vẫn được thực hiện). Một sự cố sập của proxy egress không còn kéo sập trang web công khai (người dùng vẫn truy cập được trang web; chỉ các cuộc gọi ra bên ngoài nội bộ là bị lỗi).
SLO khác nhau: khả năng sẵn sàng của ingress quan trọng đối với người dùng (sự cố ngừng hoạt động của trang web có thể nhìn thấy); khả năng sẵn sàng của egress quan trọng đối với người vận hành (các sự cố nền có thể mất nhiều thời gian hơn để phát hiện). Mỗi bên có thể áp dụng SLO riêng của mình.
Nhiều Máy chủ Egress
Một khi vai trò egress là một máy riêng, bước tiếp theo rõ ràng nhất là chạy nhiều máy egress phía sau một bộ cân bằng tải để đảm bảo độ sẵn sàng cao (HA). Mỗi dịch vụ nội bộ mới sẽ trỏ đến tên miền egress (tên miền này phân giải thành nhóm được cân bằng tải) thay vì trỏ đến một địa chỉ IP duy nhất.
Cùng một bài học như phần còn lại của hệ thống phân tán: một khi một tầng trở nên không trạng thái (stateless) & có vai trò riêng, nó có thể nhân bản một cách rẻ.
Tích hợp Đối tác Mới
Tổ chức của bạn vận hành mô hình tách biệt ingress / egress như thiết kế. Máy chủ egress có một địa chỉ IP công cộng cố định (203.0.113.99) mà bạn đã cho phép (allowlist) với ba API đối tác hiện có (một bộ xử lý thanh toán, một cổng SMS, một nhà cung cấp email).
Một nhóm sản phẩm muốn thêm tích hợp thứ tư: một hệ thống giao webhook gọi ngược lại vào các endpoint của khách hàng trên toàn thế giới. Dự báo khối lượng: 10.000 cuộc gọi mỗi phút, với các đợt tăng vọt lên 30.000.
Thiết kế Ranh giới Mạng cho Dịch vụ Đang Phát triển
Tổng hợp
Bạn đã học được lý do tại sao ingress & egress đòi hỏi các công cụ khác nhau, sự cố hairpin NAT buộc phải tách biệt trong các đội tàu thực tế, & cách mà việc mở rộng độc lập, cách ly bảo mật, & cách ly sự cố tích lũy lại một khi sự tách biệt này được thực hiện.
Áp dụng cả bốn.
Một công ty SaaS quy mô vừa chạy ba tên miền phụ sản phẩm (app, api, admin) cho người dùng của họ, cùng bốn tích hợp outbound (Stripe, Twilio, SendGrid, một hệ thống webhook khách hàng). Hiện tại, mọi thứ đều nằm sau một máy proxy duy nhất tại một địa chỉ IP công. Họ đã bắt đầu nhận được các báo cáo về sự cố hairpin ngắt quãng khi các dịch vụ nội bộ cố gắng gọi api.example.com. Họ muốn thiết kế một giải pháp sửa chữa vĩnh viễn.
Khóa học này sẽ đi tiếp đến đâu
Khóa học này sẽ đi tiếp đến đâu
Bạn vừa chứng kiến một trong những refactor tách biệt mối quan tâm (separation-of-concerns) sạch sẽ nhất trong hệ thống phân tán: một máy chủ trở thành hai, mỗi máy có một vai trò rõ ràng, & hệ thống kế thừa các lợi ích về mở rộng (scaling), bảo mật & cách ly sự cố (failure-isolation) trong quá trình đó.
Bài học tiếp theo (cs_distsys_failure_modes_and_blast_radius) sẽ mở rộng lập luận về cách cách ly sự cố. Bạn sẽ đọc một bản phân tích nguyên nhân (postmortem) đã được làm sạch về lỗi DNS-SERVFAIL, nhận diện mô hình sự cố lan truyền, & viết các mục hành động không gán lỗi (blameless) nhắm vào hệ thống thay vì con người.
Bài học đồng hành: geometry_of_ingress_egress_separation diễn giải lại sự tách biệt này dưới dạng đồ thị hai phần (bipartite graph) & khám phá các đỉnh cắt (cut vertices), sự phân vùng mạng, & những gì lý thuyết đồ thị cho bạn biết về ranh giới mạng.
Làm tốt lắm. Hãy tiếp tục.