CI/CD được viết tắt từ Continuous Integration/Continuous Delivery hoặc Continuous Deployment, đại diện cho một phương pháp tự động hóa quá trình phát triển phần mềm, đảm bảo rằng mã nguồn được kiểm tra, xây dựng và triển khai một cách liên tục. Trong bài viết này, chúng ta sẽ khám phá cách xây dựng một hệ thống CI/CD tự động sử dụng các dịch vụ như AWS CodePipeline, ECS, Fargate, CodeBuild, CodeDeploy, và Github.

Mô hình hoạt động của quy trình CI/CD trong bài viết được mô tả như sau:

  • Bước 1: Developer đẩy mã nguồn lên kho lưu trữ trên Git (trong ví dụ này, sử dụng Github).
  • Bước 2: CodeBuild tạo một bản build mới nhất (Image) dựa trên mã nguồn đã đẩy (1).
  • Bước 3: Image mới nhất được đẩy sang Elastic Container Registry (ECR) để lưu trữ (3).
  • Bước 4: CodeDeploy kích hoạt quá trình triển khai bằng cách xây dựng hình ảnh của Container chạy trên môi trường Elastic Container Service (ECS) (4).
  • Bước 5: Trong môi trường ECS, sử dụng Load Balancer để nhận các yêu cầu từ máy khách và phân tải chúng đến các nhiệm vụ (Tasks) của Dịch vụ (Service) trong ECS.

Tóm lại, mô hình này bắt đầu từ việc developer đẩy mã nguồn lên kho lưu trữ và tự động triển khai mã nguồn đó thành một Container chạy trên môi trường ECS, với khả năng phân tải tải lưu lượng truy cập đến các Tasks của Service thông qua Load Balancer.

I. Giới thiệu các dịch vụ được sử dụng

Trong bài thực hành, có một số dịch vụ của AWS được sử dụng để xây dựng mô hình CI/CD. Dưới đây là một giới thiệu sơ lược về các dịch vụ này:

  • AWS CodePipeline: AWS CodePipeline là dịch vụ quản lý luồng làm việc CI/CD tự động. Nó cho phép bạn xây dựng, triển khai và tự động hóa quá trình phát triển phần mềm.
  • AWS Elastic Container Registry (ECR): AWS ECR là dịch vụ lưu trữ hình ảnh Docker containers. Nó cho phép bạn lưu trữ và quản lý các hình ảnh containers, cung cấp tích hợp chặt chẽ với các dịch vụ khác của AWS.
  • AWS CodeBuild: AWS CodeBuild là dịch vụ xây dựng và kiểm tra mã nguồn tự động. Nó tạo ra các hình ảnh containers từ mã nguồn và sau đó kiểm tra chúng trước khi triển khai.
  • AWS CodeDeploy: AWS CodeDeploy là dịch vụ triển khai ứng dụng tự động trên các môi trường khác nhau, bao gồm Amazon Elastic Container Service (ECS).
  • AWS Elastic Container Service (ECS): Amazon ECS là dịch vụ quản lý và triển khai containers. Nó cho phép bạn chạy và quản lý các containers trên các máy ảo EC2 hoặc Fargate.
  • AWS Elastic Load Balancer (ELB): Elastic Load Balancer là dịch vụ cân bằng tải trong môi trường AWS. Nó giúp phân tải lưu lượng truy cập đến các tasks của dịch vụ ECS và đảm bảo tính ổn định và khả dụng của ứng dụng.

Tất cả các dịch vụ này kết hợp lại với nhau để tạo một quy trình CI/CD tự động, bắt đầu từ việc đẩy mã nguồn lên kho lưu trữ và kết thúc với ứng dụng chạy trên môi trường ECS với tích hợp Load Balancer để phân tải lưu lượng truy cập.


II. Các bước xây dựng CI/CD tự động bằng AWS

