0

🚀 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 tagGit 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_path hoặc ecr_repository phả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 CRITICAL vulnerability, 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

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í