问题解析

你提到 Jenkins 本身是通过 Docker 容器启动的,然后在 Jenkins 的 Docker 容器中再运行 Docker 命令来构建 Node.js 项目。这种情况下,确实会涉及嵌套 Docker 容器(即 Docker-in-Docker,简称 DinD)和嵌套挂载的问题。以下是详细分析和解决方案:


1. 嵌套 Docker 容器(DinD)的问题

  • 问题描述
    • Jenkins 容器本身运行在 Docker 中,而你在 Jenkins 容器内又运行 Docker 命令(如 docker build)。
    • 这会导致 Docker 守护进程在 Jenkins 容器内运行,形成嵌套的 Docker 环境。
  • 潜在问题
    • 资源消耗:嵌套 Docker 会增加资源占用(CPU、内存)。
    • 复杂性:需要额外配置 Docker-in-Docker(如共享 Docker 套接字或启动 DinD 服务)。
    • 安全性:嵌套 Docker 可能带来安全风险(如权限提升)。

2. 嵌套挂载的问题

  • 问题描述
    • 你想将 Jenkins 容器内构建的静态文件挂载到宿主机目录(如 ./dist)。
    • 由于 Jenkins 容器本身已经挂载了宿主机目录(通过 -v 参数),嵌套挂载可能导致路径混乱或权限问题。
  • 潜在问题
    • 路径冲突:宿主机目录和 Jenkins 容器内的目录可能不一致。
    • 权限问题:Jenkins 容器内的用户可能没有权限写入宿主机目录。

3. 解决方案

以下是几种可行的解决方案,避免嵌套 Docker 和嵌套挂载:

方案 1:直接在 Jenkins 容器中运行 Docker 命令(共享 Docker 套接字)

  • 原理
    • 将宿主机的 Docker 套接字(/var/run/docker.sock)挂载到 Jenkins 容器中。
    • Jenkins 容器内的 Docker 命令会直接调用宿主机的 Docker 守护进程,而不是嵌套的 Docker。
  • 步骤
    1. 启动 Jenkins 容器时,挂载 Docker 套接字:
      1
      2
      3
      4
      5
      6
      docker run -d \
      -p 8080:8080 \
      -p 50000:50000 \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v jenkins_home:/var/jenkins_home \
      jenkins/jenkins:lts
    2. 在 Jenkins Pipeline 中直接运行 Docker 命令:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      pipeline {
      agent any
      stages {
      stage('Build') {
      steps {
      sh '''
      docker build -t my-node-app .
      docker run --rm -v $(pwd)/dist:/app/dist my-node-app bash -c 'cp -r /app/build/* /app/dist'
      '''
      }
      }
      }
      }
  • 优点
    • 无需嵌套 Docker,性能更好。
    • 构建的静态文件可以直接挂载到宿主机目录。
  • 缺点
    • 安全性较低(Jenkins 容器可以访问宿主机的 Docker 守护进程)。

方案 2:使用 Jenkins 的 Docker 插件(推荐)

  • 原理
    • 使用 Jenkins 的 Docker Pipeline 插件,通过 Jenkins 节点(Agent)运行 Docker 命令。
    • Jenkins 节点可以是宿主机或其他容器,避免嵌套。
  • 步骤
    1. 安装 Jenkins 的 Docker Pipeline 插件。
    2. 配置 Jenkins 节点(Agent)为宿主机或其他容器。
    3. 在 Jenkinsfile 中使用 docker 步骤:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      pipeline {
      agent any
      stages {
      stage('Build') {
      steps {
      script {
      docker.image('node:18-alpine').inside('-v ${WORKSPACE}/dist:/app/dist') {
      sh '''
      npm install -g pnpm
      pnpm install
      pnpm run build:prod
      '''
      }
      }
      }
      }
      }
      }
  • 优点
    • 无需嵌套 Docker,逻辑清晰。
    • 插件管理方便,安全性较高。
  • 缺点
    • 需要配置 Jenkins 节点。

方案 3:在宿主机上运行 Docker 命令(通过 Jenkins 脚本)

  • 原理
    • Jenkins 容器通过 ssh 或直接调用宿主机命令来运行 Docker 命令。
  • 步骤
    1. 确保 Jenkins 容器可以访问宿主机(如通过 ssh 或共享脚本)。
    2. 在 Jenkins Pipeline 中调用宿主机命令:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      pipeline {
      agent any
      stages {
      stage('Build') {
      steps {
      sh '''
      ssh user@host "cd /path/to/project && docker build -t my-node-app . && docker run --rm -v /path/to/dist:/app/dist my-node-app bash -c 'cp -r /app/build/* /app/dist'"
      '''
      }
      }
      }
      }
  • 优点
    • 完全避免嵌套 Docker。
  • 缺点
    • 配置复杂,依赖宿主机环境。

4. 推荐方案

  • 推荐方案 1 或 方案 2
    • 如果 Jenkins 容器和宿主机在同一台机器上,推荐方案 1(共享 Docker 套接字),简单直接。
    • 如果需要更灵活的节点管理,推荐方案 2(Docker Pipeline 插件),安全性更高。
  • 避免嵌套 Docker
    • 嵌套 Docker 会增加复杂性和资源消耗,应尽量避免。

5. 静态文件挂载的注意事项

  • 挂载宿主机目录
    • 确保 Jenkins 容器或 Docker 命令有权限写入宿主机目录。
    • 使用绝对路径(如 /path/to/dist),避免路径混淆。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    docker.image('node:18-alpine').inside('-v /host/path/to/dist:/app/dist') {
    sh '''
    npm install -g pnpm
    pnpm install
    pnpm run build:prod
    '''
    }

总结

  1. 避免嵌套 Docker:通过共享 Docker 套接字或使用 Jenkins 插件实现。
  2. 静态文件挂载:使用绝对路径,确保权限正确。
  3. 推荐方案
    • 简单场景:共享 Docker 套接字(方案 1)。
    • 复杂场景:Docker Pipeline 插件(方案 2)。

通过以上方法,你可以高效地在 Jenkins Docker 容器中构建 Node.js 项目,并避免嵌套构建和挂载的问题。

目标

  1. 在 Jenkins 流水线中拉取 Git 仓库中的 Node.js 应用代码。
  2. 根据项目根目录中的 Dockerfile 构建镜像,并将构建的静态文件输出到工作目录 /app/dist
  3. 基于构建的静态文件启动一个 Web 服务器(如 Nginx)。

1. Dockerfile 示例

假设你的项目是一个 Vite + Vue 应用,Dockerfile 可以如下编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 第一阶段:构建静态文件
FROM node:18-alpine AS builder

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 pnpm-lock.yaml(或 package-lock.json)
COPY package.json pnpm-lock.yaml ./

# 安装 pnpm(如果项目使用 pnpm)
RUN npm install -g pnpm

# 安装项目依赖
RUN pnpm install

# 复制项目文件到工作目录
COPY . .

# 构建项目(使用 production 模式)
RUN pnpm run build:prod

# 第二阶段:使用 Nginx 作为 Web 服务器
FROM nginx:alpine

# 将构建的静态文件复制到 Nginx 的默认静态文件目录
COPY --from=builder /app/dist /usr/share/nginx/html

# 暴露 Nginx 默认端口
EXPOSE 80

# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

说明

  • 第一阶段:使用 node:18-alpine 构建项目,生成静态文件到 /app/dist
  • 第二阶段:使用 nginx:alpine 作为 Web 服务器,将静态文件复制到 /usr/share/nginx/html
  • 暴露端口:Nginx 默认监听 80 端口。

2. Jenkins Pipeline 脚本(Jenkinsfile)

以下是一个完整的 Jenkinsfile 示例,展示如何在 Jenkins 中拉取 Git 代码、构建 Docker 镜像并启动 Web 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
pipeline {
agent any

environment {
DOCKER_IMAGE = 'my-node-app'
DIST_DIR = 'dist'
}

stages {
stage('Checkout') {
steps {
// 拉取 Git 仓库代码
git branch: 'main', url: 'https://github.com/your-username/your-repo.git'
}
}

stage('Build Docker Image') {
steps {
script {
// 构建 Docker 镜像
docker.build(env.DOCKER_IMAGE, '.')
}
}
}

stage('Run Web Server') {
steps {
script {
// 运行容器并映射端口
docker.image(env.DOCKER_IMAGE).run('-p 8080:80')
}
}
}
}

post {
always {
// 清理工作空间(可选)
cleanWs()
}
}
}

说明

  1. Checkout 阶段

    • 使用 git 步骤拉取 Git 仓库代码。
    • 替换 https://github.com/your-username/your-repo.git 为你的仓库地址。
  2. Build Docker Image 阶段

    • 使用 docker.build 根据 Dockerfile 构建镜像。
    • 镜像名称为 my-node-app
  3. Run Web Server 阶段

    • 使用 docker.image(...).run 启动容器,并将宿主机的 8080 端口映射到容器的 80 端口。
    • 访问 http://localhost:8080 即可查看静态文件。
  4. post 阶段

    • cleanWs():清理工作空间(可选)。

3. 运行和验证

  1. 构建和运行

    • JenkinsfileDockerfile 提交到 Git 仓库。
    • 在 Jenkins 中配置流水线任务,选择 “Pipeline script from SCM” 并指定 Git 仓库地址。
    • 运行流水线任务,Jenkins 会自动拉取代码、构建镜像并启动 Web 服务器。
  2. 验证

    • 访问 http://localhost:8080(或 Jenkins 容器映射的端口),查看静态文件是否正确加载。

4. 注意事项

  1. Docker 权限

    • 确保 Jenkins 用户有权限运行 Docker 命令(参考前面的权限配置)。
  2. 端口映射

    • 如果宿主机 8080 端口被占用,可以修改 -p 8080:80 为其他端口(如 -p 8081:80)。
  3. 静态文件路径

    • 确保 vite.config.jsvue.config.js 中的 base 配置正确(如设置为 ./ 或空字符串),以避免资源路径问题。

通过以上配置,你可以在 Jenkins 中实现完整的 Node.js 应用构建和 Web 服务器启动流程。