Fabric8로 서비스별 Pod 상태 조회하기

Fabric8로 서비스별 Pod 상태 조회하기

-Spring Boot 4.1.0 · Java 25 · Fabric8 Kubernetes Client 7.1.0-

현대의 많은 백엔드 시스템은 Kubernetes 위에서 운영되며, 서비스 상태 점검을 위한 Pod 조회는 매우 빈번하게 발생합니다. 지금까지는 운영자(또는 개발 담당자)가 서버에 접속해 kubectl 명령어를 직접 입력하는 방식으로 처리해 왔지만, 운영 규모가 커질수록 이 방식은 여러 한계를 드러냅니다.

본 글에서는 Spring Boot 애플리케이션에 Fabric8 Kubernetes Client를 도입하여, 서비스 단에서 직접 Kubernetes API를 호출해 Pod 상태를 조회하는 기능을 구현한 과정을 소개합니다. 클러스터 접속 정보 확인부터 코드 구현, 배포 시 필요한 권한 설정까지 순서대로 다룹니다.

1. 개발 배경

1.1 셸 접속 기반 조회 방식의 한계

기존에는 Pod 상태가 의심될 때마다 운영자가 서버에 SSH로 접속해 kubectl 명령어를 직접 입력하는 방식을 사용해 왔습니다. 이 방식은 초기에는 충분했지만, 서비스가 커지면서 다음과 같은 문제가 누적되었습니다.

  • 개발자·운영 담당자가 아닌 구성원(PM, 고객 대응 담당자 등)도 상태를 즉시 확인하고 싶어 했으나, 이들에게 클러스터 접근 권한을 주는 것은 보안상 부담이 있었습니다.

  • 모든 담당자에게 kubeconfig를 배포하는 것은 관리 포인트를 늘리고 권한 회수·감사 추적을 어렵게 만들었습니다.

  • 셸 명령 결과는 텍스트로만 제공되어 대시보드·알림 시스템과 연동하거나 이력으로 축적하기 어려웠습니다.

이러한 문제들은 결국 "Pod 상태 조회를 서비스(API) 단으로 끌어올리자"는 요구로 이어졌습니다.

1.2 그 밖의 개발 배경

  • 장애 대응 자동화: 상태 변화를 애플리케이션이 직접 감지해야 재시작·알림 등 후속 처리를 코드로 구성할 수 있습니다.

  • 대시보드 연동: Pod 상태·노드·라벨 정보를 시각화하려면 이를 REST API로 제공하는 백엔드가 필요합니다.

  • 다중 네임스페이스 관리: 여러 네임스페이스를 하나의 화면에서 통합 조회할 필요가 있었습니다.

2. 라이브러리 선택: 왜 Fabric8인가

Java 진영의 대표적인 Kubernetes 클라이언트는 공식 client-java와 Fabric8 kubernetes-client입니다. 본 프로젝트는 다음 이유로 Fabric8을 선택하였습니다.

항목

설명

API 스타일

.pods().inNamespace().withLabel() 형태의 Fluent API로 가독성이 높습니다.

Spring 친화성

Spring Cloud Kubernetes가 Fabric8 기반이라 Spring Boot와 결합이 자연스럽습니다.

설정 자동 탐지

Config.autoConfigure()가 in-cluster/로컬 kubeconfig를 자동 판별합니다.

3. 사전 준비: 클러스터 접속 정보 확인

코드 작성에 앞서 API 서버 주소, 인증 방식, CA 인증서, 해당 계정의 권한(RBAC)을 정확히 파악해야 합니다.

3.1 API 서버 주소 확인

kubectl이 설정되어 있다면 다음 명령으로 간단히 확인할 수 있습니다.

$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:6443

