7

2023 部署 Next.js 至 Google Cloud Run (混搭 Cloud Build, Gitlab CI, Secret Manag...

 1 year ago
source link: https://andyyou.github.io/2023/01/12/deploy-nextjs-to-cloud-run-with-gitlab-ci/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2023 部署 Next.js 至 Google Cloud Run (混搭 Cloud Build, Gitlab CI, Secret Manager)

本文的核心為將 Next.js 部署至 Google Cloud Run,CI 的部分會使用 Gitlab CI,主要會依據採用不同服務分成兩種流程:

  1. 在 Gitlab CI 建置 Docker Image 暫存到 Gitlab 後續部署到 Cloud Run
  2. Gitlab CI 搭配 Cloud Build 部署到 Cloud Run

本文希望可以儘量補充其他教學遺失的環節,幫助您可以自行選擇想用的服務調整流程。

[toc]

使用 Gitlab CI 建置 Docker Image 暫存到 Gitlab 後續部署至 Cloud Run

1. 建立 Next.js 專案

$ npx create-next-app [PROJECT_NAME]

# 測試專案
$ cd [PROJECT_NAME]
$ npm run dev

2. Docker 化 Next.js 專案

在 Next.js 專案根目錄下

$ touch Dockerfile

下面 Dockerfile 是參考官方範例修改而成的。隨著時間演進可能有所不同,建議您可以也可以一起參考官方的範例調整。

另外,為了減少正式環境建置檔案大小複製了 /app/.next/standalonenext.config.js 須增加設定 output: "standalone"

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: "standalone",
};

module.exports = nextConfig;

Dockerfile 如下,採用複數 Stage / Layer 的理由是為了縮小最後 Image 的大小:

# 安裝相依套件
FROM node:16-alpine AS deps
# 查看 https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine
# 理解為何需要 libc6-compat
RUN apk add --no-cache libc6-compat
WORKDIR /app

# 根據偏好的套件管理工具安裝套件,您也可以使用 yarn
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# 僅在需要的時候重新編譯原始碼
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js 收集一般使用資料 - Telemetry
# 查看更多資料: https://nextjs.org/telemetry
# 如果您需要關閉資料收集可以解開註解
ENV NEXT_TELEMETRY_DISABLED 1

RUN npm run build

# 正式環境 Image, 複製全部檔案並執行 Next
FROM node:16-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
# 如果您需要關閉資料收集可以解開註解
ENV NEXT_TELEMETRY_DISABLED 1

# 您可以變更成您希望的群組和使用者名稱
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# 自動利用輸出軌跡來減小圖像大小
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]

完成 Dockerfile 之後可以執行 docker builddocker run 進行本地測試

$ docker build -t andyyou/nextjs .
$ docker run -p 3000:3000 andyyou/nextjs

現在您可以瀏覽 http://localhost:3000 它會顯示跟之前本地開發伺服器一樣的內容。
到此我們已經成功的 Docker 化一個 Next.js 應用程式。另外建議您可以閱讀 Nodejs 與 Docker

3. 設定 Google Cloud Service

這個 CI 流程大部分我們都在 Gitlab 處理,會用到 GCP 的部分只有 Cloud Run,但在發佈的過程我們會需要一組 Service Account 的金鑰。因此本節的設定:

  1. 建立 GCP 專案
  2. 啟用 Cloud Run API
  3. 建立授權一組 Service Account 並取得 JSON Key

FRBfDnT.png

08XlCB2.png

1cRbJC1.png

43LYFKG.png

bbGJeNU.png

BiJZYq0.png

Ytu49Bh.png

RjZpKtj.png

fnz4KRO.png

HQgFq5K.png

NT75oky.png

GCP 權限部分:

  • Editor - 允許 Service Account 啟用/停用 API 或從 Google Cloud SDK 安裝更新
  • Cloud Run Admin - 允許 Service Account 執行 Cloud Run 指令,例如 deploy 應用程式
  • Storage Admin - 允許 Service Account 推送 push Docker Image 到 Google Container Registry
  • Service Account User - 允許 Service Account 在 Cloud Run 中使用 Service Account 部署

備註:關於權限的部分可以依據您的需求調整,此處僅為展示。

4. 建立 .gitlab-ci.yml

