Search

kubebuilder 찍먹

컨트롤러 제작 도구

CRD로써 쿠버네티스 API를 만들기 위한 도구가 몇가지 있다. 크게 kubebuilder, controller-runtime 그리고 operator-sdk 세 가지가 있다.
kubebuilder는 프레임워크다. 빠르게 CRD를 만들고 CR을 관리할(reconcile) 컨트롤러(manager)를 개발할 수 있도록 부트스트래핑 해준다.
controller-runtime은 라이브러리로써 kubebuilder에서도 사용한다.
operator-sdk는 말그대로 sdk인데, kubebuilder보다 나온지 오래됐고 프로젝트를 관리하는 그룹이 SIG가 아니다. 그리고 역시 controller-runtime을 라이브러리로써 사용한다.
과거에 operator-sdk를 한번 찍먹했을 땐 그다지 재밌지(?) 않았어서… 이번엔 kubebuilder를 한번 시도해본다.

준비와 설치

문서의 quick start를 참고해서 준비하고 찍먹을 해본다.
이런 것들이 필요하다:
go version v1.20.0+
docker version 17.03+.
kubectl version v1.11.3+.
그리고 kubebuilder CLI를 설치한다:
# download kubebuilder and install locally. curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)" # 나의 경우 /usr/local/bin이 root의 것이다 chmod +x kubebuilder && sudo mv kubebuilder /usr/local/bin/
Shell
복사
kind를 설치하고 로컬 클러스터를 설치한다. kubebuilder로 만든 커스텀 컨트롤러를 배포하고 테스트할 용도의 로컬 클러스터로 사용할 것이다:
brew install kind kind create cluster
Shell
복사

프로젝트

튜토리얼용 프로젝트로써 Building 이라는 CRD를 관리하는 컨트롤러를 만들어 볼 것이다. Building 객체는 spec.address 를 선언하면 status.zipcode 를 가지게 된다. 물론 zipcode는 가짜이다 ㅎ; kubebuilder를 살펴보는 것을 목적으로 간단하게 만들었다.
디렉토리를 만들고 kubebuilder 프로젝트를 초기화한다:
mkdir -p kubebuilder/building cd kubebuilder/building kubebuilder init --domain my.domain --repo my.domain/building
Shell
복사
5d02316e5f5d701f003c9678722df75b15812138
commit
tree . -L 2 ├── Dockerfile ├── Makefile ├── PROJECT ├── README.md ├── cmd │ └── main.go ├── config │ ├── default │ ├── manager │ ├── prometheus │ └── rbac ├── go.mod ├── go.sum ├── hack │ └── boilerplate.go.txt └── test ├── e2e └── utils
Shell
복사
위와 같은 디렉토리 구조와 파일이 만들어졌다. cmd/main.go가 컨트롤러를 실행하는 메인 프로세스이고, config 하위는 필요한 쿠버네티스 매니페스트 관련한 kustomize 파일이다.
이쯤에서 kubebuilder 프로젝트의 아키텍처를 살펴보면, main.go로 실행하는 프로세스 밑에 매니저가 있고(그래서 앞에서 컨트롤러와 매니저가 혼용하여 표현했다) 그 아래 컴포넌트들이 있다.
이 튜토리얼에서는 컨트롤러와 리컨실러 정도를 다룬다.
Building API를 생성한다:
kubebuilder create api --group webapp --version v1 --kind Building INFO Create Resource [y/n] y INFO Create Controller [y/n] y ...
Shell
복사
v1/webapp.my.domainBuilding CRD를 만들기 위한 파일들과 기본적인 컨트롤러 코드가 생성된다.
git stataus -s M PROJECT A api/v1/building_types.go A api/v1/groupversion_info.go A api/v1/zz_generated.deepcopy.go M cmd/main.go A config/crd/kustomization.yaml A config/crd/kustomizeconfig.yaml M config/default/kustomization.yaml A config/rbac/building_editor_role.yaml A config/rbac/building_viewer_role.yaml A config/samples/kustomization.yaml A config/samples/webapp_v1_building.yaml A internal/controller/building_controller.go A internal/controller/building_controller_test.go A internal/controller/suite_test.go
Shell
복사
spec/status를 정의하기 위해 api/v1/building_types.go를 수정한다:
type BuildingSpec struct { // ... Address string `json:"address,omitempty"` } type BuildingStatus struct { // ... ZipCode string `json:"zipCode,omitempty"` }
Go
복사
internal/controller/building_controller.go 에선 랜덤 5자리 숫자 zipCode 를 만들어 준다:
func (r *BuildingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) // TODO(user): your logic here // Fetch the Building instance building := &webappv1.Building{} err := r.Get(ctx, req.NamespacedName, building) if err != nil { if client.IgnoreNotFound(err) == nil { return ctrl.Result{}, nil } return ctrl.Result{}, err } // Gen a random 5 digit zip code from the address charset := "0123456789" result := make([]byte, 5) for i := range result { result[i] = charset[rand.Intn(len(charset))] } zipCode := string(result) // Update the status if the zip code is not set if building.Status.ZipCode == "" { building.Status.ZipCode = zipCode err = r.Status().Update(ctx, building) if err != nil { return ctrl.Result{}, err } } return ctrl.Result{}, nil }
Go
복사
객체를 가져오고 갱신하기 위해 리컨실러(r )의 API를 사용한다. Reconcile 메소드는 controller-runtime(ctrl ) 요청, 응답을 처리함을 알 수 있다.
이제 CRD 매니페스트를 생성한다:
make manifests
Shell
복사
api/v1/building_types.go를 바탕으로 CRD 매니페스트가 생성됐다:
--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.14.0 name: buildings.webapp.my.domain spec: group: webapp.my.domain names: kind: Building listKind: BuildingList plural: buildings singular: building scope: Namespaced versions: - name: v1 schema: openAPIV3Schema: description: Building is the Schema for the buildings API properties: apiVersion: description: |- APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: description: |- Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object spec: description: BuildingSpec defines the desired state of Building properties: address: description: Foo is an example field of Building. Edit building_types.go to remove/update type: string type: object status: description: BuildingStatus defines the observed state of Building properties: zipCode: type: string type: object type: object served: true storage: true subresources: status: {}
YAML
복사

테스트

kind 로컬 클러스터에 CRD와 컨트롤러를 적용해 테스트 해본다. 컨텍스트가 kind-kind 가 맞는지 확인하고 적용한다:
make install make run
Shell
복사
다른 터미널에서 CRD가 잘 생성됐는지 확인한다:
❯ k get crd NAME CREATED AT buildings.webapp.my.domain 2024-02-13T22:27:52Z
Shell
복사
CR 샘플 매니페스트를 kustomize로 생성한다:
k apply -k config/samples/
Shell
복사
❯ k get building NAME AGE building-sample 59s ❯ k get building building-sample -oyaml | yq .status.zipCode 88523
Shell
복사
 CR 객체가 생성됐고, 컨트롤러의 동작도 확인했다.

정리

CRD를 제거하고 컨트롤러 프로세스에 시그널을 보내 종료한다:
make uninstall
Shell
복사
kubebuilder 프로젝트 전체와 컴포넌트 중 리컨실러 관련하여 간단하게 핸즈온을 해봤다. 아키텍처에서 보면 더 많은 기능이 있다(캐시, 웹훅, …). v3 가이드북의 공식 튜토리얼인 CronJob 만들기를 해보면 이런 기능을 활용해볼 수 있다: