1. 개요

DoEatFit 프로젝트는 dev 브랜치에 코드가 푸시되면 Jenkins가 자동으로 빌드, 테스트를 거친 후 운영 서버에 무중단 배포하는 튼튼한 CI/CD 파이프라인을 구축하여 운용하고 있었습니다. 그러던 중, 포트폴리오의 통일성을 위해 GitHub Organization의 소유자명을 [OLD_ORG_NAME]에서 DoEatFit으로 변경하는 비교적 단순한 네이밍 리팩토링을 수행했습니다. 그러나 이 사소해 보였던 조치 직후, 멀쩡히 작동하던 Jenkins 파이프라인이 owner not foundmanifest unknown 등의 연쇄 장애를 일으키며 전면 멈추어 서는 위기를 마주쳤습니다. 조사 결과, 깃허브 저장소는 리디렉션을 제공하지만 도커 레지스트리(GHCR)와 젠킨스 자격 증명(Credentials), 그리고 리눅스 파일 시스템 특유의 대소문자 구분 정책 간의 삼각 충돌로 일어난 참사였답니다. 이를 완벽하게 봉합하고 무장애 배포망을 복원해 낸 경험을 정리해 드립니다.


2. 핵심 내용

2-1. 🚨 문제 상황과 원인 분석

  • 문제 현상:
    • GitHub Organization 이름 변경 직후 첫 배포가 이루어지는 순간 Push Image to GHCR 단계에서 denied: not_found: owner not found를 뿜으며 즉각 빌드가 뻗어 버렸습니다.
    • 경로를 부랴부랴 수정한 뒤에는 CD(배포) 스케줄 상에서 docker-compose up이 엉뚱한 이미지 주소를 당기려 시도하며 Error response from daemon: manifest unknown 에러를 뿜고 서버 배포가 좌절되었습니다.
  • 원인 분석:
    1. GHCR 리디렉션 미지원: GitHub Repository URL은 조직 이름이 바뀌어도 구 주소 요청을 신규 주소로 자동 포워딩해 주는 영리한 리디렉션을 지원합니다. 하지만 컨테이너 레지스트리인 GHCR(GitHub Container Registry)은 리디렉션을 제공하지 않기에 이전 경로(ghcr.io/[OLD_ORG_NAME]/...)가 완전히 폐쇄된 길로 인식되었습니다.
    2. Jenkins Credentials의 비가시적 사각지대: CD 실행 시 젠킨스는 보안 상 보안 자격 증명(Secrets File)으로 등록된 docker-compose.yaml을 가져와 복사한 뒤 원격 서버에 넘겨 줍니다. Git 저장소 내 Jenkinsfile만 눈에 띄게 고치고, 젠킨스 설정 서버 깊숙이 묻혀 있던 도커 컴포즈 템플릿 크레덴셜의 옛날 경로를 깜빡 놓쳤던 것입니다.
    3. Docker Registry의 대소문자(Lowercase) 엄격 주의: GitHub 조직 이름은 대소문자가 섞인 DoEatFit입니다. 그러나 Docker Registry 표준에 따라 GHCR은 원격 저장 명세를 무조건 소문자화(ghcr.io/doeatfit/...)하여 저장합니다. 이때 배포 도구(Docker daemon)가 요청하는 문자열에 대문자가 섞여 있으면, 매칭되는 이미지 식별자를 찾지 못해 무정하게 manifest not found 에러를 회신하는 것이 최종 걸림돌이었습니다.

2-2. 💡 단계별 에러 극복 및 솔루션 구현

  • 1단계 해결: Jenkinsfile 경로 개편:
    • Jenkinsfile 파이프라인 스크립트에서 깃 주소 및 GHCR 도커 푸시 경로를 최신 조직명으로 전면 업데이트했습니다.
    // Jenkinsfile
    stage('🚚 Checkout') {
        steps {
            git credentialsId: '[GIT_SSH_CREDENTIAL_ID]', 
                url: 'git@github.com:DoEatFit/doeatfit_front.git'
        }
    }
    stage('📦 Build & Push Docker Image') {
        steps {
            sh 'docker build -t ghcr.io/doeatfit/doeatfit_front:dev .'
            sh 'docker push ghcr.io/doeatfit/doeatfit_front:dev'
        }
    }
  • 2단계 해결: Jenkins Credentials 동기화:
    • 젠킨스 관리 GUI 대시보드 내부의 doeatfit-compose-file (Secret File 타입)을 즉시 다운로드하여 구형 레포지토리 네이밍을 새로운 도메인으로 수동 수정한 뒤 다시 덮어씌워 업로드했습니다.
    # doeatfit-compose-file (수정 적용된 Credential 명세)
    services:
      doeatfit-frontend:
        image: ghcr.io/doeatfit/doeatfit_front:dev
  • 3단계 해결: 소문자(Lowercase) 통일화:
    • Docker 클라이언트 빌드 푸시와 원격지 데몬 풀링 시 대소문자 불일치로 인한 오작동을 근원적으로 도려내기 위해, 이미지 메타데이터 경로 주소를 전부 **소문자(doeatfit)**로 정갈하게 맞춰 설계했습니다.
graph RL
    A["GitHub Private Repo<br>(dev branch push)"] -->|Webhook| B["Jenkins Build Server"]
    
    subgraph "CI Pipeline 단계"
        B --> C["1. Checkout<br>(DoEatFit/doeatfit_front)"]
        C --> D["2. Docker Build<br>(ghcr.io/doeatfit/..)"]
        D --> E["3. Push to GHCR<br>(소문자 변환 통신)"]
    end

    subgraph "CD Release 단계"
        E -->|"SSH 원격 명령 전달"| F["4. Remote Docker Pull<br>(ghcr.io/doeatfit/..)"]
        F --> G["5. Docker Compose Up<br>(정제된 소문자 YAML 활용)"]
        G --> H[("🚀 배포 무장애 완료")]
    end

2-3. ✅ 결과 검증 및 모던 오픈소스 라이브러리 검토

  • 결과 검증:
    • dev 브랜치에 더미 커밋을 날려 트리거 테스트를 수행했습니다.
    • Jenkins 빌드 프로세스가 Checkout부터 Docker Push까지 초록불로 단숨에 통과했으며, 원격 서버에서도 대소문자 어긋남 없이 ghcr.io/doeatfit/doeatfit_front:dev 이미지를 깨끗하게 풀(Pull)하여 컴포즈 업(compose up)을 평온하게 매듭지음을 검증했습니다.
  • 추천 오픈소스 라이브러리 및 도구 검토:
    1. GitHub Actions
      • 평가: 무겁고 외부 관리 포인트가 높은 self-hosted Jenkins 대신, GitHub Ecosystem에 온전히 내장되어 GHCR 푸시 라이프사이클을 훨씬 직관적이고 간편하게 조율할 수 있는 대세 CI/CD 프레임워크입니다.
    2. Docker Compose (V2 CLI)
      • 평가: 구형 파이프라인에서 호출되던 docker-compose 단독 바이너리 방식보다 최신 Docker 엔진에 플러그인화된 docker compose 명령을 권장합니다. 환경 변수 보간 및 컴포즈 내 시크릿 바인딩 기능이 한결 고도화되어 복잡한 Credentials 의존성을 크게 줄일 수 있습니다.