-Spring Boot 4.1.0 · Java 25 · Fabric8 Kubernetes Client 7.1.0-
Многие современные серверные системы работают в Kubernetes, и запросы к Pod для проверки состояния сервисов выполняются очень часто. До сих пор операторы (или ответственные разработчики) решали эту задачу, подключаясь к серверу и напрямую вводя команды kubectl, однако с ростом масштаба эксплуатации такой подход выявляет ряд ограничений.
В этой статье рассматривается процесс реализации функции, использующей Fabric8 Kubernetes Client в приложении Spring Boot для прямого вызова Kubernetes API из слоя сервисов и получения состояния Pod. Описаны все этапы: от проверки информации о подключении к кластеру до реализации кода и настройки разрешений, необходимых для развёртывания.
1. Предпосылки разработки
1.1 Ограничения поиска на основе shell-команд
Ранее, если возникало подозрение, что состояние Pod ненормальное, оператор подключался к серверу по SSH и напрямую вводил команды kubectl. Изначально этого было достаточно, но по мере роста сервиса накопились следующие проблемы.
-
Участники, не относящиеся к разработчикам и специалистам по эксплуатации, например PM и сотрудники службы поддержки клиентов, также хотели немедленно проверять состояние, однако предоставление им доступа к кластеру создавало угрозу безопасности.
-
Раздача kubeconfig всем заинтересованным лицам увеличивала количество точек управления и усложняла отзыв разрешений и отслеживание аудита.
-
Результаты выполнения shell-команд предоставлялись только в виде текста, поэтому их было трудно интегрировать с информационными панелями и системами оповещений, а также накапливать в качестве исторических записей.
В конечном итоге эти проблемы привели к требованию «перенести проверку состояния Pod на уровень сервиса (API)».
1.2 Другие предпосылки разработки
-
Автоматизация реагирования на инциденты: приложение должно напрямую обнаруживать изменения состояния, чтобы программно настраивать последующие действия, такие как перезапуск и отправка оповещений.
-
Интеграция с информационной панелью: требуется серверная часть, предоставляющая эти сведения через REST API для визуализации состояния Pod, узлов и меток.
-
Управление несколькими пространствами имён: требовалась возможность просматривать несколько пространств имён в интегрированном виде на одном экране.
2. Выбор библиотеки: почему Fabric8
В экосистеме Java ведущими клиентами Kubernetes являются официальный client-java и Fabric8 kubernetes-client. В этом проекте был выбран Fabric8 по следующим причинам.
|
Пункт |
Описание |
|---|---|
|
Стиль API |
Его Fluent API в форме .pods().inNamespace().withLabel() отличается высокой читаемостью. |
|
Совместимость со Spring |
Поскольку Spring Cloud Kubernetes основан на Fabric8, интеграция со Spring Boot выполняется естественным образом. |
|
Автоматическое определение конфигурации |
Config.autoConfigure() автоматически определяет, следует ли использовать конфигурацию внутри кластера или локальный 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 внутри кластера, используется конфигурация внутри кластера, поэтому знать этот адрес не требуется (KUBERNETES_SERVICE_HOST внедряется автоматически). Если подключение выполняется удалённо с внешнего сервера, необходимо проверить фактический IP-адрес узла (kubectl get nodes -o wide, hostname -I). Для облачного управляемого кластера конечную точку можно узнать с помощью таких команд, как aws eks describe-cluster и gcloud container clusters describe.
3.2 Быстрая проверка ответа сервера
После проверки адреса убедитесь, что сервер отвечает, вызвав конечную точку проверки работоспособности с помощью curl. Параметр -k пропускает проверку TLS для самоподписанного CA и должен использоваться только для тестирования подключения.
$ curl -k https://<주소>:6443/healthz
ok
$ curl -k https://<주소>:6443/api # 토큰 없이 호출 시 401이 와도 연결은 정상
3.3 Проверка способа аутентификации · сертификата CA
Способ аутентификации можно определить по полям в разделе users команды kubectl config view.
|
Поле kubeconfig |
Способ аутентификации |
|---|---|
|
client-certificate / client-key |
mTLS (сертификат клиента) |
|
token |
Статический токен или токен ServiceAccount |
|
Блок exec присутствует |
Аутентификация с интеграцией с облачным CLI (например, aws eks get-token) |
При развёртывании в виде Pod внутри кластера обычно используется подход с конфигурацией внутри кластера: токен ServiceAccount автоматически монтируется независимо от локального kubeconfig. Поскольку сертификат 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, попросите администратора создать Role/RoleBinding, описанные в разделе 4.5.
$ 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
Config.autoConfigure(null) автоматически обнаруживает конфигурацию внутри кластера и локальный kubeconfig в заданной последовательности, позволяя использовать один и тот же код в разработке и production без отдельного ветвления.
@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 для Deployment
При развертывании в виде Pod внутри кластера предоставьте ServiceAccount этого Pod только минимально необходимые разрешения (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
Укажите serviceAccountName: my-backend-sa в Deployment. Если необходимо выполнять запросы к нескольким пространствам имён, область действия можно расширить с помощью ClusterRole/ClusterRoleBinding.
4.6 Обработка исключений
Вызовы Kubernetes API выбрасывают KubernetesClientException в таких случаях, как ошибка аутентификации (401) или недостаток разрешений (403). Обрабатывайте эти исключения через общий обработчик исключений, чтобы возвращать понятные коды состояния.
@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, применение той же структуры позволяет естественным образом расширить её для получения статуса других ресурсов, таких как Deployments, Services и Nodes. Впоследствии эти накопленные API можно подключить к системам оповещений или внутренним операционным панелям, создав основу для более быстрого обнаружения признаков сбоев и реагирования на них.
Jsia