Урок 3.2: Проектирование вашего API

Введение

Хорошо спроектированный API критически важен для качественного оператора. API вашего пользовательского ресурса — это то, с чем взаимодействуют пользователи; он должен быть интуитивным, валидируемым и следовать соглашениям Kubernetes. В этом уроке вы научитесь проектировать API, которые одновременно мощны и удобны для пользователя.

Теория: принципы проектирования API

Хороший дизайн API делает операторы интуитивно понятными в использовании и сопровождении. Следование соглашениям Kubernetes обеспечивает согласованность и совместимость с инструментами.

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

Разделение Spec и Status:

  • Spec: желаемое состояние, задаваемое пользователем (неизменяемое после создания)
  • Status: фактическое состояние, управляемое системой (только для чтения для пользователей)
  • Чёткое разделение предотвращает конфликты и путаницу

Версионирование API:

  • Одновременная поддержка нескольких версий API
  • Обеспечение плавных миграций
  • Следование соглашениям Kubernetes о версионировании

Валидация:

  • Валидация на уровне API (схема CRD)
  • Понятные сообщения об ошибках
  • Раннее предотвращение некорректных состояний

Почему хороший дизайн API важен:

  • Удобство использования: интуитивные API проще в использовании
  • Сопровождаемость: хорошо спроектированные API легче развивать
  • Совместимость: следование соглашениям обеспечивает совместимость с инструментами
  • Надёжность: валидация предотвращает ошибки во время выполнения

Хороший дизайн API — это основа успешного оператора.

Принципы проектирования API

Хороший дизайн API следует этим принципам:

graph TB
    API[API Design] --> CLEAR[Clear & Intuitive]
    API --> VALIDATED[Well Validated]
    API --> CONVENTIONAL[Follows Conventions]
    API --> VERSIONED[Properly Versioned]
    API --> DOCUMENTED[Well Documented]
    
    style CLEAR fill:#90EE90
    style VALIDATED fill:#FFB6C1

Разделение Spec и Status

Вспомните из Модуля 1 и Модуля 2: Spec — это желаемое состояние, Status — фактическое.

graph LR
    CR[Custom Resource] --> SPEC[spec]
    CR --> STATUS[status]
    
    SPEC --> USER[User Writes]
    STATUS --> CONTROLLER[Controller Writes]
    
    SPEC --> DESIRED[Desired State]
    STATUS --> ACTUAL[Actual State]
    
    style SPEC fill:#90EE90
    style STATUS fill:#FFB6C1

Рекомендации по Spec

Что помещается в Spec:

  • Настраиваемые пользователем параметры
  • Желаемая конфигурация
  • Требования к ресурсам
  • Настройки развёртывания

Пример:

type DatabaseSpec struct {
    // Image is the PostgreSQL image to use
    Image string `json:"image"`
    
    // Replicas is the number of database replicas
    Replicas int32 `json:"replicas"`
    
    // Storage is the storage configuration
    Storage StorageSpec `json:"storage"`
}

Рекомендации по Status

Что помещается в Status:

  • Информация о текущем состоянии
  • Индикаторы прогресса
  • Условия (conditions)
  • Наблюдаемое поколение (observed generation)

Пример:

type DatabaseStatus struct {
    // Phase is the current phase
    Phase string `json:"phase,omitempty"`
    
    // Ready indicates if the database is ready
    Ready bool `json:"ready,omitempty"`
    
    // Conditions represent the latest observations
    Conditions []Condition `json:"conditions,omitempty"`
}

Соглашения об именовании

Следуйте соглашениям Kubernetes об именовании:

graph TB
    NAMING[Naming] --> RESOURCE[Resource Names]
    NAMING --> FIELDS[Field Names]
    NAMING --> GROUPS[API Groups]
    
    RESOURCE --> PLURAL[Plural: databases]
    RESOURCE --> SINGULAR[Singular: database]
    RESOURCE --> KIND[Kind: Database]
    
    FIELDS --> CAMELCASE[CamelCase: imageName]
    FIELDS --> DESCRIPTIVE[Descriptive: postgresImage]
    
    GROUPS --> DOMAIN[Domain: example.com]
    GROUPS --> VERSION[Version: v1]
    
    style PLURAL fill:#90EE90
    style KIND fill:#FFB6C1

Именование ресурсов

  • Множественное число (Plural): databases (в нижнем регистре, множественное число)
  • Единственное число (Singular): database (в нижнем регистре, единственное число)
  • Kind: Database (PascalCase, единственное число)
  • Короткое имя (Short name): db (опционально, в нижнем регистре)

Именование полей

  • Используйте camelCase: imageName, replicaCount
  • Будьте описательны: postgresImage, а не img
  • Используйте единообразное именование для всех ресурсов

Версионирование API

API должны версионироваться правильно:

graph LR
    V1[v1] --> V2[v1beta1]
    V2 --> V3[v1beta2]
    V3 --> STABLE[v1]
    
    style V1 fill:#90EE90
    style STABLE fill:#FFB6C1

Стратегия версий

  • v1: стабильная, готова к продакшену
  • v1beta1: бета, может меняться
  • v1alpha1: альфа, экспериментальная

Правила версионирования

  1. Начинайте с v1alpha1 для новых API
  2. Повышайте до v1beta1, когда API стабилен
  3. Повышайте до v1, когда API готов к продакшену
  4. Поддерживайте несколько версий во время перехода

Валидация с помощью маркеров

Маркеры kubebuilder обеспечивают валидацию:

graph TB
    MARKER[Marker] --> VALIDATION[Validation Rule]
    MARKER --> DOC[Documentation]
    MARKER --> DISPLAY[Display Column]
    
    VALIDATION --> REQUIRED[Required]
    VALIDATION --> MINMAX[Min/Max]
    VALIDATION --> PATTERN[Pattern]
    VALIDATION --> ENUM[Enum]
    
    style VALIDATION fill:#FFB6C1

Распространённые маркеры валидации

Обязательные поля:

// +kubebuilder:validation:Required
Message string `json:"message"`

Числовые диапазоны:

// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=10
Replicas int32 `json:"replicas"`

Шаблоны строк:

// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
Name string `json:"name"`

Перечисления (Enums):

// +kubebuilder:validation:Enum=small;medium;large
Size string `json:"size"`

Значения по умолчанию

Предоставляйте разумные значения по умолчанию:

graph LR
    USER[User Creates] --> DEFAULT{Has Default?}
    DEFAULT -->|Yes| APPLY[Apply Default]
    DEFAULT -->|No| VALIDATE[Validate Required]
    APPLY --> VALIDATE
    
    style APPLY fill:#90EE90

Установка значений по умолчанию

В коде Go:

// Set defaults in webhook (Module 5)
func (r *Database) Default() {
    if r.Spec.Image == "" {
        r.Spec.Image = "postgres:14"
    }
    if r.Spec.Replicas == 0 {
        r.Spec.Replicas = 1
    }
}

С помощью маркеров:

// +kubebuilder:default="postgres:14"
Image string `json:"image,omitempty"`

Пример: проектирование API базы данных

Спроектируем API для оператора PostgreSQL:

// DatabaseSpec defines the desired state of Database
type DatabaseSpec struct {
    // Image is the PostgreSQL image to use
    // +kubebuilder:validation:Required
    // +kubebuilder:default="postgres:14"
    Image string `json:"image"`
    
    // Replicas is the number of database replicas
    // +kubebuilder:validation:Minimum=1
    // +kubebuilder:validation:Maximum=10
    // +kubebuilder:default=1
    Replicas int32 `json:"replicas,omitempty"`
    
    // Storage is the storage configuration
    Storage StorageSpec `json:"storage"`
    
    // Resources are the resource requirements
    Resources corev1.ResourceRequirements `json:"resources,omitempty"`
}

// StorageSpec defines storage configuration
type StorageSpec struct {
    // Size is the storage size
    // +kubebuilder:validation:Required
    Size string `json:"size"`
    
    // StorageClass is the storage class to use
    StorageClass string `json:"storageClass,omitempty"`
}

// DatabaseStatus defines the observed state of Database
type DatabaseStatus struct {
    // Phase is the current phase
    // +kubebuilder:validation:Enum=Pending;Creating;Ready;Failed
    Phase string `json:"phase,omitempty"`
    
    // Ready indicates if the database is ready
    Ready bool `json:"ready,omitempty"`
    
    // Conditions represent the latest observations
    Conditions []metav1.Condition `json:"conditions,omitempty"`
    
    // Endpoint is the database endpoint
    Endpoint string `json:"endpoint,omitempty"`
}

Столбцы вывода (Print Columns)

Добавьте столбцы вывода для более информативного результата kubectl get:

// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas"
// +kubebuilder:printcolumn:name="Ready",type="boolean",JSONPath=".status.ready"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type Database struct {
    // ...
}

Это заставит kubectl get databases показывать полезные столбцы!

Процесс проектирования API

flowchart TD
    START[Design API] --> IDENTIFY[Identify Use Cases]
    IDENTIFY --> SPEC[Design Spec]
    SPEC --> STATUS[Design Status]
    STATUS --> VALIDATE[Add Validation]
    VALIDATE --> TEST[Test API]
    TEST --> ITERATE{Good?}
    ITERATE -->|No| SPEC
    ITERATE -->|Yes| DOCUMENT[Document]
    
    style SPEC fill:#90EE90
    style STATUS fill:#FFB6C1

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

  • Spec = желаемое состояние (пишет пользователь)
  • Status = фактическое состояние (пишет контроллер)
  • Следуйте соглашениям Kubernetes об именовании
  • Используйте правильное версионирование (v1alpha1 → v1beta1 → v1)
  • Добавляйте маркеры валидации для безопасности
  • Предоставляйте разумные значения по умолчанию
  • Добавляйте столбцы вывода для лучшего UX
  • Документируйте свой API как следует

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

При проектировании API:

  • Думайте об опыте пользователя
  • Валидируйте всё, что возможно
  • Чётко разделяйте spec и status
  • Правильно версионируйте свои API
  • Следуйте соглашениям Kubernetes
  • Делайте API интуитивным

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

Источники

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

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

Смежные темы

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

Теперь, когда вы знаете, как проектировать API, давайте реализуем логику согласования, которая их использует.