Kubernetes部署NextJS时符号链接更新失败致应用崩溃求助
在Kubernetes中部署了NextJS应用,希望以开发模式运行,实现文件更新时的即时热重载,重点是更新config文件夹中的JSON配置文件。已创建Docker容器,复制必要文件但排除配置文件,转而通过Kubernetes ConfigMap作为卷挂载到容器内。运行环境为Windows WSL2上的minikube。
容器首次运行正常,但访问部分页面或修改ConfigMap中的值时,会出现类似如下错误(针对PlanModel.config.json等其他文件的错误逻辑类似):
> @ dev-base /usr/src/app > next dev ▲ Next.js 14.2.3 - Local: http://localhost:3000 ✓ Starting... Attention: Next.js now collects completely anonymous telemetry regarding usage. This information is used to shape Next.js' roadmap and prioritize features. You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:https://nextjs.org/telemetry ✓ Ready in 3s ○ Compiling /middleware ... ✓ Compiled /middleware in 2.3s (216 modules) ○ Compiling / ... Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest Why you should do it regularly: https://github.com/browserslist/update-db#readme ✓ Compiled / in 18.6s (4401 modules) cookie is: undefined GET / 200 in 19523ms ✓ Compiled in 1339ms (1847 modules) cookie is: undefined GET / 200 in 68ms cookie is: undefined GET / 200 in 46ms cookie is: undefined GET / 200 in 37ms ⨯ ./config/ai/..2024_09_30_15_42_23.830652425/actions.config.json Module build failed: Error: ENOENT: no such file or directory, open '/usr/src/app/config/ai/..2024_09_30_15_42_23.830652425/actions.config.json' Import trace for requested module: ./config/ai/..2024_09_30_15_42_23.830652425/actions.config.json ./lib/chat/actions.config.tsx ./lib/chat/actions.tsx ./app/(chat)/page.tsx
有时应用不会立即报错,但会停止热重载。两种情况下,进入容器shell手动验证过文件确实存在且内容正确。
Kubernetes为ConfigMap中的文件创建了多层符号链接,结构如下:
actions.config.json -> ..data/actions.config.json -> ..2024_09_30_15_42_23.830652425/actions.config.json
Next.js开发模式下对符号链接的追踪存在问题,根源在于webpack的文件监听机制限制。目前考虑两个解决方向:一是采用不创建符号链接的Kubernetes方案(仍需支持配置文件的轻松更新),二是修复Next.js的符号链接追踪问题。
目标是构建一个平台,允许用户通过修改JSON格式的配置文件自定义NextJS应用,这些配置文件被NextJS应用静态导入,决定应用的颜色、样式和行为。用户可通过编辑器完成两种操作:
- 定制应用的生产版本:部署到生产环境
- 定制应用的预览版本:编辑时自动刷新预览效果
为支持实时变更,选择直接以开发模式运行应用,而非基于现有应用创建变体版本。由于生产和预览版本都需要规模化运行,因此采用Kubernetes,并创建API动态生成两种版本的Kubernetes清单。
问题出在预览版本,以下是简化后的Kubernetes清单示例:
apiVersion: v1 kind: Namespace metadata: labels: app_id: myId app_subdomain: testapp name: myId --- apiVersion: v1 kind: ConfigMap metadata: name: app-environment namespace: myId data: # App configuration APP_ID: myId # Postgres database, MongoDB database, etc env variables --- apiVersion: v1 kind: ConfigMap metadata: labels: app_id: myId app_subdomain: testapp name: app-config namespace: myId data: theme.config.json: | CONTENT --- apiVersion: v1 kind: ConfigMap metadata: labels: app_id: myId app_subdomain: testapp name: app-config-ai namespace: myId data: actions.config.json: | CONTENT --- apiVersion: v1 kind: ConfigMap metadata: labels: app_id: myId app_subdomain: testapp name: app-config-components namespace: myId data: button-scroll-to-bottom.config.json: | CONTENT chat-history.config.json: | CONTENT chat-message-actions.config.json: | CONTENT --- apiVersion: v1 kind: ConfigMap metadata: labels: app_id: myId app_subdomain: testapp name: app-config-components-ui namespace: myId data: sheet.config.json: | CONTENT --- apiVersion: apps/v1 kind: Deployment metadata: labels: app_id: myId app_subdomain: testapp name: app namespace: myId spec: replicas: 1 selector: matchLabels: app_id: myId template: metadata: labels: app_id: myId app_subdomain: testapp spec: containers: - name: nginx image: my-nginx:latest imagePullPolicy: Always ports: - containerPort: 80 - name: app image: my-app-configurable:latest imagePullPolicy: Always envFrom: - configMapRef: name: app-environment ports: - containerPort: 3000 volumeMounts: # /usr/src/app/ is the root directory of the app in the container - name: app-config mountPath: /usr/src/app/config - name: app-config-ai mountPath: /usr/src/app/config/ai - name: app-config-components mountPath: /usr/src/app/config/components - name: app-config-components-ui mountPath: /usr/src/app/config/components/ui volumes: - name: app-config configMap: name: app-config - name: app-config-ai configMap: name: app-config-ai - name: app-config-components configMap: name: app-config-components - name: app-config-components-ui configMap: name: app-config-components-ui --- apiVersion: v1 kind: Service metadata: labels: app_id: myId app_subdomain: testapp name: nginx-service namespace: myId spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app_id: myId --- apiVersion: v1 kind: Service metadata: labels: app_id: myId app_subdomain: testapp name: app namespace: myId spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: app_id: myId --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: labels: app_id: myId app_subdomain: testapp name: nginx namespace: myId spec: rules: - host: testapp.zshape.ai http: paths: - backend: service: name: nginx-service port: number: 80 path: / pathType: Prefix
NextJS应用中导入JSON文件的示例:
import rawConfig from '@/config/components/empty-screen.config.json' interface ComponentConfig { [key: string]: any title?: string description?: string linkURL?: string linkLabel?: string } const { title, description, linkURL, linkLabel }: ComponentConfig = rawConfig export const emptyScreenConfig = { title, description, linkURL, linkLabel }
导出的emptyScreenConfig会被应用其他部分导入使用。
内容的提问来源于stack exchange,提问作者Fral

