В этой статье шаг за шагом описывается процесс создания простого оператора Kubernetes, который копирует Secret из одного неймспейса в другие.
1К открытий6К показов
В этой статье шаг за шагом описывается процесс создания простого оператора Kubernetes, который копирует Secret из одного неймспейса в другие. Также немного рассказывается о том, как проверить, что созданный оператор работает, и как отладить код, если что-то пошло не так.
Данное руководство исключительно практическое и предполагается, что с основными абстракциями Kubernetes вы уже знакомы.
Оператор будет отслеживать появление объекта типа secretsync. При появлении в кластере такого объекта оператор будет копировать секрет с именем SecretName из неймспейса SourceNamespace в неймспейсы из списка DestinationNamespace. После чего у объекта secretsync обновляется поле статуса LastSyncTime.
1. Создать и инициализировать проект оператора с помощью kubebuilder.
Обратите внимание, что в последней команде указан --kind SecretSync. Это значит, что имя нового CRD (custom resource definition) будет SecretSync.
mkdir -p secretsync
cd secretsync
kubebuilder init --domain example.com --repo=example.com/operator
kubebuilder create api --group apps --version v1 --kind SecretSync
На вопросы в последней команде ответить y:
INFO Create Resource [y/n]
y
INFO Create Controller [y/n]
y
2. Определить поля Spec и Status нового CRD
Для этого в файле secretsync_types.go отредактировать структуры.
func (r *SecretSyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx).WithValues("secretsync", req.NamespacedName)
// Fetch the SecretSync instance
secretSync := &appsv1.SecretSync{}
if err := r.Get(ctx, req.NamespacedName, secretSync); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Fetch the source Secret
sourceSecret := &corev1.Secret{}
sourceSecretName := types.NamespacedName{
Namespace: secretSync.Spec.SourceNamespace,
Name: secretSync.Spec.SecretName,
}
if err := r.Get(ctx, sourceSecretName, sourceSecret); err != nil {
log.Error(err, "Unable to get source secret", "SecretName", secretSync.Spec.SecretName, "SourceNamespace", secretSync.Spec.SourceNamespace)
return ctrl.Result{}, err
}
log.Info("Got source Secret in source namespace", "SecretName", secretSync.Spec.SecretName, "SourceNamespace", secretSync.Spec.SourceNamespace)
// Create or Update the destination Secrets in the target namespaces
for _, destinationSecretNamespace := range secretSync.Spec.DestinationNamespaces {
destinationSecret := &corev1.Secret{}
destinationSecretName := types.NamespacedName{
Namespace: destinationSecretNamespace,
Name: secretSync.Spec.SecretName,
}
log.Info("Looking for Secret in destination namespace", "Namespace", destinationSecretNamespace, "SecretName", secretSync.Spec.SecretName)
if err := r.Get(ctx, destinationSecretName, destinationSecret); err != nil {
if errors.IsNotFound(err) {
log.Info("Creating Secret in destination namespace", "Namespace", destinationSecretNamespace)
destinationSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretSync.Spec.SecretName,
Namespace: destinationSecretNamespace,
},
Data: sourceSecret.Data, // Copy data from source to destination
}
if err := r.Create(ctx, destinationSecret); err != nil {
return ctrl.Result{}, err
}
} else {
return ctrl.Result{}, err
}
} else {
log.Info("Updating Secret in destination namespace", "Namespace", destinationSecretNamespace)
destinationSecret.Data = sourceSecret.Data // Update data from source to destination
if err := r.Update(ctx, destinationSecret); err != nil {
log.Error(err, "Unable to update secretsync")
return ctrl.Result{}, err
}
}
}
secretSync.Status.LastSyncTime = metav1.Now()
if err := r.Status().Update(ctx, secretSync); err != nil {
log.Error(err, "Unable to update secretsync status")
return ctrl.Result{}, err
}
log.Info("Status secretsync updated", "LastSyncTime", secretSync.Status.LastSyncTime)
return ctrl.Result{}, nil
}
9. Собрать образ контроллера
Можно образ положить в локальный докер (но потом нужно будет руками, скопировать в kubernetes) или в докер хаб (нужно быть залогиненным в hub.docker.com в командной строке через docker login, после билда сделать make docker-push IMG=secretsync:1.0.0).
elena@elena-ABC:~/secretsync$ kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
secretsync-system secretsync-controller-manager-655c4b8bf4-jknt4 2/2 Running 0 7m32s
elena@elena-ABC:~/secretsync$ kubectl get secret -A
NAMESPACE NAME TYPE DATA AGE
default example-secret Opaque 2 138m
development example-secret Opaque 2 3m53s
production example-secret Opaque 2 3m53s
9. Если в логах есть ошибки, то можно их исправить и проверить работу оператора без деплоя перед тем, как собирать образ
make run
10. Откатить деплой оператора и CRD
make undeploy
Отладка
1. Убедиться, что в папке ~/.kube есть файл config c конфигом kubernetes
2. В VS Code при начале отладки по F5 можно добавить launch.json конфиг
3. Поставить брейкпойнт, например, в Reconcile методе и начать отладку по F5 (Run/Start Debugging)
Заключение
Эта статья появилась во многом по мотивам этого отличного материала, в котором мне немного не хватило подробностей и части про проверку работы оператора и отладку.
Если хочется посмотреть, каким может быть оператор Kubernetes, то вот пример оператора для Postgres.