Kubernetes 部署 Next.js 项目

15次阅读
没有评论

共计 6108 个字符,预计需要花费 16 分钟才能阅读完成。

在 [[Kubernetes]] 上部署 [[Next.js]] 项目是一种常见的生产级实践。与直接部署到 [[Vercel]] 不同,Kubernetes 方案让你对网络、证书、扩缩容和运行时有完全的控制权,适合对安全合规、私有化部署或成本管控有要求的场景。

本文将按照「构建镜像 → 编写 Manifest → 部署上线 → 运维优化」的顺序,完整介绍将 Next.js 应用部署到 Kubernetes 集群的全过程。

前置条件

在开始之前,需要准备以下环境和工具

  • 一个可运行的 Kubernetes 集群([[minikube]]、[[K3s]]、[[Kind]] 或云厂商托管集群均可)
  • 安装 [[Docker]] 并可正常构建镜像
  • 安装 kubectl 并已配置集群访问
  • 一个已完成开发的 Next.js 项目
  • 一个容器镜像仓库([[Docker Hub]]、[[Harbor]]、GitHub Container Registry 等)

开启 Standalone 输出模式

Next.js 提供了 standalone 输出模式,它会在构建时自动追踪项目实际使用的依赖,生成一个精简的独立目录,只包含运行所需的最小文件集合。这对容器化部署至关重要,可以将最终镜像体积从 1GB 以上压缩到 150-250MB 左右。

next.config.js 中启用

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

module.exports = nextConfig;

启用后执行 npm run build,Next.js 会在 .next/standalone 目录下生成一个包含 server.js 和精简 node_modules 的独立运行包。需要注意,静态资源和 public 目录不会自动复制到 standalone 输出中,需要在 Dockerfile 里手动处理。

编写 Dockerfile

采用多阶段构建(Multi-stage Build)可以将构建依赖与运行时环境分离,进一步减小镜像体积并提升安全性。

# 阶段 1:安装依赖
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

# 阶段 2:构建应用
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

# 阶段 3:生产运行时
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 复制 standalone 输出
COPY --from=builder /app/.next/standalone ./
# 复制静态资源
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

# 设置文件所有权
RUN chown -R nextjs:nodejs /app

USER nextjs

EXPOSE 3000

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

关键设计说明

  • deps 阶段:仅复制 package.json 和 lock 文件,利用 Docker 层缓存加速后续构建
  • builder 阶段:执行 npm run build,生成 standalone 输出
  • runner 阶段:仅包含运行时所需文件,使用非 root 用户运行以增强安全性
  • 使用 node:20-alpine 作为基础镜像,体积更小

创建 .dockerignore 文件避免将不必要的文件复制到构建上下文

node_modules
.next
.git
.env*.local
npm-debug.log*
.DS_Store
README.md

构建并测试镜像

# 构建镜像
docker build -t nextjs-app:latest .

# 本地测试
docker run -p 3000:3000 nextjs-app:latest

推送镜像到仓库

将构建好的镜像推送到 Kubernetes 集群可访问的容器仓库

# 以 Docker Hub 为例
docker tag nextjs-app:latest yourusername/nextjs-app:v1.0.0
docker push yourusername/nextjs-app:v1.0.0

如果使用私有仓库如 [[Harbor]],需要在 Kubernetes 中配置 imagePullSecrets。

编写 Kubernetes Manifest

Namespace

为应用创建独立的命名空间,便于资源隔离和管理

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: nextjs-app

ConfigMap

将非敏感的配置信息存放在 ConfigMap 中

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nextjs-config
  namespace: nextjs-app
data:
  NODE_ENV: "production"
  API_BASE_URL: "https://api.example.com"

Secret

敏感信息如数据库连接字符串和 API 密钥存放在 Secret 中。生产环境建议使用 [[HashiCorp Vault]] 或云厂商的密钥管理服务。

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: nextjs-secrets
  namespace: nextjs-app
type: Opaque
data:
  DATABASE_URL: <base64 编码的连接字符串>
  AUTH_SECRET: <base64 编码的密钥>

Deployment

Deployment 定义了应用的部署策略、副本数量和容器配置

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nextjs-app
  namespace: nextjs-app
  labels:
    app: nextjs-app
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: nextjs-app
  template:
    metadata:
      labels:
        app: nextjs-app
    spec:
      containers:
        - name: nextjs
          image: yourusername/nextjs-app:v1.0.0
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          envFrom:
            - configMapRef:
                name: nextjs-config
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: nextjs-secrets
                  key: DATABASE_URL
          livenessProbe:
            httpGet:
              path: /
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 10
            timeoutSeconds: 5
          readinessProbe:
            httpGet:
              path: /
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5

