Automation 23분 중급

수동 WAR 배포에서 Jenkins CI/CD와 JEUS 롤백 파이프라인까지

PMWORKS의 마스킹된 사례를 통해 Vue/Vite와 Maven 빌드를 Jenkins로 옮기고, 제한된 서비스 계정 래퍼로 JEUS에 WAR를 배포하며, 별도 롤백 작업으로 배포 전 백업을 복원한 과정을 살펴봅니다.

사례 환경
Node.js 22, JDK 17, JEUS 9
주황색·파란색 케이블이 연결된 서버 랙으로 CI/CD 배포와 롤백 인프라를 표현한 사진
자료 이미지: Photo by Fabio Sasso on Unsplash
이 글의 목차
  1. 1. Jenkinsfile 작성 전에 전환 범위를 정한다
  2. 2. Maven으로 WAR를 패키징하기 전에 프런트엔드를 빌드한다
  3. 3. 실제 실패 지점에서 빌드 문제를 진단한다
  4. 4. 배포 자동화에는 필요한 권한만 부여한다
  5. 5. 현재 WAR는 교체 직전에 백업한다
  6. JEUS 관리 저장소의 중요한 경계
  7. 6. 롤백은 분리하되, 보장 범위를 정확히 설명한다
  8. 7. JEUS 종료 코드를 단독 판정이 아닌 신호로 다룬다
  9. 8. 전체 경로를 검증하고, 남은 빈틈을 보완한다
  10. 참고 자료

PMWORKS의 기존 배포는 개발자 PC에서 시작했다. 개발자가 Vue 프런트엔드와 Java 백엔드를 빌드해 WAR를 만들고, 파일을 개발 VM으로 전송한 뒤, JEUS를 중지하고 WAR를 교체해 다시 시작했다. 개별 작업은 이해하기 어렵지 않았다. 문제는 어느 Git 리비전에서 배포된 파일이 만들어졌는지, 프런트엔드 빌드가 포함됐는지, 잘못된 릴리스 후 어떤 파일로 복원해야 하는지를 하나의 시스템에서 입증할 수 없다는 점이었다.

대체한 것은 단순히 Jenkins에서 Maven 명령을 실행하는 작업이 아니었다. 소스부터 산출물, VM까지 이어지는 반복 가능한 경로와, 서버에 보관한 WAR 백업을 복원하는 별도의 운영자 실행형 롤백 파이프라인을 마련했다. 이 구분은 중요하다. 이 사례에는 실패 자동 탐지, 자동 롤백 트리거, HTTP 상태 확인 게이트가 아직 구현되지 않았다. 이들은 후속 보완 과제이지, 이 글에서 이미 배포했다고 주장하는 기능이 아니다. 또한 기존 절차는 WAR를 JEUS가 관리하는 .applications 저장소에 직접 복사했다. JEUS 9 문서는 그곳의 파일을 수동으로 교체하는 방식을 권장하지 않는다. 이 글은 당시 구현과 그 한계를 기록하는 것이지, 그대로 따라 할 수 있는 공식 지원 배포 절차를 제시하는 것이 아니다.

이 글은 실제 프로젝트 기록을 기술적으로 재구성한 사례다. 아래의 PMWORKS, 경로, 계정명, 그룹명, 주소는 마스킹한 예시다. JEUS, Jenkins, Maven, Node.js, npm, Vite는 사례에서 사용한 기술을 가리킨다. 명령을 실행하기 전에 각자의 배포 제품, 디렉터리 소유권, 변경 관리 절차에 맞게 조정해야 한다.

1. Jenkinsfile 작성 전에 전환 범위를 정한다

기존 경로와 새 경로는 운영상 서로 다른 질문에 답한다.

