Triển khai HashiCorp Vault trên Kubernetes: Hướng dẫn về Dynamic Secrets và Sidecar Injector

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

Khởi động nhanh (5 phút): Dựng Vault với Sidecar Injector

Việc lưu trữ mật khẩu cơ sở dữ liệu tĩnh dưới dạng Kubernetes Secrets mã hóa base64 tiềm ẩn nguy cơ rò rỉ, gây khó khăn cho việc kiểm toán và gần như chắc chắn sẽ dẫn đến sự cố lúc 2 giờ sáng khi ai đó quên rotate mật khẩu thủ công. HashiCorp Vault giải quyết triệt để vấn đề này. Nó đóng vai trò là một broker trung tâm, cấp phát các token ngắn hạn và có thể tự xoay vòng theo nhu cầu của microservices. Bạn có thể dựng một cụm development local trong chưa đầy 5 phút với Helm chart chính thức.

Trước tiên, hãy thêm repository của HashiCorp và triển khai một instance Vault ở chế độ dev cùng với Agent Injector:

# Thêm Helm repo của HashiCorp
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

# Cài đặt Vault kèm kích hoạt Agent Injector
helm install vault hashicorp/vault \
  --namespace vault \
  --create-namespace \
  --set "server.dev.enabled=true" \
  --set "injector.enabled=true"

Kiểm tra để đảm bảo cả hai pod đều đạt trạng thái Running:

kubectl get pods -n vault

Quan sát thấy vault-0 (1/1) và vault-agent-injector-* (1/1). Ở dev mode, Vault tự động unseal khi khởi động với root token mặc định là root. Chế độ này chạy hoàn toàn trên RAM, rất nhanh và sẵn sàng cho việc thử nghiệm.

Đi sâu chi tiết: Kubernetes Authentication và Dynamic Secrets Engine

Các mật khẩu tĩnh dùng chung tuyệt đối không nên đưa trực tiếp vào container ứng dụng. Thay vào đó, các pod sẽ xuất trình JSON Web Token (JWT) ngắn hạn được Kubernetes tự động mount vào lúc runtime. Vault đọc token này, gọi Kubernetes API TokenReview để xác minh namespace và ServiceAccount của pod, ánh xạ danh tính đó với một Vault policy, rồi trả về một lease có thời hạn nhất định.

Hãy cùng cấu hình Kubernetes auth backend trên Vault và kết nối một role cơ sở dữ liệu PostgreSQL tạm thời.

1. Cấu hình Kubernetes Auth trong Vault

Mở một interactive shell bên trong container Vault của bạn:

kubectl exec -it vault-0 -n vault -- /bin/sh

Bật engine và trỏ nó tới cluster API cục bộ:

# Bật Kubernetes auth
vault auth enable kubernetes

# Cấu hình Vault kết nối tới Kubernetes API
vault write auth/kubernetes/config \
    kubernetes_host="https://kubernetes.default.svc.cluster.local:443"

2. Tạo Policy và Role

Bắt đầu với một policy có quyền hạn tối thiểu. Đoạn cấu hình sau cấp cho các pod quyền chỉ đọc đối với các thông tin xác thực tạm thời tại đường dẫn database/creds/app-role:

vault policy write app-policy - <<EOF
path "database/creds/app-role" {
  capabilities = ["read"]
}
EOF

Tiếp theo, gắn policy đó với ServiceAccount payment-service trong namespace default:

vault write auth/kubernetes/role/payment-role \
    bound_service_account_names=payment-service \
    bound_service_account_namespaces=default \
    policies=app-policy \
    ttl=1h

3. Bật Dynamic Database Secrets Engine

Với dynamic secrets, các ứng dụng không bao giờ phải chia sẻ chung thông tin xác thực. Vault kết nối với PostgreSQL bằng quyền admin và tự động tạo các username độc nhất như v-token-paym-abc123-1718000000 theo thời gian thực. Ngay khi TTL 60 phút hết hạn, Vault sẽ tự động hủy role cơ sở dữ liệu này.

# Gắn (mount) database secrets engine
vault secrets enable database

# Cấu hình kết nối Postgres
vault write database/config/my-postgres \
    plugin_name=postgresql-database-plugin \
    allowed_roles="app-role" \
    connection_url="postgresql://{{username}}:{{password}}@postgres.database.svc:5432/orders?sslmode=disable" \
    username="vaultadmin" \
    password="AdminSecretPasswordHere"

# Định nghĩa role tạo database user tạm thời
vault write database/roles/app-role \
    db_name=my-postgres \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
        GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

Sử dụng nâng cao: Thực tế với Vault Agent Sidecar Injector

