CodeDeploy Lifecycle Hooks: Thứ Tự, Biến, và Khi Hook Fail

K
Kai··5 min read

Bài trước appspec chạy qua một chuỗi lifecycle event và app lên được EC2. Nhưng "chạy qua" che giấu vài chi tiết quan trọng: thứ tự chính xác, hook nào chạy từ revision nào, và điều gì xảy ra khi một hook hỏng. Hiểu sai mấy chỗ này dẫn tới deploy lỗi khó debug. Bài này mổ kỹ phần hook, với hai thí nghiệm thật: đọc biến môi trường CodeDeploy truyền vào hook, và cố tình cho một hook fail để xem deploy phản ứng.

Mục tiêu

Nắm chính xác thứ tự lifecycle event và hook chạy ở đâu, biến nào truyền vào script, và hành vi khi hook fail.

Thứ tự lifecycle event

Một deploy in-place đi qua chuỗi event cố định. Không phải event nào cũng cho bạn gắn hook — vài cái CodeDeploy tự làm:

ApplicationStop    ← hook của bạn (chạy từ revision CŨ)
DownloadBundle     ← CodeDeploy làm (agent kéo zip từ S3)
BeforeInstall      ← hook của bạn
Install            ← CodeDeploy làm (copy file theo khối `files`)
AfterInstall       ← hook của bạn
ApplicationStart   ← hook của bạn
ValidateService    ← hook của bạn

Năm event in đậm (ApplicationStop, BeforeInstall, AfterInstall, ApplicationStart, ValidateService) là nơi bạn gắn script trong hooks. Hai event DownloadBundleInstall do CodeDeploy thực hiện, không gắn hook được.

Có một chi tiết cực kỳ quan trọng và dễ sai: ApplicationStop chạy script từ revision đã deploy trước đó, không phải revision mới. Lý do hợp lẽ — để dừng app đang chạy, CodeDeploy cần dùng kịch bản dừng của chính phiên bản đang chạy, mà phiên bản đó là revision cũ. Hệ quả thực tế: nếu bạn sửa stop_server.sh sai trong revision mới, lỗi đó chỉ lộ ra ở lần deploy sau nữa, không phải lần này. Và ở lần deploy đầu tiên (chưa có revision cũ nào), ApplicationStop đơn giản bị bỏ qua.

Hook nào cho việc gì

Mỗi event hợp một loại việc: ApplicationStop dừng app đang chạy gọn gàng; BeforeInstall chuẩn bị (cài gói hệ thống, backup, dọn thư mục); AfterInstall cấu hình sau khi file đã copy (đổi quyền, sửa config, build asset tại chỗ); ApplicationStart khởi động lại dịch vụ; ValidateService kiểm tra app thật sự hoạt động (health check). Tách việc đúng event giúp deploy có thể dừng sớm khi sai, trước khi app hỏng được phơi ra người dùng.

Biến môi trường CodeDeploy truyền vào hook

Mỗi script hook nhận một bộ biến môi trường cho biết nó đang chạy trong ngữ cảnh nào. Để xem tận mắt, thêm một hook AfterInstall ghi các biến đó ra một file mà web server phục vụ:

#!/bin/bash
# scripts/write_info.sh — AfterInstall hook
cat > /var/www/html/deploy-info.txt <<INFO
APPLICATION_NAME=${APPLICATION_NAME}
DEPLOYMENT_GROUP_NAME=${DEPLOYMENT_GROUP_NAME}
DEPLOYMENT_ID=${DEPLOYMENT_ID}
LIFECYCLE_EVENT=${LIFECYCLE_EVENT}
INFO

Sau khi deploy, đọc file qua curl:

$ curl http://<public-ip>/deploy-info.txt
APPLICATION_NAME=awscicd-demo
DEPLOYMENT_GROUP_NAME=awscicd-demo-dg
DEPLOYMENT_ID=d-XXXXXXXXX
LIFECYCLE_EVENT=AfterInstall

LIFECYCLE_EVENT cho script biết nó đang chạy ở event nào — hữu ích khi một script dùng chung cho nhiều hook và muốn rẽ nhánh theo event. DEPLOYMENT_ID, APPLICATION_NAME, DEPLOYMENT_GROUP_NAME cho ngữ cảnh để log hoặc gọi API. Những biến này khả dụng trong mọi hook script, không cần khai gì thêm.

