0

🚀 Chapter 4 — Production: Phần F — Artifact Promotion với Amazon ECR

Chapter 4E, chúng ta đã hoàn thành Security Gate:

GitHub
   ↓
GitHub Actions
   ↓
Docker Build
   ↓
Amazon ECR
   ↓
Vulnerability Scan
   ↓
Security Gate
   ├── FAIL → Dừng pipeline
   └── PASS → Cho phép tiếp tục

Tuy nhiên, sau khi image vượt qua Security Gate, chúng ta vẫn cần giải quyết một vấn đề quan trọng:

Làm thế nào để đưa chính xác image đã được kiểm tra từ Dev lên Staging rồi Production?

Một cách làm không tốt là build lại image cho từng môi trường:

Build Dev
   ↓
image-dev

Build Staging
   ↓
image-staging

Build Production
   ↓
image-production

Cách này có thể tạo ra những image khác nhau giữa các môi trường.

Trong phần này, chúng ta sẽ sử dụng mô hình:

Build Once
    ↓
Security Scan
    ↓
Promote Same Artifact
    ↓
Dev
    ↓
Staging
    ↓
Production

1. Mục tiêu

Sau khi hoàn thành phần này, chúng ta sẽ có:

✅ Build image một lần

✅ Image được tag bằng Git SHA

✅ Image được push vào Amazon ECR

✅ Image vượt qua Security Gate

✅ Promote cùng một image qua Dev → Staging → Production

✅ Không build lại image ở mỗi môi trường

✅ Có thể kiểm tra image digest giữa các môi trường

✅ Có bước approval trước Production

✅ Có thể rollback về image đã được sử dụng trước đó

Kiến trúc cuối cùng:

Developer
    ↓
git push
    ↓
GitHub Actions
    ↓
Docker Build
    ↓
Push Image vào ECR
    ↓
Security Gate
    ↓
Promote to Dev
    ↓
Promote to Staging
    ↓
[Approval]
    ↓
Promote to Production

2. Kiểm tra môi trường

Trên máy local, di chuyển vào repository:

cd online-boutique-cicd

Kiểm tra Git:

git status

Kiểm tra workflow:

find .github/workflows -type f

Ví dụ:

.github/workflows/
├── reusable-ci-cd.yml
├── scale-ci-cd.yml
└── security-gate-test.yml

Kiểm tra script Security Gate:

ls -l scripts/ecr-security-gate.sh

Kiểm tra AWS CLI:

aws --version

Kiểm tra AWS account:

aws sts get-caller-identity

Kiểm tra Region:

aws configure get region

Nếu chưa có Region, sử dụng Tokyo:

aws configure set region ap-northeast-1

3. Kiểm tra image đã vượt qua Security Gate

Ở phần trước, chúng ta đã sử dụng image tag theo Git SHA:

a81c92f

Ví dụ repository:

online-boutique-frontend

Đặt biến:

export AWS_REGION=ap-northeast-1
export ECR_REPOSITORY=online-boutique-frontend
export IMAGE_TAG=a81c92f

Thay a81c92f bằng tag thực tế trong ECR.

Kiểm tra image:

aws ecr describe-images \
  --repository-name "$ECR_REPOSITORY" \
  --image-ids imageTag="$IMAGE_TAG" \
  --query 'imageDetails[0].{Tags:imageTags,Digest:imageDigest,Pushed:imagePushedAt}' \
  --output table

Ví dụ:

----------------------------------------------------
|                 DescribeImages                   |
+----------+---------------------------------------+
| Digest   | sha256:abc123...                       |
| Pushed   | 2026-09-12T10:20:00+00:00              |
| Tags     | a81c92f                                |
+----------+---------------------------------------+

Lấy image digest:

export IMAGE_DIGEST=$(aws ecr describe-images \
  --repository-name "$ECR_REPOSITORY" \
  --image-ids imageTag="$IMAGE_TAG" \
  --query 'imageDetails[0].imageDigest' \
  --output text)

Kiểm tra:

echo "$IMAGE_DIGEST"

Ví dụ:

sha256:abc123456789...

Từ thời điểm này, chúng ta sẽ dùng image digest để xác định artifact. Tag chỉ là tên tham chiếu, còn digest xác định chính xác nội dung image.


4. Thiết kế tag cho từng môi trường

Chúng ta sẽ giữ lại tag Git SHA gốc:

a81c92f

Sau đó thêm các tag môi trường:

a81c92f
dev
staging
production

Ví dụ:

ECR Repository
└── online-boutique-frontend
    ├── a81c92f
    ├── dev
    ├── staging
    └── production

Các tag này cùng trỏ tới một image digest:

a81c92f ─────┐
dev ─────────┤
staging ─────┼──→ sha256:abc123...
production ──┘

Khi promote image:

a81c92f
   ↓
dev
   ↓
staging
   ↓
production

Chúng ta chỉ thay đổi tag tham chiếu, không build lại image.


5. Tạo script promote image

Tạo thư mục scripts nếu chưa có:

mkdir -p scripts

Tạo file:

nano scripts/promote-ecr-image.sh

Thêm nội dung:

#!/usr/bin/env bash

set -euo pipefail

REPOSITORY_NAME="${1:?Repository name is required}"
SOURCE_TAG="${2:?Source tag is required}"
TARGET_TAG="${3:?Target tag is required}"
AWS_REGION="${AWS_REGION:-ap-northeast-1}"

echo "========================================"
echo "ECR Artifact Promotion"
echo "========================================"
echo "Repository: $REPOSITORY_NAME"
echo "Source tag: $SOURCE_TAG"
echo "Target tag: $TARGET_TAG"
echo "Region:     $AWS_REGION"
echo "========================================"

SOURCE_DIGEST="$(
  aws ecr describe-images \
    --repository-name "$REPOSITORY_NAME" \
    --image-ids imageTag="$SOURCE_TAG" \
    --region "$AWS_REGION" \
    --query 'imageDetails[0].imageDigest' \
    --output text
)"

if [[ -z "$SOURCE_DIGEST" || "$SOURCE_DIGEST" == "None" ]]; then
  echo "Source image was not found."
  exit 1
fi

echo "Source digest:"
echo "$SOURCE_DIGEST"

MANIFEST="$(
  aws ecr batch-get-image \
    --repository-name "$REPOSITORY_NAME" \
    --image-ids imageTag="$SOURCE_TAG" \
    --region "$AWS_REGION" \
    --query 'images[0].imageManifest' \
    --output text
)"

if [[ -z "$MANIFEST" || "$MANIFEST" == "None" ]]; then
  echo "Image manifest was not found."
  exit 1
fi

echo "Applying target tag: $TARGET_TAG"

aws ecr put-image \
  --repository-name "$REPOSITORY_NAME" \
  --image-tag "$TARGET_TAG" \
  --image-manifest "$MANIFEST" \
  --region "$AWS_REGION" \
  > /dev/null

TARGET_DIGEST="$(
  aws ecr describe-images \
    --repository-name "$REPOSITORY_NAME" \
    --image-ids imageTag="$TARGET_TAG" \
    --region "$AWS_REGION" \
    --query 'imageDetails[0].imageDigest' \
    --output text
)"

echo "Target digest:"
echo "$TARGET_DIGEST"

if [[ "$SOURCE_DIGEST" != "$TARGET_DIGEST" ]]; then
  echo "Promotion failed: digest mismatch."
  exit 1
fi

echo "========================================"
echo "Promotion successful"
echo "========================================"
echo "Source tag: $SOURCE_TAG"
echo "Target tag: $TARGET_TAG"
echo "Digest:     $SOURCE_DIGEST"

Lưu file:

CTRL + O
ENTER
CTRL + X

Cấp quyền thực thi:

chmod +x scripts/promote-ecr-image.sh

6. Kiểm tra script promote

Sử dụng image đã tồn tại trong ECR:

./scripts/promote-ecr-image.sh \
  online-boutique-frontend \
  a81c92f \
  dev

Thay a81c92f bằng tag thực tế.

Kết quả mong đợi:

========================================
ECR Artifact Promotion
========================================
Repository: online-boutique-frontend
Source tag: a81c92f
Target tag: dev
Region:     ap-northeast-1
========================================

Source digest:
sha256:abc123456789...

Applying target tag: dev

Target digest:
sha256:abc123456789...

========================================
Promotion successful
========================================
Source tag: a81c92f
Target tag: dev
Digest:     sha256:abc123456789...

Điểm quan trọng:

Source digest == Target digest

Điều này chứng minh rằng dev đang trỏ tới đúng image đã được build và scan.


7. Kiểm tra tag Dev

Chạy:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=dev \
  --query 'imageDetails[0].{Tags:imageTags,Digest:imageDigest}' \
  --output table

Kiểm tra digest của Git SHA:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=a81c92f \
  --query 'imageDetails[0].imageDigest' \
  --output text

