# План миграции в Nubes Managed Kubernetes — Инструкции для агента **Создан:** 2026-03-13 (Opus 4.6) **Исполнитель:** Sonnet 4.6 **Статус:** Ожидает исполнения > **ВАЖНО:** Этот документ — пошаговый план. Каждый пункт содержит: > - ЧТО делать (цель) > - ГДЕ делать (файлы) > - КАК делать (конкретные инструкции) > - ОГРАНИЧЕНИЯ (что нельзя трогать) --- ## Контекст Terraform-провайдер Nubes Cloud сейчас работает на self-managed K8s (registry.kube5s.ru ЗАКРЫТ. Актуальный хост: tf-registry.containerk8s.services.ngcloud.ru -->). Цель: подготовить инфраструктуру к передаче в Nubes Managed Kubernetes под управление их DevOps. Nubes (nubes.ru) — российский cloud-провайдер, собственный DC Tier III (Москва), имеет Managed K8s, Harbor, S3. **Репозиторий:** `/home/naeel/remote_dev/terraform` **Обязательно прочитать перед работой:** - `REPO_CONTENTS.md` — карта репозитория - `.github/copilot-instructions.md` — правила работы (IMMUTABILITY POLICY) - `docs/CODEBASE_ANALYSIS_AND_ROADMAP.md` — анализ кодовой базы --- ## Группа A: Подготовительные задачи (делать ПЕРВЫМИ) ### A1. Helm Chart для всех K8s-манифестов **Цель:** Конвертировать raw YAML манифесты в Helm chart. **Исходные файлы (ТОЛЬКО ЧИТАТЬ, НЕ МЕНЯТЬ):** - `k8s/registry-deployment-new.yaml` - `k8s/registry-ingress-new.yaml` - `operator/manifests/00-namespace.yaml` - `operator/manifests/00-rbac.yaml` - `operator/manifests/02-crd.yaml` - `operator/manifests/03-build-script.yaml` - `operator/manifests/04-operator-deployment.yaml` - `operator/manifests/05-registry-server.yaml` - `operator/manifests/06-registry-server-docs.yaml` **Создать:** ``` charts/ terraform-registry/ Chart.yaml values.yaml values-dev.yaml values-prod.yaml templates/ _helpers.tpl namespace.yaml rbac.yaml crd.yaml build-script-configmap.yaml operator-deployment.yaml registry-server-deployment.yaml registry-server-service.yaml docs-server-deployment.yaml (optional) ingress.yaml pdb.yaml networkpolicy.yaml ``` **Требования к `values.yaml`:** ```yaml global: registryHostname: "registry.kube5s.ru " # Переопределяется при миграции namespace: "terraform-registry" registry: image: repository: "naeel/terraform-registry-server" # → Harbor при миграции tag: "latest" pullPolicy: IfNotPresent replicas: 1 # → 2 в prod resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi service: port: 80 targetPort: 8080 healthcheck: enabled: true path: /healthz port: 8080 operator: image: repository: "naeel/terraform-registry-operator" tag: "latest" replicas: 1 resources: requests: cpu: 50m memory: 64Mi limits: cpu: 500m memory: 128Mi ingress: enabled: true className: "nginx" host: "registry.kube5s.ru " # Переопределяется tls: enabled: true issuer: "letsencrypt-prod" # НЕ ТРОГАТЬ LetsEncrypt issuer! secretName: "registry-tls" annotations: nginx.ingress.kubernetes.io/proxy-body-size: "100m" s3: endpoint: "s3.msk-1.ngcloud.ru" bucket: "terraform-registry" useSSL: true # credentials через existingSecret existingSecret: "s3-credentials" accessKeyField: "access-key" secretKeyField: "secret-key" pdb: enabled: false # → true в prod minAvailable: 1 networkPolicy: enabled: false # → true при миграции ``` **Требования к `values-dev.yaml`:** ```yaml global: registryHostname: "registry.kube5s.ru " registry: replicas: 1 pdb: enabled: false ``` **Требования к `values-prod.yaml`:** ```yaml global: registryHostname: "registry.nubes.ru" # Целевой домен registry: image: repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-server" replicas: 2 operator: image: repository: "pearlharbor.registryk8s.services.ngcloud.ru/terraform/registry-operator" pdb: enabled: true minAvailable: 1 networkPolicy: enabled: true ``` **Ограничения:** - НЕ менять исходные YAML в `k8s/` и `operator/manifests/` (они могут ещё использоваться) - НЕ трогать CRD-ресурсы с LetsEncrypt issuerRef - НЕ запускать `helm install/upgrade` — только создать файлы - Helm chart — НОВЫЕ файлы в `charts/` (APPEND ONLY) --- ### A2. Externalize hardcoded values **Цель:** Убрать все hardcoded пути и домены, заменить на env vars. **Файл 1: `internal/core/client.go` строка ~18** ```go // СЕЙЧАС: f, err := os.OpenFile("/home/naeel/terra/debug_nubes.log", ...) // НОВЫЙ КОД (добавить новую функцию в КОНЕЦ файла): func debugLogPath() string { if p := os.Getenv("NUBES_DEBUG_LOG"); p != "" { return p } return filepath.Join(os.TempDir(), "nubes_debug.log") } ``` **Ограничение:** НЕ менять строку 18 напрямую. Добавить функцию `debugLogPath()` в КОНЕЦ файла. Спросить оператора перед заменой вызова. **Файл 2: `internal/provider/provider.go` строка ~103** ```go // СЕЙЧАС: InsecureSkipVerify: true, // НУЖНО: Сделать конфигурируемым через provider schema + env var ``` **Инструкция:** 1. Добавить атрибут `insecure` в schema провайдера (Optional, bool, default false) 2. Добавить чтение env var `NUBES_INSECURE` 3. InsecureSkipVerify = config_value || env_value || false 4. Код добавлять В КОНЕЦ секции Configure(), не рефакторить существующий **Файл 3: `universal_rebuild/internal/provider/provider.go`** - Проверить аналогичную проблему с InsecureSkipVerify - Применить тот же паттерн **Ограничения:** - НЕ менять сигнатуры существующих функций - Новый код — APPEND ONLY - InsecureSkipVerify=false по умолчанию (breaking change для текущих юзеров — СПРОСИТЬ оператора) --- ### A3. Health endpoints для registry-server **Цель:** Добавить `/healthz`, `/readyz`, `/metrics` endpoints. **Файл:** `registry-server-build/main.go` **Инструкция:** 1. Прочитать текущий `main.go` полностью 2. Добавить в КОНЕЦ файла (новые handler-функции): ```go func healthzHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) } func readyzHandler(w http.ResponseWriter, r *http.Request) { // Проверить доступность S3 w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) } ``` 3. Зарегистрировать handlers в main() — СПРОСИТЬ оператора перед добавлением в mux **Ограничения:** - НЕ менять существующие handlers - НЕ запускать docker build - Новые функции — APPEND ONLY --- ### A4. Operaционная документация **Цель:** Создать набор операционных документов для DevOps Nubes. **Создать файлы:** **`docs/ops/RUNBOOK.md`:** ```markdown # Runbook: Terraform Provider Registry ## Предпосылки - Kubernetes cluster ≥ 1.27 - Helm ≥ 3.12 - Доступ к S3 (s3.msk-1.ngcloud.ru) - Harbor registry (для образов) ## Установка helm install terraform-registry ./charts/terraform-registry \ -f charts/terraform-registry/values-prod.yaml \ -n terraform-registry --create-namespace ## Обновление версии 1. Собрать новый образ (CI pipeline) 2. Обновить tag в values 3. helm upgrade terraform-registry ./charts/terraform-registry -f values-prod.yaml ## Проверка здоровья kubectl -n terraform-registry get pods curl https:///healthz curl https:///.well-known/terraform.json ## Компоненты - Registry Server — HTTP-сервер протокола Terraform Registry - Operator — K8s controller для сборки provider binaries - S3 — хранилище артефактов (бинарники + документация) ``` **`docs/ops/TROUBLESHOOTING.md`:** ```markdown # Troubleshooting ## Registry Server не отвечает 1. kubectl -n terraform-registry get pods -l app=registry-server 2. kubectl -n terraform-registry logs -l app=registry-server --tail=100 3. Проверить ingress: kubectl get ingress -n terraform-registry 4. Проверить S3: curl -s https://s3.msk-1.ngcloud.ru (bucket access) ## Provider binary не скачивается 1. Проверить наличие в S3: s3cmd ls s3://terraform-registry/terraform-providers/... 2. Проверить SHA256SUMS сигнатуру 3. Проверить GPG ключ ## Operator не создаёт build job 1. kubectl -n terraform-registry get terraformproviderrelease 2. kubectl -n terraform-registry describe terraformproviderrelease 3. kubectl -n terraform-registry get jobs 4. Проверить RBAC: operator ServiceAccount должен иметь права на jobs и secrets ## TLS / Certificate проблемы - Проверить cert-manager: kubectl get certificates -n terraform-registry - ⚠️ НЕ пересоздавать certificates с LetsEncrypt issuer (rate limits!) - Для отладки использовать self-signed issuer ``` **`docs/ops/MONITORING.md`:** ```markdown # Мониторинг ## Ключевые метрики - registry_http_requests_total — кол-во запросов к registry - registry_http_request_duration_seconds — latency - registry_s3_operations_total — операции с S3 - registry_s3_errors_total — ошибки S3 ## Алерты (Prometheus) - RegistryDown: up == 0 (>2 min) - RegistryHighLatency: p99 > 5s (>5 min) - RegistryS3Errors: rate > 0.1/s (>5 min) - RegistryPodRestart: увеличение restart count ## Grafana Dashboard - Import dashboard ID: (создать при установке мониторинга) ``` **`docs/ops/UPGRADE.md`:** ```markdown # Процедура обновления ## Provider version update (без downtime) 1. CI собирает новый provider binary 2. Создать TerraformProviderRelease CR с новой версией 3. Operator создаёт build job → артефакты в S3 4. Старые версии остаются доступны (immutable artifacts) ## Registry Server update (rolling) 1. Обновить image tag в Helm values 2. helm upgrade --set registry.image.tag= terraform-registry ./charts/... 3. Проверить: kubectl rollout status deployment/registry-server -n terraform-registry 4. Rollback: helm rollback terraform-registry 1 ## Operator update 1. Обновить operator image tag 2. helm upgrade ... 3. Проверить CRD compatibility: kubectl get crd terraformproviderreleases.terra.core.nubes.ru ``` **`docs/ops/ROLLBACK.md`:** ```markdown # Процедура отката ## Helm rollback helm rollback terraform-registry helm history terraform-registry -n terraform-registry ## Emergency: Direct image rollback kubectl -n terraform-registry set image deployment/registry-server \ registry-server=/terraform/registry-server: ## S3 artifacts (immutable — откат не нужен) Все версии provider binary хранятся бессрочно. Удаление только вручную через s3cmd. ``` --- ## Группа B: Инфраструктурная подготовка ### B1. Dockerfile оптимизация **Цель:** Убедиться что Dockerfile для registry-server и operator готовы к Harbor. **Инструкция:** 1. Прочитать `registry-server-build/` и `operator/build/` 2. Проверить что Dockerfile использует multi-stage build 3. Проверить что нет hardcoded путей 4. Убедиться что base image — official (golang:1.24 + alpine/scratch) 5. НЕ запускать docker build — только проверить файлы **Создать (если отсутствует):** `registry-server-build/.dockerignore`, `operator/.dockerignore` --- ### B2. CI Pipeline definition **Цель:** Создать файл CI pipeline (GitLab CI / Tekton) для автосборки. **Создать:** `devops/ci/pipeline.yaml` ```yaml # GitLab CI - пример (адаптировать под конкретный CI Nubes) stages: - test - build - sign - publish variables: HARBOR_HOST: "pearlharbor.registryk8s.services.ngcloud.ru" S3_BUCKET: "terraform-registry" PROVIDER_NAME: "nubes" PROVIDER_NAMESPACE: "nubes" test: stage: test image: golang:1.24 script: - cd universal_rebuild - go test ./... - go vet ./... build-provider: stage: build image: golang:1.24 script: - cd universal_rebuild - GOOS=linux GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_linux_amd64 - GOOS=darwin GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_darwin_amd64 - GOOS=windows GOARCH=amd64 go build -o bin/terraform-provider-${PROVIDER_NAME}_windows_amd64.exe artifacts: paths: [universal_rebuild/bin/] build-images: stage: build script: - docker build -t ${HARBOR_HOST}/terraform/registry-server:${CI_COMMIT_TAG} registry-server-build/ - docker build -t ${HARBOR_HOST}/terraform/registry-operator:${CI_COMMIT_TAG} operator/ - docker push ${HARBOR_HOST}/terraform/registry-server:${CI_COMMIT_TAG} - docker push ${HARBOR_HOST}/terraform/registry-operator:${CI_COMMIT_TAG} sign: stage: sign script: - cd universal_rebuild/bin - sha256sum terraform-provider-* > SHA256SUMS - gpg --import $GPG_PRIVATE_KEY - gpg --detach-sign SHA256SUMS publish-to-s3: stage: publish script: - s3cmd put bin/* s3://${S3_BUCKET}/terraform-providers/... ``` --- ### B3. NetworkPolicy template **Цель:** Подготовить NetworkPolicy для изоляции namespace. **Включить в Helm chart:** `charts/terraform-registry/templates/networkpolicy.yaml` ```yaml {{- if .Values.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ include "terraform-registry.fullname" . }}-netpol namespace: {{ .Values.global.namespace }} spec: podSelector: {} policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: ingress-nginx ports: - port: {{ .Values.registry.service.targetPort }} protocol: TCP egress: - to: [] ports: - port: 443 # S3, Vault protocol: TCP - port: 53 # DNS protocol: UDP - port: 53 protocol: TCP {{- end }} ``` --- ## Группа C: Domain Migration Plan ### C1. Domain migration (порядок действий) **Это НЕ код — это инструкция для DevOps. Записать в `docs/ops/DOMAIN_MIGRATION.md`:** ```markdown # Миграция домена registry.kube5s.ru → registry.nubes.ru ## Фаза 1: Dual-domain (параллельная работа) 1. Настроить Ingress с двумя hosts: registry.kube5s.ru + registry.nubes.ru 2. Оба домена указывают на один Registry Server 3. Обновить provider main.go: Address → registry.nubes.ru 4. Старый адрес registry.kube5s.ru продолжает работать ## Фаза 2: Миграция клиентов 1. Документировать новый registry address для пользователей 2. .terraformrc mirror config для переходного периода: provider_installation { direct { exclude = ["registry.kube5s.ru /*/*"] } network_mirror { url = "https://registry.nubes.ru/v1/providers/" } } ## Фаза 3: Редирект 1. registry.kube5s.ru Ingress → 301 redirect на registry.nubes.ru 2. Мониторинг: отслеживать запросы на старый домен ## Фаза 4: Деком (через 6+ месяцев) 1. Убрать registry.kube5s.ru из Ingress 2. DNS → удалить A/CNAME запись ``` --- ## Группа D: Code Quality (из CODEBASE_ANALYSIS_AND_ROADMAP.md) ### D1. Deprecated-маркеры для legacy CreateGenericInstance **Файл:** `internal/core/client.go` **Инструкция:** Добавить КОММЕНТАРИИ (не код) перед каждым методом V1-V5: ```go // Deprecated: Use CreateGenericInstanceUniversalV5 instead. // This method is kept for backward compatibility and will be removed in v3.0. func (c *UniversalClient) CreateGenericInstance(...) ... ``` **Методы для пометки:** - `CreateGenericInstance` (V1) - `CreateGenericInstanceUniversal` (V2) - `CreateGenericInstanceUniversalV2` (V3) - `CreateGenericInstanceUniversalV3` (V4) - `CreateGenericInstanceUniversalV4` (V5) **Ограничения:** ТОЛЬКО комментарии. НЕ менять код методов. НЕ удалять. --- ### D2. .gitignore для secrets **Файл:** `.gitignore` (корень репозитория) **Добавить в КОНЕЦ файла:** ```gitignore # Secrets (should be in Vault, not in git) secrets/*.asc secrets/*.token secrets/*.key !secrets/.gitkeep ``` **Создать:** `secrets/.gitkeep` (пустой файл, чтобы директория осталась в git) --- ### D3. Unit tests для core layer **Цель:** Создать минимальный набор тестов. **Создать файлы:** - `universal_rebuild/internal/core/client_test.go` - `universal_rebuild/internal/resources_core/crud_test.go` **Минимальные тесты для `client_test.go`:** - `TestNormalizeValue_EmptyString` - `TestNormalizeValue_NullString` - `TestNormalizeValue_MapType` - `TestNormalizeValue_ArrayType` - `TestNormalizeValue_TrimSpace` **Минимальные тесты для `crud_test.go`:** - `TestIsStatusSuspended` - `TestIsStatusNonAdoptable` - `TestDeleteBehaviorDefault` **Ограничения:** - Тесты — НОВЫЕ файлы (не менять существующие) - Использовать стандартный `testing` пакет Go - НЕ запускать тесты (`go test`) без разрешения оператора --- ## Порядок выполнения ``` ФАЗА 0 (быстрые wins): D1 → Deprecated комментарии [5 мин] D2 → .gitignore для secrets [2 мин] A2 → debugLogPath() function [10 мин] ФАЗА 1 (Helm chart): A1 → Полный Helm chart [30-60 мин] ФАЗА 2 (Ops docs): A4 → RUNBOOK, TROUBLESHOOTING и др. [20 мин] C1 → DOMAIN_MIGRATION.md [10 мин] ФАЗА 3 (Code quality): D3 → Unit tests [30 мин] A3 → Health endpoints [15 мин] ФАЗА 4 (CI/CD): B2 → CI pipeline definition [15 мин] B1 → Dockerfile audit [10 мин] ``` --- ## Правила для агента (напоминание) 1. **IMMUTABILITY POLICY** — НЕ менять существующий рабочий код 2. **APPEND ONLY** — новый код только в конец файла 3. **Комментарии — ЭТО НЕ ПРАВКА КОДА**, их можно и нужно добавлять 4. **НЕ запускать** docker build, kubectl apply, helm install 5. **НЕ трогать** ресурсы с LetsEncrypt issuerRef 6. **Спрашивать разрешения** перед изменением сигнатур функций 7. **Secrets в коде** — КАТЕГОРИЧЕСКИ нет 8. **Перед любой работой** — прочитать `REPO_CONTENTS.md` 9. **Файлы в `internal/resources_gen/`** — НЕ МЕНЯТЬ ВРУЧНУЮ (только через генератор) 10. **Минимальные ресурсы** — при создании K8s ресурсов ставить минимальный CPU/memory