Việc viết lại ứng dụng để tích hợp Vault SDK hiếm khi khả thi trong thực tế. Vault Agent Injector giải quyết vấn đề này một cách gọn gàng. Một mutating webhook của Kubernetes sẽ theo dõi các pod, inject một init container cùng một sidecar chạy ngầm gọn nhẹ, đồng thời mount một volume in-memory dùng chung tại /vault/secrets. Sidecar này sẽ xử lý xác thực, lấy thông tin đăng nhập và âm thầm gia hạn chúng trước khi hết hạn.

Deploy ứng dụng với các Sidecar Annotation

Dưới đây là manifest Deployment sẵn sàng cho môi trường production, được cấu hình với cú pháp Consul Template trong phần annotations:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: payment-service
  namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
  namespace: default
  labels:
    app: payment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: payment
  template:
    metadata:
      labels:
        app: payment
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "payment-role"
        vault.hashicorp.com/agent-inject-secret-database-creds.txt: "database/creds/app-role"
        vault.hashicorp.com/agent-inject-template-database-creds.txt: |
          {{- with secret "database/creds/app-role" -}}
          DB_USER="{{ .Data.username }}"
          DB_PASSWORD="{{ .Data.password }}"
          {{- end -}}
    spec:
      serviceAccountName: payment-service
      containers:
      - name: payment-api
        image: alpine:3.19
        command: ["/bin/sh", "-c"]
        args:
          - "while true; do if [ -f /vault/secrets/database-creds.txt ]; then cat /vault/secrets/database-creds.txt; fi; sleep 10; done"
        resources:
          limits:
            memory: "128Mi"
            cpu: "100m"

Thứ tự khởi động ở đây rất quan trọng. Container vault-agent-init sẽ chạy trước, chặn container chính cho đến khi file database-creds.txt được tạo. Khi pod đã hoạt động, container thứ hai là vault-agent sẽ chạy nền, tự động làm mới lease vào khoảng hai phần ba thời gian sống 60 phút của nó (khoảng phút thứ 40).

Kinh nghiệm thực tế: Vận hành Day-2 & Khắc phục sự cố

Việc chuyển từ bản thử nghiệm sang môi trường production sẽ gặp phải những tình huống phát sinh khó lường. Hãy lưu ý các vấn đề vận hành thực tế sau:

  • Xử lý xoay vòng connection pool: Phần lớn các ORM (như HikariCP hay GORM) duy trì các kết nối mở vô thời hạn. Khi Vault hủy thông tin xác thực cơ sở dữ liệu đã hết hạn, các kết nối đang hoạt động trong pool có thể bị crash do lỗi xác thực. Hãy cấu hình ứng dụng để tự reload secret khi file thay đổi, hoặc thêm annotation vault.hashicorp.com/agent-inject-command để gửi tín hiệu SIGHUP đến tiến trình của bạn khi được gia hạn.
  • Kiểm tra mạng của mutating webhook: Nếu các pod mới bị treo vô thời hạn ở trạng thái Pending hoặc gặp lỗi trong quá trình admission, hãy kiểm tra port 8080 hoặc 8443 trên injector pod. Tường lửa và security group trên cloud thường chặn lưu lượng trực tiếp từ API server tới mạng overlay của worker node.
  • Kiểm tra JWT token an toàn: Quá trình debug Kubernetes auth thường đòi hỏi phải decode projected ServiceAccount token nằm tại /var/run/secrets/kubernetes.io/serviceaccount/token. Tuyệt đối không dán các token đang hoạt động vào các công cụ đám mây của bên thứ ba. Thay vào đó, hãy sử dụng JWT Decoder của ToolCraft. Công cụ này xử lý token hoàn toàn ở phía client trong trình duyệt của bạn, đảm bảo các claim và chữ ký của cluster được bảo mật cục bộ tuyệt đối.
  • Kiểm tra cú pháp template trước khi apply: Lỗi thụt lề trong các khối YAML nhiều dòng rất dễ khiến parser của Consul Template bị lỗi. Hãy chuẩn hóa các payload trung gian bằng JSON Formatter của ToolCraft, hoặc kiểm tra tính hợp lệ của manifest bằng YAML to JSON Converter của ToolCraft trước khi push lên git.
  • Bảo vệ an toàn cho storage trên production: Tuyệt đối không dùng dev mode trên production. Hãy chạy Vault ở chế độ High-Availability với Integrated Raft Storage trên một số lượng replica lẻ (ít nhất 3 hoặc 5). Thiết lập tính năng auto-unseal trên cloud (AWS KMS, GCP Cloud KMS hoặc Azure Key Vault), và thu hồi toàn bộ root token ngay sau khi các role cho đội ngũ đã được thiết lập.
Share: