Files
k3s-gitops/apps/demo-nginx/Jenkinsfile

763 lines
26 KiB
Groovy
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

pipeline {
agent any
options {
disableConcurrentBuilds()
}
environment {
APP_NAME = 'demo-nginx'
NAMESPACE = 'demo-app'
DOCKER_REGISTRY = 'docker.io'
DOCKER_REPO = 'vladcrypto'
GITEA_URL = 'http://gitea-http.gitea.svc.cluster.local:3000'
GITEA_REPO = 'admin/k3s-gitops'
GITEA_BRANCH = 'main'
BUILD_TAG = "${env.BUILD_NUMBER}"
IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}"
// Rollback configuration
ROLLBACK_ENABLED = 'true'
DEPLOYMENT_TIMEOUT = '300s'
ARGOCD_SYNC_TIMEOUT = '120'
SKIP_HEALTH_CHECK = 'true'
// Notification configuration
TELEGRAM_BOT_TOKEN = credentials('telegram-bot-token')
TELEGRAM_CHAT_ID = credentials('telegram-chat-id')
// Build info
BUILD_URL = "${env.BUILD_URL}"
GIT_COMMIT_SHORT = ""
DEPLOYMENT_START_TIME = ""
DEPLOYMENT_END_TIME = ""
}
stages {
stage('Initialization') {
steps {
script {
echo "🚀 Starting deployment pipeline..."
DEPLOYMENT_START_TIME = sh(script: 'date +%s', returnStdout: true).trim()
sendTelegramNotification(
status: 'STARTED',
message: """
🚀 <b>Deployment Started</b>
<b>Application:</b> ${APP_NAME}
<b>Build:</b> #${BUILD_NUMBER}
<b>Branch:</b> ${env.BRANCH_NAME}
<b>Namespace:</b> ${NAMESPACE}
<b>Started by:</b> ${env.BUILD_USER ?: 'Jenkins'}
<i>Building and deploying...</i>
""",
color: '🔵'
)
}
}
}
stage('Save Current State') {
when { branch 'main' }
steps {
script {
echo "📸 Saving current deployment state for rollback..."
sh """
kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
-o jsonpath='{.spec.template.spec.containers[0].image}' \
> /tmp/previous_image_${BUILD_NUMBER}.txt || echo "none" > /tmp/previous_image_${BUILD_NUMBER}.txt
PREV_IMAGE=\$(cat /tmp/previous_image_${BUILD_NUMBER}.txt)
echo "Previous image: \${PREV_IMAGE}"
"""
sh """
kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
-o jsonpath='{.spec.replicas}' \
> /tmp/previous_replicas_${BUILD_NUMBER}.txt || echo "2" > /tmp/previous_replicas_${BUILD_NUMBER}.txt
"""
echo "✅ Current state saved"
}
}
}
stage('Checkout Source') {
steps {
echo "📦 Creating application artifacts..."
sh """
cat > Dockerfile << 'EOF'
FROM nginx:1.25.3-alpine
COPY index.html /usr/share/nginx/html/index.html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
EOF
"""
sh """
cat > index.html << EOF
<!DOCTYPE html>
<html>
<head>
<title>Demo Nginx</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
background: rgba(255, 255, 255, 0.1);
padding: 40px;
border-radius: 10px;
backdrop-filter: blur(10px);
}
h1 {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 24px;
margin: 10px 0;
}
.version {
font-family: 'Courier New', monospace;
background: rgba(0, 0, 0, 0.3);
padding: 10px 20px;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Demo Nginx - Build #${BUILD_NUMBER}</h1>
<p>Environment: Production</p>
<p class="version">Version: ${IMAGE_TAG}</p>
<p style="font-size: 16px; margin-top: 30px; opacity: 0.8;">
Image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
</p>
</div>
</body>
</html>
EOF
"""
sh '''
cat > nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
EOF
'''
}
}
stage('Build Docker Image') {
steps {
script {
echo "🏗️ Building Docker image..."
sendTelegramNotification(
status: 'BUILDING',
message: """
🏗️ <b>Building Docker Image</b>
<b>Image:</b> ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
<b>Stage:</b> Build
""",
color: '🔵'
)
sh """
docker build \
-t ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} \
-t ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest \
.
"""
echo "✅ Image built successfully!"
}
}
}
stage('Push to Registry') {
when { branch 'main' }
steps {
script {
echo "📤 Pushing image to registry..."
sendTelegramNotification(
status: 'PUSHING',
message: """
📤 <b>Pushing to Registry</b>
<b>Registry:</b> ${DOCKER_REGISTRY}
<b>Image:</b> ${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
""",
color: '🔵'
)
withCredentials([usernamePassword(
credentialsId: 'docker-registry-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
echo "\${DOCKER_PASS}" | docker login ${DOCKER_REGISTRY} -u "\${DOCKER_USER}" --password-stdin
docker push ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}
docker push ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest
docker logout ${DOCKER_REGISTRY}
"""
}
echo "✅ Image pushed successfully!"
}
}
}
stage('Update GitOps Manifests') {
when { branch 'main' }
steps {
script {
echo "📝 Updating Kubernetes manifests..."
sendTelegramNotification(
status: 'UPDATING',
message: """
📝 <b>Updating GitOps Manifests</b>
<b>Repository:</b> ${GITEA_REPO}
<b>Branch:</b> ${GITEA_BRANCH}
<b>File:</b> apps/demo-nginx/deployment.yaml
""",
color: '🔵'
)
withCredentials([usernamePassword(
credentialsId: 'gitea-credentials',
usernameVariable: 'GIT_USER',
passwordVariable: 'GIT_PASS'
)]) {
sh """
rm -rf k3s-gitops || true
git clone http://\${GIT_USER}:\${GIT_PASS}@gitea-http.gitea.svc.cluster.local:3000/admin/k3s-gitops.git
cd k3s-gitops
git config user.name "Jenkins"
git config user.email "jenkins@thedevops.dev"
# Save current commit for rollback
git rev-parse HEAD > /tmp/previous_commit_${BUILD_NUMBER}.txt
sed -i 's|image: .*|image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}|' apps/demo-nginx/deployment.yaml
git add apps/demo-nginx/deployment.yaml
git commit -m "chore(demo-nginx): Update image to ${IMAGE_TAG}" || echo "No changes"
git push origin main
"""
}
echo "✅ Manifests updated!"
}
}
}
stage('Wait for ArgoCD Sync') {
steps {
script {
echo "⏳ Waiting for ArgoCD to sync manifests..."
for (int i = 1; i <= 12; i++) {
def syncStatus = sh(
script: "kubectl get application demo-nginx -n argocd -o jsonpath='{.status.sync.status}'",
returnStdout: true
).trim()
echo "ArgoCD sync status : ${syncStatus}"
if (syncStatus == "Synced") {
echo "✅ ArgoCD manifests synced"
return
}
sleep 10
}
error("❌ ArgoCD did not sync manifests in time")
}
}
}
stage('Wait for Deployment') {
when { branch 'main' }
steps {
script {
echo "⏳ Waiting for Kubernetes rollout to complete..."
sendTelegramNotification(
status: 'DEPLOYING',
message: """
🚀 <b>Deploying to Kubernetes</b>
<b>Deployment:</b> ${APP_NAME}
<b>Namespace:</b> ${NAMESPACE}
<b>Image:</b> ${IMAGE_TAG}
<b>Timeout:</b> ${DEPLOYMENT_TIMEOUT}
<i>Rolling out new pods...</i>
""",
color: '🔵'
)
try {
// Wait for rollout to complete
echo "Starting rollout status check..."
sh """
kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=${DEPLOYMENT_TIMEOUT}
"""
echo "✅ Rollout completed successfully!"
// Additional verification - wait a bit for pods to stabilize
echo "Waiting 10 seconds for pods to stabilize..."
sleep 10
} catch (Exception e) {
echo "❌ Deployment rollout failed: ${e.message}"
// Get pod status for debugging
try {
def podStatus = sh(
script: """
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} -o wide
""",
returnStdout: true
).trim()
echo "Current pod status:\n${podStatus}"
def events = sh(
script: """
kubectl get events -n ${NAMESPACE} --sort-by='.lastTimestamp' | tail -20
""",
returnStdout: true
).trim()
echo "Recent events:\n${events}"
} catch (Exception debugEx) {
echo "Could not fetch debug info: ${debugEx.message}"
}
throw e
}
}
}
}
stage('Verify Deployment') {
when { branch 'main' }
steps {
script {
echo "✅ Verifying deployment and pod status..."
/* --------------------------------
* 1. Deployment readiness
* -------------------------------- */
sh """
set -e
echo "1. Checking deployment readiness..."
READY=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}')
DESIRED=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.replicas}')
echo "Ready replicas : \$READY"
echo "Desired replicas : \$DESIRED"
if [ "\$READY" != "\$DESIRED" ]; then
echo "❌ Not all replicas are ready"
exit 1
fi
"""
/* --------------------------------
* 2. Actual running images
* -------------------------------- */
def podImages = sh(
script: """
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} \
-o jsonpath='{range .items[*]}{.status.containerStatuses[0].image}{"\\n"}{end}'
""",
returnStdout: true
).trim()
echo "Running pod images:"
podImages.split("\\n").each { img ->
echo " - ${img}"
}
if (!podImages.contains(IMAGE_TAG)) {
error("❌ Some pods are NOT running image tag ${IMAGE_TAG}")
}
/* --------------------------------
* 3. Restart count
* -------------------------------- */
def restarts = sh(
script: """
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} \
-o jsonpath='{range .items[*]}{.status.containerStatuses[0].restartCount}{"\\n"}{end}'
""",
returnStdout: true
).trim()
def maxRestart = restarts
.split("\\n")
.collect { it.toInteger() }
.max()
echo "Max restart count: ${maxRestart}"
if (maxRestart > 3) {
error("❌ High restart count detected: ${maxRestart}")
}
echo "✅ ALL VERIFICATION CHECKS PASSED"
}
}
}
}
post {
success {
script {
DEPLOYMENT_END_TIME = sh(script: 'date +%s', returnStdout: true).trim()
def duration = (DEPLOYMENT_END_TIME.toInteger() - DEPLOYMENT_START_TIME.toInteger())
def durationMin = duration / 60
def durationSec = duration % 60
// Get deployment details
def podStatus = sh(
script: """
kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} \
-o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.phase}{" "}{.status.containerStatuses[0].restartCount}{"\\n"}{end}'
""",
returnStdout: true
).trim()
def deployedImage = sh(
script: """
kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
-o jsonpath='{.spec.template.spec.containers[0].image}'
""",
returnStdout: true
).trim()
def replicas = sh(
script: """
kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \
-o jsonpath='{.status.replicas}'
""",
returnStdout: true
).trim()
echo """
✅ DEPLOYMENT SUCCESS!
Application: ${APP_NAME}
Image: ${deployedImage}
Namespace: ${NAMESPACE}
Build: #${BUILD_NUMBER}
Duration: ${durationMin}m ${durationSec}s
All checks passed! ✨
"""
// Send detailed success notification
sendTelegramNotification(
status: 'SUCCESS',
message: """
✅ <b>Deployment Successful!</b>
<b>📦 Application Details</b>
<b>Name:</b> ${APP_NAME}
<b>Build:</b> #${BUILD_NUMBER}
<b>Branch:</b> ${env.BRANCH_NAME}
<b>Namespace:</b> ${NAMESPACE}
━━━━━━━━━━━━━━━━━━━━━━
<b>🐳 Docker Image</b>
<b>Registry:</b> ${DOCKER_REGISTRY}
<b>Repository:</b> ${DOCKER_REPO}/${APP_NAME}
<b>Tag:</b> ${IMAGE_TAG}
<b>Full Image:</b> ${deployedImage}
━━━━━━━━━━━━━━━━━━━━━━
<b>☸️ Kubernetes Status</b>
<b>Replicas:</b> ${replicas}/${replicas} Ready
<b>Rollout:</b> Completed ✅
<b>Health:</b> All pods healthy
<b> Deployment Metrics</b>
<b>Duration:</b> ${durationMin}m ${durationSec}s
<b>Started:</b> ${new Date(DEPLOYMENT_START_TIME.toLong() * 1000).format('HH:mm:ss')}
<b>Completed:</b> ${new Date(DEPLOYMENT_END_TIME.toLong() * 1000).format('HH:mm:ss')}
<b>🔗 Links</b>
<a href="${BUILD_URL}">Jenkins Build</a>
<a href="${GITEA_URL}/${GITEA_REPO}">GitOps Repository</a>
<i>Deployed by Jenkins CI/CD Pipeline</i> 🚀
""",
color: '✅'
)
// Cleanup rollback files
sh """
rm -f /tmp/previous_image_${BUILD_NUMBER}.txt
rm -f /tmp/previous_replicas_${BUILD_NUMBER}.txt
rm -f /tmp/previous_commit_${BUILD_NUMBER}.txt
"""
}
}
failure {
script {
def errorDetails = ""
def previousImage = "unknown"
try {
errorDetails = sh(
script: """
kubectl get events -n ${NAMESPACE} --sort-by='.lastTimestamp' | tail -10
""",
returnStdout: true
).trim()
} catch (Exception e) {
errorDetails = "Could not fetch events: ${e.message}"
}
if (env.BRANCH_NAME == 'main' && env.ROLLBACK_ENABLED == 'true') {
echo """
❌ DEPLOYMENT FAILED - INITIATING ROLLBACK!
Rolling back to previous version...
"""
sendTelegramNotification(
status: 'ROLLING_BACK',
message: """
🔄 <b>Deployment Failed - Rolling Back</b>
<b>Application:</b> ${APP_NAME}
<b>Failed Build:</b> #${BUILD_NUMBER}
<b>Image:</b> ${IMAGE_TAG}
<i>Initiating automatic rollback...</i>
""",
color: '⚠️'
)
try {
previousImage = sh(
script: "cat /tmp/previous_image_${BUILD_NUMBER}.txt 2>/dev/null || echo 'none'",
returnStdout: true
).trim()
if (previousImage != 'none' && previousImage != '') {
echo "🔄 Rolling back to: ${previousImage}"
sh """
kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE}
kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=180s
"""
withCredentials([usernamePassword(
credentialsId: 'gitea-credentials',
usernameVariable: 'GIT_USER',
passwordVariable: 'GIT_PASS'
)]) {
sh """
cd k3s-gitops || git clone http://\${GIT_USER}:\${GIT_PASS}@gitea-http.gitea.svc.cluster.local:3000/admin/k3s-gitops.git
cd k3s-gitops
git config user.name "Jenkins"
git config user.email "jenkins@thedevops.dev"
git revert --no-edit HEAD || true
git push origin main || true
"""
}
sendTelegramNotification(
status: 'ROLLBACK_SUCCESS',
message: """
<b>Rollback Completed</b>
━━━━━━━━━━━━━━━━━━━━━━
<b>📦 Application</b>
<b>Name:</b> ${APP_NAME}
<b>Failed Build:</b> #${BUILD_NUMBER}
<b>Rolled Back To:</b> ${previousImage}
━━━━━━━━━━━━━━━━━━━━━━
<b>🔄 Rollback Status</b>
<b>Status:</b> Success ✅
<b>Method:</b> kubectl rollout undo
<b>Git:</b> Commit reverted
━━━━━━━━━━━━━━━━━━━━━━
<b>📋 Recent Events</b>
<code>${errorDetails.take(500)}</code>
━━━━━━━━━━━━━━━━━━━━━━
<b>⚠️ Action Required</b>
Please review logs and fix issues before redeploying
<a href="${BUILD_URL}console">View Build Logs</a>
""",
color: '✅'
)
} else {
sendTelegramNotification(
status: 'ROLLBACK_FAILED',
message: """
❌ <b>Rollback Failed</b>
<b>Application:</b> ${APP_NAME}
<b>Build:</b> #${BUILD_NUMBER}
<b>Error:</b> No previous version found
<b>⚠️ MANUAL INTERVENTION REQUIRED</b>
kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE}
<a href="${BUILD_URL}console">View Build Logs</a>
""",
color: '🔴'
)
}
} catch (Exception e) {
sendTelegramNotification(
status: 'ROLLBACK_FAILED',
message: """
<b>Rollback Failed</b>
<b>Application:</b> ${APP_NAME}
<b>Build:</b> #${BUILD_NUMBER}
<b>Error:</b> ${e.message}
<b> MANUAL ROLLBACK REQUIRED</b>
kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE}
<a href="${BUILD_URL}console">View Build Logs</a>
""",
color: '🔴'
)
}
} else {
sendTelegramNotification(
status: 'FAILED',
message: """
❌ <b>Deployment Failed</b>
<b>📦 Application</b>
<b>Name:</b> ${APP_NAME}
<b>Build:</b> #${BUILD_NUMBER}
<b>Branch:</b> ${env.BRANCH_NAME}
<b>Image:</b> ${IMAGE_TAG}
━━━━━━━━━━━━━━━━━━━━━━
<b>📋 Recent Events</b>
<code>${errorDetails.take(500)}</code>
━━━━━━━━━━━━━━━━━━━━━━
<b>🔗 Links</b>
<a href="${BUILD_URL}console">View Console Output</a>
<a href="${BUILD_URL}">Build Details</a>
<i>Rollback: ${env.ROLLBACK_ENABLED == 'true' ? 'Enabled but not on main branch' : 'Disabled'}</i>
""",
color: '🔴'
)
}
}
}
always {
sh """
docker rmi ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} || true
docker rmi ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest || true
docker stop test-${BUILD_NUMBER} 2>/dev/null || true
docker rm test-${BUILD_NUMBER} 2>/dev/null || true
"""
cleanWs()
}
}
}
// Telegram notification function
def sendTelegramNotification(Map args) {
def status = args.status
def message = args.message
def color = args.color ?: '🔵'
try {
// HTML formatting for Telegram
def formattedMessage = message.trim()
sh """
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d parse_mode="HTML" \
-d disable_web_page_preview=true \
-d text="${formattedMessage}"
"""
echo "📱 Telegram notification sent: ${status}"
} catch (Exception e) {
echo "⚠️ Failed to send Telegram notification: ${e.message}"
// Don't fail the pipeline if notification fails
}
}