질문수동 절차구현한 개발 환경 절차
프런트엔드는 어디서 빌드하는가?개발자 PCNode.js 22 도구를 명시적으로 선택한 Linux Jenkins 에이전트
백엔드는 어디서 빌드하는가?개발자 PC개발 프로필을 사용하는 Jenkins Maven 단계
무엇을 전송하는가?로컬에서 생성한 WAR해당 Jenkins 실행에서 생성한 WAR. 기존 배포 그룹은 이를 JEUS 관리 경로에 직접 복사했다.
누가 JEUS를 재시작하는가?서버 작업을 수행하는 사람배포 흐름에서 호출하는, 허용 범위를 좁힌 서비스 계정 래퍼
교체 전에 무엇을 보관하는가?일관된 백업 단계 없음현재 배포된 WAR를 타임스탬프와 함께 복사
롤백은 어떻게 시작하는가?상황에 따라 수동 복구운영자가 의도적으로 실행하는 별도의 롤백 작업·배포 그룹
애플리케이션 상태는 무엇으로 확인하는가?수동 관찰JEUS 시작 증적과 수동 애플리케이션 접속 확인. 자동 HTTP 상태 확인은 계획 단계

이는 개발 환경의 사례다. 동일한 통제가 운영 환경에도 이미 적용됐다는 근거는 아니다. Jenkins 실행 성공, WAR 전송 성공, JEUS 프로세스의 RUNNING 상태, 정상적인 웹 애플리케이션은 서로 다른 네 가지 관찰 결과다.

Git 리비전
  -> Jenkins 체크아웃
  -> Node.js 22에서 Vue/Vite 빌드
  -> JDK 17에서 Maven 빌드 -> WAR
  -> 현재 WAR를 백업으로 복사
  -> JEUS 중지 -> WAR 전송 -> JEUS 시작
  -> 런타임 상태와 애플리케이션 응답 확인

운영자가 실행하기로 결정했을 때의 별도 롤백 작업:
  서버의 백업 선택 -> JEUS 중지 -> WAR 복원 -> JEUS 시작
  -> 런타임 상태와 애플리케이션 응답 확인

이 순서에서 산출물의 경계가 분명해진다. JEUS에 배포하는 WAR는 반드시 이 파이프라인에서 만든 파일이어야 하고, 백업은 해당 교체 직전에 VM에 있던 파일을 나타내야 한다. 다이어그램은 이 개발 프로젝트에서 실제로 한 일을 설명한다. JEUS 관리 파일을 직접 교체하는 것이 일반적으로 지원되는 배포 API라는 뜻은 아니다.

2. Maven으로 WAR를 패키징하기 전에 프런트엔드를 빌드한다

PMWORKS의 루트 pom.xml 옆에는 frontend/ 디렉터리가 있다. 최초 Jenkins 시도에서는 프런트엔드 빌드 없이 Maven만 실행했다. 그 결과 WAR는 로컬에서 만든 WAR와 크기 차이가 컸다. 원인은 Maven 자체가 아니라 빠진 빌드 단계였다.

repository/
├── pom.xml
├── src/
└── frontend/
    ├── package.json
    ├── package-lock.json
    ├── vite.config.js
    └── src/

마스킹한 Declarative Pipeline에서 중요한 부분은 실행 순서와 작업 디렉터리다.

stage('Frontend Build') {
    tools { nodejs 'node22' }
    steps {
        dir('frontend') {
            sh 'npm install && npm run build-dev'
        }
    }
}

stage('Backend Build') {
    tools { maven 'maven' }
    steps {
        sh 'mvn clean package -Pdev && ls -lh target/*.war'
    }
}

dir('frontend')는 package.json이 있는 곳에서 npm을 실행한다. 블록이 끝나면 Jenkins는 이전 작업 공간 문맥으로 돌아가므로 Maven 실행 전에 cd ..를 덧붙일 필요가 없다. 예시는 사례에서 사용한 명령인 npm install을 그대로 보여준다. CI에서 재현 가능한 빌드를 위한 다음 보완은 커밋된 유효한 잠금 파일과 함께 npm ci를 사용하는 것이다. 이 명령은 새로 설치하며 잠금 파일과 매니페스트가 일치하지 않으면 실패한다. 기존 잠금 파일과 의존성 공급 경로가 이를 지원하는지 확인한 뒤 변경해야 한다.

환경 표에는 JDK 17이 적혀 있었지만, 축약된 Jenkinsfile은 Node.js와 Maven 도구만 선택했다. 에이전트에 JDK 17이 이미 설정돼 있으면 작동할 수 있으나, 파이프라인 자체가 그 선택을 입증하지는 않는다. 에이전트 이미지나 Jenkins 도구 설정에서 JDK를 고정하고 java -version, node --version, mvn -version을 빌드 증적에 남긴다. 마찬가지로 @main에서 불러오는 공유 라이브러리는 실행 사이에 바뀔 수 있다. 동일한 릴리스를 정확히 재실행해야 한다면 보호된 릴리스 태그나 변경 불가능한 리비전이 더 적합하다.

원본 Jenkinsfile의 사용자 정의 deployVM(...)과 convertTag(...) 호출은 사내 공유 라이브러리와 배포 플랫폼에서 제공했다. 이들은 Jenkins Pipeline의 기본 단계가 아니다. 마스킹한 이름은 빌드 산출물이 Jenkins에서 VM 배포로 넘어가는 위치를 설명할 뿐, 다른 설치 환경에서 그대로 실행할 수 있는 예제는 아니다.

3. 실제 실패 지점에서 빌드 문제를 진단한다

로컬에서 작동하던 빌드를 Linux CI 에이전트로 옮기자 서로 다른 세 가지 실패 유형이 드러났다.

관찰된 증상실패한 경계유용한 진단과 조치
npm WARN EBADENGINE: 의존성은 Node 20 이상이 필요한데 에이전트는 Node 16을 사용런타임 버전호환되는 Node.js 도구를 명시적으로 선택한다. 이 사례는 Node.js 22를 사용했다. 작업 로그에 실제 버전을 남긴다.
외부 CDN에서 tarball을 가져올 때 npm install이 503을 반환Vue나 Vite 컴파일이 아닌 패키지 다운로드 경로요청 URL, 레지스트리 설정, 프록시·외부 통신 규칙, 사내 아티팩트 저장소를 확인한다. 이 사례에서는 필요한 아티팩트를 사내 Nexus 경로로 이용할 수 있게 했다.
Windows 작업 PC에서는 빌드되던 .vue import를 Vite가 해석하지 못함Linux 작업 공간의 파일명 대소문자 구분import의 정확한 문자와 Git에 추적되는 파일명을 비교한다. import를 수정하고, 파일시스템이나 Git 인덱스가 대소문자만 바뀐 이름을 기록하지 못한다면 git mv를 두 단계로 수행한다.

예를 들어 RiskLeaderThruDate.vue와 RIskLeaderThruDate.vue는 대소문자를 구분하는 Linux 빌드에서는 서로 다른 이름이다. 불일치를 숨기는 CI 전용 별칭을 추가하기보다 소스를 고치는 편이 낫다. 두 단계 이름 변경은 다음과 같다.

git mv RIskLeaderThruDate.vue intermediate.vue
git mv intermediate.vue RiskLeaderThruDate.vue

진단 순서는 단순하다. 의존성 설치가 실패하면 프런트엔드 소스를 디버깅하기 전에 패키지 공급 경로와 네트워크 도달성을 살핀다. 설치는 성공했지만 npm run build-dev가 실패했다면 컴파일러 출력과 소스 트리를 확인한다. 실패한 모든 Jenkins 단계를 Jenkins 자체의 문제로 취급해서는 안 된다.

4. 배포 자동화에는 필요한 권한만 부여한다

SSH/SFTP 접속 계정과 JEUS 실행 계정을 분리했다. 이 마스킹된 예시에서 pmworks.cicd는 파일을 전송하고 정해진 소수의 명령을 호출하며, pmworks.sec는 JEUS 작업을 담당한다. Jenkins에는 범용 root 셸이나 서비스 계정으로 무제한 전환할 권한을 주지 않는다.

Jenkins / 배포 플랫폼
    -> pmworks.cicd로 SSH 및 SFTP 접속
    -> 승인된 래퍼 네 개에 한해 sudo -n -u pmworks.sec 실행
    -> JEUS 중지, 시작, 백업 또는 복원

아래 sudoers 규칙은 허용할 래퍼 진입점만 나열한 예시다.

pmworks.cicd ALL=(pmworks.sec) NOPASSWD: \
  /opt/pmworks/cicd/bin/pmworks-stop.sh "", \
  /opt/pmworks/cicd/bin/pmworks-start.sh "", \
  /opt/pmworks/cicd/bin/pmworks-backup.sh "", \
  /opt/pmworks/cicd/bin/pmworks-rollback.sh ""

sudo -n은 비대화형 작업에서 암호 입력을 기다리는 대신 실패하게 한다. sudoers에서 각 명령 뒤의 ""는 인수를 허용하지 않는다는 뜻이다. 인수 조건을 아예 생략하면 해당 명령에 임의의 인수를 전달할 수 있다. 이 최소 권한은 배포 계정이 래퍼 스크립트, 상위 디렉터리, 래퍼가 실행하거나 불러오는 파일을 수정할 수 없을 때에만 실제로 성립한다. 래퍼의 소유자와 권한, 서비스 계정의 환경 파일을 보호하고 관리자와 함께 적용된 sudoers 정책을 검토한다. 배포자가 수정할 수 있는 래퍼를 sudo로 허용하면 제한은 무의미해진다.

서비스 계정의 JEUS 명령에도 예측 가능한 비대화형 실행 환경이 필요하다. 로그인 셸은 .bash_profile을 읽을 수 있지만 SSH 명령이나 sudo -u 호출은 읽지 않을 수 있다. 따라서 사례에서는 래퍼 스크립트에 HOME, JAVA_HOME, JEUS_HOME, PATH를 설정하고 서비스 계정이 소유한 환경 파일을 불러왔다. 비밀 값은 Jenkinsfile, 소스 관리, 명령행 인수, 디버그 로그에 남기지 않는다. 환경 파일에 chmod 600을 적용하더라도 소유자와 상위 디렉터리 권한이 무단 접근·교체를 막지 못하면 효과가 없다.

이 설계는 두 계정이 할 수 있는 일을 의도적으로 구분한다. 기존 SFTP 덮어쓰기에는 별도의 권한 경계가 있었다. 디렉터리에 그룹 쓰기 권한이 있어도 기존 WAR 파일의 소유자가 서비스 계정이고 모드가 644라면 충분하지 않았다. 배포 계정의 그룹 소속, 디렉터리 권한, 파일의 실제 쓰기 권한을 확인해 원인을 파악했다. 그렇다고 JEUS 관리 저장소에 대한 권한을 넓혀서는 안 된다. 재귀적인 chmod 777이나 소유권 일괄 변경은 문제를 더 키운다. 지원되는 방식으로 재설계한다면 WAR를 서비스 계정이 소유한 스테이징 경로로 전송하고, JEUS 배포 도구가 애플리케이션 저장소를 관리하도록 해야 한다.

5. 현재 WAR는 교체 직전에 백업한다

개발 환경 배포 그룹은 서버 작업을 다음 순서로 수행했다.

모든 빌드 단계 통과
  -> VM에 현재 설치된 WAR 백업
  -> JEUS 중지
  -> SFTP로 새 WAR 전송
  -> JEUS 시작
  -> 시작 상태와 애플리케이션 응답 확인

