From 4e190d957d9aa50d845391ed424c80646f83caa2 Mon Sep 17 00:00:00 2001 From: lab Date: Fri, 11 Sep 2026 13:16:31 +0000 Subject: [PATCH] nelm CI: checkout from Bitbucket + docs --- CLIENT-SETUP.ru.md | 196 +++++++++++++++++++++++++++++++++++++ DEPLOY.md | 73 ++++++++++++++ Jenkinsfile | 83 ++++++++++++++++ NELM-CLOSED-LOOP.ru.md | 198 ++++++++++++++++++++++++++++++++++++++ jenkins/build.groovy | 52 ++++++++++ jenkins/checkout-repos.sh | 47 +++++++++ jenkins/deploy.groovy | 36 +++++++ 7 files changed, 685 insertions(+) create mode 100644 CLIENT-SETUP.ru.md create mode 100644 DEPLOY.md create mode 100644 Jenkinsfile create mode 100644 NELM-CLOSED-LOOP.ru.md create mode 100644 jenkins/build.groovy create mode 100755 jenkins/checkout-repos.sh create mode 100644 jenkins/deploy.groovy diff --git a/CLIENT-SETUP.ru.md b/CLIENT-SETUP.ru.md new file mode 100644 index 0000000..64a4170 --- /dev/null +++ b/CLIENT-SETUP.ru.md @@ -0,0 +1,196 @@ +# Настройка билда и деплоя (nelm + Jenkins + Bitbucket) в клиентской инфраструктуре + +Исходные условия у клиента: **уже есть** чистый Kubernetes/Deckhouse-кластер, **Jenkins** и **Bitbucket**. Ниже — только то, что нужно донастроить, чтобы нажатием кнопок в Jenkins собирать образы и выкатывать сервисы через `nelm` из актуальных репозиториев Bitbucket. + +## 1. Идея схемы + +``` +Bitbucket (источник правды: код + .helm/) + │ git clone на каждый job + ▼ +Jenkins agent (одна ВМ: docker build/push + nelm release install) + │ kubeconfig сервисного аккаунта + ▼ +Кластер(ы) dev / preprod / prod +``` + +- **Не** хранить копию репозиториев вручную на агенте (`~/nelm-work/bitbucket` и т.п.). +- На каждый build/deploy Jenkins **клонирует** нужные репозитории с ветки (обычно `main`). +- Образы уходят во **внутренний registry**. +- Манифесты применяет **`nelm release install`** (не werf, не helm из GitLab CI). +- Секреты в чартах — в `secret-values.yaml`, ключ только на агенте / в credentials Jenkins. + +## 2. Что подготовить один раз + +### 2.1. Инструменты на Jenkins agent + +На той же машине, где крутится agent (или в контейнере agent с доступом к Docker socket): + +| Компонент | Зачем | +|-----------|--------| +| `git` | checkout репозиториев | +| `docker` (+ доступ к socket / DinD) | build & push | +| `nelm` | render/install релизов | +| `kubectl` | namespaces, проверка подов, kubeconfig | +| доступ к Bitbucket HTTP(S) | clone | +| доступ к registry | push/pull | +| доступ к API Kubernetes | deploy | + +Пример установки `nelm` (если есть выход в интернет на этапе подготовки; в закрытом контуре — положить бинарь вручную): + +```bash +# бинарь nelm → /usr/local/bin/nelm или ~/bin/nelm +export PATH="$HOME/bin:/usr/local/bin:$PATH" +nelm version +``` + +### 2.2. Сервисный аккаунт для деплоя + +С админского kubeconfig (на master или с ноутбука с доступом): + +```bash +export KUBECONFIG=/path/to/admin.kubeconfig +chmod +x create-deploy-sa.sh +./create-deploy-sa.sh +# получите kube-deploy.config +``` + +Скрипт создаёт SA + `ClusterAuthorizationRule` (Deckhouse) и пишет kubeconfig. + +Дальше: + +1. Скопируйте `kube-deploy.config` на Jenkins agent (например `/var/lib/jenkins/kube-deploy.config`, права только для пользователя agent). +2. Заведите **отдельный context на каждый контур**, если это разные кластеры: + +```bash +kubectl --kubeconfig=kube-deploy.config config rename-context lab-cluster-dev +# аналогично lab-cluster-preprod, lab-cluster-prod +# либо три разных kubeconfig + переключение в job +``` + +В job по умолчанию используется context `lab-cluster-${ENV}`. + +### 2.3. Ключ секретов nelm + +Один общий ключ на контур (или на все контуры, если один vault-подход): + +```bash +nelm chart secret key create > /secure/nelm_secret_key +chmod 600 /secure/nelm_secret_key +``` + +Все `secret-values.yaml` в репозиториях должны быть зашифрованы **этим** ключом. + +В Jenkins: credential типа **Secret text**, id = `nelm-secret-key`. + +### 2.4. Доступ Jenkins → Bitbucket + +1. В Bitbucket создайте technical user / HTTP access token с правом **read** на project со сервисами. +2. В Jenkins: credential **Username with password**, id = `bitbucket-git` (user + token). +3. Убедитесь, что agent резолвит URL Bitbucket (DNS / `/etc/hosts`) и доверяет TLS (корпоративный CA в trust store или внутренний HTTP). + +### 2.5. Registry и pull secrets в кластере + +В каждом namespace (`dev` / `preprod` / `prod`) или на уровне SA чарта нужен `imagePullSecrets` (часто `registrysecret`), совпадающий с `.Values.imagePullSecrets` в чартах. + +Образы в `values.yaml` репозиториев должны указывать на **клиентский** registry, не на lab `192.168.10.68:5000` и не на docker.io. + +### 2.6. StorageClass / ноды + +В чартах по умолчанию часто `localpath` и `nodeSelector: node-role.kubernetes.io/worker: ""`. +Приведите values под клиентский StorageClass и лейблы нод **до** первого деплоя (или через `--set` в job, если так принято). + +## 3. Репозитории в Bitbucket + +Минимальный состав project (имена можно сохранить): + +| Repo | Содержимое | +|------|------------| +| `postgres`, `seaweedfs`, `ollama`, `keycloak`, `vllm` | код + `Dockerfile` + каталог `.helm/` | +| опционально `nelm-ci` | `Jenkinsfile`, `jenkins/*.groovy`, `jenkins/checkout-repos.sh` | + +В каждом сервисном репо обязательно: + +```text +.helm/Chart.yaml +.helm/values.yaml # образы внутреннего registry +.helm/secret-values.yaml # зашифрованные секреты (если есть) +.helm/templates/... +Dockerfile # если нужен build +``` + +`values-public.yaml` — только для стендов с доступом в интернет; **в закрытом контуре не использовать**. + +## 4. Job в Jenkins + +### Вариант A (предпочтительно): Pipeline from SCM + +1. Создайте repo `nelm-ci` с `Jenkinsfile` и каталогом `jenkins/`. +2. New Item → Pipeline → Pipeline script from SCM → Bitbucket Git → этот repo. +3. Credentials: `bitbucket-git`, `nelm-secret-key`. +4. Параметры job (уже в `Jenkinsfile`): + +| Параметр | Смысл | +|----------|--------| +| `SERVICE` | один сервис или `all` | +| `ENV` | `dev` / `preprod` / `prod` (= namespace) | +| `ACTION` | `build` / `deploy` / `build-and-deploy` | +| `USE_PUBLIC_IMAGES` | **false** у клиента | +| `REGISTRY` | адрес внутреннего registry | +| `GIT_BASE_URL` | `https://bitbucket.example.com/scm/PROJ` или `.../bbadmin` | +| `GIT_BRANCH` | ветка | +| `KUBE_CONTEXT` | пусто = `lab-cluster-$ENV` | + +Логика: + +1. **Checkout from Bitbucket** — `jenkins/checkout-repos.sh` клонирует актуальные сервисы в `$WORKSPACE/repos/`. +2. **Build** — `docker build` / mirror → `REGISTRY/...`, `docker push`. +3. **Deploy** — `nelm release install -n $ENV -r ${service}-${ENV}` из `$WORKSPACE/repos//.helm`. + +### Вариант B: Freestyle (как на lab) + +Shell build step вызывает тот же `checkout-repos.sh`, затем `nelm`/`docker`. +Скрипты CI лежат на агенте или тоже подтягиваются из `nelm-ci` одним `git clone`. + +## 5. Переменные окружения на agent / в job + +```bash +export PATH="/usr/local/bin:$HOME/bin:$PATH" +export KUBECONFIG=/var/lib/jenkins/kube-deploy.config +export NELM_SECRET_KEY=... # из Jenkins credentials +export GIT_BASE_URL=https://bitbucket.client.local/bbadmin +export GIT_BRANCH=main +``` + +## 6. Первый прогон (чеклист) + +1. В Bitbucket открывается repo, на `main` есть `.helm/`. +2. С agent: `git ls-remote $GIT_BASE_URL/postgres.git`. +3. `nelm chart render -n dev --set env=dev --set global.cluster=dev path/to/.helm` — без ошибок, секреты расшифровываются. +4. В нужном namespace есть `registrysecret` (если образы приватные). +5. Jenkins → Build with Parameters → `SERVICE=postgres`, `ENV=dev`, `ACTION=build-and-deploy`, `USE_PUBLIC_IMAGES=false`. +6. `kubectl -n dev get pods` — Ready; `nelm release list -n dev` — статус deployed. + +## 7. Три контура + +- Namespace = имя env (`dev` / `preprod` / `prod`), либо своя политика — тогда поправьте job. +- Разные кластеры → разные kube-context / kubeconfig. +- Разные значения (replicas, resources, storage) — через overlays в `values.yaml` (`dig .Values.env ...`), не через копипасту манифестов. + +## 8. Чего не делать + +- Не деплоить из «замороженной» папки на диске агента без `git fetch`. +- Не коммитить `NELM_SECRET_KEY` и сырые пароли в Bitbucket. +- Не оставлять в `values.yaml` адреса lab-registry / docker.io для боевого контура. +- Не смешивать werf converge и nelm на одном релизе без миграции ownership аннотаций Helm. + +## 9. Файлы из поставки lab (ориентир) + +| Файл | Назначение | +|------|------------| +| `create-deploy-sa.sh` | SA + kubeconfig для runner | +| `Jenkinsfile` | pipeline с checkout → build → deploy | +| `jenkins/checkout-repos.sh` | актуальный clone сервисов | +| `jenkins/build.groovy` / `deploy.groovy` | стадии | +| `DEPLOY.md` | краткий lab-ориентир | +| `NELM-CLOSED-LOOP.ru.md` | правки репозиториев под закрытый контур | diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..d3b8a50 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,73 @@ +# Deploy guide (nelm + Jenkins, no werf) + +Charts live under each Bitbucket repo’s `.helm/`. Jenkins **clones those repos on every job** — it does not deploy from a hand-copied folder on disk. + +## Lab URLs + +| What | URL / host | Creds | +|------|------------|-------| +| Bitbucket UI (Gitea) | https://bitbucket.valdemorte.online | `bbadmin` / `admin123` | +| Jenkins | https://jenkins.valdemorte.online | `admin` / `admin123` | +| Compose | `ubuntu@192.168.10.68:~/ci-stack` | — | + +Repos: `bbadmin/{postgres,seaweedfs,ollama,keycloak,vllm}`. Job: **`nelm-deploy`**. Contexts: `lab-cluster-{dev,preprod,prod}`. + +## How Jenkins gets the code + +1. Parameters include `GIT_BASE_URL` (default `http://192.168.10.68:7990/bbadmin`) and `GIT_BRANCH` (`main`). +2. Job runs `jenkins/checkout-repos.sh` → fresh clone into `$WORKSPACE/repos/`. +3. Build/deploy use **only** that checkout (`.helm/`, `Dockerfile`). + +Agent still holds non-git secrets/tools only: + +- `~/nelm-work/.nelm_secret_key` +- `~/nelm-work/.git_user` + `.git_token` +- `~/kube-deploy.config` +- `~/nelm-work/jenkins/checkout-repos.sh` (CI helper; prefer keeping the same scripts in a `nelm-ci` Bitbucket repo in production) + +See **`CLIENT-SETUP.ru.md`** for client rollout and **`NELM-CLOSED-LOOP.ru.md`** for closed-loop chart/Dockerfile changes. + +## Tools on the agent + +```bash +export NELM_SECRET_KEY=$(cat ~/nelm-work/.nelm_secret_key) +export PATH="$HOME/bin:$PATH" +export KUBECONFIG=$HOME/kube-deploy.config +``` + +## Images + +- Default `values.yaml` → internal registry (`192.168.10.68:5000/...` in lab). +- Lab smoke test: job flag `USE_PUBLIC_IMAGES=true` → `values-public.yaml`. +- Client closed loop: `USE_PUBLIC_IMAGES=false`. + +## Deploy SA + +```bash +./create-deploy-sa.sh +``` + +## Manual deploy (after git clone) + +```bash +git clone http://192.168.10.68:7990/bbadmin/postgres.git +export NELM_SECRET_KEY=$(cat .nelm_secret_key) +nelm release install -n dev -r postgres-dev \ + --set env=dev --set global.cluster=dev \ + --values postgres/.helm/values-public.yaml \ + postgres/.helm +``` + +## Jenkins parameters + +| Parameter | Meaning | +|-----------|---------| +| `SERVICE` | postgres … vllm / all | +| `ENV` | dev / preprod / prod | +| `ACTION` | build / deploy / build-and-deploy | +| `USE_PUBLIC_IMAGES` | lab overlay | +| `GIT_BASE_URL` | Bitbucket project base | +| `GIT_BRANCH` | branch to deploy | +| `REGISTRY` | push target for build | + +Pipeline sources: root `Jenkinsfile` + `jenkins/*.groovy` + `jenkins/checkout-repos.sh`. diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..d310b25 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,83 @@ +pipeline { + agent any + + parameters { + choice(name: 'SERVICE', choices: ['postgres', 'seaweedfs', 'ollama', 'keycloak', 'vllm', 'all'], description: 'Service to build/deploy') + choice(name: 'ENV', choices: ['dev', 'preprod', 'prod'], description: 'Target environment / namespace') + choice(name: 'ACTION', choices: ['build', 'deploy', 'build-and-deploy'], description: 'Pipeline action') + booleanParam(name: 'USE_PUBLIC_IMAGES', defaultValue: false, description: 'Use values-public.yaml (only for lab smoke tests; false in closed loop)') + string(name: 'REGISTRY', defaultValue: '192.168.10.68:5000', description: 'Internal registry for push/tag') + string(name: 'KUBE_CONTEXT', defaultValue: '', description: 'Optional override; default lab-cluster-') + string(name: 'GIT_BASE_URL', defaultValue: 'http://192.168.10.68:7990/bbadmin', description: 'Bitbucket project base URL without trailing slash') + string(name: 'GIT_BRANCH', defaultValue: 'main', description: 'Branch to build/deploy') + } + + environment { + PATH = "/home/ubuntu/bin:${env.HOME}/bin:/usr/local/bin:${env.PATH}" + KUBECONFIG = "/home/ubuntu/kube-deploy.config" + NELM_SECRET_KEY = credentials('nelm-secret-key') + BITBUCKET_GIT = credentials('bitbucket-git') + } + + options { + timestamps() + disableConcurrentBuilds() + } + + stages { + stage('Checkout from Bitbucket') { + steps { + script { + def services = params.SERVICE == 'all' ? ['postgres', 'seaweedfs', 'ollama', 'keycloak', 'vllm'] : [params.SERVICE] + env.SERVICES = services.join(' ') + env.REPOS_DIR = "${env.WORKSPACE}/repos" + withEnv([ + "GIT_BASE_URL=${params.GIT_BASE_URL}", + "GIT_BRANCH=${params.GIT_BRANCH}", + "SERVICES=${env.SERVICES}", + "DEST=${env.REPOS_DIR}", + "GIT_USER=${env.BITBUCKET_GIT_USR}", + "GIT_TOKEN=${env.BITBUCKET_GIT_PSW}", + "GIT_SSL_NO_VERIFY=1" + ]) { + sh 'bash jenkins/checkout-repos.sh' + } + } + } + } + + stage('Build') { + when { + expression { params.ACTION == 'build' || params.ACTION == 'build-and-deploy' } + } + steps { + script { + load "${env.WORKSPACE}/jenkins/build.groovy" + env.SERVICES.split(' ').each { svc -> + buildService(svc, params.REGISTRY, params.ENV, env.REPOS_DIR) + } + } + } + } + + stage('Deploy') { + when { + expression { params.ACTION == 'deploy' || params.ACTION == 'build-and-deploy' } + } + steps { + script { + load "${env.WORKSPACE}/jenkins/deploy.groovy" + env.SERVICES.split(' ').each { svc -> + deployService(svc, params.ENV, params.USE_PUBLIC_IMAGES, params.KUBE_CONTEXT, env.REPOS_DIR) + } + } + } + } + } + + post { + always { + echo "Done: SERVICE=${params.SERVICE} ENV=${params.ENV} ACTION=${params.ACTION} BRANCH=${params.GIT_BRANCH}" + } + } +} diff --git a/NELM-CLOSED-LOOP.ru.md b/NELM-CLOSED-LOOP.ru.md new file mode 100644 index 0000000..d509073 --- /dev/null +++ b/NELM-CLOSED-LOOP.ru.md @@ -0,0 +1,198 @@ +# Особенности билда/деплоя через nelm и правки репозиториев `bitbucket/` под закрытый контур + +## 1. Чем nelm отличается от привычного werf/helm в CI + +| Тема | Как было (werf / common-ci) | Как делаем (nelm + Jenkins) | +|------|-----------------------------|-----------------------------| +| Источник манифестов | `werf.yaml` + `.helm`, converge | только chart в `.helm/`, `nelm release install` | +| Секреты | werf secret / тот же формат ключа | `secret-values.yaml` + `NELM_SECRET_KEY` (`nelm chart secret …`) | +| Сборка образа | часто встроенный werf build | отдельный этап: `docker build` + `docker push` в Jenkins | +| Откуда код | GitLab CI clone | Jenkins сам клонирует Bitbucket на каждый job | +| Релиз | имя/лейблы werf | release name = `-`, namespace = env | +| Три контура | разные values / stages | `--set env=… --set global.cluster=…`, overlays через `dig` | + +Практические следствия: + +1. **`werf.yaml` в закрытом контуре не нужен для деплоя.** Его можно оставить для совместимости, но pipeline на него не опирается. Деплой идёт из `.helm/`. +2. Ресурсы в кластере получают Helm-аннотации `meta.helm.sh/release-name` / `release-namespace`. Если объект уже создан руками или другим релизом — `nelm` откажется без `--force-adoption` (лучше удалить чужой объект или один раз осознанно adopt). +3. Расшифровка секретов происходит **на agent в момент install**. Без `NELM_SECRET_KEY` job упадёт или зальёт ciphertext в Secret (если включить `--no-decrypt-secrets` — так делать нельзя). +4. `nelm` ждёт Ready у workloads в пределах `--timeout`. В закрытом контуре таймауты чаще связаны с **ImagePullBackOff** (нет образа / нет pull secret), а не с самим nelm. + +Типовые команды: + +```bash +export NELM_SECRET_KEY=$(cat /secure/nelm_secret_key) + +# проверка шаблонов без кластера +nelm chart render -n dev --set env=dev --set global.cluster=dev .helm + +# деплой +nelm release install -n dev -r postgres-dev \ + --set env=dev --set global.cluster=dev \ + --timeout 5m \ + .helm + +nelm release list -n dev +nelm release history -n dev -r postgres-dev +``` + +Шифрование значений: + +```bash +# показать plaintext (только на защищённой машине) +nelm chart secret values-file decrypt .helm/secret-values.yaml + +# зашифровать новый файл тем же ключом +nelm chart secret values-file encrypt plaintext-values.yaml > .helm/secret-values.yaml +``` + +## 2. Что поправить в репозиториях из папки `bitbucket/` + +Ниже — конкретный чеклист по текущим сервисам (`postgres`, `seaweedfs`, `ollama`, `keycloak`, `vllm`). + +### 2.1. Образы: только внутренний registry + +Сейчас в `values.yaml` часто: + +```yaml +image: + postgresql: 192.168.10.68:5000/library/postgres:16-alpine +``` + +У клиента заменить host на **их** registry, например: + +```yaml +image: + postgresql: registry.client.local/library/postgres:16-alpine +``` + +То же для всех ключей `image.*` во всех пяти чартах. + +`values-public.yaml` (ghcr/docker.io/quay) — **не подключать** в job (`USE_PUBLIC_IMAGES=false`). Файл можно оставить в repo как reference для открытых стендов или удалить, чтобы никто случайно не выкатил публичные теги. + +### 2.2. Dockerfile `FROM` — без интернета + +Сейчас: + +```dockerfile +FROM docker.io/library/postgres:16-alpine +FROM quay.io/keycloak/keycloak:26.4.0 +``` + +В закрытом контуре: + +```dockerfile +FROM registry.client.local/library/postgres:16-alpine +FROM registry.client.local/keycloak/keycloak:26.4.0 +``` + +Либо на этапе build в Jenkins делать `docker pull` уже с зеркала (если base заранее завезли), а в Dockerfile всё равно писать внутренний путь — иначе `docker build` на agent без интернета упадёт. + +Заранее завезти в registry: postgres, postgres-exporter, seaweedfs, ollama, keycloak, vllm (и зависимости keycloak plugins, если тянутся с сети на build). + +### 2.3. `imagePullSecrets` + +В values: + +```yaml +imagePullSecrets: + - name: registrysecret +``` + +В каждом namespace деплоя должен существовать Secret этого имени (тип `kubernetes.io/dockerconfigjson`) **до** первого install, либо чарт должен его создавать из зашифрованных данных (сейчас обычно ожидается снаружи). +Иначе: `ErrImagePull` / `ImagePullBackOff`. + +### 2.4. Секреты приложения + +Файлы: + +- `postgres/.helm/secret-values.yaml` — пароль БД +- `keycloak/.helm/secret-values.yaml` — admin / DB +- `seaweedfs/.helm/secret-values.yaml` — S3 keys + +Действия у клиента: + +1. Сгенерировать **свой** `NELM_SECRET_KEY` (не lab-ключ). +2. Перешифровать все `secret-values.yaml` новым ключом (`nelm chart secret key rotate` или encrypt заново). +3. Положить ключ только в Jenkins credentials / vault агента. +4. Заменить lab-пароли (`admin123`) на боевые **до** шифрования. + +Репозитории без secret-values (`ollama`, частично `vllm`) — проверить, нет ли plaintext секретов в обычном `values.yaml` / TLS файлах. + +### 2.5. TLS и вложенные секреты в git (`vllm`) + +В `vllm/.helm/secret/{dev,preprod,prod}/` лежат `tls.crt` / `tls.key`. +Для клиента: + +- либо заменить на клиентские сертификаты; +- либо убрать из git и монтировать из внешнего Secret / cert-manager; +- не коммитить боевые private key в публичные зеркала. + +### 2.6. StorageClass и scheduling + +В чартах lab: + +- `storageClassName: localpath` (или аналог) +- `nodeSelector: node-role.kubernetes.io/worker: ""` + +У клиента: + +- имя StorageClass из их кластера (`ceph-rbd`, `nfs-client`, …); +- nodeSelector/tolerations под GPU-ноды для `ollama` / `vllm` (`gpu.enabled` уже есть флагом — выставить true и нужные taints только если ноды готовы); +- ресурсы (CPU/memory) под квоты namespace. + +### 2.7. Ingress / DNS + +У `keycloak`, `ollama`, `vllm` есть Ingress. Поправить: + +- hosts под клиентскую зону; +- TLS (cert-manager annotations или уже существующие секреты); +- класс Ingress (`ingressClassName`), если не default. + +В закрытом контуре без внешнего DNS — либо внутренние имена, либо временно ClusterIP + port-forward для приёмки. + +### 2.8. Убрать/игнорировать зависимости от GitLab CI и werf + +В репозиториях могут остаться: + +- `werf.yaml` +- `.gitlab-ci.yml` / include на `common-ci` + +Для клиентского Bitbucket+Jenkins: + +- pipeline из Bitbucket **не** должен вызывать `werf converge`; +- достаточно `.helm/` + Dockerfile; +- CI logic — в Jenkins (`Jenkinsfile` / freestyle), см. `CLIENT-SETUP.ru.md`. + +### 2.9. Имена release и namespace + +Соглашение lab/клиента: + +- namespace = `dev` | `preprod` | `prod` +- release = `-` (например `postgres-dev`) + +Шаблоны используют `.Values.env` для overlays. Не хардкодить namespace в templates. + +### 2.10. Экспортёры и sidecar-образы + +У postgres отдельный образ `postgresqlExporter`. Его тоже нужно завезти во внутренний registry и прописать в `values.yaml`. +Нельзя подставлять образ postgres как exporter (так ломается readiness). + +## 3. Рекомендуемый порядок миграции репо под клиента + +1. Завести project в Bitbucket, запушить пять сервисов. +2. Заменить registry host + Dockerfile `FROM`. +3. Создать `registrysecret` в `dev`. +4. Сгенерировать клиентский `NELM_SECRET_KEY`, перешифровать `secret-values.yaml`, обновить пароли. +5. Поправить StorageClass / Ingress / nodeSelector. +6. Прогнать `nelm chart render` локально. +7. Подключить Jenkins job с **checkout из Bitbucket**, `USE_PUBLIC_IMAGES=false`. +8. `build-and-deploy` для `postgres` на `dev`, затем остальные сервисы. + +## 4. Критерий «готово для закрытого контура» + +- Job клонирует **актуальный** commit из Bitbucket (в логе виден `HEAD=…` после checkout). +- `docker build` не ходит в интернет (все `FROM` и base — внутренние). +- `nelm release install` без `values-public.yaml`. +- Секреты в кластере — расшифрованный plaintext, ключ не в git. +- Поды Running/Ready, образы с клиентского registry. diff --git a/jenkins/build.groovy b/jenkins/build.groovy new file mode 100644 index 0000000..0913ba8 --- /dev/null +++ b/jenkins/build.groovy @@ -0,0 +1,52 @@ +def buildService(String service, String registry, String envName, String reposDir) { + def dirPath = "${reposDir}/${service}" + if (!fileExists(dirPath)) { + error("Service directory not found after checkout: ${dirPath}") + } + + def images = [ + postgres: [ + [ref: 'library/postgres:16-alpine', build: true], + [ref: 'prometheuscommunity/postgres-exporter:v0.19.0', src: 'quay.io/prometheuscommunity/postgres-exporter:v0.19.0'] + ], + seaweedfs: [ + [ref: 'chrislusf/seaweedfs:3.99', build: true] + ], + ollama: [ + [ref: 'ollama/ollama:0.30.11', build: true] + ], + keycloak: [ + [ref: 'keycloak/keycloak:26.4.0', build: true], + [ref: 'library/postgres:16-alpine', src: 'docker.io/library/postgres:16-alpine'] + ], + vllm: [ + [ref: 'vllm/vllm-openai:v0.14.0', build: true] + ] + ] + + if (!images.containsKey(service)) { + error("Unknown service: ${service}") + } + + echo "Building/mirroring ${service} into ${registry} (env=${envName}) from ${dirPath}" + images[service].each { img -> + def localRef = "${registry}/${img.ref}" + if (img.build) { + sh """ + set -euo pipefail + docker build -t '${localRef}' '${dirPath}' + docker push '${localRef}' + """ + } else { + def src = img.src ?: "docker.io/${img.ref}" + sh """ + set -euo pipefail + docker pull '${src}' + docker tag '${src}' '${localRef}' + docker push '${localRef}' + """ + } + } +} + +return this diff --git a/jenkins/checkout-repos.sh b/jenkins/checkout-repos.sh new file mode 100755 index 0000000..76bb590 --- /dev/null +++ b/jenkins/checkout-repos.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Clone/update service repos from Bitbucket (or Gitea) into DEST. +# Required env: +# GIT_BASE_URL e.g. http://192.168.10.68:7990/bbadmin +# SERVICES space-separated list, e.g. "postgres seaweedfs" +# Optional: +# GIT_BRANCH default main +# DEST default $WORKSPACE/repos (or ./repos) +# GIT_USER / GIT_TOKEN if set, injected into clone URL +# GIT_SSL_NO_VERIFY=1 for lab self-signed HTTPS + +GIT_BRANCH="${GIT_BRANCH:-main}" +DEST="${DEST:-${WORKSPACE:-.}/repos}" +GIT_BASE_URL="${GIT_BASE_URL:?GIT_BASE_URL is required}" +SERVICES="${SERVICES:?SERVICES is required}" + +mkdir -p "$DEST" + +auth_base="$GIT_BASE_URL" +if [ -n "${GIT_USER:-}" ] && [ -n "${GIT_TOKEN:-}" ]; then + # inject user:token after scheme:// + auth_base="$(echo "$GIT_BASE_URL" | sed -E "s#^(https?://)#\1${GIT_USER}:${GIT_TOKEN}@#")" +fi + +export GIT_TERMINAL_PROMPT=0 + +for svc in $SERVICES; do + url="${auth_base%/}/${svc}.git" + dir="$DEST/$svc" + echo "==> checkout $svc from $GIT_BASE_URL/$svc.git branch=$GIT_BRANCH" + if [ -d "$dir/.git" ]; then + git -C "$dir" remote set-url origin "$url" + git -C "$dir" fetch --depth 1 origin "$GIT_BRANCH" + git -C "$dir" checkout -B "$GIT_BRANCH" "FETCH_HEAD" + git -C "$dir" reset --hard "FETCH_HEAD" + git -C "$dir" clean -fdx + else + rm -rf "$dir" + git clone --depth 1 --branch "$GIT_BRANCH" "$url" "$dir" + fi + test -d "$dir/.helm" || { echo "ERROR: $svc has no .helm chart"; exit 1; } + echo " HEAD=$(git -C "$dir" rev-parse --short HEAD)" +done + +echo "CHECKOUT_DIR=$DEST" diff --git a/jenkins/deploy.groovy b/jenkins/deploy.groovy new file mode 100644 index 0000000..0a208c8 --- /dev/null +++ b/jenkins/deploy.groovy @@ -0,0 +1,36 @@ +def deployService(String service, String envName, boolean usePublic, String kubeContext, String reposDir) { + def chartDir = "${reposDir}/${service}/.helm" + def release = "${service}-${envName}" + def ns = envName + def ctx = kubeContext?.trim() ? kubeContext.trim() : "lab-cluster-${envName}" + + def publicArgs = usePublic ? "--values ${chartDir}/values-public.yaml" : "" + def setArgs = "--set env=${envName} --set global.cluster=${envName}" + + echo "Deploy ${service} release=${release} ns=${ns} context=${ctx} public=${usePublic} chart=${chartDir}" + + sh """ + set -euo pipefail + export NELM_SECRET_KEY="\${NELM_SECRET_KEY}" + export PATH="/home/ubuntu/bin:\${HOME}/bin:/usr/local/bin:\${PATH}" + export KUBECONFIG="\${KUBECONFIG:-/home/ubuntu/kube-deploy.config}" + + command -v nelm >/dev/null || { echo 'nelm not found in PATH'; exit 1; } + test -d '${chartDir}' || { echo "chart missing: ${chartDir}"; exit 1; } + + kubectl --context='${ctx}' get ns '${ns}' >/dev/null 2>&1 || kubectl --context='${ctx}' create ns '${ns}' + + nelm release install \\ + -n '${ns}' \\ + -r '${release}' \\ + --kube-context '${ctx}' \\ + ${publicArgs} \\ + ${setArgs} \\ + --timeout 5m \\ + '${chartDir}' + + kubectl --context='${ctx}' -n '${ns}' get pods,svc -o wide + """ +} + +return this