이번 글에서는 kubebuilder를 사용하여 Custom Resource, Custom Controller를 직접 만들어보는 과정을 정리해보았다.
주제 정하기
Custom Resource, Custom Controller 만들기 전에 가장 먼저 해야 할 것은 어떤 것을 만들어볼지 결정하는 것이다.
일단 쿠버네티스를 운영하면서 느꼈던 불편한 점은, 쿠버네티스 클러스터의 각 노드마다 어떤 작업을 해줘야 할 때가 있다. 워커노드에 저장된 시스템 로그를 백업한다거나, 아니면 각 워커노드에 특정 설정파일을 갱신한다거나 등이 있다. 여기서 각 노드마다 어떤 작업을 해줄 적절한 워크로드가 있으면 좋을 것 같다는 생각을 해봤는데, DaemonSet은 일회성 작업을 하기에 적당하지 않은 느낌이었다.
먼저 DaemonSet은 클러스터 내 각 워커노드마다 동일한 파드를 항상 유지하는 워크로드이지만, 파드가 항상 유지되기 때문에 일회성 작업으로는 적절하지 않는다. 약간의 활용으로는 Job과 DaemonSet을 적절히 사용하면 각 워커노드마다 동일한 작업을 수행할 수는 있다.

- 먼저 DaemonSet을 실행시킬 Job을 하나 생성한다. Job에서 실행하는 컨테이너에서 DaemonSet을 실행시키고, 일정시간 대기 후 DaemonSet을 종료시키는 코드를 구현해야 하며, DaemonSet의 매니페스트를 ConfigMap 등으로 관리해야 한다.
- Pod가 구동되면 해당 Pod는 DaemonSet을 생성한다. 여기서 파드에서 DaemonSet을 생성/삭제시킬 수 있는 권한을 줘야 한다. (RBAC)
- DaemonSet이 생성되면 각 워커노드마다 한개씩의 파드가 생성된다. 구동되는 파드에서는 일회성 작업을 하고 대기해야 한다.
- Job이 구동시키는 Pod에서는 일정시간 대기 후 DaemonSet을 종료시키고 작업을 종료한다.
이렇게 하면 하지만 쿠버네티스 클러스터의 각 노드마다 일회성 작업을 하는 요구사항은 어느정도 맞춰진다. 하지만 여기서 몇 가지 불편한 점이 있다.
- DaemonSet에 의해 실행되는 파드의 작업 성공/실패 여부를 정확히 알기 어렵다. 성공/실패 여부를 알기 위해서는 Job이 실행하는 파드로 결과를 전달해야 하는 어떤 통신방법이 필요하다.
- DaemonSet에 의해 구동된 파드가 작업을 완료한 후 대기하는 시간이 Job에 의해 구동된 파드가 DaemonSet을 종료시키기 전까지 대기하는 시간보다 더 길어야 한다. 이 시간이 잘 맞춰지지 않으면 DaemonSet에 의해 구동된 파드가 작업이 완료되기 전에 파드를 종료시켜 버릴 수 있다.
- 확장성에 문제가 있다. 같은 방식을 요구하는 여러개의 잡이 있을 수 있는데, 그 잡마다 Job, ConfigMap, DaemonSet, RBAC 등을 일일히 만들어야 한다. (Helm을 이용하면 어느정도 해결이 되긴 한다.)
- 재시도 매커니즘을 직접 구현해야 한다. 쿠버네티스 Job이 가지는 장점을 써먹기 힘들다.
위의 단점을 해결하고자 쿠버네티스 클러스터의 각 노드마다 일회성 작업을 실행 및 관리하는 커스텀 리소스를 한번 구현해보자.
ClusterJob
ClusterJob은 쿠버네티스 클러스터의 각 노드마다 일회성 작업을 하는 워크로드로 정의하겠다. ClusterJob의 세부 기능을 먼저 정의해보자.
- 동시 실행 전략을 선택할 수 있다. 전체를 동시에 실행할 수도 있고, 그룹별로 동시에 실행할 수도 있다.
- all: 클러스터 내 모든 워커노드에 대해 동시에 실행하는 전략
- batch: 클러스터 내 워커노드를 지정된 크기로 그루핑하여 그룹별로 동시에 실행하는 전략
- perNodeGroup: 노드의 특정 레이블을 기준으로 그루핑하여 그룹별로 동시에 실행하는 전략
- 실패 전략을 선택할 수 있다. 현재 실행 중인 그룹의 잡이 실패하는 경우 다음 그룹으로 잡을 실행시킬지 여부를 결정할 수 있다.
- keepgoing: 현재 실행 중인 그룹의 잡이 실패하여도 다음 그룹으로 잡을 그대로 실행한다.
- exit: 현재 실행 중인 그룹의 잡이 실패하면 다음 그룹부터 잡을 실행하지 않고 ClusterJob을 실패처리 한다.
일단 간단히 두 가지 세부 기능만 주어진다고 하자. 이후에 Spec과 Status의 뼈대를 만들어보자.
Spec
ClusterJob의 Spec의 대략적인 구조를 한번 설계해보자. Spec은 리소스의 원하는 상태를 기술하면 된다. 최종적으로 원하는 상태는 각 워커노드마다 일회성 작업을 마치는 것이다. 일단 Spec 자체는 세부 기능이 나왔으니 어렵지 않게 기술이 된다.
spec:
strategy: # 동시 실행 전략
type: all # all | batch | perNodeGroup
batch:
size: 3 # 배치 크기 지정
order: random # random(무작위) | alphabetical(알파벳순)
perNodeGroup:
groupLabel: batch-group # 그루핑에 사용되는 노드의 레이블 key, value값이 같은 노드끼리 그루핑된다.
ignoreNull: true # groupLabel이 지정되지 않은 노드에 대한 처리, true인 경우 무시, false인 경우 별도의 그룹 생성
failureStrategy: keepgoing # keepgoing (계속 진행) | exit (종료 처리)
jobTemplate: {} # Job 템플릿, CronJob처럼 Job을 기술하면 된다.
| field | type | description |
| strategy | object | 동시 실행 전략 |
| strategy.type | string(enum) | all(전체), batch(동일 크기 단위로 나눠 실행), perNodeGroup(노드그룹 별로 나눠 실행) 중 하나 |
| strategy.batch | object | strategy.type = batch인 경우 필수, batch 타입에 대한 세부 설정 |
| strategy.batch.size | integer | 동시 실행 그룹 크기 |
| strategy.batch.order | string(enum) | 동시 실행 그루핑 방법, random(무작위), alphabetical(사전순) 중 하나 |
| strategy.perNodeGroup | object | strategy.type = perNodeGroup인 경우 필수, perNodeGroup 타입에 대한 세부 설정 |
| strategy.perNodeGroup.groupLabel | string | 그루핑에 사용되는 노드의 레이블 key, value값이 같은 노드끼리 그루핑된다. |
| strategy.perNodeGroup.ignoreNull | boolean | sgroupLabel이 지정되지 않은 노드에 대한 처리, true인 경우 무시, false인 경우 별도의 그룹 생성 |
| failureStrategy | string(enum) | 실패 전략, keepgoing(계속 진행), exit (종료 처리) 중 하나 |
| jobTemplate | jobTemplateSpec | Kubernetes JobTemplate 스펙, Job 기술 부분 |
Status
그 다음에는 Status를 정의해보자. Status는 원하는 상태로 도달할 때 까지 지닐 수 있는 상태로 생각하면 된다. 최종적으로 원하는 상태는 각 워커노드마다 일회성 작업을 마치는 것인데, Status를 정의할 때는 최종적으로 원하는 상태로 도달할 때 까지의 과정을 그려보면 된다.
아래 그림은 ClusterJob이 구동되는 과정을 도식화한 그림이다.

- ClusterJob이 생성되면 strategy에 따라 동시실행 그룹을 생성한다. 동시실행 그룹이 생성되면 ClusterJob이 구동할 준비가 완료되었다고 보면 된다.
- 생성된 동시실행 그룹별로 순회를 하여 그룹단위로 Job을 생성한다. Job을 생성할 때는 그룹에 속한 워커노드당 하나씩 Job을 생성하며, NodeAffinity를 통해 해당 워커노드에 파드가 스케줄링 될 수 있도록 제어하는 것이 필요하다.
- 그룹별 Job 생성이 완료되면 그룹별 Job이 성공 또는 실패가 될 때까지 Job의 상태를 주기적으로 체크한다.
- 모든 Job이 종료되었다면 성공/실패 여부를 집계한 후에 failureStrategy에 따라 다음 그룹으로 잡 실행을 계속 진행할지 끝낼지 결정한다. 하나라도 실패한 Job이 있으면서 failureStrategy가 exit라면 즉시 ClusterJob을 종료하고, 실패 처리를 한다.
- 그룹별로 2~4를 반복한 후 모든 그룹이 성공이라면 ClusterJob을 종료하고 ClusterJob은 성공으로 처리한다.
ClusterJob의 구동 과정을 모두 그려보았다면 여기서 ClusterJob을 구동시키기 위해 필요한 상태값을 추출해야 한다.
- 먼저 동시실행 그룹을 저장할 필요가 있다. 동시 실행 그룹에는 그룹명과 워커노드 리스트를 저장하는 배열형 객체가 적당해보인다.
- 그리고 현재 실행 중인 그룹에 대한 정보를 저장할 필요가 있다. 그룹 순회를 위해 필요하다.
- 추가로 상태 확인용으로 성공한 그룹, 실패한 그룹, 대기 중인 그룹의 갯수를 Status에 저장하면 ClusterJob을 운영할 때 좋을 것 같다. 성공한 그룹, 실패한 그룹은 ClusterJob의 성공/실패 처리에도 사용이 가능하다.
- 마지막으로 ClusterJob의 단계(Phase)를 저장하면 현재 어떤 과정에 있는지 대략적으로 알 수 있을 것 같다. Phase는 총 4가지가 있다.
- Preparing: 동시실행 그룹이 생성 단계, 아무런 Job이 구동되지 않음.
- Running: 특정 그룹에 Job을 생성하여 실행 중인 단계
- Failed: ClusterJob이 도중 중단되었거나 완료되었으며, 하나 이상 실패한 경우
- Completed: ClusterJob이 완료되었으며, 모든 그룹이 성공한 경우
추출한 상태값을 yaml로 표현하면 다음과 같다.
status:
nodeGroups:
- name: clusterjob-batch-0 # 동시 실행 그룹명
nodes: # 동시 실행 그룹에 속한 노드 이름 항목
- desktop-control-plane
- desktop-worker
- name: clusterjob-batch-1
nodes:
- desktop-worker2
currentGroup: clusterjob-batch-1 # 현재 실행 중인 그룹명
currentIndex: 1 # 현재 실행 중인 그룹 인덱스
completedGroups: 2 # 성공한 그룹 갯수
failedGroups: 0 # 실패한 그룹 갯수
waitGroups: 0 # 대기 중인 그룹 갯수
phase: Completed # ClusterJob의 현재 단계
| field | type | description |
| nodeGroups | list | 동시실행 그룹 항목 |
| nodeGroups[].name | string | 동시실행 그룹 이름 |
| nodeGroups[].nodes | list(string) | 그룹에 속한 노드 이름 |
| currentGroup | string | 현재 실행 중인 그룹 이름 |
| currentIndex | integer | 현재 실행 중인 그룹의 인덱스 |
| completedGroups | integer | 성공한 그룹 수 |
| failedGroups | integer | 실패한 그룹 수 |
| waitGroups | integer | 대기 중인 그룹 수 |
| phase | string(enum) | ClusterJob의 현재 단계, Pending, Running, Failed, Completed 중 하나 |
kubebuilder로 프로젝트 구성
이제 kubebuilder로 ClusterJob을 개발할 프로젝트 환경을 구성해보자. 앞서 말했듯이, kubebuilder는 Custom Resource, Custom Controller의 개발을 돕기 위한 프레임워크이며, kubebuilder는 다음의 기능을 제공한다.
- 구성파일, CRD, 컨테이너 배포와 관련된 Dockerfile, Makefuile 등을 포함하여 프로젝트 구조를 제공한다.
- CRD 및 Controller 및 Custom Resource 구조체에 대한 코드를 자동으로 생성하며, Custom Resource 구조체를 통해 CRD를 자동으로 생성한다.
- kubernetes용 envtest와 같은 테스트 도구와의 통합을 지원한다.
Prerequisites
먼저 kubebuilder를 설치해야 한다. 설치 방법은 다음과 같다.
$ curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
$ chmod +x kubebuilder && sudo mv kubebuilder /usr/local/bin/
- kubebuilder는 Go 언어 기반이기 떄문에 Go가 설치되어 있어야 한다.
- 빌드 및 배포를 위해서 추가로 docker, kubectl이 설치되어있어야 한다.
프로젝트 구성
kubebuilder를 설치했다면 명령어를 사용하여 프로젝트를 구성할 수 있다. 다음 명령어를 입력하여 워크스페이스를 생성하자.
$ kubebuilder init --domain <<원하는 도메인>> --repo <<프로젝트를 관리한 깃 주소>>
# (예시)
$ kubebuilder init --domain beer1.com --repo github.com/beer-one/cluster-job
프로젝트 생성 후에는 Custom Controller에서 다룰 Custom API를 하나 생성해야 한다. 이것 역시 명령어로 간단히 생성할 수 있다.
$ kubebuilder create api --group <<API 그룹명>> --version <<API 버전 이름>> --kind <<API 이름>>
# (예시)
$ kubebuilder create api --group cluster-batch --version v1alpha --kind ClusterJob
- 대화상자에서 Create Resource, Create Controller 가 뜨는데, 리소스와 컨트롤러 코드를 자동으로 생성할 것인지 묻는 것인데 y를 입력하도록 하자.
프로젝트와 API를 생성했다면 기본적으로 다음과 같은 구조로 파일이 생성될 것이다.
├── api
│ └── v1alpha
│ ├── clusterjob_types.go
│ ├── groupversion_info.go
│ └── zz_generated.deepcopy.go
├── bin
│ ├── controller-gen
│ └── controller-gen-v0.19.0
├── cmd
│ └── main.go
├── config
│ ├── crd
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfig.yaml
│ ├── default
│ │ ├── cert_metrics_manager_patch.yaml
│ │ ├── kustomization.yaml
│ │ ├── manager_metrics_patch.yaml
│ │ └── metrics_service.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── network-policy
│ │ ├── allow-metrics-traffic.yaml
│ │ └── kustomization.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ ├── monitor_tls_patch.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── clusterjob_admin_role.yaml
│ │ ├── clusterjob_editor_role.yaml
│ │ ├── clusterjob_viewer_role.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── metrics_auth_role_binding.yaml
│ │ ├── metrics_auth_role.yaml
│ │ ├── metrics_reader_role.yaml
│ │ ├── role_binding.yaml
│ │ ├── role.yaml
│ │ └── service_account.yaml
│ └── samples
│ ├── cluster-batch_v1alpha_clusterjob.yaml
│ └── kustomization.yaml
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── internal
│ └── controller
│ ├── clusterjob_controller_test.go
│ ├── clusterjob_controller.go
│ └── suite_test.go
├── Makefile
├── PROJECT
├── README.md
└── test
├── e2e
│ ├── e2e_suite_test.go
│ └── e2e_test.go
└── utils
└── utils.go
- api: Custom Resource의 정의 부분이다. clusterjob_types.go 파일에서 Custom Resource의 구조체를 정의할 수 있으며, kubebuilder는 이 파일을 바탕으로 CRD를 자동 생성한다.
- bin: kubebuilder에서 사용되는 도구인데 여기에는 controller-gen이 있다. controller-gen은 Go 코드에 작성된 어노테이션을 읽어서 CRD, RBAC, deepcopy 등을 자동으로 생성해주는 유틸리티이다. 일단은 넘어가도록 하자.
- cmd: 컨트롤러의 시작지점이자 main 함수가 들어있는 main.go 파일이 들어있다.
- config: 쿠버네티스 매니페스트가 정의되는 지점이다. 여기서는 CRD, RBAC, Manager(Controller), 샘플파일, kustomize 기반의 기본 배포 파일들이 포함된다. kubebuilder는 config 하위에 쿠버네티스 매니페스트를 자동으로 생성해주는 역할도 한다.
- Dockerfile: Controller 이미지를 생성하기 위해 사용되는 도커파일
- internal: 실제 컨트롤러 구현부분이다. 컨트롤러를 구현하기 위해서 internal/controller/API_KIND_controller.go 파일에다가 로직을 구현하면 된다.
- PROJECT: kubebuilder 메타데이터 파일이다. API 그룹, 버전, 도메인, 리소스타입 정보 등이 기록된다.
- Makefile: Kubebuilder가 자동 생성하는 유틸리티 스크립트 모음이다.
위의 명령어를 모두 입력하면 Custom Resource, Custom Controller의 뻐대가 자동으로 만들어진다. 생성된 파일 중 clusterjob_types.go 파일에서 Custom Resource의 구조를 구성하면 되고, clusterjob_controller.go 파일에서 Custom Controller를 구현하면 된다.
Custom Resource 구성 (Spec)
먼저 Custom Resource의 구조부터 한번 잡아보자. 우리가 위에서 대략적으로 spec과 status를 이미 설계하였기 때문에 필드 구조를 참고하여 clusterjob_types.go을 수정하면 된다.
먼저 Spec 부분을 구성해보자. clusterjob_types.go 파일을 보면 이미 ClusterJobSpec 구조체가 생성되어 있음을 확인할 수 있다. 해당 구조체의 필드값을 수정하여 원하는 구조를 만들면 된다.
| field | type | description |
| strategy | object | 동시 실행 전략 |
| strategy.type | string(enum) | all(전체), batch(동일 크기 단위로 나눠 실행), perNodeGroup(노드그룹 별로 나눠 실행) 중 하나 |
| strategy.batch | object | strategy.type = batch인 경우 필수, batch 타입에 대한 세부 설정 |
| strategy.batch.size | integer | 동시 실행 그룹 크기 |
| strategy.batch.order | string(enum) | 동시 실행 그루핑 방법, random(무작위), alphabetical(사전순) 중 하나 |
| strategy.perNodeGroup | object | strategy.type = perNodeGroup인 경우 필수, perNodeGroup 타입에 대한 세부 설정 |
| strategy.perNodeGroup.groupLabel | string | 그루핑에 사용되는 노드의 레이블 key, value값이 같은 노드끼리 그루핑된다. |
| strategy.perNodeGroup.ignoreNull | boolean | sgroupLabel이 지정되지 않은 노드에 대한 처리, true인 경우 무시, false인 경우 별도의 그룹 생성 |
| failureStrategy | string(enum) | 실패 전략, keepgoing(계속 진행), exit (종료 처리) 중 하나 |
| jobTemplate | jobTemplateSpec | Kubernetes JobTemplate 스펙, Job 기술 부분 |
import (
batchv1 "k8s.io/api/batch/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ClusterJobSpec defines the desired state of ClusterJob
type ClusterJobSpec struct {
// Strategy defines how the job should be executed
Strategy *ClusterJobStrategy `json:"strategy"`
// FailureStrategy defines how to handle failures (keepgoing or exit)
// +kubebuilder:validation:Enum=keepgoing;exit
// +kubebuilder:default:=keepgoing
FailureStrategy string `json:"failureStrategy,omitempty"`
// +required
JobTemplate *batchv1.JobTemplateSpec `json:"jobTemplate"`
}
// ClusterJobStrategy holds job execution strategy details
type ClusterJobStrategy struct {
// Type specifies which strategy is used: batch, perNodeGroup or all
// +kubebuilder:validation:Enum=batch;perNodeGroup;all
Type string `json:"type"`
// Batch execution strategy (valid only when Type=batch)
// +optional
Batch *BatchStrategy `json:"batch,omitempty"`
// PerNodeGroup execution strategy (valid only when Type=perNodeGroup)
// +optional
PerNodeGroup *PerNodeGroupStrategy `json:"perNodeGroup,omitempty"`
}
// BatchStrategy defines fields for parallel execution
type BatchStrategy struct {
// Order defines grouping order (random or alphabetical)
// +kubebuilder:validation:Enum=random;alphabetical
// +kubebuilder:default:=random
Order string `json:"order,omitempty"`
// Size defines group size for parallel execution
// +kubebuilder:validation:Minimum=1
// +kubebuilder:default:=1
Size int32 `json:"size,omitempty"`
}
// PerNodeGroupStrategy defines fields for per-node-group execution
type PerNodeGroupStrategy struct {
// GroupLabel is the node label key to use for grouping
// +required
GroupLabel string `json:"groupLabel"`
// IgnoreNull specifies whether to ignore nodes without the groupLabel (true),
// or treat them as a separate group (false)
IgnoreNull bool `json:"ignoreNull,omitempty"`
}
- Object 타입의 필드를 구성할 때는 새로운 구조체를 추가하면 된다. (ClusterJobStrategy 등)
- CronJob의 spec인 JobTemplate과 같이 쿠버네티스 기본 오브젝트의 객체 타입을 가져오고 싶은 경우에는 import를 해서 가져오면 된다. (metav1 import 참조)
CRD 배포
ClusterJobSpec을 위와 같이 구성한 다음, 아래 명령어를 사용하면 구성한 구조체와 같은 구조로 이루어진 CRD 파일이 생성된다.
$ make manifests
생성된 CRD 파일 경로는 config/crd/bases/cluster-batch.beer1.com_clusterjobs.yaml 이며, CRD를 클러스터에 반영하는 명령어는 다음과 같다.
$ make install
그런데 간혹 may not be more than 262144 bytes 와 같은 에러가 발생할 수도 있다. kubebuilder는 controller-gen이라는 프로그램을 사용하여 clusterjob_types.go 파일의 구조체를 CRD로 변경하는데, 변경 과정에서 주석도 참고하여 필드에 대한 설명문도 CRD에 추가하게 된다. 이 과정에서 CRD 크기가 262144 바이트를 넘어가게 되는 경우가 발생할 수 있다.
이를 해결하기 위해서는 Makefile을 다음과 같이 수정한 다음, make manifest와 make install을 다시 해주면 된다.
.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
$(CONTROLLER_GEN) rbac:roleName=manager-role crd:maxDescLen=0 webhook paths="./..." output:crd:artifacts:config=config/crd/bases
- manidfests 부분에서 기존 crd 부분을 crd:maxDescLen=0 으로 변경하면 된다.
이렇게 하면 쿠버네티스 클러스터에 CRD가 설치될 것이다. CRD가 설치되었는지 확인하려면 다음 명령어를 사용하면 된다.
$ kubectl get crd
NAME CREATED AT
clusterjobs.cluster-batch.beer1.com 2025-10-24T14:33:59Z
구조체 파일 특수 주석
Spec 부분에서 몇가지 살펴봐야 할 것이 있다. +로 되어있는 주석 부분인데, 해당 주석은 특수한 역할을 한다.
+kubebuilder:validation
+kubebuilder:validation 주석은 CRD에 특정 필드에 대한 값 검증로직을 주입하기 위해 사용한다. ClusterJobStrategy에서 type 필드를 보면 다음과 같이 주석처리가 되어있다.
type ClusterJobStrategy struct {
// Type specifies which strategy is used: batch, perNodeGroup or all
// +kubebuilder:validation:Enum=batch;perNodeGroup;all
Type string `json:"type"`
...
}
해당 주석의 의미는 type 필드는 String이지만 Enum 타입이며, batch, perNodeGroup, all 중 하나만 올 수 있도록 검증로직이 구성된다. 실제로 cluster-batch.beer1.com_clusterjobs.yaml 파일을 보면 다음과 같이 구성되어 있을 것이다.
strategy:
properties:
batch:
...
perNodeGroup:
...
type:
enum:
- batch
- perNodeGroup
- all
type: string
required:
- type
type: object
+kubebuilder:default
+kubebuilder:default는 필드에 값을 지정하지 않은 경우 기본값을 지정해주는 옵션이다.
// ClusterJobSpec defines the desired state of ClusterJob
type ClusterJobSpec struct {
// Strategy defines how the job should be executed
Strategy *ClusterJobStrategy `json:"strategy"`
// FailureStrategy defines how to handle failures (keepgoing or exit)
// +kubebuilder:validation:Enum=keepgoing;exit
// +kubebuilder:default:=keepgoing
FailureStrategy string `json:"failureStrategy,omitempty"`
// +required
JobTemplate *batchv1.JobTemplateSpec `json:"jobTemplate"`
}
여기서 failureStrategy 값을 지정하지 않으면 기본값으로 keepgoing이 된다.
+optional, +required
+optional 주석은 해당 필드는 선택값임을 알려주는 주석이다. 반대로 +required는 필수값임을 알려주는 주석이다.
그 외의 여러가지 기능을 하는 주석은 공식문서를 통해 찾아서 사용하면 된다.
https://book.kubebuilder.io/reference/markers/crd-validation#crd-validation
CRD Validation - The Kubebuilder Book
These markers modify how the CRD validation schema is produced for the types and fields they modify. Each corresponds roughly to an OpenAPI/JSON schema option. See Generating CRDs for examples. Certain markers may seem duplicated. However, these markers ar
book.kubebuilder.io
Custom Resource 구성 (Status)
Spec과 마찬가지로 Status도 구성해보자.
| field | type | description |
| nodeGroups | list | 동시실행 그룹 항목 |
| nodeGroups[].name | string | 동시실행 그룹 이름 |
| nodeGroups[].nodes | list(string) | 그룹에 속한 노드 이름 |
| currentGroup | string | 현재 실행 중인 그룹 이름 |
| currentIndex | integer | 현재 실행 중인 그룹의 인덱스 |
| completedGroups | integer | 성공한 그룹 수 |
| failedGroups | integer | 실패한 그룹 수 |
| waitGroups | integer | 대기 중인 그룹 수 |
| phase | string(enum) | ClusterJob의 현재 단계, Preparing, Running, Failed, Completed 중 하나 |
// ClusterJobStatus defines the observed state of ClusterJob.
type ClusterJobStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// For Kubernetes API conventions, see:
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
// conditions represent the current state of the ClusterJob resource.
// Each condition has a unique type and reflects the status of a specific aspect of the resource.
//
// Standard condition types include:
// - "Available": the resource is fully functional
// - "Progressing": the resource is being created or updated
// - "Degraded": the resource failed to reach or maintain its desired state
//
// The status of each condition is one of True, False, or Unknown.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// NodeGroups stores execution unit groups and their member nodes
NodeGroups []NodeGroupStatus `json:"nodeGroups,omitempty"`
// This status is phase of ClustertJob, phase is one of preparing, running, completed, or failed.
// +kubebuilder:validation:Enum=Preparing;Running;Completed;Failed
Phase string `json:"phase,omitempty"`
CurrentGroup string `json:"currentGroup,omitempty"`
CurrentIndex int `json:"currentIndex"`
CompletedGroups int `json:"completedGroups"`
FailedGroups int `json:"failedGroups"`
WaitGroups int `json:"waitGroups"`
}
// NodeGroupStatus represents a single execution unit group in status
type NodeGroupStatus struct {
Name string `json:"name"` // Nodegroup name
Nodes []string `json:"nodes"` // Nodes belonging to that group
}
- Conditions는 모든 쿠버네티스 오브젝트에 있는 필드로 쿠버네티스 API Convention이므로 유지해두자.
status까지 구성한 후에 make manifest & make install을 한번 더 실행하여 클러스터에 Status 변경사항까지 반영해두자.
Custom Resource 반영
CRD가 생성되면 Custom Resource를 쿠버네티스 클러스터에 반영할 수 있게 된다. 테스트용 Custom Resource를 하나 만들어보자.
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 반영 후에 kubectl로 조회가 되는 것을 확인할 수 있다.
$ kubectl get clusterjob
NAME AGE
clusterjob-sample 99s
make generate
마지막으로, 커스텀 리소스 구조체의 필드가 변경될 때마다 해줘야 하는 것이 있는데 바로 make generate 명령어이다. 해당 명령어는 커스텀 리소스 구조체의 DeepCopy 함수를 자동으로 생성해주는 도구이다. 예시로는 api/v1alpha/zz_generated.deepcopy.go 파일이 있는데, make generate 명령어를 호출하면 여기에 ClusterJob과 관련된 구조체들의 DeepCopy 함수 구현코드가 갱신된다.
$ make generate
마무리
ClusterJob이 생성되었지만 이것은 지금은 그냥 데이터에 불과하다. ClusterJob에 대한 컨트롤러가 없기 때문에 우리가 원하는 워커노드별로 잡을 실행시키는 것은 아직 불가능하다.
다음 시간에는 컨트롤러를 구현하여 워커노드별로 잡을 실행시키는 과정을 정리해볼 예정이다.
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
2025.10.26 - [DevOps/Kubernetes] - kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [3]
kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [3]
이전 시간에는 kubebuilder를 사용하여 ClusterJob이라는 Custom Controller의 일부를 구현하였고, 실제로 쿠버네티스에 배포하여 동작을 확인하였다. 이번 시간에는 ClusterJob Custom Controller의 나머지 기능들
beer1.tistory.com
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 만들어보기 [3] (0) | 2025.10.26 |
|---|---|
| kubebuilder를 사용하여 Custom Resource, Custom Controller 만들어보기 [2] (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 |
댓글