Xây Dựng Pipeline CI/CD Từ Con Số 0 Đến Production: Docker → Kubernetes → Terraform

Xây Dựng Pipeline CI/CD Từ Con Số 0 Đến Production: Docker → Kubernetes → Terraform

Hướng dẫn thực chiến xây dựng pipeline CI/CD cho môi trường production với Docker, GitHub Actions, Kubernetes, và Terraform. Config thật, bài học thật.


Mục lục

Nếu quy trình deploy của bạn vẫn còn bao gồm việc SSH vào server rồi chạy git pull, thì bài viết này chính là sự can thiệp cần thiết dành cho bạn. Chúng ta cần nói chuyện nghiêm túc. Cách làm đó không thể mở rộng, rất dễ gây ra lỗi do con người, và thẳng thắn mà nói, đó là nguồn gốc của các sự cố sập hệ thống. Một pipeline CI/CD (Continuous Integration/Continuous Deployment - Tích hợp Liên tục/Triển khai Liên tục) đúng nghĩa sẽ tự động hóa con đường từ lúc commit code cho đến khi lên production, giúp việc triển khai của bạn nhanh hơn, đáng tin cậy hơn và bớt đáng sợ hơn.

Bài viết này là một hướng dẫn thực tế, không lý thuyết suông để xây dựng một pipeline đạt chuẩn production vào năm 2026 bằng một bộ công cụ hiện đại, đã được kiểm chứng qua thực tế: Docker, GitHub Actions, Kubernetes, và Terraform.

Tổng quan về stack công nghệ

Trước khi chúng ta lặn sâu vào “chiến hào” YAML và HCL, hãy cùng xem xét kiến trúc ở tầm vĩ mô. Mục tiêu của chúng ta là tạo ra một luồng công việc liền mạch, nơi mỗi thay đổi trong code sẽ kích hoạt một chuỗi các bước kiểm tra chất lượng và triển khai tự động.

Đây là luồng hoạt động:

  1. Developer push code lên một repository trên GitHub.
  2. GitHub Actions (CI) kích hoạt một workflow. Nó sẽ build, test, và quét mã nguồn.
  3. Một Docker Image được build và đẩy lên một container registry (ví dụ: AWS ECR).
  4. ArgoCD (CD), công cụ GitOps của chúng ta, phát hiện tag image mới trong repository chứa cấu hình Kubernetes.
  5. ArgoCD đồng bộ hóa thay đổi, ra lệnh cho Kubernetes thực hiện rolling update, triển khai phiên bản mới của ứng dụng.
  6. Toàn bộ hạ tầng bên dưới—Kubernetes cluster (EKS), VPC, database (RDS)—đều được định nghĩa dưới dạng code (Infrastructure as Code) bằng Terraform.

Hãy hình dung nó như một dây chuyền lắp ráp tự động cho phần mềm của bạn.

[Git Push] -> [GitHub Actions: Build, Test, Scan] -> [Push Docker Image to ECR]
                                                                   |
                                                                   v
                                [ArgoCD phát hiện thay đổi trong Git] <- [Cập nhật K8s Manifests]
                                                 |
                                                 v
                     [Kubernetes Cluster (EKS)] <---- [Terraform Quản lý Hạ tầng]
                     - Triển khai image mới
                     - Được quản lý bởi Terraform

Docker: container hóa mọi thứ

Container là nền tảng của CI/CD hiện đại. Chúng đóng gói ứng dụng và các dependency của nó vào một đơn vị duy nhất, có thể di động. Sự nhất quán này loại bỏ hoàn toàn vấn đề kinh điển “nhưng nó chạy trên máy em”.

Một kỹ thuật quan trọng là multi-stage build, giúp cho image cuối cùng của bạn gọn nhẹ và an toàn hơn.

Đây là một Dockerfile cho một ứng dụng Node.js điển hình:

# ---- Base Stage ----
# Sử dụng một phiên bản Node cụ thể để đảm bảo tính nhất quán
FROM node:22-alpine AS base
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production

