🚀 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
vì 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