Análisis del Jenkinsfile - Eziuz/Proyecto-POLI-Generador-de-Claves GitHub Wiki
📋 Análisis Sección por Sección
1. 🔧 Declaración del Pipeline
pipeline {
agent any
Elemento | Descripción | Propósito |
---|---|---|
pipeline |
Bloque principal del pipeline declarativo | Define la estructura completa |
agent any |
Ejecuta en cualquier agente disponible | Flexibilidad de ejecución |
¿Por qué agent any
?
- ✅ Simplicidad: No requiere agentes específicos
- ✅ Flexibilidad: Funciona en cualquier nodo Jenkins
- ✅ Escalabilidad: Permite distribución automática
2. 🌍 Variables de Entorno
environment {
// Configuración de DockerHub
DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')
DOCKERHUB_USERNAME = "${DOCKERHUB_CREDENTIALS_USR}"
DOCKERHUB_REPO = 'generador-claves'
// Etiquetas de la imagen con versionado semántico
IMAGE_NAME = "${DOCKERHUB_USERNAME}/${DOCKERHUB_REPO}"
GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
}
Análisis de Variables
Variable | Valor | Propósito |
---|---|---|
DOCKERHUB_CREDENTIALS |
credentials('dockerhub-credentials') |
Acceso seguro a credenciales |
DOCKERHUB_USERNAME |
${DOCKERHUB_CREDENTIALS_USR} |
Usuario extraído de credenciales |
DOCKERHUB_REPO |
'generador-claves' |
Nombre del repositorio en Docker Hub |
IMAGE_NAME |
"${DOCKERHUB_USERNAME}/${DOCKERHUB_REPO}" |
Nombre completo de la imagen |
GIT_COMMIT_SHORT |
sh(script: 'git rev-parse --short HEAD', ...) |
Hash corto del commit |
🔒 Seguridad de Credenciales
DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')
¿Cómo funciona?
-
Jenkins busca credenciales con ID
dockerhub-credentials
-
Automáticamente crea variables:
-
DOCKERHUB_CREDENTIALS_USR
→ Usuario -
DOCKERHUB_CREDENTIALS_PSW
→ Contraseña -
Las credenciales se enmascaran en los logs
Ventajas:
- 🔐 Seguridad: Credenciales nunca expuestas en código
- 🎭 Enmascaramiento: Automático en logs
- 🔄 Reutilización: Disponible en todo el pipeline
3. 📥 Stage: Checkout
stage('Checkout') {
steps {
checkout scm
}
}
¿Qué hace este stage?
Acción | Descripción | Resultado |
---|---|---|
checkout scm |
Clona el repositorio configurado | Código disponible en workspace |
Para repositorios privados:
- ✅ Utiliza las credenciales de GitHub configuradas
- ✅ Clona la rama especificada (
*/main
) - ✅ Descarga todo el historial necesario
Archivos Descargados
workspace/
├── Dockerfile ← Para construcción de imagen
├── Jenkinsfile ← Este mismo archivo
├── package.json ← Información de versión
├── app/ ← Código de la aplicación
├── components/ ← Componentes React
├── public/ ← Archivos estáticos
└── ...
4. 🏷️ Stage: Determine Version
stage('Determine Version') {
steps {
script {
// Intentar obtener la versión desde Git tags
def gitTag = sh(
script: "git describe --tags --exact-match HEAD 2>/dev/null || echo ''",
returnStdout: true
).trim()
if (gitTag && gitTag.startsWith('v')) {
// Si hay un tag que empiece con 'v', usarlo como versión
env.VERSION = gitTag.substring(1) // Remover la 'v' del inicio
env.IS_RELEASE = 'true'
} else {
// Si no hay tag, usar versión desde package.json + build number
def packageVersion = sh(
script: "node -p \"require('./package.json').version\"",
returnStdout: true
).trim()
env.VERSION = "${packageVersion}-build.${BUILD_NUMBER}"
env.IS_RELEASE = 'false'
}
echo "🏷️ Version determined: ${env.VERSION}"
echo "📦 Is release: ${env.IS_RELEASE}"
}
}
}
🧠 Lógica de Versionado
Flujo de Decisión:
flowchart TD
A[Check Git Tags] --> B{Tag en HEAD?}
B -->|Si| C{Empieza con v?}
B -->|No| D[Read package.json]
C -->|Si| E[Use Tag Version]
C -->|No| D
D --> F[Add Build Number]
E --> G[IS_RELEASE = true]
F --> H[IS_RELEASE = false]
Ejemplos de Versionado
Escenario | Git Tag | package.json | BUILD_NUMBER | Resultado | IS_RELEASE |
---|---|---|---|---|---|
Release oficial | v1.2.3 |
1.2.3 |
42 |
1.2.3 |
true |
Development build | (ninguno) | 1.2.3 |
42 |
1.2.3-build.42 |
false |
Tag sin 'v' | release-1.2.3 |
1.2.3 |
42 |
1.2.3-build.42 |
false |
🔍 Comandos Utilizados
# Obtener tag exacto en HEAD (si existe)
git describe --tags --exact-match HEAD 2>/dev/null || echo ''
# Leer versión desde package.json
node -p "require('./package.json').version"
¿Por qué este enfoque?
- 🎯 Flexibilidad: Soporta releases y development builds
- 📋 Trazabilidad: Cada build tiene versión única
- 🏷️ Semántico: Sigue convenciones de versionado semántico
5. 🏗️ Stage: Build Docker Image
stage('Build Docker Image') {
steps {
script {
// Construir la imagen Docker con versionado semántico
sh """
docker build -t ${IMAGE_NAME}:${VERSION} \
-t ${IMAGE_NAME}:latest \
-t ${IMAGE_NAME}:${GIT_COMMIT_SHORT} .
"""
}
}
}
🏷️ Estrategia de Etiquetado
Cada build crea 3 tags:
Tag | Ejemplo | Propósito |
---|---|---|
Versión específica | usuario/generador-claves:1.2.3 |
Versión exacta |
Latest | usuario/generador-claves:latest |
Última versión |
Commit hash | usuario/generador-claves:a1b2c3d |
Trazabilidad por commit |
🔧 Comando Docker Build
docker build -t usuario/generador-claves:1.2.3 \
-t usuario/generador-claves:latest \
-t usuario/generador-claves:a1b2c3d .
Parámetros explicados:
-t
: Asigna un tag a la imagen.
: Contexto de build (directorio actual)\
: Continuación de línea para legibilidad
📁 Contexto de Build
El comando utiliza el Dockerfile en la raíz del proyecto:
# Usar Node.js 20 LTS con Alpine como base
FROM node:20-alpine AS base
# Instalar dependencias solo cuando sea necesario
FROM base AS deps
# ... configuración de dependencias
# Construir la aplicación
FROM base AS builder
# ... proceso de build
# Imagen de producción
FROM node:20-alpine AS runner
# ... configuración final
6. ✅ Stage: Verify Image
stage('Verify Image') {
steps {
script {
sh """
echo "Verificando que la imagen existe..."
docker images ${IMAGE_NAME}:${VERSION}
echo "Verificando estructura de la imagen..."
docker inspect ${IMAGE_NAME}:${VERSION} > /dev/null
echo "Verificando configuración de la imagen..."
docker inspect ${IMAGE_NAME}:${VERSION} | grep -E '"User"|"Entrypoint"|"Cmd"|"WorkingDir"' || true
echo "Imagen verificada exitosamente"
"""
}
}
}
🔍 Verificaciones Realizadas
Verificación | Comando | Propósito |
---|---|---|
Existencia | docker images ${IMAGE_NAME}:${VERSION} |
Confirma que la imagen se creó |
Estructura | docker inspect ${IMAGE_NAME}:${VERSION} |
Valida metadatos de la imagen |
Configuración | `grep -E '"User" | "Entrypoint"...'` |
📊 Salida Esperada
# Verificación de existencia
REPOSITORY TAG IMAGE ID CREATED SIZE
usuario/generador-claves 1.2.3 a1b2c3d4e5f6 2 minutes ago 150MB
# Verificación de configuración
"User": "nextjs",
"Entrypoint": null,
"Cmd": ["node", "server.js"],
"WorkingDir": "/app"
🛡️ Verificaciones de Seguridad
¿Qué busca el grep?
"User"
: Confirma que no ejecuta como root"Entrypoint"
: Verifica punto de entrada"Cmd"
: Confirma comando de inicio"WorkingDir"
: Valida directorio de trabajo
7. 📦 Stage: Push to DockerHub
stage('Push to DockerHub') {
steps {
script {
// Iniciar sesión en DockerHub
sh "echo ${DOCKERHUB_CREDENTIALS_PSW} | docker login -u ${DOCKERHUB_CREDENTIALS_USR} --password-stdin"
// Publicar las imágenes en DockerHub
sh """
docker push ${IMAGE_NAME}:${VERSION}
docker push ${IMAGE_NAME}:${GIT_COMMIT_SHORT}
"""
// Solo pushear 'latest' si es un release oficial
if (env.IS_RELEASE == 'true') {
sh "docker push ${IMAGE_NAME}:latest"
echo "✅ Released version ${VERSION} as latest"
} else {
echo "⚠️ Development build - not updating 'latest' tag"
}
// Cerrar sesión de DockerHub
sh 'docker logout'
}
}
}
🔐 Proceso de Autenticación
# Login seguro usando stdin
echo $PASSWORD | docker login -u $USERNAME --password-stdin
Ventajas de --password-stdin
:
- 🔒 Seguridad: Password no aparece en historial de comandos
- 🎭 Enmascaramiento: Jenkins oculta automáticamente la contraseña
- 🛡️ Mejores prácticas: Recomendado por Docker
📤 Estrategia de Push
Siempre se publican:
- ✅ Tag de versión específica (
1.2.3
) - ✅ Tag de commit hash (
a1b2c3d
)
Condicionalmente se publica:
- 🔄 Tag
latest
(solo para releases oficiales)
🎯 Lógica de Latest Tag
flowchart TD
A[Ready to Push] --> B{IS_RELEASE == true?}
B -->|Si| C[Push latest tag]
B -->|No| D[Skip latest tag]
C --> E[Official Release]
D --> F[Development Build]
¿Por qué esta estrategia?
- 🎯 Control de calidad: Solo releases verificados como
latest
- 🔄 Estabilidad: Evita que builds de desarrollo sobrescriban
latest
- 📋 Trazabilidad: Siempre hay versiones específicas disponibles
8. 🧹 Post Actions
post {
always {
// Limpiar workspace y eliminar imágenes locales
cleanWs()
script {
sh """
# Obtener contenedores que usan nuestras imágenes
CONTAINERS=\$(docker ps -aq --filter ancestor=${IMAGE_NAME} 2>/dev/null || echo "")
if [ ! -z "\$CONTAINERS" ]; then
echo "Limpiando contenedores: \$CONTAINERS"
docker stop \$CONTAINERS || true
docker rm \$CONTAINERS || true
else
echo "No hay contenedores que limpiar"
fi
# Eliminar las imágenes
docker rmi ${IMAGE_NAME}:${VERSION} || true
docker rmi ${IMAGE_NAME}:latest || true
docker rmi ${IMAGE_NAME}:${GIT_COMMIT_SHORT} || true
docker image prune -f || true
"""
}
}
success {
script {
if (env.IS_RELEASE == 'true') {
echo """
🎉 ¡Release ${VERSION} publicado exitosamente!
Para usar esta versión:
docker pull ${IMAGE_NAME}:${VERSION}
docker pull ${IMAGE_NAME}:latest
docker run -d -p 3000:3000 ${IMAGE_NAME}:${VERSION}
"""
} else {
echo """
✅ ¡Build de desarrollo completado!
Para usar esta versión:
docker pull ${IMAGE_NAME}:${VERSION}
docker run -d -p 3000:3000 ${IMAGE_NAME}:${VERSION}
También disponible por commit:
docker pull ${IMAGE_NAME}:${GIT_COMMIT_SHORT}
"""
}
}
}
failure {
echo '❌ Error al construir o publicar la imagen'
}
}
🧹 Limpieza Automática
Sección always
(se ejecuta siempre):
Acción | Comando | Propósito |
---|---|---|
Limpiar workspace | cleanWs() |
Libera espacio en disco |
Parar contenedores | docker stop $CONTAINERS |
Detiene contenedores activos |
Eliminar contenedores | docker rm $CONTAINERS |
Remueve contenedores |
Eliminar imágenes | docker rmi ${IMAGE_NAME}:* |
Libera espacio de imágenes |
Limpiar imágenes huérfanas | docker image prune -f |
Remueve imágenes sin usar |
🎉 Notificaciones de Éxito
Para releases oficiales:
🎉 ¡Release 1.2.3 publicado exitosamente!
Para usar esta versión:
docker pull usuario/generador-claves:1.2.3
docker pull usuario/generador-claves:latest
docker run -d -p 3000:3000 usuario/generador-claves:1.2.3
Para builds de desarrollo:
✅ ¡Build de desarrollo completado!
Para usar esta versión:
docker pull usuario/generador-claves:1.2.3-build.42
docker run -d -p 3000:3000 usuario/generador-claves:1.2.3-build.42
También disponible por commit:
docker pull usuario/generador-claves:a1b2c3d
❌ Manejo de Errores
En caso de fallo:
- 🚨 Mensaje de error claro
- 🧹 Limpieza automática ejecutada
- 📋 Logs disponibles para debugging
🔧 Características Avanzadas
1. 🛡️ Manejo de Errores
# Comandos con manejo de errores
docker stop \$CONTAINERS || true
docker rm \$CONTAINERS || true
docker rmi ${IMAGE_NAME}:${VERSION} || true
|| true
significa:
- ✅ Si el comando falla, continúa sin error
- 🔄 Útil para limpieza (puede que no haya nada que limpiar)
- 🛡️ Evita que el pipeline falle por limpieza
2. 🎭 Enmascaramiento de Credenciales
DOCKERHUB_CREDENTIALS = credentials('dockerhub-credentials')
Jenkins automáticamente:
- 🎭 Enmascara
DOCKERHUB_CREDENTIALS_PSW
en logs - 🔒 Protege credenciales en salida de consola
- 🛡️ Evita exposición accidental
3. 📊 Logging Estructurado
echo "🏷️ Version determined: ${env.VERSION}"
echo "📦 Is release: ${env.IS_RELEASE}"
Ventajas:
- 🎯 Claridad: Emojis y formato consistente
- 📋 Trazabilidad: Información clave visible
- 🔍 Debugging: Fácil identificación de problemas
4. 🔄 Versionado Inteligente
Combina múltiples fuentes:
- 🏷️ Git tags para releases oficiales
- 📦 package.json para versión base
- 🔢 BUILD_NUMBER para uniqueness
📊 Métricas y Monitoreo
Tiempos Esperados por Stage
Stage | Tiempo Típico | Factores que Afectan |
---|---|---|
Checkout | 10-30s | Tamaño del repositorio, velocidad de red |
Determine Version | 5-10s | Complejidad de Git history |
Build Docker Image | 2-5 min | Tamaño de dependencias, cache de Docker |
Verify Image | 10-30s | Complejidad de verificaciones |
Push to DockerHub | 30s-2min | Tamaño de imagen, velocidad de upload |
Post Actions | 10-30s | Cantidad de recursos a limpiar |
📈 Optimizaciones Implementadas
Optimización | Beneficio | Implementación |
---|---|---|
Multi-stage Dockerfile | Imágenes más pequeñas | FROM node:20-alpine AS base |
Cache de dependencias | Builds más rápidos | Separación de layers en Docker |
Limpieza automática | Ahorro de espacio | Post actions comprensivas |
Versionado inteligente | Mejor organización | Lógica de tags condicional |
🚨 Troubleshooting del Jenkinsfile
Error: "Version determination failed"
Síntomas:
ERROR: script returned exit code 1
Posibles causas:
- ❌
package.json
no existe o está malformado - ❌ Node.js no está disponible en el agente
- ❌ Permisos de Git insuficientes
Soluciones:
// Agregar validación
script {
if (!fileExists('package.json')) {
error "package.json not found"
}
def packageVersion = sh(
script: "node -p \"require('./package.json').version\" 2>/dev/null || echo 'unknown'",
returnStdout: true
).trim()
if (packageVersion == 'unknown') {
error "Could not read version from package.json"
}
}
Error: "Docker build failed"
Síntomas:
ERROR: The command '/bin/sh -c yarn install --immutable' returned a non-zero code: 1
Posibles causas:
- ❌ Dockerfile tiene errores de sintaxis
- ❌ Dependencias no disponibles
- ❌ Problemas de red durante build
Soluciones:
// Agregar retry y mejor logging
script {
retry(3) {
sh """
echo "Building Docker image (attempt \${env.BUILD_NUMBER})..."
docker build --no-cache -t ${IMAGE_NAME}:${VERSION} .
"""
}
}
Error: "Push to DockerHub failed"
Síntomas:
denied: requested access to the resource is denied
Posibles causas:
- ❌ Credenciales incorrectas o expiradas
- ❌ Repositorio no existe en Docker Hub
- ❌ Permisos insuficientes
Soluciones:
// Verificar login antes de push
script {
sh """
echo "Testing Docker Hub connection..."
docker login -u ${DOCKERHUB_CREDENTIALS_USR} --password-stdin <<< ${DOCKERHUB_CREDENTIALS_PSW}
echo "Verifying repository access..."
docker pull hello-world
echo "Pushing images..."
docker push ${IMAGE_NAME}:${VERSION}
"""
}
🔧 Personalizaciones Comunes
1. Agregar Tests Automatizados
stage('Run Tests') {
steps {
script {
sh """
echo "Running unit tests..."
docker run --rm ${IMAGE_NAME}:${VERSION} npm test
echo "Running integration tests..."
docker run --rm -p 3000:3000 -d --name test-container ${IMAGE_NAME}:${VERSION}
sleep 10
curl -f http://localhost:3000 || exit 1
docker stop test-container
"""
}
}
}
2. Notificaciones Slack
post {
success {
slackSend(
channel: '#deployments',
color: 'good',
message: "✅ Build ${env.BUILD_NUMBER} successful! Version: ${env.VERSION}"
)
}
failure {
slackSend(
channel: '#deployments',
color: 'danger',
message: "❌ Build ${env.BUILD_NUMBER} failed! Check: ${env.BUILD_URL}"
)
}
}
3. Análisis de Seguridad
stage('Security Scan') {
steps {
script {
sh """
echo "Scanning image for vulnerabilities..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image ${IMAGE_NAME}:${VERSION}
"""
}
}
}
📋 Checklist de Validación
Antes de usar el Jenkinsfile:
- ✅ Credenciales configuradas (
dockerhub-credentials
) - ✅ Dockerfile presente en la raíz del proyecto
- ✅ package.json válido con campo
version
- ✅ Docker disponible en el agente Jenkins
- ✅ Permisos de Git configurados para repositorio privado
Durante la ejecución:
- ✅ Checkout exitoso del repositorio privado
- ✅ Versión determinada correctamente
- ✅ Imagen construida sin errores
- ✅ Verificaciones pasadas todas las validaciones
- ✅ Push exitoso a Docker Hub
Después de la ejecución:
- ✅ Imagen disponible en Docker Hub
- ✅ Tags correctos aplicados
- ✅ Limpieza completada en Jenkins
- ✅ Logs claros y sin errores
🎯 Mejores Prácticas Implementadas
1. 🔒 Seguridad
- ✅ Credenciales seguras: Uso de Jenkins credentials store
- ✅ No hardcoding: Variables de entorno para configuración
- ✅ Limpieza automática: Remoción de credenciales y recursos
2. 🔄 Mantenibilidad
- ✅ Código declarativo: Pipeline fácil de leer y mantener
- ✅ Logging estructurado: Mensajes claros con emojis
- ✅ Manejo de errores: Graceful degradation con
|| true
3. 📊 Observabilidad
- ✅ Versionado claro: Estrategia de tags comprensible
- ✅ Métricas de tiempo: Stages bien definidos
- ✅ Notificaciones informativas: Mensajes de éxito detallados
4. 🚀 Performance
- ✅ Builds paralelos: Uso eficiente de recursos
- ✅ Cache de Docker: Optimización de build times
- ✅ Limpieza automática: Prevención de acumulación de recursos
📚 Referencias y Documentación
Jenkins Pipeline
Docker
Git
- 🏷️ Git Tagging
- 📋 Git Describe
- 🔍 Git Rev-parse