Update common/Jenkinsfile.docker.j2
This commit is contained in:
@@ -1,53 +1,275 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
options { timestamps() }
|
||||
|
||||
parameters {
|
||||
string(name: 'BRANCH_NAME', defaultValue: '', description: '빌드할 브랜치명(비우면 main)')
|
||||
string(name: 'GIT_COMMIT_HASH', defaultValue: '', description: '빌드할 커밋 해시(비우면 최신 커밋 기준)')
|
||||
string(name: 'BRANCH_NAME', defaultValue: '', description: '빌드할 브랜치명 (비우면 main)')
|
||||
string(name: 'GIT_COMMIT_HASH', defaultValue: '', description: '빌드할 커밋 해시 (비우면 최신 커밋)')
|
||||
}
|
||||
|
||||
environment {
|
||||
NODE_ID = '{{ node_id }}'
|
||||
NODE_TYPE = '{{ node_type }}'
|
||||
}
|
||||
|
||||
stages {
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Stage 1. Checkout
|
||||
// - 브랜치 / 커밋 해시 기준으로 소스코드 체크아웃
|
||||
// - 이후 stage 에서 사용할 환경변수 세팅
|
||||
// ──────────────────────────────────────────────
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
script {
|
||||
sh 'git fetch --all'
|
||||
def checkoutBranch = params.BRANCH_NAME?.trim() ? params.BRANCH_NAME.trim() : 'main'
|
||||
def commitHash = params.GIT_COMMIT_HASH?.trim()
|
||||
sh 'git fetch --all || true'
|
||||
|
||||
def checkoutBranch = params.BRANCH_NAME?.trim() ?: 'main'
|
||||
def commitHash = params.GIT_COMMIT_HASH?.trim()
|
||||
|
||||
if (commitHash) {
|
||||
echo "브랜치(${checkoutBranch})의 커밋 해시(${commitHash})로 체크아웃합니다."
|
||||
sh "git fetch origin ${checkoutBranch}"
|
||||
sh "git checkout ${checkoutBranch}"
|
||||
sh "git checkout ${commitHash}"
|
||||
} else {
|
||||
echo "브랜치(${checkoutBranch})의 최신 커밋으로 체크아웃합니다."
|
||||
sh "git fetch origin ${checkoutBranch}"
|
||||
sh "git checkout ${checkoutBranch}"
|
||||
commitHash = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
|
||||
}
|
||||
env.BRANCH_NAME = checkoutBranch
|
||||
|
||||
env.BRANCH_NAME = checkoutBranch
|
||||
env.GIT_COMMIT_HASH = commitHash
|
||||
env.COMPOSE_PROJECT = "${NODE_ID}-${checkoutBranch}"
|
||||
|
||||
// .env 파일 생성 (docker compose 에서 참조)
|
||||
writeFile file: '.env', text: """\
|
||||
BRANCH_NAME=${env.BRANCH_NAME}
|
||||
GIT_COMMIT_HASH=${env.GIT_COMMIT_HASH}
|
||||
NODE_ID=${NODE_ID}
|
||||
NODE_TYPE=${NODE_TYPE}
|
||||
"""
|
||||
|
||||
echo "NODE_ID=${NODE_ID}"
|
||||
echo "BRANCH_NAME=${env.BRANCH_NAME}"
|
||||
echo "GIT_COMMIT_HASH=${env.GIT_COMMIT_HASH}"
|
||||
echo "COMPOSE_PROJECT=${env.COMPOSE_PROJECT}"
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Build & Deploy') {
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Stage 2. Build
|
||||
// - Docker 이미지 빌드
|
||||
// - 실패 시 로그에 pip install / SyntaxError 등 원인 출력
|
||||
// ──────────────────────────────────────────────
|
||||
stage('Build') {
|
||||
steps {
|
||||
script {
|
||||
def composeProject = "{{ node_id }}-" + (env.BRANCH_NAME ?: "") + "{{ node_type }}"
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} --env-file .env build --no-cache"
|
||||
}
|
||||
}
|
||||
post {
|
||||
failure {
|
||||
echo '[Build] ★ BUILD FAILED ★'
|
||||
echo '[Build] docker compose build 실패. 위 로그에서 원인을 확인하세요.'
|
||||
echo '[Build] 주요 원인: Dockerfile 오류 / pip install 실패 / SyntaxError / ModuleNotFoundError'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeFile file: '.env', text: """
|
||||
BRANCH_NAME=${env.BRANCH_NAME}
|
||||
GIT_COMMIT_HASH=${env.GIT_COMMIT_HASH}
|
||||
"""
|
||||
sh "cat .env"
|
||||
sh "cat docker-compose.yml"
|
||||
sh "docker compose -p ${composeProject} --env-file .env down --rmi all"
|
||||
sh "docker compose -p ${composeProject} build --no-cache"
|
||||
sh "docker compose -p ${composeProject} --env-file .env up --force-recreate --remove-orphans -d"
|
||||
// ──────────────────────────────────────────────
|
||||
// Stage 3. Down
|
||||
// - 기존 실행 중인 컨테이너 종료 및 정리
|
||||
// - 최초 배포 시 컨테이너가 없어도 오류 처리 안 함
|
||||
// ──────────────────────────────────────────────
|
||||
stage('Down') {
|
||||
steps {
|
||||
script {
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} --env-file .env down --rmi all || true"
|
||||
echo "[Down] 기존 컨테이너 정리 완료"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Stage 4. Deploy
|
||||
// - 빌드된 이미지로 컨테이너 기동
|
||||
// - 컨테이너 ID 출력 (watcher 가 추적에 사용)
|
||||
// ──────────────────────────────────────────────
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
script {
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} --env-file .env up --force-recreate --remove-orphans -d"
|
||||
|
||||
// 컨테이너 ID 및 상태 출력
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} ps"
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} ps -q | xargs docker inspect --format '{{.Id}} {{.State.Status}} {{.State.ExitCode}}' || true"
|
||||
}
|
||||
}
|
||||
post {
|
||||
failure {
|
||||
echo '[Deploy] ★ DEPLOY FAILED ★'
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} ps || true"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Stage 5. Verify
|
||||
// - 컨테이너 정상 기동 여부 검증
|
||||
// - Running 상태 + exit code 확인
|
||||
// - Docker HEALTHCHECK 결과 확인 (정의된 경우)
|
||||
// - 성공 시: 기동 로그 첫 30줄 출력
|
||||
// - 실패 시: 컨테이너 로그 마지막 50줄 출력 → Poller 가 수집
|
||||
// ──────────────────────────────────────────────
|
||||
stage('Verify') {
|
||||
steps {
|
||||
script {
|
||||
def maxWait = 60 // 최대 대기 시간 (초)
|
||||
def interval = 3 // 확인 간격 (초)
|
||||
def stabilize = 10 // 안정화 대기 시간 (초)
|
||||
def elapsed = 0
|
||||
def containerId = sh(
|
||||
script: "docker compose -p ${env.COMPOSE_PROJECT} ps -q | head -1",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
if (!containerId) {
|
||||
error('[Verify] 컨테이너 ID 를 찾을 수 없습니다.')
|
||||
}
|
||||
|
||||
echo "[Verify] 컨테이너 ID: ${containerId}"
|
||||
|
||||
// ── Running 상태 대기 ──────────────────
|
||||
def isRunning = false
|
||||
while (elapsed < maxWait) {
|
||||
def status = sh(
|
||||
script: "docker inspect --format '{{.State.Status}}' ${containerId}",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
def exitCode = sh(
|
||||
script: "docker inspect --format '{{.State.ExitCode}}' ${containerId}",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
echo "[Verify] elapsed=${elapsed}s | status=${status} | exitCode=${exitCode}"
|
||||
|
||||
if (status == 'exited' || exitCode != '0') {
|
||||
echo '[Verify] ★ VERIFY FAILED ★ 컨테이너가 비정상 종료되었습니다.'
|
||||
echo "[Verify] Status: ${status} | ExitCode: ${exitCode}"
|
||||
echo '[Verify] --- 컨테이너 로그 (마지막 50줄) ---'
|
||||
sh "docker logs --tail 50 ${containerId} || true"
|
||||
error("[Verify] 컨테이너 비정상 종료. ExitCode=${exitCode}")
|
||||
}
|
||||
|
||||
if (status == 'running') {
|
||||
isRunning = true
|
||||
break
|
||||
}
|
||||
|
||||
sleep(interval)
|
||||
elapsed += interval
|
||||
}
|
||||
|
||||
if (!isRunning) {
|
||||
echo '[Verify] ★ VERIFY FAILED ★ 타임아웃: 컨테이너가 running 상태에 도달하지 못했습니다.'
|
||||
echo '[Verify] --- 컨테이너 로그 (마지막 50줄) ---'
|
||||
sh "docker logs --tail 50 ${containerId} || true"
|
||||
error("[Verify] 컨테이너 기동 타임아웃 (${maxWait}s 초과)")
|
||||
}
|
||||
|
||||
// ── 안정화 대기 (바로 죽는 케이스 방지) ─
|
||||
echo "[Verify] Running 확인. ${stabilize}초 안정화 대기 중..."
|
||||
sleep(stabilize)
|
||||
|
||||
def finalStatus = sh(
|
||||
script: "docker inspect --format '{{.State.Status}}' ${containerId}",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
def finalExitCode = sh(
|
||||
script: "docker inspect --format '{{.State.ExitCode}}' ${containerId}",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
if (finalStatus != 'running' || finalExitCode != '0') {
|
||||
echo '[Verify] ★ VERIFY FAILED ★ 안정화 대기 후 컨테이너 비정상 종료'
|
||||
echo '[Verify] --- 컨테이너 로그 (마지막 50줄) ---'
|
||||
sh "docker logs --tail 50 ${containerId} || true"
|
||||
error("[Verify] 안정화 실패. Status=${finalStatus} ExitCode=${finalExitCode}")
|
||||
}
|
||||
|
||||
// ── Docker HEALTHCHECK 확인 (정의된 경우) ─
|
||||
def healthStatus = sh(
|
||||
script: "docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' ${containerId}",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
|
||||
if (healthStatus != 'none') {
|
||||
echo "[Verify] HEALTHCHECK 감지됨. 상태: ${healthStatus}"
|
||||
def healthElapsed = 0
|
||||
def healthMaxWait = 60
|
||||
while (healthStatus == 'starting' && healthElapsed < healthMaxWait) {
|
||||
sleep(interval)
|
||||
healthElapsed += interval
|
||||
healthStatus = sh(
|
||||
script: "docker inspect --format '{{.State.Health.Status}}' ${containerId}",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
echo "[Verify] HEALTHCHECK elapsed=${healthElapsed}s | status=${healthStatus}"
|
||||
}
|
||||
if (healthStatus == 'unhealthy') {
|
||||
echo '[Verify] ★ VERIFY FAILED ★ Docker HEALTHCHECK 실패'
|
||||
echo '[Verify] --- 컨테이너 로그 (마지막 50줄) ---'
|
||||
sh "docker logs --tail 50 ${containerId} || true"
|
||||
error('[Verify] Docker HEALTHCHECK unhealthy')
|
||||
}
|
||||
if (healthStatus == 'starting') {
|
||||
echo "[Verify] ★ VERIFY FAILED ★ HEALTHCHECK 타임아웃 (${healthMaxWait}s 초과)"
|
||||
error("[Verify] HEALTHCHECK 타임아웃")
|
||||
}
|
||||
}
|
||||
|
||||
// ── 최종 성공 ───────────────────────────
|
||||
echo '[Verify] ✅ VERIFY SUCCESS'
|
||||
echo "[Verify] Status=${finalStatus} | ExitCode=${finalExitCode} | Health=${healthStatus}"
|
||||
echo '[Verify] --- 컨테이너 기동 로그 (첫 30줄) ---'
|
||||
sh "docker logs ${containerId} 2>&1 | head -30 || true"
|
||||
echo '[Verify] --- 컨테이너 상태 ---'
|
||||
sh "docker inspect --format 'ID={{.Id}}\nStatus={{.State.Status}}\nStartedAt={{.State.StartedAt}}\nImage={{.Config.Image}}' ${containerId} || true"
|
||||
}
|
||||
}
|
||||
post {
|
||||
failure {
|
||||
echo '[Verify] ★ VERIFY FAILED - post ★'
|
||||
script {
|
||||
def containerId = sh(
|
||||
script: "docker compose -p ${env.COMPOSE_PROJECT} ps -q | head -1 || true",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
if (containerId) {
|
||||
echo '[Verify] --- 최종 컨테이너 상태 ---'
|
||||
sh "docker inspect ${containerId} || true"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success {
|
||||
echo '✅ DEPLOY SUCCESS'
|
||||
script {
|
||||
sh "docker compose -p ${env.COMPOSE_PROJECT} ps || true"
|
||||
}
|
||||
}
|
||||
failure {
|
||||
echo '★ DEPLOY FAILED ★'
|
||||
echo "NODE_ID=${NODE_ID} | BRANCH=${env.BRANCH_NAME} | COMMIT=${env.GIT_COMMIT_HASH}"
|
||||
}
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user