백업 래퍼는 기존 대상 파일을 20260917-134512_pmworks.war처럼 정렬 가능한 타임스탬프가 붙은 이름으로 전용 디렉터리에 복사했다. 이렇게 하면 복구에 사용할 파일을 눈으로 확인할 수 있다. 그러나 첫 배포에는 예외가 생긴다. 설치된 WAR가 없으면 백업 단계가 실패하므로 최초 설치를 위한 별도의 명시적 경로가 필요하다. 원래 구현에는 SHA-256 다이제스트 기록이나 동일한 초에 생성된 두 백업의 이름 충돌 방지 기능이 없었다.

JEUS 관리 저장소의 중요한 경계

원래 사례의 대상은 JEUS 도메인이 관리하는 애플리케이션 저장소인 DOMAIN_HOME/.applications 아래 경로였다. JEUS 9 문서는 이 디렉터리가 설치 명령으로 관리된다고 설명하며, JEUS 도구를 통한 애플리케이션 관리·배포 절차를 안내한다. 그 디렉터리에서 SFTP로 직접 교체하거나 cp -f로 복원한 것은 기존 환경의 구현 특징일 뿐, 다른 환경에 그대로 적용하거나 공식적으로 권장할 JEUS 배포 방식이 아니다. 재시작이나 Master Server와의 동기화가 일어나면 파일 수준의 변경이 관리되는 애플리케이션 상태를 정확히 반영하지 않을 수도 있다.

새로 구현한다면 먼저 빌드한 WAR와 다이제스트를 .applications 바깥의 아티팩트 저장소나 통제된 스테이징 디렉터리에 보관한다. 그런 다음 해당 도메인에 맞는 JEUS 지원 설치·배포 또는 재배포 절차를 사용하고, 이전 산출물로 돌아갈 때도 같은 절차를 적용한다. JEUS는 install-application, deploy-application, redeploy-application 명령을 문서화한다. 정확한 명령 순서는 애플리케이션 ID, 저장소 모드, Master Server, 대상 서버에 따라 달라지므로 이 사례의 명령을 복사하지 말고 대상 환경에서 검증해야 한다.

해시 기록, 이름 충돌 탐지, 배포·롤백 작업 간 잠금, 검증된 산출물과 릴리스의 연결은 권장 보완 사항이지 PMWORKS에서 완료된 통제가 아니다. 직접 덮어쓰기는 전송이 실패했을 때 파일 일부만 남길 수도 있다. chmod나 cp 옵션을 추가하는 데 그치지 않고 기존 파일 교체 경로를 걷어내야 하는 또 다른 이유다.

예시 Jenkins 파이프라인은 사내 deployVM 단계를 호출하지만, 배포 전후 명령은 VM 배포 그룹에 설정돼 있다. Jenkinsfile에서 그 명령을 볼 수 없다면 그룹 설정의 버전 관리 사본이나 변경 기록을 남겨야 한다. 그렇지 않으면 빌드 로그만으로 전체 배포 순서를 재현할 수 없다.

6. 롤백은 분리하되, 보장 범위를 정확히 설명한다

롤백 작업은 명령 실행만 하도록 설정한 별도 VM 배포 그룹을 사용한다. 이전 커밋을 체크아웃하거나 npm과 Maven으로 다시 빌드하지 않는다. 덕분에 복구 경로가 짧아지고, 장애 대응 중 의존성 다운로드나 빌드 도구의 변화에 영향을 덜 받는다.

운영자가 롤백 작업 실행
  -> JEUS 중지
  -> 복원 래퍼가 최신 백업 WAR를 자동 선택
  -> 대상 WAR 위에 복원
  -> JEUS 시작
  -> 런타임 상태와 애플리케이션 응답 확인

원래 복원 래퍼는 백업 디렉터리에서 사전순으로 가장 뒤에 오는 *_pmworks.war 파일을 골라 기존 JEUS 관리 대상에 복사했다. 타임스탬프 형식 덕분에 선택 구현은 간단하지만, 가장 최신 파일이 정상 파일이라는 뜻은 아니다. 배포 A에 문제가 있는데 완전히 해결하기 전에 배포 B를 시도해 A를 백업했다고 가정해 보자. 이제 최신 백업은 문제가 있는 산출물이다. 롤백을 다시 실행해도 같은 백업이 선택돼 결과가 바뀌지 않을 수 있다. 재설계한 흐름에서는 이전 산출물을 .applications에 직접 쓰지 말고 JEUS가 지원하는 배포 경로로 복원해야 한다.

현행 작업은 백업 승인을 기다리며 멈추지 않는다. 복원 래퍼가 실행되는 시점에 이름이 일치하는 최신 파일을 선택한다. 운영자가 작업 시작 전에 백업 디렉터리를 살펴볼 수는 있지만, 다른 배포가 동시에 실행될 수 있으므로 그 확인이 선택 대상을 고정하거나 파일이 정상이었음을 입증하지는 못한다. 더 견고한 설계라면 Git 리비전, 산출물 SHA-256, 배포 시각, 검증 결과, 마지막으로 정상 확인된 릴리스 상태를 매니페스트에 기록하고 특정 백업 ID를 선택할 수 있게 한다. 상태 검증과 출처 정보가 마련되기 전에는 이 작업을 ‘저장된 최신 WAR 복원’이라고 설명해야지, ‘마지막 정상 릴리스 자동 복원’이라고 설명해서는 안 된다.

명령만 실행하는 배포 그룹의 stop && restore && start 연결은 실패 시 즉시 중단하므로 유용하다. 하지만 중지에 성공한 뒤 복원이 실패하면 시작 명령도 건너뛰어 서비스가 중지된 채 남을 수 있다. 이 상태에서 대상 파일을 확인하고, 검증된 다른 백업을 고른 다음, 안전하게 재시작하는 운영자 복구 절차를 문서화해야 한다. ; start를 무조건 덧붙여 해결하려 해서는 안 된다. 누락되거나 일부만 남은 WAR로 기동할 수 있기 때문이다.

서로 다른 Jenkins 작업은 같은 VM에서 동시에 실행될 수도 있다. 한 작업의 disableConcurrentBuilds()는 다른 롤백 작업과 배포 작업을 직렬화하지 않는다. 두 흐름이 공유하는 대상 환경 잠금을 추가하고 복구 중 대기 중인 릴리스를 어떻게 처리할지 정해야 한다.

7. JEUS 종료 코드를 단독 판정이 아닌 신호로 다룬다

이 사례에서 혼란스러웠던 관찰 중 하나는 JEUS 출력에 다음 내용이 있는데도 Jenkins 단계가 실패로 표시됐다는 점이다.

Successfully started the server.
The server state is now RUNNING.
Failed command ... with status 10

프로세스가 0이 아닌 상태 코드를 반환하면 Jenkins는 셸 단계를 실패로 표시한다. RUNNING이라는 로그와 종료 코드 10은 서로 상충하는 신호이므로 조사가 필요하다. 기존 시작 래퍼는 해당 환경의 로그 검토와 반복 테스트를 거친 뒤 종료 코드 10을 0으로 실제 변환했다. 다만 예외 처리 분기 안에서 실행 상태나 HTTP 응답을 새로 확인하지는 않았다. 따라서 이 변환은 그 환경에서 관찰한 동작을 반영할 뿐, JEUS 전반에 적용되는 공식 규칙이나 애플리케이션 정상 동작의 증명은 아니다.

다른 JEUS 환경에 10 -> 0을 일괄 적용해서는 안 된다. 래퍼는 먼저 서비스 상태를, 가능하다면 실제 애플리케이션 요청까지 확인한 뒤에만 성공을 반환해야 한다. 어느 하나라도 실패하면 실패 상태 코드를 유지해 배포 실패가 드러나게 한다. 원래 명령 출력과 반환 코드도 감사 기록에 남겨야 예외가 향후 동작 변화를 숨기지 않는다.

이 사례의 현행 수동 애플리케이션 접속 확인은 서버 프로세스 확인 다음에 수행한다. JEUS 프로세스가 정상이더라도 잘못된 WAR를 서비스할 수 있고, 파일 전송 성공만으로 애플리케이션의 의존성, 경로, 데이터베이스 연결 상태를 알 수는 없다.

8. 전체 경로를 검증하고, 남은 빈틈을 보완한다

구현된 개발 환경 흐름에서는 운영자가 하나의 릴리스 시도를 다음 관찰 결과로 이어서 확인할 수 있어야 한다.

경계남길 증적이것만으로는 입증할 수 없는 것
체크아웃Git 리비전과 저장소·브랜치패키징된 WAR에 프런트엔드 결과물이 포함됐는지
프런트엔드 빌드도구 버전, 의존성 설치 결과, Vite 출력Maven이 새 정적 파일을 포함했는지
백엔드 빌드Maven 프로필, JDK 버전, WAR 이름과 크기바로 그 WAR가 VM에 도달했는지
백업파일명, 타임스탬프, 크기, 선택적 체크섬백업한 애플리케이션이 정상이었는지
전송대상 경로, 파일 크기, 선택적 체크섬JEUS가 새 산출물로 시작했는지
재시작JEUS 명령 종료 코드와 관찰된 상태HTTP 요청과 의존성이 정상인지
애플리케이션예상한 배포에서 실제로 반환한 응답모든 업무 흐름이 정상인지
롤백선택한 백업 ID, 복원된 파일과 응답‘최신’ 파일이 의도한 이전 릴리스인지

이 표는 빌드 성공, 배포 성공, 서비스 정상 동작을 구분한다. 원래 사례는 반복 가능한 빌드·배포 경로와 별도의 수동 롤백 경로를 구현했다. 그러나 위의 모든 증적 공백을 메우지는 못했다. 다음 변경은 줄일 수 있는 위험을 기준으로 우선순위를 정할 수 있다.

  1. 실제 애플리케이션 상태 확인 엔드포인트를 추가하고 예상대로 응답하지 않으면 배포를 실패로 처리한다. 그 이후에야 반복 실행과 오탐을 막는 명시적 보호 장치를 갖춘 자동 롤백 트리거를 고려한다.
  2. JEUS 관리 .applications에 직접 쓰는 배포와 롤백을 중단한다. 버전이 구분되는 WAR를 해당 디렉터리 밖에 준비하고, 두 방향 모두 JEUS가 지원하는 애플리케이션 관리 절차를 사용한다.
  3. 빌드·전송·백업한 WAR의 Git 리비전과 SHA-256을 기록한다. 타임스탬프만으로 선택하지 말고 마지막 정상 릴리스 매니페스트를 유지한다.
  4. 잠금 파일과 사내 Nexus 의존성 경로를 검증한 뒤 npm install을 npm ci로 바꾼다. 반복 가능한 빌드에 필요한 만큼 Node.js, JDK, Maven, 플러그인, 공유 라이브러리 리비전을 고정한다.
  5. 배포와 롤백 사이에 작업 간 잠금을 추가하고, 최초 배포 경로와 JEUS 중지 후 복원 실패 시의 절차를 마련한다.
  6. 이전 산출물을 삭제하기 전에 백업 보존 정책과 복원 훈련을 정한다. ‘10개 유지’나 ‘30일 후 삭제’ 같은 정책은 복구 요구사항과 저장 공간 한계를 이해한 뒤에야 안전하게 결정할 수 있다.
  7. 운영 환경에는 변경 승인 게이트, 분리된 운영 계정과 백업 위치, 검증된 복구 의사 결정 절차를 추가한다. 브랜치 매개변수만 바꿔 개발 작업을 운영용으로 승격해서는 안 된다.

실무적 교훈은 Jenkinsfile을 만들면 배포가 안전해진다는 말이 아니다. 릴리스에는 추적 가능한 산출물, 명확한 권한 경계, 검증된 교체 순서, 그리고 운영자가 의도한 파일을 실제로 선택하는 복구 경로가 필요하다는 것이다.

참고 자료

관련 글