Kiểm tra digest của dev:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=dev \
  --query 'imageDetails[0].imageDigest' \
  --output text

So sánh:

SOURCE_DIGEST=$(aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=a81c92f \
  --query 'imageDetails[0].imageDigest' \
  --output text)

DEV_DIGEST=$(aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=dev \
  --query 'imageDetails[0].imageDigest' \
  --output text)

if [[ "$SOURCE_DIGEST" == "$DEV_DIGEST" ]]; then
  echo "Same artifact"
else
  echo "Different artifact"
fi

Kết quả:

Same artifact

8. Promote từ Dev sang Staging

Sau khi image được kiểm tra ở Dev, promote image sang Staging:

./scripts/promote-ecr-image.sh \
  online-boutique-frontend \
  dev \
  staging

Kiểm tra:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=staging \
  --query 'imageDetails[0].{Tags:imageTags,Digest:imageDigest}' \
  --output table

Kiểm tra cả ba tag:

for tag in a81c92f dev staging
do
  echo "===== $tag ====="

  aws ecr describe-images \
    --repository-name online-boutique-frontend \
    --image-ids imageTag="$tag" \
    --query 'imageDetails[0].{Tag:imageTags,Digest:imageDigest}' \
    --output table
done

Kết quả mong đợi:

a81c92f → sha256:abc123...
dev     → sha256:abc123...
staging → sha256:abc123...

9. Promote từ Staging sang Production

Sau khi kiểm tra Staging thành công, promote image sang Production:

./scripts/promote-ecr-image.sh \
  online-boutique-frontend \
  staging \
  production

Kiểm tra:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=production \
  --query 'imageDetails[0].{Tags:imageTags,Digest:imageDigest}' \
  --output table

Kiểm tra toàn bộ artifact:

for tag in a81c92f dev staging production
do
  echo "===== $tag ====="

  aws ecr describe-images \
    --repository-name online-boutique-frontend \
    --image-ids imageTag="$tag" \
    --query 'imageDetails[0].imageDigest' \
    --output text
done

Kết quả mong đợi:

sha256:abc123...
sha256:abc123...
sha256:abc123...
sha256:abc123...

Như vậy:

Git SHA
   ↓
Dev
   ↓
Staging
   ↓
Production

đều sử dụng cùng một image digest.


10. Tạo workflow promotion trên GitHub Actions

Tạo file:

nano .github/workflows/artifact-promotion.yml

Thêm nội dung:

name: Artifact Promotion

on:
  workflow_dispatch:
    inputs:
      service:
        description: "ECR repository name"
        required: true
        type: string
      image_tag:
        description: "Git SHA image tag"
        required: true
        type: string
      target_environment:
        description: "Target environment"
        required: true
        type: choice
        options:
          - dev
          - staging
          - production

permissions:
  contents: read

jobs:
  promote:
    name: Promote artifact
    runs-on: ubuntu-latest

    environment: ${{ inputs.target_environment }}

    steps:
      - name: Checkout source
        uses: actions/checkout@v4

      - 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: Promote image
        env:
          AWS_REGION: ${{ vars.AWS_REGION }}
          ECR_REPOSITORY: ${{ inputs.service }}
          SOURCE_TAG: ${{ inputs.image_tag }}
          TARGET_TAG: ${{ inputs.target_environment }}
        run: |
          chmod +x scripts/promote-ecr-image.sh

          ./scripts/promote-ecr-image.sh \
            "$ECR_REPOSITORY" \
            "$SOURCE_TAG" \
            "$TARGET_TAG"

Workflow này cho phép chạy thủ công từ GitHub Actions.

Ví dụ:

Service:
online-boutique-frontend

Image tag:
a81c92f

Target environment:
dev

GitHub Actions sẽ thực hiện:

a81c92f
   ↓
dev

11. Thiết lập GitHub Environment

Vào GitHub repository:

Settings
   ↓
Environments

Tạo ba Environment:

dev
staging
production

Cấu hình:

dev
└── Không cần approval

staging
└── Có thể yêu cầu approval nếu muốn

production
└── Bắt buộc approval

Đối với production:

Settings
   ↓
Environments
   ↓
production
   ↓
Required reviewers

Thêm GitHub account được phép approve.

Khi workflow target production, GitHub Actions sẽ dừng ở bước Environment:

Workflow
   ↓
Production Environment
   ↓
[Waiting for approval]
   ↓
Approve
   ↓
Promote image

12. Thiết lập GitHub Variables và Secrets

Vào:

Settings
   ↓
Secrets and variables
   ↓
Actions

