CI ko builder VM par bhejo, CD controller par: docker.sock hataya, secrets ab Jenkins credentials se
This commit is contained in:
parent
2122ef2437
commit
7e4b66556e
135
Jenkinsfile
vendored
135
Jenkinsfile
vendored
@ -1,5 +1,8 @@
|
|||||||
pipeline {
|
pipeline {
|
||||||
agent any
|
// Koi default agent nahi. Har stage khud batati hai kahan chalegi -
|
||||||
|
// CI builder VM par, CD controller par. Isse build ko production ki
|
||||||
|
// chaabi kabhi nahi milti.
|
||||||
|
agent none
|
||||||
|
|
||||||
parameters {
|
parameters {
|
||||||
string(name: 'ROLLBACK_TAG', defaultValue: '',
|
string(name: 'ROLLBACK_TAG', defaultValue: '',
|
||||||
@ -9,18 +12,17 @@ pipeline {
|
|||||||
environment {
|
environment {
|
||||||
REGISTRY = 'ns31240276.ip-51-195-4.eu:5000'
|
REGISTRY = 'ns31240276.ip-51-195-4.eu:5000'
|
||||||
IMAGE_NAME = 'swim/demo-app'
|
IMAGE_NAME = 'swim/demo-app'
|
||||||
VAULT_ADDR = 'http://vault:8200'
|
// Sab jagah se ek hi naam. Controller isse proxy par bhejta hai
|
||||||
// Jenkins container ka workspace /var/jenkins_home/... hai, par docker
|
// (extra_hosts), builder VM bridge IP par (/etc/hosts). TLS cert
|
||||||
// daemon HOST par chalta hai jahan wahi folder /opt/devops/data/jenkins/...
|
// bhi isi naam ka hai, isliye andar ke DNS naam use nahi karte.
|
||||||
// par hai. `docker run -v` ko HOST ka path chahiye.
|
VAULT_ADDR = 'https://ns31240276.ip-51-195-4.eu:8444'
|
||||||
JENKINS_HOME_CONTAINER = '/var/jenkins_home'
|
SONAR_URL = 'https://ns31240276.ip-51-195-4.eu:8446'
|
||||||
JENKINS_HOME_HOST = '/opt/devops/data/jenkins'
|
HOST_BRIDGE_IP = '192.168.124.1'
|
||||||
TRIVY_SEVERITY = 'HIGH,CRITICAL'
|
TRIVY_SEVERITY = 'HIGH,CRITICAL'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger ko YAHAN declare karna zaroori hai. Agar sirf job config.xml me ho
|
// Trigger ko YAHAN declare karna zaroori hai. Sirf config.xml me hone par
|
||||||
// aur Jenkinsfile me na ho, to DeclarativeJobPropertyTrackerAction NPE deta hai.
|
// DeclarativeJobPropertyTrackerAction NPE deta hai.
|
||||||
// Khaali spec = polling band, sirf webhook (notifyCommit) se chalega.
|
|
||||||
triggers {
|
triggers {
|
||||||
pollSCM('')
|
pollSCM('')
|
||||||
}
|
}
|
||||||
@ -28,20 +30,27 @@ pipeline {
|
|||||||
options {
|
options {
|
||||||
timestamps()
|
timestamps()
|
||||||
buildDiscarder(logRotator(numToKeepStr: '20'))
|
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||||
timeout(time: 30, unit: 'MINUTES')
|
timeout(time: 40, unit: 'MINUTES')
|
||||||
disableConcurrentBuilds()
|
disableConcurrentBuilds()
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
|
|
||||||
|
// ============ CI: sab kuch builder VM par ============
|
||||||
|
// Yahan docker daemon builder VM ka hai. Build escape hui to attacker ko
|
||||||
|
// sirf ye VM milti hai - na secrets, na Vault, na production ki key.
|
||||||
|
stage('CI (builder-vm-01)') {
|
||||||
|
agent { label 'builder' }
|
||||||
|
|
||||||
|
stages {
|
||||||
stage('Init') {
|
stage('Init') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
// params ko environment block me mat chhuo - wahan null hone par
|
// params ko environment block me mat chhuo - wahan null hone par NPE.
|
||||||
// NPE aata hai. Yahan script block me safe hai.
|
|
||||||
def rb = (params.ROLLBACK_TAG ?: '').trim()
|
def rb = (params.ROLLBACK_TAG ?: '').trim()
|
||||||
env.IS_ROLLBACK = rb ? 'true' : 'false'
|
env.IS_ROLLBACK = rb ? 'true' : 'false'
|
||||||
env.IMAGE_TAG = rb ? rb : "${env.BUILD_NUMBER}"
|
env.IMAGE_TAG = rb ? rb : "${env.BUILD_NUMBER}"
|
||||||
|
echo "NODE : ${env.NODE_NAME}"
|
||||||
echo "MODE : ${env.IS_ROLLBACK == 'true' ? 'ROLLBACK' : 'NORMAL BUILD'}"
|
echo "MODE : ${env.IS_ROLLBACK == 'true' ? 'ROLLBACK' : 'NORMAL BUILD'}"
|
||||||
echo "IMAGE_TAG : ${env.IMAGE_TAG}"
|
echo "IMAGE_TAG : ${env.IMAGE_TAG}"
|
||||||
}
|
}
|
||||||
@ -57,11 +66,13 @@ pipeline {
|
|||||||
|
|
||||||
stage('Secrets from Vault') {
|
stage('Secrets from Vault') {
|
||||||
steps {
|
steps {
|
||||||
|
// Token ab file se nahi, Jenkins credential se aata hai aur
|
||||||
|
// log me masked rehta hai. Ye token sirf swim/registry padh
|
||||||
|
// sakta hai - baaki Vault iske liye band hai.
|
||||||
|
withCredentials([string(credentialsId: 'vault-builder-token', variable: 'VAULT_TOKEN')]) {
|
||||||
sh '''
|
sh '''
|
||||||
set +x
|
set +x
|
||||||
VT=$(cat /var/jenkins_secrets/vault_token)
|
curl -sf -H "X-Vault-Token: $VAULT_TOKEN" \
|
||||||
# Vault se registry ka password lo - Jenkins credential se NAHI
|
|
||||||
curl -sf -H "X-Vault-Token: $VT" \
|
|
||||||
"${VAULT_ADDR}/v1/swim/data/registry" \
|
"${VAULT_ADDR}/v1/swim/data/registry" \
|
||||||
| python3 -c "
|
| python3 -c "
|
||||||
import sys, json
|
import sys, json
|
||||||
@ -74,13 +85,13 @@ print(' Vault se registry creds mil gaye (user=%s)' % d['username'])
|
|||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stage('Test') {
|
stage('Test') {
|
||||||
when { expression { env.IS_ROLLBACK != 'true' } }
|
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|")
|
docker run --rm -v "$PWD":/src -w /src python:3.12-slim sh -c "
|
||||||
docker run --rm -v "$HOST_WS":/src -w /src python:3.12-slim sh -c "
|
|
||||||
pip install --quiet -r requirements-dev.txt &&
|
pip install --quiet -r requirements-dev.txt &&
|
||||||
python -m pytest tests/ -v
|
python -m pytest tests/ -v
|
||||||
"
|
"
|
||||||
@ -91,20 +102,20 @@ print(' Vault se registry creds mil gaye (user=%s)' % d['username'])
|
|||||||
stage('Code quality (SonarQube)') {
|
stage('Code quality (SonarQube)') {
|
||||||
when { expression { env.IS_ROLLBACK != 'true' } }
|
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||||
steps {
|
steps {
|
||||||
|
withCredentials([string(credentialsId: 'sonar-token', variable: 'SONAR_TOKEN')]) {
|
||||||
sh '''
|
sh '''
|
||||||
set +x
|
set +x
|
||||||
ST=$(cat /var/jenkins_secrets/sonar_token)
|
|
||||||
HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|")
|
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--network devops \
|
--add-host "ns31240276.ip-51-195-4.eu:${HOST_BRIDGE_IP}" \
|
||||||
-e SONAR_HOST_URL=http://sonarqube:9000 \
|
-e SONAR_HOST_URL="${SONAR_URL}" \
|
||||||
-e SONAR_TOKEN="$ST" \
|
-e SONAR_TOKEN="$SONAR_TOKEN" \
|
||||||
-v "$HOST_WS":/usr/src \
|
-v "$PWD":/usr/src \
|
||||||
sonarsource/sonar-scanner-cli:11 \
|
sonarsource/sonar-scanner-cli:11 \
|
||||||
-Dsonar.projectKey=swim-demo-app
|
-Dsonar.projectKey=swim-demo-app
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stage('Build image') {
|
stage('Build image') {
|
||||||
when { expression { env.IS_ROLLBACK != 'true' } }
|
when { expression { env.IS_ROLLBACK != 'true' } }
|
||||||
@ -124,12 +135,10 @@ print(' Vault se registry creds mil gaye (user=%s)' % d['username'])
|
|||||||
sh '''
|
sh '''
|
||||||
# GATE hai, report nahi: HIGH/CRITICAL mila to build yahin fail hogi.
|
# GATE hai, report nahi: HIGH/CRITICAL mila to build yahin fail hogi.
|
||||||
# Jo CVE business ne accept kiye hain wo .trivyignore me hain.
|
# Jo CVE business ne accept kiye hain wo .trivyignore me hain.
|
||||||
# DooD: docker daemon host ka hai, isliye workspace ka host path chahiye.
|
|
||||||
HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|")
|
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-v trivy-cache:/root/.cache/ \
|
-v trivy-cache:/root/.cache/ \
|
||||||
-v "$HOST_WS/.trivyignore":/.trivyignore:ro \
|
-v "$PWD/.trivyignore":/.trivyignore:ro \
|
||||||
aquasec/trivy:0.58.1 image \
|
aquasec/trivy:0.58.1 image \
|
||||||
--severity ${TRIVY_SEVERITY} \
|
--severity ${TRIVY_SEVERITY} \
|
||||||
--ignore-unfixed \
|
--ignore-unfixed \
|
||||||
@ -146,6 +155,7 @@ print(' Vault se registry creds mil gaye (user=%s)' % d['username'])
|
|||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG})
|
CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG})
|
||||||
|
trap "docker rm -f $CID >/dev/null 2>&1" EXIT
|
||||||
sleep 5
|
sleep 5
|
||||||
docker exec $CID python -c "
|
docker exec $CID python -c "
|
||||||
import urllib.request, json
|
import urllib.request, json
|
||||||
@ -153,7 +163,6 @@ r = json.loads(urllib.request.urlopen('http://127.0.0.1:8000/version').read())
|
|||||||
print('version endpoint:', r)
|
print('version endpoint:', r)
|
||||||
assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
||||||
"
|
"
|
||||||
docker rm -f $CID
|
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,26 +180,39 @@ assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
|||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stage('Deploy to STAGING') {
|
post {
|
||||||
|
always {
|
||||||
|
sh 'rm -f .vault-registry.env || true'
|
||||||
|
sh 'docker image prune -f --filter "until=24h" || true'
|
||||||
|
cleanWs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ CD: controller par ============
|
||||||
|
// Deploy key sirf yahan aati hai. Builder VM ise kabhi nahi dekhti.
|
||||||
|
stage('Deploy STAGING') {
|
||||||
|
agent { label 'built-in' }
|
||||||
steps {
|
steps {
|
||||||
|
checkout scm
|
||||||
|
withCredentials([
|
||||||
|
sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'),
|
||||||
|
usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')
|
||||||
|
]) {
|
||||||
sh '''
|
sh '''
|
||||||
set +x
|
set +x
|
||||||
. ./.vault-registry.env
|
|
||||||
export REG_USER REG_PASS
|
|
||||||
export ANSIBLE_HOST_KEY_CHECKING=False
|
export ANSIBLE_HOST_KEY_CHECKING=False
|
||||||
export IMAGE_TAG=${IMAGE_TAG}
|
|
||||||
ansible-galaxy collection install community.docker --force -q || true
|
ansible-galaxy collection install community.docker --force -q || true
|
||||||
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml -e target=staging
|
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \
|
||||||
|
-u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \
|
||||||
|
-e target=staging
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
stage('Verify STAGING') {
|
|
||||||
steps {
|
|
||||||
sh '''
|
sh '''
|
||||||
SVM=$(grep "^staging-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
|
SVM=$(grep "^staging-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
|
||||||
GOT=$(curl -sf --max-time 10 http://${SVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)[\'version\'])")
|
GOT=$(curl -sf --max-time 10 http://${SVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])")
|
||||||
echo " staging expected: ${IMAGE_TAG} | live: ${GOT}"
|
echo " staging expected: ${IMAGE_TAG} | live: ${GOT}"
|
||||||
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "STAGING MISMATCH"; exit 1; }
|
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "STAGING MISMATCH"; exit 1; }
|
||||||
echo " STAGING VERIFIED"
|
echo " STAGING VERIFIED"
|
||||||
@ -199,12 +221,10 @@ assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage('Production approval') {
|
stage('Production approval') {
|
||||||
|
// agent none = intezaar ke 15 minute me koi executor block nahi hota
|
||||||
|
agent none
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
// Enterprise gate: insaan ki manzoori ke bina production par kuch nahi.
|
|
||||||
// 15 min me jawab na aaye to build abort - production safe rehta hai.
|
|
||||||
// Simple proceed/abort input. Parameters wale input ko
|
|
||||||
// API se submit karna jhanjhat hai; UI ke liye bhi ye saaf hai.
|
|
||||||
timeout(time: 15, unit: 'MINUTES') {
|
timeout(time: 15, unit: 'MINUTES') {
|
||||||
input(message: "Staging par ${env.IMAGE_TAG} verified hai. PRODUCTION par deploy karein?",
|
input(message: "Staging par ${env.IMAGE_TAG} verified hai. PRODUCTION par deploy karein?",
|
||||||
ok: 'Deploy to Production')
|
ok: 'Deploy to Production')
|
||||||
@ -214,42 +234,35 @@ assert r['version'] == '${IMAGE_TAG}', 'version galat!'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Deploy to PRODUCTION') {
|
stage('Deploy PRODUCTION') {
|
||||||
|
agent { label 'built-in' }
|
||||||
steps {
|
steps {
|
||||||
|
checkout scm
|
||||||
|
withCredentials([
|
||||||
|
sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'),
|
||||||
|
usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')
|
||||||
|
]) {
|
||||||
sh '''
|
sh '''
|
||||||
set +x
|
set +x
|
||||||
. ./.vault-registry.env
|
|
||||||
export REG_USER REG_PASS
|
|
||||||
export ANSIBLE_HOST_KEY_CHECKING=False
|
export ANSIBLE_HOST_KEY_CHECKING=False
|
||||||
export IMAGE_TAG=${IMAGE_TAG}
|
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \
|
||||||
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml -e target=production
|
-u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \
|
||||||
|
-e target=production
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
stage('Verify PRODUCTION') {
|
|
||||||
steps {
|
|
||||||
sh '''
|
sh '''
|
||||||
PVM=$(grep "^app-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
|
PVM=$(grep "^app-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
|
||||||
GOT=$(curl -sf --max-time 10 http://${PVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)[\'version\'])")
|
GOT=$(curl -sf --max-time 10 http://${PVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])")
|
||||||
echo " production expected: ${IMAGE_TAG} | live: ${GOT}"
|
echo " production expected: ${IMAGE_TAG} | live: ${GOT}"
|
||||||
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "PROD MISMATCH"; exit 1; }
|
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "PROD MISMATCH"; exit 1; }
|
||||||
echo " PRODUCTION VERIFIED"
|
echo " PRODUCTION VERIFIED"
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
|
post { always { cleanWs() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
always {
|
|
||||||
script {
|
|
||||||
if (env.WORKSPACE) {
|
|
||||||
sh 'rm -f .vault-registry.env || true'
|
|
||||||
sh 'docker image prune -f --filter "until=24h" || true'
|
|
||||||
cleanWs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
success { echo "BUILD ${env.BUILD_NUMBER} SAFAL -> ${env.REGISTRY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" }
|
success { echo "BUILD ${env.BUILD_NUMBER} SAFAL -> ${env.REGISTRY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" }
|
||||||
failure { echo "BUILD ${env.BUILD_NUMBER} FAIL" }
|
failure { echo "BUILD ${env.BUILD_NUMBER} FAIL" }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
|
# SSH key yahan nahi likhi. Jenkins credential se --private-key par aati hai,
|
||||||
|
# taaki key repo me ya kisi file mount me na rahe.
|
||||||
[staging]
|
[staging]
|
||||||
staging-vm-01 ansible_host=192.168.124.167
|
staging-vm-01 ansible_host=192.168.124.167
|
||||||
|
|
||||||
[production]
|
[production]
|
||||||
app-vm-01 ansible_host=192.168.124.136
|
app-vm-01 ansible_host=192.168.124.136
|
||||||
|
|
||||||
[all:vars]
|
|
||||||
ansible_user=deploy
|
|
||||||
ansible_ssh_private_key_file=/var/jenkins_secrets/deploy_key
|
|
||||||
ansible_ssh_common_args=-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user