Писал ли операторы для Kubernetes?

«Писал ли операторы для Kubernetes?» — вопрос из категории Kubernetes, который задают на 23% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, разрабатывал кастомные операторы на Go с использованием Kubebuilder для управления состоянием специфичных для компании ресурсов. Оператор — это предпочтительный способ автоматизации сложной логики в k8s, которую нельзя описать стандартными контроллерами.

Мой опыт включает:

  1. Оператор для управления сетевыми политиками Calico: Автоматически создавал NetworkPolicy на основе аннотаций в Deployment'ах. Оператор следил за Deployments с аннотацией netpolicy.company.com/enabled: "true" и генерировал соответствующие политики, изолирующие трафик.
  2. Оператор для канареечного деплоя: Управлял канареечными релизами, постепенно переводя трафик с одного Deployment на другой на основе анализа метрик из Prometheus (например, процент ошибок HTTP 5xx).

Ключевые аспекты разработки:

  • Определение Custom Resource Definition (CRD): Создание CRD, например, CanaryDeployment, с полями spec.image, spec.trafficStep.
  • Реализация reconcile-цикла: Логика в контроллере, которая приводит фактическое состояние кластера (существующие Deployments, Services) к желаемому, описанному в CR.
  • Обработка ошибок и повторные попытки: Корректная настройка RequeueAfter при временных ошибках.
  • Тестирование: Использование envtest от Kubebuilder для модульного тестирования логики контроллера.

Упрощенный фрагмент reconcile-логики для оператора NetworkPolicy:

func (r *NetworkPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var dep appsv1.Deployment
    if err := r.Get(ctx, req.NamespacedName, &dep); err != nil {
        // Обработка удаления Deployment
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // Проверяем аннотацию
    if dep.Annotations["netpolicy.company.com/enabled"] != "true" {
        // Удаляем созданную ранее NetworkPolicy, если она есть
        return ctrl.Result{}, r.cleanupNetworkPolicy(ctx, dep)
    }

    // Создаем или обновляем NetworkPolicy
    desiredPolicy := buildNetworkPolicy(&dep)
    if err := r.createOrUpdatePolicy(ctx, desiredPolicy); err != nil {
        // В случае ошибки просим перезапустить reconcile через 10 секунд
        return ctrl.Result{RequeueAfter: 10 * time.Second}, err
    }

    return ctrl.Result{}, nil
}