Chấm dứt nỗi lo Migration lúc 2 giờ sáng: Quản lý Database Schema dạng Declarative với SchemaHero

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

Cuộc gọi đánh thức lúc 2:14 sáng từ PagerDuty

Bây giờ là 2:14 sáng và điện thoại của bạn đang rung chuông liên hồi. Một cảnh báo từ PagerDuty vừa cắt ngang giấc ngủ của bạn vì bản deployment API mới nhất đang bị crash loop. Bạn kiểm tra log và thấy một cơn ác mộng quen thuộc: ERROR: column "last_login_at" của relation "users" không tồn tại.

Ai đó đã quên chạy script migration. Hoặc tệ hơn, script đã chạy nhưng thất bại ở bước 4 trên 10, để lại database production trong tình trạng dở dang, lỗi lầm. Các công cụ truyền thống như Flyway hay Liquibase rất mạnh mẽ, nhưng chúng hoạt động theo mô hình imperative (mệnh lệnh). Chúng dựa trên một chuỗi các script được đánh số phiên bản nghiêm ngặt (001, 002, 003). Nếu một bước thất bại hoặc ai đó thực hiện thay đổi thủ công trên production, toàn bộ pipeline của bạn sẽ bị đình trệ.

Trong môi trường Kubernetes hiện đại, chúng ta quản lý hầu hết mọi thứ—từ deployment đến network policy—theo dạng declarative (khai báo). Database schema của bạn cũng không nên là ngoại lệ. SchemaHero thay đổi tư duy này. Thay vì viết các script ALTER TABLE, bạn định nghĩa trạng thái mong muốn của bảng trong một file YAML. SchemaHero sẽ đảm nhận việc so sánh sự khác biệt (diffing) và thực thi. Kể từ khi chuyển sang mô hình này, tôi đã thấy các đội ngũ giảm hơn 80% lỗi deployment liên quan đến migration trong khi vẫn giữ cho workflow GitOps luôn sạch sẽ.

Cài đặt: Triển khai Operator

SchemaHero hoạt động như một Kubernetes operator kết hợp với một CLI plugin. Operator sẽ theo dõi các thay đổi trong cluster, trong khi CLI giúp bạn tạo schema từ các database hiện có.

Bắt đầu bằng cách cài đặt plugin kubectl thông qua Krew. Công cụ này rất cần thiết để quản lý và giám sát Kubernetes Cluster, giúp kiểm tra các bản migration và tạo các định nghĩa YAML từ môi trường hiện tại của bạn:

kubectl krew install schemahero

Tiếp theo, bạn cần chạy operator trong cluster. Mặc dù bạn có thể cài đặt trực tiếp qua CLI, tôi thực sự khuyên bạn nên sử dụng Helm cho các môi trường production. Điều này đảm bảo việc cài đặt được quản lý phiên bản và dễ dàng lặp lại:

helm repo add schemahero https://charts.schemahero.io
helm install schemahero schemahero/schemahero \
  --namespace schemahero-system \
  --create-namespace

Khi các pod trong namespace schemahero-system đã hoạt động ổn định, cluster của bạn đã sẵn sàng. Operator giờ đây sẽ lắng nghe hai Custom Resource Definitions (CRD) chính: DatabaseTable.

Cấu hình: Coi SQL như Code

SchemaHero không quan tâm đến quá trình; nó chỉ quan tâm đến đích đến. Bạn định nghĩa trạng thái cuối cùng, và operator sẽ tính toán con đường để đạt được điều đó.

1. Kết nối đến Instance

Đầu tiên, hãy định nghĩa một đối tượng Database. Điều này cho SchemaHero biết database của bạn nằm ở đâu và cách xác thực. Nó hỗ trợ PostgreSQL, MySQL và CockroachDB. Đây là cấu hình tiêu chuẩn cho một instance PostgreSQL:

apiVersion: databases.schemahero.io/v1alpha4
kind: Database
metadata:
  name: app-db
  namespace: storage
spec:
  connection:
    postgres:
      uri:
        valueFrom:
          secretKeyRef:
            name: db-credentials
            key: uri

