Kubebuiler란
kubebuilder란 kubernetes의 리소스를 go 언어로 쉽게 개발할 수 있게 하는 패키지입니다.
kubernetes API를 확장하여 사용자 정의 리소스(CRD)를 간편하게 생성 할 수 있으며,
패키지는 이전글에 설명했던 controller-runtime 패키지가 내장되어 동작합니다.
추가적으로 kubebuilder는 CLI를 지원하며 명령어로 템플릿을 쉽게 생성 할 수 있습니다.
사전 구성
- go version v1.20.0+
- docker version 17.03+.
- kubectl version v1.11.3+.
- Access to a Kubernetes v1.11.3+ cluster.
Installation
kubebuilder: 설치
필자는 Mac OS를 사용하고 있어서 brew 명령어로 설치 하였습니다.
# for mac
brew install kubebuilder
kubebuilder completion zsh > ~/.kubebuilder-completion.zsh
---
# 명령어 자동 완성을 위해 .zshrc에 추가
source ~/.kubebuilder-completion.zsh
autoload -Uz compinit
compinit
---
# zsh 소스 업데이트
source ~/.zshrc
# 로컬에 바로 설치를 원하면 아래의 명령어 사용
curl -L -o kubebuilder "<https://go.kubebuilder.io/dl/latest/$>(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
Kubernetes에서 CronJob 컨트롤러를 비 Kubeuilder로 구현하는 것에 대한 유지보수 부담이 마침내 지쳤다고 가정하고 Kubbuilder를 사용하여 다시 작성해 보겠습니다.
CronJob 컨트롤러의 작업은 일정한 간격으로 Kubernetes 클러스터에서 일회성 작업을 실행하는 것입니다. Job 컨트롤러 위에 구축하여 작업을 완료할 때까지 일회성 작업을 한 번 실행하는 것입니다.
Job controller를 다시 쓰는 문제를 해결하는 대신 외부 유형과 상호 작용하는 방법을 확인하는 기회로 사용하겠습니다.
Project 구조 만들기
먼저, Project 구조를 만들기 위해 아래와 같이 kubebuilder init 명령어를 실행한다.
$ mkdir kubebilder-test -p
$ cd kubebilder-test
$ kubebuilder init --domain test.kubebuilder.io --repo test.kubebuilder.io/project
도메인을 tutorial.kubebuilder.io 로 했으므로 모든 API Group 은 <group>.tutorial.kubebuilder.io 방식으로 정해지게 된다.
또한 특별히 프로젝트 이름은 지정하지 않았는데, --project-name=<dns1123-label-string> 과 같이 옵션을 추가하지 않으면 폴더의 이름이 기본적으로 프로젝트 이름이 된다.
한가지 주의해야 할 점은 cronjob-kubebuilder 디렉토리는 $GOPATH 경로 아래에 있어서는 안된다.
이는 Go modules 의 규칙 때문인데 좀 더 자세히 알고 싶으면
https://go.dev/blog/using-go-modules 블로그 포스트를 읽어보자.
go.mod 파일은 모듈 디펜던시를 표시하고, Makefile 은 custom controller 를 빌드하고 디플로이 할 수 있다.
config 디렉토리 아래에는 Kustomize 로 작성되어 CustomResourceDefinition, RBAC, WebhookConfiguration 등의 yaml 파일들이 정의되어 있다.
특히, config/manager 디렉토리에는 Cluster 에 Custom Controller 를 파드 형태로 배포할 수 있는 Kustomize yaml 이 있고,
config/rbac 디렉토리에는 서비스 어카운트로 Custom Controller 의 권한이 정의되어 있다.
Custom Controller 의 Entrypoint 는 cmd/main.go 파일이다.
처음 필요한 모듈을 import한 것을 보면 아래 2개가 보인다.
- core controller-runtime 라이브러리
- 기본 controller-runtime 로깅인 Zap
//main.go
package main
import (
"flag"
"os"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
// +kubebuilder:scaffold:imports
)
모든 컨트롤러는 Kinds와 Go Type간의 매핑을 제공하는 Scheme이 필요하다.
Kinds는 API를 정의할 때 사용된다.
//main.go
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
}
main function에서 중요 사항은
- matric을 위한 기본 flags가 세팅됨
- 관리자를 인스턴스화하여 모든 컨트롤러를 실행하는 과정을 추적하고 공유 캐시와 클라이언트를 API 서버에 설정한다.
- 관리자를 실행하고 모든 컨트롤러와 웹 후크를 실행합니다. 관리자는 graceful shutdown 신호를 받을 때까지 실행되도록 설정되어 있습니다. 이렇게 하면 Kubernetes에서 실행할 때 파드 종료와 함께 적절하게 행동합니다.
// main.go
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: server.Options{
BindAddress: metricsAddr,
},
WebhookServer: webhook.NewServer(webhook.Options{Port: 9443}),
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "80807133.tutorial.kubebuilder.io",
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
Manager는 contorller가 바라보는 리소스를 namespace에 종속되게 할 수 있다.
//main.go
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Cache: cache.Options{
DefaultNamespaces: map[string]cache.Config{
namespace: {},
},
},
Metrics: server.Options{
BindAddress: metricsAddr,
},
WebhookServer: webhook.NewServer(webhook.Options{Port: 9443}),
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "80807133.tutorial.kubebuilder.io",
})
만약 하나의 네임스페이스를 보게 설정하고 싶다면
기본 ClusterRole과 ClusterRoleBinding을 Role과 RoleBinding으로 각각 대체하여 제공된 권한을 설정한 네임스페이스로 제한하는 것이 좋습니다 RBAC Authorization.
var namespaces []string // List of Namespaces
defaultNamespaces := make(map[string]cache.Config)
for _, ns := range namespaces {
defaultNamespaces[ns] = cache.Config{}
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Cache: cache.Options{
DefaultNamespaces: defaultNamespaces,
},
Metrics: server.Options{
BindAddress: metricsAddr,
},
WebhookServer: webhook.NewServer(webhook.Options{Port: 9443}),
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "80807133.tutorial.kubebuilder.io",
})
아래 코드의 정보는 [cache.Options{}](<https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cache#Options>)
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
이제 구조가 이해되었다면 API 설계를 진행해보자
Reperence
https://sdk.operatorframework.io/docs/faqs/
https://book.kubebuilder.io/quick-start#enabling-shell-autocompletion