-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として提供するバックエンドが必要です。
-
複数ネームスペースの管理:複数のネームスペースを1つの画面で統合して取得する必要がありました。
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取得サービス
全リストの取得、ラベルベースのフィルタリング、単一項目の取得の3つが、最も頻繁に求められます。
@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を指定します。複数のNamespaceを参照する必要がある場合は、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