Tạo Repository Variable:

Name:
AWS_REGION

Value:
ap-northeast-1

Tạo hoặc kiểm tra các Secrets:

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY

Nếu các giá trị này đã được tạo ở Chapter 4D hoặc Chapter 4E thì không cần tạo lại.


13. Chạy promotion bằng GitHub Actions

Commit workflow:

git add \
  scripts/promote-ecr-image.sh \
  .github/workflows/artifact-promotion.yml

Commit:

git commit -m "add ECR artifact promotion"

Push:

git push origin main

Mở GitHub:

Actions
   ↓
Artifact Promotion
   ↓
Run workflow

Nhập:

Service:
online-boutique-frontend

Image tag:
a81c92f

Target environment:
dev

Chọn:

Run workflow

Kết quả:

Checkout source
      ↓
Configure AWS credentials
      ↓
Promote image
      ↓
Promotion successful

14. Promote lên Staging

Chạy lại workflow:

Actions
   ↓
Artifact Promotion
   ↓
Run workflow

Nhập:

Service:
online-boutique-frontend

Image tag:
a81c92f

Target environment:
staging

Workflow sẽ thực hiện:

a81c92f
   ↓
staging

Lưu ý: trong ví dụ này, staging được tạo trực tiếp từ Git SHA gốc. Điều này giúp tránh phụ thuộc vào tag dev.


15. Promote lên Production

Chạy workflow lần nữa:

Actions
   ↓
Artifact Promotion
   ↓
Run workflow

Nhập:

Service:
online-boutique-frontend

Image tag:
a81c92f

Target environment:
production

Flow:

Run workflow
    ↓
GitHub Environment: production
    ↓
[Approval required]
    ↓
Approve
    ↓
Promote image
    ↓
production tag created

Nếu chưa approve:

Waiting

Nếu approve thành công:

Promotion successful

16. Áp dụng cho nhiều service

Online Boutique có nhiều service, vì vậy chúng ta có thể áp dụng cùng một script cho từng ECR repository.

Ví dụ:

online-boutique-frontend
online-boutique-cartservice
online-boutique-paymentservice
online-boutique-checkoutservice
online-boutique-shippingservice
online-boutique-productcatalogservice

Promote cartservice:

./scripts/promote-ecr-image.sh \
  online-boutique-cartservice \
  a81c92f \
  dev

Promote paymentservice:

./scripts/promote-ecr-image.sh \
  online-boutique-paymentservice \
  b72d81a \
  dev

Promote checkoutservice:

./scripts/promote-ecr-image.sh \
  online-boutique-checkoutservice \
  c63e91b \
  staging

Mỗi service có image tag riêng:

frontend:
  a81c92f

cartservice:
  b72d81a

paymentservice:
  c63e91b

Nhưng tất cả đều dùng chung logic:

scripts/promote-ecr-image.sh

17. Kiểm tra artifact bằng digest

Tạo file:

nano scripts/verify-artifact-digest.sh

Thêm:

#!/usr/bin/env bash

set -euo pipefail

REPOSITORY_NAME="${1:?Repository name is required}"
SOURCE_TAG="${2:?Source tag is required}"
TARGET_TAG="${3:?Target tag is required}"
AWS_REGION="${AWS_REGION:-ap-northeast-1}"

SOURCE_DIGEST="$(
  aws ecr describe-images \
    --repository-name "$REPOSITORY_NAME" \
    --image-ids imageTag="$SOURCE_TAG" \
    --region "$AWS_REGION" \
    --query 'imageDetails[0].imageDigest' \
    --output text
)"

TARGET_DIGEST="$(
  aws ecr describe-images \
    --repository-name "$REPOSITORY_NAME" \
    --image-ids imageTag="$TARGET_TAG" \
    --region "$AWS_REGION" \
    --query 'imageDetails[0].imageDigest' \
    --output text
)"

echo "Source tag: $SOURCE_TAG"
echo "Source digest: $SOURCE_DIGEST"

echo "Target tag: $TARGET_TAG"
echo "Target digest: $TARGET_DIGEST"

if [[ "$SOURCE_DIGEST" != "$TARGET_DIGEST" ]]; then
  echo "Artifact verification failed."
  exit 1
fi

echo "Artifact verification passed."

Cấp quyền:

chmod +x scripts/verify-artifact-digest.sh

Kiểm tra:

./scripts/verify-artifact-digest.sh \
  online-boutique-frontend \
  a81c92f \
  production

Kết quả:

Source tag: a81c92f
Source digest: sha256:abc123...

