Go와 controller-runtime, Kubebuilder를 사용해 Kube-Shift 예제를 기반으로 Kubernetes 오퍼레이터를 처음부터 구현하는 방법을 설명합니다.
Kubernetes는 이제 컨테이너 오케스트레이션의 사실상 표준(de-facto standard)이 되었습니다. 도입이 늘어남에 따라 복잡한 상태 기반(stateful) 애플리케이션을 관리하기 위해 그 기능을 확장해야 할 필요성도 함께 커지고 있습니다. 여기서 바로 Kubernetes 오퍼레이터(Operator)가 등장합니다.
이 글에서는 Go와 controller-runtime 라이브러리를 사용해 커스텀 Kubernetes 오퍼레이터를 처음부터 어떻게 만드는지 살펴보겠습니다. 실제 예제로 데이터베이스 스키마 마이그레이션을 위한 커스텀 오퍼레이터인 kube-shift를 사용해 전체 과정을 따라가 보겠습니다. 더 많은 글은 여기에서 볼 수 있습니다: blog
코드로 들어가기 전에, 먼저 오퍼레이터가 어떤 문제를 해결하는지 이해해 봅시다. Kubernetes에서 애플리케이션을 관리하는 일은 단순히 파드(Pod) 몇 개를 배포하는 것에서 끝나지 않습니다. 많은 애플리케이션은 설치, 업그레이드, 장애 처리 등을 포함하는 복잡한 라이프사이클 관리가 필요합니다.
오퍼레이터는 이런 작업을 자동화하기 위해 Kubernetes API를 확장합니다. 구체적으로는 Custom Resource Definition(CRD)을 추가하고, 이를 관리하는 커스텀 컨트롤러를 함께 제공하는 방식입니다.
kube-shift 프로젝트는 이런 개념의 좋은 예시입니다. 이 프로젝트는 Kubernetes에 DatabaseMigration 리소스를 도입해, 데이터베이스 스키마 변경을 Kubernetes 리소스처럼 배포 프로세스의 일부로 관리할 수 있게 해줍니다.
많은 Go 기반 오퍼레이터와 마찬가지로 kube-shift 오퍼레이터는 Kubebuilder 프레임워크로 만들어졌습니다. Kubebuilder는 새로운 오퍼레이터 프로젝트를 빠르게 스캐폴딩할 수 있는 도구 모음을 제공하며, CRD, 컨트롤러, 기타 필수 컴포넌트에 대한 보일러플레이트 코드를 생성합니다.
오퍼레이터의 엔트리 포인트는 cmd/main.go입니다. 이 파일은 컨트롤러 매니저를 설정하고 실행하는 역할을 합니다.
go// cmd/main.go package main import ( // ... imports "github.com/mohit-nagaraj/kube-shift/api/v1alpha1" "github.com/mohit-nagaraj/kube-shift/internal/controller" // ... ) func main() { // ... flag parsing and setup mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ // ... options }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } if err = (&controller.DatabaseMigrationReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "DatabaseMigration") os.Exit(1) } // ... }
여기서는 새로운 매니저를 생성하고, 그 위에 DatabaseMigrationReconciler를 등록합니다. 매니저는 컨트롤러를 실행하고 라이프사이클을 관리하는 역할을 담당합니다.
DatabaseMigration CRD오퍼레이터를 만들 때 첫 단계는 오퍼레이터가 관리할 커스텀 리소스를 정의하는 것입니다. 이는 api/v1alpha1/databasemigration_types.go 파일에서 이루어집니다. 이 파일에는 DatabaseMigration CRD를 표현하는 Go 타입이 정의되어 있습니다.
go// api/v1alpha1/databasemigration_types.go package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // DatabaseMigrationSpec defines the desired state of DatabaseMigration type DatabaseMigrationSpec struct { Database DatabaseConfig `json:"database"` Migration MigrationConfig `json:"migration"` } // DatabaseMigrationStatus defines the observed state of DatabaseMigration type DatabaseMigrationStatus struct { Phase MigrationPhase `json:"phase,omitempty"` Message string `json:"message,omitempty"` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status // DatabaseMigration is the Schema for the databasemigrations API type DatabaseMigration struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec DatabaseMigrationSpec `json:"spec,omitempty"` Status DatabaseMigrationStatus `json:"status,omitempty"` }
DatabaseMigration 구조체는 모든 Kubernetes 오브젝트에 공통적인 metav1.TypeMeta와 metav1.ObjectMeta를 임베딩하고 있습니다. Spec 필드는 리소스의 원하는 상태(desired state) 를 정의하고, Status 필드는 리소스의 현재 상태(observed state) 를 나타냅니다.
CRD를 정의했으니, 이제 이 리소스에 대해 동작할 컨트롤러가 필요합니다. 컨트롤러의 로직은 internal/controller/databasemigration_controller.go에 구현되어 있습니다. 컨트롤러의 핵심은 Reconcile 함수입니다. 이 함수는 DatabaseMigration 리소스가 생성, 수정, 삭제될 때마다 호출됩니다.
go// internal/controller/databasemigration_controller.go // DatabaseMigrationReconciler reconciles a DatabaseMigration object type DatabaseMigrationReconciler struct { client.Client Scheme *runtime.Scheme Log logr.Logger // ... other fields } // +kubebuilder:rbac:groups=database.mohitnagaraj.in,resources=databasemigrations,verbs=get;list;watch;create;update;patch;delete // ... other rbac markers func (r *DatabaseMigrationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("databasemigration", req.NamespacedName) // your logic here return ctrl.Result{}, nil }
Reconcile 함수는 이벤트를 발생시킨 DatabaseMigration 리소스의 이름과 네임스페이스 정보를 전달받습니다. 리컨실러는 이 정보를 이용해 리소스를 조회하고, 리소스의 Spec에 정의된 원하는 상태에 맞게 시스템을 맞추기 위한 필요한 작업을 수행합니다. 이 과정에서 데이터베이스 마이그레이션을 실행하기 위한 Job, Pod, 기타 리소스를 생성할 수도 있습니다.
견고한 오퍼레이터는 단순히 리소스를 생성하는 것 이상의 일을 수행합니다. kube-shift는 몇 가지 고급 개념을 잘 보여줍니다.
DatabaseMigration 리소스의 Status 필드를 업데이트합니다. 이를 통해 사용자는 진행 상황과 상태를 명확하게 확인할 수 있습니다.kube-shift는 파이널라이저를 사용합니다. DatabaseMigrationFinalizer는 리소스 생성 시 추가되며, 컨트롤러가 필요한 정리 작업을 모두 마치기 전까지 리소스가 실제로 삭제되지 않도록 보장합니다.cmd/main.go에서 구성합니다.프로덕션급 소프트웨어라면 테스트는 필수입니다. kube-shift에는 유닛 테스트와 통합 테스트가 모두 포함되어 있습니다.
envtest 패키지를 사용해 실제 Kubernetes API 서버를 대상으로 통합 테스트를 수행합니다. internal/controller/suite_test.go에서 컨트롤러 테스트를 위한 테스트 환경을 설정하는 코드를 확인할 수 있습니다.Kubernetes 오퍼레이터를 만드는 것은 Kubernetes를 확장하고, 복잡한 애플리케이션 관리 작업을 자동화하는 강력한 방법입니다. Kubebuilder와 controller-runtime 같은 도구를 활용하면 견고하고 프로덕션 준비가 된 오퍼레이터를 비교적 수월하게 구현할 수 있습니다.
kube-shift 프로젝트는 이러한 개념을 실제 문제에 어떻게 적용할 수 있는지 잘 보여주는 훌륭한 예시입니다.
더 자세히 알아보고 싶다면 GitHub의 kube-shift 저장소를 살펴보는 것을 추천합니다. 전체 소스 코드와 함께 오퍼레이터를 빌드, 배포, 사용하는 방법에 대한 더 많은 정보를 얻을 수 있습니다. 즐거운 코딩 되세요!