在開始撰寫 .gitlab-ci.yml 之前,請先建立好我們的 Gitlab Repository ,我們還需要將 Cloud Run 的服務名稱,Google Project ID,Service Account 金鑰設到 Gitlab ,如此 CI 才能讀取相關資料。

登入 Gitlab > 專案 > 左邊選單 Settings > CI/CD > Variables 新增變數。下面變數您應該可以在 GCP Console 以及剛剛下載的 JSON 取得。GCP_CLOUD_RUN_SERVICE_NAME 您可以自訂就是這個專案服務的名稱。

如果要在其他分支使用變數記得取消 Protect variable

  • GCP_SERVICE_KEY

  • GCP_PROJECT_ID

  • GCP_CLOUD_RUN_SERVICE_NAME

接著,我們就可以使用 Gitlab CI 將 Next.js 專案部署到 Cloud Run 了

$ touch .gitlab-ci.yml

.gitlab-ci.yml 檔案如下:

image: docker

services:
- docker:dind

variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
CI_IMAGE: $CI_REGISTRY_IMAGE/$GCP_CLOUD_RUN_SERVICE_NAME:$CI_COMMIT_SHORT_SHA
DEPLOY_IMAGE: gcr.io/$GCP_PROJECT_ID/$GCP_CLOUD_RUN_SERVICE_NAME:$CI_COMMIT_SHORT_SHA

stages:
- build
- deploy

build:
stage: build
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_IMAGE .
- docker push $CI_IMAGE

deploy:
stage: deploy
image: google/cloud-sdk:slim
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker pull $CI_IMAGE
- docker tag $CI_IMAGE $DEPLOY_IMAGE
- echo $GCP_SERVICE_KEY > key.json
- gcloud auth activate-service-account --key-file key.json
- gcloud config set project $GCP_PROJECT_ID
- gcloud auth configure-docker
- docker push $DEPLOY_IMAGE
- gcloud run deploy $GCP_CLOUD_RUN_SERVICE_NAME --image $DEPLOY_IMAGE --project $GCP_PROJECT_ID --platform managed --region asia-east1 --allow-unauthenticated
when: manual
only:
- main

然後您就可以到 Gitlab > CI/CD > Pipeline 去發佈。

5. 補充 - 環境變數處理

假設您沒有把 .env 相關的檔案 commit 到 Git 檔案庫上。上面的範例遺漏了一個沒有處理的部分,Next.js 在建置時期會無法取得環境變數,導致前端部分無法使用,即便您後來在 Cloud Run 介面補上這些變數也無法讀取。

這裡提供一個簡單的方式即先將變數以 VAR1=a,VAR2=b,VAR3=c 的格式儲存到 Gitlab 的 CI/CD 環境變數。然後利用 --build-arg 參數將變數傳入 Docker 建置流程,在稍微變更一下 Dockerfile 來處理。下面為兩個設定檔案

這裡示範補上一個 NEXT_ENV 環境變數。

Dockerfile 的部分注意 RUN npm run build 之前補上 ARG

// ...

FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1

ARG NEXT_ENV
RUN echo $NEXT_ENV | tr "," "\n" > .env.production
RUN npm run build

// ...

.gitlab-ci.yml 則在 build 階段補上 --build-arg

build:
stage: build
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_IMAGE . --build-arg "NEXT_ENV=$NEXT_ENV"
- docker push $CI_IMAGE

進階 - 使用 Gitlab CI 搭配 Google Cloud Build 部署至 Cloud Run

開始前概略介紹

  • Cloud Run 是一個託管計算平台,提供自動擴展無狀態,無伺服器的容器
  • Cloud Build 是一個服務,可以在 Google Cloud Platform 執行編譯建置的任務
  • Gitlab CI 是 Gitlab 服務的一部分,可以在開發者將程式碼推送到檔案庫時建置和測試軟體

本節和上一節的差異為這次我們不在 Gitlab 編譯 Docker Image 而是將任務直接交給 Cloud Build 後續的部署也是一樣都由 Cloud Build 處理。以及我們會使用 Secret Manager 來處理正式環境的環境變數。

1. 設定 Google Cloud Service

  • 如果您還沒有 GCP 專案請先建立專案,或參考上面章節。
  • 啟用 Cloud Build
  • 啟用 Cloud Run API
  • 啟用 Secret Manager
  • 登入您的 GCP 切換至 Cloud Build > Settings,在 Service account permissions 下面確認 Cloud Run & Service Accounts & Secret Manager Secret AccessorENABLED
  • 參考上面建立 Service Accout 的步驟不過這次我們需要的權限是 Cloud Build Service AgentSecret Manager Secret Accessor ,一樣取得 JSON 檔案。您也可以直接至 Google IAM 去調整。
  • 登入 GCP > Secret Manager > Create Secret > 使用 VAR1=a,VAR2=b,VAR3=c 格式建立您的環境變數。

JklRgmT.png

OYDzSfq.png

2. 建立或使用既有 Next.js 專案

您可以參考上面章節建立專案或使用既有的專案。除此之外我們還需要

3. 設定 Gitlab CI/CD, Cloud Build 與 Docker

從上概覽,整個流程的順序會是我們靠 .gitlab-ci.yml 使用 gcloud builds submitcloudbuild.yaml 腳本和專案本身一併交給 Cloud Build。然後 Cloud Build 依據 cloudbuild.yaml

  • 讀取 Secret Manager 的資料
  • 建置 Docker Image
  • 推送 Docker Image 到 Registry
  • 使用 gcloud 指令部署到 Cloud Run

第一步我們先搞定 Gitlab CI,一樣到 Gitlab 專案下左邊選單 Settings > CI/CD > Variables 設定環境變數

  • GCP_SERVICE_KEY
  • GCP_PROJECT_ID
$ touch .gitlab-ci.yml

.gitlab-ci.yml 檔案如下

image: google/cloud-sdk:slim

stages:
- deploy

deploy:
stage: deploy
script:
- echo $GCP_SERVICE_KEY > key.json
- gcloud auth activate-service-account --key-file key.json
- gcloud config set project $GCP_PROJECT_ID
- gcloud builds submit . --config=cloudbuild.yaml
when: manual
only:
- main

注意到這裡我們使用 Google 的 Image 支援 gcloud 相關指令。如果您希望在 Gitlab 這邊也增加 testbuild 階段可以自行增加。我們這裡單純演示使用 Cloud Build 的部分。

4. 建立 cloudbuild.yaml

接著我們新增 cloudbuild.yaml 檔案如下。注意 Secret Manager 的使用。您可以閱讀一下官方文件,有個重點 -c 後面的指令是不可以拆開的。

steps:
- name: "gcr.io/cloud-builders/docker"
entrypoint: "bash"
args:
[
"-c",
"docker build -t gcr.io/$PROJECT_ID/nextjs . --build-arg NEXT_ENV=$$NEXT_ENV",
]
secretEnv: ["NEXT_ENV"]
- name: "gcr.io/cloud-builders/docker"
args: ["push", "gcr.io/$PROJECT_ID/nextjs"]
- name: "gcr.io/cloud-builders/gcloud"
args:
[
"run",
"deploy",
"nextjs",
"--image",
"gcr.io/$PROJECT_ID/nextjs",
"--platform",
"managed",
"--region",
"asia-east1",
"--allow-unauthenticated",
]
availableSecrets:
secretManager:
- versionName: projects/$PROJECT_ID/secrets/NEXT_ENV/versions/1
env: "NEXT_ENV"


5. 調整 Dockerfile

# 安裝相依套件
FROM node:16-alpine AS deps
# 查看 https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine
# 理解為何需要 libc6-compat
RUN apk add --no-cache libc6-compat
WORKDIR /app

# 根據偏好的套件管理工具安裝套件,您也可以使用 yarn
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# 僅在需要的時候重新編譯原始碼
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js 收集一般使用資料 - Telemetry
# 查看更多資料: https://nextjs.org/telemetry
# 如果您需要關閉資料收集可以解開註解
ENV NEXT_TELEMETRY_DISABLED 1

ARG NEXT_ENV
RUN echo $NEXT_ENV | tr "," "\n" > .env.production
RUN npm run build

# 正式環境 Image, 複製全部檔案並執行 Next
FROM node:16-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
# 如果您需要關閉資料收集可以解開註解
ENV NEXT_TELEMETRY_DISABLED 1

# 您可以變更成您希望的群組和使用者名稱
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# 自動利用輸出軌跡來減小圖像大小
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]

到此推送到 Gitlab 之後就可以部署了。本文的使用範例都是經過測試,希望可以提供一些幫助。

另外,兩個方式都是使用 --build-arg 的方式傳遞,如果您專精 Docker 有更好的作法歡迎告訴我 XD。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK