0

🚀 Chapter 2 — Automation: Từ Manual Deployment đến CI/CD

Bối cảnh: ShopNow đã có khách hàng và team phát triển bắt đầu lớn lên. Việc SSH vào Production để build và deploy thủ công không còn phù hợp.

Mục tiêu: Xây dựng pipeline CI/CD đầu tiên bằng Azure DevOps, tự động hóa Build → Test → Package → Deploy.


1. Vấn đề không còn nằm ở Docker

Ở Phase 1, chúng ta có:

Developer
    │
    ▼
  GitHub
    │
    │ SSH
    ▼
Cloud VM
    │
    ├── git pull
    ├── docker build
    └── docker compose up

Khi team chỉ có vài người, cách này vẫn hoạt động.

Nhưng khi team phát triển lên:

5 developers
     ↓
15 developers
     ↓
30 developers

và số lần release tăng:

1 deploy/day
     ↓
5 deploy/day
     ↓
20 deploy/day

thì Production server bắt đầu trở thành nơi con người phải làm việc thủ công.

Đó mới là vấn đề.


2. Chúng ta muốn đạt được điều gì?

Mỗi khi developer push code:

git push
    │
    ▼
Pipeline
    │
    ├── Build
    ├── Test
    ├── Package
    └── Deploy
             │
             ▼
          Cloud VM

Developer không cần:

ssh server
git pull
docker build
docker compose up

nữa.

Pipeline làm thay họ.


3. Architecture mới

Sau Phase 2:

                     ┌──────────────────┐
                     │    Developer     │
                     └────────┬─────────┘
                              │
                         git push
                              │
                              ▼
                     ┌──────────────────┐
                     │      GitHub      │
                     └────────┬─────────┘
                              │
                         Webhook/Trigger
                              │
                              ▼
                 ┌────────────────────────┐
                 │     Azure DevOps       │
                 │                        │
                 │  Build → Test → Image  │
                 │             ↓          │
                 │          Deploy        │
                 └────────────┬───────────┘
                              │
                              ▼
                     ┌──────────────────┐
                     │     Cloud VM     │
                     │                  │
                     │ Docker Compose   │
                     └──────────────────┘

Đây là bước chuyển quan trọng:

Phase 1

Human → Server
Phase 2

Human → Git → Pipeline → Server

Con người không còn trực tiếp thực hiện từng bước release.


4. Tại sao chọn Azure DevOps?

Trong Phase này chúng ta sử dụng:

Azure DevOps + Azure Pipelines

Không phải vì Azure DevOps là công cụ duy nhất.

Điều quan trọng cần học là CI/CD concept.

Các công cụ khác như:

GitHub Actions
GitLab CI
Jenkins
CircleCI
Azure Pipelines

đều giải quyết bài toán tương tự:

Source Code
    ↓
Trigger
    ↓
Build
    ↓
Test
    ↓
Package
    ↓
Deploy

Vì vậy:

Đừng học thuộc YAML của Azure Pipelines. Hãy học pipeline architecture.


5. Step 1 — Xác định Release Process hiện tại

Trước khi viết pipeline, hãy document quy trình cũ.

Phase 1:

Developer
    │
    │ git push
    ▼
 GitHub
    │
    │ SSH
    ▼
Production
    │
    ├── git pull
    ├── docker build
    ├── docker compose up
    └── verify

Ta đặt câu hỏi:

Bước nào có thể tự động?

Gần như tất cả:

git pull       → Pipeline checkout
docker build   → Pipeline build
test           → Pipeline test
docker compose → Pipeline deploy
verify         → Pipeline health check

Đây là cách một DevOps engineer bắt đầu thiết kế CI/CD.

Không phải mở Azure DevOps rồi viết YAML ngay.


6. Step 2 — Tạo CI Pipeline đầu tiên

Pipeline đầu tiên chưa cần deploy.

Chúng ta chỉ muốn trả lời:

"Code mới có build và test được không?"

Architecture:

Git Push
   │
   ▼
Azure Pipeline
   │
   ├── Checkout
   │
   ├── Install Dependencies
   │
   ├── Build
   │
   └── Test

Ví dụ pipeline đơn giản:

trigger:
  - main

pool:
  vmImage: ubuntu-latest

