Kubernetes 연산자: CRD, 컨트롤러 패턴 및 연산자 SDK
프로덕션 환경에서 PostgreSQL 클러스터를 어떻게 관리합니까? 기본을 모니터링하고 감지해야 합니다. 오류, 복제본 승격, 구성 업데이트, 백업 수행 인증서 순환을 예약하고 관리합니다. 전문 DBA가 수행하는 작업입니다. 암기하는 방법을 알고 있지만 문제가 발생할 때마다 몇 시간씩 수작업을 해야 합니다. 오전 3시에 비뚤어진.
패턴 쿠버네티스 운영자 이 지식을 코드화할 수 있습니다. Kubernetes 기반 소프트웨어에서 작동: 클러스터 상태를 관찰하는 컨트롤러 이를 원하는 상태와 비교하고 이를 조정하는 데 필요한 조치를 취합니다. 자동으로. 계속해서. 인간의 개입 없이. 이 기사에서 우리는 Operator SDK와 Kubebuilder를 사용하여 컨트롤러를 철저하게 이해하는 완전한 Operator 패턴과 조정 루프.
무엇을 배울 것인가
- Kubernetes Operator는 무엇이며 언제 구축하는 것이 합리적입니까?
- 사용자 정의 리소스 정의(CRD): 스키마, 버전 관리, 유효성 검사
- 컨트롤러 패턴과 조정 루프
- Operator SDK와 Kubebuilder: 차이점 및 사용 시기
- Kubebuilder로 완전한 Operator 구현
- 운영자 허브 및 운영자 라이프사이클 관리자(OLM)
- envtest를 사용한 연산자 테스트
- 프로덕션 운영자: Zalando Postgres 운영자, Kafka용 Strimzi
쿠버네티스 오퍼레이터란?
"Operator"라는 용어는 패턴을 설명하기 위해 2016년 CoreOS에서 도입되었습니다. 특정 애플리케이션의 운영 노하우를 캡슐화한 소프트웨어 Kubernetes 컨트롤러 세트. Google의 공식 정의:
Kubernetes 애플리케이션을 패키징, 배포 및 관리하는 연산자 및 방법입니다. 운영자는 관리 시 인간 운영자의 일반적인 작업을 구현하고 자동화합니다. 해당 유형의 애플리케이션: 배포, 업데이트, 백업, 장애 조치, 확장.
Operator는 Kubernetes 선언 모델을 특정 도메인으로 확장합니다. 대신에 "팟을 주세요"라고 말하면 "복제본 3개, 일일 백업이 포함된 PostgreSQL 클러스터를 주세요"라고 말할 수 있습니다. S3에서는 자동 장애 조치 및 TLS 인증서"를 제공합니다. 운영자는 이것을 변환하는 방법을 알고 있습니다. 구체적인 Kubernetes 리소스의 높은 수준 사양.
운영자 성숙도 모델
운영자 역량 모델은 성숙도 향상을 5가지 수준으로 정의합니다.
| 수준 | 이름 | 용량 |
|---|---|---|
| 1 | 기본 설치 | 자동화된 애플리케이션 프로비저닝 |
| 2 | 원활한 업그레이드 | 패치 및 마이너 버전 업그레이드 |
| 3 | 전체 수명주기 | 백업, 장애 복구, 재구성 |
| 4 | 깊은 통찰력 | 지표, 경고, 로그 처리, 워크로드 분석 |
| 5 | 자동 조종 장치 | 자동 확장, 자동 구성, 이상 감지 |
사용자 정의 리소스 정의(CRD)
CRD는 사용자 정의 리소스 유형으로 Kubernetes API를 확장합니다. 그냥 사용하는 것보다
기본 리소스(Pod, 배포, 서비스), 특정 도메인 리소스를 정의할 수 있습니다.
어떻게 PostgresCluster, KafkaTopic, MLModel.
CRD 정의
# postgres-cluster-crd.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: postgresclusters.database.example.com
spec:
group: database.example.com
scope: Namespaced
names:
plural: postgresclusters
singular: postgrescluster
kind: PostgresCluster
shortNames:
- pgc
versions:
- name: v1alpha1
served: true
storage: true
# Schema di validazione OpenAPI v3
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- replicas
- version
properties:
replicas:
type: integer
minimum: 1
maximum: 5
description: "Numero di repliche PostgreSQL"
version:
type: string
enum: ["14", "15", "16"]
description: "Versione PostgreSQL"
storage:
type: object
properties:
size:
type: string
pattern: "^[0-9]+Gi$"
default: "10Gi"
storageClass:
type: string
default: "fast-ssd"
backup:
type: object
properties:
enabled:
type: boolean
default: false
schedule:
type: string
description: "Cron expression per backup schedulato"
s3Bucket:
type: string
resources:
type: object
properties:
requests:
type: object
properties:
memory:
type: string
cpu:
type: string
limits:
type: object
properties:
memory:
type: string
cpu:
type: string
status:
type: object
properties:
phase:
type: string
enum: ["Pending", "Creating", "Running", "Degraded", "Failed"]
readyReplicas:
type: integer
primaryEndpoint:
type: string
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
reason:
type: string
message:
type: string
lastTransitionTime:
type: string
format: date-time
# Stampa colonne aggiuntive in kubectl get
additionalPrinterColumns:
- name: Replicas
type: integer
jsonPath: .spec.replicas
- name: Version
type: string
jsonPath: .spec.version
- name: Status
type: string
jsonPath: .status.phase
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
# Subresource status (necessario per UpdateStatus)
subresources:
status: {}
사용자 정의 리소스 실행
# my-postgres-cluster.yaml
apiVersion: database.example.com/v1alpha1
kind: PostgresCluster
metadata:
name: myapp-db
namespace: production
spec:
replicas: 3
version: "16"
storage:
size: "100Gi"
storageClass: fast-ssd
backup:
enabled: true
schedule: "0 2 * * *" # ogni notte alle 2:00
s3Bucket: "my-postgres-backups"
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
컨트롤러 패턴과 조정 루프
오퍼레이터의 마음은 제어 장치: 지속적으로 진행되는 과정 클러스터에 있는 리소스의 현재 상태를 관찰하고 이를 원하는 상태와 비교합니다. 사용자 정의 리소스에 선언되었습니다. 차이(드리프트)가 있는 경우 컨트롤러는 다음을 실행합니다. 두 국가를 조화시키는 데 필요한 조치. 이 사이클을 루프를 조정하다.
// Pseudocodice del reconcile loop
for {
desiredState = getDesiredState(customResource)
currentState = getCurrentState(cluster)
if currentState != desiredState {
actions = computeActions(desiredState, currentState)
execute(actions)
}
// Attendi il prossimo trigger (evento API server o requeueing)
waitForTrigger()
}
컨트롤러는 "순수한 이벤트 기반" 접근 방식(각 이벤트가 작업을 트리거하는 방식)을 사용하지 않습니다. 사양) 그러나 접근 방식 레벨 기반: 전체적인 상태를 관찰하고 화해하다. 이는 컨트롤러를 더욱 강력하게 만듭니다. 이벤트(충돌, 재시작)가 누락된 경우 어쨌든 컨트롤러는 다시 시작되고 올바른 상태로 수렴됩니다.
Kubebuilder: 연산자 구축
Kubebuilder는 Go에서 Operator를 구축하기 위한 공식 CNCF 프레임워크입니다. 생성 프로젝트 스캐폴딩, API 서버와의 통신 관리 및 도우미 제공 조정 루프의 경우. Operator SDK는 Kubebuilder를 기반으로 하며 다음에 대한 지원을 추가합니다. Helm 및 Ansible 연산자.
프로젝트 설정
# Installa Kubebuilder
curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder
sudo mv kubebuilder /usr/local/bin/
# Crea un nuovo progetto Operator
mkdir postgres-operator && cd postgres-operator
kubebuilder init \
--domain database.example.com \
--repo github.com/myorg/postgres-operator
# Genera l'API e il controller per PostgresCluster
kubebuilder create api \
--group database \
--version v1alpha1 \
--kind PostgresCluster \
--resource \
--controller
# Struttura generata:
# api/v1alpha1/
# postgrescluster_types.go <- Definizione della struct CRD
# groupversion_info.go
# internal/controller/
# postgrescluster_controller.go <- Logica del reconcile loop
# config/crd/ <- Manifest YAML della CRD
API 유형 정의
// api/v1alpha1/postgrescluster_types.go
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
)
// PostgresClusterSpec definisce lo stato desiderato
type PostgresClusterSpec struct {
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=5
Replicas int32 `json:"replicas"`
// +kubebuilder:validation:Enum={"14","15","16"}
Version string `json:"version"`
Storage PostgresStorageSpec `json:"storage,omitempty"`
Backup PostgresBackupSpec `json:"backup,omitempty"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}
type PostgresStorageSpec struct {
// +kubebuilder:default="10Gi"
Size string `json:"size,omitempty"`
StorageClass string `json:"storageClass,omitempty"`
}
type PostgresBackupSpec struct {
Enabled bool `json:"enabled,omitempty"`
Schedule string `json:"schedule,omitempty"`
S3Bucket string `json:"s3Bucket,omitempty"`
}
// PostgresClusterStatus descrive lo stato osservato
type PostgresClusterStatus struct {
Phase string `json:"phase,omitempty"`
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
PrimaryEndpoint string `json:"primaryEndpoint,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=".spec.replicas"
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"
type PostgresCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PostgresClusterSpec `json:"spec,omitempty"`
Status PostgresClusterStatus `json:"status,omitempty"`
}
조정 루프: 구현
// internal/controller/postgrescluster_controller.go
package controller
import (
"context"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
databasev1alpha1 "github.com/myorg/postgres-operator/api/v1alpha1"
)
type PostgresClusterReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=database.example.com,resources=postgresclusters,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=database.example.com,resources=postgresclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
func (r *PostgresClusterReconciler) Reconcile(
ctx context.Context,
req ctrl.Request,
) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// 1. Ottieni la Custom Resource
pgCluster := &databasev1alpha1.PostgresCluster{}
if err := r.Get(ctx, req.NamespacedName, pgCluster); err != nil {
if errors.IsNotFound(err) {
// CR eliminata, pulizia gia gestita dai finalizer
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
logger.Info("Reconciling PostgresCluster",
"name", pgCluster.Name,
"namespace", pgCluster.Namespace,
"replicas", pgCluster.Spec.Replicas)
// 2. Reconcilia il Service headless
if err := r.reconcileHeadlessService(ctx, pgCluster); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to reconcile headless service: %w", err)
}
// 3. Reconcilia lo StatefulSet
sts, err := r.reconcileStatefulSet(ctx, pgCluster)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to reconcile statefulset: %w", err)
}
// 4. Aggiorna lo status della CR
pgCluster.Status.ReadyReplicas = sts.Status.ReadyReplicas
pgCluster.Status.PrimaryEndpoint = fmt.Sprintf(
"%s-0.%s.%s.svc.cluster.local:5432",
pgCluster.Name,
pgCluster.Name,
pgCluster.Namespace,
)
if sts.Status.ReadyReplicas == pgCluster.Spec.Replicas {
pgCluster.Status.Phase = "Running"
} else if sts.Status.ReadyReplicas > 0 {
pgCluster.Status.Phase = "Degraded"
} else {
pgCluster.Status.Phase = "Creating"
}
if err := r.Status().Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to update status: %w", err)
}
logger.Info("Reconciliation complete",
"phase", pgCluster.Status.Phase,
"readyReplicas", pgCluster.Status.ReadyReplicas)
return ctrl.Result{}, nil
}
func (r *PostgresClusterReconciler) reconcileStatefulSet(
ctx context.Context,
pgCluster *databasev1alpha1.PostgresCluster,
) (*appsv1.StatefulSet, error) {
desired := r.buildStatefulSet(pgCluster)
// Imposta il owner reference per la garbage collection automatica
if err := ctrl.SetControllerReference(pgCluster, desired, r.Scheme); err != nil {
return nil, err
}
existing := &appsv1.StatefulSet{}
err := r.Get(ctx, client.ObjectKeyFromObject(desired), existing)
if errors.IsNotFound(err) {
// StatefulSet non esiste: crealo
if err := r.Create(ctx, desired); err != nil {
return nil, fmt.Errorf("failed to create StatefulSet: %w", err)
}
return desired, nil
}
if err != nil {
return nil, err
}
// StatefulSet esiste: aggiornalo se necessario
existing.Spec.Replicas = desired.Spec.Replicas
existing.Spec.Template = desired.Spec.Template
if err := r.Update(ctx, existing); err != nil {
return nil, fmt.Errorf("failed to update StatefulSet: %w", err)
}
return existing, nil
}
func (r *PostgresClusterReconciler) buildStatefulSet(
pgCluster *databasev1alpha1.PostgresCluster,
) *appsv1.StatefulSet {
image := fmt.Sprintf("postgres:%s", pgCluster.Spec.Version)
return &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: pgCluster.Name,
Namespace: pgCluster.Namespace,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &pgCluster.Spec.Replicas,
ServiceName: pgCluster.Name,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": pgCluster.Name},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": pgCluster.Name},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "postgres",
Image: image,
Resources: pgCluster.Spec.Resources,
},
},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{
Name: "data",
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
StorageClassName: &pgCluster.Spec.Storage.StorageClass,
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: pgCluster.Spec.Storage.ParsedSize(),
},
},
},
},
},
},
}
}
// SetupWithManager registra il controller con il manager
func (r *PostgresClusterReconciler) SetupWithManager(
mgr ctrl.Manager,
) error {
return ctrl.NewControllerManagedBy(mgr).
For(&databasev1alpha1.PostgresCluster{}).
Owns(&appsv1.StatefulSet{}). // Reconcilia quando cambia lo StatefulSet owned
Owns(&corev1.Service{}).
Complete(r)
}
종료자: 리소스 정리
종료자를 사용하면 리소스가 해제되기 전에 정리 작업을 수행할 수 있습니다. 제거되었습니다. 종료자가 없으면 PostgresCluster CR을 삭제하면 CR은 삭제되지만 반드시 S3 또는 백업의 데이터일 필요는 없습니다. 종료자를 사용하면 다음과 같이 정리를 관리할 수 있습니다.
// Aggiungi finalizer handling al Reconcile
const pgClusterFinalizer = "database.example.com/finalizer"
func (r *PostgresClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
pgCluster := &databasev1alpha1.PostgresCluster{}
if err := r.Get(ctx, req.NamespacedName, pgCluster); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Gestione eliminazione
if !pgCluster.DeletionTimestamp.IsZero() {
if controllerutil.ContainsFinalizer(pgCluster, pgClusterFinalizer) {
// Esegui cleanup
if err := r.cleanupExternalResources(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
// Rimuovi il finalizer
controllerutil.RemoveFinalizer(pgCluster, pgClusterFinalizer)
if err := r.Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// Aggiungi finalizer se non presente
if !controllerutil.ContainsFinalizer(pgCluster, pgClusterFinalizer) {
controllerutil.AddFinalizer(pgCluster, pgClusterFinalizer)
if err := r.Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
}
// ... resto della logica di reconcile
return ctrl.Result{}, nil
}
envtest를 사용한 연산자 테스트
Kubebuilder가 제공하는 envtest, API 서버를 시작하는 테스트 프레임워크 통합된 방식으로 컨트롤러를 테스트하기 위한 실제 Kubernetes(kubelet 및 노드 없음):
// internal/controller/postgrescluster_controller_test.go
package controller
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
databasev1alpha1 "github.com/myorg/postgres-operator/api/v1alpha1"
)
var _ = Describe("PostgresCluster Controller", func() {
const (
timeout = time.Second * 10
interval = time.Millisecond * 250
)
Context("Quando crea un PostgresCluster", func() {
It("Deve creare lo StatefulSet corrispondente", func() {
ctx := context.Background()
pgCluster := &databasev1alpha1.PostgresCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-postgres",
Namespace: "default",
},
Spec: databasev1alpha1.PostgresClusterSpec{
Replicas: 1,
Version: "16",
Storage: databasev1alpha1.PostgresStorageSpec{
Size: "10Gi",
StorageClass: "standard",
},
},
}
Expect(k8sClient.Create(ctx, pgCluster)).Should(Succeed())
// Verifica che lo StatefulSet venga creato
stsLookupKey := types.NamespacedName{
Name: "test-postgres",
Namespace: "default",
}
createdSts := &appsv1.StatefulSet{}
Eventually(func() bool {
err := k8sClient.Get(ctx, stsLookupKey, createdSts)
return err == nil
}, timeout, interval).Should(BeTrue())
// Verifica le specifiche dello StatefulSet
Expect(*createdSts.Spec.Replicas).Should(Equal(int32(1)))
Expect(createdSts.Spec.Template.Spec.Containers[0].Image).
Should(Equal("postgres:16"))
// Cleanup
Expect(k8sClient.Delete(ctx, pgCluster)).Should(Succeed())
})
})
})
운영자 빌드 및 배포
# Build dell'immagine
make docker-build docker-push IMG="myregistry/postgres-operator:v0.1.0"
# Deploy dell'Operator nel cluster
make deploy IMG="myregistry/postgres-operator:v0.1.0"
# Verifica il deployment
kubectl get pods -n postgres-operator-system
kubectl logs -n postgres-operator-system deployment/postgres-operator-controller-manager
# Applica una CR
kubectl apply -f my-postgres-cluster.yaml
kubectl get postgresclusters -n production
kubectl describe postgrescluster myapp-db -n production
생산 운영자: 실제 사례
모든 일반 애플리케이션에 대해 Operator를 구축할 필요는 없습니다. 생태계 Kubernetes는 주요 상태 저장 애플리케이션을 위한 성숙한 Operator를 제공합니다.
Zalando Postgres 운영자
# Installa il Postgres Operator di Zalando (level 5 maturity)
helm repo add postgres-operator-charts \
https://opensource.zalando.com/postgres-operator/charts/postgres-operator
helm install postgres-operator \
postgres-operator-charts/postgres-operator \
-n postgres-operator --create-namespace
# Crea un cluster PostgreSQL con HA e backup su S3
apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
name: myapp-postgres
namespace: production
spec:
teamId: "myteam"
volume:
size: 100Gi
storageClass: fast-ssd
numberOfInstances: 3
users:
myapp:
- superuser
- createdb
databases:
myapp: myapp
postgresql:
version: "16"
parameters:
shared_buffers: "1GB"
max_connections: "200"
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
patroni:
failsafe_mode: false
# Backup automatico su S3 con WAL-G
enableLogicalBackup: true
logicalBackupSchedule: "00 02 * * *"
Strimzi: Kubernetes의 Kafka
# Kafka cluster con Strimzi (level 5 maturity)
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: production-cluster
namespace: kafka
spec:
kafka:
version: 3.7.0
replicas: 3
listeners:
- name: plain
port: 9092
type: internal
tls: false
- name: tls
port: 9093
type: internal
tls: true
config:
offsets.topic.replication.factor: 3
transaction.state.log.replication.factor: 3
transaction.state.log.min.isr: 2
default.replication.factor: 3
min.insync.replicas: 2
inter.broker.protocol.version: "3.7"
storage:
type: jbod
volumes:
- id: 0
type: persistent-claim
size: 200Gi
class: fast-ssd
deleteClaim: false
resources:
requests:
memory: 4Gi
cpu: 2000m
limits:
memory: 8Gi
cpu: 4000m
zookeeper:
replicas: 3
storage:
type: persistent-claim
size: 10Gi
class: fast-ssd
deleteClaim: false
entityOperator:
topicOperator: {}
userOperator: {}
OLM(운영자 라이프사이클 관리자)
OLM은 설치, 업그레이드 및 수명주기 관리를 관리합니다. 클러스터의 운영자입니다. 그리고 OperatorHub.io가 Operator를 배포하는 데 사용하는 메커니즘입니다.
# Installa OLM nel cluster
curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.28.0/install.sh | bash -s v0.28.0
# Installa un Operator da OperatorHub tramite OLM
kubectl create -f https://operatorhub.io/install/postgres-operator.yaml
# Verifica gli Operator installati
kubectl get csv -n operators # ClusterServiceVersion
kubectl get subscription -n operators
운영자를 위한 모범 사례
생산 운영자를 위한 체크리스트
- 종료자 사용: 외부 부작용이 있는 리소스(S3 버킷, DNS 레코드 등)에 대해 항상
- 상태 조건을 구현합니다. Kubernetes 조건 패턴(유형, 상태, 이유, 메시지)을 따릅니다.
- 멱등성: 조정 루프는 동일한 결과로 여러 번 실행해도 안전해야 합니다.
- 재시도로 오류를 처리합니다. 미국
ctrl.Result{RequeueAfter: time.Minute}일시적인 오류의 경우 - 차단 작업을 수행하지 마세요. 조정은 차단되어서는 안 됩니다. 긴 작업에는 고루틴을 사용하세요
- 최소 RBAC: 주석에는 꼭 필요한 권한만 사용하세요.
+kubebuilder:rbac - envtest로 테스트하기: 각 조정 시나리오에 대한 통합 테스트 작성
- 버전 관리 API: 마이그레이션을 위해 버전 관리(v1alpha1 -> v1beta1 -> v1) 및 변환 웹훅을 사용하세요.
연산자를 빌드하지 말아야 할 경우
운영자는 상당한 개발 및 유지 관리 비용을 부담합니다. 하나 만들어 보세요 (1) 해당 애플리케이션에 대해 OperatorHub에 성숙한 Operator가 있는 경우에만 의미가 있습니다. 당신이 관리하고 있다면 그것을 사용하세요. (2) 애플리케이션이 복잡하고 상태를 저장하며 지식이 필요합니다. 자동화할 전문 작업; (3) 유지 관리할 수 있는 전담 팀이 있습니다. 시간이 지남에 따라 코드를 작성하십시오. 간단한 배포의 경우에는 필요하지 않습니다.
결론 및 다음 단계
Kubernetes Operator 패턴은 Kubernetes의 선언적 철학을 자연스럽게 확장한 것입니다. Kubernetes를 복잡한 애플리케이션 도메인으로. 데이터베이스를 수동으로 관리하는 대신, 메시징 시스템 및 상태 저장 서비스를 통해 운영 지식을 코드화합니다. 24시간 연중무휴로 작동하여 시스템을 원하는 상태로 유지하는 컨트롤러입니다.
Kubebuilder와 Operator SDK는 프로젝트 스캐폴딩, 관리 등의 기반을 제공합니다. 서버 API의 프레임워크를 조정합니다. 하지만 비즈니스 로직 - 관리 방법 PostgreSQL 장애 조치, Kafka 클러스터 확장 방법, TLS 인증서 교체 방법 - 특정 애플리케이션에 대한 깊은 지식을 바탕으로 구현해야 합니다.
Kubernetes at Scale 시리즈의 향후 기사
관련 자료 및 시리즈
- Kubernetes 네트워킹: CNI, Cilium(eBPF 포함)
- Kubernetes의 영구 스토리지
- MLOps: Kubernetes에서 ML 확장 — 훈련 파이프라인을 위한 연산자