127.0.0.1로 표시된다면 주의가 필요합니다. 이는 컨트롤 플레인 노드 자기 자신을 가리키는 주소이므로, 애플리케이션이 동일 서버에서 구동될 때만 그대로 쓸 수 있습니다. 클러스터 내부 Pod로 배포한다면 in-cluster 설정을 사용하므로 주소를 몰라도 되고(KUBERNETES_SERVICE_HOST 자동 주입), 외부 서버에서 원격 접속한다면 노드의 실제 IP(kubectl get nodes -o wide, hostname -I)를 확인해야 합니다. 클라우드 관리형 클러스터라면 aws eks describe-cluster, gcloud container clusters describe 등으로 엔드포인트를 조회할 수 있습니다.

3.2 응답 여부 빠르게 확인하기

주소 확인 후에는 curl로 헬스 체크 엔드포인트를 호출해 응답 여부를 검증합니다. -k는 자체 서명 CA에 대한 TLS 검증을 건너뛰는 옵션으로, 연결 확인 용도로만 사용합니다.

$ curl -k https://<주소>:6443/healthz
ok

$ curl -k https://<주소>:6443/api   # 토큰 없이 호출 시 401이 와도 연결은 정상

3.3 인증 방식 · CA 인증서 확인

kubectl config view의 users 항목 아래 필드로 인증 방식을 구분합니다.

kubeconfig 필드

인증 방식

client-certificate / client-key

mTLS (클라이언트 인증서)

token

정적 토큰 또는 ServiceAccount 토큰

exec 블록 존재

클라우드 CLI 연동 인증 (예: aws eks get-token)

클러스터 내부 Pod로 배포하는 경우에는 로컬 kubeconfig와 무관하게 ServiceAccount 토큰이 자동 마운트되는 in-cluster 방식을 쓰는 것이 일반적입니다. CA 인증서는 kubeconfig에 base64로 포함되어 있어 다음과 같이 추출합니다.

$ kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' \
  | base64 -d > ca.crt

3.4 권한(RBAC) 사전 점검

해당 계정이 실제로 Pod 조회 권한을 갖는지 미리 점검합니다. 둘 다 yes가 나와야 하며, no라면 관리자에게 4.5절의 Role/RoleBinding 생성을 요청해야 합니다.

$ kubectl auth can-i list pods --namespace=<대상-네임스페이스>
$ kubectl auth can-i get pods --namespace=<대상-네임스페이스>

4. Spring Boot 구현

4.1 의존성 추가

// build.gradle.kts
implementation("io.fabric8:kubernetes-client:7.1.0")
 
<!-- pom.xml -->
<dependency>
    <groupId>io.fabric8</groupId>
    <artifactId>kubernetes-client</artifactId>
    <version>7.1.0</version>
</dependency>

※ 버전은 배포 시점에 Maven Central에서 다시 확인하는 것을 권장합니다.

4.2 KubernetesClient Bean 등록

Config.autoConfigure(null)는 in-cluster 설정과 로컬 kubeconfig를 순서대로 자동 탐지하므로, 개발 환경과 운영 환경에서 별도 분기 없이 동일한 코드를 쓸 수 있습니다.

@Configuration
public class KubernetesConfig {
 
    @Bean
    public KubernetesClient kubernetesClient() {
        Config config = Config.autoConfigure(null); // in-cluster → kubeconfig 순 탐지
        return new KubernetesClientBuilder().withConfig(config).build();
    }
}

4.3 Pod 조회 서비스

전체 목록 조회, 라벨 기반 필터링, 단건 조회 세 가지가 가장 빈번하게 요구됩니다.

@Service
public class PodQueryService {
 
    private final KubernetesClient kubernetesClient;
 
    public PodQueryService(KubernetesClient kubernetesClient) {
        this.kubernetesClient = kubernetesClient;
    }
 
    // 특정 네임스페이스 Pod 목록 조회
    public List<PodSummary> getPods(String namespace) {
        PodList podList = kubernetesClient.pods()
                .inNamespace(namespace)
                .list();
 
        return podList.getItems().stream()
                .map(this::toSummary)
                .toList();
    }
 
    // 라벨 셀렉터로 필터링 조회
    public List<PodSummary> getPodsByLabel(String namespace, String key, String value) {
        PodList podList = kubernetesClient.pods()
                .inNamespace(namespace)
                .withLabel(key, value)
                .list();
 
        return podList.getItems().stream()
                .map(this::toSummary)
                .toList();
    }
 