steps:

  - checkout: self

  - script: |
      npm ci
    displayName: Install dependencies

  - script: |
      npm run build
    displayName: Build

  - script: |
      npm test
    displayName: Test

Điểm quan trọng không phải syntax.

Hãy nhìn pipeline như một DAG:

        Source
           │
           ▼
      Dependencies
           │
           ▼
         Build
           │
           ▼
          Test
           │
           ▼
         PASS

Nếu Test fail:

Build
  │
  ▼
 Test
  │
  └──── FAIL
          │
          ▼
       Pipeline STOP

Code lỗi không được đi tiếp.


7. CI thực sự có nghĩa là gì?

CI không đơn giản là:

"Có một file YAML."

CI là:

Mỗi thay đổi code được kiểm tra tự động và thường xuyên.

Ví dụ developer push:

commit abc123

Pipeline chạy:

Build       ✓
Unit Test   ✓
Lint        ✓

→ Change này đủ điều kiện để tiếp tục.

Nhưng nếu:

Build       ✓
Unit Test   ✗

thì:

             ┌── Test FAIL
             │
Build ───────┤
             │
             └── STOP

Đây chính là quality gate đầu tiên.


8. Step 3 — Đóng gói thành Docker Image

Sau khi CI pass, chúng ta cần một artifact để deploy.

Với application containerized:

Source Code
     │
     ▼
   Build
     │
     ▼
Docker Image

Ví dụ:

docker build \
  -t shopnow:${BUILD_BUILDID} .

Không nên chỉ dùng:

shopnow:latest

latest không cho chúng ta biết chính xác version nào đang chạy.

Thay vào đó:

shopnow:1024
shopnow:1025
shopnow:1026

hoặc:

shopnow:<git-sha>

Ví dụ:

shopnow:a81f32c

Bây giờ chúng ta có một artifact có identity rõ ràng.


9. Tại sao Image phải có Version?

Giả sử Production đang chạy:

shopnow:latest

Developer deploy version mới.

Sau đó application crash.

Ta không biết:

latest = version nào?

Nhưng nếu dùng:

shopnow:a81f32c

ta biết chính xác:

Git commit
      │
      ▼
Docker Image
      │
      ▼
Production

Đây là nền tảng cho rollback sau này.


10. Step 4 — CI → CD

Bây giờ chúng ta nối CI với deployment.

Pipeline:

                  CI
                   │
Git Push ──► Build ──► Test
                      │
                     PASS
                      │
                      ▼
                 Docker Build
                      │
                      ▼
                     CD
                      │
                      ▼
                 Cloud VM

Pipeline hoàn chỉnh:

Developer
    │
    │ git push
    ▼
  GitHub
    │
    ▼
Azure DevOps
    │
    ├──────────── CI ────────────┐
    │                            │
    │  Checkout                  │
    │  Install                   │
    │  Build                     │
    │  Test                      │
    │                            │
    └────────── PASS ───────────┘
                     │
                     ▼
                 Docker Build
                     │
                     ▼
                   Deploy
                     │
                     ▼
                Cloud VM
                     │
                     ▼
              Docker Compose

11. Step 5 — Deploy tự động

Ở Phase 1:

ssh production
git pull
docker compose up -d

Ở Phase 2:

Pipeline
   │
   ▼
Deploy Job
   │
   ▼
Cloud VM

Pipeline có thể thực hiện remote deployment.

Ví dụ concept:

- script: |
    ssh $(DEPLOY_USER)@$(DEPLOY_HOST) \
      "cd /opt/shopnow &&
       docker compose pull &&
       docker compose up -d"
  displayName: Deploy

Nhưng có một vấn đề mới:

Pipeline đang cần credential để truy cập Production.

Đừng hard-code:

password: my-password

Đây là lúc chúng ta cần Secrets & Service Connections.


12. Step 6 — Quản lý Secret

Pipeline có thể cần:

SSH Private Key
Cloud Credential
Registry Credential
Database Password
API Token

Không được đưa trực tiếp vào Git:

password: "123456"

Thay vào đó:

Azure DevOps
     │
     ├── Variables
     ├── Secret Variables
     └── Service Connections

Pipeline chỉ reference:

$(DEPLOY_HOST)
$(DEPLOY_USER)
$(SSH_KEY)

Secret thực tế được quản lý bên ngoài source code.