Hãy bảo mật các chuỗi kết nối (connection strings). Secret db-credentials nên chứa một URI như postgres://user:password@postgres-svc:5432/appdb. SchemaHero sử dụng kết nối này để thực hiện “drift detection”—so sánh file YAML của bạn với engine database thực tế.

2. Định nghĩa cấu trúc bảng

Hãy quên các file .sql đi. Giờ đây bạn định nghĩa các bảng dưới dạng đối tượng Table. Nếu bạn cần thêm cột last_login_at, bạn không cần viết câu lệnh ALTER; bạn chỉ đơn giản là thêm cột đó vào spec YAML của mình.

apiVersion: schemas.schemahero.io/v1alpha4
kind: Table
metadata:
  name: users
  namespace: storage
spec:
  database: app-db
  name: users
  schema:
    postgres:
      primaryKey: [id]
      columns:
        - name: id
          type: integer
          constraints:
            notNull: true
        - name: username
          type: varchar(255)
          constraints:
            notNull: true
        - name: last_login_at
          type: timestamp with time zone
          constraints:
            nullable: true

Khi bạn apply file YAML này, SchemaHero không chỉ chạy code một cách mù quáng. Nó sẽ tạo ra một kế hoạch (plan). Nếu cột đã tồn tại, it sẽ không làm gì cả. Nếu kiểu dữ liệu khác đi, nó sẽ lập kế hoạch chuyển đổi.

An toàn là trên hết: Kiểm tra và Phê duyệt

Tự động hóa các thay đổi database đôi khi giống như đưa một chiếc máy cưa vào tay một đứa trẻ chập chững biết đi. Để ngăn chặn rủi ro, SchemaHero tạo ra một đối tượng Migration cho mỗi thay đổi. Đối tượng này đóng vai trò như một khu vực chờ (staging area), nơi bạn có thể xem lại mã SQL được tạo ra trước khi nó tác động đến dữ liệu của bạn.

Kiểm tra trạng thái của các thay đổi đang chờ xử lý bằng plugin:

kubectl schemahero get migrations -n storage

Để xem chính xác mã SQL mà SchemaHero dự định thực thi, hãy describe bản migration đó:

kubectl schemahero describe migration <migration-name> -n storage

Trong một thiết lập GitOps nghiêm ngặt sử dụng ArgoCD, bạn có thể cấu hình SchemaHero để yêu cầu phê duyệt thủ công cho các bản migration trên production. Đối với môi trường dev hoặc staging, bạn có thể đặt immediateDeploy: true để giữ cho pipeline luôn trôi chảy. Nếu một bản migration thất bại—ví dụ, nếu bạn cố thêm ràng buộc NOT NULL vào một bảng đã có 500.000 dòng dữ liệu null—operator sẽ báo cáo lỗi trong status của đối tượng Migration. Sau đó, bạn có thể cập nhật YAML để bao gồm giá trị mặc định và đẩy lại (re-push).

Chấm dứt tình trạng Configuration Drift

Việc giám sát các thay đổi này rất đơn giản vì SchemaHero tuân theo các pattern tiêu chuẩn của Kubernetes. Bạn có thể chuyển operator log sang Loki hoặc thiết lập cảnh báo Prometheus cho các đối tượng migration bị lỗi. Chạy lệnh sau để kiểm tra sức khỏe của operator:

kubectl logs -n schemahero-system -l app.kubernetes.io/name=schemahero

Sức mạnh thực sự ở đây là tính nhất quán. Nếu một DBA cấp dưới vô tình xóa một cột thủ công trên production, vòng lặp đối soát (reconciliation loop) tiếp theo của SchemaHero sẽ phát hiện ra sự sai lệch (drift). Nó sẽ thấy rằng trạng thái thực tế không khớp với Git repository của bạn và tự động tạo lại cột bị thiếu. Bằng cách coi database như một tài nguyên K8s thông thường, bạn sẽ biến những đợt migration đầy căng thẳng thành một sự kiện bình thường. Nó giúp hạ tầng của bạn kiên cố hơn và quan trọng hơn là giúp bạn có thể ngủ ngon giấc suốt đêm.

Share: