🚀 Chapter 4 — Production: Phần A — Đưa Online Boutique lên Amazon ECR
Ở Chapter 3D, chúng ta đã xây dựng được CI/CD có khả năng scale cho nhiều microservice bằng:
GitHub
↓
Matrix
↓
Reusable Workflow
↓
Docker Build
↓
SSH
↓
EC2
Nhưng vẫn còn một vấn đề.
Ở Chapter 3D, EC2 vẫn phải build Docker image:
GitHub Actions
↓
SSH
↓
EC2
↓
docker compose build
↓
docker compose up
Khi hệ thống lớn hơn, chúng ta muốn tách quá trình:
BUILD
↓
ARTIFACT
↓
DEPLOY
Vì vậy Chapter 4A sẽ đưa Amazon ECR vào giữa.
Kiến trúc mới:
GitHub
↓
GitHub Actions
↓
Matrix
↓
Reusable Workflow
↓
Docker Build
↓
Amazon ECR
↓
SSH
↓
EC2
↓
docker pull
↓
docker compose up
Online Boutique là ứng dụng microservices gồm nhiều service viết bằng nhiều ngôn ngữ khác nhau, rất phù hợp để tiếp tục sử dụng làm lab cho phần này.
1. Mục tiêu
Sau Chapter 4A, pipeline phải đạt được:
Developer
↓
git push
↓
GitHub
↓
GitHub Actions
↓
Matrix
↓
Reusable Workflow
↓
Docker Build
↓
Push Image → ECR
↓
SSH → EC2
↓
Pull Image ← ECR
↓
Docker Compose
↓
Running
Có 4 service:
frontend
cartservice
productcatalogservice
checkoutservice
Mỗi service sẽ có một Docker image riêng trong ECR.
Ví dụ:
ECR
├── online-boutique-frontend
├── online-boutique-cartservice
├── online-boutique-productcatalogservice
└── online-boutique-checkoutservice
2. So sánh Chapter 3D và Chapter 4A
Chapter 3D
GitHub
↓
Matrix
↓
Reusable Workflow
↓
Docker Build
↓
SSH
↓
EC2
↓
Docker Compose Build
↓
Docker Compose Up
EC2 tự build image.
Chapter 4A
GitHub
↓
Matrix
↓
Reusable Workflow
↓
Docker Build
↓
ECR
↓
SSH
↓
EC2
↓
Docker Pull
↓
Docker Compose Up
EC2 không build image nữa.
Đây là thay đổi quan trọng nhất của Chapter 4A.
3. Kiểm tra trạng thái từ Chapter 3D
Trên local:
cd online-boutique-cicd
Kiểm tra:
git status
Kiểm tra workflow:
find .github/workflows -type f
Kết quả:
.github/workflows/reusable-ci-cd.yml
.github/workflows/scale-ci-cd.yml
Đây chính là trạng thái cuối của Chapter 3D.
4. Kiểm tra Matrix Workflow
Chạy:
cat .github/workflows/scale-ci-cd.yml
Bạn sẽ có cấu trúc tương tự:
name: Scale CI/CD
on:
push:
branches:
- main
paths:
- "src/**"
- ".github/workflows/**"
jobs:
deploy:
strategy:
matrix:
service:
- frontend
- cartservice
- productcatalogservice
- checkoutservice
uses: ./.github/workflows/reusable-ci-cd.yml
with:
service: ${{ matrix.service }}
secrets: inherit
Sau này có thể đổi tên job:
deploy
thành:
build-and-deploy
nhưng chưa cần thay đổi ngay.
5. Tạo ECR Repository
Vào:
AWS Console
→ ECR
→ Repositories
→ Create repository
Chọn:
Visibility:
Private
Tạo repository đầu tiên:
online-boutique-frontend
Sau đó tạo:
online-boutique-cartservice
online-boutique-productcatalogservice
online-boutique-checkoutservice
Kết quả:
ECR
│
├── online-boutique-frontend
├── online-boutique-cartservice
├── online-boutique-productcatalogservice
└── online-boutique-checkoutservice
ECR private repository phải tồn tại trước khi push image nếu bạn không dùng repository creation template.
6. Lấy AWS Account ID
Trên local:
aws sts get-caller-identity
Kết quả:
{
"UserId": "...",
"Account": "123456789012",
"Arn": "..."
}
Lưu:
AWS_ACCOUNT_ID=123456789012
Region sử dụng:
ap-northeast-1
ECR registry sẽ có dạng:
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
7. Tạo GitHub OIDC Provider trong AWS
Chúng ta không muốn lưu AWS Access Key trực tiếp vào GitHub Actions.
Flow sẽ là:
GitHub Actions
↓
GitHub OIDC
↓
AWS IAM Role
↓
Amazon ECR
AWS cũng hỗ trợ pattern GitHub Actions sử dụng OIDC để build Docker image và push vào ECR.
Vào:
AWS Console
→ IAM
→ Identity providers
→ Add provider
Chọn:
Provider type:
OpenID Connect
URL:
https://token.actions.githubusercontent.com
Audience:
sts.amazonaws.com
Sau đó:
Add provider
8. Tạo IAM Policy cho GitHub Actions
Vào:
IAM
→ Policies
→ Create policy
Chọn:
JSON
Dùng:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:InitiateLayerUpload",
"PutImage",
"UploadLayerPart"
],
"Resource": "arn:aws:ecr:ap-northeast-1:YOUR_ACCOUNT_ID:repository/online-boutique-*"
}
]
}
Thay:
YOUR_ACCOUNT_ID
bằng AWS Account ID.
Ví dụ:
arn:aws:ecr:ap-northeast-1:123456789012:repository/online-boutique-*
Đặt tên:
GitHubActionsOnlineBoutiqueECRPush
9. Tạo IAM Role cho GitHub Actions
Vào:
IAM
→ Roles
→ Create role
Chọn:
Custom trust policy
Dùng:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:YOUR_GITHUB_USERNAME/online-boutique-cicd:ref:refs/heads/main"
}
}
}
]
}
Thay:
YOUR_ACCOUNT_ID
và:
YOUR_GITHUB_USERNAME
Ví dụ:
repo:Juu-dev/online-boutique-cicd:ref:refs/heads/main
Đặt tên:
GitHubActionsOnlineBoutiqueECRRole
10. Attach ECR Policy vào Role
Mở:
IAM
→ Roles
→ GitHubActionsOnlineBoutiqueECRRole
Chọn:
Add permissions
Attach:
GitHubActionsOnlineBoutiqueECRPush
Flow:
GitHub
↓
OIDC
↓
GitHubActionsOnlineBoutiqueECRRole
↓
ECR
11. Lấy Role ARN
Mở:
IAM
→ Roles
→ GitHubActionsOnlineBoutiqueECRRole
Copy ARN.
Ví dụ:
arn:aws:iam::123456789012:role/GitHubActionsOnlineBoutiqueECRRole
12. Thêm GitHub Repository Variables
Vào repository:
GitHub
→ online-boutique-cicd
→ Settings
→ Secrets and variables
→ Actions
→ Variables
Tạo:
AWS_ROLE_ARN
Value:
arn:aws:iam::123456789012:role/GitHubActionsOnlineBoutiqueECRRole
Tạo thêm:
AWS_REGION
Value:
ap-northeast-1
13. Chuẩn bị EC2 để Pull ECR
Bây giờ GitHub Actions đã có quyền:
Push → ECR
Nhưng EC2 cũng cần quyền:
Pull ← ECR
Không nên lưu AWS Access Key trên EC2.
Thay vào đó sử dụng:
EC2
↓
IAM Role
↓
ECR Read
14. Tạo IAM Policy cho EC2
Vào:
IAM
→ Policies
→ Create policy
Dùng:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "arn:aws:ecr:ap-northeast-1:YOUR_ACCOUNT_ID:repository/online-boutique-*"
}
]
}
Đặt tên:
EC2OnlineBoutiqueECRPull
15. Attach Role cho EC2
Tạo IAM Role:
EC2OnlineBoutiqueRole
Attach policy:
EC2OnlineBoutiqueECRPull
Sau đó:
EC2
→ Actions
→ Security
→ Modify IAM role
Chọn:
EC2OnlineBoutiqueRole
16. Kiểm tra AWS CLI trên EC2
SSH vào EC2:
ssh -i ~/Downloads/chapter1-key.pem ubuntu@YOUR_EC2_IP
Kiểm tra:
aws --version
Nếu chưa có:
sudo apt update
sudo apt install -y awscli
Kiểm tra IAM Role:
aws sts get-caller-identity
Bạn phải thấy Account ID của AWS.
17. Kiểm tra EC2 Login ECR
Trên EC2:
aws ecr get-login-password \
--region ap-northeast-1 \
| docker login \
--username AWS \
--password-stdin \
YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com
Nếu thành công:
Login Succeeded
Đây là cách AWS hướng dẫn authenticate Docker với private ECR. Authorization token có thời hạn 12 giờ.
18. Sửa Docker Compose trên EC2
Đây là thay đổi quan trọng.
Chapter 3D:
docker compose
↓
build source code
Chapter 4A:
docker compose
↓
pull image từ ECR
Mở:
cd ~/online-boutique
Mở file Compose:
nano compose.yaml
Với frontend, thay phần build bằng image:
services:
frontend:
image: YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend:${FRONTEND_TAG}
cartservice:
cartservice:
image: YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-cartservice:${CARTSERVICE_TAG}
productcatalogservice:
productcatalogservice:
image: YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-productcatalogservice:${PRODUCTCATALOGSERVICE_TAG}
checkoutservice:
checkoutservice:
image: YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-checkoutservice:${CHECKOUTSERVICE_TAG}
Nếu file Compose hiện tại của bạn dùng cấu trúc khác, chỉ thay
build:của 4 service này bằngimage:tương ứng. Không cần thay đổi các service dependency khác.
19. Không dùng latest
Không dùng:
frontend:latest
Mà dùng:
frontend:<git-sha>
Ví dụ:
frontend:a81c92f
Flow:
Git Commit
↓
a81c92f
↓
Docker Image
↓
ECR
↓
frontend:a81c92f
Điều này giúp EC2 biết chính xác version nào đang chạy.
20. Tạo file .env trên EC2
Trong:
cd ~/online-boutique
Tạo:
nano .env
Ví dụ:
FRONTEND_TAG=a81c92f
CARTSERVICE_TAG=a81c92f
PRODUCTCATALOGSERVICE_TAG=a81c92f
CHECKOUTSERVICE_TAG=a81c92f
Nhưng chúng ta sẽ không sửa file này thủ công mỗi lần deploy.
GitHub Actions sẽ cập nhật nó.
21. Test Pull Image thủ công
Trước khi đưa vào GitHub Actions, test một lần.
Trên EC2:
export AWS_REGION=ap-northeast-1
export AWS_ACCOUNT_ID=YOUR_ACCOUNT_ID
Login:
aws ecr get-login-password \
--region $AWS_REGION \
| docker login \
--username AWS \
--password-stdin \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
Pull:
docker pull \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/online-boutique-frontend:IMAGE_TAG
Kiểm tra:
docker images
Nếu thấy:
online-boutique-frontend
thì ECR → EC2 đã hoạt động.
22. Sửa Reusable Workflow
Bây giờ mở local:
nano .github/workflows/reusable-ci-cd.yml
Thay workflow cũ bằng:
name: Reusable Build and Deploy
on:
workflow_call:
inputs:
service:
required: true
type: string
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build Docker image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: online-boutique-${{ inputs.service }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build \
-t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
src/${{ inputs.service }}
- name: Push Docker image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: online-boutique-${{ inputs.service }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker push \
$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Deploy to EC2
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd ~/online-boutique
export AWS_REGION=${{ vars.AWS_REGION }}
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws ecr get-login-password \
--region $AWS_REGION \
| docker login \
--username AWS \
--password-stdin \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
docker pull \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/online-boutique-${{ inputs.service }}:${{ github.sha }}
echo "${{ inputs.service }}_TAG=${{ github.sha }}" > .deploy-${{ inputs.service }}
case "${{ inputs.service }}" in
frontend)
export FRONTEND_TAG=${{ github.sha }}
;;
cartservice)
export CARTSERVICE_TAG=${{ github.sha }}
;;
productcatalogservice)
export PRODUCTCATALOGSERVICE_TAG=${{ github.sha }}
;;
checkoutservice)
export CHECKOUTSERVICE_TAG=${{ github.sha }}
;;
esac
docker compose up -d ${{ inputs.service }}
23. Một vấn đề quan trọng với .env
Workflow trên mới chỉ export biến trong SSH session.
Nếu Compose sử dụng:
${FRONTEND_TAG}
thì cần đảm bảo biến đó được truyền vào Compose.
Cách đơn giản hơn cho lab là cập nhật .env trên EC2.
Thay phần:
echo "${{ inputs.service }}_TAG=${{ github.sha }}" > .deploy-${{ inputs.service }}
bằng:
sed -i "/^${{ inputs.service }}_TAG=/d" .env
echo "${{ inputs.service }}_TAG=${{ github.sha }}" >> .env
Sau đó:
docker compose up -d ${{ inputs.service }}
Vì vậy phần deploy cuối cùng nên là:
- name: Deploy to EC2
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd ~/online-boutique
export AWS_REGION=${{ vars.AWS_REGION }}
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
aws ecr get-login-password \
--region $AWS_REGION \
| docker login \
--username AWS \
--password-stdin \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
docker pull \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/online-boutique-${{ inputs.service }}:${{ github.sha }}
sed -i "/^${{ inputs.service }}_TAG=/d" .env
echo "${{ inputs.service }}_TAG=${{ github.sha }}" >> .env
docker compose up -d ${{ inputs.service }}
24. Kiểm tra GitHub Secrets
Vào:
GitHub
→ Settings
→ Secrets and variables
→ Actions
→ Secrets
Bạn phải có:
EC2_HOST
EC2_SSH_KEY
Trong đó:
EC2_HOST
là Public IP hoặc DNS của EC2.
EC2_SSH_KEY
là private key:
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
Không commit private key vào repository.
25. Kiểm tra workflow
Local:
cat .github/workflows/reusable-ci-cd.yml
Flow phải là:
Checkout
↓
Configure AWS credentials
↓
Login ECR
↓
Docker Build
↓
Docker Push
↓
SSH EC2
↓
ECR Login
↓
Docker Pull
↓
Update .env
↓
Docker Compose Up
26. Commit thay đổi
Kiểm tra:
git status
Xem diff:
git diff
Add:
git add .github/workflows
Commit:
git commit -m "deploy images from ecr"
Push:
git push origin main
27. GitHub Actions chạy Matrix
Vào:
GitHub
→ Actions
→ Scale CI/CD
Bạn sẽ thấy:
Scale CI/CD
│
├── build-and-deploy (frontend)
│
├── build-and-deploy (cartservice)
│
├── build-and-deploy (productcatalogservice)
│
└── build-and-deploy (checkoutservice)
Mỗi job:
Docker Build
↓
Push ECR
↓
SSH EC2
↓
Pull ECR
↓
Docker Compose
28. Kiểm tra ECR
Vào:
AWS Console
→ ECR
→ Repositories
Mở:
online-boutique-frontend
Bạn sẽ thấy:
Image tag
a81c92f
Tương tự:
online-boutique-cartservice
online-boutique-productcatalogservice
online-boutique-checkoutservice
ECR sẽ lưu các image được push từ GitHub Actions.
29. Kiểm tra EC2
SSH vào EC2:
ssh -i ~/Downloads/chapter1-key.pem ubuntu@YOUR_EC2_IP
Kiểm tra:
docker images
Bạn sẽ thấy:
online-boutique-frontend
online-boutique-cartservice
online-boutique-productcatalogservice
online-boutique-checkoutservice
Kiểm tra container:
docker compose ps
Ví dụ:
NAME STATUS
frontend Up
cartservice Up
productcatalog Up
checkoutservice Up
30. Kiểm tra Image đang chạy
Frontend:
docker inspect frontend \
--format '{{.Config.Image}}'
Kết quả:
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend:a81c92f
Đây chính là điều chúng ta muốn.
EC2 đang chạy:
ECR Image
chứ không phải:
EC2 tự build source code
31. Test Deployment
Thay đổi một phần frontend.
Ví dụ:
git checkout -b test-ecr-deployment
Thực hiện một thay đổi nhỏ trong:
src/frontend/
Commit:
git add .
git commit -m "test ecr deployment"
git push origin test-ecr-deployment
Sau đó merge vào main.
GitHub Actions chạy:
GitHub
↓
Matrix
↓
frontend
↓
Docker Build
↓
ECR
↓
SSH
↓
EC2
↓
docker pull
↓
docker compose up
32. Kiểm tra Git SHA
Local:
git rev-parse HEAD
Ví dụ:
a81c92f
ECR:
frontend:a81c92f
EC2:
docker inspect frontend \
--format '{{.Config.Image}}'
Kết quả:
.../online-boutique-frontend:a81c92f
Ba nơi đều cùng một version:
Git
↓
a81c92f
↓
ECR
↓
a81c92f
↓
EC2
↓
a81c92f
33. Architecture cuối Chapter 4A
Developer
│
▼
GitHub
│
▼
GitHub Actions
│
▼
Matrix
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
frontend cartservice checkoutservice
│ │ │
└──────────────────┼──────────────────┘
▼
Reusable Workflow
│
▼
Docker Build
│
▼
Amazon ECR
│
┌──────────────┼──────────────┐
▼ ▼ ▼
frontend cart checkout
image image image
│ │ │
└──────────────┼──────────────┘
▼
SSH
│
▼
EC2
│
ECR Authentication
│
▼
Docker Pull
│
▼
Docker Compose
│
▼
Running
34. Sự thay đổi quan trọng
Chapter 3D
GitHub Actions
↓
Docker Build
↓
SSH
↓
EC2
↓
Docker Compose Build
↓
Container
Chapter 4A
GitHub Actions
↓
Docker Build
↓
ECR
↓
SSH
↓
EC2
↓
Docker Pull
↓
Docker Compose
↓
Container
Build không còn xảy ra trên EC2.
Đây chính là bước đầu tiên để chuyển từ:
Source Code → Server
sang:
Source Code
↓
Docker Image
↓
Container Registry
↓
Server
35. Kết quả cần đạt
Cuối Chapter 4A:
✅ GitHub Actions
✅ Matrix
✅ Reusable Workflow
✅ Docker Build
✅ Amazon ECR
✅ GitHub OIDC → AWS
✅ EC2 IAM Role → ECR
✅ SSH deployment
✅ Docker Pull
✅ Docker Compose
✅ Git SHA image tag
Architecture:
GitHub
↓
Matrix
↓
Reusable Workflow
↓
Docker Build
↓
ECR
↓
SSH
↓
EC2 Pull
↓
Docker Compose
Chưa làm:
❌ ECR Lifecycle Policy
❌ Vulnerability Scanning
❌ Immutable Tag
❌ Security Gate
❌ Approval
❌ Dev → Staging → Production Promotion
Các phần này sẽ được xây dựng tiếp trong Chapter 4B trở đi.
36. Tư duy của Chapter 4A
Chapter 3D giải quyết:
Làm sao chạy CI/CD cho nhiều microservice mà không copy-paste workflow?
Chapter 4A giải quyết:
Build image xong thì quản lý và deploy artifact đó như thế nào?
Và architecture bắt đầu trở thành:
BUILD
↓
Docker Image
↓
ECR
↓
DEPLOY
↓
EC2
Đây là nền tảng để các phần Production tiếp theo xây dựng:
4A
ECR + Deploy
↓
4B
Lifecycle
↓
4C
Security Scan
↓
4D
Immutable Artifact
↓
4E
Security Gate
↓
4F
Artifact Promotion
All Rights Reserved