fix(ci): deploy client frontend on target node

This commit is contained in:
2026-08-24 14:40:50 +08:00
parent dca29d5eb0
commit 78a2f7da96
4 changed files with 76 additions and 40 deletions
+6 -3
View File
@@ -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
Vendored
+49 -37
View File
@@ -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
+2
View File
@@ -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
+19
View File
@@ -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"/)
})