diff --git a/Jenkinsfile.pjt b/Jenkinsfile.pjt index e0f84dc..6d44fa7 100644 --- a/Jenkinsfile.pjt +++ b/Jenkinsfile.pjt @@ -95,7 +95,27 @@ pipeline { } } - stage('6) Copy compose') { + stage('6) Jenkins cleanup') { + steps { + sh ''' + set -e + + echo "[1/4] Remove pushed image from Jenkins" + docker rmi ${REGISTRY_HOST}/${IMAGE_REPO}:${IMAGE_TAG} 2>/dev/null || true + + echo "[2/4] Remove dangling images" + docker image prune -f + + echo "[3/4] Remove old build cache (safe-ish)" + # 빌드 캐시만 정리: 다른 컨테이너/볼륨에는 영향 적음 + docker builder prune -f || true + + echo "[4/4] Skip docker system prune --volumes (too aggressive on shared nodes)" + ''' + } + } + + stage('7) Copy compose') { steps { sshagent(['ssh-pjt']) { sh ''' @@ -113,7 +133,7 @@ pipeline { } } - stage('7) Remote docker registry login') { + stage('8) Remote docker registry login') { steps { sshagent(['ssh-pjt']) { withCredentials([usernamePassword( @@ -143,7 +163,7 @@ pipeline { } } - stage('8) Remote docker pull') { + stage('9) Remote docker pull') { steps { sshagent(['ssh-pjt']) { sh ''' @@ -162,7 +182,7 @@ pipeline { } } - stage('9) Remote docker down & up') { + stage('10) Remote docker down & up') { steps { sshagent(['ssh-pjt']) { sh ''' @@ -187,5 +207,134 @@ pipeline { } } + stage('11) Remote cleanup') { + steps { + sshagent(['ssh-pjt']) { + sh ''' + set -e + ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + ${SSH_USER}@${SSH_HOST} \ + 'set -e + + echo "[0] Collect images used by running containers" + RUNNING_IMAGES=$(docker ps -q | xargs -r docker inspect --format="{{.Image}}" | sort -u) + + echo "[1/3] Remove old registry images (except running ones)" + docker images '"${REGISTRY_HOST}/${IMAGE_REPO}"' --format "{{.ID}} {{.CreatedAt}}" | \ + sort -k2 -r | \ + awk "{print \$1}" | \ + while read IMAGE_ID; do + echo "$RUNNING_IMAGES" | grep -q "$IMAGE_ID" && continue + docker rmi -f "$IMAGE_ID" 2>/dev/null || true + done + + echo "[2/3] Remove dangling images (safe)" + docker image prune -f + + echo "[3/3] Remove unused volumes (safe)" + docker volume prune -f + ' + ''' + } + } + } + + stage('12) Registry cleanup (keep last 5 + protect running)') { + steps { + sshagent(['ssh-pjt']) { + sh ''' + set -e + ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + ${SSH_USER}@${SSH_HOST} \ + 'set -e + + REGISTRY_URL="https://'${REGISTRY_HOST}'" # 예: https://registry.pjt.kr + REPO="'${IMAGE_REPO}'" # 예: alist/api + KEEP=5 + + # (옵션) Basic Auth 쓰면 아래 활성화하고 curl에 -u "$AUTH" 추가 + # AUTH="user:pass" + + echo "[0/5] Ensure jq exists" + if ! command -v jq >/dev/null 2>&1; then + sudo apt-get update -y + sudo apt-get install -y jq + fi + + echo "[1/5] Collect running tags to protect" + # 현재 서버에서 실행 중인 컨테이너가 사용하는 해당 레포 이미지 태그만 추출 + RUNNING_TAGS=$(docker ps --format "{{.Image}}" | \ + grep -E "^'${REGISTRY_HOST}'/'${IMAGE_REPO}':" | \ + awk -F: "{print \\$2}" | sort -u || true) + + if [ -n "$RUNNING_TAGS" ]; then + echo "Running tags (protected):" + echo "$RUNNING_TAGS" | sed "s/^/ - /" + else + echo "No running tags found for ${REGISTRY_HOST}/${IMAGE_REPO} (ok)" + fi + + echo "[2/5] Fetch tags from registry: $REPO" + TAGS=$(curl -fsS "$REGISTRY_URL/v2/$REPO/tags/list" | jq -r ".tags[]?" || true) + # Basic Auth 쓰면 위 curl을 아래로 바꿔: + # TAGS=$(curl -fsS -u "$AUTH" "$REGISTRY_URL/v2/$REPO/tags/list" | jq -r ".tags[]?" || true) + + if [ -z "$TAGS" ]; then + echo "No tags found in registry. Skip." + exit 0 + fi + + echo "[3/5] Sort tags desc (tag must be sortable for 'latest' semantics)" + SORTED=$(echo "$TAGS" | sort -r) + + KEEP_TAGS=$(echo "$SORTED" | head -n $KEEP) + DEL_CANDIDATES=$(echo "$SORTED" | tail -n +$((KEEP+1))) + + echo "Keep tags (latest $KEEP):" + echo "$KEEP_TAGS" | sed "s/^/ - /" + + echo "[4/5] Delete old tags except protected running tags" + for TAG in $DEL_CANDIDATES; do + # 실행 중 태그는 무조건 스킵 + if [ -n "$RUNNING_TAGS" ] && echo "$RUNNING_TAGS" | grep -qx "$TAG"; then + echo "Skip running tag: $REPO:$TAG" + continue + fi + + echo "Deleting $REPO:$TAG" + + DIGEST=$(curl -fsSI \ + -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + "$REGISTRY_URL/v2/$REPO/manifests/$TAG" \ + | awk -F": " "/Docker-Content-Digest/ {print \\$2}" | tr -d "\\r") + + # Basic Auth 쓰면 위 curl을 아래로 바꿔: + # DIGEST=$(curl -fsSI -u "$AUTH" \ + # -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ + # "$REGISTRY_URL/v2/$REPO/manifests/$TAG" \ + # | awk -F": " "/Docker-Content-Digest/ {print \\$2}" | tr -d "\\r") + + if [ -z "$DIGEST" ]; then + echo " digest not found. skip $TAG" + continue + fi + + curl -fsS -X DELETE "$REGISTRY_URL/v2/$REPO/manifests/$DIGEST" || true + # Basic Auth 쓰면: + # curl -fsS -u "$AUTH" -X DELETE "$REGISTRY_URL/v2/$REPO/manifests/$DIGEST" || true + done + + echo "[5/5] Garbage collect (reclaim disk space)" + # push가 진행 중이면 위험할 수 있으니, 배포 성공 후 마지막 단계에서만 실행 추천 + docker exec registry registry garbage-collect /etc/docker/registry/config.yml + + echo "Registry cleanup done." + ' + ''' + } + } + } + + } }