# ---- Build Stage ----
# Stage này build các tài sản frontend của chúng ta
FROM base AS build
COPY . .
RUN npm install
RUN npm run build

# ---- Production Stage ----
# Đây là image cuối cùng, gọn nhẹ mà chúng ta sẽ deploy
FROM base
COPY --from=build /usr/src/app/dist ./dist
COPY --from=base /usr/src/app/node_modules ./node_modules
COPY . .

# Chạy quét bảo mật với Trivy
RUN apk add --no-cache trivy
RUN trivy fs --exit-code 1 --severity HIGH,CRITICAL .

EXPOSE 3000
CMD ["node", "src/server.js"]

Dockerfile này có ba giai đoạn. Giai đoạn build cài đặt các dev dependency và build tài sản, nhưng giai đoạn production cuối cùng chỉ sao chép các thành phần cần thiết, tạo ra một image nhỏ hơn và an toàn hơn nhiều. Chúng ta cũng chạy quét trivy trực tiếp trong quá trình build để phát hiện các lỗ hổng nghiêm trọng trước khi chúng được đẩy đi.

GitHub Actions: lớp CI

GitHub Actions là động cơ của pipeline. Nó hoạt động dựa trên sự kiện, phổ biến nhất là được kích hoạt bởi git push hoặc pull_request. Workflow được định nghĩa trong một tệp YAML bên trong thư mục .github/workflows.

Workflow này xử lý việc build, test, quét và đẩy Docker image của chúng ta.

# .github/workflows/ci.yml
name: CI-Pipeline

on:
  push:
    branches:
      - main

env:
  REGISTRY: "your-account-id.dkr.ecr.us-east-1.amazonaws.com"
  IMAGE_NAME: "my-app"

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write # Cần thiết cho OIDC

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::your-account-id:role/github-actions-role
          aws-region: us-east-1

      - name: Log in to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ env.REGISTRY }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$IMAGE_NAME:$IMAGE_TAG" >> $GITHUB_OUTPUT

Các tính năng chính ở đây:

  • Xác thực OIDC: Chúng ta không sử dụng secret IAM user có tuổi thọ dài. configure-aws-credentials sử dụng OpenID Connect để yêu cầu thông tin xác thực tạm thời một cách an toàn từ AWS. Đây là một điểm cộng rất lớn về bảo mật.
  • Gắn thẻ Image: Chúng ta gắn thẻ image với github.sha (mã hash của commit). Điều này đảm bảo mỗi image là duy nhất và có thể truy xuất được. Đừng bao giờ, không bao giờ sử dụng tag :latest trong môi trường production. Tôi đã từng thấy nó làm sập production hai lần. Cả hai lần đều vào 5 giờ chiều thứ Sáu.

Kubernetes & ArgoCD: lớp CD

Kubernetes là công cụ điều phối container của chúng ta. Nó quản lý vòng đời, khả năng mở rộng và “sức khỏe” của ứng dụng. Chúng ta định nghĩa trạng thái mong muốn trong các file manifest YAML.

Đây là một file deployment.yaml đơn giản hóa:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: your-account-id.dkr.ecr.us-east-1.amazonaws.com/my-app:commit-hash # Sẽ được CI cập nhật
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /healthz
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

Tệp này được lưu trữ trong một Git repository riêng (một “config repo”). Bước cuối cùng của pipeline CI sẽ là cập nhật tag image trong tệp này.

ArgoCD sau đó sẽ tiếp quản. Nó là một công cụ GitOps liên tục so sánh trạng thái thực tế của cluster với trạng thái mong muốn trong config repo. Khi phát hiện sự khác biệt (như tag image mới của chúng ta), nó sẽ tự động kéo các thay đổi và áp dụng chúng vào cluster, kích hoạt một quá trình rolling update an toàn. Mô hình pull-based này an toàn và mang tính khai báo hơn so với CD push-based truyền thống.

Terraform: infrastructure as code

