Tự động hóa thay đổi Schema cơ sở dữ liệu với Atlas và GitHub Actions: Hướng dẫn GitOps

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

Thảm họa cơ sở dữ liệu lúc 2 giờ 15 sáng

Đó là một đêm thứ Ba trong năm đầu tiên tôi làm dev. Chúng tôi chuẩn bị tung ra bản release v2.4 vào lúc nửa đêm. Code đã ổn định, Docker image đã sẵn sàng và pipeline CI/CD đang hiện màu xanh hy vọng. Tôi nhấn nút ‘Deploy’ và đi pha một tách espresso để ăn mừng.

Mười phút sau, các cảnh báo monitoring bùng nổ. Trang web của chúng tôi gặp lỗi 500 hàng loạt với 15.000 người dùng đang hoạt động. Log ghi rõ ràng: column "user_tier" does not exist. Tôi đã thêm trường này vào model Django ở máy local nhưng lại quên chạy migration SQL thủ công trên instance PostgreSQL thực tế. Tôi đã dành 45 phút tiếp theo mướt mồ hôi trước terminal, chạy các lệnh ALTER TABLE trong khi CTO nhìn chằm chằm vào đồng hồ đếm thời gian downtime. Chúng tôi đã mất 4% doanh thu hàng tuần chỉ trong một giờ. Đó là một bài học đắt giá và hoàn toàn có thể tránh được.

Tự động hóa các thay đổi cơ sở dữ liệu là một kỹ năng bắt buộc. Nếu bạn xử lý schema với sự nghiêm ngặt về quản lý phiên bản giống như code ứng dụng, bạn sẽ loại bỏ được cả một nhóm các lỗi thất bại khi triển khai.

Tại sao triển khai cơ sở dữ liệu thường thất bại

Code ứng dụng thường là stateless (không lưu trạng thái). Nếu việc triển khai gặp trục trặc, bạn chỉ cần rollback về container image trước đó. Nhưng cơ sở dữ liệu thì khác. Chúng có trạng thái (state). Bạn không thể đơn giản “hoàn tác” một lệnh DROP COLUMN một khi nó đã xóa sạch lịch sử ba năm của khách hàng.

Tại sao điều này lại xảy ra thường xuyên? Thông thường nó nằm ở ba điểm mấu chốt sau:

  • Sự phụ thuộc vào trí nhớ con người: Mong đợi một kỹ sư đang mệt mỏi chạy script thủ công lúc 3 giờ sáng là công thức cho thảm họa. Chúng ta bỏ sót các bước. Chúng ta gõ nhầm.
  • Schema Drift (Lệch schema): DB staging của bạn trông chẳng giống gì với production vì ai đó đã chạy một bản “fix nhanh” trực tiếp trên console live tháng trước.
  • Khoảng cách phiên bản: Nếu code của bạn lên v3.0 nhưng cơ sở dữ liệu vẫn kẹt ở v2.8, ứng dụng sẽ crash. Việc giữ chúng đồng bộ thủ công là điều không thể khi team của bạn lớn mạnh.

So sánh các bộ công cụ

Tôi đã thử nhiều cách tiếp cận trước khi dừng lại ở Atlas. Hiểu được nơi các phương pháp khác thất bại sẽ giúp làm rõ tại sao cách tiếp cận mang tính khai báo (declarative) lại hoạt động tốt hơn khi bạn mở rộng quy mô.

1. Thư mục SQL thủ công

Đây là cách tiếp cận kinh điển của lứa junior: một thư mục /migrations chứa đầy các file 001_init.sql002_add_email.sql. Bạn theo dõi tiến độ trong một bảng tính hoặc đơn giản là ghi nhớ trong đầu. Nó hoạt động tốt trong một tuần. Sau đó ai đó bỏ lỡ script số #005, và toàn bộ hệ thống sụp đổ.

2. Các ORM theo ngôn ngữ (Prisma, TypeORM, Django)

Những công cụ này rất tuyệt vì chúng tạo ra SQL từ code của bạn. Tuy nhiên, chúng gặp khó khăn với các bản migration phức tạp và khóa bạn vào một hệ sinh thái duy nhất. Trong môi trường microservices nơi Go, Python và Node.js đều dùng chung một cơ sở dữ liệu, thì ORM nào sẽ được chọn làm nguồn dữ liệu tin cậy (source of truth)?

3. Những gã khổng lồ chạy trên Java (Flyway, Liquibase)

Flyway và Liquibase là những cái tên kỳ cựu trong ngành. Chúng mạnh mẽ nhưng tạo cảm giác nặng nề. Chúng yêu cầu môi trường Java và thường buộc bạn phải quản lý các file cấu hình XML dài dòng. Chúng hoạt động theo kiểu “mệnh lệnh” (imperative), nghĩa là bạn phải chỉ dẫn chính xác cách thay đổi cơ sở dữ liệu theo từng bước một.

