Cấu hình Firewall (UFW) và chứng chỉ SSL cho dịch vụ
Bước đầu tiên là khóa chặt các cổng mạng chỉ cho phép lưu lượng truy cập từ các thành phần nội bộ (Internal) và thiết lập mã hóa TLS cho giao tiếp giữa Command Side, Query Side và Event Store.
Tắt UFW nếu đang bật và thiết lập lại chính sách mặc định để từ chối tất cả kết nối vào (deny incoming) nhưng cho phép kết nối ra (allow outgoing) nhằm đảm bảo an toàn chủ động.
sudo ufw disable
sudo ufw default deny incoming
sudo ufw default allow outgoing
Kết quả: UFW chuyển sang trạng thái inactive, sẵn sàng để cấu hình các quy tắc mới.
Cài đặt UFW và kích hoạt lại với các quy tắc cụ thể. Chỉ mở cổng 80 (HTTP) và 443 (HTTPS) cho công chúng, các cổng dịch vụ nội bộ (PostgreSQL 5432, Event Store 2113, gRPC 8081) chỉ mở cho subnet nội bộ.
sudo apt update && sudo apt install -y ufw
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 10.0.0.0/8 to any port 5432 proto tcp
sudo ufw allow from 10.0.0.0/8 to any port 2113 proto tcp
sudo ufw allow from 10.0.0.0/8 to any port 8081 proto tcp
sudo ufw enable
Kết quả: Firewall kích hoạt (active), chỉ cho phép truy cập web từ bên ngoài và truy cập database/internal services từ mạng nội bộ 10.0.0.0/8.
Triển khai chứng chỉ SSL tự ký cho Event Store và API
Tạo thư mục chứa chứng chỉ và sinh ra Private Key cùng Certificate Signing Request (CSR) dùng thuật toán RSA 4096 bit để bảo mật cao cho giao tiếp nội bộ.
sudo mkdir -p /etc/ssl/private/eventstore
sudo openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout /etc/ssl/private/eventstore/eventstore.key -out /etc/ssl/certs/eventstore.crt -subj "/C=VN/ST=HCM/City=HCM/O=MyCompany/OU=IT/CN=eventstore.internal"
sudo chmod 600 /etc/ssl/private/eventstore/eventstore.key
sudo chown root:root /etc/ssl/private/eventstore/eventstore.key
Kết quả: Tạo ra file key riêng tư (.key) và chứng chỉ (.crt) với quyền truy cập hạn chế (600) để bảo vệ key.
Cấu hình Event Store để sử dụng chứng chỉ này. Chỉnh sửa file cấu hình chính để bật HTTPS và trỏ đến đường dẫn file cert và key vừa tạo.
sudo nano /etc/eventstore/config.yml
Nội dung trong file /etc/eventstore/config.yml cần thêm/modify các dòng sau:
node:
ssl:
enabled: true
certPath: /etc/ssl/certs/eventstore.crt
keyPath: /etc/ssl/private/eventstore/eventstore.key
requireClientCertificate: false
Kết quả: Event Store sẽ khởi động lại và lắng nghe trên cổng HTTPS (thường là 2113 hoặc 443 tùy cấu hình port) với mã hóa TLS.
Verify bằng cách kiểm tra trạng thái SSL từ client. Sử dụng curl để gọi API của Event Store qua HTTPS và xem thông tin chứng chỉ.
curl -kv https://localhost:2113/streams -H "Host: eventstore.internal"
Kết quả mong đợi: Output hiển thị dòng * SSL certificate verify ok hoặc * subject: ... CN=eventstore.internal, xác nhận kết nối mã hóa thành công.
Triển khai Prometheus và Grafana để giám sát hiệu năng
Cài đặt Prometheus và cấu hình scraping
Cài đặt Prometheus từ repository chính thức hoặc snap để có bản mới nhất tương thích với Ubuntu 24.04, sau đó tạo file cấu hình để thu thập metrics từ Event Store.
sudo apt install -y prometheus prometheus-node-exporter
sudo systemctl enable prometheus
sudo systemctl enable prometheus-node-exporter
Kết quả: Dịch vụ Prometheus và Node Exporter được cài đặt và tự động khởi động khi server lên.
Viết file cấu hình /etc/prometheus/prometheus.yml để định nghĩa target scraping cho Event Store (thường có endpoint metrics trên cổng 2112 hoặc 2113 tùy phiên bản) và Node Exporter cho resource hệ thống.
sudo nano /etc/prometheus/prometheus.yml
Nội dung hoàn chỉnh cho file /etc/prometheus/prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
- job_name: 'eventstore'
metrics_path: '/metrics'
static_configs:
- targets: ['localhost:2112']
scheme: 'http'
Kết quả: Prometheus đã được cấu hình để quét metrics từ chính nó, hệ thống Ubuntu và Event Store mỗi 15 giây.
Khởi động lại Prometheus để áp dụng cấu hình mới và kiểm tra trạng thái target trong giao diện web.
sudo systemctl restart prometheus
sudo systemctl status prometheus
Kết quả mong đợi: Status hiển thị "active (running)" và không có lỗi trong log. Truy cập http://localhost:9090/targets thấy cả 3 job đều có trạng thái "UP".
Cài đặt và cấu hình Grafana
Cài đặt Grafana để trực quan hóa dữ liệu metrics đã thu thập, sử dụng repository chính thức để đảm bảo tính tương thích với Ubuntu 24.04.
sudo apt install -y adduser libfontconfig1
sudo adduser --system --group --home /var/lib/grafana --shell /bin/false grafana
sudo apt install -y grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-server
Kết quả: Grafana được cài đặt, tạo user hệ thống và tự động khởi động dịch vụ.
Cấu hình kết nối giữa Grafana và Prometheus. Truy cập giao diện web Grafana (mặc định port 3000), thêm Data Source mới và chỉ định URL của Prometheus.
sudo curl -X POST -H "Content-Type: application/json" -d '{"name":"Prometheus","type":"prometheus","access":"proxy","url":"http://localhost:9090","isDefault":true}' http://admin:admin@localhost:3000/api/datasources
Kết quả mong đợi: API trả về mã 200 (Created) hoặc 201, xác nhận Data Source Prometheus đã được thêm vào Grafana.
Import Dashboard mẫu cho Event Store. Sử dụng Dashboard ID chính thức từ Grafana Labs (thường là ID 10747 hoặc tương tự cho Event Store) để hiển thị các biểu đồ về Throughput, Latency, và Event Count.
curl -X POST -H "Content-Type: application/json" -d '{"dashboard":{"uid":"es-dashboard","title":"Event Store Metrics","inputs":[{"name":"DS_PROMETHEUS","label":"Prometheus","pluginId":"prometheus","type":"datasource","value":"prometheus"}]}}' http://admin:admin@localhost:3000/api/dashboards/import
Kết quả mong đợi: Dashboard mới được tạo trong thư mục "My Dashboards" của Grafana, sẵn sàng hiển thị biểu đồ thực tế.
Verify bằng cách truy cập http://localhost:3000, đăng nhập (admin/admin) và xem dashboard Event Store. Kiểm tra xem các metric như events_written_total có tăng lên khi bạn gửi lệnh không.
Xử lý các tình huống lỗi phổ biến
Xử lý Duplicate Event (Sự kiện trùng lặp)
Trong kiến trúc Event Sourcing, vấn đề Duplicate Event xảy ra khi consumer nhận cùng một sự kiện nhiều lần do mạng chập chờn hoặc retry logic. Cần triển khai cơ chế Idempotency (Tính không thay đổi) trên Command Side.
Cấu hình Command Side để lưu trữ ID của các sự kiện đã xử lý vào một bảng cache (hoặc Redis) với TTL ngắn, hoặc sử dụng cơ chế deduplication dựa trên metadata của Event Store.
sudo nano /app/command-side/src/EventProcessor.cs
Logic xử lý (ví dụ giả định C#):
public async Task ProcessEventAsync(Event @event)
{
var eventId = @event.Metadata["EventId"];
if (await _dedupCache.ExistsAsync(eventId))
{
Console.WriteLine($"Duplicate event detected: {eventId}. Ignoring.");
return;
}
await _dedupCache.SetAsync(eventId, "processed", TimeSpan.FromMinutes(10));
// Thực thi logic business logic tại đây
await _repository.SaveAsync(@event.Data);
}
Kết quả mong đợi: Khi cùng một event được gửi vào lần thứ 2, hệ thống sẽ log cảnh báo và bỏ qua, đảm bảo dữ liệu không bị ghi lặp (double charge, double update).
Xử lý Lost Event (Sự kiện bị mất)
Lost Event xảy ra khi sự kiện được ghi vào Event Store nhưng chưa kịp được consumer xử lý thì hệ thống sập. Cần thiết lập cơ chế Replay và Checkpoint để đảm bảo không bỏ sót sự kiện nào.
Cấu hình Consumer (Query Side) để lưu trữ vị trí (Position/Checkpoint) của sự kiện đã xử lý cuối cùng vào một bảng bền vững (Database). Khi khởi động lại, consumer phải đọc checkpoint cũ và replay từ đó.
sudo nano /app/query-side/src/SubscriptionManager.cs
Logic xử lý (ví dụ giả định C#):
public async Task SubscribeAsync()
{
var lastCheckpoint = await _checkpointRepository.GetLastPositionAsync();
// Nếu không có checkpoint, bắt đầu từ đầu (Head)
var startPosition = lastCheckpoint ?? EventStoreConstants.Start;
var subscription = await _eventStore.SubscribeAsync(startPosition);
await foreach (var @event in subscription)
{
try
{
await ProcessEventAsync(@event);
await _checkpointRepository.SavePositionAsync(@event.Position);
}
catch (Exception ex)
{
// Log lỗi nhưng KHÔNG dừng subscription
Console.WriteLine($"Error processing event {ex.Message}");
}
}
}
Kết quả mong đợi: Nếu server sập giữa chừng, khi khởi động lại, Query Side sẽ tự động đọc vị trí cuối cùng đã lưu và tiếp tục xử lý các sự kiện còn lại, đảm bảo dữ liệu đồng bộ 100%.
Verify bằng cách giả lập lỗi: Chạy một lệnh tạo sự kiện, sau đó kill ngay tiến trình Query Side bằng kill -9 , rồi khởi động lại. Kiểm tra xem sự kiện đó có được xử lý đúng lúc khởi động lại không.
Các mẹo nâng cao để mở rộng quy mô (Scaling)
Tối ưu hóa Kernel và Filesystem cho I/O
Để mở rộng quy mô Event Store trên Ubuntu 24.04, bước quan trọng nhất là tối ưu hóa I/O disk vì Event Store ghi dữ liệu liên tục. Cần điều chỉnh các tham số kernel để tăng tốc độ write.
sudo nano /etc/sysctl.conf
Thêm các dòng sau vào cuối file để tối ưu hóa bộ nhớ cache và I/O:
vm.dirty_ratio = 20
vm.dirty_background_ratio = 5
vm.vfs_cache_pressure = 50
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
Kết quả mong đợi: Giảm độ trễ khi ghi dữ liệu xuống disk và tăng khả năng xử lý các kết nối đồng thời cao.
Áp dụng các thay đổi cấu hình kernel vừa thực hiện.
sudo sysctl -p
Kết quả: Hệ thống áp dụng các tham số mới ngay lập tức mà không cần reboot.
Cấu hình Persistence và Backup tự động
Để mở rộng quy mô an toàn, cần đảm bảo dữ liệu Event Store được sao lưu định kỳ và có cơ chế khôi phục nhanh. Sử dụng công cụ eventstore backup tích hợp sẵn.
sudo nano /etc/cron.d/eventstore-backup
Tạo script backup chạy mỗi ngày lúc 2 AM để lưu snapshot vào thư mục riêng hoặc S3 bucket.
0 2 * * * root /usr/bin/eventstore backup --output /var/backup/eventstore-daily-$(date +\%Y-\%m-\%d).tar.gz
Kết quả mong đợi: Cronjob được tạo và sẽ tự động chạy backup mỗi ngày, tạo file tar.gz chứa toàn bộ dữ liệu events và snapshots.
Triển khai Horizontal Scaling (Chia tách Read/Write)
Khi tải tăng cao, chiến lược scaling tốt nhất cho CQRS là tách biệt Command Side (Write) và Query Side (Read) ra các server riêng biệt. Command Side giữ nguyên 1 instance (hoặc cluster nhỏ), Query Side có thể mở rộng (scale-out) tùy theo lượng truy vấn.
Cấu hình Load Balancer (Nginx) để phân phối traffic Query đến nhiều instance Query Side, trong khi Command Side vẫn trỏ trực tiếp vào Event Store.
sudo nano /etc/nginx/sites-available/query-side-lb
Nội dung cấu hình Nginx:
upstream query_side {
server 10.0.0.10:8080;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
}
server {
listen 80;
server_name query.api.mycompany.com;
location / {
proxy_pass http://query_side;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Kết quả mong đợi: Traffic truy vấn được phân tải đều cho 3 server Query Side, giúp tăng thông lượng đọc lên gấp 3 lần mà không ảnh hưởng đến hiệu năng ghi của Event Store.
Verify bằng cách kích hoạt site Nginx và test load:
sudo ln -s /etc/nginx/sites-available/query-side-lb /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
ab -n 1000 -c 50 http://localhost/query-side-lb/health
Kết quả mong đợi: Test load cho thấy thời gian phản hồi trung bình giảm và không có lỗi 503 (Service Unavailable), xác nhận scaling thành công.
Điều hướng series:
Mục lục: Series: Triển khai Database CQRS với Event Sourcing và Ubuntu 24.04
« Phần 7: Triển khai Snapshotting và khôi phục trạng thái