Việc click chuột thủ công qua giao diện web của nhà cung cấp đám mây để thiết lập hạ tầng là một công thức cho thảm họa. Nó không thể lặp lại, không thể kiểm toán, và không thể mở rộng. Terraform giải quyết vấn đề này bằng cách cho phép bạn định nghĩa hạ tầng của mình dưới dạng code.

Đây là một đoạn code để tạo một cluster EKS (Elastic Kubernetes Service) trên AWS:

provider "aws" {
  region = "us-east-1"
}

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"
  // ... các cấu hình VPC khác
}

module "eks" {
  source = "terraform-aws-modules/eks/aws"
  version = "20.0.0"

  cluster_name    = "my-cluster"
  cluster_version = "1.32"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    one = {
      min_size     = 1
      max_size     = 3
      desired_size = 2
      instance_type = "t3.medium"
    }
  }
}

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "global/s3/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

Các khái niệm chính:

  • Modules: Chúng ta sử dụng các module được xây dựng sẵn cho các thành phần phức tạp như VPC và EKS. Điều này giữ cho code của chúng ta DRY (Don’t Repeat Yourself - Đừng Lặp Lại Chính Mình).
  • Quản lý State: Khối backend "s3" là cực kỳ quan trọng. Nó chỉ cho Terraform lưu trữ tệp state của nó trong một S3 bucket và sử dụng một bảng DynamoDB để khóa. Điều này ngăn chặn xung đột khi nhiều kỹ sư chạy terraform apply cùng một lúc.

Kết hợp tất cả lại

Hãy cùng theo dõi toàn bộ hành trình:

  1. Bạn push một tính năng mới lên nhánh main.
  2. GitHub Actions bắt đầu workflow CI.
  3. Code của bạn được test và một Docker image được build, gắn thẻ bằng mã hash của commit (ví dụ: .../my-app:a1b2c3d).
  4. Image được đẩy lên AWS ECR.
  5. Một bước cuối cùng trong CI job sẽ checkout K8s config repo, cập nhật deployment.yaml với tag image mới, và push thay đổi đó.
  6. ArgoCD, đang theo dõi config repo, thấy commit mới.
  7. ArgoCD áp dụng thay đổi vào EKS cluster. Kubernetes thấy image của Deployment đã thay đổi và bắt đầu một rolling update, thay thế các pod cũ bằng các pod mới một cách an toàn.
  8. Thay đổi của bạn đã được triển khai. Không có bước thủ công nào, toàn bộ quá trình đều có dấu vết để kiểm tra.

Những cạm bẫy phổ biến & mẹo hay

  • Lộ Bí Mật (Secret Leaks): Sử dụng các công cụ như git-secrets và quét repository. Lưu trữ secret trong một trình quản lý chuyên dụng như AWS Secrets Manager hoặc HashiCorp Vault, không phải trong biến môi trường.
  • Image Tags: Tôi sẽ nói lại lần nữa: đừng bao giờ dùng :latest. Nó không có tính bất biến. Hãy sử dụng commit hash hoặc semantic versioning.
  • Khóa State Terraform: Luôn sử dụng remote backend có chức năng khóa (như S3 + DynamoDB) để ngăn chặn hỏng hóc file state.
  • Giới Hạn Tài Nguyên K8s: Luôn đặt request và limit cho CPU và memory. Nếu không có chúng, một pod hoạt động ngoài tầm kiểm soát có thể làm sập cả một node.
  • Tốc Độ CI: Cache các dependency một cách tích cực trong pipeline CI của bạn (ví dụ: actions/cache cho các module npm, Docker layer caching). Một bản build mất 20 phút là kẻ giết chết năng suất của developer.

Xây dựng một pipeline CI/CD vững chắc là một sự đầu tư, nhưng lợi ích về tốc độ, sự ổn định và sự tỉnh táo của developer là vô giá. Hãy chấm dứt sự điên rồ của git pull. Tự động hóa con đường của bạn đến production.

Chúc bạn deploy thành công!

Luồng

0
⌘/Ctrl+Enter để gửiGõ / để xem lệnh · Tab để @nhắc tên