13. Service Connection là gì?

Hãy hình dung Pipeline cần nói chuyện với:

Azure
  │
  ├── VM
  └── Resources

Harbor
  │
  └── Container Registry

Kubernetes
  │
  └── Cluster

Nếu mỗi pipeline tự quản lý credential:

Pipeline A → Credential A
Pipeline B → Credential B
Pipeline C → Credential C

sẽ rất khó quản lý.

Service Connection tạo ra một abstraction:

                Azure DevOps
                     │
             ┌───────┼────────┐
             ▼       ▼        ▼
          Azure    Harbor   Kubernetes
             │       │        │
        Connection Connection Connection

Pipeline chỉ cần sử dụng connection phù hợp.


14. Step 7 — Tách Build và Deploy

Đừng biến pipeline thành một script khổng lồ.

Thay vào đó:

                 Pipeline
                    │
          ┌─────────┴─────────┐
          │                   │
          ▼                   ▼
       CI Stage            CD Stage
          │                   │
     ┌────┼────┐              │
     ▼    ▼    ▼              ▼
   Build Test Scan         Deploy
                              │
                              ▼
                           Cloud VM

Ví dụ:

stages:

- stage: CI
  jobs:
  - job: BuildAndTest
    steps:
      - script: npm ci
      - script: npm run build
      - script: npm test

- stage: CD
  dependsOn: CI
  condition: succeeded()
  jobs:
  - job: Deploy
    steps:
      - script: ./deploy.sh

Điểm quan trọng:

CI PASS
   │
   ▼
CD được phép chạy


CI FAIL
   │
   ▼
CD KHÔNG được chạy

15. Step 8 — Multi-Stage Pipeline

Khi startup tiếp tục phát triển, chúng ta không chỉ có Production.

Bắt đầu xuất hiện:

Development
     │
     ▼
  Staging
     │
     ▼
 Production

Pipeline:

Git Push
   │
   ▼
 Build
   │
   ▼
 Test
   │
   ▼
 Deploy Dev
   │
   ▼
 Deploy Staging
   │
   ▼
 Deploy Production

Đây chính là Multi-Stage CI/CD.

Nhưng có một vấn đề:

Có nên để Production deploy tự động ngay khi Staging pass?

Trong startup nhỏ:

Staging PASS
     │
     ▼
Production

có thể chấp nhận.

Nhưng khi application quan trọng hơn:

Staging PASS
     │
     ▼
   Approval
     │
     ▼
 Production

Con người quay trở lại pipeline, nhưng lần này chỉ để ra quyết định, không phải thực hiện deployment.

Đây là một distinction rất quan trọng:

Manual Deployment
→ Human thực hiện công việc


Approval Gate
→ Human chỉ quyết định có cho phép deployment hay không

16. Architecture cuối Phase 2

Sau Phase 2, architecture trở thành:

                           Developer
                               │
                               │ git push
                               ▼
                          ┌─────────┐
                          │ GitHub  │
                          └────┬────┘
                               │
                               ▼
                     ┌──────────────────┐
                     │   Azure DevOps   │
                     │                  │
                     │       CI         │
                     │   ┌──────────┐   │
                     │   │ Build    │   │
                     │   │ Test     │   │
                     │   │ Package  │   │
                     │   └────┬─────┘   │
                     │        │         │
                     │       PASS       │
                     │        │         │
                     │       CD         │
                     │        │         │
                     └────────┼─────────┘
                              │
                              ▼
                       ┌──────────────┐
                       │    Cloud VM  │
                       │              │
                       │ Docker       │
                       │ Compose      │
                       └──────────────┘

Và flow release:

Code
  ↓
Git Push
  ↓
Build
  ↓
Test
  ↓
Docker Image
  ↓
Deploy
  ↓
Cloud VM

Con người đã được loại khỏi phần lớn thao tác thủ công.


17. Nhưng Automation tạo ra vấn đề mới

Đây là phần quan trọng nhất.

CI/CD đã giải quyết:

Manual Build       ✓
Manual Test        ✓
Manual Deploy      ✓
Human Error        ↓
Release Speed      ↑

Nhưng bây giờ chúng ta có một vấn đề khác.

Pipeline build ra:

shopnow:a81f32c

Nhưng image đang nằm ở đâu?

