Kiến trúc microservices yêu cầu giao tiếp đáng tin cậy giữa các dịch vụ, bảo vệ các yêu cầu API bên ngoài và cân bằng tải. Các máy chủ proxy giải quyết những vấn đề này, đóng vai trò là trung gian giữa các dịch vụ, API bên ngoài và khách hàng. Trong hướng dẫn này, chúng ta sẽ xem xét cách tích hợp proxy đúng cách vào hạ tầng microservices, các loại proxy nào nên sử dụng cho các kịch bản khác nhau và cách thiết lập giao tiếp an toàn.
Vai trò của proxy trong kiến trúc microservices
Trong kiến trúc microservices, các máy chủ proxy thực hiện một số chức năng quan trọng, khác với việc sử dụng proxy truyền thống để ẩn danh hoặc vượt qua các khối. Tại đây, proxy trở thành một phần không thể thiếu của hạ tầng, đảm bảo giao tiếp an toàn và đáng tin cậy giữa các thành phần của hệ thống.
Các vai trò chính của proxy trong microservices:
- API Gateway — điểm truy cập duy nhất cho tất cả các yêu cầu của khách hàng, định tuyến chúng đến các microservices tương ứng, ẩn đi kiến trúc nội bộ của hệ thống
- Sidecar Proxy — container proxy hoạt động bên cạnh mỗi dịch vụ (mô hình Service Mesh), chặn toàn bộ lưu lượng vào và ra
- Reverse Proxy — phân phối tải giữa nhiều phiên bản của một dịch vụ, đảm bảo khả năng chịu lỗi
- Forward Proxy — kiểm soát và bảo vệ các yêu cầu ra bên ngoài đến các API, ẩn đi các địa chỉ IP nội bộ của hạ tầng
- Proxy bảo mật — kết thúc SSL/TLS, xác thực, phân quyền, bảo vệ chống lại DDoS và các cuộc tấn công khác
Proxy cho phép thực hiện các mẫu kiến trúc quan trọng: circuit breaker (tự động ngắt kết nối các dịch vụ không hoạt động), retry logic (thử lại khi gặp lỗi), rate limiting (giới hạn tần suất yêu cầu), request/response transformation (chuyển đổi định dạng dữ liệu). Tất cả những điều này làm cho hệ thống trở nên bền bỉ hơn trước các lỗi và đơn giản hóa việc quản lý hạ tầng phân tán phức tạp.
Quan trọng: Trong kiến trúc microservices, proxy hoạt động ở hai cấp độ — như một cổng vào cho khách hàng (API Gateway) và như các proxy nội bộ giữa các dịch vụ (Service Mesh). Cả hai cấp độ đều rất quan trọng cho an ninh và độ tin cậy của hệ thống.
Các loại proxy cho các kịch bản sử dụng khác nhau
Việc lựa chọn loại proxy phụ thuộc vào nhiệm vụ cụ thể trong kiến trúc microservices. Các kịch bản khác nhau yêu cầu các đặc điểm khác nhau: tốc độ, độ tin cậy, ẩn danh hoặc phân phối địa lý.
| Kịch bản | Loại proxy | Tại sao |
|---|---|---|
| Giao tiếp nội bộ giữa các dịch vụ | Proxy HTTP/HTTPS (Envoy, NGINX) | Tốc độ tối đa, độ trễ thấp, hỗ trợ HTTP/2 |
| Yêu cầu đến API bên ngoài với giới hạn | Proxy dân cư | Vượt qua giới hạn tần suất, địa chỉ IP thực của người dùng, rủi ro bị chặn thấp |
| Phân tích dữ liệu cho phân tích | Proxy trung tâm dữ liệu | Tốc độ cao, chi phí thấp, phù hợp cho các yêu cầu lớn |
| Làm việc với API di động | Proxy di động | Giả lập người dùng di động thực, truy cập vào API chỉ dành cho di động |
| Cân bằng tải | Reverse Proxy (HAProxy, NGINX) | Phân phối lưu lượng, kiểm tra tình trạng, tự động chuyển đổi khi gặp lỗi |
| Hệ thống phân phối địa lý | Proxy dân cư với geo-targeting | Truy cập vào API khu vực, đáp ứng yêu cầu về định vị dữ liệu |
Đối với giao tiếp nội bộ giữa các microservices, thường sử dụng các giải pháp proxy chuyên dụng như Envoy Proxy hoặc NGINX, được tối ưu hóa cho độ trễ thấp và băng thông cao. Chúng hỗ trợ các giao thức hiện đại (HTTP/2, gRPC) và tích hợp với các hệ thống Service Mesh.
Đối với việc làm việc với các API bên ngoài, sự lựa chọn phụ thuộc vào yêu cầu của dịch vụ cụ thể. Nếu API có giới hạn tần suất nghiêm ngặt hoặc chặn các yêu cầu từ IP trung tâm dữ liệu, cần sử dụng proxy dân cư. Đối với việc thu thập dữ liệu hàng loạt, nơi tốc độ quan trọng hơn ẩn danh, proxy trung tâm dữ liệu sẽ phù hợp. Proxy di động là cần thiết khi làm việc với các API, kiểm tra loại thiết bị hoặc yêu cầu địa chỉ IP di động.
Proxy như API Gateway: bảo vệ và định tuyến
API Gateway là một máy chủ proxy chuyên dụng, đóng vai trò là điểm truy cập duy nhất cho tất cả các yêu cầu của khách hàng đến hệ thống microservices. Thay vì để khách hàng trực tiếp truy cập vào hàng chục dịch vụ khác nhau, họ gửi tất cả các yêu cầu đến một địa chỉ API Gateway, mà sau đó định tuyến chúng đến các dịch vụ cần thiết.
Các chức năng chính của API Gateway:
- Định tuyến yêu cầu — xác định dịch vụ nào sẽ xử lý yêu cầu dựa trên URL, tiêu đề hoặc các tham số khác
- Xác thực và phân quyền — kiểm tra token (JWT, OAuth), quản lý quyền truy cập đến các dịch vụ khác nhau
- Rate Limiting — giới hạn số lượng yêu cầu từ một khách hàng để bảo vệ khỏi quá tải và DDoS
- Tổng hợp phản hồi — kết hợp dữ liệu từ nhiều dịch vụ thành một phản hồi cho khách hàng
- Chuyển đổi giao thức — chuyển đổi REST thành gRPC, HTTP/1.1 thành HTTP/2
- Cache — lưu trữ dữ liệu thường xuyên được yêu cầu để giảm tải cho các dịch vụ
- Ghi log và giám sát — thu thập tập trung các chỉ số và log của tất cả các yêu cầu
Các giải pháp phổ biến cho API Gateway: Kong, Tyk, AWS API Gateway, Azure API Management, NGINX Plus, Traefik. Sự lựa chọn phụ thuộc vào quy mô của hệ thống, yêu cầu về hiệu suất và nền tảng đám mây đang sử dụng.
// Ví dụ cấu hình NGINX như API Gateway
upstream auth_service {
server auth:8001;
}
upstream user_service {
server user:8002;
}
upstream order_service {
server order:8003;
}
server {
listen 80;
server_name api.example.com;
# Giới hạn tần suất yêu cầu
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
location /api/auth/ {
limit_req zone=api_limit burst=20;
proxy_pass http://auth_service/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/users/ {
# Kiểm tra token trước khi proxy
auth_request /auth/verify;
proxy_pass http://user_service/;
}
location /api/orders/ {
auth_request /auth/verify;
proxy_pass http://order_service/;
}
# Endpoint nội bộ để kiểm tra token
location = /auth/verify {
internal;
proxy_pass http://auth_service/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
API Gateway ẩn đi kiến trúc nội bộ của hệ thống khỏi các khách hàng bên ngoài. Khách hàng không biết có bao nhiêu microservices tồn tại và cách chúng tương tác — họ chỉ thấy một API duy nhất. Điều này đơn giản hóa việc quản lý phiên bản, cho phép thay đổi cấu trúc nội bộ mà không ảnh hưởng đến khách hàng và cải thiện an ninh, vì các dịch vụ nội bộ không thể truy cập trực tiếp từ internet.
Tích hợp với Service Mesh (Istio, Linkerd)
Service Mesh là một lớp hạ tầng quản lý giao tiếp giữa các microservices thông qua các máy chủ proxy được triển khai bên cạnh mỗi dịch vụ (mô hình Sidecar). Khác với API Gateway, chỉ xử lý lưu lượng bên ngoài, Service Mesh kiểm soát toàn bộ lưu lượng nội bộ giữa các dịch vụ.
Các giải pháp Service Mesh phổ biến nhất là Istio (sử dụng Envoy Proxy như sidecar) và Linkerd (sử dụng proxy nhẹ của riêng mình). Chúng tự động triển khai một container proxy bên cạnh mỗi pod trong Kubernetes, chặn toàn bộ lưu lượng vào và ra.
Các khả năng của Service Mesh thông qua proxy:
- Mutual TLS (mTLS) — mã hóa tự động toàn bộ lưu lượng giữa các dịch vụ với xác thực lẫn nhau
- Traffic Management — quản lý định tuyến, triển khai canary, A/B testing
- Observability — thu thập tự động các chỉ số, trace và log mà không cần thay đổi mã của các dịch vụ
- Resilience — circuit breaking, retry logic, quản lý timeout, fault injection để kiểm tra
- Service Discovery — tự động phát hiện các dịch vụ và cân bằng tải
# Ví dụ cấu hình Istio VirtualService cho định tuyến
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
version:
exact: "v2"
route:
- destination:
host: user-service
subset: v2
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10 # Triển khai canary: 10% lưu lượng đến v2
---
# Circuit Breaker để bảo vệ khỏi các lỗi chuỗi
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
Service Mesh giải quyết vấn đề "monolith phân tán" — khi logic tương tác giữa các dịch vụ (retry, timeout, circuit breaking) bị trùng lặp trong mã của mỗi dịch vụ. Thay vào đó, toàn bộ logic này được đưa vào lớp proxy, giúp mã của các dịch vụ trở nên đơn giản hơn và đảm bảo hành vi đồng nhất cho toàn bộ hệ thống.
Một lợi thế quan trọng là tính minh bạch hoàn toàn của lưu lượng. Mỗi yêu cầu giữa các dịch vụ đi qua proxy, mà ghi lại các chỉ số: thời gian phản hồi, mã lỗi, kích thước payload. Dữ liệu này tự động được gửi đến các hệ thống giám sát (Prometheus, Grafana) và tracing (Jaeger, Zipkin), tạo ra bức tranh đầy đủ về hoạt động của hệ thống phân tán mà không cần thêm công cụ vào mã của mỗi dịch vụ.
Bảo vệ các yêu cầu đến API bên ngoài thông qua proxy
Các microservices thường tương tác với các API bên ngoài: hệ thống thanh toán, dịch vụ định vị, API mạng xã hội, nhà cung cấp dữ liệu. Các yêu cầu trực tiếp đến các API bên ngoài tạo ra một số vấn đề: tiết lộ các địa chỉ IP nội bộ của hạ tầng, rủi ro bị chặn khi vượt quá giới hạn tần suất, thiếu kiểm soát đối với lưu lượng ra.
Việc sử dụng proxy cho các yêu cầu ra giải quyết những vấn đề này và thêm vào các khả năng bổ sung:
- Ẩn đi hạ tầng — các API bên ngoài thấy địa chỉ IP của proxy, không phải máy chủ của bạn
- Vượt qua giới hạn tần suất — xoay vòng các địa chỉ IP để phân phối yêu cầu
- Phân phối địa lý — truy cập vào các API khu vực thông qua proxy ở các quốc gia cần thiết
- Quản lý tập trung — điểm kiểm soát duy nhất cho tất cả các yêu cầu ra
- Cache phản hồi — giảm số lượng yêu cầu đến các API tốn kém
- Giám sát và ghi log — theo dõi tất cả các yêu cầu đến các dịch vụ bên ngoài
// Python: cấu hình proxy cho requests đến các API bên ngoài
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class ExternalAPIClient:
def __init__(self, proxy_url, proxy_rotation=False):
self.session = requests.Session()
# Cấu hình proxy
self.proxies = {
'http': proxy_url,
'https': proxy_url
}
# Logic thử lại để đảm bảo độ bền
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def call_payment_api(self, data):
"""Yêu cầu đến API thanh toán thông qua proxy"""
try:
response = self.session.post(
'https://api.payment-provider.com/charge',
json=data,
proxies=self.proxies,
timeout=10,
headers={'User-Agent': 'MyService/1.0'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
# Ghi log lỗi
print(f"Lỗi API thanh toán: {e}")
raise
# Sử dụng với một nhóm proxy để xoay vòng
class ProxyPool:
def __init__(self, proxy_list):
self.proxies = proxy_list
self.current = 0
def get_next(self):
proxy = self.proxies[self.current]
self.current = (self.current + 1) % len(self.proxies)
return proxy
# Khởi tạo
proxy_pool = ProxyPool([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
])
# Đối với mỗi yêu cầu, sử dụng proxy tiếp theo
client = ExternalAPIClient(proxy_pool.get_next())
Đối với việc làm việc với các API bên ngoài có giới hạn nghiêm ngặt hoặc chặn các yêu cầu từ IP trung tâm dữ liệu, proxy dân cư trở thành một nhu cầu. Chúng cung cấp các địa chỉ IP thực của người dùng tại nhà, giảm thiểu rủi ro bị chặn và cho phép vượt qua các giới hạn địa lý.
// Node.js: proxy cho các API bên ngoài với xoay vòng tự động
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
class ExternalAPIService {
constructor(proxyList) {
this.proxyList = proxyList;
this.currentProxyIndex = 0;
this.requestCounts = new Map(); // Bộ đếm yêu cầu cho giới hạn tần suất
}
getNextProxy() {
const proxy = this.proxyList[this.currentProxyIndex];
this.currentProxyIndex = (this.currentProxyIndex + 1) % this.proxyList.length;
return proxy;
}
async callAPI(endpoint, data, options = {}) {
const proxyUrl = this.getNextProxy();
const agent = new HttpsProxyAgent(proxyUrl);
// Giới hạn tần suất: không quá 100 yêu cầu mỗi phút trên proxy
const proxyKey = proxyUrl;
const now = Date.now();
const count = this.requestCounts.get(proxyKey) || { count: 0, resetTime: now + 60000 };
if (count.count >= 100 && now < count.resetTime) {
// Chuyển sang proxy tiếp theo
return this.callAPI(endpoint, data, options);
}
try {
const response = await axios({
method: options.method || 'POST',
url: endpoint,
data: data,
httpsAgent: agent,
timeout: options.timeout || 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MyService/1.0)',
...options.headers
}
});
// Cập nhật bộ đếm
if (now >= count.resetTime) {
this.requestCounts.set(proxyKey, { count: 1, resetTime: now + 60000 });
} else {
count.count++;
}
return response.data;
} catch (error) {
if (error.response?.status === 429) {
// Vượt quá giới hạn tần suất - chuyển sang proxy khác
console.log(`Giới hạn tần suất trên ${proxyUrl}, chuyển proxy`);
return this.callAPI(endpoint, data, options);
}
throw error;
}
}
}
// Sử dụng
const apiService = new ExternalAPIService([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080'
]);
module.exports = apiService;
Cân bằng tải và khả năng chịu lỗi
Các máy chủ proxy đóng vai trò quan trọng trong việc đảm bảo tính khả dụng cao của hệ thống microservices thông qua cân bằng tải và tự động chuyển đổi khi gặp lỗi. Khi bạn chạy nhiều phiên bản của một dịch vụ (để mở rộng theo chiều ngang), proxy phân phối các yêu cầu giữa chúng, đảm bảo tải được phân bổ đều.
Các thuật toán cân bằng tải chính:
- Round Robin — gửi yêu cầu lần lượt đến mỗi máy chủ trong danh sách, đơn giản và hiệu quả cho các máy chủ đồng nhất
- Least Connections — gửi yêu cầu đến máy chủ có số lượng kết nối hoạt động ít nhất, phù hợp cho các yêu cầu lâu dài
- IP Hash — gán khách hàng đến một máy chủ cụ thể dựa trên IP của họ, đảm bảo các phiên liên kết
- Weighted Round Robin — phân phối dựa trên sức mạnh của các máy chủ (các máy chủ mạnh hơn nhận nhiều yêu cầu hơn)
- Random — chọn ngẫu nhiên máy chủ, phù hợp cho các dịch vụ không trạng thái
# Cấu hình HAProxy cho cân bằng tải với kiểm tra tình trạng
global
maxconn 4096
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 50s
timeout server 50s
option httplog
frontend api_frontend
bind *:80
default_backend api_servers
backend api_servers
balance roundrobin
# Kiểm tra tình trạng: kiểm tra /health mỗi 2 giây
option httpchk GET /health
http-check expect status 200
# Logic thử lại
retries 3
option redispatch
# Các máy chủ với trọng số (server3 mạnh gấp 2 lần)
server server1 10.0.1.10:8080 check weight 1 maxconn 500
server server2 10.0.1.11:8080 check weight 1 maxconn 500
server server3 10.0.1.12:8080 check weight 2 maxconn 1000
# Máy chủ dự phòng (chỉ sử dụng khi các máy chủ chính không khả dụng)
server backup1 10.0.2.10:8080 check backup
Kiểm tra tình trạng là một chức năng rất quan trọng cho khả năng chịu lỗi. Proxy thường xuyên kiểm tra tính khả dụng của từng máy chủ (thường thông qua endpoint HTTP /health hoặc /ready) và tự động loại bỏ các máy chủ không hoạt động khỏi nhóm cân bằng tải. Khi máy chủ phục hồi và bắt đầu phản hồi các kiểm tra tình trạng, nó sẽ tự động quay lại nhóm.
Các chiến lược chịu lỗi thông qua proxy:
- Active Health Checks — proxy chủ động kiểm tra các máy chủ để xác định tính khả dụng của chúng
- Passive Health Checks — proxy theo dõi các yêu cầu thực tế và loại bỏ các máy chủ khi tích lũy lỗi
- Circuit Breaker — tạm thời ngắt kết nối dịch vụ gặp sự cố để ngăn chặn các lỗi chuỗi
- Graceful Degradation — chuyển sang chế độ hoạt động đơn giản hơn hoặc dữ liệu đã được cache khi gặp lỗi
- Failover to Backup — tự động chuyển sang các máy chủ hoặc khu vực dự phòng
// Python: triển khai Circuit Breaker cho proxy đến các dịch vụ bên ngoài
from datetime import datetime, timedelta
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed" # Hoạt động bình thường
OPEN = "open" # Dịch vụ không khả dụng, các yêu cầu bị chặn
HALF_OPEN = "half_open" # Chế độ thử nghiệm sau khi phục hồi
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60, success_threshold=2):
self.failure_threshold = failure_threshold # Số lỗi trước khi mở
self.timeout = timeout # Giây trước khi thử phục hồi
self.success_threshold = success_threshold # Số thành công để đóng lại
self.state = CircuitState.CLOSED
self.failures = 0
self.successes = 0
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout):
self.state = CircuitState.HALF_OPEN
print("Circuit breaker: chuyển sang HALF_OPEN")
else:
raise Exception("Circuit breaker OPEN: dịch vụ không khả dụng")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
self.failures = 0
if self.state == CircuitState.HALF_OPEN:
self.successes += 1
if self.successes >= self.success_threshold:
self.state = CircuitState.CLOSED
self.successes = 0
print("Circuit breaker: phục hồi, chuyển sang CLOSED")
def _on_failure(self):
self.failures += 1
self.last_failure_time = datetime.now()
if self.failures >= self.failure_threshold:
self.state = CircuitState.OPEN
print(f"Circuit breaker OPEN: {self.failures} lỗi liên tiếp")
# Sử dụng
breaker = CircuitBreaker(failure_threshold=3, timeout=30)
def call_external_service():
# Mã của bạn để yêu cầu đến API bên ngoài thông qua proxy
pass
try:
result = breaker.call(call_external_service)
except Exception as e:
# Logic dự phòng: cache, giá trị mặc định, v.v.
print(f"Dịch vụ không khả dụng: {e}")
An ninh giao tiếp giữa các dịch vụ
Trong kiến trúc microservices, các máy chủ proxy cung cấp nhiều cấp độ an ninh: mã hóa lưu lượng, xác thực các dịch vụ, bảo vệ chống lại các cuộc tấn công và cách ly các phân đoạn mạng. Nếu không có cấu hình an ninh đúng cách, lưu lượng nội bộ giữa các dịch vụ có thể bị chặn hoặc giả mạo.
Các khía cạnh chính của an ninh thông qua proxy:
- Mutual TLS (mTLS) — xác thực hai chiều, khi cả khách hàng và máy chủ đều kiểm tra chứng chỉ của nhau. Service Mesh tự động cấu hình mTLS giữa tất cả các dịch vụ
- TLS Termination — proxy giải mã lưu lượng HTTPS tại biên, kiểm tra và chuyển tiếp đến các dịch vụ qua kênh bảo mật
- JWT Validation — kiểm tra các token truy cập ở cấp độ proxy, trước khi yêu cầu đến dịch vụ
- IP Whitelisting — giới hạn quyền truy cập đến các dịch vụ chỉ từ các địa chỉ IP được phép
- DDoS Protection — giới hạn tần suất, giới hạn kết nối, bảo vệ chống lại SYN flood ở cấp độ proxy
- WAF (Web Application Firewall) — lọc các yêu cầu độc hại, bảo vệ chống lại SQL injection, XSS
# Cấu hình NGINX với SSL/TLS và an ninh
server {
listen 443 ssl http2;
server_name api.internal.example.com;
# Chứng chỉ SSL
ssl_certificate /etc/nginx/certs/api.crt;
ssl_certificate_key /etc/nginx/certs/api.key;
# Các giao thức và mã hóa hiện đại
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Chứng chỉ của khách hàng cho mTLS
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;
# Tiêu đề an toàn
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
# Giới hạn tần suất
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
limit_req zone=api burst=200 nodelay;
# Giới hạn kết nối
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 10;
# IP whitelisting
allow 10.0.0.0/8; # Mạng nội bộ
allow 172.16.0.0/12; # VPC
deny all;
location / {
# Kiểm tra token JWT
auth_jwt "API bị hạn chế";
auth_jwt_key_file /etc/nginx/jwt_key.json;
proxy_pass http://backend_service;
# Chuyển tiếp thông tin về chứng chỉ của khách hàng
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-Verify $ssl_client_verify;
}
}
Service Mesh đơn giản hóa việc cấu hình an ninh, tự động tạo và xoay vòng các chứng chỉ cho mTLS, áp dụng các chính sách truy cập và mã hóa toàn bộ lưu lượng giữa các dịch vụ. Ví dụ, trong Istio, có thể đặt chính sách rằng dịch vụ "payment" chỉ có thể nhận yêu cầu từ dịch vụ "order", và điều này sẽ tự động được áp dụng ở cấp độ proxy mà không cần thay đổi mã của các dịch vụ.
Quan trọng cho production: Luôn sử dụng mTLS cho giao tiếp nội bộ giữa các dịch vụ, ngay cả khi chúng nằm trong cùng một mạng riêng. Điều này bảo vệ khỏi các cuộc tấn công kiểu man-in-the-middle và đảm bảo xác thực ở cấp độ dịch vụ, không chỉ ở cấp độ mạng.
Giám sát và ghi log lưu lượng proxy
Các máy chủ proxy cung cấp cơ hội độc đáo cho việc giám sát tập trung toàn bộ lưu lượng trong hệ thống microservices. Vì toàn bộ lưu lượng đi qua proxy (cả bên ngoài thông qua API Gateway và bên trong thông qua Service Mesh), bạn có được cái nhìn đầy đủ về hoạt động của hệ thống mà không cần phải thêm công cụ vào từng dịch vụ.
Các chỉ số chính để giám sát ở cấp độ proxy:
- Latency (độ trễ) — thời gian xử lý yêu cầu ở mỗi giai đoạn: proxy, dịch vụ, API bên ngoài
- Throughput (băng thông) — số lượng yêu cầu mỗi giây, khối lượng dữ liệu đã truyền
- Error Rate — tỷ lệ lỗi (4xx, 5xx), loại lỗi, các endpoint gặp vấn đề
- Connection Metrics — số lượng kết nối hoạt động, sử dụng pool kết nối
- Circuit Breaker State — trạng thái của các circuit breaker cho mỗi dịch vụ
- SSL/TLS Metrics — trạng thái của các chứng chỉ, phiên bản giao thức, lỗi handshake
# Cấu hình NGINX để xuất các chỉ số vào Prometheus
server {
listen 9113;
location /metrics {
stub_status;
access_log off;
allow 10.0.0.0/8; # Chỉ cho máy chủ Prometheus
deny all;
}
}
# Ghi log ở định dạng JSON cho ghi log có cấu trúc
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status": "$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"http_referrer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
access_log /var/log/nginx/access.log json_combined;
Distributed Tracing — một trong những khả năng mạnh mẽ nhất của việc giám sát thông qua proxy. Mỗi yêu cầu nhận được một trace ID duy nhất, mà proxy thêm vào các tiêu đề và chuyển tiếp đến các dịch vụ tiếp theo. Các hệ thống tracing (Jaeger, Zipkin) thu thập thông tin từ tất cả các proxy và xây dựng toàn bộ đường đi của yêu cầu qua hệ thống, cho thấy thời gian mà nó đã dành ở mỗi dịch vụ.
// Node.js: thêm tracing vào middleware proxy
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');
const app = express();
// Middleware để thêm trace ID
app.use((req, res, next) => {
// Lấy trace ID từ tiêu đề hoặc tạo mới
const traceId = req.headers['x-trace-id'] || uuidv4();
const spanId = uuidv4();
// Thêm vào tiêu đề để chuyển tiếp
req.traceId = traceId;
req.spanId = spanId;
res.setHeader('x-trace-id', traceId);
// Ghi log thời gian bắt đầu xử lý
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
// Ghi log có cấu trúc để phân tích
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
traceId: traceId,
spanId: spanId,
method: req.method,
path: req.path,
status: res.statusCode,
duration: duration,
userAgent: req.headers['user-agent'],
ip: req.ip
}));
});
next();
});
// Endpoint proxy với việc chuyển tiếp các tiêu đề tracing
app.all('/api/*', async (req, res) => {
const targetService = determineTargetService(req.path);
try {
const response = await axios({
method: req.method,
url: `http://${targetService}${req.path}`,
data: req.body,
headers: {
...req.headers,
'x-trace-id': req.traceId,
'x-parent-span-id': req.spanId,
'x-span-id': uuidv4() // Span mới cho yêu cầu downstream
}
});
res.status(response.status).json(response.data);
} catch (error) {
console.error(JSON.stringify({
traceId: req.traceId,
error: error.message,
service: targetService
}));
res.status(500).json({ error: 'Dịch vụ không khả dụng' });
}
});
function determineTargetService(path) {
if (path.startsWith('/api/users')) return 'user-service:8080';
if (path.startsWith('/api/orders')) return 'order-service:8080';
return 'default-service:8080';
}
app.listen(3000);
Cảnh báo dựa trên các chỉ số của proxy cho phép phát hiện nhanh chóng các vấn đề. Ví dụ, có thể thiết lập cảnh báo cho: sự gia tăng đột ngột độ trễ (có thể một trong các dịch vụ đang giảm hiệu suất), tăng tỷ lệ lỗi vượt quá ngưỡng (các vấn đề với mã hoặc phụ thuộc), thay đổi mẫu lưu lượng (có thể là cuộc tấn công DDoS hoặc tải virus).
Ví dụ triển khai trên Python và Node.js
Hãy xem xét các ví dụ thực tế về việc tích hợp proxy vào các microservices trên Python và Node.js cho các kịch bản khác nhau: giao tiếp nội bộ, làm việc với các API bên ngoài, cân bằng tải.
Python: dịch vụ với proxy cho các API bên ngoài
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import httpx
import asyncio
from typing import List, Optional
import logging
app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ProxyConfig(BaseModel):
url: str
max_requests_per_minute: int = 60
class ProxyPool:
def __init__(self, proxies: List[ProxyConfig]):
self.proxies = proxies
self.current_index = 0
self.request_counts = {p.url: 0 for p in proxies}
self.reset_time = asyncio.get_event_loop().time() + 60
async def get_next_proxy(self) -> str:
# Đặt lại bộ đếm mỗi phút
current_time = asyncio.get_event_loop().time()
if current_time >= self.reset_time:
self.request_counts = {p.url: 0 for p in self.proxies}
self.reset_time = current_time + 60
# Tìm proxy với yêu cầu còn lại
for _ in range(len(self.proxies)):
proxy = self.proxies[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxies)
if self.request_counts[proxy.url] < proxy.max_requests_per_minute:
self.request_counts[proxy.url] += 1
return proxy.url
# Tất cả các proxy đã hết giới hạn
raise HTTPException(status_code=429, detail="Tất cả các proxy đã bị giới hạn")
# Khởi tạo nhóm proxy
proxy_pool = ProxyPool([
ProxyConfig(url="http://user:pass@proxy1.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy2.example.com:8080", max_requests_per_minute=100),
ProxyConfig(url="http://user:pass@proxy3.example.com:8080", max_requests_per_minute=100)
])
class ExternalAPIClient:
def __init__(self, proxy_pool: ProxyPool):
self.proxy_pool = proxy_pool
async def fetch_data(self, endpoint: str, params: dict = None) -> dict:
proxy_url = await self.proxy_pool.get_next_proxy()
async with httpx.AsyncClient(proxies={"http://": proxy_url, "https://": proxy_url}) as client:
try:
response = await client.get(
endpoint,
params=params,
timeout=10.0,
headers={"User-Agent": "MyMicroservice/1.0"}
)
response.raise_for_status()
logger.info(f"Đã lấy thành công từ {endpoint} qua {proxy_url}")
return response.json()
except httpx.HTTPStatusError as e:
logger.error(f"Lỗi HTTP {e.response.status_code} từ {endpoint}")
raise HTTPException(status_code=e.response.status_code, detail=str(e))
except httpx.RequestError as e:
logger.error(f"Lỗi yêu cầu đến {endpoint}: {e}")
raise HTTPException(status_code=503, detail="API bên ngoài không khả dụng")
api_client = ExternalAPIClient(proxy_pool)
@app.get("/data/{resource_id}")
async def get_external_data(resource_id: str):
"""Endpoint, nhận dữ liệu từ API bên ngoài qua proxy"""
external_endpoint = f"https://api.external-service.com/v1/resources/{resource_id}"
try:
data = await api_client.fetch_data(external_endpoint)
return {"status": "success", "data": data}
except HTTPException:
raise
except Exception as e:
logger.error(f"Lỗi không mong muốn: {e}")
raise HTTPException(status_code=500, detail="Lỗi nội bộ của máy chủ")
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "external-api-proxy"}
# Khởi chạy: uvicorn main:app --host 0.0.0.0 --port 8000
Node.js: API Gateway với cân bằng tải
const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const app = express();
app.use(express.json());
// Cấu hình các microservices
const services = {
users: [
{ url: 'http://user-service-1:8001', healthy: true, activeConnections: 0 },
{ url: 'http://user-service-2:8001', healthy: true, activeConnections: 0 },
// Thêm các dịch vụ khác ở đây
],
orders: [
{ url: 'http://order-service-1:8002', healthy: true, activeConnections: 0 },
{ url: 'http://order-service-2:8002', healthy: true, activeConnections: 0 },
// Thêm các dịch vụ khác ở đây
],
};
// Middleware để cân bằng tải
app.use('/api/users', (req, res, next) => {
const service = getNextService(services.users);
proxyRequest(service.url, req, res);
});
app.use('/api/orders', (req, res, next) => {
const service = getNextService(services.orders);
proxyRequest(service.url, req, res);
});
// Hàm để lấy dịch vụ tiếp theo
function getNextService(serviceList) {
// Logic để lấy dịch vụ tiếp theo (có thể là round-robin hoặc dựa trên tình trạng)
return serviceList[0]; // Ví dụ đơn giản
}
// Hàm để proxy yêu cầu đến dịch vụ
function proxyRequest(serviceUrl, req, res) {
axios({
method: req.method,
url: `${serviceUrl}${req.path}`,
data: req.body,
headers: {
...req.headers,
'X-Forwarded-For': req.ip,
},
})
.then(response => {
res.status(response.status).json(response.data);
})
.catch(error => {
res.status(error.response?.status || 500).json({ error: 'Dịch vụ không khả dụng' });
});
}
// Khởi chạy máy chủ
app.listen(3000, () => {
console.log('API Gateway đang chạy trên cổng 3000');
});