CI/CD Pipeline Thực Chiến: AWS CodePipeline và GitHub Actions
Danh Mục Bài Viết
- 1. Tại sao CI/CD lại quan trọng hơn bạn nghĩ?
- 2. Kiến trúc tổng quan của pipeline
- 3. Thiết lập GitHub Actions: Gate trước khi deploy
- 4. Thiết lập AWS CodePipeline
- 5. buildspec.yml: Trái tim của Build Stage
- 6. Lỗi thường gặp và cách debug
- 7. Monitoring và Notification
- 8. FAQ
- 9. Một pipeline tốt không phải thứ bạn nghĩ đến mỗi ngày
Ba tháng trước, team mình deploy bằng cách SSH vào server, git pull, rồi restart service. Nghe đơn giản, nhưng mỗi lần release là một buổi sáng căng thẳng — lỡ quên một bước, service down, khách hàng gọi điện. Đến khi set up xong CI/CD với AWS CodePipeline + GitHub Actions, cái cảm giác push code rồi đi uống cà phê chờ deploy tự chạy... thực sự là một bước ngoặt.
Tại sao CI/CD lại quan trọng hơn bạn nghĩ?
Nhiều team vẫn đang deploy thủ công và nghĩ rằng "dự án nhỏ thì không cần". Nhưng vấn đề không phải ở quy mô — mà ở rủi ro. Deploy thủ công nghĩa là: con người thực hiện, con người có thể sai. Không có ai kiểm tra code trước khi lên production, không có rollback tự động, không có audit log ai deploy lúc mấy giờ.
CI/CD giải quyết tất cả: code được test tự động, build artifact được kiểm soát version, deploy có thể rollback trong vài giây. AWS CodePipeline xử lý orchestration (điều phối luồng chạy), còn GitHub Actions lo phần test và build ngay trên repo — hai công cụ này kết hợp tốt hơn nhiều so với dùng riêng lẻ.
Kiến trúc tổng quan của pipeline
Trước khi đụng tay vào code, hãy hiểu rõ luồng chạy:
- Source Stage: GitHub repo kết nối với CodePipeline qua CodeStar Connection. Mỗi khi merge vào nhánh
main, pipeline tự động trigger. - Build Stage: AWS CodeBuild chạy — ở đây mình thường để unit test + build Docker image + push lên ECR.
- Test Stage (optional nhưng nên có): integration test chạy trên staging environment.
- Deploy Stage: CodeDeploy hoặc ECS deployment tùy stack của bạn.
GitHub Actions trong sơ đồ này làm gì? Mình dùng nó để chạy pre-flight checks — lint, type check, unit test — ngay trên PR. Chỉ khi PR pass hết và được merge mới kích hoạt CodePipeline. Tức là CodePipeline chỉ nhận code đã "sạch" từ trước.
Thiết lập GitHub Actions: Gate trước khi deploy
Tạo file .github/workflows/ci.yml:
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Build
run: npm run build
- name: Upload coverage
uses: codecov/codecov-action@v4
if: always()Điểm quan trọng: dùng npm ci thay vì npm install để đảm bảo install đúng version theo package-lock.json. Cache node_modules qua cache: 'npm' giúp workflow chạy nhanh hơn đáng kể từ lần thứ 2 trở đi.
Nếu test mất hơn 5 phút, hãy tách ra chạy parallel bằng
strategy.matrix. Job 15 phút = developer chờ = frustration.
Thiết lập AWS CodePipeline
Có hai cách: console hoặc IaC (mình dùng CloudFormation/Terraform). Ở đây mình giải thích logic, còn cách implement thì tuỳ stack:
Bước 1 — Tạo CodeStar Connection với GitHub:
Vào AWS Console → Developer Tools → Settings → Connections → Create connection. Chọn GitHub, authorize, lấy Connection ARN. Đây là cầu nối an toàn giữa AWS và GitHub repo — không cần lưu GitHub token thủ công.
Bước 2 — Tạo S3 bucket cho artifact:
aws s3 mb s3://my-pipeline-artifacts-{account-id} --region ap-southeast-1
aws s3api put-bucket-versioning --bucket my-pipeline-artifacts-{account-id} --versioning-configuration Status=EnabledBước 3 — Tạo IAM role cho pipeline:
CodePipeline cần role với quyền: s3:* trên artifact bucket, codebuild:*, codestar-connections:UseConnection, ecs:* (nếu deploy lên ECS). Nguyên tắc least privilege — chỉ grant những gì cần thiết.
Bước 4 — Cấu hình pipeline stages trong CloudFormation:
Resources:
MyPipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: my-app-pipeline
RoleArn: !GetAtt PipelineRole.Arn
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: GitHub
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeStarSourceConnection
Version: '1'
Configuration:
ConnectionArn: !Ref GitHubConnection
FullRepositoryId: "your-org/your-repo"
BranchName: main
DetectChanges: true
OutputArtifacts:
- Name: SourceCode
- Name: Build
Actions:
- Name: CodeBuild
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: '1'
Configuration:
ProjectName: !Ref BuildProject
InputArtifacts:
- Name: SourceCode
OutputArtifacts:
- Name: BuildOutput
buildspec.yml: Trái tim của Build Stage
File này nằm ở root repo, CodeBuild đọc và thực thi:
version: 0.2
env:
variables:
IMAGE_REPO_NAME: "my-app"
parameter-store:
DB_PASSWORD: "/my-app/prod/db-password"
phases:
install:
runtime-versions:
nodejs: 20
commands:
- npm ci
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION |
docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
- IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
build:
commands:
- echo Build started on `date`
- npm run test:unit
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
post_build:
commands:
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- printf '[{"name":"my-app","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imagedefinitions.json
artifacts:
files:
- imagedefinitions.jsonLưu ý parameter-store: lấy secret từ AWS Systems Manager Parameter Store thay vì hardcode. Đây là practice quan trọng — không bao giờ commit secret vào repo.
Dùng
$CODEBUILD_RESOLVED_SOURCE_VERSION(commit SHA) làm Docker image tag. Rollback về commit cũ chỉ cần đổi tag — truy vết được ngay version nào đang chạy trên production.
Lỗi thường gặp và cách debug
1. Pipeline trigger nhưng không chạy: Kiểm tra CodeStar Connection status — phải là "Available". Sau khi tạo connection, cần vào console approve một lần đầu bằng tay. Nhiều người tạo xong tưởng là xong nhưng quên bước này.
2. CodeBuild fail với lỗi permissions: Thường do IAM role thiếu quyền ECR hoặc S3. Xem CloudTrail logs → filter by errorCode: AccessDenied để tìm action cụ thể bị chặn, sau đó thêm vào policy.
3. Docker build timeout: CodeBuild mặc định timeout 60 phút, nhưng nếu build layer Docker không được cache, mỗi lần build lại từ đầu rất chậm. Fix: enable privilegedMode: true trong CodeBuild project và dùng --cache-from trong Dockerfile.
4. ECS deployment stuck: Service stuck ở "IN_PROGRESS" thường do container mới khởi động rồi crash (health check fail). Xem CloudWatch Logs của task — lỗi thường là missing environment variable hoặc DB connection refused.
5. GitHub Actions và CodePipeline chạy đôi khi không đồng bộ: GitHub Actions chạy trên mọi push, còn CodePipeline trigger khi merge vào main. Nếu GitHub Actions fail, CodePipeline vẫn chạy được. Để "lock" lại, dùng branch protection rule: require status checks từ GitHub Actions pass trước khi merge.
Monitoring và Notification
Pipeline chạy tự động nhưng bạn vẫn cần biết khi nào nó fail. Cách đơn giản nhất: EventBridge rule → SNS → Slack/Email.
aws events put-rule --name "CodePipelineFailure" --event-pattern '{
"source": ["aws.codepipeline"],
"detail-type": ["CodePipeline Pipeline Execution State Change"],
"detail": {
"state": ["FAILED"]
}
}'Kết hợp với Lambda để format message trước khi gửi Slack — thay vì nhận cái JSON khó đọc, bạn nhận được: "Pipeline my-app-pipeline FAILED at stage Build - Commit abc1234 by john.doe".
FAQ
Q: Chi phí AWS CodePipeline có đắt không?A: Mỗi pipeline active (có ít nhất 1 lần trigger trong tháng) tốn $1/tháng. CodeBuild tính theo phút compute — với build 5 phút và 20 lần deploy/tháng, chi phí khoảng $0.5-1. Tổng cộng dưới $5/tháng cho một team nhỏ. Nếu dùng Free Tier, CodePipeline có 1 pipeline miễn phí.
Q: Có nên dùng GitHub Actions thay hoàn toàn cho CodePipeline không?A: Được, nhưng có đánh đổi. GitHub Actions thuần túy đơn giản hơn để set up, nhưng deploy lên AWS infrastructure (ECS, Lambda) cần nhiều IAM setup phức tạp hơn so với dùng CodePipeline native. Với team đang dùng AWS ecosystem, kết hợp cả hai như bài này describe là lựa chọn cân bằng tốt nhất.
Q: Làm sao rollback khi deployment fail?A: Nếu dùng ECS, CodeDeploy có sẵn tính năng automatic rollback — set
autoRollbackConfigurationlà xong. Với Lambda, dùngaws lambda update-aliastrỏ về version trước. Quan trọng nhất: đừng bao giờ để pipeline deploy thẳng, phải qua health check hoặc canary deployment để detect lỗi trước khi 100% traffic chuyển sang version mới.
Một pipeline tốt không phải thứ bạn nghĩ đến mỗi ngày
Đó là điểm mình thích nhất của CI/CD: khi set up đúng, bạn không nghĩ đến nó nữa. Code push lên, test chạy, deploy tự động. Nếu có lỗi, bạn nhận thông báo Slack trong vòng 2 phút thay vì khách hàng báo.
Đầu tư 1-2 ngày set up đúng ngay từ đầu sẽ tiết kiệm hàng trăm giờ về sau. AWS CodePipeline + GitHub Actions không phải combo duy nhất, nhưng với team đang dùng AWS, đây là lựa chọn ít "ma sát" nhất để bắt đầu.