Target tag: production
Target digest: sha256:abc123...

Artifact verification passed.

18. Thêm bước verify vào workflow

Mở workflow:

nano .github/workflows/artifact-promotion.yml

Sau bước Promote image, thêm:

      - name: Verify promoted artifact
        env:
          AWS_REGION: ${{ vars.AWS_REGION }}
          ECR_REPOSITORY: ${{ inputs.service }}
          SOURCE_TAG: ${{ inputs.image_tag }}
          TARGET_TAG: ${{ inputs.target_environment }}
        run: |
          chmod +x scripts/verify-artifact-digest.sh

          ./scripts/verify-artifact-digest.sh \
            "$ECR_REPOSITORY" \
            "$SOURCE_TAG" \
            "$TARGET_TAG"

Flow hoàn chỉnh:

Checkout source
      ↓
Configure AWS credentials
      ↓
Promote image
      ↓
Verify source digest
      ↓
Verify target digest
      ↓
Compare digest
      ↓
PASS

Nếu digest khác nhau:

Artifact verification failed

Workflow sẽ fail.


19. Kiểm tra bằng AWS CLI

Liệt kê các tag của image:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --query 'imageDetails[].{Tags:imageTags,Digest:imageDigest}' \
  --output table

Ví dụ:

--------------------------------------------------
|                 DescribeImages                 |
+-------------------+----------------------------+
| Tags              | Digest                     |
+-------------------+----------------------------+
| a81c92f, dev,      | sha256:abc123...           |
| staging,          |                            |
| production        |                            |
+-------------------+----------------------------+

Kết quả cho thấy các tag môi trường cùng trỏ tới một digest.


20. Rollback về artifact trước đó

Giả sử Production hiện tại đang sử dụng:

production → sha256:new123...

Image cũ vẫn còn trong ECR:

old-release

Kiểm tra image cũ:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=old-release \
  --query 'imageDetails[0].{Tags:imageTags,Digest:imageDigest}' \
  --output table

Rollback bằng cách promote image cũ thành production:

./scripts/promote-ecr-image.sh \
  online-boutique-frontend \
  old-release \
  production

Kiểm tra:

aws ecr describe-images \
  --repository-name online-boutique-frontend \
  --image-ids imageTag=production \
  --query 'imageDetails[0].imageDigest' \
  --output text

Flow rollback:

Previous Artifact
       ↓
   ECR Image
       ↓
Promote to production
       ↓
  Production

Không cần build lại image cũ.


21. Lưu ý về tag môi trường

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

dev
staging
production

để dễ quan sát quá trình promotion.

Tuy nhiên, trong production thực tế, nên ưu tiên:

Git SHA

hoặc:

Image Digest

làm định danh chính.

Ví dụ:

online-boutique-frontend@sha256:abc123...

Các tag như dev, staging, production chỉ nên được xem là các pointer trỏ tới artifact cụ thể.

Không nên dùng:

latest

làm định danh duy nhất cho production vì tag này có thể thay đổi.


22. Kết quả cuối cùng

Sau Chapter 4F, pipeline sẽ có dạng:

Developer
    ↓
GitHub
    ↓
GitHub Actions
    ↓
Reusable Workflow
    ↓
Test
    ↓
Docker Build
    ↓
Git SHA Tag
    ↓
Amazon ECR
    ↓
Security Gate
    ├── FAIL → Stop
    └── PASS
          ↓
        Dev
          ↓
      Staging
          ↓
      Approval
          ↓
     Production

Artifact flow:

frontend:a81c92f
        │
        ├──→ frontend:dev
        │
        ├──→ frontend:staging
        │
        └──→ frontend:production

Tất cả tag đều trỏ tới cùng một digest:

a81c92f
dev
staging
production
   ↓
sha256:abc123...

23. Kết luận

Trong Chapter 4E, chúng ta trả lời câu hỏi:

Image có đủ an toàn để tiếp tục không?

Trong Chapter 4F, chúng ta trả lời câu hỏi:

Làm thế nào để đưa đúng image đã được kiểm tra qua nhiều môi trường?

Nguyên tắc chính:

Build Once
    ↓
Scan Once
    ↓
Promote Many Times

Kết quả:

Không build lại image
Không tạo artifact khác nhau giữa các môi trường
Có thể kiểm tra bằng digest
Có approval trước Production
Có thể rollback nhanh

Đây là nền tảng quan trọng trước khi chuyển sang Chapter 5 — Kubernetes + GitOps với Amazon EKS và ArgoCD.


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í