配置要点说明

  • replicas 设为 2 实现基本的高可用
  • RollingUpdate 策略确保更新时零停机
  • resources 的 requests 和 limits 避免资源争抢和 OOM
  • livenessProbe 检测应用是否存活,失败会触发重启
  • readinessProbe 检测应用是否就绪,未就绪的 Pod 不会接收流量

Service

Service 为 Pod 提供稳定的访问入口和负载均衡

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nextjs-service
  namespace: nextjs-app
spec:
  selector:
    app: nextjs-app
  ports:
    - port: 80
      targetPort: 3000
      protocol: TCP
  type: ClusterIP

这里使用 ClusterIP 类型,配合 [[Kubernetes Ingress]] 对外暴露服务。如果不使用 Ingress,也可以改为 NodePort 或 LoadBalancer 类型。

Ingress

通过 Ingress 配置域名和 TLS 证书,将外部流量路由到 Service

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nextjs-ingress
  namespace: nextjs-app
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: nextjs-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nextjs-service
                port:
                  number: 80

这里通过 [[cert-manager]] 自动申请和续期 [[Let’s Encrypt]] 证书。

部署流程

按照依赖顺序依次执行

# 创建 Namespace
kubectl apply -f namespace.yaml

# 部署配置和密钥
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml

# 部署应用
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml

验证部署状态

# 查看 Pod 状态
kubectl get pods -n nextjs-app

# 查看 Pod 日志
kubectl logs -f <pod-name> -n nextjs-app

# 查看 Service 和 Ingress
kubectl get svc,ingress -n nextjs-app

配置自动扩缩容

使用 HorizontalPodAutoscaler(HPA)根据 CPU 或内存使用率自动调整副本数

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nextjs-hpa
  namespace: nextjs-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nextjs-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

HPA 需要集群中安装 [[Metrics Server]] 才能正常工作。

使用 Helm 管理部署

当配置文件数量增多时,可以使用 [[Helm]] 将所有 Manifest 打包为 Chart,通过 values 文件管理不同环境的配置差异

# 创建 Helm Chart
helm create nextjs-chart

# 安装到集群
helm install nextjs-app ./nextjs-chart \
  --namespace nextjs-app \
  --set image.repository=yourusername/nextjs-app \
  --set image.tag=v1.0.0

# 升级
helm upgrade nextjs-app ./nextjs-chart \
  --set image.tag=v1.1.0

常见问题排查

以下是部署过程中常见的问题和排查思路

  • Pod 处于 CrashLoopBackOff 状态:使用 kubectl logs 查看错误日志,常见原因是环境变量缺失或数据库连接失败
  • 镜像拉取失败(ImagePullBackOff):检查镜像名称和标签是否正确,私有仓库需要配置 imagePullSecrets
  • 健康检查失败:确认应用启动完成所需的时间,适当增大 initialDelaySeconds
  • 静态资源 404:确认 Dockerfile 中已正确复制 .next/staticpublic 目录
  • 内存溢出(OOMKilled):调高 resources.limits.memory,或排查应用是否存在内存泄漏
  • 多副本缓存不一致:Next.js 默认使用本地文件系统缓存,多副本部署时需要引入 [[Redis]] 等分布式缓存方案

与其他部署方式的对比

方式 适合场景 特点
[[Vercel]] 个人项目、快速上线 零配置,与 Next.js 深度集成
[[Docker Compose]] 单服务器部署 简单易用,适合小规模应用
Kubernetes 生产级、企业级部署 自动扩缩容、滚动更新、高可用
静态导出 纯静态站点 不支持 SSR 和 API 路由

总结

在 Kubernetes 上部署 Next.js 项目的核心流程可以概括为四步:启用 standalone 输出减小构建产物体积,使用多阶段 Docker 构建生成精简镜像,编写 Deployment、Service、Ingress 等 Manifest 描述部署拓扑,最后通过 HPA 和健康检查保障运行时的稳定性和弹性。虽然比 Vercel 一键部署复杂,但换来了对基础设施的完全控制,适合有定制化需求的生产环境。

相关链接

正文完
 0
评论(没有评论)