    // 단건 조회
    public PodSummary getPod(String namespace, String podName) {
        Pod pod = kubernetesClient.pods()
                .inNamespace(namespace)
                .withName(podName)
                .get();
 
        if (pod == null) {
            throw new NoSuchElementException("Pod not found: " + podName);
        }
        return toSummary(pod);
    }
 
    private PodSummary toSummary(Pod pod) {
        return new PodSummary(
                pod.getMetadata().getName(),
                pod.getMetadata().getNamespace(),
                pod.getStatus().getPhase(),
                pod.getSpec().getNodeName(),
                pod.getMetadata().getLabels(),
                pod.getMetadata().getCreationTimestamp()
        );
    }
}

public record PodSummary(
        String name,
        String namespace,
        String phase,
        String nodeName,
        Map<String, String> labels,
        String createdAt
) {}

4.4 컨트롤러

서비스 계층의 정보를 REST API로 노출하면, 운영 대시보드나 사내 도구에서 그대로 호출해 Pod 상태를 확인할 수 있습니다.

@RestController
@RequestMapping("/api/pods")
public class PodController {
 
    private final PodQueryService podQueryService;
 
    public PodController(PodQueryService podQueryService) {
        this.podQueryService = podQueryService;
    }
 
    @GetMapping
    public List<PodSummary> list(
            @RequestParam String namespace,
            @RequestParam(required = false) String labelKey,
            @RequestParam(required = false) String labelValue
    ) {
        if (labelKey != null && labelValue != null) {
            return podQueryService.getPodsByLabel(namespace, labelKey, labelValue);
        }
        return podQueryService.getPods(namespace);
    }
 
    @GetMapping("/{name}")
    public PodSummary get(@RequestParam String namespace, @PathVariable String name) {
        return podQueryService.getPod(namespace, name);
    }
}

4.5 배포 시 RBAC 설정

클러스터 내부 Pod로 배포하는 경우, 해당 Pod의 ServiceAccount에 최소 권한(get/list/watch)만 부여합니다.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-backend-sa
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: default
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-backend-pod-reader-binding
  namespace: default
subjects:
- kind: ServiceAccount
  name: my-backend-sa
  namespace: default
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Deployment에는 serviceAccountName: my-backend-sa를 지정합니다. 여러 네임스페이스를 조회해야 한다면 ClusterRole/ClusterRoleBinding으로 범위를 확장할 수 있습니다.

4.6 예외 처리

Kubernetes API 호출은 인증 실패(401), 권한 부족(403) 등에서 KubernetesClientException을 던집니다. 공통 예외 처리기로 매핑해 명확한 상태 코드를 반환합니다.

@RestControllerAdvice
public class KubernetesExceptionHandler {
 
    @ExceptionHandler(KubernetesClientException.class)
    public ResponseEntity<String> handleK8sException(KubernetesClientException e) {
        // 403이면 RBAC 권한 부족, 401이면 인증 실패로 해석
        int code = e.getCode();
        return ResponseEntity.status(code).body("K8s API 호출 실패: " + e.getMessage());
    }
}

5. 마치며

이번 작업을 통해 운영자가 매번 서버에 접속해 kubectl 명령을 입력하던 절차를, 서비스 단 REST API 호출로 대체할 기반을 마련하였습니다. 클러스터 접근 권한이 없는 구성원도 필요한 정보를 안전하게 확인할 수 있게 되었고, 대시보드·알림 시스템과 연동할 수 있는 발판도 마련되었습니다.

이번 구현은 Pod 조회라는 단일 기능에서 출발했지만, 동일한 구조를 응용하면 Deployment, Service, Node 등다른 리소스의 상태 조회로도 자연스럽게 확장할 수 있습니다. 이렇게 축적된 API는 이후 알림 시스템이나 사내 운영 대시보드와 연결되어, 장애 징후를 더 빠르게 포착하고 대응하는 기반으로 활용될 수 있을 것입니다.

Jsia

Site footer