티스토리 뷰
이전 포스트: 쿠버네티스 오퍼레이터를 Golang으로 개발해보기
안녕하세요.
Cloud Strategy팀 박규민입니다.
지난번에 Golang으로 쿠버네티스 오퍼레이터를 간단하게 만들어 봤습니다. 하지만 국내에서는 아무래도 Golang보다는 Java의 수요가 압도적으로 많은데요. 이번 포스트로 Java로 오퍼레이터를 구현하는 과정을 보여드리겠습니다.
Java Operator SDK
Java Operator SDK는 Kubernetes Client Java API인 fabric8io를 기반으로 작성되어 있습니다. 이는 세부적으로 쿠버네티스와 상호 작용하기 위한 Low Level 단에서의 코드 작성 걱정 없이 개발자에게 친숙한 Java API를 사용하여 오퍼레이터를 쉽게 작성할 수 있도록 설계되어 있습니다.
Architecture
Operator는 Controller 클래스의 집합이며, Controller 클래스가 Kubernetes 단일 리소스를 조정(Reconciling)해주는 역할을 합니다.
EventSourceManager가 Controller와 관련된 여러 EventSource들의 수명 주기를 관리해 줍니다. 여기에서의 Event는 리소스 조정을 유발하는 사건을 의미합니다.
EventSource에서 EventProcessor에 전파되는 Event를 생성합니다. (Controller와 관련된 기본 리소스의 변경 사항을 감시할 때는 ControllerResourceEventSource를 통해 Event를 전파하여 관련 상태를 캐싱합니다) Event를 받은 EventProcessor에서 리소스가 아직 처리되지 않은 경우, 적절한 Reconciler 메소드로 호출하여 전달해 주는 ReconcilerDispatcher를 호출하여 필요한 모든 정보를 차례대로 전달합니다.
Reconciler 메소드가 끝났을 때 EventProcessor가 다시 호출되어 실행을 완료하고 Controller의 상태를 업데이트합니다. 그리고 Reconciler 메소드에서 반환한 결과에 따라서 필요한 경우, ReconcilerDispatcher는 Kubernetes API 서버에 호출합니다.
마지막으로 EventProcessor는 요청 재시도를 해야 할지, 그리고 동일한 리소스에 대해 수신된 후속 Event가 없는지 확인합니다. 이 중 어느 것도 일어나지 않으면, Event 처리가 완료됩니다.
직접 구현해야 할 포인트는?
개발자들에게는 다음과 같은 구성 요소들을 Java Class로 만들 필요가 있습니다.
- Primary Resource: k8s 클러스터에 배포할 CRD
- Spec, Status: CRD에 필요한 내부 구성 요소
- Spec: 사용자가 CR에 적용할 상태 정의
- Status: CR의 현재 상태
- Reconciler: Primary Resource의 변경사항을 감지하고 조정
- KubernetesDependentResource: CRD 배포의 결과로 클러스터에서 만들고 싶은 각 K8s 리소스(DeploymentConfig, Service, Ingress 등)
Project 생성
먼저 Spring Boot 기반으로 프로젝트를 생성합니다.
Intellij 기준으로 Spring Initializr Generators 메뉴를 통해 프로젝트를 생성합니다.
build.gradle에서 다음과 같이 수정합니다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.1'
id 'io.spring.dependency-management' version '1.1.5'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
annotationProcessor 'io.fabric8:crd-generator-apt:6.13.0'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'io.javaoperatorsdk:operator-framework-spring-boot-starter:5.5.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.javaoperatorsdk:operator-framework-spring-boot-starter-test:5.5.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
Custom Resource 작성
customresources라는 package를 만든 다음, 하위에 Spec, Status, Primary Resource Class를 만듭니다.
Spec Class
package com.example.petclinicoperatorjava.customresources;
// Lombok @Data로도 적용 가능
public class PetclinicSpec {
private String image;
private Integer size;
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
private Integer port;
}
Status Class
ObservedGenerationAwareStatus 클래스를 확장합니다.
이는 k8s의 controller가 Petclinic CR에 대한 변경사항을 추적할 수 있도록 Petclinic 오브젝트가 매번 변경될 때마다 Petclinic CR 내에 observedGeneration status 값을 증가시킵니다.
package com.example.petclinicoperatorjava.customresources;
import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;
public class PetclinicStatus extends ObservedGenerationAwareStatus {
}
Primary Resource Class
Petclinic CR을 Class로 생성합니다.
CustomResource 추상클래스를 확장할 때 PetclinicSpec과 PetclinicStatus 클래스를 참조합니다.
package com.example.petclinicoperatorjava.customresources;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;
@Group("spring.my.domain")
@Version("v1")
public class Petclinic extends CustomResource<PetclinicSpec, PetclinicStatus> implements Namespaced {
@Override
public String toString() {
return "Petclinic{spec=" + spec + ", status=" + status + "}";
}
}
Dependent Resources 작성
dependentresources라는 package를 생성한 다음, 하위에 KubernetesDependentResource 클래스들을 만듭니다.
CRUDKubernetesDependentResource 추상클래스를 확장하여 CRD에 필요한 k8s 리소스들의 manifest를 class로 정의합니다.
@KubernetesDependent 어노테이션을 통해 Petclinic CR의 변화에 대응하여 해당 k8s 리소스의 수명 주기를 관리합니다.
Deployment
package com.example.petclinicoperatorjava.dependentresources;
import com.example.petclinicoperatorjava.customresources.Petclinic;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
@KubernetesDependent
public class PetclinicDeploymentResource extends CRUDKubernetesDependentResource<Deployment, Petclinic> {
public PetclinicDeploymentResource() {
super(Deployment.class);
}
@Override
protected Deployment desired(Petclinic petclinic, Context<Petclinic> context) {
final ObjectMeta petclinicMetadata = petclinic.getMetadata();
final String petclinicName = petclinicMetadata.getName();
return new DeploymentBuilder()
.editMetadata()
.withName(petclinicName)
.withNamespace(petclinicMetadata.getNamespace())
.addToLabels("app", petclinicName)
.endMetadata()
.editSpec()
.withSelector(new LabelSelectorBuilder()
.addToMatchLabels("app", petclinicName)
.build())
.withReplicas(petclinic.getSpec().getSize())
.withTemplate(new PodTemplateSpecBuilder()
.editMetadata()
.addToLabels("app", petclinicName)
.endMetadata()
.editSpec()
.withContainers(new ContainerBuilder()
.withName(petclinicName + "-container")
.withImage(petclinic.getSpec().getImage())
.addToPorts(new ContainerPortBuilder()
.withContainerPort(petclinic.getSpec().getPort())
.build())
.build())
.endSpec()
.build())
.endSpec()
.build();
}
}
Service
package com.example.petclinicoperatorjava.dependentresources;
import com.example.petclinicoperatorjava.customresources.Petclinic;
import io.fabric8.kubernetes.api.model.*;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
@KubernetesDependent
public class PetclinicServiceResource extends CRUDKubernetesDependentResource<Service, Petclinic> {
public PetclinicServiceResource() {
super(Service.class);
}
@Override
protected Service desired(Petclinic petclinic, Context<Petclinic> context) {
final ObjectMeta petclinicMetadata = petclinic.getMetadata();
final String petclinicName = petclinicMetadata.getName();
return new ServiceBuilder()
.editMetadata()
.withName(petclinicName)
.withNamespace(petclinicMetadata.getNamespace())
.addToLabels("app", petclinicName)
.endMetadata()
.editSpec()
.withType("NodePort")
.addToSelector("app", petclinicName)
.addToPorts(new ServicePortBuilder().withName("http").withPort(petclinic.getSpec().getPort()).withProtocol("TCP").withTargetPort(new IntOrStringBuilder().withValue(petclinic.getSpec().getPort()).build()).build())
.endSpec()
.build();
}
}
Ingress
package com.example.petclinicoperatorjava.dependentresources;
import com.example.petclinicoperatorjava.customresources.Petclinic;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.networking.v1.*;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
@KubernetesDependent
public class PetclinicIngressResource extends CRUDKubernetesDependentResource<Ingress, Petclinic> {
public PetclinicIngressResource() {
super(Ingress.class);
}
@Override
protected Ingress desired(Petclinic petclinic, Context<Petclinic> context) {
final ObjectMeta petclinicMetadata = petclinic.getMetadata();
final String petclinicName = petclinicMetadata.getName();
return new IngressBuilder()
.editMetadata()
.withName(petclinicName)
.withNamespace(petclinicMetadata.getNamespace())
.addToLabels("app", petclinicName)
.endMetadata()
.editSpec()
.withIngressClassName("nginx")
.withRules(new IngressRuleBuilder()
.withHttp(new HTTPIngressRuleValueBuilder()
.withPaths(new HTTPIngressPathBuilder()
.withPath("/")
.withPathType("Prefix")
.withBackend(new IngressBackendBuilder()
.withService(new IngressServiceBackendBuilder()
.withName(petclinicName)
.withPort(new ServiceBackendPortBuilder()
.withNumber(petclinic.getSpec().getPort())
.build())
.build())
.build())
.build())
.build())
.build())
.endSpec()
.build();
}
}
Reconciler 작성
Petclinic Primary Resource의 변경사항을 감지하고 조정을 해주는 Reconciler 클래스를 작성합니다.
@ControllerConfiguration 어노테이션을 붙여 dependents 속성을 통해 @KubernetesDependent 어노테이션을 붙인 KubernetesDependentResource 클래스를 @Dependent 어노테이션으로 여기에 연결합니다.
package com.example.petclinicoperatorjava;
import com.example.petclinicoperatorjava.customresources.Petclinic;
import com.example.petclinicoperatorjava.dependentresources.PetclinicDeploymentResource;
import com.example.petclinicoperatorjava.dependentresources.PetclinicIngressResource;
import com.example.petclinicoperatorjava.dependentresources.PetclinicServiceResource;
import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import org.springframework.stereotype.Component;
@ControllerConfiguration(
dependents = {
@Dependent(type = PetclinicDeploymentResource.class),
@Dependent(type = PetclinicServiceResource.class),
@Dependent(type = PetclinicIngressResource.class)
})
public class PetclinicReconciler implements Reconciler<Petclinic>, ErrorStatusHandler<Petclinic>, Cleaner<Petclinic> {
@Override
public UpdateControl<Petclinic> reconcile(Petclinic petclinic, Context<Petclinic> context) {
return UpdateControl.updateResourceAndPatchStatus(petclinic);
}
@Override
public DeleteControl cleanup(Petclinic petclinic, Context<Petclinic> context) {
return DeleteControl.defaultDelete();
}
@Override
public ErrorStatusUpdateControl<Petclinic> updateErrorStatus(Petclinic petclinic, Context<Petclinic> context, Exception e) {
return ErrorStatusUpdateControl.patchStatus(petclinic);
}
}
Config 작성
해당 Reconciler 클래스와 직접 구현한 PetclinicOperator를 각각 Bean으로 등록하도록 Config 클래스를 config package 하위에 만듭니다.
package com.example.petclinicoperatorjava.config;
import com.example.petclinicoperatorjava.PetclinicReconciler;
import io.javaoperatorsdk.operator.Operator;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class PetclinicOperatorConfig {
@Bean
public PetclinicReconciler petclinicReconciler() {
return new PetclinicReconciler();
}
@Bean(initMethod = "start", destroyMethod = "stop")
public Operator operator(List<Reconciler<?>> controllers) {
Operator operator = new Operator();
controllers.forEach(operator::register);
return operator;
}
}
k8s 클러스터에 CRD 적용
io.fabric8:crd-generator-apt 라이브러리에 의해 프로젝트 컴파일을 진행할 시 자동으로 build 디렉토리 내부에서 classpath/META-INF/fabric8 디렉토리에 yml 형식으로 생성됩니다.
이를 지난 시간에 구축한 kind 기반의 로컬 k8s 클러스터에 CRD로 등록합니다.
$ kubectl apply -f ./build/classes/java/main/META-INF/fabric8/petclinics.spring.my.domain-v1.yml
customresourcedefinition.apiextensions.k8s.io/petclinics.spring.my.domain created
로컬 테스트
PetclinicOperatorJavaApplication에서 main 클래스를 실행합니다.
kind cluster에 Petclinic manifest가 적용이 잘 되는지 확인합니다.
$ kubectl config set-context --current --namespace=petclinic
$ kubectl apply -f - <<EOF
apiVersion: spring.my.domain/v1
kind: Petclinic
metadata:
name: sample
spec:
image: springio/petclinic
size: 1
port: 8080
EOF
petclinic.spring.my.domain/sample created
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/sample-594c8976df-5gmzr 1/1 Running 0 47s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/sample NodePort 10.96.34.237 <none> 8080:32494/TCP 48s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/sample 1/1 1 1 48s
NAME DESIRED CURRENT READY AGE
replicaset.apps/sample-594c8976df 1 1 1 48s
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
sample nginx * localhost 80 69s
http://localhost로 접속하여 Petclinic 메인 화면이 나오는지 확인합니다.
Integration Test
@EnableMockOperator 어노테이션을 통해 k8s 클러스터를 mocking 하여 직접 구현한 Operator의 CRD를 적용하여 전용 통합 테스트를 작성할 수 있습니다.
package com.example.petclinicoperatorjava;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.springboot.starter.test.EnableMockOperator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@EnableMockOperator(crdPaths = "classpath:META-INF/fabric8/petclinics.spring.my.domain-v1.yml")
public class PetclinicOperatorUnitTest {
@Autowired
KubernetesClient k8sClient;
@Test
void whenContextLoaded_thenCrdApplied() {
assertThat(
k8sClient
.apiextensions()
.v1()
.customResourceDefinitions()
.withName("petclinics.spring.my.domain")
.get()
).isNotNull();
}
}
Operator 배포
먼저 gradle build task를 실행하여 jar 파일을 생성합니다.
프로젝트 루트 프로젝트 밑에 k8s라는 디렉토리를 만들어 다음과 같이 Dockerfile(./k8s/Dockerfile)을 정의합니다.
FROM bellsoft/liberica-openjdk-alpine:21
COPY build/libs/*-0.0.1-SNAPSHOT.jar /opt/app/app.jar
EXPOSE 80
CMD ["java", "-showversion", "-jar", "/opt/app/app.jar"]
프로젝트 루트 디렉토리에서 docker build 명령어를 실행하여 petclinic java operator 이미지를 생성합니다.
# 개인 docker public registry에 petclinic java operator를 push합니다.
# 현재 로컬 kind cluster에서의 worker node는 linux/arm64 기반이어서 맞출 필요가 있음
$ docker build -f ./k8s/Dockerfile -t ycatt/petclinic-operator-java:0.0.1 . --platform "linux/arm64"
$ docker login
$ docker push ycatt/petclinic-operator-java:0.0.1
kind 로컬 클러스터에 다음과 같은 매니페스트 파일을 배포합니다. (./k8s/petclinic-operator.yaml)
apiVersion: v1
kind: ServiceAccount
metadata:
name: petclinic-operator
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: petclinic-operator
spec:
selector:
matchLabels:
app: petclinic-operator
replicas: 1
template:
metadata:
labels:
app: petclinic-operator
spec:
serviceAccountName: petclinic-operator
containers:
- name: petclinic-operator
image: ycatt/petclinic-operator-java:0.0.1
imagePullPolicy: Always
ports:
- containerPort: 80
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: petclinic-operator-admin
subjects:
- kind: ServiceAccount
name: petclinic-operator
namespace: default
roleRef:
kind: ClusterRole
name: petclinic-operator
apiGroup: ""
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: petclinic-operator
rules:
- apiGroups:
- ""
- "extensions"
- "apps"
resources:
- deployments
- services
- pods
- pods/exec
verbs:
- '*'
- apiGroups:
- "apiextensions.k8s.io"
resources:
- customresourcedefinitions
verbs:
- '*'
- apiGroups:
- "spring.my.domain"
resources:
- petclinics
verbs:
- '*'
- apiGroups:
- "networking.k8s.io"
resources:
- ingresses
verbs:
- '*'
$ kubectl apply -f ./k8s/petclinic-operator.yaml
serviceaccount/petclinic-operator created
deployment.apps/petclinic-operator created
clusterrolebinding.rbac.authorization.k8s.io/petclinic-operator-admin created
clusterrole.rbac.authorization.k8s.io/petclinic-operator created
$ kubectl get pod -n default
NAME READY STATUS RESTARTS AGE
petclinic-operator-6dc9f97b96-qg84p 1/1 Running 0 20m
$ kubectl logs petclinic-operator-6dc9f97b96-qg84p -n default
openjdk version "21.0.3" 2024-04-16 LTS
OpenJDK Runtime Environment (build 21.0.3+10-LTS)
OpenJDK 64-Bit Server VM (build 21.0.3+10-LTS, mixed mode)
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.1)
2024-07-02T06:57:28.720Z INFO 1 --- [petclinic-operator-java] [ main] c.e.p.PetclinicOperatorJavaApplication : Starting PetclinicOperatorJavaApplication v0.0.1-SNAPSHOT using Java 21.0.3 with PID 1 (/opt/app/app.jar started by root in /)
2024-07-02T06:57:28.723Z INFO 1 --- [petclinic-operator-java] [ main] c.e.p.PetclinicOperatorJavaApplication : No active profile set, falling back to 1 default profile: "default"
2024-07-02T06:57:29.603Z WARN 1 --- [petclinic-operator-java] [ main] ault ConfigurationService implementation : Configuration for reconciler 'petclinicreconciler' was not found. Known reconcilers: None.
2024-07-02T06:57:29.619Z INFO 1 --- [petclinic-operator-java] [ main] ault ConfigurationService implementation : Created configuration for reconciler com.example.petclinicoperatorjava.PetclinicReconciler with name petclinicreconciler
2024-07-02T06:57:29.657Z INFO 1 --- [petclinic-operator-java] [ main] io.javaoperatorsdk.operator.Operator : Registered reconciler: 'petclinicreconciler' for resource: 'class com.example.petclinicoperatorjava.customresources.Petclinic' for namespace(s): [all namespaces]
2024-07-02T06:57:29.659Z INFO 1 --- [petclinic-operator-java] [ main] io.javaoperatorsdk.operator.Operator : Operator SDK 4.9.1 (commit: 135b239) built on Tue May 28 08:09:11 GMT 2024 starting...
2024-07-02T06:57:29.666Z INFO 1 --- [petclinic-operator-java] [ main] io.javaoperatorsdk.operator.Operator : Client version: 6.12.1
2024-07-02T06:57:29.667Z INFO 1 --- [petclinic-operator-java] [linicreconciler] i.j.operator.processing.Controller : Starting 'petclinicreconciler' controller for reconciler: com.example.petclinicoperatorjava.PetclinicReconciler, resource: com.example.petclinicoperatorjava.customresources.Petclinic
2024-07-02T06:57:30.233Z INFO 1 --- [petclinic-operator-java] [linicreconciler] i.j.operator.processing.Controller : 'petclinicreconciler' controller started
2024-07-02T06:57:30.360Z INFO 1 --- [petclinic-operator-java] [ main] c.e.p.PetclinicOperatorJavaApplication : Started PetclinicOperatorJavaApplication in 1.885 seconds (process running for 2.43)
IDE에서 실행한 Petclinic Operator Application을 Terminate한 뒤, 기존에 로컬에서 테스트했던 Petclinic CR을 삭제하고 재생성하여 정상적으로 메인 페이지 접속이 되는 지 확인합니다.
$ kubectl delete petclinic --all
petclinic.spring.my.domain "sample" deleted
$ kubectl apply -f - <<EOF
apiVersion: spring.my.domain/v1
kind: Petclinic
metadata:
name: sample
namespace: petclinic
spec:
image: springio/petclinic
size: 1
port: 8080
EOF
petclinic.spring.my.domain/sample created
$ kubectl get all -n petclinic
NAME READY STATUS RESTARTS AGE
pod/sample-594c8976df-k4944 1/1 Running 0 8s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/sample NodePort 10.96.140.108 <none> 8080:30502/TCP 8s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/sample 1/1 1 1 8s
NAME DESIRED CURRENT READY AGE
replicaset.apps/sample-594c8976df 1 1 1 8s
$ kubectl get ing -n petclinic
NAME CLASS HOSTS ADDRESS PORTS AGE
sample nginx * localhost 80 44s
# curl로 확인
$ curl http://localhost:80 -v -o /dev/null
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200
< Date: Tue, 02 Jul 2024 07:31:39 GMT
< Content-Type: text/html;charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Content-Language: en-US
<
{ [3168 bytes data]
100 3161 0 3161 0 0 10577 0 --:--:-- --:--:-- --:--:-- 10788
* Connection #0 to host localhost left intact
마치면서...
이번 포스트에서는 Java를 사용하여 Kubernetes Operator를 직접 구현해 보았습니다. Golang에 비해 구조가 비교적 간단하고 친숙하여 더 쉽게 접근할 수 있다는 느낌이 있는데요. 기존 Java/Spring Boot 기반 서비스와의 연계성, 개발자 커뮤니티의 폭넓은 지원까지 생각해 본다면 Kubernetes Operator를 개발할 때 Java라는 언어로도 꽤나 매력적인 선택이 될 수 있습니다. Java Operator SDK 활용에 작은 도움이 되었기를 바라면서 글을 마치겠습니다.
감사합니다.
참조
- https://javaoperatorsdk.io/docs
- https://github.com/operator-framework/java-operator-sdk/tree/main/sample-operators
- https://www.baeldung.com/java-kubernetes-operator-sdk
- https://techtalk.11stcorp.com/2022/pdf/TECH-TALK-2022_SESSION-02.pdf
'Infra' 카테고리의 다른 글
Redis Vs Mongo DB By Item View Count (이 상품 몇명이 보고 있어요) (2) | 2024.09.25 |
---|---|
Redis Stream 적용기 (5) | 2024.07.11 |
신규 서비스 "꿀템"을 만들기 위한 여정(네? 다음달까지요?) -2편 (30) | 2024.06.30 |
경력 입사자의 스크럼 스프린트 적응기 (with Jira software) (36) | 2024.06.28 |
Jenkins 성능 개선 part1 - 캐싱 적용 (0) | 2023.07.27 |