k8s의 CRD 만들기
Custom Resource Definition
by HOON
1
Last updated on Feb. 6, 2025, 9:16 p.m.
안녕하세요. 저번 포스트에서 k8s의 Deployment, ReplicaSet을 직접 생성하고 테스트하는 내용을 작성했었습니다. 👉🏻 Deployment,RecpliaSet 테스트
오늘은 이어서, 테스트를 위해 kubebuilder, go언어를 설치한김에 Custom Resource Definition
을 작성 후 테스트까지 해볼까 합니다.
간단한 ConfigMap으로 동작하는 리소스를 예시로 만들어볼게요.
1. 설치 환경
우선 테스트를 위해 kubernetes cluster와 Go언어 개발 환경, kubebuilder 설치가 필요합니다.
제 환경은 VirtualBox의 ubuntu22 릴리즈 버전을 사용하고있습니다. master,node1,node2로 이루어져있지만 master에서만 실행해보겠습니다.
추가로 해당 vm의 용량은 50GB로 잡았습니다.
글은 명령어 이미지-> 복사 가능한 코드 순서로 작성해두겠습니다
2. 프로젝트 디렉토리 및 초기화
테스트를 위한 폴더 생성 및 초기화를 진행해볼게요.
mkdir simple-operator
cd simple-operator
# 저는 go버전이 달라서 버전 스킵 옵션을 사용했습니다. 현재 버전:go1.21.5, 필요 버전: go1.23
k8s-master@k8s-master:~/simple-operator$ kubebuilder init --skip-go-version-check --domain=mydomain.io --repo=simple-operator
#--domain 옵션은 Custom Resource의 그룹 도메인을 결정합니다. 예시로 mydomain.io라고 설정했습니다.
이 작업을 진행하면 go.mod , 주요 폴더 구조(config, contollers , api 등) 이 생성됩니다.
3. API(= CRD) 및 Controller 코드 생성
k8s-master@k8s-master:~/simple-operator$ kubebuilder create api --group=sample --version=v1alpha1 --kind=Hello
#--group=sample: 그룹은 보통 도메인 하위로 sample.mydomain.io 이런 식으로 생성될 것입니다.
#--version=v1alpha1: CRD 버전을 v1alpha1로 지정합니다.
#--kind=Hello: Custom Resource의 Kind를 Hello라고 지었고, 이를 통해 CR 이름은 hellos.sample.mydomain.io 형태가 됩니다.
4. API(= CRD) 구조 정의
이제, API 구조 정의를 위해서 경로를 이동해보겠습니다.
api/v1alpha1/hello_types.go
파일을 열어보면, HelloSpec과, HelloStatus가 존재할텐데 이 내용을 수정 해 볼게요.
// HelloSpec defines the desired state of Hello
type HelloSpec struct {
// 원하는 메시지
Message string `json:"message,omitempty"`
}
// HelloStatus defines the observed state of Hello
type HelloStatus struct {
// 현재 생성된 ConfigMap 이름(예시)
ConfigMapName string `json:"configMapName,omitempty"`
}
이 구조를 정의하면 Spec 과 Status를 커스텀 정의 할 수 있습니다. 예를들어, spec에 Message를 정의 해 뒀다면,
아래와 같이 실제 메니페스트 파일을 작성할때 사용 가능합니다.
apiVersion: mygroup.example.com/v1alpha1
kind: Hello
metadata:
name: my-hello
spec:
message: "Hello, Kubernetes!"
다음은, Controller 로직을 작성 해보겠습니다.
우선 다음 경로로 이동하겠습니다. internal/controller/hello_controller.go
(kubebuilder 버전에 따라 controllers/hello_controller.go 경로가 될 수도 있습니다.)
func (r *HelloReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := ctrllog.FromContext(ctx)
// 1) CR(Hello) 가져오기
var hello samplev1alpha1.Hello
if err := r.Get(ctx, req.NamespacedName, &hello); err != nil {
// CR이 삭제된 경우 등 NotFound 에러면 무시
if errors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
// 2) CR.spec.message를 참조하여 ConfigMap 생성/업데이트
configMapName := hello.Name + "-configmap"
// ConfigMap 원하는 spec
desiredConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
Namespace: hello.Namespace,
},
Data: map[string]string{
"message": hello.Spec.Message,
},
}
// ConfigMap 실제 상태 가져오기
var existingConfigMap corev1.ConfigMap
err := r.Get(ctx, types.NamespacedName{
Name: configMapName,
Namespace: hello.Namespace,
}, &existingConfigMap)
if err != nil && errors.IsNotFound(err) {
// 없으면 만들기
if createErr := r.Create(ctx, desiredConfigMap); createErr != nil {
return ctrl.Result{}, createErr
}
log.Info("Created ConfigMap", "ConfigMapName", configMapName)
} else if err == nil {
// 이미 있으면, Data만 업데이트
existingConfigMap.Data = desiredConfigMap.Data
if updateErr := r.Update(ctx, &existingConfigMap); updateErr != nil {
return ctrl.Result{}, updateErr
}
log.Info("Updated ConfigMap", "ConfigMapName", configMapName)
} else {
return ctrl.Result{}, err
}
// 3) CR Status 업데이트(옵션)
if hello.Status.ConfigMapName != configMapName {
hello.Status.ConfigMapName = configMapName
if statusErr := r.Status().Update(ctx, &hello); statusErr != nil {
return ctrl.Result{}, statusErr
}
}
return ctrl.Result{}, nil
}
- CR 가져오기 -> 2. ConfigMap 생성, 실제 상태 가져오기 -> 3. CR Status 업데이트의 순으로 이루어져있습니다.
5. Makefile 이용해서 CRD 생성 및 Controller 배포
처음 kubebuilder create api 명령 이후, config/ 디렉터리 안에 CRD, RBAC, Kustomize 관련 매니페스트들이 생성되어 있습니다.
따라서 make install
명령어를 실행하게되면 config/crd/bases/ 아래에 정의된 CRD yaml(hellos.sample.mydomain.io.yaml)을 클러스터에 적용 할 수 있습니다.
먼저 make install 명령어로 CRD를 생성합니다.
다음, Controller 배포를 위해 make deploy
명령을 실행하여, config/default/ 아래 있는 내용들을 kustomize로 빌드 후 클러스터에 배포합니다.
6. CR(Hello) 생성하여 동작 확인
자 이제, 코드는 완성됐고 배포까지 됐으니 정상작동이 되는지 확인 한번 해볼까요??
manifest 파일을 한번 작성 해 볼게요.
apiVersion: sample.mydomain.io/v1alpha1 #위에서 정의한 domain이름 내용을 토대로 apiVersion을 작성 할 수 있습니다.
kind: Hello #저희가 작성한 ConfigMap의 기능을 하는 새로운 리소스 Hello를 정의합니다.
metadata:
name: hello-sample
namespace: default
spec:
message: "Hello from CR!" #작성된 API 구조 정의된 내용을 토대로 작성 할 수 있습니다.
이제 명령어를 통해 리소스를 생성해볼게요.
kubectl apply -f hello-sample.yaml
kubectl get hello hello-sample -o yaml
적용이 됐으니 한번 실행중인지 살펴볼게요!!!
7. 에러 해결
Pending?? 이상하네요.. 한번 describe 해볼까요?
k8s-master@k8s-master:~/simple-operator$ kubectl get pods -n simple-operator-system
NAME READY STATUS RESTARTS AGE
simple-operator-controller-manager-6f9c5ff4d5-dv2rl 0/1 Pending 0 12m
k8s-master@k8s-master:~/simple-operator$ kubectl logs simple-operator-controller-manager-6f9c5ff4d5-dv2rl -n simple-operator-system
k8s-master@k8s-master:~/simple-operator$ kubectl describe pod simple-operator-controller-manager-6f9c5ff4d5-dv2rl -n simple-operator-system
Name: simple-operator-controller-manager-6f9c5ff4d5-dv2rl
Namespace: simple-operator-system
Priority: 0
Service Account: simple-operator-controller-manager
Node: <none>
Labels: app.kubernetes.io/name=simple-operator
control-plane=controller-manager
pod-template-hash=6f9c5ff4d5
Annotations: kubectl.kubernetes.io/default-container: manager
Status: Pending
. . .
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 13m default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) had untolerated taint {node.kubernetes.io/unreachable: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
Warning FailedScheduling 3m34s (x2 over 8m34s) default-scheduler 0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) had untolerated taint {node.kubernetes.io/unreachable: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
아, Events를 보니 untolerated taint {node.kubernetes.io/unreachable: } taint가 적용되어있어서 에러가 발생한거네요. CKA 단골문제였습니다..
그럼 방법은 2가지가 있습니다.
1. toleration을 추가하기
2. node의 taint 삭제하기
저는 2번은 테스트용으로는 간편해서 좋지만, 보안상 위협이 생길 수 있다 판단하여 1번으로 진행하겠습니다.
control-plane taint에 대해 toleration을 추가하기위해, Operator의 매니페스트(일반적으로 config/manager/manager.yaml 또는 config/default/manager.yaml 파일)를 열어 아래와 같이 수정합니다.
spec:
template:
spec:
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
이제 다시 한 번 점검 해볼게요.
kubectl get nodes -A
이상없이 Running중인걸 확인 할 수 있습니다!
테스트는 여기까지 하겠습니다.👏🏻
8. 마무리
이번 포스트는 기존에 존재하는 리소스처럼 작동하는 custom resource를 작성하고 배포하여 테스트까지 해봤습니다.
이제 어느정도 리소스들이 작동하는 원리 기본개념을 좀 익힌 것 같아요.
물론 ConfigMap이라는 아주 단순한 리소스를 대상으로 했지만,
추후 제가 진짜 필요한 리소스 (redis 혹은 postgresql 등) 이 생긴다면 조금 더 복잡하겠지만 도전 해 볼 수 있겠죠??
이번 포스트를 진행하면서 에러가 꽤 많았지만 (vm 디스크 용량부족, image build 에러 등) 여기선 다루지 않았습니다.
추후에 다뤄보도록 하겠습니다. 감사합니다.
Leave a Comment: