Урок 3.4: Работа с Client-Go

Введение

Клиент Kubernetes — это ваш интерфейс к API-серверу. Умение эффективно его использовать критически важно для создания производительных операторов. В этом уроке вы изучите продвинутые операции клиента, стратегии патчинга и способы обработки конкурентного доступа.

Теория: работа с Client-Go

Client-go предоставляет низкоуровневый доступ к API Kubernetes, а controller-runtime строит на нём абстракции более высокого уровня.

Основные концепции

Типизированные и динамические клиенты:

  • Типизированный (Typed): типобезопасность, проверка на этапе компиляции, лучшая производительность
  • Динамический (Dynamic): проверка типов во время выполнения, гибкость, медленнее
  • Выбирайте в зависимости от сценария использования

Механизм отслеживания (Watch):

  • Долгоживущие соединения для уведомлений об изменениях
  • Эффективнее опроса (polling)
  • Автоматически обрабатывает переподключение
  • Используется информерами и контроллерами

Операции патчинга (Patch):

  • Strategic merge patch: слияние с учётом особенностей Kubernetes
  • JSON merge patch: стандартный патчинг JSON
  • JSON patch: точечное обновление полей
  • Выбирайте в зависимости от потребностей обновления

Почему Client-Go важен:

  • Производительность: эффективное взаимодействие с API
  • Контроль: тонкий контроль над операциями
  • Совместимость: работает со всеми версиями Kubernetes
  • Основа: controller-runtime построен на нём

Понимание client-go помогает оптимизировать производительность оператора и обрабатывать граничные случаи.

Типы клиентов

Существуют разные способы взаимодействия с API Kubernetes:

graph TB
    CLIENT[Client Options]
    
    CLIENT --> TYPED[Typed Client<br/>controller-runtime]
    CLIENT --> DYNAMIC[Dynamic Client<br/>unstructured]
    CLIENT --> REST[REST Client<br/>direct API calls]
    
    TYPED --> SAFE[Type Safe]
    TYPED --> CACHE[Uses Cache]
    TYPED --> RECOMMENDED[Recommended]
    
    DYNAMIC --> FLEXIBLE[Flexible]
    DYNAMIC --> NO_CACHE[No Cache]
    
    REST --> CONTROL[Full Control]
    REST --> MANUAL[Manual]
    
    style TYPED fill:#90EE90
    style RECOMMENDED fill:#FFB6C1

Типизированный клиент (рекомендуется)

Типизированный клиент из controller-runtime — это то, что вы уже использовали:

type DatabaseReconciler struct {
    client.Client  // Typed client
    Scheme *runtime.Scheme
}

Преимущества:

  • Типобезопасность
  • Использует кеш (быстрее)
  • Обрабатывает события отслеживания (watch)
  • Автоматические повторы

Чтение ресурсов

Получение одного ресурса (Get)

// Get a specific resource
statefulSet := &appsv1.StatefulSet{}
err := r.Get(ctx, client.ObjectKey{
    Name:      "my-db",
    Namespace: "default",
}, statefulSet)

if errors.IsNotFound(err) {
    // Resource doesn't exist
}

Получение списка ресурсов (List)

// List all StatefulSets in namespace
statefulSetList := &appsv1.StatefulSetList{}
err := r.List(ctx, statefulSetList, client.InNamespace("default"))

// Filter by labels
err := r.List(ctx, statefulSetList, 
    client.InNamespace("default"),
    client.MatchingLabels{"app": "database"})

Создание ресурсов

// Create a resource
statefulSet := &appsv1.StatefulSet{
    ObjectMeta: metav1.ObjectMeta{
        Name:      "my-db",
        Namespace: "default",
    },
    Spec: appsv1.StatefulSetSpec{
        // ... spec
    },
}

err := r.Create(ctx, statefulSet)

Обновление ресурсов

Полное обновление

// Update entire resource
statefulSet.Spec.Replicas = &replicas
err := r.Update(ctx, statefulSet)

Обновление статуса

// Update only status (uses status subresource)
db.Status.Phase = "Ready"
err := r.Status().Update(ctx, db)

Стратегии патчинга

Иногда нужно обновить только определённые поля. Используйте патчи:

graph TB
    PATCH[Patch Operation]
    
    PATCH --> MERGE[Merge Patch]
    PATCH --> STRATEGIC[Strategic Merge]
    PATCH --> JSON[JSON Patch]
    
    MERGE --> SIMPLE[Simple merge]
    STRATEGIC --> K8S[Kubernetes aware]
    JSON --> PRECISE[Precise control]
    
    style STRATEGIC fill:#90EE90

Strategic Merge Patch

// Patch specific fields
patch := client.MergeFrom(statefulSet.DeepCopy())
statefulSet.Spec.Replicas = &newReplicas

err := r.Patch(ctx, statefulSet, patch)

