ci: add x64 backend docker pipeline

This commit is contained in:
2026-08-21 22:12:09 +08:00
parent 294a7de61d
commit 613a278505
3 changed files with 308 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
**
!Dockerfile.backend
!admin-web/**
!backend/**
admin-web/node_modules/
admin-web/dist/
admin-web/.env*
backend/**/bin/
backend/**/obj/
backend/**/TestResults/
backend/MiaoJiZhang.Api/wwwroot/assets/
backend/*.log
+48
View File
@@ -0,0 +1,48 @@
# syntax=docker/dockerfile:1.7
FROM node:22-bookworm-slim AS admin-build
WORKDIR /src/admin-web
COPY admin-web/package.json admin-web/package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --no-audit --no-fund
COPY admin-web/ ./
RUN npm run build
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS api-build
WORKDIR /src
COPY backend/MiaoJiZhang.sln backend/
COPY backend/MiaoJiZhang.Api/MiaoJiZhang.Api.csproj backend/MiaoJiZhang.Api/
COPY backend/MiaoJiZhang.Domain/MiaoJiZhang.Domain.csproj backend/MiaoJiZhang.Domain/
COPY backend/MiaoJiZhang.Infrastructure/MiaoJiZhang.Infrastructure.csproj backend/MiaoJiZhang.Infrastructure/
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet restore backend/MiaoJiZhang.Api/MiaoJiZhang.Api.csproj
COPY backend/ backend/
ARG BUILD_VERSION=dev
RUN --mount=type=cache,target=/root/.nuget/packages \
dotnet publish backend/MiaoJiZhang.Api/MiaoJiZhang.Api.csproj \
--configuration Release \
--output /app/publish \
--no-restore \
--property:UseAppHost=false \
--property:InformationalVersion="$BUILD_VERSION" \
&& rm -rf /app/publish/wwwroot \
&& mkdir -p /app/publish/wwwroot
COPY --from=admin-build /src/admin-web/dist/ /app/publish/wwwroot/
FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS runtime
ARG BUILD_VERSION=dev
ARG VCS_REF=unknown
ARG BUILD_DATE=unknown
LABEL org.opencontainers.image.title="JiZhi Backend" \
org.opencontainers.image.description="JiZhi admin web and ASP.NET backend" \
org.opencontainers.image.version="$BUILD_VERSION" \
org.opencontainers.image.revision="$VCS_REF" \
org.opencontainers.image.created="$BUILD_DATE" \
org.opencontainers.image.source="https://gitea.nxsir.cn/nanxun/jizhi"
WORKDIR /app
COPY --from=api-build --chown=$APP_UID:$APP_UID /app/publish/ ./
ENV ASPNETCORE_HTTP_PORTS=8080 \
DOTNET_EnableDiagnostics=0
USER $APP_UID
EXPOSE 8080
ENTRYPOINT ["dotnet", "MiaoJiZhang.Api.dll"]
+247
View File
@@ -0,0 +1,247 @@
pipeline {
agent {
node {
label '构建机1'
customWorkspace '/home/nanxunai/goujian/workspace/jizhang-backend-docker'
}
}
options {
timestamps()
disableConcurrentBuilds()
skipDefaultCheckout(true)
timeout(time: 120, unit: 'MINUTES')
buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '20'))
}
environment {
PATH = '/usr/local/bin:/usr/bin:/bin'
HTTP_PROXY = 'http://192.168.5.200:7890'
HTTPS_PROXY = 'http://192.168.5.200:7890'
http_proxy = 'http://192.168.5.200:7890'
https_proxy = 'http://192.168.5.200:7890'
NO_PROXY = '127.0.0.1,localhost,192.168.5.8,192.168.5.100,.nxsir.cn'
no_proxy = '127.0.0.1,localhost,192.168.5.8,192.168.5.100,.nxsir.cn'
OPENLIST_CREDENTIALS = 'openlist_key'
OPENLIST_BASE_URL = 'https://openlist.nxsir.cn'
OPENLIST_REMOTE_DIR = '/yidongpan/构建产物/jizhang/backend-docker/linux-amd64'
}
stages {
stage('Checkout') {
steps {
deleteDir()
checkout scm
}
}
stage('Metadata And Preflight') {
steps {
script {
env.SHORT_SHA = sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim()
env.FULL_SHA = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
env.BUILD_DATE = sh(script: 'date -u +%Y-%m-%dT%H:%M:%SZ', returnStdout: true).trim()
env.BACKEND_VERSION = sh(script: 'date -u +%Y%m%d-%H%M', returnStdout: true).trim()
env.IMMUTABLE_TAG = "${env.BACKEND_VERSION}-b${env.BUILD_NUMBER}-${env.SHORT_SHA}"
env.IMAGE_REF = "jizhi-backend:${env.IMMUTABLE_TAG}"
env.ARCHIVE_BASENAME = "JiZhi-Backend-${env.IMMUTABLE_TAG}-linux-amd64.tar.gz"
env.ARCHIVE_PATH = "${env.WORKSPACE}/artifacts/${env.ARCHIVE_BASENAME}"
env.MANIFEST_PATH = "${env.WORKSPACE}/artifacts/JiZhi-Backend-${env.IMMUTABLE_TAG}-manifest.json"
env.SMOKE_NETWORK = "jizhi-smoke-net-${env.BUILD_NUMBER}"
env.SMOKE_MYSQL = "jizhi-smoke-mysql-${env.BUILD_NUMBER}"
env.SMOKE_APP = "jizhi-smoke-app-${env.BUILD_NUMBER}"
currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.IMMUTABLE_TAG}"
currentBuild.description = 'linux/amd64 · Admin Web + ASP.NET API'
}
sh '''
set -euo pipefail
test "$(uname -m)" = x86_64
for command_name in git docker curl gzip sha256sum python3; do
command -v "$command_name" >/dev/null
done
docker version >/dev/null
docker buildx version
test -f Dockerfile.backend
test -f admin-web/package-lock.json
mkdir -p artifacts
available_kb=$(df -Pk "$WORKSPACE" | awk 'NR == 2 { print $4 }')
test "$available_kb" -ge 8388608
'''
}
}
stage('Build linux-amd64 Image') {
steps {
retry(2) {
sh '''
set -euo pipefail
docker buildx build \
--platform linux/amd64 \
--load \
--file Dockerfile.backend \
--build-arg "BUILD_VERSION=$IMMUTABLE_TAG" \
--build-arg "VCS_REF=$FULL_SHA" \
--build-arg "BUILD_DATE=$BUILD_DATE" \
--tag "$IMAGE_REF" \
.
'''
}
sh '''
set -euo pipefail
test "$(docker image inspect "$IMAGE_REF" --format '{{.Os}}/{{.Architecture}}')" = 'linux/amd64'
test "$(docker image inspect "$IMAGE_REF" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$FULL_SHA"
docker run --rm --entrypoint /bin/sh "$IMAGE_REF" -c \
'test -s /app/MiaoJiZhang.Api.dll && test -s /app/wwwroot/index.html'
'''
}
}
stage('Smoke Test Image') {
steps {
sh '''
set -euo pipefail
docker network create "$SMOKE_NETWORK" >/dev/null
docker run --detach \
--name "$SMOKE_MYSQL" \
--network "$SMOKE_NETWORK" \
--env MYSQL_DATABASE=jizhi_smoke \
--env MYSQL_USER=jizhi \
--env MYSQL_PASSWORD=jizhi-smoke-password \
--env MYSQL_ROOT_PASSWORD=jizhi-smoke-root-password \
mysql:8.4 >/dev/null
mysql_ready=0
for attempt in $(seq 1 60); do
if docker exec "$SMOKE_MYSQL" mysqladmin ping \
--user=jizhi --password=jizhi-smoke-password --silent; then
mysql_ready=1
break
fi
sleep 2
done
if [ "$mysql_ready" != 1 ]; then
docker logs "$SMOKE_MYSQL" >&2
exit 1
fi
docker run --detach \
--name "$SMOKE_APP" \
--network "$SMOKE_NETWORK" \
--publish 127.0.0.1::8080 \
--env ASPNETCORE_ENVIRONMENT=Production \
--env "ConnectionStrings__Default=Server=$SMOKE_MYSQL;Port=3306;Database=jizhi_smoke;User=jizhi;Password=jizhi-smoke-password" \
--env Jwt__Secret=jizhi-smoke-jwt-secret-at-least-32-characters \
--env Jwt__Issuer=MiaoJiZhang.Api \
--env Jwt__Audience=MiaoJiZhang.App \
--env Admin__BootstrapUsername=smoke_admin \
--env Admin__BootstrapPassword=jizhi-smoke-admin-password \
--env Admin__CookieSecure=false \
--env "Build__Version=$IMMUTABLE_TAG" \
"$IMAGE_REF" >/dev/null
host_port=$(docker port "$SMOKE_APP" 8080/tcp | sed -n 's/.*://p' | head -n 1)
test -n "$host_port"
app_ready=0
for attempt in $(seq 1 90); do
if curl --fail --silent --show-error \
"http://127.0.0.1:$host_port/api/ping" >artifacts/smoke-ping.json; then
app_ready=1
break
fi
if ! docker inspect "$SMOKE_APP" --format '{{.State.Running}}' | grep -qx true; then
docker logs "$SMOKE_APP" >&2
exit 1
fi
sleep 2
done
if [ "$app_ready" != 1 ]; then
docker logs "$SMOKE_APP" >&2
exit 1
fi
grep -Fq "\"version\":\"$IMMUTABLE_TAG\"" artifacts/smoke-ping.json
curl --fail --silent --show-error "http://127.0.0.1:$host_port/" \
| grep -Fq '<div id="app"></div>'
'''
}
}
stage('Package Image') {
steps {
sh '''
set -euo pipefail
docker save "$IMAGE_REF" | gzip -1 >"$ARCHIVE_PATH"
gzip -t "$ARCHIVE_PATH"
gzip -dc "$ARCHIVE_PATH" | tar -tf - | grep -q '^manifest.json$'
sha256sum "$ARCHIVE_PATH" >"$ARCHIVE_PATH.sha256"
image_id=$(docker image inspect "$IMAGE_REF" --format '{{.Id}}')
archive_sha=$(awk '{print $1}' "$ARCHIVE_PATH.sha256")
export image_id archive_sha
python3 - <<'PY'
import json, os, pathlib
path = pathlib.Path(os.environ['MANIFEST_PATH'])
payload = {
'project': 'jizhang-backend',
'buildNumber': os.environ['BUILD_NUMBER'],
'version': os.environ['IMMUTABLE_TAG'],
'commit': os.environ['FULL_SHA'],
'platform': 'linux/amd64',
'image': os.environ['IMAGE_REF'],
'imageId': os.environ['image_id'],
'archive': pathlib.Path(os.environ['ARCHIVE_PATH']).name,
'archiveSha256': os.environ['archive_sha'],
'builtAt': os.environ['BUILD_DATE'],
'contents': ['admin-web', 'MiaoJiZhang.Api'],
'smokeTests': ['image architecture', '/api/ping', 'admin web index'],
}
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
PY
sha256sum "$MANIFEST_PATH" >"$MANIFEST_PATH.sha256"
'''
}
}
stage('Upload To OpenList') {
steps {
withCredentials([usernamePassword(credentialsId: "${OPENLIST_CREDENTIALS}", usernameVariable: 'OPENLIST_USERNAME', passwordVariable: 'OPENLIST_PASSWORD')]) {
sh '''
set -euo pipefail
for artifact in \
"$ARCHIVE_PATH" \
"$ARCHIVE_PATH.sha256" \
"$MANIFEST_PATH" \
"$MANIFEST_PATH.sha256"; do
./scripts/upload-openlist-artifact.sh "$artifact" "$OPENLIST_REMOTE_DIR"
done
'''
}
}
}
}
post {
always {
script {
if (env.NODE_NAME?.trim()) {
sh(
script: '''
docker rm --force "$SMOKE_APP" "$SMOKE_MYSQL" >/dev/null 2>&1 || true
docker network rm "$SMOKE_NETWORK" >/dev/null 2>&1 || true
docker image rm "$IMAGE_REF" >/dev/null 2>&1 || true
''',
returnStatus: true,
)
}
}
}
success {
archiveArtifacts artifacts: 'artifacts/*.json,artifacts/*.sha256', fingerprint: true
}
cleanup {
script {
if (env.NODE_NAME?.trim()) {
cleanWs(deleteDirs: true, notFailBuild: true)
}
}
}
}
}