1. Hướng dẫn tạo một Application Load Balancer trên AWS

  • Bước 1: Truy cập tài khoản AWS của bạn bằng cách đăng nhập.
  • Bước 2: Đến trang "EC2 Dashboard" trong giao diện AWS Console.
  • Bước 3: Tại góc trái, chọn "Load Balancers" trong menu.
  • Bước 4: Nhấp vào nút "Create Load Balancer" và chọn loại Load Balancer bạn muốn tạo. Trong trường hợp này, chọn "Application Load Balancer".
  • Bước 5: Tiếp theo, bạn cần cấu hình Load Balancer bằng các bước sau:
    - Đặt tên cho Load Balancer của bạn.
    - Lựa chọn Availability Zones để triển khai Load Balancer. Đề nghị lựa chọn ít nhất 2 Availability Zones để đảm bảo tính sẵn sàng cao (High Availability) cho ứng dụng của bạn.
  • Bước 6: Sau đó, bạn cần tạo và cấu hình Target Group cho Load Balancer. Target Group sẽ quyết định cách các yêu cầu truy cập sử dụng cổng 8080 sẽ được định tuyến đến các đối tượng cụ thể. Ví dụ, bạn có thể tạo một Target Group sử dụng cổng 8080 cho ứng dụng NodeJS.
  • Bước 7: Cuối cùng, bạn chỉ cần kiểm tra và xác nhận các cài đặt của bạn trước khi tạo Load Balancer. Hãy lưu ý rằng phần này chỉ là một bước nhỏ trong quá trình tổng thể, và bạn có thể tùy chỉnh và cấu hình nâng cao thêm cho Application Load Balancer (ALB) của bạn theo nhu cầu cụ thể của dự án.

2. Mã nguồn sử dụng cho bài thực hành

Ở đây, mình đã tạo một dự án mã nguồn Node.js, sử dụng framework Express, với phiên bản node: 18.15.0. Dự án này bao gồm một tệp index.js, mà khi được truy cập, sẽ trả về thông tin dưới dạng JSON. Mình sẽ tập trung chủ yếu vào việc thay đổi nội dung trong tệp này. Ứng dụng mình sẽ lắng nghe trên cổng 8080 (sẽ được ánh xạ với Target Group khi tạo Application Load Balancer ở trên). Bạn có thể sao chép dự án của mình tại đây.

File Dockerfile dùng để định nghĩa các bước build một images.

Tệp buildspec.yml này sẽ chia thành các phần sau:

  • Phần pre_build: Đây là nơi chúng ta thiết lập các biến môi trường và thực hiện kết nối đến AWS Elastic Container Registry (ECR) để đăng nhập và lấy hình ảnh Docker.
  • Phần build: Trong phần này, chúng ta sẽ xây dựng ứng dụng của mình và lưu trữ hình ảnh Docker được tạo trong ECR.
  • Phần post_build: Ở phần cuối cùng, chúng ta sẽ đăng ký và triển khai ứng dụng của mình.
version: 0.2
phases:
  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
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - 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:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imagedefinition.json
      - cat imagedefinition.json
artifacts:
  files: imagedefinition.json

3. Tạo một Elastic Container Registry (ECR) trên AWS

  • Bước 1: Đăng nhập vào bảng điều khiển (console) của AWS.
  • Bước 2: Chọn dịch vụ ECR từ danh sách các dịch vụ AWS.
  • Bước 3: Nhấp vào nút "Create repository" để tạo một repository mới.
  • Bước 4: Đặt tên cho repository của bạn.
  • Bước 5: Chọn thư mục để lưu trữ hình ảnh (image) của bạn: "public" hoặc "private".
  • Bước 6: Nếu bạn chọn "private", hãy xác định các đối tượng IAM (Identity and Access Management) mà bạn muốn cho phép truy cập vào repository.
  • Bước 7: Sau khi đã hoàn tất các cài đặt, hãy nhấp vào nút "Create repository" để tạo repository.

4. Khởi tạo CodeBuild

  • Bước 1: Đăng nhập vào bảng điều khiển (console) của AWS.
  • Bước 2: Chọn dịch vụ CodeBuild từ danh sách các dịch vụ AWS.
  • Bước 3: Nhấp vào nút "Create project" để tạo một dự án mới.
  • Bước 4: Đặt tên cho dự án.
  • Bước 5: Cấu hình thông tin về nguồn dữ liệu của dự án, bao gồm thông tin về kho lưu trữ mã nguồn và phiên bản mã nguồn mà bạn muốn sử dụng.
  • Bước 6: Cấu hình thông tin về môi trường xây dựng (build environment), bao gồm hệ điều hành, kiến trúc, Docker image, và các biến môi trường. Trong phần Source, bạn chọn Source provider là Github. AWS sẽ yêu cầu bạn xác thực, bạn chỉ cần đăng nhập vào tài khoản Github của bạn. Điều này rất đơn giản. Nếu bạn đã đăng nhập trước đó, bạn không cần phải đăng nhập lại. Ở mục "GitHub repository," bạn chọn kho lưu trữ (repo) mà bạn muốn triển khai (hoặc nếu bạn chưa có, bạn có thể sao chép dự án của mình tại đây).
  • Bước 7: Trong phần "Primary source webhook events", bạn chọn vào mục "Webhook" và "Build type" là "Single build". Loại sự kiện (Event type) được chọn là "PUSH". Điều này có nghĩa rằng khi bạn thay đổi mã nguồn và đẩy mã lên, một tín hiệu (trigger) sẽ được kích hoạt và thông báo cho CodeBuild. CodeBuild sẽ tự động xây dựng lại và cập nhật những thay đổi mới.
  • Bước 8: Trong mục "Environment," bạn chọn "Managed image". Hệ điều hành là "Amazon Linux 2", Runtime là "Standard", và Image là "amazonlinux-86_64-standard:4.0" (dành cho việc xây dựng hình ảnh trên môi trường Linux). Quan trọng là phải chọn tùy chọn "Privileged". Nếu bạn chưa có, bạn có thể chọn "New service role" trong phần "Service role".
  • Bước 9: Trong mục "Buildspec", bạn chọn "Use a buildspec file". AWS sẽ mặc định tìm tệp có tên "buildspec.yml" trong thư mục gốc của mã nguồn của bạn để thực thi. Nếu bạn đặt tên tệp khác hoặc đặt tệp trong thư mục con khác, bạn cần cung cấp tên của tệp Buildspec trong trường "Buildspec name" (tùy chọn).
  • Bước 10: Trong mục "Artifacts", phần "Logs", bạn tạo một thư mục để lưu trữ các thông tin log trong quá trình xây dựng mã nguồn. Sau đó, nhấn nút "Create build project" để tạo dự án xây dựng.
  • Bước 11: Sau khi tạo xong, bạn sẽ thấy dự án CodeBuild vừa được tạo. Bạn có thể thực hiện các tác vụ tiếp theo.
  • Bước 12: Để nâng cao tính chuyên nghiệp và tốc độ xây dựng mã nguồn, bạn nên cấu hình các biến môi trường bổ sung trong quá trình xây dựng mã nguồn. Bạn có thể thực hiện điều này bằng cách chọn "Edit" -> "Environment" (Chỉnh sửa -> Môi trường).
  • Bước 13: Tiếp theo, các bạn nhấn vào Additional configuration
  • Bước 14: Bạn cần cấu hình tên của các biến môi trường trong tệp buildspec.yml mà bạn đã khai báo trước đó. Phần về tài nguyên máy tính (compute) có thể thay đổi, lưu ý rằng việc tăng dung lượng bộ nhớ có thể cải thiện tốc độ xây dựng mã nguồn, nhưng cũng có thể tăng chi phí. Cuối cùng, sau khi thực hiện các cấu hình, bạn nhấn nút "Update environment" (Cập nhật môi trường).
  • Bước 15: Lưu ý quan trọng: Để CodeBuild có thể truy cập và lấy các hình ảnh mới nhất từ Elastic Container Registry, bạn phải cấu hình quyền truy cập cho CodeBuild. Trong bài viết này, mình đã chọn quyền "AmazonEC2ContainerRegistryFullAccess" để minh họa, nhưng trên thực tế, bạn nên giới hạn quyền truy cập theo nguyên tắc bảo mật và theo nhu cầu cụ thể của dự án của bạn.

5. Tạo Amazon Elastic Container Service (ECS) để triển khai ứng dụng Node.js

Bước 1: Tạo Cluster
  • Chọn dịch vụ Amazon ECS.
  • Trên trang quản lý Amazon ECS, chọn tạo một cluster mới.
  • Chọn loại cluster (ví dụ: mình sẽ để mặc định là Infrastructure và sử dụng AWS Fargate + Networking, bạn hãy chọn VPC của bạn và đặt các subnet trong subnet public).
  • Chú ý, nếu bạn đặt các subnet trong subnet private, bạn cần cấu hình thêm NAT Gateway trong bảng định tuyến (route table) của nhóm subnet để cho phép các dịch vụ tải các gói (packages) từ Dockerfile về máy chủ (download các package) được.
  • Sau đó nhấp vào nút “Create
