컨트롤러 제작 도구
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
복사
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.domain 의 Building 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 만들기를 해보면 이런 기능을 활용해볼 수 있다: