23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
Git 저장소에 커밋된 소스 코드를 빌드해 배포했는데 운영 서버가 로컬 테스트와 전혀 다르게 동작하거나, 공식 API 문서를 그대로 복사해 호출했는데 400 Bad Request가 반환되는 상황은 백엔드 실무에서 흔히 일어납니다. 원인은 명확합니다. 개발자가 바라보는 '기록'(형상관리 커밋, 작성된 API 문서, 셸 스크립트 가이드)과 실제 배포되어 구동 중인 '런타임'(JVM 클래스 파일, 활성화된 엔드포인트, 쿠버네티스 클러스터의 Pod) 사이에 괴리가 생겼기 때문입니다.
오늘 소식 중 세 개가 이 문제를 서로 다른 계층에서 해결한 사례를 보여줍니다. 문서와 서버 구현의 분리, 긴급 패치로 소실된 원본 코드의 역공학 복구, 터미널 수동 명령을 서비스 내부 API 호출로 전환한 인프라 진단 자동화입니다. 기록을 신뢰하지 못해 사람이 일일이 터미널에 접속하거나 문서를 대조하던 방식을 걷어내고, 프로그래밍 방식으로 런타임의 진실을 복구하는 방법을 정리합니다.
외부 고객사나 사내 타 팀에 제공하는 Open API 문서는 갱신 주기가 코드 배포 주기와 어긋나기 쉽습니다. 채널톡 백엔드 팀의 Open API 개선기에 따르면, 과거 채널톡 Open API는 문서가 복잡하고 링크가 깨져 있거나 설명이 낡아 연동을 포기했다는 피드백이 소셜 미디어(Threads)에 올라왔습니다.
조사 결과 실제 서버의 API 응답 구조와 문서에 표기된 JSON 명세가 일치하지 않는 현상이 확인되었습니다. 서비스 코드가 변경될 때 문서 갱신 작업이 누락되거나, 모놀리식 코드베이스 내부에서 공개 API와 내부 로직이 얽혀 있어 발생한 문제였습니다. 채널톡 팀은 이를 해결하기 위해 명세 중심의 문서 관리 체계를 도입하고 Open API 전용 서버를 물리적으로 분리하는 아키텍처 결정을 내렸습니다.
여기서 얻을 수 있는 해석은, API 문서를 단순한 정적 가이드로 취급하면 코드 릴리스와 문서 간의 불일치는 필연적으로 발생한다는 점입니다. 엔드포인트의 라우팅과 입출력 스키마를 단일 명세로부터 유도하거나 전용 프록시 레이어를 통해 일치시키지 않으면, 외부 개발자는 존재하지 않는 필드를 참조하거나 필수 파라미터를 누락하게 됩니다.
이 아키텍처는 지금 써볼 만합니다. 공개 API를 제공하는 팀이라면 내부 비즈니스 로직과 공개 인터페이스를 같은 배포 파이프라인에서 분리하고, OpenAPI Spec(Swagger)과 같은 표준 명세를 기준으로 라우팅 및 유효성 검증을 자동화하는 것이 운영 부담을 줄이는 가장 안정적인 방법입니다.
배포 파이프라인이 완전히 정착되기 전 레거시 시스템을 운영했던 조직에서는 더 심각한 불일치가 발생합니다. 올리브영 백오피스 시스템의 복구 사례에서는 10년 된 운영 서버에 빌드된 .class 파일만 존재하고, 이에 대응하는 최신 소스 코드가 Git 저장소에서 유실된 상태를 마주했습니다.
과거 장애 대응 과정에서 CI/CD 파이프라인 바깥에서 서버에 직접 파일을 복사해 넣는 수동 주입(Hot-fix injection)이 발생했거나, 형상관리에 커밋하지 않은 채 바이너리만 빌드해 배포했던 흔적이 기술 부채로 남은 것입니다. 올리브영 팀은 Java 디컴파일러를 활용해 운영 클래스 파일로부터 소스를 역으로 추출하고, AI 분류 및 재컴파일 비교를 통한 4단계 절차로 원본 소스를 검증하여 형상관리 시스템을 복구했습니다.
# 1. 운영 서버의 JAR/WAR에서 클래스 파일 추출
unzip -q production-legacy-service.war -d ./extracted-app
# 2. Fernflower 또는 CFR 디컴파일러를 통한 소스 코드 복원 예시
java -jar cfr-0.152.jar ./extracted-app/WEB-INF/classes/com/service/LegacyLogic.class \
--outputdir ./recovered-src
# 3. 복원된 소스를 동일 컴파일러 버전으로 재컴파일
javac -source 1.8 -target 1.8 -d ./recompiled-check \
./recovered-src/com/service/LegacyLogic.java
# 4. 바이트코드 동등성 검증 (javap 역어셈블 결과 비교)
javap -c -p ./extracted-app/WEB-INF/classes/com/service/LegacyLogic.class > prod_byte.txt
javap -c -p ./recompiled-check/com/service/LegacyLogic.class > recompiled_byte.txt
diff -u prod_byte.txt recompiled_byte.txt
디컴파일러로 복원한 코드는 변수명이나 컴파일러 최적화 옵션에 따라 원본과 미세하게 다를 수 있으므로, 복원된 코드를 동일한 JDK 버전으로 다시 컴파일한 뒤 생성된 바이트코드가 운영 바이트코드와 일치하는지 javap나 해시 비교로 교차 검증해야 합니다.
이 방법은 레거시 정상화 작업 시 조건부로 유효합니다. 긴급 상황에서 단일 진실 공급원(Single Source of Truth)을 되찾기 위한 구제책으로는 훌륭하지만, 근본적으로는 CI/CD 외부에서의 서버 파일 임의 수정을 인프라 수준(컨테이너 이미지 불변성, 서버 쓰기 권한 격리)에서 원천 차단하는 조치가 병행되어야 합니다.
형상과 런타임의 불일치는 애플리케이션 코드뿐 아니라 인프라 운영 상태 진단에서도 발생합니다. Spring Boot 기반 Pod 상태 조회 구현 사례는 클러스터 환경에서 운영자가 매번 SSH로 접속해 kubectl get pods 명령을 직접 입력하던 방식을 서비스 내부 API로 전환한 과정을 다룹니다.
운영자가 수동으로 터미널 명령을 실행해 상태를 확인하는 방식은 작업자의 숙련도에 의존하며, 클러스터 접속 권한을 운영자 개인에게 넓게 열어주어야 하므로 보안상 한계가 있습니다. 해당 팀은 Spring Boot 4.1.0, Java 25 환경에 Fabric8 Kubernetes Client 7.1.0을 도입하여, 백엔드 서비스가 Kubernetes API 서버를 직접 호출하도록 구조를 바꿨습니다.
package com.example.k8s.service;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ClusterPodInspectionService {
private final KubernetesClient kubernetesClient;
public ClusterPodInspectionService() {
// 클러스터 내부 실행 시 ServiceAccount 토큰을 자동 로드
this.kubernetesClient = new KubernetesClientBuilder().build();
}
public record PodStatusDto(String podName, String phase, String podIP, int restartCount) {}
public List<PodStatusDto> getServicePodStatuses(String namespace, String appLabelValue) {
PodList podList = kubernetesClient.pods()
.inNamespace(namespace)
.withLabel("app", appLabelValue)
.list();
return podList.getItems().stream()
.map(this::mapToDto)
.collect(Collectors.toList());
}
private PodStatusDto mapToDto(Pod pod) {
String name = pod.getMetadata().getName();
String phase = pod.getStatus().getPhase();
String podIP = pod.getStatus().getPodIP();
int totalRestarts = pod.getStatus().getContainerStatuses().stream()
.mapToInt(cs -> cs.getRestartCount())
.sum();
return new PodStatusDto(name, phase, podIP, totalRestarts);
}
}
터미널을 거치지 않고 서비스 계정(ServiceAccount)의 최소 권한(RBAC, 역할 기반 접근 제어)을 애플리케이션에 부여함으로써, 인프라의 실제 구동 상태를 정형화된 JSON 데이터로 즉시 조회하고 백오피스 대시보드나 헬스체크 파이프라인에 연결할 수 있습니다.
이 접근 방식은 지금 써볼 만합니다. 운영자가 셸 스크립트나 터미널에 의존해 런타임 상태를 점검하는 방식에서 벗어나, RBAC 권한이 제어된 클라이언트 SDK를 애플리케이션에 통합하면 진단 속도가 빨라지고 권한 오남용을 줄일 수 있습니다.
세 사례는 개발 영역이 각기 다르지만 해결 방식의 공통점이 있습니다:
런타임 상태와 코드 형상을 프로그래밍 방식으로 일치시키려는 팀은 다음 세 가지를 먼저 점검해야 합니다.
RoleBinding이 pods/get, pods/list 외에 불필요한 쓰기(create, delete) 권한을 갖지 않도록 제한되어 있는지 확인해야 합니다.readOnlyRootFilesystem: true)으로 설정하고, 모든 배포가 CI/CD 파이프라인의 Git 태그를 통해서만 이루어지도록 강제해야 합니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.