JSON Patch

// More precise control
patch := []byte(`[
    {"op": "replace", "path": "/spec/replicas", "value": 3}
]`)

err := r.Patch(ctx, statefulSet, client.RawPatch(types.JSONPatchType, patch))

Отслеживание ресурсов

Отслеживайте изменения ресурсов:

sequenceDiagram
    participant Controller
    participant Watch as Watch Interface
    participant API as API Server
    
    Controller->>Watch: Start Watch
    Watch->>API: Watch Request
    API->>Watch: Event Stream
    Watch->>Controller: Event (ADD/UPDATE/DELETE)
    Controller->>Controller: Handle Event

Настройка отслеживания

func (r *DatabaseReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&databasev1.Database{}).
        Owns(&appsv1.StatefulSet{}).  // Watch owned StatefulSets
        Watches(
            &source.Kind{Type: &corev1.Secret{}},
            &handler.EnqueueRequestForObject{},
        ).
        Complete(r)
}

Оптимистичное управление конкурентным доступом

Kubernetes использует версии ресурсов для предотвращения конфликтов:

sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant API as API Server
    
    C1->>API: GET (rv: 100)
    C2->>API: GET (rv: 100)
    API-->>C1: Resource (rv: 100)
    API-->>C2: Resource (rv: 100)
    C1->>API: UPDATE (rv: 100)
    API-->>C1: Success (rv: 101)
    C2->>API: UPDATE (rv: 100)
    API-->>C2: Conflict (rv mismatch)
    
    Note over C2: Conflict detected

Обработка конфликтов

// Retry on conflict
for retries := 0; retries < 3; retries++ {
    err := r.Get(ctx, key, resource)
    if err != nil {
        return err
    }
    
    // Modify resource
    resource.Spec.Replicas = &newReplicas
    
    err = r.Update(ctx, resource)
    if err == nil {
        return nil  // Success
    }
    
    if !errors.IsConflict(err) {
        return err  // Non-conflict error
    }
    
    // Conflict - retry
    time.Sleep(100 * time.Millisecond)
}

Фильтрация и поиск

По пространству имён

// List resources in namespace
r.List(ctx, list, client.InNamespace("production"))

По меткам

// Match labels
r.List(ctx, list, client.MatchingLabels{
    "app": "database",
    "env": "prod",
})

// Match label selector
selector := labels.SelectorFromSet(labels.Set{"app": "database"})
r.List(ctx, list, client.MatchingLabelsSelector{Selector: selector})

По полям

// Match specific field
r.List(ctx, list, client.MatchingFields{
    "metadata.name": "my-db",
})

Селекторы полей (Field Selectors)

Используйте селекторы полей для эффективных запросов:

// Find StatefulSets owned by Database
r.List(ctx, &appsv1.StatefulSetList{},
    client.InNamespace("default"),
    client.MatchingFields{
        ".metadata.ownerReferences[0].kind": "Database",
        ".metadata.ownerReferences[0].name": db.Name,
    })

Лучшие практики

1. Используйте типизированный клиент

// Good: Type-safe
r.Get(ctx, key, &appsv1.StatefulSet{})

// Avoid: Dynamic client unless necessary

2. Задействуйте кеш

// Client uses cache automatically
// No need to worry about it
r.Get(ctx, key, resource)  // Uses cache

3. Правильно обрабатывайте ошибки

if errors.IsNotFound(err) {
    // Handle not found
} else if errors.IsConflict(err) {
    // Handle conflict
} else {
    // Handle other errors
}

4. Используйте контекст

// Always use context for cancellation
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

r.Get(ctx, key, resource)

Ключевые выводы

  • Типизированный клиент рекомендуется (типобезопасный, кешируется)
  • Get для одиночных ресурсов, List для нескольких
  • Используйте Patch для частичных обновлений
  • Watch для обновлений в реальном времени
  • Обрабатывайте конфликты через повторы
  • Используйте фильтры для эффективных запросов
  • Всегда используйте контекст для отмены

Что нужно понимать для создания операторов

При работе с клиентами:

  • Предпочитайте типизированный клиент динамическому
  • Используйте List с фильтрами для эффективности
  • Аккуратно обрабатывайте конфликты
  • Используйте патчи для частичных обновлений
  • Настраивайте отслеживание зависимых ресурсов
  • Всегда правильно обрабатывайте ошибки

Связанная лабораторная работа

Источники

Официальная документация

Дополнительное чтение

Смежные темы

Дальнейшие шаги

Поздравляем! Вы завершили Модуль 3. Теперь вы понимаете:

  • Архитектуру controller-runtime
  • Принципы проектирования API
  • Логику согласования
  • Продвинутые операции клиента

В Модуле 4 вы изучите продвинутые паттерны согласования, такие как условия (conditions), финализаторы и многофазное согласование.