Bước 2: Tạo Task Definition
  • Trong trang quản lý Amazon ECS, chọn tạo mới một Task Definition.
  • Đặt tên cho Task Definition.
  • Điền thông tin cho Container. Lưu ý rằng tên của container phải trùng với tên bạn đã đặt($CONTAINER_NAME) ở phần cấu hình Environment trên CodeBuild. Ở đây, mình đặt là "lab-app", nên tên container cũng là "lab-app". Nếu không khớp, ứng dụng sẽ không chạy được.
  • Tiếp theo, bạn cần copy Image URI từ repository trong dịch vụ Elastic Container Registry mà bạn đã tạo trước đó và dán vào trường Image. Hãy đảm bảo rằng bạn đã thêm tag "latest" vào URI (ví dụ: "012345789.dkr.ecr.ap-southeast-1.amazonaws.com/demo-codebuild:latest"). Mặc định, CodeBuild sẽ lấy hình ảnh có tag "latest" mới nhất để chạy.
  • Sau đó, nhấn nút "Next".
  • Ở phần "Configure environment, storage, monitoring, and tags": chọn môi trường là AWS Fargate, hệ điều hành là Linux/X86_64. Task size: ở đây, bạn có thể đặt tối thiểu (minimum) để tiết kiệm chi phí. Trong phần Task execution role, nếu bạn chưa có vai trò (role), bạn có thể chọn "Create new role". Tuy nhiên, nếu bạn đã tạo trước đó, bạn có thể sử dụng lại vai trò đã có.
  • Cuối cùng, nhấn "Next" để xem lại thông tin cấu hình và sau đó nhấn "Create" để hoàn thành quy trình tạo Task Definition.
Bước 3: Tạo Service
  • Chọn cluster đã tạo trong Bước 1
  • Chọn Create Service
  • Mục Enviroment, các bạn chọn Lauch type là FARGATE nhé. (các bạn có thể sử dụng EC2 Instance cũng được, nhưng cần phải cài nhiều config trên EC2 để chạy dịch vụ, trong bài demo này mình dùng Fargate để đơn giản hóa hạ tầng)
  • Mục Deployment configuration, các bạn chọn là Service, Family thì các bạn chọn tên Task Definition đã khởi tạo ở bước 1 nhé. Service name thì các bạn đặt tên tùy ý cho dịch vụ của mình. Desired tasks là số task mà các bạn muốn dịch vụ của mình chạy, ở đây mình để là 1 task nhé.
  • Mục Networking, các bạn chọn VPC của mình, Sunet thì các bạn chọn Subnet Public nhé. Security group thì các bạn chọn security group của các bạn nếu đã tồn tại, hoặc tạo mới nếu chưa có. Nhớ mở port 8080 chạy NodeJS cho dịch vụ của mình nhé.
  • Ở đây, không cần bật Public IP vì sẽ truy xuất thông qua Application Load Balancer đã tạo trước đó.
  • Ở mục Load balancing, các bạn chọn loại Application, và chọn Application balancing trước đó các bạn đã tạo nhé. Port là 8080, Target group đã tạo trước đó.Health check mình để 15s cho nhanh nhé. Sau đó nhấn nút Create.

Note: Ở đây chúng ta có thể thiết lập cấu hình nâng cao như tự động mở rộng, giao tiếp giữa nhiều Service trong hệ thống như thế nào ( Auto Scale, Service Discovery…). Đây là chủ đề Advance rất thú vị, luôn được sử dụng nhiều trong dự án thực tế, có thời gian mình sẽ chia sẻ tiếp ở một bài khác nhé.

  • Sau khi đợi Service chạy xong, các bạn có thể thấy task của mình đã được tạo ra,Container tên là lab-app
    và một số thông tin cấu hình cơ bản bên dưới:
  • Thử truy cập trên web nhé, các bạn quay lại dịch vụ EC2, chọn Load Balancers, sau đó copy DNS name và dán vào Browers nhé, lưu ý nhớ thêm port 8080 nhé.
  • Như vậy là chúng ta đã deploy thành công website NodeJS của mình lên ECS rồi.
  • Để kiểm tra CodeBuild chúng ta đã hoạt động tốt không, các bạn thử thay đổi source code rồi commit và push lên lại nhé.
  • Ở đây mình sẽ sửa nội dung là Version 2 và commit và push lên lại. CodeBuild sẽ tự động build lại những thay đổi đó. Và lưu images mới nhất vào ECR.

III. Kết luận và tài liệu thao khảo

Bài viết đã khá dài tới đây, hẹn gặp lại các bạn trong phần 2.

Trong phần 2, mình sẽ hướng dẫn bạn cách sử dụng CodePipeline để tự động hóa việc cập nhật ứng dụng khi có sự thay đổi trong mã nguồn cục bộ (ví dụ: khi nhóm phát triển hoàn thành một tính năng mới và muốn phát hành), để trang web của chúng ta tự động cập nhật các thay đổi đó.

Chúc các bạn thành công và hẹn gặp lại trong phần tiếp theo!

IV. Tài liệu tham khảo