Nếu chúng ta chỉ build image trên Azure DevOps agent:

Azure DevOps Agent
      │
      ▼
Docker Image
      │
      X
      │
      └── Agent bị destroy

Image biến mất sau khi job kết thúc.

Chúng ta cần một nơi lưu trữ image.


18. Vấn đề tiếp theo: Container Registry

Kiến trúc bắt đầu cần thêm:

                    Azure DevOps
                         │
                    Docker Build
                         │
                         ▼
                    Docker Image
                         │
                         ▼
                 ┌────────────────┐
                 │    Registry    │
                 └───────┬────────┘
                         │
                         ▼
                     Cloud VM

Registry sẽ trở thành nguồn lưu trữ artifact của application.

Nhưng registry public như Docker Hub đặt ra những câu hỏi mới:

Ai được push?
Ai được pull?
Image có vulnerability không?
Image nào đang được sử dụng?
Bao giờ image hết hạn?
Có thể audit không?

Và đó chính là lý do Harbor xuất hiện ở Phase tiếp theo.


19. Architecture Evolution

Hãy nhìn toàn bộ quá trình:

PHASE 1
──────────────────────────────

Developer
    ↓
GitHub
    ↓
SSH
    ↓
Cloud VM
    ↓
Docker Compose

Problem:
Manual deployment


                ↓


PHASE 2
──────────────────────────────

Developer
    ↓
GitHub
    ↓
Azure DevOps
    │
    ├── Build
    ├── Test
    ├── Package
    └── Deploy
            ↓
        Cloud VM

Problem:
Where do we store
and govern images?


                ↓


PHASE 3
──────────────────────────────

Developer
    ↓
GitHub
    ↓
Azure DevOps
    ↓
Container Registry
    ↓
Deployment

Problem:
Pipelines are becoming
duplicated as the company grows.

Đây là lý do Phase 3 sẽ tập trung vào:

Reusable CI/CD Pipeline — xây dựng pipeline như một Internal Platform thay vì copy-paste YAML cho từng service.


20. Senior Thinking

Sau Phase 2, đừng chỉ nhớ:

Azure Pipelines
YAML
Stages
Jobs
Steps
Variables
Service Connections

Hãy nhớ flow:

                 SOURCE
                   │
                   ▼
                  CI
                   │
          ┌────────┼────────┐
          ▼        ▼        ▼
        Build     Test     Scan
          │
          ▼
       Artifact
          │
          ▼
          CD
          │
     ┌────┴─────┐
     ▼          ▼
    Dev      Staging
                │
             Approval
                │
                ▼
           Production

Và hãy luôn đặt 5 câu hỏi:

① Trigger là gì?

Push?
Pull Request?
Tag?
Manual?
Schedule?

② Artifact là gì?

Docker Image?
Binary?
Package?

③ Artifact được lưu ở đâu?

Registry?
Artifact Repository?

④ Ai được phép deploy?

Developer?
Pipeline?
Approval?

⑤ Rollback bằng cách nào?

Previous Image?
Previous Artifact?
Previous Release?

Nếu bạn trả lời được 5 câu hỏi này, bạn đã hiểu architecture CI/CD tốt hơn rất nhiều so với việc thuộc lòng YAML.


Key Takeaways

Manual Deployment
       ↓
Human Bottleneck
       ↓
CI/CD
       ↓
Automated Build & Test
       ↓
Automated Deployment
       ↓
Artifact Management
       ↓
Need Container Registry
       ↓
Need Reusable Pipelines

Phase 2 không nhằm mục tiêu biến bạn thành người viết Azure Pipeline YAML thật nhanh.

Mục tiêu là giúp bạn hiểu:

CI/CD là một hệ thống biến code change thành một release có thể kiểm soát được.

Và khi số lượng service tăng từ:

1 application

lên:

10 services
↓
50 services
↓
100 services

thì việc copy pipeline cho từng service sẽ trở thành một vấn đề mới.

Đó là lúc chúng ta bước sang:

Phase 3 — Scale: Reusable CI/CD Platform

"Nếu công ty có 100 microservices, chúng ta có nên viết 100 pipeline YAML khác nhau không?"

Câu trả lời là không.

Chúng ta cần biến CI/CD thành một platform có thể tái sử dụng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí