This commit is contained in:
271
DEPLOY.md
271
DEPLOY.md
@@ -14,6 +14,8 @@
|
|||||||
6. [Troubleshooting Guide](#6-troubleshooting-guide)
|
6. [Troubleshooting Guide](#6-troubleshooting-guide)
|
||||||
7. [Updating the Application](#7-updating-the-application)
|
7. [Updating the Application](#7-updating-the-application)
|
||||||
8. [Teardown](#8-teardown)
|
8. [Teardown](#8-teardown)
|
||||||
|
9. [CI/CD — Jenkins Multibranch Pipeline](#9-cicd--jenkins-multibranch-pipeline)
|
||||||
|
10. [GitOps — ArgoCD (Reference)](#10-gitops--argocd-reference)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -797,6 +799,275 @@ kubectl delete -f https://raw.githubusercontent.com/rancher/local-path-provision
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. CI/CD — Jenkins Multibranch Pipeline
|
||||||
|
|
||||||
|
This project ships with a `Jenkinsfile` at the repo root that automates the full
|
||||||
|
build → test → push → deploy lifecycle.
|
||||||
|
|
||||||
|
### 9.1 Prerequisites on the Jenkins Server
|
||||||
|
|
||||||
|
| Tool | Purpose | Install |
|
||||||
|
|------|---------|---------|
|
||||||
|
| Docker | Build & push images | `apt install docker.io` + add `jenkins` user to `docker` group |
|
||||||
|
| kubectl | Apply k8s manifests | [Official install guide](https://kubernetes.io/docs/tasks/tools/) |
|
||||||
|
| kustomize | Render overlays | `curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash` |
|
||||||
|
| NodeJS (v18+) | `npm ci` / `npm test` | Jenkins NodeJS plugin or system package |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add jenkins user to docker group (run on Jenkins host, then restart Jenkins)
|
||||||
|
sudo usermod -aG docker jenkins
|
||||||
|
sudo systemctl restart jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Jenkins Plugins Required
|
||||||
|
|
||||||
|
Install via **Manage Jenkins → Plugins**:
|
||||||
|
|
||||||
|
| Plugin | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| **Multibranch Pipeline** | Detects branches + PRs automatically |
|
||||||
|
| **Docker Pipeline** | Docker credential binding |
|
||||||
|
| **Credentials Binding** | Injects secrets into pipeline steps |
|
||||||
|
| **NodeJS** | Managed Node.js installations |
|
||||||
|
| **Git** | SCM checkout |
|
||||||
|
|
||||||
|
### 9.3 Configure Jenkins Credentials
|
||||||
|
|
||||||
|
Go to **Manage Jenkins → Credentials → System → Global credentials → Add**:
|
||||||
|
|
||||||
|
| Credential ID | Kind | Fields | Used for |
|
||||||
|
|---------------|------|--------|---------|
|
||||||
|
| `dockerhub-credentials` | Username with password | Username: `subkamble` / Password: Docker Hub PAT | `docker push` |
|
||||||
|
| `kubeconfig-secret` | Secret file | Upload your `~/.kube/config` | `kubectl` cluster access |
|
||||||
|
|
||||||
|
> **Docker Hub PAT:** Generate at hub.docker.com → Account Settings → Security → New Access Token (Read/Write scope).
|
||||||
|
|
||||||
|
> **kubeconfig tip:** If Jenkins runs inside the same cluster, use a ServiceAccount + RBAC instead of a kubeconfig file.
|
||||||
|
|
||||||
|
### 9.4 Create the Multibranch Pipeline Job
|
||||||
|
|
||||||
|
1. **Jenkins Dashboard → New Item**
|
||||||
|
2. Name: `react-mysql` → select **Multibranch Pipeline** → OK
|
||||||
|
3. Under **Branch Sources** → Add source → **Git**
|
||||||
|
- Repository URL: your repo URL
|
||||||
|
- Credentials: add repo access if private
|
||||||
|
4. Under **Build Configuration** → Mode: **by Jenkinsfile** → Script Path: `Jenkinsfile`
|
||||||
|
5. Under **Scan Multibranch Pipeline Triggers** → enable **Periodically if not otherwise run** (e.g. every 5 min), or configure a webhook (preferred)
|
||||||
|
6. Save → **Scan Multibranch Pipeline Now**
|
||||||
|
|
||||||
|
Jenkins will discover all branches and create sub-jobs automatically.
|
||||||
|
|
||||||
|
### 9.5 Webhook Setup (recommended — avoids polling)
|
||||||
|
|
||||||
|
**GitHub:**
|
||||||
|
1. Repo → Settings → Webhooks → Add webhook
|
||||||
|
2. Payload URL: `http://<JENKINS_URL>/multibranch-webhook-trigger/invoke?token=react-mysql`
|
||||||
|
3. Content type: `application/json`
|
||||||
|
4. Events: **Push** + **Pull requests**
|
||||||
|
|
||||||
|
Install the **Multibranch Scan Webhook Trigger** plugin on Jenkins to handle the token.
|
||||||
|
|
||||||
|
### 9.6 Branch Behavior Summary
|
||||||
|
|
||||||
|
| Branch | Build | Test | Push Image | Deploy |
|
||||||
|
|--------|-------|------|-----------|--------|
|
||||||
|
| `main` | Yes | Yes | Yes (`latest` + SHA tag) | `k8s/overlays/onpremise` (production) |
|
||||||
|
| `staging` | Yes | Yes | Yes (branch-SHA tag) | `k8s/overlays/minikube` (staging) |
|
||||||
|
| `develop` | Yes | Yes | No | No |
|
||||||
|
| `feature/**` | Yes | Yes | No | No |
|
||||||
|
| PR branches | Yes | Yes | No | No |
|
||||||
|
|
||||||
|
### 9.7 Image Tagging Convention
|
||||||
|
|
||||||
|
| Branch | Tag format | Example |
|
||||||
|
|--------|-----------|---------|
|
||||||
|
| `main` | `latest` + `<sha8>` | `latest`, `a1b2c3d4` |
|
||||||
|
| `staging` | `staging-<sha8>` | `staging-a1b2c3d4` |
|
||||||
|
| `feature/login` | `feature-login-<sha8>` | `feature-login-a1b2c3d4` |
|
||||||
|
|
||||||
|
### 9.8 First-Run Checklist
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify Jenkins can reach Docker Hub
|
||||||
|
docker login -u subkamble
|
||||||
|
|
||||||
|
# Verify Jenkins can reach the cluster
|
||||||
|
kubectl get nodes
|
||||||
|
|
||||||
|
# Verify kustomize is on PATH
|
||||||
|
kustomize version
|
||||||
|
|
||||||
|
# Trigger a manual build for main branch from Jenkins UI
|
||||||
|
# then watch the Console Output for each stage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.9 Rollback
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List available image tags (via Docker Hub API or local docker images)
|
||||||
|
docker images subkamble/react-mysql-backend
|
||||||
|
|
||||||
|
# Pin a specific tag manually and re-deploy
|
||||||
|
cd k8s/base
|
||||||
|
kustomize edit set image \
|
||||||
|
subkamble/react-mysql-backend=subkamble/react-mysql-backend:<previous-sha> \
|
||||||
|
subkamble/react-mysql-frontend=subkamble/react-mysql-frontend:<previous-sha>
|
||||||
|
cd -
|
||||||
|
kubectl apply -k k8s/overlays/onpremise
|
||||||
|
kubectl rollout status deployment/backend -n react-mysql
|
||||||
|
kubectl rollout status deployment/frontend -n react-mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. GitOps — ArgoCD (Reference)
|
||||||
|
|
||||||
|
> The Jenkinsfile contains fully commented-out ArgoCD pipeline stages.
|
||||||
|
> Follow this section to activate GitOps mode.
|
||||||
|
|
||||||
|
### 10.1 Install ArgoCD
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create namespace argocd
|
||||||
|
kubectl apply -n argocd \
|
||||||
|
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
|
||||||
|
|
||||||
|
# Wait for all ArgoCD pods to be ready
|
||||||
|
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=180s
|
||||||
|
|
||||||
|
# Expose the ArgoCD API server (choose one):
|
||||||
|
|
||||||
|
# Option A — Port-forward (local access only)
|
||||||
|
kubectl port-forward svc/argocd-server -n argocd 8080:443
|
||||||
|
|
||||||
|
# Option B — LoadBalancer (on-prem with MetalLB or cloud)
|
||||||
|
kubectl patch svc argocd-server -n argocd \
|
||||||
|
-p '{"spec":{"type":"LoadBalancer"}}'
|
||||||
|
|
||||||
|
# Option C — Ingress (add argocd.myapp.local to your Ingress controller)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 Get Initial Admin Password
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get secret argocd-initial-admin-secret \
|
||||||
|
-n argocd \
|
||||||
|
-o jsonpath="{.data.password}" | base64 -d && echo
|
||||||
|
|
||||||
|
# Login
|
||||||
|
argocd login localhost:8080 --username admin --password <above-password> --insecure
|
||||||
|
|
||||||
|
# Change password immediately
|
||||||
|
argocd account update-password
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 Apply ArgoCD Application Manifests
|
||||||
|
|
||||||
|
Create the two Application objects (one per environment).
|
||||||
|
These are documented in full inside the `Jenkinsfile` comments.
|
||||||
|
Here is the summary of what they do:
|
||||||
|
|
||||||
|
| Application | Watches branch | Overlay | Sync policy |
|
||||||
|
|-------------|---------------|---------|------------|
|
||||||
|
| `react-mysql-production` | `main` | `k8s/overlays/onpremise` | Automated (prune + selfHeal) |
|
||||||
|
| `react-mysql-staging` | `staging` | `k8s/overlays/minikube` | Automated (prune + selfHeal) |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the YAML blocks from the Jenkinsfile comments into files, then:
|
||||||
|
kubectl apply -f argocd/application-production.yaml
|
||||||
|
kubectl apply -f argocd/application-staging.yaml
|
||||||
|
|
||||||
|
# Verify apps are registered
|
||||||
|
argocd app list
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.4 Jenkins Credentials for GitOps Mode
|
||||||
|
|
||||||
|
Add these alongside the existing credentials:
|
||||||
|
|
||||||
|
| Credential ID | Kind | Purpose |
|
||||||
|
|---------------|------|---------|
|
||||||
|
| `argocd-auth-token` | Secret text | ArgoCD API token for `argocd app sync` |
|
||||||
|
| `gitops-ssh-key` | SSH username with private key | Push image tag updates back to Git |
|
||||||
|
|
||||||
|
**Generate an ArgoCD API token:**
|
||||||
|
```bash
|
||||||
|
argocd account generate-token --account admin
|
||||||
|
# Copy the output → paste as the 'argocd-auth-token' secret text
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.5 Activate GitOps in the Jenkinsfile
|
||||||
|
|
||||||
|
1. Open `Jenkinsfile`
|
||||||
|
2. Remove the active **Deploy** and **Smoke Test** stages (or leave them as fallback)
|
||||||
|
3. Uncomment the `Update GitOps Manifest` and `ArgoCD Sync` stage blocks in the GitOps section at the bottom
|
||||||
|
4. Replace `YOUR_ARGOCD_SERVER` with your ArgoCD server hostname/IP
|
||||||
|
5. Replace `YOUR_ORG` in the git clone URL with your GitHub org/username
|
||||||
|
6. Commit and push — Jenkins picks up the change automatically
|
||||||
|
|
||||||
|
### 10.6 GitOps Flow Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
Developer pushes code
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Jenkins CI
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ 1. npm ci → test │
|
||||||
|
│ 2. docker build + push :<sha-tag> │
|
||||||
|
│ 3. kustomize edit set image │
|
||||||
|
│ 4. git commit + push [skip ci] │
|
||||||
|
└─────────────────┬───────────────────┘
|
||||||
|
│ Git push (image tag bump)
|
||||||
|
▼
|
||||||
|
Git Repository
|
||||||
|
(k8s/base/kustomization.yaml updated)
|
||||||
|
│
|
||||||
|
│ ArgoCD polls every 3 min
|
||||||
|
│ (or Jenkins triggers sync via argocd CLI)
|
||||||
|
▼
|
||||||
|
ArgoCD detects drift
|
||||||
|
┌────────────────────────────┐
|
||||||
|
│ kubectl apply -k overlay/ │
|
||||||
|
│ prune removed resources │
|
||||||
|
│ self-heal manual changes │
|
||||||
|
└────────────┬───────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Kubernetes Cluster
|
||||||
|
(react-mysql namespace updated)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.7 ArgoCD Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View app status
|
||||||
|
argocd app get react-mysql-production
|
||||||
|
|
||||||
|
# Force immediate sync (skip waiting for poll interval)
|
||||||
|
argocd app sync react-mysql-production
|
||||||
|
|
||||||
|
# Rollback to previous revision
|
||||||
|
argocd app history react-mysql-production
|
||||||
|
argocd app rollback react-mysql-production <revision-id>
|
||||||
|
|
||||||
|
# Pause auto-sync (for maintenance)
|
||||||
|
argocd app set react-mysql-production --sync-policy none
|
||||||
|
|
||||||
|
# Resume auto-sync
|
||||||
|
argocd app set react-mysql-production --sync-policy automated
|
||||||
|
|
||||||
|
# Delete app (does NOT delete k8s resources by default)
|
||||||
|
argocd app delete react-mysql-production
|
||||||
|
|
||||||
|
# Delete app AND k8s resources
|
||||||
|
argocd app delete react-mysql-production --cascade
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Quick Reference Card
|
## Quick Reference Card
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
428
Jenkinsfile
vendored
Normal file
428
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Multibranch Pipeline - react-mysql
|
||||||
|
// Docker Hub: subkamble/react-mysql-backend, subkamble/react-mysql-frontend
|
||||||
|
// Kubernetes Namespaces: react-mysql
|
||||||
|
// Overlays: k8s/overlays/minikube | k8s/overlays/onpremise
|
||||||
|
//
|
||||||
|
// Branch Strategy:
|
||||||
|
// main → deploy to production (onpremise overlay)
|
||||||
|
// staging → deploy to staging (minikube overlay, or staging namespace)
|
||||||
|
// develop → build + test only (no deploy)
|
||||||
|
// feature/** → build + test only (no deploy)
|
||||||
|
// PR branches → build + test only (no deploy)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Environment variables - sensitive values come from Jenkins Credentials
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
environment {
|
||||||
|
DOCKER_HUB_USER = 'subkamble'
|
||||||
|
BACKEND_IMAGE = "${DOCKER_HUB_USER}/react-mysql-backend"
|
||||||
|
FRONTEND_IMAGE = "${DOCKER_HUB_USER}/react-mysql-frontend"
|
||||||
|
|
||||||
|
// Jenkins credential IDs (configure in Jenkins > Credentials)
|
||||||
|
DOCKER_CREDENTIALS = 'dockerhub-credentials' // kind: Username/Password
|
||||||
|
KUBECONFIG_SECRET = 'kubeconfig-secret' // kind: Secret File
|
||||||
|
|
||||||
|
// Derived from branch name
|
||||||
|
IS_MAIN = "${env.BRANCH_NAME == 'main'}"
|
||||||
|
IS_STAGING = "${env.BRANCH_NAME == 'staging'}"
|
||||||
|
IS_DEPLOYABLE = "${env.BRANCH_NAME == 'main' || env.BRANCH_NAME == 'staging'}"
|
||||||
|
|
||||||
|
// Image tag: use git short SHA for traceability
|
||||||
|
GIT_SHORT_SHA = "${env.GIT_COMMIT ? env.GIT_COMMIT.take(8) : 'unknown'}"
|
||||||
|
IMAGE_TAG = "${env.BRANCH_NAME == 'main' ? 'latest' : env.BRANCH_NAME.replaceAll('/', '-') + '-' + GIT_SHORT_SHA}"
|
||||||
|
}
|
||||||
|
|
||||||
|
options {
|
||||||
|
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||||
|
timeout(time: 30, unit: 'MINUTES')
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
timestamps()
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// STAGE 1: Checkout & Metadata
|
||||||
|
// =====================================================================
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
script {
|
||||||
|
echo "Branch : ${env.BRANCH_NAME}"
|
||||||
|
echo "Commit SHA : ${env.GIT_COMMIT}"
|
||||||
|
echo "Image Tag : ${env.IMAGE_TAG}"
|
||||||
|
echo "Deployable : ${env.IS_DEPLOYABLE}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// STAGE 2: Install Dependencies & Lint
|
||||||
|
// =====================================================================
|
||||||
|
stage('Install & Lint') {
|
||||||
|
parallel {
|
||||||
|
stage('Backend - Install') {
|
||||||
|
steps {
|
||||||
|
dir('backend') {
|
||||||
|
sh 'npm ci --prefer-offline'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Frontend - Install') {
|
||||||
|
steps {
|
||||||
|
dir('frontend') {
|
||||||
|
sh 'npm ci --prefer-offline'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// STAGE 3: Test
|
||||||
|
// =====================================================================
|
||||||
|
stage('Test') {
|
||||||
|
parallel {
|
||||||
|
stage('Backend - Test') {
|
||||||
|
steps {
|
||||||
|
dir('backend') {
|
||||||
|
// Replace with your actual test command
|
||||||
|
sh 'npm test --if-present || echo "No backend tests configured"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Frontend - Test') {
|
||||||
|
steps {
|
||||||
|
dir('frontend') {
|
||||||
|
// CI=true prevents interactive watch mode in React
|
||||||
|
sh 'CI=true npm test --if-present || echo "No frontend tests configured"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// STAGE 4: Docker Build (all branches)
|
||||||
|
// =====================================================================
|
||||||
|
stage('Docker Build') {
|
||||||
|
parallel {
|
||||||
|
stage('Build Backend') {
|
||||||
|
steps {
|
||||||
|
dir('backend') {
|
||||||
|
sh "docker build -t ${BACKEND_IMAGE}:${IMAGE_TAG} ."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Build Frontend') {
|
||||||
|
steps {
|
||||||
|
dir('frontend') {
|
||||||
|
sh "docker build -t ${FRONTEND_IMAGE}:${IMAGE_TAG} ."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// STAGE 5: Docker Push (main + staging branches only)
|
||||||
|
// =====================================================================
|
||||||
|
stage('Docker Push') {
|
||||||
|
when {
|
||||||
|
expression { env.IS_DEPLOYABLE == 'true' }
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
withCredentials([usernamePassword(
|
||||||
|
credentialsId: "${DOCKER_CREDENTIALS}",
|
||||||
|
usernameVariable: 'DOCKER_USER',
|
||||||
|
passwordVariable: 'DOCKER_PASS'
|
||||||
|
)]) {
|
||||||
|
sh '''
|
||||||
|
echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin
|
||||||
|
docker push ${BACKEND_IMAGE}:${IMAGE_TAG}
|
||||||
|
docker push ${FRONTEND_IMAGE}:${IMAGE_TAG}
|
||||||
|
docker logout
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
// Also tag + push as 'latest' on main
|
||||||
|
script {
|
||||||
|
if (env.BRANCH_NAME == 'main') {
|
||||||
|
withCredentials([usernamePassword(
|
||||||
|
credentialsId: "${DOCKER_CREDENTIALS}",
|
||||||
|
usernameVariable: 'DOCKER_USER',
|
||||||
|
passwordVariable: 'DOCKER_PASS'
|
||||||
|
)]) {
|
||||||
|
sh '''
|
||||||
|
echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin
|
||||||
|
docker tag ${BACKEND_IMAGE}:${IMAGE_TAG} ${BACKEND_IMAGE}:latest
|
||||||
|
docker tag ${FRONTEND_IMAGE}:${IMAGE_TAG} ${FRONTEND_IMAGE}:latest
|
||||||
|
docker push ${BACKEND_IMAGE}:latest
|
||||||
|
docker push ${FRONTEND_IMAGE}:latest
|
||||||
|
docker logout
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// STAGE 6: Deploy to Kubernetes
|
||||||
|
//
|
||||||
|
// main → onpremise overlay (production)
|
||||||
|
// staging → minikube overlay (staging / dev cluster)
|
||||||
|
// =====================================================================
|
||||||
|
stage('Deploy') {
|
||||||
|
when {
|
||||||
|
expression { env.IS_DEPLOYABLE == 'true' }
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
def overlay = (env.BRANCH_NAME == 'main') ? 'onpremise' : 'minikube'
|
||||||
|
echo "Deploying to overlay: ${overlay}"
|
||||||
|
|
||||||
|
withCredentials([file(
|
||||||
|
credentialsId: "${KUBECONFIG_SECRET}",
|
||||||
|
variable: 'KUBECONFIG'
|
||||||
|
)]) {
|
||||||
|
sh """
|
||||||
|
export KUBECONFIG=\$KUBECONFIG
|
||||||
|
|
||||||
|
# Update image tags in kustomize overlays in-place
|
||||||
|
cd k8s/base
|
||||||
|
kustomize edit set image \\
|
||||||
|
${BACKEND_IMAGE}=${BACKEND_IMAGE}:${IMAGE_TAG} \\
|
||||||
|
${FRONTEND_IMAGE}=${FRONTEND_IMAGE}:${IMAGE_TAG}
|
||||||
|
cd -
|
||||||
|
|
||||||
|
# Apply the overlay
|
||||||
|
kubectl apply -k k8s/overlays/${overlay}
|
||||||
|
|
||||||
|
# Wait for rollout
|
||||||
|
kubectl rollout status deployment/backend -n react-mysql --timeout=120s
|
||||||
|
kubectl rollout status deployment/frontend -n react-mysql --timeout=120s
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// STAGE 7: Smoke Test (post-deploy health check)
|
||||||
|
// =====================================================================
|
||||||
|
stage('Smoke Test') {
|
||||||
|
when {
|
||||||
|
expression { env.IS_DEPLOYABLE == 'true' }
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
withCredentials([file(
|
||||||
|
credentialsId: "${KUBECONFIG_SECRET}",
|
||||||
|
variable: 'KUBECONFIG'
|
||||||
|
)]) {
|
||||||
|
sh '''
|
||||||
|
export KUBECONFIG=$KUBECONFIG
|
||||||
|
# Verify pods are running
|
||||||
|
kubectl get pods -n react-mysql
|
||||||
|
# Quick readiness check
|
||||||
|
kubectl wait --for=condition=Ready pods -l app=backend -n react-mysql --timeout=60s
|
||||||
|
kubectl wait --for=condition=Ready pods -l app=frontend -n react-mysql --timeout=60s
|
||||||
|
echo "Smoke test passed."
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// POST Actions
|
||||||
|
// =========================================================================
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
// Clean up dangling local Docker images to save disk space
|
||||||
|
sh '''
|
||||||
|
docker rmi ${BACKEND_IMAGE}:${IMAGE_TAG} || true
|
||||||
|
docker rmi ${FRONTEND_IMAGE}:${IMAGE_TAG} || true
|
||||||
|
docker image prune -f || true
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
success {
|
||||||
|
echo "Pipeline SUCCESS on branch '${env.BRANCH_NAME}' — image tag: ${env.IMAGE_TAG}"
|
||||||
|
}
|
||||||
|
failure {
|
||||||
|
echo "Pipeline FAILED on branch '${env.BRANCH_NAME}'. Check logs above."
|
||||||
|
// Uncomment to send email notifications:
|
||||||
|
// emailext(
|
||||||
|
// subject: "FAILED: ${env.JOB_NAME} [${env.BUILD_NUMBER}]",
|
||||||
|
// body: "Branch: ${env.BRANCH_NAME}\nBuild URL: ${env.BUILD_URL}",
|
||||||
|
// to: 'your-team@example.com'
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// =============================================================================
|
||||||
|
//
|
||||||
|
// ██████╗ ██╗████████╗ ██████╗ ██████╗ ███████╗ ██████╗ █████╗ ████████╗
|
||||||
|
// ██╔════╝ ██║╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝ ██╔══██╗██╔══██╗╚══██╔══╝
|
||||||
|
// ██║ ███╗██║ ██║ ██║ ██║██████╔╝███████╗ ██║ ██║███████║ ██║
|
||||||
|
// ██║ ██║██║ ██║ ██║ ██║██╔═══╝ ╚════██║ ██║ ██║██╔══██║ ██║
|
||||||
|
// ╚██████╔╝██║ ██║ ╚██████╔╝██║ ███████║ ██████╔╝██║ ██║ ██║
|
||||||
|
// ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
|
||||||
|
//
|
||||||
|
// GITOPS / ARGOCD WORKFLOW (commented out — activate when ArgoCD is installed)
|
||||||
|
// =============================================================================
|
||||||
|
//
|
||||||
|
// Overview:
|
||||||
|
// Instead of Jenkins calling kubectl directly (push model), ArgoCD watches
|
||||||
|
// a Git repository for k8s manifests and syncs the cluster (pull model).
|
||||||
|
//
|
||||||
|
// Jenkins responsibility becomes:
|
||||||
|
// 1. Build & push Docker image
|
||||||
|
// 2. Update the image tag in the GitOps repo (PR or direct commit)
|
||||||
|
// 3. ArgoCD detects the change and syncs the cluster automatically
|
||||||
|
//
|
||||||
|
// Prerequisites:
|
||||||
|
// - ArgoCD installed in cluster: kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
|
||||||
|
// - argocd CLI available on Jenkins agent
|
||||||
|
// - Jenkins credentials:
|
||||||
|
// 'argocd-auth-token' → Secret Text (ArgoCD API token)
|
||||||
|
// 'gitops-ssh-key' → SSH Key (write access to GitOps repo)
|
||||||
|
// - A dedicated GitOps repo (can be this same repo or a separate one)
|
||||||
|
//
|
||||||
|
// =============================================================================
|
||||||
|
//
|
||||||
|
// ---- ARGOCD APPLICATION MANIFEST (apply once, not in pipeline) --------------
|
||||||
|
//
|
||||||
|
// # argocd/application-production.yaml
|
||||||
|
// apiVersion: argoproj.io/v1alpha1
|
||||||
|
// kind: Application
|
||||||
|
// metadata:
|
||||||
|
// name: react-mysql-production
|
||||||
|
// namespace: argocd
|
||||||
|
// spec:
|
||||||
|
// project: default
|
||||||
|
// source:
|
||||||
|
// repoURL: https://github.com/YOUR_ORG/react-mysql.git
|
||||||
|
// targetRevision: main
|
||||||
|
// path: k8s/overlays/onpremise
|
||||||
|
// destination:
|
||||||
|
// server: https://kubernetes.default.svc
|
||||||
|
// namespace: react-mysql
|
||||||
|
// syncPolicy:
|
||||||
|
// automated:
|
||||||
|
// prune: true # Remove resources no longer in Git
|
||||||
|
// selfHeal: true # Revert manual cluster changes
|
||||||
|
// syncOptions:
|
||||||
|
// - CreateNamespace=true
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
//
|
||||||
|
// # argocd/application-staging.yaml
|
||||||
|
// apiVersion: argoproj.io/v1alpha1
|
||||||
|
// kind: Application
|
||||||
|
// metadata:
|
||||||
|
// name: react-mysql-staging
|
||||||
|
// namespace: argocd
|
||||||
|
// spec:
|
||||||
|
// project: default
|
||||||
|
// source:
|
||||||
|
// repoURL: https://github.com/YOUR_ORG/react-mysql.git
|
||||||
|
// targetRevision: staging
|
||||||
|
// path: k8s/overlays/minikube
|
||||||
|
// destination:
|
||||||
|
// server: https://kubernetes.default.svc
|
||||||
|
// namespace: react-mysql
|
||||||
|
// syncPolicy:
|
||||||
|
// automated:
|
||||||
|
// prune: true
|
||||||
|
// selfHeal: true
|
||||||
|
// syncOptions:
|
||||||
|
// - CreateNamespace=true
|
||||||
|
//
|
||||||
|
// =============================================================================
|
||||||
|
//
|
||||||
|
// ---- GITOPS-ENABLED PIPELINE STAGES (replace stages 5-7 above) --------------
|
||||||
|
//
|
||||||
|
// // stage('Docker Push') { ... } ← keep as-is
|
||||||
|
//
|
||||||
|
// // stage('Update GitOps Manifest') {
|
||||||
|
// // when { expression { env.IS_DEPLOYABLE == 'true' } }
|
||||||
|
// // steps {
|
||||||
|
// // script {
|
||||||
|
// // def overlay = (env.BRANCH_NAME == 'main') ? 'onpremise' : 'minikube'
|
||||||
|
// // withCredentials([sshUserPrivateKey(
|
||||||
|
// // credentialsId: 'gitops-ssh-key',
|
||||||
|
// // keyFileVariable: 'SSH_KEY'
|
||||||
|
// // )]) {
|
||||||
|
// // sh """
|
||||||
|
// // # Configure git SSH
|
||||||
|
// // GIT_SSH_COMMAND="ssh -i \$SSH_KEY -o StrictHostKeyChecking=no"
|
||||||
|
// // export GIT_SSH_COMMAND
|
||||||
|
// //
|
||||||
|
// // # Clone the GitOps repo (or use current repo)
|
||||||
|
// // git clone git@github.com:YOUR_ORG/react-mysql.git gitops-repo
|
||||||
|
// // cd gitops-repo
|
||||||
|
// //
|
||||||
|
// // git config user.email "jenkins@ci.local"
|
||||||
|
// // git config user.name "Jenkins CI"
|
||||||
|
// //
|
||||||
|
// // # Bump image tags using kustomize
|
||||||
|
// // cd k8s/base
|
||||||
|
// // kustomize edit set image \\
|
||||||
|
// // ${BACKEND_IMAGE}=${BACKEND_IMAGE}:${IMAGE_TAG} \\
|
||||||
|
// // ${FRONTEND_IMAGE}=${FRONTEND_IMAGE}:${IMAGE_TAG}
|
||||||
|
// // cd ../..
|
||||||
|
// //
|
||||||
|
// // git add k8s/
|
||||||
|
// // git commit -m "ci: bump images to ${IMAGE_TAG} [${env.BRANCH_NAME}] [skip ci]"
|
||||||
|
// // git push origin ${env.BRANCH_NAME}
|
||||||
|
// // """
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
//
|
||||||
|
// // stage('ArgoCD Sync') {
|
||||||
|
// // when { expression { env.IS_DEPLOYABLE == 'true' } }
|
||||||
|
// // steps {
|
||||||
|
// // script {
|
||||||
|
// // def appName = (env.BRANCH_NAME == 'main') ? 'react-mysql-production' : 'react-mysql-staging'
|
||||||
|
// // withCredentials([string(
|
||||||
|
// // credentialsId: 'argocd-auth-token',
|
||||||
|
// // variable: 'ARGOCD_TOKEN'
|
||||||
|
// // )]) {
|
||||||
|
// // sh """
|
||||||
|
// // # Login to ArgoCD (token-based)
|
||||||
|
// // argocd login YOUR_ARGOCD_SERVER \\
|
||||||
|
// // --auth-token \$ARGOCD_TOKEN \\
|
||||||
|
// // --grpc-web \\
|
||||||
|
// // --insecure
|
||||||
|
// //
|
||||||
|
// // # Trigger sync (ArgoCD auto-detects git changes too,
|
||||||
|
// // # but this forces an immediate sync)
|
||||||
|
// // argocd app sync ${appName} --prune --timeout 120
|
||||||
|
// //
|
||||||
|
// // # Wait for sync to complete
|
||||||
|
// // argocd app wait ${appName} --health --timeout 180
|
||||||
|
// //
|
||||||
|
// // echo "ArgoCD sync complete for ${appName}"
|
||||||
|
// // """
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
//
|
||||||
|
// // stage('Smoke Test') { ... } ← keep as-is (or query ArgoCD health instead)
|
||||||
|
//
|
||||||
|
// =============================================================================
|
||||||
|
// END GITOPS / ARGOCD SECTION
|
||||||
|
// =============================================================================
|
||||||
Reference in New Issue
Block a user