- Đăng vào
Sử dụng GitHub Actions để xây dựng CI cho một dự án Nextjs
Gần đây mình có làm việc nhiều liên quan đến GitHub Actions trong việc xây dựng CI/CD cho nhiều dự án sử dụng các ngôn ngữ: C/C++, Java, JavaScripts/Typescripts, ... Hôm nay tranh giới thiệu một ví dụ đơn giản trong việc xây dựng một quy trình CI dành cho một dự án frontend dùng Nextjs và có sử dụng Helm Chart để deploy trên Kubernetes.
Giới thiệu
GitHub Actions
GitHub Actions là một công cụ CI/CD mạnh mẽ tích hợp sẵn trên GitHub, cho phép bạn tự động hóa toàn bộ quy trình phát triển phần mềm từ xây dựng, kiểm thử, đến triển khai. Nó giúp bạn tạo ra các "workflow" (quy trình làm việc) để tự động thực hiện các tác vụ khi có các sự kiện cụ thể xảy trên repo chứa mã nguồn của bạn.
Với GitHub Actions, bạn không cần phải đăng ký dịch vụ CI/CD riêng biệt, không cần cài đặt và quản lý dịch vụ của riêng mình, và không cần thiết lập webhook và mã thông báo truy cập. Tất cả đều được tích hợp sẵn trong GitHub, giúp bạn tập trung vào việc phát triển mã nguồn mà không phải lo lắng về việc triển khai và kiểm tra.
Nextjs
Next.js là một framework mã nguồn mở được xây dựng trên nền tảng của React. Điểm đặc biệt của Next.js là khả năng tạo ra các trang web tĩnh (static) với tốc độ siêu nhanh và thân thiện với người dùng. Nó cũng cho phép bạn xây dựng các ứng dụng web React một cách hiệu quả. Next.js ra đời vào năm 2016 và hiện thuộc sở hữu của Vercel (trước đây là Zeit). Từ đó, Next.js đã trở thành một lựa chọn phổ biến trong cộng đồng phát triển web.
Trong bài viết này mình sẽ giới thiệu ví dụ sử dụng CI workflows đơn giản cho một project frontend dùng Nextjs:
- Chạy CI build project
- Scan secret dùng Gitleak
- Scan docker image bằng Trivy
- Khi PR được merge thì build và đẩy docker image lên ghcr.io
![](/_next/image?url=%2Fstatic%2Fimages%2Fgithub-action-ci-workflows-frontend-app%2Fci-workflows.png&w=2048&q=75)
CI Workflow
Khi nhắc đến việc CI thông thường sẽ diễn ra khi Developer mở một Pull Request (PR), nó sẽ thực hiện các công việc như: build, unit test, ...
Một khi những thay đổi source code trong PR vượt qua dược các công việc trên rồi thì chúng ta mới yêu cầu các đồng nghiệp khác kiểm tra, đánh giá PR đó. Khi được họ đồng ý thì code mới của chúng ta sẽ được thêm vào nhánh chính và coi như xong 1 task.
Hiển nhiên, sau khi được PR được merge thì sẽ có những bài kiểm tra khác, tùy theo project cũng như mức độ phức tạp của dự án. Dưới đây mình chỉ đề cập đến một vài bước cơ bản khi xây dựng một CI cho dự dán frontend dùng NextJS. Các bước cở bản được thể hiện trong file YAML dưới đây:
name: CI Workflows
on:
pull_request:
types:
- opened
- reopened
- synchronize
branches:
- main
paths-ignore:
- 'charts/**'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
jobs:
ci-build:
...
sonarqube:
...
gitleaks:
...
ci-docker:
...
ci-trivy:
...
Trong đó:
on
: phần này đề cập đến khi nào Workflows này sẽ chạy. Trong ví dụ này, CI Workflows sẽ chạy khi có PR được mở, PR được mở lại hoặc khi có thêm bất kì commit mới nào trong PR.concurrency
: phần này nhằm đảm bảo trong PR này mỗi lần chỉ có thể có duy nhât 1 Workflow chạy. Việc này chủ yếu nhằm tránh lãng phí tài nguyên. Chẳng hạn khi 2 commit trong PR cách nhau quá ngắn, CI của commit trước chạy chưa xong thì CI của commit sau đó buộc phải chờ.jobs
: đây chính là nội dung chính của một Workflow, các quy trình, công việc cần thực hiện sẽ được khai báo ở đây.
Sử dụng GitHub Actions cho dự án dùng Nextjs
...
jobs:
ci-build:
name: Build and test
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
cache-dependency-path: yarn.lock
- name: Install dependencies
run: yarn install --immutable
# - name: Run lint
# run: yarn lint
- name: Run build
run: yarn build
# - name: Run test
# run: npm test
sonarqube:
...
gitleaks:
...
ci-docker:
...
ci-trivy:
...
Một số lưu ý:
runs-on
: phần này khai báo runner sẽ thực hiện công việc này. Thông thường sẽ có 2 loại runner: một loại runner do GitHub cung cấp, loại còn lại là runner do người dùng tự cài đặt và thiết lập (self-hosted runner). Với mỗi tài khoản GitHub cho bạn sử dụng 2000 phút chạy miễn phí mỗi tháng với điều kiện là repository để ở dạng công khai (public). Chúng ta có thể sử dụng thời lượng đó để phục vụ học tập nghiên cứu hoặc chạy các dự án nhỏ.steps
: phần này khai báo chi tiết các công việc sẽ chạy. Thông thường sẽ luôn có bướcactions/checkout@v4
để clone code từ GitHub về runner. Ở ví dụ trên thì sau bước checkout sẽ cài đặt Nodejs do project này dùng Nextjs, rồi sẽ cài các thư viện (dependencies). Cuối cùng là các bước chạy cơ bản như lint, build và unit test.
Sử dụng SonarCloud trên GitHub Actions
SonarQube là một nền tảng mã nguồn mở được phát triển bởi SonarSource để kiểm soát chất lượng mã liên tục. Nó hỗ trợ hơn 30 ngôn ngữ lập trình chính với nhiều plugin khác nhau. SonarQube cloud sẽ cho dùng miễn phí khi mà chúng ta để code trên GitHub ở dạng public. Việc đăng ký khá dễ dàng, chỉ cần đăng nhập sonarcloud.io bằng tài khoản GitHub của bạn. Sau khi đăng nhập bạn tạo một Token và lưu nó trên phần quản lý Secrets của GitHub với tên SONAR_CLOUD_TOKEN
chẳng hạn.
Dưới đây là ví dự về SonarCloud với GitHub Actions
jobs:
ci-build:
...
sonarqube:
name: Run SonarQube scan
runs-on: ubuntu-22.04
needs: ci-build
steps:
- uses: actions/checkout@v4
with:
# Disabling shallow clones is recommended for improving the relevancy of reporting
fetch-depth: 0
- name: SonarQube Scan
uses: sonarsource/sonarcloud-github-action@v2.3.0
with:
projectBaseDir: .
args: >
-Dsonar.projectKey=tailwind-nextjs-starter-blog-i18n
-Dsonar.organization=${{ github.repository_owner }}
-Dsonar.verbose=true
env:
SONAR_TOKEN: ${{ secrets.SONAR_CLOUD_TOKEN }}
SONAR_HOST_URL: https://sonarcloud.io
Quét thông tin nhạy cảm với gitleaks
Gitleaks là một công cụ mã nguồn mở được thiết kế để phát hiện và ngăn chặn các bí mật mã hóa như mật khẩu, khóa API, và token trong các kho lưu trữ Git1. Đây là một công cụ phân tích bảo mật tĩnh (SAST) giúp phát hiện các bí mật được mã hóa cứng trong mã nguồn của bạn.
...
jobs:
ci-build:
...
sonarqube:
...
gitleaks:
name: Run secrets scan
runs-on: ubuntu-22.04
needs: ci-build
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
id: gitleaks
uses: DariuszPorowski/github-action-gitleaks@v2
with:
fail: false
- name: Post PR comment
uses: actions/github-script@v6
if: ${{ steps.gitleaks.outputs.exitcode == 1 && github.event_name == 'pull_request' }}
with:
github-token: ${{ github.token }}
script: |
const { GITLEAKS_RESULT, GITLEAKS_OUTPUT } = process.env
const output = `### ${GITLEAKS_RESULT}
<details><summary>Log output</summary>
${GITLEAKS_OUTPUT}
</details>
`
github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: output
})
env:
GITLEAKS_RESULT: ${{ steps.gitleaks.outputs.result }}
GITLEAKS_OUTPUT: ${{ steps.gitleaks.outputs.output }}
Build docker trên GitHub Actions
Trong dự án Nextjs này có sử dụng Docker để triển khai ứng dụng lên Kuberentes, nên bước CI nhất định phải có phần build Docker image. Thông thường, bước CI chúng ta chỉ build chứ không đẩy lên Artifactory liền.
...
jobs:
ci-build:
...
sonarqube:
...
gitleaks:
...
ci-docker:
name: Build Docker image
needs: ci-build
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=docker,dest=frontend-image.tar
# load: true
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: frontend-image
path: frontend-image.tar
Ở bước cuối của ci-docker
mình có đẩy image lên GitHub Artifact để dùng cho các bước CI khác, chẳng hạn như quét Docker image bằng Trivy dưới đây.
Chạy Trivy trên GitHub Actions
Trivy là một công cụ mã nguồn mở mạnh mẽ được thiết kế để quét các hình ảnh Docker nhằm phát hiện các lỗ hổng bảo mật. Sau khi docker build thành công thì chúng ta sẽ quét image đó xem có gặp lỗ hổng nào nghiêm trọng hay không bằng công cụ TriVy.
...
jobs:
ci-build:
...
sonarqube:
...
gitleaks:
...
ci-docker:
...
ci-trivy:
name: Trivy scan docker image
needs: ci-docker
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download frontend image to scan
uses: actions/download-artifact@v4
with:
name: frontend-image
- name: Run Trivy vulnerability scanner in tarball mode
uses: aquasecurity/trivy-action@0.20.0
with:
input: frontend-image.tar
ignore-unfixed: true
severity: 'CRITICAL,HIGH'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
category: Trivy
Như vậy, mình đã tóm lược sở bộ cách sử dụng GitHub Actions để chạy CI của một dự án sử dụng Nextjs, chi tiết có thể tham khảo thêm tại https://github.com/codemauvn/tailwind-nextjs-starter-blog-i18n/blob/main/.github/workflows/ci.yaml
Chúc thành công, Anh Nguyễn