Лабораторная 7.4: Оптимизация производительности
Связанный урок: Урок 7.4: Производительность и масштабируемость
Навигация: ← Предыдущая лабораторная: HA | Обзор модуля
Цели
- Реализовать ограничение частоты (rate limiting)
- Добавить стратегии кеширования
- Оптимизировать согласование
- Профилировать и оптимизировать производительность
Предварительные требования
- Завершение Лабораторной 7.3
- Оператор с настройкой HA
- Понимание концепций производительности
Упражнение 1: настройка ограничения частоты контроллера
У Controller-runtime (используемого kubebuilder) есть встроенное ограничение частоты. Настроим его.
Задача 1.1: настройте MaxConcurrentReconciles
Обновите SetupWithManager вашего контроллера в internal/controller/database_controller.go:
import (
"time"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func (r *DatabaseReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&databasev1.Database{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Service{}).
Owns(&corev1.Secret{}).
WithOptions(controller.Options{
// Limit concurrent reconciliations
MaxConcurrentReconciles: 2,
// Custom rate limiter for requeue (typed for controller-runtime v0.19+)
RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](
time.Millisecond*5, // Base delay
time.Second*1000, // Max delay
),
}).
Complete(r)
}
Задача 1.2: добавьте ограничение частоты для внешних вызовов API (опционально)
Если ваш оператор вызывает внешние API, добавьте ограничение частоты:
import (
"golang.org/x/time/rate"
)
type DatabaseReconciler struct {
client.Client
Scheme *runtime.Scheme
APILimiter *rate.Limiter // For external API calls
}
// In cmd/main.go when creating the reconciler:
if err = (&controller.DatabaseReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
APILimiter: rate.NewLimiter(rate.Limit(10), 1), // 10 req/sec
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Database")
os.Exit(1)
}
// In Reconcile, use before external calls:
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// Wait for rate limiter before external API calls
if err := r.APILimiter.Wait(ctx); err != nil {
return ctrl.Result{}, err
}
// ... reconciliation with external API calls ...
}
Упражнение 2: добавление индексирования полей для быстрого поиска
Controller-runtime обеспечивает автоматическое кеширование. Вы можете добавить пользовательские индексы для быстрого поиска.
Задача 2.1: создайте индексатор полей
Добавьте индексирование в cmd/main.go до запуска менеджера:
// In cmd/main.go, after creating manager but before SetupWithManager
// Index databases by environment for fast filtering
if err := mgr.GetFieldIndexer().IndexField(
context.Background(),
&databasev1.Database{},
"spec.environment",
func(obj client.Object) []string {
db := obj.(*databasev1.Database)
if db.Spec.Environment == "" {
return nil
}
return []string{db.Spec.Environment}
},
); err != nil {
setupLog.Error(err, "unable to create field index")
os.Exit(1)
}
Задача 2.2: используйте индексы в контроллере
// In your reconciler, use MatchingFields for indexed queries
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// Fast lookup using index
prodDatabases := &databasev1.DatabaseList{}
if err := r.List(ctx, prodDatabases, client.MatchingFields{
"spec.environment": "production",
}); err != nil {
return ctrl.Result{}, err
}
// Use namespace selector for namespace-scoped queries
nsDatabases := &databasev1.DatabaseList{}
if err := r.List(ctx, nsDatabases, client.InNamespace(req.Namespace)); err != nil {
return ctrl.Result{}, err
}
// ... rest of reconciliation
}
Упражнение 3: оптимизация согласования
Задача 3.1: пакетные операции
func (r *DatabaseReconciler) reconcileBatch(ctx context.Context, databases []databasev1.Database) error {
// Group by operation
var toCreate, toUpdate []databasev1.Database
for _, db := range databases {
if db.Status.Phase == "" {
toCreate = append(toCreate, db)
} else {
toUpdate = append(toUpdate, db)
}
}
// Batch create
for _, db := range toCreate {
if err := r.reconcileDatabase(ctx, &db); err != nil {
return err
}
}
// Batch update
for _, db := range toUpdate {
if err := r.reconcileDatabase(ctx, &db); err != nil {
return err
}
}
return nil
}
Упражнение 4: мониторинг производительности со встроенными метриками
Controller-runtime автоматически экспонирует метрики. У вашего postgres-operator уже настроены пользовательские метрики!
Задача 4.1: изучите существующий код метрик
У вашего оператора уже есть метрики в internal/controller/metrics.go:
cd ~/postgres-operator
# Review the metrics file
cat internal/controller/metrics.go
Вы должны увидеть уже определённые пользовательские метрики:
var (
// ReconcileTotal counts the total number of reconciliations
ReconcileTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "database_reconcile_total",
Help: "Total number of reconciliations per controller",
},
[]string{"result"}, // success, error, requeue
)
// ReconcileDuration measures the duration of reconciliations
ReconcileDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "database_reconcile_duration_seconds",
Help: "Duration of reconciliations in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"result"},
)
// DatabasesTotal tracks the current number of Database resources
DatabasesTotal = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "database_resources_total",
Help: "Current number of Database resources by phase",
},
[]string{"phase"},
)
// DatabaseInfo provides information about each database
DatabaseInfo = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "database_info",
Help: "Information about Database resources",
},
[]string{"name", "namespace", "image", "phase"},
)
)
func init() {
// Register custom metrics with the global registry
metrics.Registry.MustRegister(
ReconcileTotal,
ReconcileDuration,
DatabasesTotal,
DatabaseInfo,
)
}
Задача 4.2: изучите использование метрик в контроллере
Проверьте, как метрики используются в функции Reconcile:
# See how metrics are recorded in the reconcile loop
grep -A 10 "Defer metrics" internal/controller/database_controller.go
Вы должны увидеть:
// Defer metrics recording
defer func() {
duration := time.Since(start).Seconds()
ReconcileDuration.WithLabelValues(reconcileResult).Observe(duration)
ReconcileTotal.WithLabelValues(reconcileResult).Inc()
}()
И установку метрик информации о базе данных:
DatabaseInfo.WithLabelValues(
db.Name,
db.Namespace,
db.Spec.Image,
db.Status.Phase,
).Set(1)
Задача 4.3: доступ к эндпоинту метрик
Эндпоинт метрик требует аутентификации с bearer-токеном. Мы используем токен ServiceAccount для аутентификации.
# For Docker: Build and Deploy the operator with network policies enabled
make docker-build IMG=postgres-operator:latest
kind load docker-image postgres-operator:latest --name k8s-operators-course
make deploy IMG=postgres-operator:latest
# For Podman: Build and Deploy operator - use localhost/ prefix to match the loaded image
make docker-build IMG=postgres-operator:latest CONTAINER_TOOL=podman
podman save localhost/postgres-operator:latest -o /tmp/postgres-operator.tar
kind load image-archive /tmp/postgres-operator.tar --name k8s-operators-course
rm /tmp/postgres-operator.tar
make deploy IMG=localhost/postgres-operator:latest
# Restart operator if already deployed
kubectl rollout restart deploy -n postgres-operator-system postgres-operator-controller-manager
# Port forward to metrics endpoint (using HTTPS on port 8443)
kubectl port-forward -n postgres-operator-system \
svc/postgres-operator-controller-manager-metrics-service 8443:8443 &
# Get a token for authentication (use the controller-manager service account)
TOKEN=$(kubectl create token postgres-operator-controller-manager -n postgres-operator-system)
# create database to generate some metrics
kubectl apply -f - <<EOF
apiVersion: database.example.com/v1
kind: Database
metadata:
name: valid-db
spec:
image: postgres:14
replicas: 1
databaseName: mydb
username: admin
storage:
size: 10Gi
EOF
# View custom database metrics (using -k for self-signed cert, -H for auth header)
curl -sk -H "Authorization: Bearer $TOKEN" https://localhost:8443/metrics | grep database_
# View reconciliation metrics
curl -sk -H "Authorization: Bearer $TOKEN" https://localhost:8443/metrics | grep database_reconcile
# View controller-runtime built-in metrics
curl -sk -H "Authorization: Bearer $TOKEN" https://localhost:8443/metrics | grep controller_runtime
# View all metrics
curl -sk -H "Authorization: Bearer $TOKEN" https://localhost:8443/metrics | head -100
# Stop port-forward
pkill -f "port-forward.*8443"
Примечание: эндпоинт метрик использует RBAC Kubernetes для авторизации. У ServiceAccount должна быть ClusterRole metrics-reader (настроена в Лабораторной 7.2).
Задача 4.4: просмотр метрик в Prometheus
Если у вас настроен Prometheus (из Лабораторной 7.2), просмотрите метрики там:
# Port forward to Prometheus
kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090 &
# Open http://localhost:9090 and query:
# - database_reconcile_total
# - database_reconcile_duration_seconds
# - database_resources_total
# - database_info
# Stop port-forward when done
pkill -f "port-forward.*9090"
Примеры запросов Prometheus:
| Запрос | Описание |
|---|---|
database_reconcile_total |
Всего согласований по результату |
rate(database_reconcile_total[5m]) |
Согласований в секунду |
database_reconcile_duration_seconds_bucket |
Гистограмма задержки согласования |
histogram_quantile(0.99, rate(database_reconcile_duration_seconds_bucket[5m])) |
Задержка p99 |
database_resources_total |
Текущие базы данных по фазе |
database_info |
Информация о каждой базе данных |
Упражнение 5: нагрузочное тестирование
Задача 5.1: создайте много ресурсов
# Create multiple databases for load testing
for i in {1..50}; do
kubectl apply -f - <<EOF
apiVersion: database.example.com/v1
kind: Database
metadata:
name: test-db-$i
namespace: default
spec:
image: postgres:14
replicas: 1
databaseName: db$i
username: admin
storage:
size: 1Gi
EOF
done
echo "Created 50 test databases"
Задача 5.2: отслеживайте производительность под нагрузкой
Примечание: kubectl top требует установленного metrics-server. Скрипт настройки курса (scripts/setup-kind-cluster.sh) устанавливает его автоматически. Если вы получаете «Metrics API not available», установите его вручную:
# Install metrics-server (if not already installed)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Patch for kind (disable TLS verification for kubelet)
kubectl patch deployment metrics-server -n kube-system --type='json' -p='[
{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"},
{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-preferred-address-types=InternalIP"}
]'
# Wait for it to be ready
kubectl rollout status deployment/metrics-server -n kube-system
Теперь отслеживайте производительность:
# Watch operator resource usage (requires metrics-server)
watch kubectl top pods -n postgres-operator-system -l control-plane=controller-manager
# Alternative: Check resource requests/limits if metrics-server not available
kubectl get pods -n postgres-operator-system -l control-plane=controller-manager -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].resources}{"\n"}{end}'
# In another terminal, watch reconciliation metrics
# Port forward to metrics endpoint (using HTTPS on port 8443)
kubectl port-forward -n postgres-operator-system \
svc/postgres-operator-controller-manager-metrics-service 8443:8443 &
# Get a token for authentication (use the controller-manager service account)
TOKEN=$(kubectl create token postgres-operator-controller-manager -n postgres-operator-system)
while true; do
curl -sk -H "Authorization: Bearer $TOKEN" https://localhost:8443/metrics 2>/dev/null | grep database_reconcile_total
sleep 5
done
# Check queue length
curl -sk -H "Authorization: Bearer $TOKEN" https://localhost:8443/metrics | grep workqueue
# Check controller logs for reconciliation activity
kubectl logs -n postgres-operator-system -l control-plane=controller-manager --tail=20 -f
Задача 5.3: убедитесь, что все ресурсы согласованы
# Check status of all databases
kubectl get databases -o custom-columns=NAME:.metadata.name,PHASE:.status.phase,READY:.status.ready
# Count databases in each phase
kubectl get databases -o jsonpath='{range .items[*]}{.status.phase}{"\n"}{end}' | sort | uniq -c
Очистка
# Delete test resources
kubectl delete databases --all
# Undeploy operator
make undeploy
Итоги лабораторной
В этой лабораторной вы:
- Настроили ограничение частоты controller-runtime
- Добавили индексирование полей для быстрого поиска
- Оптимизировали согласование с помощью MaxConcurrentReconciles
- Добавили пользовательские метрики производительности
- Провели нагрузочное тестирование оператора с множеством ресурсов
Ключевые уроки
- У Controller-runtime есть встроенное ограничение частоты через опцию RateLimiter
- MaxConcurrentReconciles контролирует параллелизм
- Индексы полей обеспечивают быстрые фильтрованные запросы
- Встроенные метрики доступны на
:8443/metrics - Пользовательские метрики используют клиент prometheus с metrics.Registry
- Нагрузочное тестирование подтверждает производительность оператора при масштабе
client.MatchingFields{}задействует индексы для быстрого поиска
Решения
Полные рабочие решения для этой лабораторной доступны в каталоге решений:
- Rate Limiter — полная реализация ограничения частоты
- Performance Optimizations — пакетная обработка, кеширование, метрики
Поздравляем!
Вы завершили Модуль 7! Теперь вы понимаете:
- Упаковку и распространение
- RBAC и безопасность
- Высокую доступность
- Оптимизацию производительности
В Модуле 8 вы изучите продвинутые темы и практические паттерны!