From 78a2f7da964e58a6568cb7266fc5d9243cdca9c2 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 24 Aug 2026 14:40:50 +0800 Subject: [PATCH] fix(ci): deploy client frontend on target node --- Dockerfile | 9 ++- Jenkinsfile | 86 ++++++++++++++++------------- docker-compose.test.yml | 2 + src/deployment/nginxConfig.test.mjs | 19 +++++++ 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/Dockerfile b/Dockerfile index a747a6c..77cd790 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ -# 构建 Vue 静态文件 -FROM node:22-alpine AS build +# 构建 Vue 静态文件。CI 通过构建参数切换到内网基础镜像, +# 本地开发仍可使用 Docker Hub 默认值。 +ARG NODE_IMAGE=node:22-alpine +ARG NGINX_IMAGE=nginx:1.27-alpine +FROM ${NODE_IMAGE} AS build WORKDIR /app @@ -21,7 +24,7 @@ ENV VITE_ADMIN_WEB_ORIGIN=${VITE_ADMIN_WEB_ORIGIN} RUN pnpm build # 运行阶段仅保留构建产物与 Nginx。 -FROM nginx:1.27-alpine +FROM ${NGINX_IMAGE} COPY nginx/default.conf.template /etc/nginx/templates/default.conf.template COPY --from=build /app/dist /usr/share/nginx/html diff --git a/Jenkinsfile b/Jenkinsfile index 04a0029..7ca2df2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,11 +1,14 @@ pipeline { - agent any + agent { + label "${env.JOB_BASE_NAME?.endsWith('-dev') ? 'java-dev' : 'java-test'}" + } environment { - DEVELOPMENT_SSH_TARGET = 'deploy@172.31.198.8' - DEVELOPMENT_SSH_KEY = '/var/jenkins_home/.ssh/dev_deploy' - DEVELOPMENT_SSH_KNOWN_HOSTS = '/var/jenkins_home/.ssh/known_hosts' - DEVELOPMENT_DEPLOY_DIR = '/opt/firstleg/frontend/client-web' + DEVELOPMENT_COMPOSE_FILE = 'docker-compose.development.yml' + REGISTRY_HOST = '172.31.198.7:10082' + REGISTRY_CREDENTIALS = 'gitea-registry-token' + NODE_BASE_IMAGE = '172.31.198.7:10082/java-project/node:22-alpine' + NGINX_BASE_IMAGE = '172.31.198.7:10082/java-project/nginx:1.27-alpine' } options { @@ -27,42 +30,53 @@ pipeline { stage('构建并部署测试前端') { when { expression { env.IS_DEVELOPMENT != 'true' } } steps { - sh ''' - set -eu - docker compose -f docker-compose.test.yml build --pull=false - docker compose -f docker-compose.test.yml up -d --remove-orphans - ''' + withCredentials([usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD')]) { + sh ''' + set -eu + export DOCKER_CONFIG="${WORKSPACE}@tmp/docker-auth" + rm -rf "$DOCKER_CONFIG" + mkdir -p "$DOCKER_CONFIG" + cleanup_registry() { + docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true + rm -rf "$DOCKER_CONFIG" + } + trap cleanup_registry EXIT HUP INT TERM + printf %s "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST" + docker compose -f docker-compose.test.yml build --pull=false + docker compose -f docker-compose.test.yml up -d --remove-orphans + ''' + } } } stage('构建并部署开发前端') { when { expression { env.IS_DEVELOPMENT == 'true' } } steps { - sh ''' - set -eu - image="client-web-development:${SOURCE_TAG}" - ssh_args="-i ${DEVELOPMENT_SSH_KEY} -o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=${DEVELOPMENT_SSH_KNOWN_HOSTS} -o ConnectTimeout=10" + withCredentials([usernamePassword(credentialsId: env.REGISTRY_CREDENTIALS, usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD')]) { + sh ''' + set -eu + export DOCKER_CONFIG="${WORKSPACE}@tmp/docker-auth" + rm -rf "$DOCKER_CONFIG" + mkdir -p "$DOCKER_CONFIG" + cleanup_registry() { + docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true + rm -rf "$DOCKER_CONFIG" + } + trap cleanup_registry EXIT HUP INT TERM + printf %s "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$REGISTRY_HOST" + image="client-web-development:${SOURCE_TAG}" - docker build \ - --build-arg VITE_ADMIN_WEB_ORIGIN=http://8.163.49.32:9001 \ - -t "$image" . - docker save "$image" | gzip -1 | ssh $ssh_args "$DEVELOPMENT_SSH_TARGET" 'gzip -dc | docker load' - - ssh $ssh_args "$DEVELOPMENT_SSH_TARGET" "install -d -m 750 '$DEVELOPMENT_DEPLOY_DIR'" - scp $ssh_args docker-compose.development.yml \ - "$DEVELOPMENT_SSH_TARGET:$DEVELOPMENT_DEPLOY_DIR/docker-compose.yml.tmp" - ssh $ssh_args "$DEVELOPMENT_SSH_TARGET" \ - "mv -f '$DEVELOPMENT_DEPLOY_DIR/docker-compose.yml.tmp' '$DEVELOPMENT_DEPLOY_DIR/docker-compose.yml'" - - ssh $ssh_args "$DEVELOPMENT_SSH_TARGET" sh -s -- "$DEVELOPMENT_DEPLOY_DIR" "$SOURCE_TAG" <<'REMOTE' - set -eu - cd "$1" - export IMAGE_TAG="$2" - docker compose -f docker-compose.yml up -d --remove-orphans --pull never - docker compose -f docker-compose.yml ps - docker compose -f docker-compose.yml exec -T client-web wget -qO- http://127.0.0.1/healthz -REMOTE - ''' + docker build \ + --build-arg NODE_IMAGE="$NODE_BASE_IMAGE" \ + --build-arg NGINX_IMAGE="$NGINX_BASE_IMAGE" \ + --build-arg VITE_ADMIN_WEB_ORIGIN=http://8.163.49.32:9001 \ + -t "$image" . + export IMAGE_TAG="$SOURCE_TAG" + docker compose -f "$DEVELOPMENT_COMPOSE_FILE" up -d --remove-orphans --pull never + docker compose -f "$DEVELOPMENT_COMPOSE_FILE" ps + docker compose -f "$DEVELOPMENT_COMPOSE_FILE" exec -T client-web wget -qO- http://127.0.0.1/healthz + ''' + } } } @@ -82,9 +96,7 @@ REMOTE failure { sh ''' if [ "${IS_DEVELOPMENT:-false}" = "true" ]; then - ssh -i "$DEVELOPMENT_SSH_KEY" -o BatchMode=yes -o StrictHostKeyChecking=yes \ - -o UserKnownHostsFile="$DEVELOPMENT_SSH_KNOWN_HOSTS" \ - "$DEVELOPMENT_SSH_TARGET" 'docker logs --tail=100 client-web-development || true' || true + docker logs --tail=100 client-web-development || true else docker compose -f docker-compose.test.yml logs --tail=100 || true fi diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 3404d87..d9e8b3c 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -5,6 +5,8 @@ services: dockerfile: Dockerfile args: VITE_ADMIN_WEB_ORIGIN: "${VITE_ADMIN_WEB_ORIGIN:-http://8.163.30.128:9001}" + NODE_IMAGE: "${NODE_BASE_IMAGE:-node:22-alpine}" + NGINX_IMAGE: "${NGINX_BASE_IMAGE:-nginx:1.27-alpine}" image: client-web-test:local container_name: client-web-test restart: unless-stopped diff --git a/src/deployment/nginxConfig.test.mjs b/src/deployment/nginxConfig.test.mjs index 62259a8..f22de41 100644 --- a/src/deployment/nginxConfig.test.mjs +++ b/src/deployment/nginxConfig.test.mjs @@ -3,7 +3,26 @@ import { readFileSync } from 'node:fs' import test from 'node:test' const config = readFileSync(new URL('../../nginx/default.conf.template', import.meta.url), 'utf8') +const jenkinsfile = readFileSync(new URL('../../Jenkinsfile', import.meta.url), 'utf8') +const dockerfile = readFileSync(new URL('../../Dockerfile', import.meta.url), 'utf8') test('allows qualification uploads with multipart overhead through the API proxy', () => { assert.match(config, /location \/api\/ \{[\s\S]*?client_max_body_size 12m;/) }) + +test('runs each deployment on its target node without controller-local SSH files', () => { + assert.match(jenkinsfile, /env\.JOB_BASE_NAME\?\.endsWith\('-dev'\) \? 'java-dev' : 'java-test'/) + assert.match(jenkinsfile, /docker compose -f "\$DEVELOPMENT_COMPOSE_FILE" up -d/) + assert.doesNotMatch(jenkinsfile, /\/var\/jenkins_home\/\.ssh|docker save|\bssh\b/) +}) + +test('uses authenticated internal base images in CI', () => { + assert.match(dockerfile, /ARG NODE_IMAGE=node:22-alpine/) + assert.match(dockerfile, /ARG NGINX_IMAGE=nginx:1\.27-alpine/) + assert.match(dockerfile, /FROM \$\{NODE_IMAGE\} AS build/) + assert.match(dockerfile, /FROM \$\{NGINX_IMAGE\}/) + assert.match(jenkinsfile, /NODE_BASE_IMAGE = '172\.31\.198\.7:10082\/java-project\/node:22-alpine'/) + assert.match(jenkinsfile, /NGINX_BASE_IMAGE = '172\.31\.198\.7:10082\/java-project\/nginx:1\.27-alpine'/) + assert.match(jenkinsfile, /withCredentials\(\[usernamePassword\(/) + assert.match(jenkinsfile, /export DOCKER_CONFIG="\$\{WORKSPACE\}@tmp\/docker-auth"/) +})