Khi một hook fail

Đây là phần quan trọng nhất. Cố tình cho validate.sh thất bại:

#!/bin/bash
echo "Simulating a failed validation"
exit 1

Deploy lại và xem kết quả:

$ aws deploy get-deployment --deployment-id $DID --query 'deploymentInfo.status'
Failed

$ aws deploy get-deployment-instance --deployment-id $DID --instance-id i-03a4... \
    --query 'instanceSummary.lifecycleEvents[].[lifecycleEventName,status]' --output text
ApplicationStop   Succeeded
DownloadBundle    Succeeded
BeforeInstall     Succeeded
Install           Succeeded
AfterInstall      Succeeded
ApplicationStart  Succeeded
ValidateService   Failed

Deploy dừng đúng tại ValidateService — các event trước nó đã chạy xong, và (nếu có) event sau nó sẽ không chạy. Một hook trả mã thoát khác 0 làm cả deploy fail. Xem chẩn đoán để biết chính xác lỗi gì:

$ aws deploy get-deployment-instance --deployment-id $DID --instance-id i-03a4... \
    --query 'instanceSummary.lifecycleEvents[?status==`Failed`].diagnostics.[scriptName,message,errorCode]' --output text
scripts/validate.sh   Script at specified location: scripts/validate.sh run as user root failed with exit code 1   ScriptFailed

CodeDeploy chỉ thẳng script nào (scripts/validate.sh), mã lỗi (ScriptFailed), và exit code (1). Đây là lý do ValidateService quý giá: nó là chốt chặn cuối trên máy đích — nếu app vừa deploy không qua được health check, deploy được đánh dấu fail thay vì âm thầm phục vụ một bản hỏng. Ghép với auto-rollback (bài 10), một validate fail sẽ tự kéo về bản tốt trước đó.

   timeline một lần deploy (in-place):

   [revision CŨ]        [revision MỚI]
   ApplicationStop  ──▶ DownloadBundle ─▶ BeforeInstall ─▶ Install ─▶ AfterInstall
        (dừng app)       (kéo zip S3)      (chuẩn bị)      (copy)     (cấu hình)
                                                                          │
                                              ApplicationStart ◀──────────┘
                                                    │
                                              ValidateService ──┬─ Succeeded → deploy OK
                                                                └─ exit≠0   → deploy FAILED, dừng tại đây

runas và timeout

Mỗi hook khai thêm runas (chạy script dưới user nào — ở đây root để cài gói, sửa /var/www) và timeout (giây tối đa; quá thì hook coi như fail). Đặt timeout hợp lý cho hook chậm (ví dụ chờ service sẵn sàng) để deploy không treo vô hạn.

🧹 Dọn dẹp

Bài 10 chuyển sang Auto Scaling Group nên kết thúc bài này là lúc terminate instance đơn lẻ:

$ aws ec2 terminate-instances --instance-ids i-03a4...

(Application và deployment group giữ lại — bài 10 gắn ASG vào chính chúng.)

Tổng kết

Một in-place deploy đi qua chuỗi event cố định: ApplicationStop → DownloadBundle → BeforeInstall → Install → AfterInstallApplicationStartValidateService, trong đó năm event bạn gắn hook, hai event CodeDeploy tự làm. ApplicationStop chạy script từ revision (để dừng đúng app đang chạy), nên lỗi trong nó lộ ra ở lần deploy sau. Mọi hook nhận biến môi trường (LIFECYCLE_EVENT, DEPLOYMENT_ID...) cho biết ngữ cảnh. Một hook trả mã khác 0 làm deploy fail ngay tại event đó, kèm chẩn đoán chỉ rõ script và lỗi — biến ValidateService thành chốt chặn cuối trên máy đích.

Bài tới mở rộng từ một instance lên nhiều: deploy lên Auto Scaling Group, và chọn deployment config (OneAtATime, HalfAtATime, AllAtOnce) để điều khiển deploy lần lượt từng máy hay đồng loạt — đánh đổi giữa tốc độ và an toàn.