Dapr trong thực tế: Tách biệt Microservices trên Kubernetes với State Management và Pub/Sub

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

Nợ tích hợp trong Microservices

Xây dựng microservices thường mang lại cảm giác bạn đang dành nhiều thời gian cho việc “lắp ống nước” (hệ thống phụ trợ) hơn là phát triển tính năng thực tế. Khi bắt đầu một hệ thống phân tán, mã nguồn của bạn nhanh chóng bị phình to bởi các logic đặc thù cho hạ tầng. Để lưu trữ một session, bạn phải thêm Redis SDK. Để gửi một tin nhắn, bạn phải dùng Kafka client. Trước khi kịp nhận ra, logic nghiệp vụ đã bị vùi lấp dưới 15 thư viện bên ngoài khác nhau cùng với các chính sách retry (thử lại) riêng biệt của chúng.

Nợ tích hợp gây khó khăn nhất khi hạ tầng của bạn cần thay đổi. Giả sử nhóm của bạn quyết định chuyển từ AWS SQS sang Azure Service Bus. Đột nhiên, bạn phải đối mặt với việc viết lại mã tích hợp cho hơn 20 dịch vụ. Sự ràng buộc chặt chẽ này biến kiến trúc linh hoạt của bạn thành một khối monolith cứng nhắc về phụ thuộc. Dapr (Distributed Application Runtime) giải quyết vấn đề này bằng cách đóng vai trò như một lớp trừu tượng phổ quát cho các dịch vụ của bạn.

Sidecar Pattern giúp đơn giản hóa việc phát triển như thế nào

Dapr cung cấp các “building blocks” (khối xây dựng) để xử lý những phần việc nặng nhọc của hệ thống phân tán. Thay vì ứng dụng của bạn giao tiếp trực tiếp với cơ sở dữ liệu, nó sẽ nói chuyện with Dapr thông qua một API HTTP hoặc gRPC đơn giản trên cổng 3500. Sau đó, Dapr sẽ chuyển đổi yêu cầu đó cho bất kỳ hạ tầng nào bạn đã cắm vào.

Cơ chế cốt lõi là Sidecar Pattern. Trên Kubernetes, Dapr chạy như một container nhỏ song song với ứng dụng của bạn trong cùng một Pod. Ứng dụng của bạn gọi tới localhost:3500, và sidecar sẽ quản lý việc kết nối mạng, bảo mật và thử lại. Trong lần triển khai sản phẩm thực tế gần nhất của tôi, phương pháp này đã giảm khoảng 30% mã nguồn tích hợp rườm rà, giúp nhóm phát triển tính năng nhanh hơn 2 ngày mỗi sprint.

Các Building Blocks chính trong hướng dẫn này

  • State Management: Một API dạng CRUD để lưu trữ và truy xuất dữ liệu mà không cần driver cơ sở dữ liệu.
  • Publish and Subscribe (Pub/Sub): Một cách chuẩn hóa để phát tin (broadcast) các sự kiện giữa các dịch vụ trong khi vẫn giữ chúng hoàn toàn ẩn danh với nhau.

Khởi tạo Cluster

Bạn sẽ cần một cluster Kubernetes (như Minikube) và Dapr CLI. Hãy bắt đầu bằng cách khởi tạo control plane của Dapr.

dapr init -k

Lệnh này triển khai Dapr operator và sidecar injector vào namespace dapr-system. Chạy lệnh sau để đảm bảo cả năm dịch vụ cốt lõi đều hoạt động tốt:

kubectl get pods -n dapr-system

Triển khai State Management

Dapr sử dụng mô hình Component. Bạn định nghĩa cơ sở dữ liệu trong một file YAML một lần duy nhất, và mã nguồn của bạn không bao giờ cần biết đó là gì. Hãy sử dụng Redis. Lưu file này dưới tên statestore.yaml:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: my-statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis-master.default.svc.cluster.local:6379
  - name: redisPassword
    secretKeyRef:
      name: redis
      key: redis-password

Sau khi áp dụng file này vào Kubernetes, ứng dụng của bạn có thể lưu dữ liệu bằng một yêu cầu POST đơn giản. Không cần SDK:

# Lưu một đơn hàng qua HTTP
curl -X POST http://localhost:3500/v1.0/state/my-statestore \
     -H "Content-Type: application/json" \
     -d '[{"key": "order_99", "value": {"sku": "A100", "qty": 1}}]'

Muốn đổi Redis sang MongoDB sau này? Chỉ cần cập nhật file YAML đó. Bạn sẽ không phải chạm vào bất kỳ dòng mã Go, Python hay Node.js nào của mình.

Giao tiếp không đồng bộ với Pub/Sub

Các hệ thống hướng sự kiện không nên yêu cầu cấu hình broker phức tạp. Trong Dapr, chúng ta định nghĩa một component pubsub.yaml để xử lý lớp nhắn tin (messaging layer).

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: my-pubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis-master.default.svc.cluster.local:6379

Phát một sự kiện

Để xuất bản (publish) một tin nhắn, dịch vụ của bạn sẽ gọi tới sidecar tại địa phương. Đây là giao diện của nó trong một lệnh shell tiêu chuẩn:

curl -X POST http://localhost:3500/v1.0/publish/my-pubsub/orders \
     -H "Content-Type: application/json" \
     -d '{"orderId": "99", "status": "paid"}'

Xử lý đăng ký (Subscriptions)

Việc đăng ký còn dễ dàng hơn. Ứng dụng của bạn chỉ cần cho Dapr biết nó muốn gì bằng cách cung cấp một endpoint GET /dapr/subscribe. Khi một tin nhắn gửi tới topic “orders”, Dapr sẽ tự động chuyển nó đến route /process-order của bạn thông qua một yêu cầu POST.

// Cấu hình đăng ký (subscription) của ứng dụng
[
  {
    "pubsubname": "my-pubsub",
    "topic": "orders",
    "route": "/process-order"
  }
]

Triển khai trên Kubernetes

Để kết nối, hãy thêm ba annotation cụ thể vào bản triển khai (deployment). Điều này báo cho Kubernetes tự động chèn (inject) Dapr sidecar.apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: template: metadata: annotations: dapr.io/enabled: "true" dapr.io/app-id: "order-processor" dapr.io/app-port: "8080" spec: containers: - name: order-service image: myregistry/order-service:v1.2

app-id là cực kỳ quan trọng. Đó là định danh duy nhất mà Dapr sử dụng để khám phá dịch vụ (service discovery) và bảo mật mTLS.

Chuẩn hóa kiến trúc của bạn

Chuyển sang Dapr là một bước thay đổi chiến lược. Đó là việc xây dựng các ứng dụng có tính di động cao mà không bị trói buộc vào các SDK của một nhà cung cấp đám mây cụ thể. Theo kinh nghiệm của tôi, thời gian thiết lập ban đầu sẽ xứng đáng ngay khi bạn cần mở rộng quy mô hoặc thay đổi dịch vụ. Hãy bắt đầu nhỏ bằng cách thay thế phần quản lý state tại địa phương. Một khi bạn thấy sidecar hoạt động, bạn có thể mở rộng sang Secrets Management hoặc Distributed Tracing của Dapr để có được cái nhìn toàn diện về cluster của mình ngay lập tức.

Share: