perf(ci): paralelizar tests/security/builds y cachear deps entre runs
Optimizaciones aplicadas al pipeline para reducir tiempo total ~5-10 min:
1. Tests + Security Scan fusionados en un único stage paralelo de 4
substages: Test Backend, Test Frontend Vitest, pip-audit, npm audit.
Antes secuencial (Tests → Security): ahora máximo de los 4 en vez de
la suma. Ahorro: ~30-60s.
2. Docker Build de backend y frontend paralelizado en substages propios.
Antes secuencial (~6-10 min totales), ahora ~max(3-5min). Cada substage
hace su propio docker login (cheap, ~1s). Ahorro: ~3-5 min.
3. --cache-from + BUILDKIT_INLINE_CACHE=1 en ambos builds de Harbor.
docker pull ${IMG}:latest antes del build alimenta el cache. Las
layers de pip install / pnpm install se reusan cuando los lockfiles
no cambian. Ahorro: ~1-3 min por imagen.
4. Volumes Docker nombrados (jenkins-a76-pip-cache, jenkins-a76-pnpm-store)
en los contenedores de Test Backend, Test Frontend Vitest y pip-audit.
Persisten entre builds en el mismo agente Jenkins; primer build los
puebla, siguientes reusan paquetes/wheels. Ahorro: ~30-60s por stage.
Estimado total: pipeline pasa de ~25-30min a ~15-20min.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
136
Jenkinsfile
vendored
136
Jenkinsfile
vendored
@@ -1,5 +1,5 @@
|
||||
// Pipeline CI/CD — Aduanasoft Anexo76
|
||||
// Ejecuta: tests → security scan → docker build/push → E2E → deploy dev → smoke test
|
||||
// Ejecuta: tests+security (paralelo) → docker build/push (paralelo) → E2E → deploy dev → smoke test
|
||||
//
|
||||
// Credenciales requeridas en Jenkins:
|
||||
// - harbor-credentials : username/password para dev.aduanasoft.com
|
||||
@@ -37,8 +37,13 @@ pipeline {
|
||||
|
||||
stages {
|
||||
|
||||
// ── Tests (backend y frontend unit en paralelo) ───────────────────────
|
||||
stage('Tests') {
|
||||
// ── Tests + Security (4 trabajos en paralelo) ─────────────────────────
|
||||
// Backend tests, frontend Vitest, pip-audit y npm audit son independientes:
|
||||
// todos en un solo bloque parallel — la stage tarda el máximo de los 4,
|
||||
// no la suma. pip y pnpm cachean en volumes persistentes del agente
|
||||
// (jenkins-a76-pip-cache, jenkins-a76-pnpm-store) para reusar deps
|
||||
// entre builds (~30-60s menos cada uno cuando lockfiles no cambian).
|
||||
stage('Tests & Security') {
|
||||
parallel {
|
||||
|
||||
stage('Test — Backend') {
|
||||
@@ -92,7 +97,11 @@ pipeline {
|
||||
"
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" --network "$NET" "$PYTHON_IMAGE" sleep infinity
|
||||
# Volume persistente para el cache de pip (~/.cache/pip):
|
||||
# primer build lo puebla, siguientes reusan wheels si requirements.txt no cambió.
|
||||
docker run -d --name "$C" --network "$NET" \
|
||||
-v jenkins-a76-pip-cache:/root/.cache/pip \
|
||||
"$PYTHON_IMAGE" sleep infinity
|
||||
docker cp "$WORKSPACE/backend/." "$C:/app"
|
||||
|
||||
docker exec \
|
||||
@@ -144,7 +153,12 @@ pipeline {
|
||||
trap cleanup EXIT
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity
|
||||
# Volume persistente para el store de pnpm (content-addressable):
|
||||
# primer build lo puebla, siguientes reusan paquetes si pnpm-lock.yaml no cambió.
|
||||
# Path estándar de pnpm@9 en Linux como root: /root/.local/share/pnpm/store
|
||||
docker run -d --name "$C" \
|
||||
-v jenkins-a76-pnpm-store:/root/.local/share/pnpm/store \
|
||||
"$PLAYWRIGHT_IMAGE" sleep infinity
|
||||
docker exec "$C" mkdir -p /workspace
|
||||
docker cp "$WORKSPACE/." "$C:/workspace"
|
||||
|
||||
@@ -165,12 +179,6 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ── Escaneo de vulnerabilidades ───────────────────────────────────────
|
||||
stage('Security Scan') {
|
||||
parallel {
|
||||
stage('pip-audit') {
|
||||
steps {
|
||||
sh '''
|
||||
@@ -180,7 +188,9 @@ pipeline {
|
||||
trap cleanup EXIT
|
||||
|
||||
docker rm -f "$C" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$C" "$PYTHON_IMAGE" sleep infinity
|
||||
docker run -d --name "$C" \
|
||||
-v jenkins-a76-pip-cache:/root/.cache/pip \
|
||||
"$PYTHON_IMAGE" sleep infinity
|
||||
docker cp "$WORKSPACE/backend/requirements.txt" "$C:/requirements.txt"
|
||||
|
||||
docker exec "$C" bash -lc '
|
||||
@@ -212,6 +222,7 @@ pipeline {
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,55 +254,86 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Docker Build + Push ───────────────────────────────────────────────
|
||||
// ── Docker Build + Push (backend y frontend en paralelo) ──────────────
|
||||
// Antes era secuencial: ~6-10 min totales. Ahora cada imagen builda+pushea
|
||||
// en su propio substage en paralelo → tarda el máximo de los dos.
|
||||
// BuildKit inline cache + --cache-from reusan las layers de :latest cuando
|
||||
// pip-lock / pnpm-lock no cambiaron (~1-3 min menos por imagen).
|
||||
stage('Docker Build') {
|
||||
when {
|
||||
anyOf { branch 'main'; branch 'development' }
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: 'harbor-credentials',
|
||||
usernameVariable: 'HARBOR_USER',
|
||||
passwordVariable: 'HARBOR_PASS'
|
||||
)
|
||||
]) {
|
||||
sh "echo \"\${HARBOR_PASS}\" | docker login ${HARBOR_REGISTRY} -u \"\${HARBOR_USER}\" --password-stdin"
|
||||
|
||||
sh """
|
||||
docker build --progress=plain \
|
||||
--build-arg APP_VERSION=${env.APP_VERSION} \
|
||||
-t ${IMAGE_BACKEND}:${env.APP_VERSION} \
|
||||
-t ${IMAGE_BACKEND}:latest \
|
||||
-f backend/Dockerfile \
|
||||
backend/
|
||||
"""
|
||||
parallel {
|
||||
|
||||
stage('Build & Push — Backend') {
|
||||
steps {
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: 'harbor-credentials',
|
||||
usernameVariable: 'HARBOR_USER',
|
||||
passwordVariable: 'HARBOR_PASS'
|
||||
)
|
||||
]) {
|
||||
sh """
|
||||
set -euo pipefail
|
||||
export DOCKER_BUILDKIT=1
|
||||
echo "\${HARBOR_PASS}" | docker login ${HARBOR_REGISTRY} -u "\${HARBOR_USER}" --password-stdin
|
||||
|
||||
# Pull de :latest para alimentar --cache-from (silencioso si no existe)
|
||||
docker pull ${IMAGE_BACKEND}:latest >/dev/null 2>&1 || true
|
||||
|
||||
docker build --progress=plain \\
|
||||
--cache-from ${IMAGE_BACKEND}:latest \\
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \\
|
||||
--build-arg APP_VERSION=${env.APP_VERSION} \\
|
||||
-t ${IMAGE_BACKEND}:${env.APP_VERSION} \\
|
||||
-t ${IMAGE_BACKEND}:latest \\
|
||||
-f backend/Dockerfile \\
|
||||
backend/
|
||||
|
||||
docker push ${IMAGE_BACKEND}:${env.APP_VERSION}
|
||||
docker push ${IMAGE_BACKEND}:latest
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build & Push — Frontend') {
|
||||
steps {
|
||||
withCredentials([
|
||||
usernamePassword(
|
||||
credentialsId: 'harbor-credentials',
|
||||
usernameVariable: 'HARBOR_USER',
|
||||
passwordVariable: 'HARBOR_PASS'
|
||||
),
|
||||
string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL')
|
||||
]) {
|
||||
sh """
|
||||
docker build --progress=plain \
|
||||
--build-arg VITE_API_URL=\${A76_URL}/api/ \
|
||||
--build-arg VITE_KEYCLOAK_URL=${KC_URL} \
|
||||
--build-arg VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend \
|
||||
--build-arg INTERNAL_API_URL=http://backend:3467/api/ \
|
||||
-t ${IMAGE_FRONTEND}:${env.APP_VERSION} \
|
||||
-t ${IMAGE_FRONTEND}:latest \
|
||||
-f frontend/Dockerfile.prod \
|
||||
set -euo pipefail
|
||||
export DOCKER_BUILDKIT=1
|
||||
echo "\${HARBOR_PASS}" | docker login ${HARBOR_REGISTRY} -u "\${HARBOR_USER}" --password-stdin
|
||||
|
||||
docker pull ${IMAGE_FRONTEND}:latest >/dev/null 2>&1 || true
|
||||
|
||||
docker build --progress=plain \\
|
||||
--cache-from ${IMAGE_FRONTEND}:latest \\
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \\
|
||||
--build-arg VITE_API_URL=\${A76_URL}/api/ \\
|
||||
--build-arg VITE_KEYCLOAK_URL=${KC_URL} \\
|
||||
--build-arg VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend \\
|
||||
--build-arg INTERNAL_API_URL=http://backend:3467/api/ \\
|
||||
-t ${IMAGE_FRONTEND}:${env.APP_VERSION} \\
|
||||
-t ${IMAGE_FRONTEND}:latest \\
|
||||
-f frontend/Dockerfile.prod \\
|
||||
frontend/
|
||||
|
||||
docker push ${IMAGE_FRONTEND}:${env.APP_VERSION}
|
||||
docker push ${IMAGE_FRONTEND}:latest
|
||||
"""
|
||||
}
|
||||
|
||||
sh """
|
||||
docker push ${IMAGE_BACKEND}:${env.APP_VERSION}
|
||||
docker push ${IMAGE_BACKEND}:latest
|
||||
docker push ${IMAGE_FRONTEND}:${env.APP_VERSION}
|
||||
docker push ${IMAGE_FRONTEND}:latest
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user