이전 시간에는 kubebuilder를 사용하여 ClusterJob이라는 Custom Controller의 일부를 구현하였고, 실제로 쿠버네티스에 배포하여 동작을 확인하였다. 이번 시간에는 ClusterJob Custom Controller의 나머지 기능들을 전부 구현해볼 예정이다.
2025.10.25 - [DevOps/Kubernetes] - kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [1]
kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [1]
이번 글에서는 kubebuilder를 사용하여 Custom Resource, Custom Controller를 직접 만들어보는 과정을 정리해보았다.주제 정하기Custom Resource, Custom Controller 만들기 전에 가장 먼저 해야 할 것은 어떤 것을 만
beer1.tistory.com
2025.10.25 - [DevOps/Kubernetes] - kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [2]
kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [2]
// +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch이전 시간에는 kubebuilder를 사용하여 프로젝트를 구축하고, Custom Resource를 만들었다. 이번 시간에는 Custom Controller를 만들어서 ClusterJob의 기능을
beer1.tistory.com
Custom Controller 구현
이전 시간에서는 Preparing 단계까지 구현하였고, 앞으로 Preparing -> Running, Running -> Running, Running -> Preparing, Running -> Failed, Running -> Completed 단계 구현이 남아있다.

Reconcile 구현 - (Preparing → Running)
Preparing 상태의 ClusterJob이 있다면 Controller에서는 해당 ClusterJob의 0번째 그룹에 속한 각 워커노드에 Job을 실행시켜야 한다. 이 기능을 한번 구현해보자.
일단 internal/controller/clusterjob_controller.go 파일의 Reconcile 함수의 Pending case 부분에 다음 코드를 추가하자.
internal/controller/clusterjob_controller.go
func (r *ClusterJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
...
switch clusterJob.Status.Phase {
case "":
...
case "Pending":
logger.Info("ClusterJob is already grouped.", "namespace", clusterJob.Namespace, "name", clusterJob.Name, "phase", clusterJob.Status.Phase)
if err := r.scheduleCurrentNodeGroup(ctx, &clusterJob); err != nil {
return ctrl.Result{}, err
}
case "Running":
...
}- Pending 상태에서는 0번째 실행 노드그룹을 확인하여 각 노드마다 Job을 생성하는 작업을 수행한다.
- 이에 대한 세부 로직은 r.scheduleCurrentNodeGroup() 함수에서 수행한다.
r.scheduleCurrentNodeGroup() 함수는 다음과 같다.
internal/controller/clusterjob_controller.go
const (
ClusterJobNameLabel = "clusterjob/name"
ClusterJobGroupLabel = "clusterjob/group-name"
ClusterJobNodeLabel = "clusterjob/targer-node"
ClusterJobNullGroupName = "null-group"
)
// scheduleNextNodeGroup는 ClusterJob의 현재 실행 노드그룹에 잡을 실행시킨다.
func (r *ClusterJobReconciler) scheduleCurrentNodeGroup(ctx context.Context, clusterJob *clusterbatchv1alpha.ClusterJob) error {
var logger = logf.FromContext(ctx)
var groups = clusterJob.Status.NodeGroups
// 실행 노드그룹의 인덱스를 넘어간 경우는 더 이상 실행할 노드그룹이 없다는 뜻이므로 완료 처리
if clusterJob.Status.CurrentIndex >= len(groups) {
clusterJob.UpdateCompletedStatus()
return nil
}
var nextGroup = groups[clusterJob.Status.CurrentIndex]
// 이미 해당 그룹의 Job들이 생성되어 있는지 확인
var existingJobList batchv1.JobList
labelSelector := client.MatchingLabels{
ClusterJobNameLabel: clusterJob.Name,
ClusterJobGroupLabel: nextGroup.Name,
}
if err := r.List(ctx, &existingJobList, client.InNamespace(clusterJob.Namespace), labelSelector); err != nil {
return err
}
// 이미 잡이 생성된 노드를 판별하는 맵
jobCreatedNodes := map[string]bool{}
for _, job := range existingJobList.Items {
jobCreatedNodes[job.ObjectMeta.Labels[ClusterJobNodeLabel]] = true
}
errors := 0
for index, node := range nextGroup.Nodes {
// 특정 노드에 잡이 이미 생성되어있다면 스킵
if jobCreatedNodes[node] {
logger.Info("Job is already created.", "node", node)
continue
}
var jobSpec = clusterJob.Spec.JobTemplate.Spec
jobSpec.Template.Spec.NodeName = node
// 특정 워커노드로 배포되는 잡에 레이블 부여하기 위해 key값 추가
labelSelector[ClusterJobNodeLabel] = node
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-%s-%d-", clusterJob.Name, nextGroup.Name, index),
Namespace: clusterJob.Namespace,
Labels: labelSelector,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: clusterJob.APIVersion,
Kind: clusterJob.Kind,
Name: clusterJob.Name,
UID: clusterJob.ObjectMeta.UID,
Controller: &[]bool{true}[0],
BlockOwnerDeletion: &[]bool{true}[0],
},
},
},
Spec: jobSpec,
}
// Job 생성에 실패하면 다음 Reconcile 때 처리
if err := r.Create(ctx, job); err != nil {
errors += 1
logger.Error(err, "Job creation failed.", "namespace", clusterJob.Namespace, "name", clusterJob.Name, "jobName", job.ObjectMeta.GenerateName, "node", node)
} else {
logger.Info("Job is created.", "created", "namespace", clusterJob.Namespace, "name", clusterJob.Name, "jobName", job.ObjectMeta.GenerateName, "node", node)
}
}
// Job 생성 에러가 없는 경우에만 Running Status로 변경
if errors == 0 {
clusterJob.UpdateRunningStatus()
}
return nil
}- 현재 실행 노드그룹을 조회하기 위해 인덱스를 구한다. 여기서 현재 인덱스 번호가 그룹 배열 크기보다 크거나 같다면 더 이상 실행할 노드그룹이 없으므로 완료처리를 한다.
- 잡 스케줄링을 진행하기 전에 실행할 노드그룹에 대해 이미 잡이 생성되어있는지 확인한다. 특정 ClusterJob의 특정 실행 노드그룹에 의해 생성된 잡에는 clusterjob/name(ClusterJob이름), clusterjob/group-name(실행 그룹 이름), clusterjob/target-node(실행시킬 노드 이름) 레이블을 가지도록 구현해놓았다. (Const 참조)
- 그룹을 순회하여 각 노드에 실행시킬 잡을 생성한다. 여기서 이미 특정 노드에 잡이 생성되어있지 않은 경우에만 잡을 생성한다.
- 잡 생성 에러가 발생하는 경우에는 다음 Reconcile 에서 재시도하도록 유도할 예정이므로 에러를 무시한다.
- 그룹 내 모든 노드에 대해 잡을 생성하면 Running 단계로 ClusterJob 상태를 업데이트한다.
api/v1alpha/clusterjob_types.go
func (cj *ClusterJob) UpdateRunningStatus() {
cj.Status.Phase = "Running"
cj.Status.WaitGroups = len(cj.Status.NodeGroups) - cj.Status.CurrentIndex - 1
cj.Status.CurrentGroup = cj.Status.NodeGroups[cj.Status.CurrentIndex].Name
}
func (cj *ClusterJob) UpdateCompletedStatus() {
if cj.Status.FailedGroups > 0 {
cj.UpdateFailedStatus()
} else {
cj.Status.Phase = "Completed"
}
}
func (cj *ClusterJob) UpdateFailedStatus() {
cj.Status.Phase = "Failed"
}- UpdateRunningStatus() 에서는 남은 대기 중인 그룹 개수를 업데이트한다. 추가로 현재 실행 중인 그룹 이름을 갱신한다.
- UpdateCompletedStatus() 에서는 ClusterJob에서 실패한 그룹이 있다면 Failed로 업데이트하고 아니면 Completed로 업데이트한다. 추가로 간단히 UpdateFailedStatus() 함수도 작성하였다.
OwnerReference
잡 생성 부분을 자세히 확인해보면 ObjectMeta.OwnerReferences 부분을 주입하고 있다. OwnerReference는 특수한 메타데이터로, 해당 객체의 Owner를 기록하는 용도이며, 기본적으로 OwnerReference에 등록된 리소스의 라이프사이클을 따른다.
여기서는 Job의 OwnerReference는 ClusterJob인데, ClusterJob이 삭제되면 해당 ClusterJob으로 OwnerReference로 등록된 모든 Job들이 같이 삭제된다. Deployment를 삭제할 때 관련된 ReplicaSet과 Pod가 삭제되는 것과 같은 이치이다.
따라서 ClusterJob과 같이 존재하는 쿠버네티스 리소스를 활용한 커스텀 리소스를 만든다면 OwnerReference를 추가하여 의존 관계를 명확히 하는것이 중요하다. 만약 OwnerReference를 등록하지 않는다면 ClusterJob이 삭제되어도 관련 Job이 삭제되지 않는다.
RBAC
초기상태 구현과 비슷하게 r.ScheduleCurrentNodeGroup() 함수에서는 Job 추가 및 조회 권한이 필요하다. 마찬가지로 필요한 RBAC 리소스를 확보하기 위해 어노테이션을 추가하자.
// +kubebuilder:rbac:groups="batch",resources=jobs,verbs=get;list;watch;create
func (r *ClusterJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
...
}
Reconcile 구현 - Running
그 다음에는 실제로 실행 중일 때에 대한 Reconcile 동작을 구현해보자. Running 상태인 경우에 대한 동작은 다음과 같다.
internal/controller/clusterjob_controller.go
type GroupJobStatus struct {
TotalCount int // 현재 실행 노드 그룹의 총 잡 갯수
FailedCount int // 현재 실행 노드 그룹에서 실패한 잡 갯수
CompletedCount int // 현재 실행 노드 그룹에서 성공한 잡 갯수
}
func (r *ClusterJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
...
switch clusterJob.Status.Phase {
...
case "Running":
status, err := r.checkClusterJobGroupStatus(ctx, &clusterJob)
if err != nil {
return ctrl.Result{RequeueAfter: 5 * time.Second}, err
}
switch status.TotalCount {
case status.CompletedCount: // 현재 그룹잡 모두 성공적으로 완료
clusterJob.PrepareNextGroup()
case status.CompletedCount + status.FailedCount: // 현재 그룹잡 모두 실행은 완료
if clusterJob.Spec.FailureStrategy == "keepgoing" {
clusterJob.PrepareNextGroup()
} else {
clusterJob.UpdateFailedStatus()
logger.Info("ClusterJob failed", "namespace", clusterJob.Namespace, "name", clusterJob.Name, "group", clusterJob.Status.CurrentGroup, "failureStrategy", clusterJob.Spec.FailureStrategy)
}
default: // 그룹이 생성한 잡 중 실행완료되지 않은 잡이 있는 경우
return ctrl.Result{RequeueAfter: 5 * time.Second}, nil
}
...
}- 일단 현재 실행 그룹에서 실행 중인 Job의 상태를 확인한다. 상태 확인에 실패했다면 5초 후 조정작업을 재시도한다. 상태 확인에 성공하였다면 상태 확인을 요약한 GroupJobStatus 구조체를 얻는다.
- 만약 TotalConunt가 CompletedCount와 같다면 모든 잡이 성공적으로 완료했다는 뜻이므로 다음 실행 노드그룹으로 잡 실행을 준비하기 위해 ClusterJob의 상태를 Preparing으로 변경한다. 이는 clusterJob.PrepareNextGroup() 에서 진행한다.
- 만약 TotalConunt가 CompletedCount + FailedCount와 같다면 모든 잡이 완료했지만 실패한 잡이 있다는 뜻이다. 여기서는 FailureStrategy가 keepgoing이면 실패를 무시하고 다음 실행 노드그룹으로 잡 실행을 준비하고, 아니라면 ClusterJob을 실패처리한다.
- 위의 두가지 경우 외에는 아직 실행 중인 잡이 있다는 뜻이므로 5초 후 다시 잡 상태를 확인하도록 WorkQueue에 넣는다.
api/v1alpha/clusterjob_types.go
func (cj *ClusterJob) PrepareNextGroup() {
cj.Status.Phase = "Prepering"
cj.Status.CurrentIndex = cj.Status.CurrentIndex + 1
}- PrepareNextGroup()을 사용하여 ClusterJob의 상태를 변경하면 CurrentIndex가 1 증가하게 되고, 다음 Reconcile에서 Preparing 상태가 되기 때문에 r.scheduleCurrentNodeGroup() 을 호출하여 다음 실행 노드그룹에 잡을 배포하게 된다. 일종의 반복문 기능을 한다고 보면 된다.
잡 상태를 확인하는 세부로직은 다음과 같다.
internal/controller/clusterjob_controller.go
// ClusterJob의 현재 그룹의 잡 실행 상태를 취합하여 결과를 반환하는 함수
// GroupJobStatus는 그룹 내 잡의 총 개수, 성공 개수, 실패 개수를 포함하는 구조체
func (r *ClusterJobReconciler) checkClusterJobGroupStatus(ctx context.Context, clusterJob *clusterbatchv1alpha.ClusterJob) (GroupJobStatus, error) {
// var logger = logf.FromContext(ctx)
var jobList batchv1.JobList
labelSelector := client.MatchingLabels{
ClusterJobNameLabel: clusterJob.Name,
ClusterJobGroupLabel: clusterJob.Status.CurrentGroup,
}
// ClusterJob이 만든 Job 조회
if err := r.List(ctx, &jobList, client.InNamespace(clusterJob.Namespace), labelSelector); err != nil {
return GroupJobStatus{}, err
}
completedCount := 0
failedCount := 0
// 현재 실행 그룹에서 실행 중인 잡의 성공/실패 여부를 확인한다.
// 성공 갯수는 job.Status.Succeeded로 파악이 가능
// 실패 갯수는 job.Status.Failed이 job.Spec.BackoffLimit + 1인 경우이다.
for _, job := range jobList.Items {
backoffLimit := job.Spec.BackoffLimit
succeeded := job.Status.Succeeded
failed := job.Status.Failed
if succeeded > 0 {
completedCount += 1
}
if failed == *backoffLimit+1 {
failedCount += 1
}
}
return GroupJobStatus{TotalCount: len(jobList.Items), CompletedCount: completedCount, FailedCount: failedCount}, nil
}- 일단 ClusterJob의 현재 실행 노드그룹에 생성된 잡을 모두 조회한다.
- 조회한 잡의 상태를 확인한 후 실패한 잡, 성공한 잡의 갯수를 확인하여 GroupJobStatus로 반환한다.
- 쿠버네티스 Job 특성상, backoffLimit이라는 재시도 정책이 있기 때문에, 잡별로 실패 횟수가 하나라도 있다면 실패로 처리하는 것이 아닌 실패 횟수가 backoffLimit보다 1 더 많을 때 실패로 처리하도록 해야 한다.
Reconcile 구현 - (Completed & Failed)
마지막으로 Completed, Failed 단계에 대한 로직을 작성해보자. 사실 여기에는 따로 기능이 없어서 로그만 찍도록 하였다.
internal/controller/clusterjob_controller.go
func (r *ClusterJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
...
switch clusterJob.Status.Phase {
...
case "Completed":
logger.Info("ClusterJob completed", "namespace", clusterJob.Namespace, "name", clusterJob.Name)
return ctrl.Result{}, nil
case "Failed":
logger.Info("ClusterJob failed", "namespace", clusterJob.Namespace, "name", clusterJob.Name)
return ctrl.Result{}, nil
...
}- Completed, Failed 상태는 완료 상태이기 때문에 더 이상 Reconcile이 필요없기 때문에 별도의 작업을 하지않고 즉시 리턴한다.
- 필요하다면 웹훅이라던가 성공/실패 이벤트 핸들러 등의 기능을 추가해도 좋을 것 같다.
개발 완료 - 배포
이제 기초적인 개발은 완료하였다. 컨트롤러 기능을 업데이트하기 위해 다시 이미지를 빌드하고 푸쉬한 다음 컨트롤러를 쿠버네티스에 배포하자.
$ make docker-build docker-push IMG=beer1/cluster-job-controller:dev
$ make deploy IMG=beer1/cluster-job-controller:dev
만약 예시처럼 이미지 태깅을 하지 않았다면 docker push 까지만 하고, 컨트롤러의 Deployment 부분에서 imagePullPolicy를 Always로 변경 후 롤아웃 해야 한다.
배포 후에 다시 ClusterJob을 생성해보자.
apiVersion: cluster-batch.beer1.com/v1alpha
kind: ClusterJob
metadata:
name: clusterjob-sample
spec:
strategy:
type: all
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
containers:
- name: hello
image: busybox:latest
command: ["sh", "-c", "echo 'hello world'"]
restartPolicy: Never
그리고 각각 ClusterJob과 Job, Pod를 조회해보면 JobTemplate에서 선언한대로 잡이 생성되고 성공한 것을 확인할 수 있다.
$ kubectl get clusterjob
NAME AGE
clusterjob-sample 30s
$ kubectl get job
NAME STATUS COMPLETIONS DURATION AGE
clusterjob-sample-clusterjob-sample-all-0-sxdrt Complete 1/1 5s 2m55s
clusterjob-sample-clusterjob-sample-all-1-xzpvz Complete 1/1 5s 2m55s
clusterjob-sample-clusterjob-sample-all-2-5pzrp Complete 1/1 5s 2m55s
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
clusterjob-sample-clusterjob-sample-all-0-sxdrt-jltjl 0/1 Completed 0 2m58s 10.244.0.12 desktop-control-plane <none> <none>
clusterjob-sample-clusterjob-sample-all-1-xzpvz-pt8g6 0/1 Completed 0 2m58s 10.244.1.11 desktop-worker <none> <none>
clusterjob-sample-clusterjob-sample-all-2-5pzrp-zlpd9 0/1 Completed 0 2m58s 10.244.2.13 desktop-worker2 <none> <none>
$ kubectl get no
NAME STATUS ROLES AGE VERSION
desktop-control-plane Ready control-plane 4h51m v1.32.8
desktop-worker Ready <none> 4h51m v1.32.8
desktop-worker2 Ready <none> 4h51m v1.32.8- pod를 보면 노드당 ClusterJob에 의해 실행된 파드들이 실행되었다.
그리고 ClusterJob을 한번 삭제해보자. 그러면 관련 Job도 모두 삭제될 것이다.
$ kubectl delete clusterjob clusterjob-sample
clusterjob.cluster-batch.beer1.com "clusterjob-sample" deleted
$ kubectl get job
No resources found in cluster-job-system namespace.- Job을 생성할 때 OwnerReference를 주입했기 때문이다.
- 컨트롤러에서 Job을 생성할 때 OwnerReference를 주입하지 않는다면 어떻게 동작하는지 확인해봐도 좋을 것이다.
이 뿐만 아니라 strategy과 failureStrategy를 변경하여 여러 경우에서도 잘 돌아가는지도 확인해봐야 한다.
apiVersion: cluster-batch.beer1.com/v1alpha
kind: ClusterJob
metadata:
name: batch-type-job
spec:
strategy:
type: batch
batch:
size: 2
failureStrategy: exit
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
containers:
- name: hello
image: busybox:latest
command: ["sh", "-c", "exit 1"]
restartPolicy: Never
여기서는 strategy.type을 batch로 주었고, batch.size를 2로 주었다. 그리고 failureStrategy를 exit로 둬서 특정 실행 노드 그룹이 실패하면 실패 처리하여 더 이상 다른 실행 노드 그룹을 실행하지 않도록 하였다. 실제로 동작이 잘 이루어지는지 조회해보자.
$ kubectl get clusterjob -o custom-columns=Name:.metadata.name,Phase:.status.phase
Name Phase
batch-type-job Failed
$ kubectl get job -L clusterjob/group-name
NAME STATUS COMPLETIONS DURATION AGE GROUP-NAME
batch-type-job-batch-type-job-0-0-zwcng Failed 0/1 24s 24s batch-type-job-0
batch-type-job-batch-type-job-0-1-rmzjk Failed 0/1 24s 24s batch-type-job-0
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
batch-type-job-batch-type-job-0-0-zwcng-6pnmt 0/1 Error 0 25s 10.244.0.14 desktop-control-plane <none> <none>
batch-type-job-batch-type-job-0-0-zwcng-x2lpz 0/1 Error 0 38s 10.244.0.13 desktop-control-plane <none> <none>
batch-type-job-batch-type-job-0-1-rmzjk-9qrd9 0/1 Error 0 38s 10.244.1.12 desktop-worker <none> <none>
batch-type-job-batch-type-job-0-1-rmzjk-dd4hc 0/1 Error 0 25s 10.244.1.13 desktop-worker <none> <none>- Job에서는 exit 1을 실행하기 때문에 무조건 실패한다.
- ClusterJob의 Phase를 조회해보면 Failed로 완료되었다.
- 일단 0번째 실행 노드그룹의 Job이 생성되었다. clusterjob/group-name 레이블을 보면 알 수 있다. 하지만 모든 Job이 실패하였기 때문에 1번째 실행 노드그룹의 Job은 생성되지 않았다.
- Pod를 보면 backoffLimit에 의해 한 번 재시도 되었다.
printColumn
일단 ClusterJob은 의도적으로 잘 동작하는 것 같다. 하지만 kubectl get 출력 결과가 맘에 들지 않는다. 기본적으로는 이름과 Age만 나오기 때문이다. ClusterJob을 조회하기 위해 항상 custom-columns이나 jsonpath를 추가하는 것은 상당한 불편함이 따른다.
그래서 사용자에게 편리함을 주기 위해서는 kubectl get 출력 결과도 신경써줘야 한다. 많은 쿠버네티스 환경에서 사용하는 오픈소스 (ArgoCD, Istio, Prometheus Operator 등)에서 제공해주는 커스텀 리소스를 조회해보면 커스텀 리소스마다 출력 결과가 다르다. 이처럼 커스텀 리소스의 기능 및 특성에 맞게 kubectl get 출력 결과를 꾸며주는 것도 중요하다.
kubebuilder에서는 +kubebuilder:printColumn 이라는 어노테이션으로 커스텀 리소스의 kubectl get 출력 결과를 설정할 수 있다. 일단 api/v1alpha/clusterjob_types.go 파일의 ClusterJob 구조체 선언 부분에 다음을 추가해보자.
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="Phase of ClsterJob"
// +kubebuilder:printcolumn:name="CurrentGroup",type="string",JSONPath=".status.currentGroup",description="The group in which the ClusterJob is currently running"
// ClusterJob is the Schema for the clusterjobs API
type ClusterJob struct {
...
}- printcolumn의 구조는 컬럼명(name), 컬럼타입(type), 출력 필드(JSONPath), 설명(description) 으로 구성되어 있다.
- 여기서는 Phase와 현재 실행 중인 그룹이름을 출력하도록 구성하였다.
kubectl get 출력 결과는 CRD에서 제공하는 기능이기 때문에 해당 어노테이션을 추가한다면 다음 명령어로 CRD를 업데이트 해줘야 한다. 따로 컨트롤러는 배포하지 않아도 된다.
$ make manifest
$ make install
CRD 배포 후에 ClusterJob을 조회하면 의도한 대로 잘 나오는 것을 확인할 수 있다.
$ kubectl get clusterjob
NAME PHASE CURRENTGROUP
batch-type-job Failed batch-type-job-0
clusterjob-sample Running clusterjob-sample-all
마무리
이번 시간에는 kubebuilder를 사용하여 ClusterJob Custom controller의 기능을 모두 개발하고, 동작 확인 및 printColumn으로 kubectl get 출력 결과를 설정하는 방법을 알아보았다. 시간이 있다면 이를 자동화하고, Helm chart로 패키징 하는 것 까지 다뤄보면 좋을 것 같다.
ClusterJob에 대한 코드는 해당 레포에서 관리하고 있다.
https://github.com/beer-one/cluster-job/
GitHub - beer-one/cluster-job: cluster-job CRD
cluster-job CRD. Contribute to beer-one/cluster-job development by creating an account on GitHub.
github.com
'DevOps > Kubernetes' 카테고리의 다른 글
| kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [2] (0) | 2025.10.25 |
|---|---|
| kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [1] (0) | 2025.10.25 |
| Kubernetes Custom Resource와 Custom Controller 소개 (0) | 2025.10.13 |
| Kubernetes Node ContainerGCFailed 트러블슈팅 (0) | 2025.09.30 |
| CoreDNS DNS Resolve timedout과 ndots (0) | 2025.09.27 |
댓글