Xây Dựng Hệ Thống CICD và infrastructure cho hệ thống web application nhiều tiers chuẩn thực tế (Phần 2)

Long Ngo
Xây dựng hệ thống CICD , và infrastructure trên AWS cho hệ thống Web Application nhiều layer.
Lab Introduction
- AWS experience: Intermediate
- Time to complete: 180 minutes
- AWS Region: US East (N. Virginia) us-east-1
- Cost to complete: (Optional) Free Tier eligible, bellow 10$
- Services used: Github Action (Github), EC2, Auto Scaling Group, S3, Cloudwatch Log, AMI, RDS, KMS, Terraform (Hashi Corp), Packer (HashiCorp)
Giới thiệu về Lab
Xin chào các bạn, Mình là Long Mentor hôm nay mình sẽ mang tới cho các bạn một bài lab khá thú vị, về việc xây dựng một hệ thống CICD hoàn chỉnh với độ phức tạp tiệm cận với một dự án thực tế. Mục đích mình xây dựng bài lab này là để cho các bạn có thể hình dung về một hệ thống CICD hoàn chỉnh thực tế sẽ trông như thế nào. Dù bài viết sẽ không cover được hoàn toàn tất cả các biến thể của hệ thống thực tế nhưng mà có sao đâu, học hành là con đường dài, nếu các bạn đi với mình thì chúng ta cùng nhau khám phá dần dần nhé. 😎 Vì bài viết khá dài nên mình xin phép được chia ra làm nhiều phần.
Bài viết không được generate bởi AI, nên có thể sẽ bao gồm :
- Lỗi chính tả. ✍
- Ý văn lủng củng. ☹
- Một số nội dung không phù hợp do mình cao hứng viết. 😝
- Câu chuyện do mình bịa ra nhưng gây đồng cảm sâu sắc. 😊
Tuy nhiên sẽ không bao gồm:
- Câu từ hoa mỹ nhưng không có ý nghĩa. 🤦♂️
- Code không được verify.🤬
- Cảm giác fake fake khi đọc. 🤢
Câu chuyện, tiếp nối phần trước ...
⚠️ Tuyên Bố Miễn Trừ Trách Nhiệm
Lưu ý: Tất cả các nhân vật, công ty, sự kiện, và tình huống được đề cập trong câu chuyện này đều là sản phẩm của sự hư cấu, được tạo ra với mục đích giải trí hoặc minh họa.
Câu chuyện không có mục đích công kích, bôi nhọ bất kỳ cá nhân, tổ chức, hoặc ám chỉ đến bất kỳ sự thật thực tế nào. Mọi sự trùng hợp về tên gọi, sự kiện, hoặc chi tiết đều chỉ là ngẫu nhiên.
Tác giả không chịu bất kỳ trách nhiệm nào đối với việc nội dung này bị sử dụng sai mục đích, chẳng hạn như tuyên truyền hoặc xuyên tạc bởi bất kỳ cá nhân hoặc tổ chức nào khác.
"Mẹ nó chứ!"
Đôi môi Trung mím chặt, hàm răng nghiến ken két, từng từ như lướt qua kẽ răng. Chửi xong, anh cầm ly bia tươi mới rót, bọt còn sủi, nốc một hơi cạn sạch.
"Không biết mấy lão sếp nghĩ cái quái gì trong đầu! Start dự án ngay kỳ 30/4 - 1/5. Em đã có plan đi Phú Quốc, vé máy bay đặt trước ba tháng rồi. Giờ lẽ nào bỏ việc?"
Trung là thành viên đội dev, từng làm cùng team DevOps với Long. Hai người gắn bó lâu năm, ăn ý như hình với bóng. Trung giỏi kỹ thuật nhưng tính nóng như lửa, sẵn sàng cãi tay đôi với quản lý hay team lead. Báo cáo task thì uể oải, nhưng hễ ai đụng đến mình, anh không bao giờ bỏ qua, thậm chí sẵn sàng "xử lý" bằng nắm đấm nếu cần. Quản lý nào họp với Trung vài lần cũng ngán ngẩm, xin chuyển team. Câu cửa miệng của Trung là:
"Con lợn nào chả làm quản lý được. Giỏi hơn dev thì vào mà code!"
Nhưng với Long, Trung lại khác. Long xuất thân từ dev, từng hướng dẫn Trung từ ngày đầu vào công ty. Long ít nói, dễ tính, hay cả nể, thường hùa theo ý kiến người khác. Vì thế, hai người hợp cạ, lâu dần thành thân thiết. Ông bà xưa có câu: "Cây khô có cành, người khó có đôi" – chính là để chỉ những mối quan hệ như thế này.
Về vụ start dự án trúng kỳ nghỉ lễ, Long lúc nhận task từ sếp chẳng nghĩ ngợi gì. Dù là team lead, anh thiếu kinh nghiệm quản lý và tố chất lãnh đạo. Trước giờ, Long chỉ làm qua loa, giao việc cho team rồi tự ôm task khó về mình, cố gắng hoàn thành đúng hạn. Ngoài cách đó, Long chẳng có chiêu gì khác. Mấy tuần nay, anh không tổ chức daily meeting, nên chẳng biết team đã lên lịch nghỉ. Đến giờ, chẳng biết trách ai, đành chơi lại bài cũ.
Long chặc lưỡi: "Thôi chú cứ nghỉ thong thả, task cũng không lớn, team sẽ cover được."
Vì ngại giao tiếp và sợ xung đột, Long thường ôm việc về mình, lặng lẽ OT giải quyết. Trước kia thì ổn, nhưng giờ Long cảm thấy sức khỏe không còn như xưa. Gần 38 tuổi, anh chẳng thể thức đêm code hay học công nghệ mới như bọn trẻ. Trong làn sóng "vibe coding", những dev lớn tuổi như Long, với mức lương cao, thường là mục tiêu bị ngắm cho layoff. Làm sao vô tư như Trung, kiểu công ty làm phật ý thì nghỉ việc, chuyển chỗ khác?
Đang mải suy nghĩ, một giọng nhỏ nhẹ vang lên bên cạnh: "Chắc công ty cũng có khó khăn. Mọi người trong team cùng cố gắng, em nghĩ sẽ vượt qua được."
Đó là Phương Linh, cô bé Gen Z mới vào công ty đầu năm dưới dạng intern. Dù chưa có nhiều kinh nghiệm code, Linh biết ba thứ tiếng, được bố mẹ đầu tư từ nhỏ, sở hữu loạt chứng chỉ từ ghế giảng đường. Nhờ đó, cô dễ dàng giành vị trí intern trong team của Long. Công việc của Linh không nặng, chủ yếu làm tester, dịch tài liệu cho user nước ngoài, và tham gia quảng bá sản phẩm ở hội chợ công nghệ nhờ ngoại hình xinh xắn. Quý nào cô cũng nhận giải "Tài năng trẻ tiêu biểu" của công ty.
Nhìn Linh, Long luôn hoài niệm tuổi trẻ của mình. Giá như ngày xưa chăm học ngoại ngữ, có lẽ giờ anh đã có nhiều cơ hội tốt hơn, thậm chí đổi ngành. Càng nghĩ càng buồn, Long cạn chén, nốc bia ừng ực, say lúc nào không hay.
Long về nhà trong tình trạng say bí tỉ. Lâu rồi anh không uống quá chén thế này. Mở cửa, bước đến thềm nhà, Long thấy nôn nao, đầu óc quay cuồng như sắp ngất. Trong cơn màng màng, anh chợt nhớ lời các cụ ở quê: uống rượu say, đi đêm dễ bị trúng gió. Nhẹ thì méo miệng, nặng thì "cưỡi hạc" về trời. Sáng ra, người ta chỉ thấy cái xác cứng đờ.
Nghĩ đến đó, Long lạnh sống lưng, mồ hôi túa ra. Chẳng lẽ đây là khoảnh khắc các cụ nói? Anh cố mở mắt, gượng dậy, nhưng tay chân vô lực. Những hình ảnh tuổi thơ chợt lướt qua như cuốn phim quay chậm. Ý thức dần mờ đi...
Cứ ngỡ đã chấm hết, thì ánh đèn trong nhà sáng lên. Một dáng người mảnh mai chạy ra, quỳ xuống, đỡ Long dậy. Giọng nói ấm áp, ngọt ngào xen chút trách móc: "Sao lại ra bộ dạng này? Em đã bảo uống ít thôi mà. Mau vào nhà, em thay quần áo cho."
Long thấy cơ thể nhẹ hẳn, lồng ngực ấm lên, tay chân dần có cảm giác. Vợ dìu anh đi tắm, nấu bát canh nóng giải rượu, thay đồ pyjama. Hai vợ chồng lên giường. Long kể hết chuyện công việc, những buồn phiền gần đây, mắt cay cay.
Vợ dịu dàng: "Cuộc sống có lúc khó khăn. Nếu anh mất việc, em sẽ đi dạy lại. Con mình lớn rồi, sẽ san sẻ việc nhà. Tằn tiện một chút, mình sẽ vượt qua."
Nghe những lời an ủi, Long chìm vào giấc ngủ, lòng nhẹ nhàng, đầy động lực như chưa từng có.
Chuông báo thức từ iPhone vang lên. Long mở mắt, nhận ra mình vẫn nằm trước cửa nhà, mặc nguyên bộ đồ đi nhậu hôm qua. Trước mặt là bãi nôn lẫn dịch mật xanh vàng. Cổ họng khô khốc, đầu đau như búa bổ. Làm gì có vợ nào? 37 tuổi, anh vẫn độc thân.
Long uể oải tắt chuông, cố nhắm mắt tìm lại giấc mơ, nhưng chợt nhớ hôm nay phải đi làm. Anh vội chồm dậy, vệ sinh qua loa, vác gương mặt mệt mỏi đến công ty.
Lên công ty, xung quanh vắng tanh. Mọi người đã nghỉ lễ Độc lập, chỉ team của Long phải làm. Nhớ lại giấc mơ đêm qua, Long bỗng thấy cô đơn – cảm giác chưa từng xuất hiện bao năm nay.
Điện thoại rung. Tin nhắn từ Phương Linh: "Sáng nay em không khỏe, đau bụng, buồn nôn. Bác sĩ bảo ngộ độc thực phẩm, phải nhập viện vài ngày. Em xin nghỉ đến ngày XX."
Long lo lắng, nghĩ chắc do quán nhậu hôm qua. Cảm thấy có phần trách nhiệm, anh định chiều xong việc sẽ đến thăm cô intern nhỏ.
Nhưng chẳng bao lâu, Long phát hiện ra sự thật. Pha tách cà phê, lướt feed như thường lệ, anh thấy bài post của Phương Linh: "Nắng vàng, biển xanh, Phú Quốc và anh." Phía dưới là ảnh tay trong tay, trước bãi biển trắng tinh và hàng dừa xanh.
Hóa ra cặp đôi hôm qua về chung đã bay đi Phú Quốc sáng nay. "Chắc Linh quên mình có trong friend list." Long cười nhạt, lòng như mếu. Mở backlog, nhìn đống task phải xong trong ba ngày, anh thở dài ngao ngán...
(To be continued...)
OK văn vở đủ rồi ! vô món chính thôi các bạn 🤓
Về sự thay đổi so với phần 1
Sau khi được góp ý và xem xét mình có thay đổi kiến trúc như bên dưới:
Trước khi thay đổi:
Sau khi thay đổi:
Mình đã thay đổi những điểm sau:
- Tầng app run bằng ec2 mình đưa vào private subnet
- Dù không thể hiện trên sơ đồ architect nhưng cách kết nối cũng thay đổi như sau :
- Kết nối vào EC2 thay vì sử dụng SSH -> chuyển qua sử dụng SSM
- Packer cũng phải build trong private subnet
- Thêm bastion host để kết nối rds
Để cho đơn giản dễ hiểu mình sẽ trình bày cách tiếp cận và build của mình như sau :
Thành phần hệ thống :
- Application (Frontend, Backend) : Là ứng dụng chúng cần deploy.
- Infrastructure (EC2, RDS, S3 ...): Là config của cơ sở hạ tầng để triển khai ứng dụng, cái này hiểu nôm na là cái server để ở đây sẽ được quản lý dưới dạng IAC (Terraform) code.
- CICD : Là các workflow tự động hóa các quy trình build, kiểm thử , deploy ứng dụng của chúng ta lên cơ sở hạ tầng . Không có cái này thì hệ thống của chúng ta vẫn hoạt động đc các bạn nhé chỉ là sẽ phải làm bằng tay hết thôi.
Hướng tiếp cận của mình không chỉ riêng cho bài lab này mà là bất kỳ công việc thực tế được giao luôn luôn là manual first. Có nghĩa là mình luôn tạo ra một workflow hoàn chỉnh , làm thủ công bằng tay , sau đó mới tìm cách tự động hóa . Trong bài này mình vẫn giữ nguyên hướng tiếp cận như vậy.
Giới thiệu infra code
infra
├── README.md
├── backend.tf
├── main.tf
├── modules
│ ├── cloudfront
│ │ ├── main.tf
│ │ ├── output.tf
│ │ └── variables.tf
│ ├── compute
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── efs
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── load_balancer
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── monitoring
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── rds
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── rds_bastion
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── s3
│ │ ├── main.tf
│ │ ├── ouput.tf
│ │ └── variables.tf
│ ├── security
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ └── vpc
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── outputs.tf
├── packer
│ └── packer.pkr.hcl
├── plan.out
├── scripts
└── variables.tf
Dưới đây là mô tả ngắn gọn chức năng của từng module Terraform trong mã :
cloudfront:
- Tạo CloudFront distribution để phân phối nội dung tĩnh từ S3 (path
/
) và chuyển tiếp yêu cầu API (/api/*
) tới ALB. - Cấu hình cache behavior, HTTPS redirect, và chứng chỉ mặc định.
- Tạo CloudFront distribution để phân phối nội dung tĩnh từ S3 (path
compute:
- Triển khai EC2 trong Auto Scaling Group (ASG) với launch template.
- Tạo IAM role và security group cho EC2, cho phép truy cập S3 và CloudWatch.
load_balancer:
- Tạo Application Load Balancer (ALB) với target group cho backend EC2.
- Cấu hình listener HTTP (port 80) và security group cho traffic công khai.
monitoring:
- Thiết lập CloudWatch logs cho EC2, RDS, CloudFront.
- Tạo alarms cho CPU EC2 (>75%) và kết nối RDS (>100), gửi thông báo qua SNS.
rds:
- Triển khai PostgreSQL instance (RDS) với multi-AZ.
- Tạo subnet group và security group, cho phép EC2 truy cập port 5432.
rds_bastion:
- Tạo EC2 bastion host với SSM và PostgreSQL client.
- Cấu hình IAM role và security group để truy cập RDS qua SSM.
s3:
- Tạo S3 bucket cho nội dung tĩnh với tên duy nhất (prefix + UUID).
- Cấu hình website hosting, chính sách cho CloudFront OAI, chặn truy cập công khai.
security:
- Tạo WAF để chặn IP độc hại, gắn với ALB.
- Tạo KMS key để mã hóa tài nguyên.
vpc:
- Tạo VPC với public/private subnets, NAT Gateway, Internet Gateway.
- Cấu hình route table và S3 VPC endpoint để truy cập S3 an toàn.
Code từng phần các bạn hãy xem ở đây nhé :
https://github.com/longngo192/cmp-cicd/tree/main/infra
Chạy thử infra code
Để chạy được yêu cầu các bạn phải cài đặt terraform, vì tránh cho bài viết quá dài mình xin rút ngắn khoản cài đặt terraform nhé các bạn. Lưu ý : Ở file variable các bạn hãy thay đổi tên s3 bucket và email cho hợp lý nhé , hoặc các bạn có thể truyền giá trị các biến vào khi chạy lệnh terraform apply.
- Di chuyển vào thư mục infra
cd infra
- Chạy lệnh terraform apply
terraform apply --auto-approve
Sau khi deploy xong chúng ta sẽ kiểm tra một vài đâu mục bên dưới để đảm bảo code terraform provision chính xác như mong đợi :
- Check connection vào ec2 (backend)
- Kiểm tra config cloud front
Check connection vào ec2 (backend)
trước khi access nào ec2 thông qua ssm các bạn cần phải config (manual) SSM service một tẹo. Config này chỉ cần làm một lần.
Nguyên do là vì khi connect vào ec2 thông qua ssm , chúng ta sẽ sử dụng ssm-user . User này được service ssm tạo thông qua ssm agent trong instance, tuy nhiên khi deploy và run application của chúng ta , AWS khuyến khích sử dụng một user riêng nên chúng ta config như bên dưới để chuyển sang ec2-user nhé
- Tìm service SSM
- Ở menu bên trái tìm Session Manager
- Setting như hình gif
Okey tiến hành access thôi nào.
- Để access vào ec2 instance thông qua ssm chúng ta có thể chạy thẳng câu lệnh từ local cli hoặc kết nối thông qua aws connect or aws cloud shell Connect thông qua AWS connect
aws ssm start-session \
--target <instance-id>
- Trường hợp của mình sẽ là
aws ssm start-session \
--target i-062b8408428f21739
Volla !!!
Chạy những câu lệnh bên dưới để Cài đặt backend
Trường hợp các bạn fork repo của mình hãy chú ý đổi link repo cho hợp lý nhé
# Sau khi access vào ec2 instance chạy những lệnh bên dưới để setup server.
sudo yum clean all
sudo yum makecache
sudo yum install -y git gcc postgresql-devel python3 python3-pip python3-devel
sudo mkdir -p ~/app
sudo chown ec2-user:ec2-user ~/app
cd ~/app
git clone https://github.com/longngo192/cmp-cicd.git .
cd app/backend/
cat requirements.txt
pip3 install --no-cache-dir -r requirements.txt
RDS_ENDPOINT=$(aws rds describe-db-instances \
--db-instance-identifier webapp-rds-primary \
--region us-east-1 \
--query 'DBInstances[0].Endpoint.Address' \
--output text)
sed -i "s/YOUR_RDS_ENDPOINT/$RDS_ENDPOINT/g" .env
cat .env
ls app.py
sudo mkdir -p migrations
sudo chown ec2-user:ec2-user migrations
export FLASK_APP=app.py
export FLASK_ENV=production
python3 db_init.py
flask db init
flask db migrate
# chạy app
gunicorn -w 4 -b 0.0.0.0:5000 app:app
Test backend
- Lấy alb endpoint
- Chúng ta hãy vào trình duyệt và test api
http://webapp-alb-376268860.us-east-1.elb.amazonaws.com/api
- Data trả về thành công
Kiểm tra config cloudfront
Chúng ta cần kiểm tra xem cloudfront đã trỏ tới ALB chưa
- Lấy domain của cloudfront
- Chúng ta hãy vào trình duyệt và test api (với domain của cloudfront)
https://d2z4bbmw3ua6rp.cloudfront.net/api
- Data trả về thành công
Triển khai frontend manual
Build Frontend
- Vào thư mục frontend
cd app/frontend
# Set api backend & build
REACT_APP_API_URL=https://d2z4bbmw3ua6rp.cloudfront.net/api npm run build
- Updload toàn bộ nội dung trong thư mục build lên s3
- Test thôi
Hôm nay chúng ta đã hoàn thành phần 2 của seri phần cuối sẽ hướng dẫn các bạn tự động hóa tất cả step . Các bạn hãy đón xem nhé
Tất cả code hoàn chỉnh bao gồm workfow đã được upload trên repo bên dưới các bạn có thể tham khảo trước or đợi phần 3 của mình nhé
Nhá hàng một chút về workflow