4. Cách tiếp cận của Atlas (Declarative GitOps)

Atlas coi cơ sở dữ liệu của bạn giống như cách Kubernetes coi hạ tầng. Bạn xác định “trạng thái mong muốn” (desired state) — cơ sở dữ liệu **nên** trông như thế nào — và Atlas sẽ tính toán con đường hiệu quả nhất để đạt được điều đó. Nó sạch sẽ, không phụ thuộc vào ngôn ngữ và được xây dựng cho GitOps.

Triển khai Atlas với GitHub Actions

Hãy xây dựng một pipeline tự động lint, test và áp dụng các thay đổi schema khi bạn merge một Pull Request. Điều này giúp Git repo của bạn trở thành nguồn dữ liệu tin cậy tuyệt đối.

Bước 1: Cài đặt Atlas CLI

Tải file thực thi. Trên Linux hoặc macOS, sử dụng script cài đặt:

curl -sSf https://atlasgo.sh | sh

Bước 2: Định nghĩa Schema của bạn

Atlas sử dụng HCL (HashiCorp Configuration Language). Nó dễ đọc hơn nhiều so với SQL thuần túy. Tạo file schema.hcl:

table "users" {
  schema = schema.public
  column "id" {
    null = false
    type = int
  }
  column "username" {
    null = false
    type = varchar(255)
  }
  column "email" {
    null = false
    type = varchar(255)
  }
  primary_key {
    columns = [column.id]
  }
}

Bước 3: Tạo các bản Migration có phiên bản

Đừng áp dụng thay đổi một cách mù quáng. Sử dụng workflow “Versioned Migration” để tạo ra một lịch sử mà bạn có thể kiểm tra (audit). Sử dụng một Docker container tạm thời làm sandbox để tạo các file này:

atlas migrate diff add_users_table \
  --dir "file://migrations" \
  --to "file://schema.hcl" \
  --dev-url "docker://postgres/15/dev"

Lệnh này tạo ra một thư mục migrations với các file SQL có gắn timestamp. Bây giờ bạn đã có một dấu vết lịch sử phiên bản để kiểm soát.

Bước 4: Tự động hóa kiểm tra CI

Chúng ta cần một tấm lưới an toàn. Hãy phát hiện SQL lỗi trước khi nó chạm tới một hàng dữ liệu nào. Tạo file .github/workflows/atlas-ci.yaml:

name: Atlas CI
on:
  pull_request:
    paths:
      - 'migrations/**'
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: ariga/atlas-action/migrate/lint@v1
        with:
          dir: 'file://migrations'
          dev-url: 'docker://postgres/15/dev'

Workflow này sẽ gắn cờ các thay đổi mang tính phá hủy — như xóa một bảng chính — trước khi chúng có cơ hội vào nhánh main.

Bước 5: Tự động hóa triển khai (CD)

Khi PR được merge, các thay đổi sẽ tự động được áp dụng lên môi trường live. Tạo file .github/workflows/atlas-deploy.yaml:

name: Atlas Deploy
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: ariga/atlas-action/migrate/apply@v1
        with:
          dir: 'file://migrations'
          url: ${{ secrets.DATABASE_URL }}

Lời kết

Quá trình thiết lập mất khoảng 45 phút. Nó sẽ giúp bạn tiết kiệm hàng tuần ròng rã debug lúc 3 giờ sáng trong năm tới. Một khi bạn áp dụng pipeline tự động cho cơ sở dữ liệu, nỗi sợ hãi mang tên “ngày triển khai” sẽ biến mất. Cơ sở dữ liệu sẽ không còn là một chiếc hộp đen đáng sợ mà trở thành một phần bình thường trong code của bạn.

Ba quy tắc nằm lòng:

  1. Tin tưởng Dev Database: Luôn sử dụng docker:// trong CI để xác minh các khác biệt (diff). Đó là cách duy nhất để đảm bảo SQL của bạn thực sự hợp lệ.
  2. Bảo vệ Secret: Không bao giờ code cứng DATABASE_URL. Hãy sử dụng GitHub Secrets cho mọi thứ.
  3. Kiểm tra bằng Dry Run: Nếu một bản migration có vẻ rủi ro, hãy chạy atlas migrate apply --dry-run để xem chính xác điều gì sẽ xảy ra mà không làm ảnh hưởng đến dữ liệu thực.

Hãy xây dựng những thói quen này ngay bây giờ. Bản thân bạn trong tương lai — và cả team vận hành — sẽ cảm ơn bạn vì sự yên bình trong những đêm triển khai hệ thống.

Share: