Chào các bạn độc giả thân mến, đã lâu không gặp. Tôi là Nobito, phóng viên thông tấn xã VNLAB, hôm nay chúng ta sẽ cùng hòa mình vào thế giới phức tạp nhưng không kém phần thú vị của công nghệ. Cùng tôi đến gặp gỡ một nhân vật đặc biệt, một siêu nhân trong thế giới CI/CD - người triển khai NestJS lên Cloud Run (GCP) bằng Git Actions. Hãy cùng chào đón anh lập trình viên "Siêu Lườiiiii"

LTV Siêu Lười:  à, chào!

I. Giới thiệu CICD

nobito: vâng, đúng như tên gọi, siêu lười không trật phát nào. Thưa anh, đầu tiên thì anh hãy cho mọi người biết là tại sao anh quyết định triển khai CI/CD cho ứng dụng NestJS của mình?

LTV Siêu lười: À, là vì lười nên tôi muốn kiếm thêm thời gian để ngủ. Cậu không biết ấy chứ, trước khi chưa có CI/CD, tôi phải làm nhiều việc cùng lúc như:

1, Lập kế hoạch và Thiết kế:

  • Nhóm phát triển lên kế hoạch cho sprints/phases của dự án.
  • Thực hiện thiết kế ứng dụng và xây dựng các chức năng.

2, Phát triển:

  • Nhóm phát triển code và thực hiện các chức năng theo kế hoạch.

3, Kiểm thử đơn vị:

  • Nhóm phát triển thực hiện kiểm thử đơn vị để đảm bảo rằng mỗi phần mã nguồn hoạt động đúng như dự kiến.

4, Kiểm thử tích hợp:

  • Các thành viên từ các nhóm khác nhau đồng bộ hóa và tích hợp các phần của hệ thống để đảm bảo chúng làm việc chung.

5, Kiểm thử hệ thống:

  • Thực hiện kiểm thử hệ thống để đảm bảo toàn bộ ứng dụng hoạt động đúng cách khi tích hợp.

6, Triển khai thủ công:

  • Nhóm triển khai tiến hành triển khai ứng dụng lên môi trường sản xuất hoặc môi trường thử nghiệm.

7, Rollback thủ công:

  • Nếu có lỗi, nhóm triển khai phải thực hiện rollback thủ công để quay lại phiên bản trước đó của ứng dụng.

8, Quản lý phiên bản:

  • Sử dụng các công cụ quản lý phiên bản như Git để theo dõi và quản lý mã nguồn.

9. Ghi chú và Thông báo:

  • Ghi chú các thay đổi và thông báo đến các bên liên quan bằng cách thủ công.

CI/CD được thiết kế để giải quyết những vấn đề tôi nêu trên này bằng cách tự động hóa nhiều khía cạnh của quy trình, giúp tăng cường tính nhất quán, ổn định, và độ tin cậy của quy trình phát triển và triển khai. Nhờ đó, thời gian ngủ của tôi được tăng đáng kể đấy, hehehe.

II. Github Action, Google Cloud Platform: Cloud Run

nobito: ồ rất chi tiết, tôi hoàn toàn cảm thông khoảng thời gian trước kia của anh. Chủ đề hôm nay là Github action và Cloud Run, vậy anh có thể giới thiệu sơ qua 1 chút để cho các đọc giả biết được không ạ.

LTV Siêu lười: tất nhiên, vì lười nói nên tôi tóm tắt sẵn ở đây, anh hãy đọc nó. (*ngáp ngáp*)

GitHub Actions:

GitHub Actions là một dịch vụ tích hợp liền mạch vào nền tảng GitHub, giúp tự động hóa các quy trình phát triển phần mềm. Đây là một hệ thống CI/CD (Continuous Integration/Continuous Deployment) mạnh mẽ, cho phép bạn xây dựng, kiểm thử, và triển khai ứng dụng của mình một cách tự động mỗi khi có sự thay đổi trong mã nguồn.

Google Cloud Run:

Google Cloud Run là dịch vụ của Google Cloud Platform. Nó là một dịch vụ chạy container tự động và quản lý mọi thứ liên quan đến việc chạy ứng dụng mà không cần quản lý máy chủ. Bạn có thể đóng gói ứng dụng của mình vào container Docker và triển khai chúng trên Cloud Run. Nó tự động mở rộng dựa trên lưu lượng và chỉ tính phí khi ứng dụng đang chạy.

Ngoài ra, để thực sự deploy lên được Cloud run, thì cũng phải kể đến một số dịch vụ quan trọng khác của GCP là Cloud Build, Cloud Storage hay Artifact Registry.

Cloud Build:

Cloud Build là một dịch vụ quản lý và xây dựng liên tục (CI/CD) trên Google Cloud Platform. Nó cho phép bạn xây dựng, kiểm thử và triển khai ứng dụng một cách tự động thông qua các bước cấu hình trong file cấu hình YAML. Cloud Build hỗ trợ nhiều ngôn ngữ lập trình và môi trường phát triển, giúp tăng cường quá trình phát triển và triển khai ứng dụng của bạn.

Cloud Storage:

Cloud Storage là dịch vụ lưu trữ đối tượng của Google Cloud. Nó cung cấp khả năng lưu trữ không giới hạn và độ tin cậy cao cho dữ liệu của bạn. Cloud Storage được sử dụng để lưu trữ các tệp tin, dữ liệu và tài nguyên khác, và nó tích hợp tốt với các dịch vụ khác của Google Cloud, như Compute Engine, App Engine và BigQuery. Bạn có thể lưu trữ và truy cập dữ liệu từ bất kỳ đâu trên thế giới thông qua giao diện lập trình và giao diện người dùng.

Artifact Registry:

Artifact Registry là một dịch vụ quản lý các artifcat (gói, thư viện, container images) được sử dụng trong quá trình phát triển và triển khai ứng dụng. Nó giúp quản lý và lưu trữ an toàn các bản sao của các artifcat, giúp đảm bảo tính nhất quán và an toàn trong quy trình phát triển. Artifact Registry hỗ trợ nhiều định dạng artifcat như Docker container images, npm packages, Maven artifacts, và nhiều hơn nữa. Dịch vụ này tích hợp chặt chẽ với các công cụ phát triển và triển khai, như Cloud Build và Kubernetes

II. Triển khai NestJS lên Cloud Run bằng Git Actions

nobito: cảm ơn anh rất nhiều về phần giới thiệu vừa rồi. Giờ đi vào vấn đề chính, anh có thể tóm gọn một cách chi tiết quá trình triển khai NestJS lên Cloud Run bằng Git Actions được không nhỉ?

LTV Siêu lười: ok, hôm nay tôi sẽ phá lệ lười 1 bữa để giải thích thật chi tiết cho người chưa biết gì cũng làm được, hè hè. Trước khi đi vào chi tiết, thì chúng ta đi qua follow deploy 1 tí.

Giới thiệu Follow:

  1. Tạo source code, config Dockerfile cho source NestJS.
  2. Push code lên trên github, khi đã tạo 1 github action, thì github action sẽ lắng nghe sự kiện, ví dụ ở đây chúng ta merge code vào main, thì github action sẽ hoạt động, và sẽ tiến hành đẩy source code của chúng ta lên môi trường GCP.
  3. Tại đây, Github action thông qua Service Account, xác thực tài khoản hợp lệ, và tiến hành triển khai. Ở đây, Cloud build sẽ có nhiệm vụ phân phối source code và image từ file docker đến nơi tương ứng là Cloud Storage và Artifact Registry. Cuối cùng là triển khai lên Cloud Run để web có thể chạy được trước khi đến với người dùng cuối.

Nói thì khó hiểu vậy, bây giờ chúng ta hãy đi vào chi tiết hơn.

1. Chuẩn bị source code

nếu các bạn chưa có dự án NestJS ở local thì hãy tạo 1 dự án theo follow sau:

# lệnh install nestjs cli (yêu cầu phải cài node & npm trước nhé)
$ npm i -g @nestjs/cli

# lệnh tạo mới dự án nestjs với tên nestjs-cicd
$ nest new nestjs-cicd

2. Tạo Dockerfile và chạy thử ở local

# vào thư mục dự án
$ cd nestjs-cicd

# tạo Dockerfile
$ touch Dockerfile

Nội dung Dockerfile như sau:

###################
# BUILD FOR LOCAL DEVELOPMENT
###################

FROM node:18-alpine As development

# Create app directory
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
# Copying this first prevents re-running npm install on every code change.
COPY --chown=node:node package*.json ./

# Install app dependencies using the `npm ci` command instead of `npm install`
RUN npm ci

# Bundle app source
COPY --chown=node:node . .

# Use the node user from the image (instead of the root user)
USER node

###################
# BUILD FOR PRODUCTION
###################

FROM node:18-alpine As build

WORKDIR /usr/src/app

COPY --chown=node:node package*.json ./

# In order to run `npm run build` we need access to the Nest CLI which is a dev dependency. In the previous development stage we ran `npm ci` which installed all dependencies, so we can copy over the node_modules directory from the development image
COPY --chown=node:node --from=development /usr/src/app/node_modules ./node_modules

COPY --chown=node:node . .

# Run the build command which creates the production bundle
RUN npm run build

# Set NODE_ENV environment variable
ENV NODE_ENV production

