🚀 Chapter 4 — Production: Phần D — Immutable Artifact với Amazon ECR
Ở Chapter 4C, chúng ta đã hoàn thành:
GitHub
↓
GitHub Actions
↓
Reusable Workflow
↓
Docker Build
↓
Amazon ECR
↓
Amazon Inspector
↓
Vulnerability Scan
Tuy nhiên, vẫn còn một vấn đề:
Nếu cùng một Docker tag bị ghi đè bởi image khác thì chúng ta có thể biết chính xác image nào đã được deploy hay không?
Trong phần này, chúng ta sẽ sử dụng immutable tag và Git commit SHA để đảm bảo mỗi Docker image có một định danh riêng.
1. Mục tiêu
Sau Chapter 4D, chúng ta sẽ có:
✅ Không sử dụng latest làm version chính
✅ Mỗi image có tag theo Git commit SHA
✅ Một tag không được ghi đè
✅ Có thể truy ngược image về commit cụ thể
✅ Có thể xác định chính xác image đã được build
✅ Chuẩn bị nền tảng cho Artifact Promotion ở Chapter 4F
Kiến trúc mới:
Developer
↓
git push
↓
GitHub
↓
GitHub Actions
↓
Reusable Workflow
↓
Docker Build
↓
Git Commit SHA
↓
Amazon ECR
↓
Amazon Inspector
2. Vấn đề với tag latest
Giả sử service frontend sử dụng tag:
frontend:latest
Lần build đầu tiên:
frontend:latest
↓
Image A
Lần build tiếp theo:
frontend:latest
↓
Image B
Tag latest vẫn giữ nguyên, nhưng nội dung image đã thay đổi.
Điều này gây khó khăn khi:
Rollback
Debug
Audit
Release tracking
Artifact promotion
Ví dụ:
Production đang chạy:
frontend:latest
Nhưng chúng ta không biết chính xác:
Image này được build từ commit nào?
Image được build lúc nào?
Image có giống image ở Staging không?
3. Sử dụng Git commit SHA làm Docker tag
Mỗi commit Git có một mã định danh riêng.
Ví dụ:
a81c92f
Thay vì:
frontend:latest
chúng ta sử dụng:
frontend:a81c92f
Commit tiếp theo:
92ab821
sẽ tạo ra:
frontend:92ab821
Kết quả:
ECR
├── frontend:a81c92f
├── frontend:92ab821
├── frontend:f72a921
└── frontend:83ab112
Mỗi commit tạo ra một tag riêng.
4. Kiểm tra project hiện tại
Trên máy local:
cd online-boutique-cicd
Kiểm tra Git:
git status
Kiểm tra các workflow:
find .github/workflows -type f
Ví dụ:
.github/workflows/
├── reusable-ci-cd.yml
└── scale-ci-cd.yml
Kiểm tra remote repository:
git remote -v
Kiểm tra branch hiện tại:
git branch --show-current
Kết quả ví dụ:
main
5. Kiểm tra các ECR repository
Kiểm tra các repository của Online Boutique:
aws ecr describe-repositories \
--query 'repositories[?starts_with(repositoryName, `online-boutique-`)].repositoryName' \
--output table
Ví dụ:
------------------------------------------------
| DescribeRepositories |
+----------------------------------------------+
| online-boutique-frontend |
| online-boutique-cartservice |
| online-boutique-paymentservice |
| online-boutique-checkoutservice |
+----------------------------------------------+
Kiểm tra image hiện tại của frontend:
aws ecr list-images \
--repository-name online-boutique-frontend \
--output table
Ví dụ:
-----------------------------------------
| ListImages |
+----------------------+----------------+
| imageDigest | imageTag |
+----------------------+----------------+
| sha256:abc... | latest |
| sha256:def... | a81c92f |
+----------------------+----------------+
Trong phần này, chúng ta sẽ dần chuyển sang tag theo Git SHA.
6. Lấy Git commit SHA
Tại thư mục project:
git rev-parse --short HEAD
Kết quả ví dụ:
a81c92f
Lấy full commit SHA:
git rev-parse HEAD
Kết quả:
a81c92f4b7c2e4d7c8c3c7e2b9a1f0d8e6c5b4a3
Trong GitHub Actions, chúng ta có thể sử dụng biến:
GITHUB_SHA
Biến này chứa full SHA của commit đang chạy workflow.
Để sử dụng tag ngắn, chúng ta có thể lấy 7 ký tự đầu:
SHORT_SHA="${GITHUB_SHA::7}"
Ví dụ:
GITHUB_SHA:
a81c92f4b7c2e4d7c8c3c7e2b9a1f0d8e6c5b4a3
SHORT_SHA:
a81c92f
7. Kiểm tra workflow hiện tại
Mở reusable workflow:
nano .github/workflows/reusable-ci-cd.yml
Tìm phần build và push image.
Ví dụ cấu hình cũ:
- name: Build Docker image
run: |
docker build \
-t $ECR_REGISTRY/$ECR_REPOSITORY:latest \
$SERVICE_PATH
- name: Push Docker image
run: |
docker push \
$ECR_REGISTRY/$ECR_REPOSITORY:latest
Vấn đề của cấu hình này:
Mọi commit đều sử dụng:
latest
Chúng ta sẽ thay đổi thành:
Git SHA
8. Cập nhật workflow sử dụng Git SHA
Mở file:
nano .github/workflows/reusable-ci-cd.yml
Thêm step tạo image tag:
- name: Generate image tag
id: image-tag
shell: bash
run: |
SHORT_SHA="${GITHUB_SHA::7}"
echo "tag=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
Sau đó build image:
- name: Build Docker image
env:
IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
run: |
docker build \
-t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \
"$SERVICE_PATH"
Push image:
- name: Push Docker image
env:
IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
run: |
docker push \
"$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
Flow mới:
GitHub Commit
↓
GITHUB_SHA
↓
Short SHA
↓
Docker Tag
↓
ECR
Ví dụ:
Commit:
a81c92f4b7c2...
Docker image:
online-boutique-frontend:a81c92f
9. Ví dụ reusable workflow hoàn chỉnh
Ví dụ dưới đây minh họa phần chính của workflow:
name: Reusable CI/CD
on:
workflow_call:
inputs:
service:
required: true
type: string
service_path:
required: true
type: string
ecr_repository:
required: true
type: string
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Generate image tag
id: image-tag
shell: bash
run: |
SHORT_SHA="${GITHUB_SHA::7}"
echo "tag=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ vars.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Build Docker image
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
ECR_REPOSITORY: ${{ inputs.ecr_repository }}
IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
SERVICE_PATH: ${{ inputs.service_path }}
run: |
docker build \
-t "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \
"$SERVICE_PATH"
- name: Push Docker image
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
ECR_REPOSITORY: ${{ inputs.ecr_repository }}
IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
run: |
docker push \
"$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
- name: Display image information
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
ECR_REPOSITORY: ${{ inputs.ecr_repository }}
IMAGE_TAG: ${{ steps.image-tag.outputs.tag }}
run: |
echo "Image:"
echo "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
Lưu ý:
- Giữ nguyên các bước khác trong workflow hiện tại của bạn.
- Chỉ thay đổi phần tạo tag, build image và push image nếu các bước đó đã tồn tại.
- Tên input như
service_pathhoặcecr_repositoryphải khớp với workflow hiện tại của project.
10. Cập nhật workflow gọi reusable workflow
Mở workflow chính:
nano .github/workflows/scale-ci-cd.yml
Ví dụ:
jobs:
frontend:
uses: ./.github/workflows/reusable-ci-cd.yml
with:
service: frontend
service_path: ./src/frontend
ecr_repository: online-boutique-frontend
secrets: inherit
cartservice:
uses: ./.github/workflows/reusable-ci-cd.yml
with:
service: cartservice
service_path: ./src/cartservice
ecr_repository: online-boutique-cartservice
secrets: inherit
Kết quả:
frontend
↓
reusable-ci-cd.yml
↓
online-boutique-frontend:<commit-sha>
cartservice
↓
reusable-ci-cd.yml
↓
online-boutique-cartservice:<commit-sha>
11. Commit thay đổi workflow
Kiểm tra thay đổi:
git diff
Kiểm tra file:
git status
Commit:
git add .github/workflows/
git commit -m "use immutable image tags"
Push lên GitHub:
git push origin main
12. Kiểm tra GitHub Actions
Mở repository trên GitHub.
Đi tới:
Actions
↓
Workflow
↓
Latest run
Kiểm tra các bước:
Checkout source
↓
Generate image tag
↓
Configure AWS credentials
↓
Login to Amazon ECR
↓
Build Docker image
↓
Push Docker image
↓
Display image information
Trong log, bạn sẽ thấy image tương tự:
Image:
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend:a81c92f
13. Kiểm tra image mới trong ECR
Kiểm tra image của frontend:
aws ecr list-images \
--repository-name online-boutique-frontend \
--output table
Kết quả ví dụ:
-----------------------------------------
| ListImages |
+----------------------+----------------+
| imageDigest | imageTag |
+----------------------+----------------+
| sha256:abc... | latest |
| sha256:def... | a81c92f |
+----------------------+----------------+
Lấy image mới nhất:
aws ecr describe-images \
--repository-name online-boutique-frontend \
--query 'sort_by(imageDetails,&imagePushedAt)[-1].{Tags:imageTags,Digest:imageDigest,Pushed:imagePushedAt}' \
--output table
Ví dụ:
----------------------------------------------------------------
| DescribeImages |
+----------------------+----------------------+----------------+
| Tags | Digest | Pushed |
+----------------------+----------------------+----------------+
| a81c92f | sha256:def... | 2026-09-12... |
+----------------------+----------------------+----------------+
14. Kiểm tra image bằng Docker CLI
Lấy địa chỉ image:
aws ecr describe-repositories \
--repository-names online-boutique-frontend \
--query 'repositories[0].repositoryUri' \
--output text
Ví dụ:
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend
Đặt biến:
ECR_REPOSITORY_URI=$(aws ecr describe-repositories \
--repository-names online-boutique-frontend \
--query 'repositories[0].repositoryUri' \
--output text)
Đặt tag:
IMAGE_TAG=$(git rev-parse --short HEAD)
In image đầy đủ:
echo "$ECR_REPOSITORY_URI:$IMAGE_TAG"
Kết quả:
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend:a81c92f
15. Bật tag immutability cho ECR
Cho đến thời điểm này, workflow đã sử dụng Git SHA. Tuy nhiên, ECR vẫn có thể cho phép ghi đè tag nếu tag immutability chưa được bật.
Kiểm tra trạng thái hiện tại:
aws ecr describe-repositories \
--repository-names online-boutique-frontend \
--query 'repositories[0].imageTagMutability' \
--output text
Nếu kết quả là:
MUTABLE
thì tag vẫn có thể bị ghi đè.
Chuyển repository sang immutable:
aws ecr put-image-tag-mutability \
--repository-name online-boutique-frontend \
--image-tag-mutability IMMUTABLE
Kiểm tra lại:
aws ecr describe-repositories \
--repository-names online-boutique-frontend \
--query 'repositories[0].imageTagMutability' \
--output text
Kết quả mong đợi:
IMMUTABLE
16. Bật immutability cho các repository còn lại
Chạy lần lượt:
for repo in \
online-boutique-frontend \
online-boutique-cartservice \
online-boutique-productcatalogservice \
online-boutique-checkoutservice \
online-boutique-paymentservice \
online-boutique-shippingservice
do
echo "Updating $repo"
aws ecr put-image-tag-mutability \
--repository-name "$repo" \
--image-tag-mutability IMMUTABLE
done
Kiểm tra:
for repo in \
online-boutique-frontend \
online-boutique-cartservice \
online-boutique-productcatalogservice \
online-boutique-checkoutservice \
online-boutique-paymentservice \
online-boutique-shippingservice
do
echo "===== $repo ====="
aws ecr describe-repositories \
--repository-names "$repo" \
--query 'repositories[0].{Repository:repositoryName,Mutability:imageTagMutability}' \
--output table
done
Kết quả mong đợi:
IMMUTABLE
17. Test ghi đè image tag
Đây là bước quan trọng nhất của lab.
Mục tiêu:
Push image với tag a81c92f lần đầu
↓
Push image khác với cùng tag a81c92f
↓
ECR từ chối
Đăng nhập ECR:
aws ecr get-login-password --region ap-northeast-1 | \
docker login \
--username AWS \
--password-stdin \
"$(aws ecr describe-repositories \
--repository-names online-boutique-frontend \
--query 'repositories[0].repositoryUri' \
--output text | cut -d/ -f1)"
Lấy repository URI:
ECR_REPOSITORY_URI=$(aws ecr describe-repositories \
--repository-names online-boutique-frontend \
--query 'repositories[0].repositoryUri' \
--output text)
Tạo một image thử nghiệm:
docker pull nginx:alpine
Tag image:
docker tag nginx:alpine "$ECR_REPOSITORY_URI:test-immutable"
Push lần đầu:
docker push "$ECR_REPOSITORY_URI:test-immutable"
Lần đầu push sẽ thành công nếu tag chưa tồn tại.
Push lại cùng tag:
docker push "$ECR_REPOSITORY_URI:test-immutable"
Kết quả mong đợi:
denied:
Image tag 'test-immutable' already exists in the repository
and cannot be overwritten because the repository is immutable.
Tên lỗi thực tế có thể khác một chút, nhưng kết quả phải thể hiện rằng ECR từ chối ghi đè tag.
18. Xóa image thử nghiệm
Liệt kê image thử nghiệm:
aws ecr list-images \
--repository-name online-boutique-frontend \
--filter tagStatus=TAGGED \
--query 'imageIds[?imageTag==`test-immutable`]' \
--output table
Xóa image:
aws ecr batch-delete-image \
--repository-name online-boutique-frontend \
--image-ids imageTag=test-immutable
Kiểm tra lại:
aws ecr list-images \
--repository-name online-boutique-frontend \
--filter tagStatus=TAGGED \
--query 'imageIds[?imageTag==`test-immutable`]' \
--output table
19. Kiểm tra tất cả image tag theo Git SHA
Kiểm tra image của nhiều service:
for repo in \
online-boutique-frontend \
online-boutique-cartservice \
online-boutique-productcatalogservice \
online-boutique-checkoutservice
do
echo "======================================"
echo "$repo"
echo "======================================"
aws ecr describe-images \
--repository-name "$repo" \
--query 'sort_by(imageDetails,&imagePushedAt)[-1].{Tags:imageTags,Digest:imageDigest,Pushed:imagePushedAt}' \
--output table
done
Kết quả ví dụ:
online-boutique-frontend
Tag: a81c92f
online-boutique-cartservice
Tag: a81c92f
online-boutique-productcatalogservice
Tag: a81c92f
online-boutique-checkoutservice
Tag: a81c92f
Các service có thể sử dụng cùng một commit SHA nếu chúng được build từ cùng một commit của repository.
20. Kiểm tra quan hệ giữa Git commit và Docker image
Lấy commit hiện tại:
git log -1 --oneline
Ví dụ:
a81c92f use immutable image tags
Lấy image tag:
git rev-parse --short HEAD
Kết quả:
a81c92f
Như vậy chúng ta có quan hệ:
Git commit:
a81c92f
↓
Docker image:
online-boutique-frontend:a81c92f
Khi cần debug, chúng ta có thể tìm ngược:
Docker image
↓
Image tag
↓
Git commit
↓
Source code
21. Không sử dụng latest trong deployment
Từ thời điểm này, không nên sử dụng:
online-boutique-frontend:latest
Thay vào đó, sử dụng:
online-boutique-frontend:a81c92f
Ví dụ trong file deployment:
services:
frontend:
image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend:a81c92f
Hoặc sử dụng biến môi trường:
services:
frontend:
image: ${FRONTEND_IMAGE}
File .env:
FRONTEND_IMAGE=123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend:a81c92f
Lúc này deployment luôn sử dụng một image cụ thể.
22. So sánh trước và sau
Trước Chapter 4D
Build
↓
Docker Image
↓
ECR
↓
latest
Vấn đề:
latest có thể bị ghi đè
Không biết chính xác version
Khó rollback
Khó audit
Sau Chapter 4D
Git Commit
↓
Git SHA
↓
Docker Image
↓
ECR Immutable Tag
Ví dụ:
frontend:a81c92f
frontend:92ab821
frontend:f72a921
Ưu điểm:
✅ Mỗi commit có tag riêng
✅ Tag không bị ghi đè
✅ Dễ truy vết source code
✅ Dễ rollback
✅ Có thể promote cùng một image
✅ Chuẩn bị cho production release
23. Kiểm tra kết quả cuối cùng
Chạy các lệnh sau:
git status
git log -1 --oneline
aws ecr describe-repositories \
--repository-names online-boutique-frontend \
--query 'repositories[0].imageTagMutability' \
--output text
aws ecr list-images \
--repository-name online-boutique-frontend \
--output table
Kết quả cần đạt:
Git commit:
a81c92f
ECR mutability:
IMMUTABLE
Image tag:
a81c92f
24. Architecture sau Chapter 4D
Developer
│
▼
GitHub
│
▼
GitHub Actions
│
▼
Reusable Workflow
│
▼
Docker Build
│
▼
Git Commit SHA
│
▼
┌──────────────────┐
│ Amazon ECR │
│ │
│ Immutable Tags │
└──────────────────┘
│
▼
Amazon Inspector
│
▼
Security Findings
Ví dụ artifact:
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/online-boutique-frontend:a81c92f
25. Kết luận
Trong Chapter 4C, chúng ta biết image có vulnerability nào.
Trong Chapter 4D, chúng ta đảm bảo image có định danh rõ ràng và không bị ghi đè.
Chapter 4C
Image Security
↓
Biết image có an toàn hay không
Chapter 4D
Immutable Artifact
↓
Biết chính xác image nào đang được sử dụng
Pipeline hiện tại:
GitHub
↓
GitHub Actions
↓
Reusable Workflow
↓
Docker Build
↓
Git SHA Tag
↓
Amazon ECR
↓
Immutable Image
↓
Amazon Inspector
Vấn đề tiếp theo:
Nếu Amazon Inspector phát hiện
CRITICALvulnerability, pipeline có nên tiếp tục hay phải dừng lại?
Đó sẽ là nội dung của:
Chapter 4E — Security Gate
All rights reserved