# Running `npm ci` removes the existing node_modules directory and passing in --only=production ensures that only the production dependencies are installed. This ensures that the node_modules directory is as optimized as possible
RUN npm ci --only=production && npm cache clean --force

USER node

###################
# PRODUCTION
###################

FROM node:18-alpine As production

# Copy the bundled code from the build stage to the production image
COPY --chown=node:node --from=build /usr/src/app/node_modules ./node_modules
COPY --chown=node:node --from=build /usr/src/app/dist ./dist

# Start the server using the production build
CMD [ "node", "dist/main.js" ]

Chạy thử docker trên local

# build image
$ docker build -t nestjs-cicd .

# chạy image với port 3033 trên máy, sau khi chạy vào đường dẫn http://127.0.0.1:3033 để kiểm tra source đã chạy được hay chưa nhé
$ docker run -p3033:3000 nestjs-cicd

3. Tạo mới repository và push code lên

Vào đường dẫn https://github.com/new và tạo dự án mới với tên nestjs-cicd và follow theo hướng dẫn để push source code của bạn lên:

1kgfFQdQU_PRH7NozRJx-rRFZAs6eUlZz

4. Cài đặt GCP SDK trên local, deploy thử lên GCP từ local

Mọi người có thể follow Link này để cài đặt: https://cloud.google.com/sdk/docs/install

Các lệnh thao tác cơ bản trên GCP:

- Login: $ gcloud auth login
- Create Project: $ gcloud projects create <PROJECT_ID>
- Show config list: $ gcloud config configurations list
- Set Account: $ gcloud config set account ACCOUNT
- Set project: $ gcloud config set project PROJECT ID
- Set region: $ gcloud config set compute/region asia-east1
- Set zone: $ gcloud config set compute/zone asia-east1-c

Deploy thử lên GCP, vào thư mục dự án và chạy lệnh sau
- Deploy: $ gcloud run deploy

Sau khi cài đặt xong các bạn có thể vào Service URL để kiểm tra ứng dụng mình đã được deploy lên chưa:

5. Tạo Service Account GCP và set quyền

  1. Mở Google Cloud Console: Truy cập Google Cloud Console.
  2. Chọn Dự án:Chọn dự án trong góc trên cùng bên phải của trang web, hoặc tạo một dự án mới.
  3. Mở trang "IAM & Quản trị tài nguyên":Từ thanh điều hướng bên trái, chọn "IAM & Quản trị tài nguyên."
  4. Chọn "Tài khoản Dịch vụ":Trong thanh điều hướng bên trái, chọn "Tài khoản Dịch vụ."
  5. Chọn "Tạo Tài khoản Dịch vụ":Bấm nút "Tạo Tài khoản Dịch vụ."

Điền thông tin:

  • Tên tài khoản: Nhập một tên duy nhất cho tài khoản dịch vụ.
  • Mô tả: (Tùy chọn) Nhập mô tả cho tài khoản dịch vụ.

Chọn vai trò:

các bạn hãy bật các quyền yêu cầu khi deploy dưới đây:
https://cloud.google.com/run/docs/deploying-source-code#permissions_required_to_deploy

Tạo Khóa:

  • Chọn "Tạo Khóa."
  • Chọn loại khóa: JSON.
  • Nhấn "Tạo" để tải xuống khóa JSON.

Lưu khóa JSON:Lưu khóa JSON tải về nơi an toàn trên máy tính của bạn. Khóa này sẽ được sử dụng để xác thực tài khoản dịch vụ từ ứng dụng hoặc máy chủ của bạn.

6. Cài đặt Github action để tự động deploy

  1. Tạo một file mới trong thư mục .github/workflows trong dự án GitHub của bạn. Ví dụ, bạn có thể tạo file .github/workflows/deploy.yml.

Nội dung file deploy.yml

# This workflow will deploy source code on Cloud Run when a commit is pushed to the "main" branch
#
# Overview:
#
# 1. Authenticate to Google Cloud
# 2. Deploy it to Cloud Run
#
# To configure this workflow:
#
# 1. Ensure the required Google Cloud APIs are enabled:
#
#    Cloud Run            run.googleapis.com
#    Cloud Build          cloudbuild.googleapis.com
#    Artifact Registry    artifactregistry.googleapis.com
#
# 2. Create and configure Workload Identity Federation for GitHub (https://github.com/google-github-actions/auth#setting-up-workload-identity-federation)
#
# 3. Ensure the required IAM permissions are granted
#
#    Cloud Run
#      roles/run.admin
#      roles/iam.serviceAccountUser     (to act as the Cloud Run runtime service account)
#
#    Cloud Build
#      roles/cloudbuild.builds.editor
#
#    Cloud Storage
#      roles/storage.objectAdmin
#
#    Artifact Registry
#      roles/artifactregistry.admin     (project or repository level)
#
#    NOTE: You should always follow the principle of least privilege when assigning IAM roles
#
# 4. Create GitHub secrets for WIF_PROVIDER and WIF_SERVICE_ACCOUNT
#
# 5. Change the values for the SERVICE and REGION environment variables (below).
#
# For more support on how to run this workflow, please visit https://github.com/marketplace/actions/deploy-to-cloud-run
#
# Further reading:
#   Cloud Run runtime service account   - https://cloud.google.com/run/docs/securing/service-identity
#   Cloud Run IAM permissions           - https://cloud.google.com/run/docs/deploying-source-code#permissions_required_to_deploy
#   Cloud Run builds from source        - https://cloud.google.com/run/docs/deploying-source-code
#   Principle of least privilege        - https://cloud.google.com/blog/products/identity-security/dont-get-pwned-practicing-the-principle-of-least-privilege

name: Deploy to Cloud Run from Source

on:
  push:
    branches: [ "main" ]

env:
  PROJECT_ID: nodejs-cicd # TODO: update Google Cloud project id
  SERVICE: nestjs-cicd # TODO: update Cloud Run service name
  REGION: asia-east1 # TODO: update Cloud Run service region

jobs:
  deploy:
    # Add 'id-token' with the intended permissions for workload identity federation
    permissions:
      contents: 'read'
      id-token: 'write'

    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

     # - name: Google Auth
     #   id: auth
     #   uses: 'google-github-actions/auth@v0'
     #   with:
     #     workload_identity_provider: '${{ secrets.WIF_PROVIDER }}' # e.g. - projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider
     #     service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}' # e.g. - my-service-account@my-project.iam.gserviceaccount.com

      # NOTE: Alternative option - authentication via credentials json
      - name: Google Auth
        id: auth
        uses: 'google-github-actions/auth@v2'
        with:
          credentials_json: '${{ secrets.GCP_CREDENTIALS }}'

      - name: Deploy to Cloud Run
        id: deploy
        uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: ${{ env.SERVICE }}
          region: ${{ env.REGION }}
          # NOTE: If required, update to the appropriate source folder
          source: ./

      # If required, use the Cloud Run url output in later steps
      - name: Show Output
        run: echo ${{ steps.deploy.outputs.url }}

2. Thay thế các giá trị trong env thành giá trị của bạn

PROJECT_ID: nodejs-cicd # TODO: update Google Cloud project id
SERVICE: nestjs-cicd # TODO: update Cloud Run service name
REGION: asia-east1 # TODO: update Cloud Run service region

3. Tạo 1 secret key GCP_CREDENTIALS và lưu giá trị JSON lúc tạo service account vào. (vào setting dự án > Secrets and variables > Actions > Secrets)

7. Testing

Sau khi cài đặt xong các bước, các bạn tạo pull request và merge vào nhánh `main`. Sau đó, vào phần action để xem kết quả deploy nhé.

Nếu thành công sẽ như thế này này:

vào đường dẫn Cloud Run để kiểm tra, nếu thấy "Hello Would!" là bạn đã deploy thành công rồi đó:

IV. Tổng Kết

LTV siêu lười: đấy, tôi đã chỉ các bạn tận tình như vậy rồi, nếu có vướng mắc gì nữa thì liên hệ đến đường dây nóng của... anh nobito ở VNLAB nhé, anh ta sẽ giải đáp thắc mắc của bạn thay tôi. Tôi bận cày anime rồi, hehehe.

nobito: A đu  !!! tuy lười nhưng anh đá bóng cũng giỏi nhỉ. Nhưng cũng rất cảm ơn phần giải thích chi tiết của anh về CI/CD kết hợp giữa Github Action và Cloud Run vừa rồi.

Như các bạn thấy. Với Git Actions, LTV siêu lười của chúng không chỉ tiết kiệm thời gian mà còn đạt được sự đồng bộ và độ tin cậy trong quá trình phát triển ứng dụng. Việc này không chỉ giúp anh ta giữ được tâm trạng thoải mái và thư giãn, mà còn mở ra khả năng tận hưởng những khoảnh khắc giải trí như xem anime mà không phải lo lắng về việc triển khai.

Cuối cùng, thông điệp của chúng ta là: CI/CD không chỉ là công cụ hữu ích, mà còn là một nguồn động viên thú vị và hữu ích cho những người có tinh thần "lười biếng" như anh LTV Siêu Lười. Hãy để công nghệ làm cho cuộc sống phát triển một cách tự nhiên và thoải mái, và đừng quên thưởng thức những khoảnh khắc giải trí trong hành trình của bạn! Xin chào và hẹn gặp lại !

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