Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a713c46ed0 | ||
|
|
492d31b253 | ||
|
|
0b5a9b8f21 | ||
|
|
a67ab33e5d | ||
|
|
75d1fba14a | ||
|
|
da2a0b33a7 | ||
|
|
cb4130ad9d | ||
|
|
b5471e664d | ||
|
|
2474d82cea | ||
|
|
1f336b6f93 | ||
|
|
be205b3b14 | ||
|
|
3640bc8229 | ||
|
|
f3c2fa194b | ||
|
|
ab086a86b4 | ||
|
|
36313e8f6f | ||
|
|
0c0c3ebd9d | ||
|
|
c8555d432c | ||
|
|
e1a81d3dd8 | ||
|
|
5efa1a5fd2 | ||
|
|
6d6703ed2d | ||
|
|
3d205fa7e0 | ||
|
|
96d0d2e373 | ||
|
|
285255e628 | ||
|
|
c8eefab4b6 | ||
|
|
c24f02b078 | ||
|
|
c750a08f8d | ||
|
|
0a69d528b3 | ||
|
|
f9874afb2b | ||
|
|
7d90c23734 | ||
|
|
f45802c0b8 | ||
|
|
00056b9150 | ||
|
|
46616f2260 | ||
|
|
d320af6ad7 | ||
|
|
e55958f7cb | ||
|
|
77204c1550 | ||
|
|
c7c194feaa | ||
|
|
51b9120797 | ||
|
|
237dcb9b28 | ||
|
|
c2a9bae0db | ||
|
|
bddcc955fe | ||
|
|
ee1881e516 | ||
|
|
8e14cd1e0b | ||
|
|
565faa0e17 | ||
|
|
da8e40c044 | ||
|
|
4ae7a8286b | ||
|
|
b28e5dd27a | ||
|
|
037d069c85 | ||
|
|
457f0dad41 | ||
|
|
8a8cd66f59 | ||
|
|
e39404cb4c | ||
|
|
262401efe1 | ||
|
|
5105eee602 | ||
|
|
714dc55db1 | ||
|
|
a7c97027ae | ||
|
|
934ccbbb73 | ||
|
|
4c4bb6f8a4 | ||
|
|
497ca1342d | ||
|
|
383d19157c | ||
|
|
5f84c8d19b | ||
|
|
9a39f4568e | ||
|
|
e122cc6f0b | ||
|
|
6244a05c19 | ||
|
|
b7f4d07544 | ||
|
|
f9bde75ed7 | ||
|
|
5ef655f1bc | ||
|
|
e4953042c0 | ||
|
|
0f9969ddad | ||
|
|
8551d58d08 | ||
|
|
cb37ba000e | ||
|
|
75c47e932f | ||
|
|
4d9b1807bf | ||
|
|
2c3fce7834 | ||
|
|
83a8339db7 | ||
|
|
3a2ee9d6b6 | ||
|
|
333677dedf | ||
|
|
0ad515e6c4 | ||
|
|
ea23b885c4 | ||
|
|
d87af910d6 | ||
|
|
93e9d9fed0 | ||
|
|
eb2748170a | ||
|
|
361b7155bf | ||
|
|
fb001710c3 | ||
|
|
b8c29be7a5 | ||
|
|
2bbee05013 |
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# В контекст сборки нужны только манифесты: clusters/, apps/, infrastructure/
|
||||||
|
# и скрипты из hack/. Всё остальное — история, IDE, секреты — только раздувает
|
||||||
|
# контекст и рискует уехать в образ.
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.idea
|
||||||
|
.claude
|
||||||
|
CLAUDE.md
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
docs
|
||||||
|
*.md
|
||||||
|
aero
|
||||||
|
inventory.yaml
|
||||||
|
tmp
|
||||||
|
.tmp/
|
||||||
|
snapshots
|
||||||
|
out
|
||||||
|
|
||||||
|
# Секреты и ключи не должны попадать в контекст сборки даже случайно.
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.p12
|
||||||
|
kubeconfig*
|
||||||
|
*service-account*.json
|
||||||
13
.gitignore
vendored
13
.gitignore
vendored
@ -2,7 +2,20 @@
|
|||||||
.claude
|
.claude
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
.env
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
tmp
|
tmp
|
||||||
.tmp/
|
.tmp/
|
||||||
snapshots/*
|
snapshots/*
|
||||||
|
|
||||||
|
# Рантайм-артефакты контура aero (gitea-data, k3s-storage, ключи реестра)
|
||||||
|
# переехали в infra/aero-deploy вместе с docker-compose, который их создаёт.
|
||||||
|
# Здесь остаётся только gitea-data: каталог мог остаться от прежней раскладки.
|
||||||
|
gitea-data/
|
||||||
|
|
||||||
|
# --- Секреты и ключи: не коммитить никогда ---
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.p12
|
||||||
|
kubeconfig*
|
||||||
|
*service-account*.json
|
||||||
|
|||||||
77
Dockerfile
Normal file
77
Dockerfile
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Полная сборка манифестов IaC-репозитория для одного контура.
|
||||||
|
#
|
||||||
|
# docker build --build-arg CONTOUR=aero -t iac:aero .
|
||||||
|
# docker run --rm iac:aero list # что собралось
|
||||||
|
# docker run --rm iac:aero cat clusters # слои Flux одним потоком
|
||||||
|
# docker run --rm -v "$PWD/out:/export" iac:aero export
|
||||||
|
#
|
||||||
|
# Контур — имя каталога в clusters/ (aero, brusnika-prod, dsinv, wb, ...).
|
||||||
|
# Тот же идентификатор служит именем overlay внутри apps/* и infrastructure/*,
|
||||||
|
# поэтому одного аргумента хватает на весь рендер.
|
||||||
|
#
|
||||||
|
# Сборка офлайновая: kustomize build разворачивает оверлеи и патчи, а
|
||||||
|
# HelmRelease остаётся Custom Resource'ом — чарты тянет Flux уже в кластере.
|
||||||
|
# Ни helm, ни доступ к OCI-реестру здесь не нужны. Исключение — валидация:
|
||||||
|
# схемы CRD kubeconform берёт из сети (отключается VALIDATE=0).
|
||||||
|
|
||||||
|
ARG KUSTOMIZE_VERSION=v5.4.3
|
||||||
|
ARG KUBECONFORM_VERSION=v0.6.7
|
||||||
|
|
||||||
|
FROM registry.k8s.io/kustomize/kustomize:${KUSTOMIZE_VERSION} AS kustomize
|
||||||
|
FROM ghcr.io/yannh/kubeconform:${KUBECONFORM_VERSION} AS kubeconform
|
||||||
|
|
||||||
|
|
||||||
|
FROM alpine:3.20 AS builder
|
||||||
|
|
||||||
|
# Контур обязателен по смыслу, но значение по умолчанию оставлено: сборка без
|
||||||
|
# --build-arg собирает aero, а не падает на пустой переменной.
|
||||||
|
ARG CONTOUR=aero
|
||||||
|
ARG VALIDATE=1
|
||||||
|
ARG KUBERNETES_VERSION=1.30.0
|
||||||
|
|
||||||
|
COPY --from=kustomize /app/kustomize /usr/local/bin/kustomize
|
||||||
|
COPY --from=kubeconform /kubeconform /usr/local/bin/kubeconform
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Скрипт копируется отдельным слоем: правка манифестов не инвалидирует его,
|
||||||
|
# а правка скрипта не тянет за собой пересборку базовых слоёв.
|
||||||
|
COPY hack/render-contour.sh /usr/local/bin/render-contour
|
||||||
|
RUN chmod +x /usr/local/bin/render-contour
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN render-contour "${CONTOUR}" /out
|
||||||
|
|
||||||
|
# Валидация отдельным слоем: смена версии Kubernetes не пересобирает рендер.
|
||||||
|
# --ignore-missing-schemas пропускает ресурсы, чьих схем нет в каталоге
|
||||||
|
# (часть CRD), но не заглушает ошибки в тех, что нашлись.
|
||||||
|
RUN if [ "${VALIDATE}" = "1" ]; then \
|
||||||
|
kubeconform \
|
||||||
|
-kubernetes-version "${KUBERNETES_VERSION}" \
|
||||||
|
-strict \
|
||||||
|
-ignore-missing-schemas \
|
||||||
|
-schema-location default \
|
||||||
|
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
|
||||||
|
-summary \
|
||||||
|
/out/*.yaml ; \
|
||||||
|
else \
|
||||||
|
echo "VALIDATE=0 — проверка схемами пропущена" ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
FROM alpine:3.20
|
||||||
|
|
||||||
|
ARG CONTOUR=aero
|
||||||
|
ENV CONTOUR=${CONTOUR}
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="iac-manifests" \
|
||||||
|
org.opencontainers.image.description="Собранные манифесты контура ${CONTOUR}" \
|
||||||
|
ru.iac.contour="${CONTOUR}"
|
||||||
|
|
||||||
|
COPY --from=builder /out /manifests
|
||||||
|
COPY hack/entrypoint.sh /usr/local/bin/entrypoint
|
||||||
|
RUN chmod +x /usr/local/bin/entrypoint
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/entrypoint"]
|
||||||
|
CMD ["list"]
|
||||||
@ -60,7 +60,7 @@ Superset, Trino, GoAlert, GlitchTip, argo-*, otel-*) **существуют об
|
|||||||
|
|
||||||
| Репозиторий | Зона ответственности | Ключевое содержимое |
|
| Репозиторий | Зона ответственности | Ключевое содержимое |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| ★ [infra/iac](https://gitlab.sarex.io/infra/iac) ✅ | **Основной GitOps-монорепозиторий (Flux v2).** Декларативное состояние 9 кластеров: инфраструктурные компоненты + прикладные сервисы | `clusters/`, `infrastructure/` (36), `apps/` (37), `inventory.yaml`, `docs/apps/` |
|
| ★ [infra/iac](https://gitlab.sarex.io/infra/iac) ✅ | **Основной GitOps-монорепозиторий (Flux v2).** Декларативное состояние 9 кластеров: инфраструктурные компоненты + прикладные сервисы. Только манифесты — развёртывание контура aero выделено в [infra/aero-deploy](https://gitlab.sarex.io/infra/aero-deploy) | `clusters/`, `infrastructure/` (36), `apps/` (37), `inventory.yaml`, `docs/apps/` |
|
||||||
| [infra/terraform](https://gitlab.sarex.io/infra/terraform) ✅ | **Ресурсы Yandex Cloud и Kubernetes**: namespaces, PostgreSQL БД/пользователи, S3-бакеты, Valkey/Redis-пользователи, Kafka-топики, RabbitMQ, k8s-секреты. Единый источник правды — `infrastructure.yaml`, секреты через SOPS | `live/prod` (Terragrunt), `modules/`: `k8s-namespace`, `k8s-secret`, `k8s-secrets`, `kafka-topics-yc`, `rabbitmq`, `yc-database`, `yc-s3`, `yc-valkey-user` |
|
| [infra/terraform](https://gitlab.sarex.io/infra/terraform) ✅ | **Ресурсы Yandex Cloud и Kubernetes**: namespaces, PostgreSQL БД/пользователи, S3-бакеты, Valkey/Redis-пользователи, Kafka-топики, RabbitMQ, k8s-секреты. Единый источник правды — `infrastructure.yaml`, секреты через SOPS | `live/prod` (Terragrunt), `modules/`: `k8s-namespace`, `k8s-secret`, `k8s-secrets`, `kafka-topics-yc`, `rabbitmq`, `yc-database`, `yc-s3`, `yc-valkey-user` |
|
||||||
| [infra/terraform-contour](https://gitlab.sarex.io/infra/terraform-contour) ✅ | Тот же подход **для изолированного контура**: namespaces и секреты из Vault | `live/{namespace,vault-secrets}`, `modules/{k8s-namespace,vault-platform-secrets}` |
|
| [infra/terraform-contour](https://gitlab.sarex.io/infra/terraform-contour) ✅ | Тот же подход **для изолированного контура**: namespaces и секреты из Vault | `live/{namespace,vault-secrets}`, `modules/{k8s-namespace,vault-platform-secrets}` |
|
||||||
| [infra/terraform-contour-mirror](https://gitlab.sarex.io/infra/terraform-contour-mirror) ✅ | **Зеркало `infra/terraform` для контура** (ветка `contour`, единственная). Тот же README и структура + `Dockerfile` для запуска в закрытом периметре | `live/`, `modules/`, `scripts/`, `Dockerfile` |
|
| [infra/terraform-contour-mirror](https://gitlab.sarex.io/infra/terraform-contour-mirror) ✅ | **Зеркало `infra/terraform` для контура** (ветка `contour`, единственная). Тот же README и структура + `Dockerfile` для запуска в закрытом периметре | `live/`, `modules/`, `scripts/`, `Dockerfile` |
|
||||||
@ -74,6 +74,7 @@ Superset, Trino, GoAlert, GlitchTip, argo-*, otel-*) **существуют об
|
|||||||
|
|
||||||
| Репозиторий | Отвечает за |
|
| Репозиторий | Отвечает за |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| [infra/aero-deploy](https://gitlab.sarex.io/infra/aero-deploy) ✅ | **Изолированный контур aero на одном хосте**: ansible-роли (`base`, `docker`, `sarex_stack`) + `docker-compose.yaml` подложки — k3s тремя контейнерами, gitea как источник правды, Vault, bootstrap FluxCD. Манифесты берёт из `infra/iac` (`flux_src_root`) и синхронизирует в gitea контура. Выделен из `infra/iac` |
|
||||||
| [infra/kubespray](https://gitlab.sarex.io/infra/kubespray) ✅ | Форк upstream Kubespray — **развёртывание самих кластеров Kubernetes** на VM |
|
| [infra/kubespray](https://gitlab.sarex.io/infra/kubespray) ✅ | Форк upstream Kubespray — **развёртывание самих кластеров Kubernetes** на VM |
|
||||||
| [infra/k8s-provision](https://gitlab.sarex.io/infra/k8s-provision) ✅ | **Первичная обвязка свежего кластера**: `namespaces`, `cert_manager`, `istio` (+`ISTIO.md`), `ingress`, `dashboard`, `prometheus`, `postgres`/`ya_postgres`, `rabbitmq`, `pvc`, `gitlab`, `teamcity`, `migration` |
|
| [infra/k8s-provision](https://gitlab.sarex.io/infra/k8s-provision) ✅ | **Первичная обвязка свежего кластера**: `namespaces`, `cert_manager`, `istio` (+`ISTIO.md`), `ingress`, `dashboard`, `prometheus`, `postgres`/`ya_postgres`, `rabbitmq`, `pvc`, `gitlab`, `teamcity`, `migration` |
|
||||||
| [infra/ansible-playbooks](https://gitlab.sarex.io/infra/ansible-playbooks) ✅ | Зонтичный Ansible-репозиторий: каталоги `minio/`, `patroni/` |
|
| [infra/ansible-playbooks](https://gitlab.sarex.io/infra/ansible-playbooks) ✅ | Зонтичный Ansible-репозиторий: каталоги `minio/`, `patroni/` |
|
||||||
|
|||||||
17
README.md
17
README.md
@ -369,6 +369,23 @@ flowchart LR
|
|||||||
│ └── patches/ # Патчи поверх base
|
│ └── patches/ # Патчи поверх base
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Чего здесь нет: развёртывание контура aero
|
||||||
|
|
||||||
|
Машинерия подъёма самого хоста живёт в отдельном репозитории
|
||||||
|
[`infra/aero-deploy`](https://gitlab.sarex.io/infra/aero-deploy): ansible-роли,
|
||||||
|
`docker-compose.yaml` подложки (k3s, gitea, vault, bootstrap Flux),
|
||||||
|
конфигурация Vault и k3s-манифесты DNS-мостов. Здесь остаётся только то, что
|
||||||
|
раскатывает Flux.
|
||||||
|
|
||||||
|
Связь между репозиториями односторонняя: роль `sarex_stack` пакует отсюда
|
||||||
|
`clusters/aero` и его замыкание (переменная `gitea_sync_paths`) и коммитит в
|
||||||
|
gitea внутри контура, откуда это читает Flux. Путь к этому репозиторию роль
|
||||||
|
берёт из `flux_src_root`, по умолчанию — соседний каталог `../iac`.
|
||||||
|
|
||||||
|
Практическое следствие: **добавили компонент в `clusters/aero/controllers` —
|
||||||
|
допишите его в `gitea_sync_paths`** в `aero-deploy`, иначе kustomize в кластере
|
||||||
|
упадёт на отсутствующем каталоге. Обратной ссылки нет, и забыть об этом легко.
|
||||||
|
|
||||||
## Как это работает
|
## Как это работает
|
||||||
|
|
||||||
Flux отслеживает директорию `clusters/<имя-кластера>/`. Каждый кластер содержит два Flux Kustomization CRD верхнего уровня:
|
Flux отслеживает директорию `clusters/<имя-кластера>/`. Каждый кластер содержит два Flux Kustomization CRD верхнего уровня:
|
||||||
|
|||||||
224
apps/bim/aero/bim-api.yaml
Normal file
224
apps/bim/aero/bim-api.yaml
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
---
|
||||||
|
# BIM API — приложение из aero/bimbackend (Python, api + worker).
|
||||||
|
#
|
||||||
|
# ЭТО НЕ ТО ЖЕ, ЧТО apps/bim/base. В base описан platform/bim-backend-v2 —
|
||||||
|
# другой сервис, на Go, с единственным httpserver. Спутать их легко: ОБА
|
||||||
|
# пушат в реестр один и тот же образ bim-api, и различаются только тегами и
|
||||||
|
# содержимым. У bimbackend теги — полный SHA коммита (CI пушит
|
||||||
|
# bim-api:${CI_COMMIT_SHA} с master), у bim-backend-v2 — contour_*.
|
||||||
|
# Поэтому здесь ../base намеренно НЕ подключён.
|
||||||
|
#
|
||||||
|
# Манифесты обычные, а не HelmRelease с universal-chart: в репозитории этот
|
||||||
|
# сервис так и описан (apps/bim/dsinv), своего чарта у него нет, и заводить
|
||||||
|
# его ради контура значило бы поддерживать ещё одну сущность.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: bim-api-sa
|
||||||
|
namespace: bim
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bim-api
|
||||||
|
namespace: bim
|
||||||
|
labels:
|
||||||
|
app: bim-api
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: bim-api
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: bim-api
|
||||||
|
annotations:
|
||||||
|
# Порт Vault исключён из перехвата sidecar'ом: сам Vault живёт вне
|
||||||
|
# кластера, и заворачивать обращения к нему в mesh незачем.
|
||||||
|
traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
|
||||||
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
|
vault.hashicorp.com/auth-path: auth/kubernetes
|
||||||
|
vault.hashicorp.com/role: bim
|
||||||
|
# БД. Имена переменных — DB_*, как их читает settings.py bimbackend,
|
||||||
|
# а не POSTGRES_*, как у остальных приложений контура.
|
||||||
|
vault.hashicorp.com/agent-inject-secret-bim-db: secrets/data/apps/bim/postgres
|
||||||
|
vault.hashicorp.com/agent-inject-template-bim-db: |-
|
||||||
|
{{- with secret "secrets/data/apps/bim/postgres" -}}
|
||||||
|
DB_HOST={{ index .Data.data "host" }}
|
||||||
|
DB_PORT={{ index .Data.data "port" }}
|
||||||
|
DB_USER={{ index .Data.data "username" }}
|
||||||
|
DB_PASSWORD={{ index .Data.data "password" }}
|
||||||
|
{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-bim-rabbitmq: secrets/data/rabbitmq/apps/bim
|
||||||
|
vault.hashicorp.com/agent-inject-template-bim-rabbitmq: |-
|
||||||
|
{{- with secret "secrets/data/rabbitmq/apps/bim" -}}
|
||||||
|
RABBIT_USER={{ index .Data.data "username" }}
|
||||||
|
RABBIT_PASSWORD={{ index .Data.data "password" }}
|
||||||
|
{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-bim-s3: secrets/data/minio/apps/bim
|
||||||
|
vault.hashicorp.com/agent-inject-template-bim-s3: |-
|
||||||
|
{{- with secret "secrets/data/minio/apps/bim" -}}
|
||||||
|
AWS_ACCESS_KEY_ID={{ index .Data.data "access_key" }}
|
||||||
|
AWS_SECRET_ACCESS_KEY={{ index .Data.data "secret_key" }}
|
||||||
|
{{- end -}}
|
||||||
|
# Публичный ключ JWT отдаётся файлом. Оба флага аутентификации ниже
|
||||||
|
# выключены, то есть читаться он не должен, — но путь в переменной
|
||||||
|
# обязан существовать, иначе рискуем падением при импорте настроек.
|
||||||
|
vault.hashicorp.com/agent-inject-secret-jwt-public: secrets/data/vault/common/rsa_keys
|
||||||
|
vault.hashicorp.com/agent-inject-template-jwt-public: |-
|
||||||
|
{{- with secret "secrets/data/vault/common/rsa_keys" -}}
|
||||||
|
{{ index .Data.data "public_key" }}
|
||||||
|
{{- end -}}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: bim-api-sa
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
containers:
|
||||||
|
- name: bim-api
|
||||||
|
# Тег — полный SHA, как их пушит CI aero/bimbackend с master. Именно
|
||||||
|
# этот образ крутится в dsinv. contour_*-теги у образа bim-api брать
|
||||||
|
# НЕЛЬЗЯ: их собирает bim-backend-v2, это чужое приложение.
|
||||||
|
image: cr.yandex/crp3ccidau046kdj8g9q/bim-api:870965d1967cd761965029b0480884ab7fa7c951
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
# Штатный entrypoint не умеет читать секреты из файлов, поэтому
|
||||||
|
# оборачиваем его: подгружаем отрендеренное vault-агентом и передаём
|
||||||
|
# управление. exec — чтобы приложение осталось процессом с PID 1 и
|
||||||
|
# корректно получало сигналы остановки.
|
||||||
|
command: ["/bin/sh", "-ec"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -a
|
||||||
|
[ -f /vault/secrets/bim-db ] && . /vault/secrets/bim-db
|
||||||
|
[ -f /vault/secrets/bim-rabbitmq ] && . /vault/secrets/bim-rabbitmq
|
||||||
|
[ -f /vault/secrets/bim-s3 ] && . /vault/secrets/bim-s3
|
||||||
|
set +a
|
||||||
|
exec ./entrypoint_api.sh
|
||||||
|
ports:
|
||||||
|
- containerPort: 5555
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 9000
|
||||||
|
name: prom
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: JWT_PUBLIC_KEY_PATH
|
||||||
|
value: /vault/secrets/jwt-public
|
||||||
|
- name: FEATURE_ENABLE_JWT_AUTH
|
||||||
|
value: "false"
|
||||||
|
- name: FEATURE_ENABLE_SAREX_AUTH
|
||||||
|
value: "false"
|
||||||
|
- name: FEATURE_ENABLE_WORKSPACES
|
||||||
|
value: "true"
|
||||||
|
# Google-хранилища в контуре нет и не будет — флаг выключен.
|
||||||
|
# Том с ключом, который есть в dsinv, здесь не нужен, а сама
|
||||||
|
# переменная ниже нужна, см. комментарий к ней.
|
||||||
|
- name: FEATURE_ENABLE_GCLOUD
|
||||||
|
value: "false"
|
||||||
|
# Значение НЕ УДАЛЯТЬ, даже при выключенном флаге выше. settings.py
|
||||||
|
# делает check_settings(): проходит по всем ключам ENV_FIELDS и
|
||||||
|
# падает на первом же незаданном —
|
||||||
|
# Exception: ENV_VARIABLE NOT SET: GOOGLE_APPLICATION_CREDENTIALS
|
||||||
|
# Флаги проверяются позже, до них дело не доходит. Файла по этому
|
||||||
|
# пути нет и не будет: при FEATURE_ENABLE_GCLOUD=false его никто не
|
||||||
|
# открывает, важно лишь наличие самой переменной.
|
||||||
|
- name: GOOGLE_APPLICATION_CREDENTIALS
|
||||||
|
value: /etc/sarex/google-storage/service_account.json
|
||||||
|
# ВНИМАНИЕ: S3 в этом приложении НЕ НАСТРАИВАЕТСЯ на MinIO.
|
||||||
|
# В storage/s3.py адрес зашит в код:
|
||||||
|
# endpoint_url='https://storage.yandexcloud.net'
|
||||||
|
# переменной окружения для него нет. Из контура он недостижим,
|
||||||
|
# поэтому любые операции с файлами будут падать по таймауту —
|
||||||
|
# это принято осознанно, API и остальные эндпоинты живут.
|
||||||
|
# Починить можно только пересборкой образа с вынесенным endpoint
|
||||||
|
# либо переводом на BIM_CURRENT_STORAGE=local (тип есть в
|
||||||
|
# storage/storages.py).
|
||||||
|
- name: FEATURE_ENABLE_S3
|
||||||
|
value: "true"
|
||||||
|
- name: S3_BUCKET
|
||||||
|
value: sarex-media-storage
|
||||||
|
- name: BIM_CURRENT_STORAGE
|
||||||
|
value: s3
|
||||||
|
- name: AWS_DEFAULT_REGION
|
||||||
|
value: ru-central1
|
||||||
|
- name: FEATURE_ENABLE_PROCESSING
|
||||||
|
value: "1"
|
||||||
|
# Адреса смежных сервисов приведены к именам контура. В dsinv это
|
||||||
|
# workflows-service:8000 и workspaces-service:8000 — таких сервисов
|
||||||
|
# здесь нет, оба называются backend-svc и слушают 80.
|
||||||
|
- name: PROCESSING_API_URL
|
||||||
|
value: http://backend-svc.processing.svc.cluster.local:80/internal
|
||||||
|
- name: PROCESSING_C2M_VERSION
|
||||||
|
value: stable
|
||||||
|
- name: WORKSPACES_API_URL
|
||||||
|
value: http://backend-svc.workspaces.svc.cluster.local:80/internal
|
||||||
|
# Сервиса сравнений в контуре нет и не планируется, флага для его
|
||||||
|
# отключения приложение не предоставляет. Имя оставлено говорящим:
|
||||||
|
# в логах будет видно, куда именно оно не достучалось.
|
||||||
|
- name: COMPARATOR_API_URL
|
||||||
|
value: http://comparator-api-service.comparator.svc.cluster.local/internal
|
||||||
|
- name: BIM_API_EXTERNAL_HOST_SCHEMA
|
||||||
|
value: https
|
||||||
|
- name: BIM_API_EXTERNAL_HOST
|
||||||
|
value: bim.sarex.local.lonsdaleites.ru
|
||||||
|
- name: BIM_API_INTERNAL_HOST_SCHEMA
|
||||||
|
value: http
|
||||||
|
- name: BIM_API_INTERNAL_HOST
|
||||||
|
value: bim-api-service.bim.svc.cluster.local:5555
|
||||||
|
- name: WEBHOOK_URL
|
||||||
|
value: http://bim-api-service.bim.svc.cluster.local:5555
|
||||||
|
# Брокер общий, vhost по умолчанию. В dsinv vhost называется api,
|
||||||
|
# но в контуре отдельных vhost'ов нет — см. secrets/rabbitmq/*.
|
||||||
|
- name: RABBIT_HOST
|
||||||
|
value: rabbitmq.rabbitmq.svc.cluster.local:5672
|
||||||
|
- name: RABBIT_VHOST
|
||||||
|
value: "/"
|
||||||
|
- name: MESH_OPTIMIZER_PATH
|
||||||
|
value: gltfpack
|
||||||
|
- name: DEBUG
|
||||||
|
value: "0"
|
||||||
|
- name: STORAGE_SEGMENT
|
||||||
|
value: sarex-production-storage
|
||||||
|
- name: STORAGE_PROJECT
|
||||||
|
value: sarex-production
|
||||||
|
- name: TMP_DIR
|
||||||
|
value: /tmp
|
||||||
|
- name: prometheus_multiproc_dir
|
||||||
|
value: /tmp
|
||||||
|
- name: BUNDLE_VERSION
|
||||||
|
value: v1
|
||||||
|
- name: DISABLE_INSTANCING
|
||||||
|
value: "0"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 25m
|
||||||
|
memory: 200Mi
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp
|
||||||
|
name: tmp-volume
|
||||||
|
volumes:
|
||||||
|
- name: tmp-volume
|
||||||
|
emptyDir: {}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: bim-api-service
|
||||||
|
namespace: bim
|
||||||
|
labels:
|
||||||
|
app: bim-api
|
||||||
|
spec:
|
||||||
|
# ClusterIP, а не NodePort с прибитым портом 31352, как в dsinv: наружу
|
||||||
|
# сервис выходит через общий Gateway и VirtualService контура, отдельный
|
||||||
|
# порт на нодах ему не нужен.
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 5555
|
||||||
|
targetPort: 5555
|
||||||
|
protocol: TCP
|
||||||
|
selector:
|
||||||
|
app: bim-api
|
||||||
157
apps/bim/aero/bim-worker.yaml
Normal file
157
apps/bim/aero/bim-worker.yaml
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
# Воркер bimbackend. Крутит тот же образ и те же настройки, что api, —
|
||||||
|
# отличается только точкой входа (entrypoint_worker.sh) и отсутствием портов.
|
||||||
|
# Обоснования переменных не дублирую, они в bim-api.yaml.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: bim-worker-sa
|
||||||
|
namespace: bim
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bim-worker
|
||||||
|
namespace: bim
|
||||||
|
labels:
|
||||||
|
app: bim-worker
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: bim-worker
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: bim-worker
|
||||||
|
annotations:
|
||||||
|
traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
|
||||||
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
|
vault.hashicorp.com/auth-path: auth/kubernetes
|
||||||
|
vault.hashicorp.com/role: bim
|
||||||
|
vault.hashicorp.com/agent-inject-secret-bim-db: secrets/data/apps/bim/postgres
|
||||||
|
vault.hashicorp.com/agent-inject-template-bim-db: |-
|
||||||
|
{{- with secret "secrets/data/apps/bim/postgres" -}}
|
||||||
|
DB_HOST={{ index .Data.data "host" }}
|
||||||
|
DB_PORT={{ index .Data.data "port" }}
|
||||||
|
DB_USER={{ index .Data.data "username" }}
|
||||||
|
DB_PASSWORD={{ index .Data.data "password" }}
|
||||||
|
{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-bim-rabbitmq: secrets/data/rabbitmq/apps/bim
|
||||||
|
vault.hashicorp.com/agent-inject-template-bim-rabbitmq: |-
|
||||||
|
{{- with secret "secrets/data/rabbitmq/apps/bim" -}}
|
||||||
|
RABBIT_USER={{ index .Data.data "username" }}
|
||||||
|
RABBIT_PASSWORD={{ index .Data.data "password" }}
|
||||||
|
{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-bim-s3: secrets/data/minio/apps/bim
|
||||||
|
vault.hashicorp.com/agent-inject-template-bim-s3: |-
|
||||||
|
{{- with secret "secrets/data/minio/apps/bim" -}}
|
||||||
|
AWS_ACCESS_KEY_ID={{ index .Data.data "access_key" }}
|
||||||
|
AWS_SECRET_ACCESS_KEY={{ index .Data.data "secret_key" }}
|
||||||
|
{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-jwt-public: secrets/data/vault/common/rsa_keys
|
||||||
|
vault.hashicorp.com/agent-inject-template-jwt-public: |-
|
||||||
|
{{- with secret "secrets/data/vault/common/rsa_keys" -}}
|
||||||
|
{{ index .Data.data "public_key" }}
|
||||||
|
{{- end -}}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: bim-worker-sa
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
containers:
|
||||||
|
- name: bim-worker
|
||||||
|
image: cr.yandex/crp3ccidau046kdj8g9q/bim-api:870965d1967cd761965029b0480884ab7fa7c951
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/bin/sh", "-ec"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -a
|
||||||
|
[ -f /vault/secrets/bim-db ] && . /vault/secrets/bim-db
|
||||||
|
[ -f /vault/secrets/bim-rabbitmq ] && . /vault/secrets/bim-rabbitmq
|
||||||
|
[ -f /vault/secrets/bim-s3 ] && . /vault/secrets/bim-s3
|
||||||
|
set +a
|
||||||
|
exec ./entrypoint_worker.sh
|
||||||
|
env:
|
||||||
|
- name: JWT_PUBLIC_KEY_PATH
|
||||||
|
value: /vault/secrets/jwt-public
|
||||||
|
- name: FEATURE_ENABLE_JWT_AUTH
|
||||||
|
value: "false"
|
||||||
|
- name: FEATURE_ENABLE_SAREX_AUTH
|
||||||
|
value: "false"
|
||||||
|
- name: FEATURE_ENABLE_WORKSPACES
|
||||||
|
value: "true"
|
||||||
|
- name: FEATURE_ENABLE_GCLOUD
|
||||||
|
value: "false"
|
||||||
|
# Значение НЕ УДАЛЯТЬ, даже при выключенном флаге выше. settings.py
|
||||||
|
# делает check_settings(): проходит по всем ключам ENV_FIELDS и
|
||||||
|
# падает на первом же незаданном —
|
||||||
|
# Exception: ENV_VARIABLE NOT SET: GOOGLE_APPLICATION_CREDENTIALS
|
||||||
|
# Флаги проверяются позже, до них дело не доходит. Файла по этому
|
||||||
|
# пути нет и не будет: при FEATURE_ENABLE_GCLOUD=false его никто не
|
||||||
|
# открывает, важно лишь наличие самой переменной.
|
||||||
|
- name: GOOGLE_APPLICATION_CREDENTIALS
|
||||||
|
value: /etc/sarex/google-storage/service_account.json
|
||||||
|
- name: FEATURE_ENABLE_S3
|
||||||
|
value: "true"
|
||||||
|
- name: S3_BUCKET
|
||||||
|
value: sarex-media-storage
|
||||||
|
- name: BIM_CURRENT_STORAGE
|
||||||
|
value: s3
|
||||||
|
- name: AWS_DEFAULT_REGION
|
||||||
|
value: ru-central1
|
||||||
|
- name: FEATURE_ENABLE_PROCESSING
|
||||||
|
value: "1"
|
||||||
|
- name: PROCESSING_API_URL
|
||||||
|
value: http://backend-svc.processing.svc.cluster.local:80/internal
|
||||||
|
- name: PROCESSING_C2M_VERSION
|
||||||
|
value: stable
|
||||||
|
- name: WORKSPACES_API_URL
|
||||||
|
value: http://backend-svc.workspaces.svc.cluster.local:80/internal
|
||||||
|
- name: COMPARATOR_API_URL
|
||||||
|
value: http://comparator-api-service.comparator.svc.cluster.local/internal
|
||||||
|
- name: BIM_API_EXTERNAL_HOST_SCHEMA
|
||||||
|
value: https
|
||||||
|
- name: BIM_API_EXTERNAL_HOST
|
||||||
|
value: bim.sarex.local.lonsdaleites.ru
|
||||||
|
- name: BIM_API_INTERNAL_HOST_SCHEMA
|
||||||
|
value: http
|
||||||
|
- name: BIM_API_INTERNAL_HOST
|
||||||
|
value: bim-api-service.bim.svc.cluster.local:5555
|
||||||
|
- name: WEBHOOK_URL
|
||||||
|
value: http://bim-api-service.bim.svc.cluster.local:5555
|
||||||
|
# В dsinv у воркера здесь rabbitmq-service.bim:5672 — брокера в
|
||||||
|
# namespace bim нет ни там, ни здесь; у api в том же файле адрес
|
||||||
|
# другой и правильный. Ставим общий брокер контура.
|
||||||
|
- name: RABBIT_HOST
|
||||||
|
value: rabbitmq.rabbitmq.svc.cluster.local:5672
|
||||||
|
- name: RABBIT_VHOST
|
||||||
|
value: "/"
|
||||||
|
- name: MESH_OPTIMIZER_PATH
|
||||||
|
value: gltfpack
|
||||||
|
- name: DEBUG
|
||||||
|
value: "0"
|
||||||
|
- name: STORAGE_SEGMENT
|
||||||
|
value: sarex-production-storage
|
||||||
|
- name: STORAGE_PROJECT
|
||||||
|
value: sarex-production
|
||||||
|
- name: TMP_DIR
|
||||||
|
value: /tmp
|
||||||
|
- name: prometheus_multiproc_dir
|
||||||
|
value: /tmp
|
||||||
|
- name: BUNDLE_VERSION
|
||||||
|
value: v1
|
||||||
|
- name: DISABLE_INSTANCING
|
||||||
|
value: "0"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 25m
|
||||||
|
memory: 200Mi
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp
|
||||||
|
name: tmp-volume
|
||||||
|
volumes:
|
||||||
|
- name: tmp-volume
|
||||||
|
emptyDir: {}
|
||||||
11
apps/bim/aero/kustomization.yaml
Normal file
11
apps/bim/aero/kustomization.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: bim
|
||||||
|
# ../base НЕ подключён намеренно: там описан platform/bim-backend-v2 — другое
|
||||||
|
# приложение, которое разворачивать в контуре не нужно. Подробности в шапке
|
||||||
|
# bim-api.yaml.
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- bim-api.yaml
|
||||||
|
- bim-worker.yaml
|
||||||
7
apps/bim/aero/namespace.yaml
Normal file
7
apps/bim/aero/namespace.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: bim
|
||||||
|
labels:
|
||||||
|
istio-injection: enabled
|
||||||
387
apps/django/aero/django-configmap.yaml
Normal file
387
apps/django/aero/django-configmap.yaml
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
# Копия django-configmap из base с заменой домена на домен контура.
|
||||||
|
#
|
||||||
|
# Патчем этого не сделать: все настройки лежат ОДНИМ значением data.production.py
|
||||||
|
# (331 строка python), kustomize умеет заменить его только целиком. Ровно та же
|
||||||
|
# причина, что у nginx-configmap.yaml рядом.
|
||||||
|
#
|
||||||
|
# ЧТО ИЗМЕНЕНО — две строки про домен sarex.contour.infra.sarex.tech,
|
||||||
|
# принадлежащий ДРУГОМУ контуру:
|
||||||
|
#
|
||||||
|
# HOST — по нему backend строит АБСОЛЮТНЫЕ ссылки на медиа.
|
||||||
|
# Браузер получал
|
||||||
|
# https://sarex.contour.infra.sarex.tech/media/state/previews/....png
|
||||||
|
# и, разумеется, 404: чужой хост, наших файлов там нет.
|
||||||
|
# CSRF_TRUSTED_ORIGINS — без нашего домена django отклонял бы POST-формы
|
||||||
|
# админки с ошибкой проверки Referer.
|
||||||
|
#
|
||||||
|
# И один блок сверх того: CsrfExemptSessionAuthentication в
|
||||||
|
# DEFAULT_AUTHENTICATION_CLASSES вместо штатного SessionAuthentication —
|
||||||
|
# из-за него SPA получала 403 на любое удаление. Обоснование и цена решения
|
||||||
|
# расписаны прямо над классом.
|
||||||
|
#
|
||||||
|
# Больше в файле ничего не менялось: сравнивать с base удобно обычным diff.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: django-configmap
|
||||||
|
namespace: django
|
||||||
|
data:
|
||||||
|
production.py: |
|
||||||
|
import ast
|
||||||
|
import os
|
||||||
|
from .base import *
|
||||||
|
from logging.handlers import SysLogHandler
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
def _load_env_file(path):
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
for raw_line in f:
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
|
||||||
|
try:
|
||||||
|
value = ast.literal_eval(value)
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
value = value[1:-1]
|
||||||
|
if key and key not in os.environ:
|
||||||
|
os.environ[key] = value
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _read_secret_file(path, default=""):
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
# Fallback for manage.py launched via `kubectl exec` (outside entrypoint),
|
||||||
|
# so Django can still read DB/JWT values from Vault-injected files.
|
||||||
|
_load_env_file("/vault/secrets/django-postgresql")
|
||||||
|
_load_env_file("/vault/secrets/django-rabbitmq")
|
||||||
|
_load_env_file("/vault/secrets/django-s3")
|
||||||
|
_load_env_file("/vault/secrets/django-kafka")
|
||||||
|
_load_env_file("/vault/secrets/django-common")
|
||||||
|
|
||||||
|
if not os.environ.get("JWT_PRIVATE_KEY"):
|
||||||
|
os.environ["JWT_PRIVATE_KEY"] = _read_secret_file("/vault/secrets/django-jwt-private")
|
||||||
|
if not os.environ.get("JWT_PUBLIC_KEY"):
|
||||||
|
os.environ["JWT_PUBLIC_KEY"] = _read_secret_file("/vault/secrets/django-jwt-public")
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
FILE_UPLOAD_PERMISSIONS = 0o644
|
||||||
|
DEBUG = False
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
CSRF_TRUSTED_ORIGINS = ["https://sarex.local.lonsdaleites.ru", "http://sarex.local.lonsdaleites.ru"]
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
SECURE_SSL_REDIRECT = False
|
||||||
|
|
||||||
|
SECRET_KEY = 't2=9+($2f%7ptsdy4!rby$)mcfl1l%o2e@vs^d(g&(wwi&%k1v'
|
||||||
|
|
||||||
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
|
SERVERSETTINGS.cache_enabled = True
|
||||||
|
INSTALLED_APPS = list(INSTALLED_APPS) + ['corsheaders']
|
||||||
|
|
||||||
|
CORS_ALLOW_METHODS = (
|
||||||
|
'DELETE',
|
||||||
|
'GET',
|
||||||
|
'OPTIONS',
|
||||||
|
'PATCH',
|
||||||
|
'POST',
|
||||||
|
'PUT',
|
||||||
|
)
|
||||||
|
BASIC_USER_ID = 2
|
||||||
|
|
||||||
|
CORS_ALLOW_HEADERS = (
|
||||||
|
'accept',
|
||||||
|
'accept-encoding',
|
||||||
|
'authorization',
|
||||||
|
'content-type',
|
||||||
|
'user-agent',
|
||||||
|
'x-csrftoken',
|
||||||
|
'x-requested-with',
|
||||||
|
'x-token',
|
||||||
|
'Bearer',
|
||||||
|
)
|
||||||
|
|
||||||
|
HOST = "https://sarex.local.lonsdaleites.ru"
|
||||||
|
|
||||||
|
POSTGRES_DATABASE = os.environ.get('DJANGO_POSTGRES_DATABASE')
|
||||||
|
POSTGRES_USER = os.environ.get('DJANGO_POSTGRES_USER')
|
||||||
|
POSTGRES_PASSWORD = os.environ.get('DJANGO_POSTGRES_PASSWORD')
|
||||||
|
POSTGRES_HOST = os.environ.get('DJANGO_POSTGRES_HOST')
|
||||||
|
POSTGRES_PORTS = os.environ.get('DJANGO_POSTGRES_PORTS', "5432")
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django_prometheus.db.backends.postgresql',
|
||||||
|
'NAME': POSTGRES_DATABASE,
|
||||||
|
'USER': POSTGRES_USER,
|
||||||
|
'PASSWORD': POSTGRES_PASSWORD,
|
||||||
|
'HOST': POSTGRES_HOST,
|
||||||
|
'PORT': POSTGRES_PORTS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MEASUREMENTSETTINGS = MeasurementSettings()
|
||||||
|
WORKFLOWSSETTINGS = WorkFlowsSettings()
|
||||||
|
S3SETTINGS = S3Settings()
|
||||||
|
GATEWAYSETTINGS = GateWaySetttings()
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'filters': {
|
||||||
|
'require_debug_false': {
|
||||||
|
'()': 'django.utils.log.RequireDebugFalse',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'formatters': {
|
||||||
|
'verbose': {
|
||||||
|
'format': '[contactor] %(levelname)s %(asctime)s %(message)s',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'console': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
},
|
||||||
|
'sentry': {
|
||||||
|
'level': 'ERROR',
|
||||||
|
'filters': ['require_debug_false'],
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'': {
|
||||||
|
'handlers': ['console', 'sentry'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPARATOR_JWT = os.environ.get("COMPARATOR_JWT", "default_jwt")
|
||||||
|
COMPARATOR_URL = os.environ.get("COMPARATOR_URL", "https://wb.sarex.io/comparator")
|
||||||
|
COMPARATOR_SECTION = os.environ.get("COMPARATOR_SECTION", "sarex-production-storage")
|
||||||
|
|
||||||
|
SIMPLE_JWT = {
|
||||||
|
'ACCESS_TOKEN_LIFETIME': timedelta(hours=1),
|
||||||
|
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
|
||||||
|
'ROTATE_REFRESH_TOKENS': False,
|
||||||
|
'BLACKLIST_AFTER_ROTATION': True,
|
||||||
|
'UPDATE_LAST_LOGIN': False,
|
||||||
|
'ALGORITHM': 'RS512',
|
||||||
|
'SIGNING_KEY': os.environ.get("JWT_PRIVATE_KEY", "").replace("\\n", "\n"),
|
||||||
|
'VERIFYING_KEY': os.environ.get("JWT_PUBLIC_KEY", "").replace("\\n", "\n"),
|
||||||
|
'AUDIENCE': None,
|
||||||
|
'ISSUER': os.environ.get('SIMPLE_JWT_ISSUER', 'default_issuer'),
|
||||||
|
'AUTH_HEADER_TYPES': ('Bearer',),
|
||||||
|
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
|
||||||
|
'USER_ID_FIELD': 'id',
|
||||||
|
'USER_ID_CLAIM': 'user_id',
|
||||||
|
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
|
||||||
|
'TOKEN_TYPE_CLAIM': 'token_type',
|
||||||
|
'JTI_CLAIM': 'jti',
|
||||||
|
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
|
||||||
|
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
|
||||||
|
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
|
||||||
|
}
|
||||||
|
|
||||||
|
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||||
|
DEFAULT_FILE_STORAGE = 'sarex.core.storages.CustomS3Boto3Storage'
|
||||||
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 268435456
|
||||||
|
|
||||||
|
if not os.environ.get('ISOLATED', False):
|
||||||
|
import sentry_sdk
|
||||||
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
|
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn="https://3df2f4b8d3d14595a06c92e9d7c562cb@sentry.io/1501541",
|
||||||
|
integrations=[DjangoIntegration()],
|
||||||
|
environment=os.environ.get('SENTRY_ENVIRONMENT', 'production'),
|
||||||
|
send_default_pii=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
COMPARISON_API_URL = f"{os.environ.get('WORKFLOWSSETTINGS_HOST')}/comparisons"
|
||||||
|
DOCUMENTATION_API_URL = f"{os.environ.get('WORKFLOWSSETTINGS_HOST')}/documentations"
|
||||||
|
PDM_FILES_API_URL = f"{os.environ.get('WORKFLOWSSETTINGS_HOST')}/files"
|
||||||
|
|
||||||
|
WORKFLOWS_TASKS = {
|
||||||
|
"update_orthomosaic_data": {
|
||||||
|
"image": f"{os.environ.get('WORKFLOWSSETTINGS_REGISTRY')}/update-orthomosaic-data:dev",
|
||||||
|
"service_requests": ["django-auth"],
|
||||||
|
"backoff_limit": 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Сессионная аутентификация без проверки CSRF.
|
||||||
|
#
|
||||||
|
# SPA платформы аутентифицируется сессионной кукой и не прикладывает
|
||||||
|
# заголовок X-CSRFToken, а SessionAuthentication из DRF требует его на
|
||||||
|
# любом небезопасном методе. Наружу это выглядело так:
|
||||||
|
# DELETE /api/core/videos/<id>/ → 403
|
||||||
|
# {"detail": "CSRF Failed: CSRF token missing."}
|
||||||
|
#
|
||||||
|
# Проверено на контуре: тот же запрос с X-CSRFToken проходит, и он же с
|
||||||
|
# заголовком Authorization: Bearer проходит тоже — до SessionAuthentication
|
||||||
|
# дело в этом случае просто не доходит, потому что simple_jwt в списке ниже
|
||||||
|
# стоит раньше. То есть бэкенд исправен, чинить нужно ровно сочетание
|
||||||
|
# "кука без заголовка".
|
||||||
|
#
|
||||||
|
# ЧЕМ ПЛАТИМ: с запросов, авторизованных кукой, снимается защита от CSRF.
|
||||||
|
# Основной вектор закрыт самими куками — sessionid и csrftoken выставляются
|
||||||
|
# с SameSite=Lax, и браузер не пошлёт их при кросс-сайтовом DELETE. Щель
|
||||||
|
# остаётся одна: SameSite смотрит на site, а не на origin, поэтому соседи
|
||||||
|
# по домену (minio., rabbitmq., dashboard.) считаются "своими", а в MinIO
|
||||||
|
# лежит пользовательский контент.
|
||||||
|
#
|
||||||
|
# Импорт DRF прямо в модуле настроек безопасен НЕ всегда — обычно так и
|
||||||
|
# ловят AppRegistryNotReady. Здесь проверено запуском django.setup() внутри
|
||||||
|
# работающего пода: rest_framework.authentication тянет только
|
||||||
|
# django.contrib.auth и rest_framework.exceptions, и ни один из них к
|
||||||
|
# моделям на уровне модуля не обращается.
|
||||||
|
from rest_framework.authentication import SessionAuthentication
|
||||||
|
|
||||||
|
|
||||||
|
class CsrfExemptSessionAuthentication(SessionAuthentication):
|
||||||
|
def enforce_csrf(self, request):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': (
|
||||||
|
'rest_framework.pagination.LimitOffsetPagination' ),
|
||||||
|
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
|
||||||
|
'PAGE_SIZE': 1000, 'DEFAULT_FILTER_BACKENDS': [
|
||||||
|
'django_filters.rest_framework.DjangoFilterBackend' ],
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'sarex.authentication.backends.ZitadelJWTAuthentication',
|
||||||
|
'rest_framework.authentication.RemoteUserAuthentication',
|
||||||
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
|
'config.settings.production.CsrfExemptSessionAuthentication',
|
||||||
|
'sarex.authentication.backends.JWTAuthentication' ],
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
|
'rest_framework.permissions.IsAuthenticated', ] }
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'sarex.authentication.backends.CustomRemoteUserBackend',
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
'guardian.backends.ObjectPermissionBackend',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django_prometheus.middleware.PrometheusBeforeMiddleware',
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
#'django_keycloak.middlewares.AuthorizationHeaderMiddleware',
|
||||||
|
#'django_keycloak.middlewares.KeycloakSessionMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
#'django.contrib.auth.middleware.RemoteUserMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'django_user_agents.middleware.UserAgentMiddleware',
|
||||||
|
'simple_history.middleware.HistoryRequestMiddleware',
|
||||||
|
'django_prometheus.middleware.PrometheusAfterMiddleware', ]
|
||||||
|
|
||||||
|
|
||||||
|
class KeyCloakSettings(BaseSettings):
|
||||||
|
client_id: str = "client_id"
|
||||||
|
client_secret: str = "client_secret"
|
||||||
|
discovery_url: str = "https://login.wb.sarex.io/realms/sarex/.well-known/openid-configuration"
|
||||||
|
staff: Optional[str] = "Sarex staff"
|
||||||
|
superuser: Optional[str] = "Sarex superusers"
|
||||||
|
sync_with_django: bool = True
|
||||||
|
sync_admin: bool = False
|
||||||
|
group_prefix: str = 'Sarex-Role'
|
||||||
|
company_prefix: str = 'Sarex-Company'
|
||||||
|
department_prefix: str = 'Sarex-Department'
|
||||||
|
position_prefix: str = 'Sarex-Position'
|
||||||
|
separator: str = '__'
|
||||||
|
sync_user_groups: bool = False
|
||||||
|
sync_user_positions: bool = False
|
||||||
|
sync_user_departments: bool = False
|
||||||
|
sync_user_companies: bool = False
|
||||||
|
use_redirect_logout: bool = False
|
||||||
|
logout_redirect_uri: str = "/"
|
||||||
|
default_group_name: Optional[str] = 'Тест'
|
||||||
|
default_company_name: Optional[str] = 'Брусника'
|
||||||
|
trusted_uri: List[str] = ['/api/core/orthophotos/', '/api/token', '/api/token/me']
|
||||||
|
trusted_uri: List[str] = []
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_prefix = "KC_"
|
||||||
|
|
||||||
|
|
||||||
|
KEYCLOAKSETTINGS = KeyCloakSettings()
|
||||||
|
|
||||||
|
REMOTE_USER_DEFAULT_COMPANY_ID = 1
|
||||||
|
SAREX_MODULES = [
|
||||||
|
{
|
||||||
|
"name": "Замечания",
|
||||||
|
"uri": "/remarks"
|
||||||
|
},
|
||||||
|
# {
|
||||||
|
# "name": "Управление проектами",
|
||||||
|
# "uri": "/management/projects",
|
||||||
|
# },
|
||||||
|
{
|
||||||
|
"name": "Замечания V2",
|
||||||
|
"uri": "/issues"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Документация",
|
||||||
|
"uri": "/documentations",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Согласование документов",
|
||||||
|
"uri": "/reviews"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Рабочие процессы",
|
||||||
|
"uri": "/processes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Запросы",
|
||||||
|
"uri": "/rfi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Управление проектами",
|
||||||
|
"uri": "/management/projects"
|
||||||
|
},
|
||||||
|
|
||||||
|
# {
|
||||||
|
# "name": "Обзор",
|
||||||
|
# "uri": "/projects"
|
||||||
|
# },
|
||||||
|
{
|
||||||
|
"name": "Передача документации",
|
||||||
|
"uri": "/transmittal"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
AUTH_SETTINGS = {
|
||||||
|
"refresh_token": False,
|
||||||
|
"refresh_token_uri": "/api/token/me",
|
||||||
|
"refresh_oauth_token": True,
|
||||||
|
"refresh_oauth_token_uri": "/oauth/token",
|
||||||
|
"refresh_time": 240,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG=True
|
||||||
|
WEB_APP_AUTH_MODE='jwt-session-based'
|
||||||
|
|
||||||
|
|
||||||
|
SAREX_MODULES_SETTINGS = {
|
||||||
|
"aero": {
|
||||||
|
"enable_new_media": True
|
||||||
|
},
|
||||||
|
"sso_logout_redirect": True
|
||||||
|
}
|
||||||
262
apps/django/aero/kustomization.yaml
Normal file
262
apps/django/aero/kustomization.yaml
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: django
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
|
||||||
|
patches:
|
||||||
|
# Свой nginx.conf вместо базового: в base зашиты upstream'ы на namespace,
|
||||||
|
# которых в контуре нет, и nginx из-за этого не стартовал. Подробности —
|
||||||
|
# в шапке nginx-configmap.yaml.
|
||||||
|
#
|
||||||
|
# ИМЕННО ПАТЧ, а не resources. Объект с тем же kind+name уже пришёл из
|
||||||
|
# ../base, и добавление его вторым ресурсом валит сборку целиком:
|
||||||
|
# may not add resource with an already registered id:
|
||||||
|
# ConfigMap.v1.[noGrp]/nginx-configmap.django
|
||||||
|
# Перекрывать базовый ресурс можно только патчем.
|
||||||
|
- path: nginx-configmap.yaml
|
||||||
|
target:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: nginx-configmap
|
||||||
|
# Настройки django: в base захардкожен домен ЧУЖОГО контура. По нему backend
|
||||||
|
# строил абсолютные ссылки на медиа, и браузер уходил на
|
||||||
|
# https://sarex.contour.infra.sarex.tech/media/state/previews/....png
|
||||||
|
# получая 404. Причина копии та же, что у nginx-configmap: настройки лежат
|
||||||
|
# одним значением data.production.py, заменить можно только целиком.
|
||||||
|
- path: django-configmap.yaml
|
||||||
|
target:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: django-configmap
|
||||||
|
# --- celery: те же правки, что и у backend --------------------------------
|
||||||
|
# Worker крутит тот же образ и те же настройки, поэтому и расхождения с base
|
||||||
|
# у него ровно те же: нет Kafka, нет Zitadel, MinIO живёт внутри кластера.
|
||||||
|
# Обоснования — в блоке backend ниже, здесь не дублирую.
|
||||||
|
#
|
||||||
|
# SERVER_ZITADEL_ENABLED правки не требует: в base у celery он уже "False"
|
||||||
|
# (в отличие от backend).
|
||||||
|
- target: {kind: HelmRelease, name: celery}
|
||||||
|
patch: |
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/celery/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-django-kafka
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/celery/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-kafka
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/celery/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-django-common
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/celery/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-common
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/celery/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-s3
|
||||||
|
value: |-
|
||||||
|
{{- with secret "secrets/data/minio/apps/django" }}
|
||||||
|
AWS_S3_ENDPOINT_URL=http://minio.minio.svc.cluster.local:9000
|
||||||
|
S3_HOST=http://minio.minio.svc.cluster.local:9000
|
||||||
|
{{- $buckets := index .Data.data "buckets" }}
|
||||||
|
S3_BUCKET={{ if gt (len $buckets) 0 }}{{ index (index $buckets 0) "name" }}{{ else }}django{{ end }}
|
||||||
|
S3_LOGIN={{ index .Data.data "access_key" }}
|
||||||
|
S3_PASSWORD={{ index .Data.data "secret_key" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
# Стартовый скрипт без подключения django-kafka: этого секрета в контуре
|
||||||
|
# нет. migrate и создание администратора сюда НЕ добавляются — схемой
|
||||||
|
# владеет backend, а два процесса, катящих миграции наперегонки при
|
||||||
|
# одновременном старте, — это гонка на ровном месте.
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/celery/deployment/args/_default/0
|
||||||
|
value: |
|
||||||
|
set -a
|
||||||
|
[ -f /vault/secrets/django-postgresql ] && . /vault/secrets/django-postgresql
|
||||||
|
[ -f /vault/secrets/django-rabbitmq ] && . /vault/secrets/django-rabbitmq
|
||||||
|
[ -f /vault/secrets/django-s3 ] && . /vault/secrets/django-s3
|
||||||
|
[ -f /vault/secrets/django-jwt-private ] && export JWT_PRIVATE_KEY="$(cat /vault/secrets/django-jwt-private)"
|
||||||
|
[ -f /vault/secrets/django-jwt-public ] && export JWT_PUBLIC_KEY="$(cat /vault/secrets/django-jwt-public)"
|
||||||
|
set +a
|
||||||
|
exec celery -A config worker -B -l info -E -Q default -n default_worker.%h --concurrency=2
|
||||||
|
|
||||||
|
# Адреса на wb.sarex.io — чужой контур, из aero недостижим. В base их
|
||||||
|
# четыре, и все четыре у celery: у backend те же переменные уже указывают
|
||||||
|
# внутрь кластера. WORKFLOWS_USE=1, так что оставить как есть — значит
|
||||||
|
# получить зависания на недоступном домене вместо явной ошибки.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/celery/envs/15/name
|
||||||
|
value: SERVER_API_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/celery/envs/15/value/_default
|
||||||
|
value: https://sarex.local.lonsdaleites.ru
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/celery/envs/16/name
|
||||||
|
value: SERVER_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/celery/envs/16/value/_default
|
||||||
|
value: https://sarex.local.lonsdaleites.ru
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/celery/envs/17/name
|
||||||
|
value: WORKFLOWS_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/celery/envs/17/value/_default
|
||||||
|
value: http://backend-svc.processing.svc.cluster.local:80
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/celery/envs/18/name
|
||||||
|
value: WORKFLOWS_BASE_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/celery/envs/18/value/_default
|
||||||
|
value: http://backend-svc.django.svc.cluster.local:80
|
||||||
|
|
||||||
|
# Тег образа выравнен с backend. В base у celery production_a96dead0, а у
|
||||||
|
# backend production_f813140d — worker и web крутили бы разный код, и
|
||||||
|
# рассинхрон вылезал бы не при старте, а на конкретной задаче: worker
|
||||||
|
# десериализует аргументы по своей версии модели.
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/celery/image/name/_default
|
||||||
|
value: cr.yandex/crp3ccidau046kdj8g9q/backend:production_f813140d
|
||||||
|
|
||||||
|
# --- s3-proxy: S3 внутри кластера ------------------------------------------
|
||||||
|
# В base адрес MinIO зашит ВНУТРИ vault-шаблона:
|
||||||
|
# AWS_API_ENDPOINT=https://minio.contour.infra.sarex.tech
|
||||||
|
# Это чужой контур. Через values не перекрыть — значение внутри шаблона
|
||||||
|
# аннотации, поэтому заменяем аннотацию целиком, как у backend и celery.
|
||||||
|
#
|
||||||
|
# Проявлялось не отказом в соединении, а тем, что s3-proxy ДОСТУЧАЛСЯ до
|
||||||
|
# чужого MinIO и получил от него настоящий ответ S3:
|
||||||
|
# SignatureDoesNotMatch: The request signature we calculated does not
|
||||||
|
# match the signature you provided
|
||||||
|
# наружу это выглядело как 500 на каждую плитку /media/orthophotos/...
|
||||||
|
# Наши ключи в чужом хранилище, разумеется, не подходят — и хорошо, что так:
|
||||||
|
# иначе контур молча читал бы и писал в чужой S3.
|
||||||
|
- target: {kind: HelmRelease, name: s3-proxy}
|
||||||
|
patch: |
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/s3-proxy/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-s3
|
||||||
|
value: |
|
||||||
|
{{- with secret "secrets/data/minio/apps/django" }}
|
||||||
|
AWS_API_ENDPOINT=http://minio.minio.svc.cluster.local:9000
|
||||||
|
{{- $buckets := index .Data.data "buckets" }}
|
||||||
|
AWS_S3_BUCKET={{ if gt (len $buckets) 0 }}{{ index (index $buckets 0) "name" }}{{ else }}django{{ end }}
|
||||||
|
AWS_ACCESS_KEY_ID={{ index .Data.data "access_key" }}
|
||||||
|
AWS_SECRET_ACCESS_KEY={{ index .Data.data "secret_key" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
# --- backend: убираем зависимости, которых в контуре нет ------------------
|
||||||
|
- target: {kind: HelmRelease, name: backend}
|
||||||
|
patch: |
|
||||||
|
# Kafka и Zitadel в контуре не развёрнуты. Само приложение без них живёт:
|
||||||
|
# * SERVER_KAFKA_ENABLED в base уже "False", а в sarex-backend продюсер
|
||||||
|
# создаётся только под флагом (config/settings/deps/kafka.py);
|
||||||
|
# * zitadel_enabled читается ровно в одном месте — update_ams_user()
|
||||||
|
# в sarex/core/utils.py, которая при выключенном флаге сразу выходит.
|
||||||
|
# К аутентификации Zitadel отношения не имеет.
|
||||||
|
#
|
||||||
|
# Но аннотации инжекции убрать ОБЯЗАТЕЛЬНО: vault-agent-init не отрендерит
|
||||||
|
# шаблон для несуществующего секрета, упадёт, и под навсегда останется в
|
||||||
|
# Init:Error — приложение до своих флагов даже не дойдёт.
|
||||||
|
#
|
||||||
|
# В JSON Pointer слэш внутри ключа экранируется как ~1, поэтому
|
||||||
|
# vault.hashicorp.com/agent-... записан как vault.hashicorp.com~1agent-...
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-django-kafka
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-kafka
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-django-common
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-common
|
||||||
|
|
||||||
|
# S3 внутри кластера, а не через внешний домен. В base шаблон прибит к
|
||||||
|
# https://minio.contour.infra.sarex.tech — чужому домену, который отсюда
|
||||||
|
# не резолвится. Через values это не перекрыть: адрес зашит ВНУТРИ
|
||||||
|
# vault-шаблона аннотации, поэтому заменяем аннотацию целиком.
|
||||||
|
# Ходим напрямую в сервис MinIO, как это делал compose-стек.
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-s3
|
||||||
|
value: |-
|
||||||
|
{{- with secret "secrets/data/minio/apps/django" }}
|
||||||
|
AWS_S3_ENDPOINT_URL=http://minio.minio.svc.cluster.local:9000
|
||||||
|
S3_HOST=http://minio.minio.svc.cluster.local:9000
|
||||||
|
{{- $buckets := index .Data.data "buckets" }}
|
||||||
|
S3_BUCKET={{ if gt (len $buckets) 0 }}{{ index (index $buckets 0) "name" }}{{ else }}django{{ end }}
|
||||||
|
S3_LOGIN={{ index .Data.data "access_key" }}
|
||||||
|
S3_PASSWORD={{ index .Data.data "secret_key" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
# Выключаем Zitadel. Операция test перед replace — страховка от смещения
|
||||||
|
# индекса: если в base порядок envs изменится, патч упадёт явно, вместо
|
||||||
|
# того чтобы молча перезаписать соседнюю переменную.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/2/name
|
||||||
|
value: SERVER_ZITADEL_ENABLED
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/2/value/_default
|
||||||
|
value: "False"
|
||||||
|
|
||||||
|
# Адреса на чужой контур. У celery те же две переменные поправлены выше,
|
||||||
|
# у backend они оставались с sarex.contour.infra.sarex.tech.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/17/name
|
||||||
|
value: SERVER_API_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/17/value/_default
|
||||||
|
value: https://sarex.local.lonsdaleites.ru
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/18/name
|
||||||
|
value: SERVER_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/18/value/_default
|
||||||
|
value: https://sarex.local.lonsdaleites.ru
|
||||||
|
|
||||||
|
# --- Администратор платформы --------------------------------------------
|
||||||
|
# Учётка приезжает из Vault под именами, которые ждёт штатная
|
||||||
|
# createsuperuser --noinput. Секрет кладёт ansible
|
||||||
|
# (vault_app_secrets, secrets/apps/django/superuser).
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-django-superuser
|
||||||
|
value: secrets/data/apps/django/superuser
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-django-superuser
|
||||||
|
value: |-
|
||||||
|
{{- with secret "secrets/data/apps/django/superuser" -}}
|
||||||
|
DJANGO_SUPERUSER_USERNAME={{ index .Data.data "username" }}
|
||||||
|
DJANGO_SUPERUSER_EMAIL={{ index .Data.data "email" }}
|
||||||
|
DJANGO_SUPERUSER_PASSWORD={{ index .Data.data "password" }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
# Стартовый скрипт целиком, а не точечная правка: JSON6902 умеет заменять
|
||||||
|
# только элемент списка, а вся логика запуска — один shell-блок args[0].
|
||||||
|
# Отличия от base:
|
||||||
|
# * не подключается django-kafka — этого секрета в контуре нет;
|
||||||
|
# * добавлены migrate и создание администратора.
|
||||||
|
#
|
||||||
|
# Почему migrate здесь, хотя его же делает /opt/sarex/entrypoint.sh:
|
||||||
|
# там он запущен без set -e, и его падение НИКАК не мешает uwsgi
|
||||||
|
# стартовать. Именно так контур несколько часов отдавал 200 на /admin/
|
||||||
|
# с пустой схемой БД, пока backend не мог аутентифицироваться. Здесь
|
||||||
|
# команда выполняется под set -e из command базы (/bin/sh -ec), поэтому
|
||||||
|
# неудача валит под в CrashLoopBackOff — то есть становится видимой.
|
||||||
|
# Повторный прогон внутри entrypoint.sh при уже накатанной схеме
|
||||||
|
# отрабатывает вхолостую за секунды.
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/deployment/args/_default/0
|
||||||
|
value: |
|
||||||
|
set -a
|
||||||
|
[ -f /vault/secrets/django-postgresql ] && . /vault/secrets/django-postgresql
|
||||||
|
[ -f /vault/secrets/django-rabbitmq ] && . /vault/secrets/django-rabbitmq
|
||||||
|
[ -f /vault/secrets/django-s3 ] && . /vault/secrets/django-s3
|
||||||
|
[ -f /vault/secrets/django-superuser ] && . /vault/secrets/django-superuser
|
||||||
|
[ -f /vault/secrets/django-jwt-private ] && export JWT_PRIVATE_KEY="$(cat /vault/secrets/django-jwt-private)"
|
||||||
|
[ -f /vault/secrets/django-jwt-public ] && export JWT_PUBLIC_KEY="$(cat /vault/secrets/django-jwt-public)"
|
||||||
|
set +a
|
||||||
|
|
||||||
|
python manage.py migrate --noinput
|
||||||
|
|
||||||
|
# Идемпотентно по построению: команда отрабатывает при КАЖДОМ старте
|
||||||
|
# пода, а на уже существующем логине штатно возвращает
|
||||||
|
# "That username is already taken" с ненулевым кодом. Ошибка ожидаема
|
||||||
|
# и гасится веткой else, иначе set -e ронял бы под на втором запуске.
|
||||||
|
if [ -n "${DJANGO_SUPERUSER_USERNAME:-}" ]; then
|
||||||
|
if python manage.py createsuperuser --noinput; then
|
||||||
|
echo "entrypoint: администратор создан"
|
||||||
|
else
|
||||||
|
echo "entrypoint: администратор уже существует, пропускаем"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /opt/sarex/entrypoint.sh
|
||||||
194
apps/django/aero/nginx-configmap.yaml
Normal file
194
apps/django/aero/nginx-configmap.yaml
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
# Копия nginx-configmap из base с правками под контур aero.
|
||||||
|
#
|
||||||
|
# Патчем это сделать нельзя: конфиг лежит одной строкой в data.nginx.conf,
|
||||||
|
# и kustomize умеет только заменить значение целиком.
|
||||||
|
#
|
||||||
|
# ЧТО ИЗМЕНЕНО ОТНОСИТЕЛЬНО base — три вещи:
|
||||||
|
#
|
||||||
|
# 1. Убран location /api/pm/. Сервис pm в контуре не нужен и не планируется.
|
||||||
|
#
|
||||||
|
# 2. Для workspaces и processing адрес вынесен в переменную и добавлен
|
||||||
|
# resolver. Причина: nginx резолвит все upstream'ы ПРИ СТАРТЕ и падает
|
||||||
|
# целиком, если хоть один не найден:
|
||||||
|
# nginx: [emerg] host not found in upstream "backend-svc.pm..."
|
||||||
|
# Из-за этого весь frontend уходил в CrashLoopBackOff, хотя не работал
|
||||||
|
# лишь один маршрут. С переменной nginx откладывает резолвинг до запроса:
|
||||||
|
# сейчас эти пути отдают 502, а когда сервисы приедут — заработают сами,
|
||||||
|
# без правки конфига и перезапуска.
|
||||||
|
#
|
||||||
|
# ПОРЯДОК ДИРЕКТИВ ВНУТРИ ТАКОГО location ЗНАЧИМ: set обязан идти ДО
|
||||||
|
# rewrite ... break. Обе директивы принадлежат ngx_http_rewrite_module, а
|
||||||
|
# флаг break прекращает обработку его директив в этом location — set после
|
||||||
|
# него просто не выполняется, переменная остаётся пустой, и запрос падает с
|
||||||
|
# [error] no host in upstream ":80"
|
||||||
|
# отдавая наружу 500. Именно так и было: / работал, а /workspaces-v2/... и
|
||||||
|
# /workflows/... возвращали 500, пока set стоял ниже rewrite.
|
||||||
|
#
|
||||||
|
# 3. backend-svc оставлен прямой ссылкой: он в этом же namespace и существует
|
||||||
|
# всегда, откладывать его резолвинг незачем — наоборот, ошибка в имени
|
||||||
|
# обнаружится сразу при старте.
|
||||||
|
#
|
||||||
|
# Адрес resolver'а — CoreDNS кластера. В k3s он называется kube-dns
|
||||||
|
# (имя сохранено для совместимости), namespace kube-system.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: nginx-configmap
|
||||||
|
namespace: django
|
||||||
|
data:
|
||||||
|
nginx.conf: |
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
use epoll;
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
# Basic Settings
|
||||||
|
large_client_header_buffers 8 128k;
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 300;
|
||||||
|
types_hash_max_size 2048;
|
||||||
|
client_max_body_size 5000M;
|
||||||
|
client_header_buffer_size 5M;
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Logging Settings
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
# GZIP Settings
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
# Резолвер нужен для upstream'ов, заданных переменной (см. ниже).
|
||||||
|
resolver kube-dns.kube-system.svc.cluster.local valid=30s ipv6=off;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
root /opt/react_client/;
|
||||||
|
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' '*' always;
|
||||||
|
|
||||||
|
location = /static/index.bundle.js {
|
||||||
|
add_header Cache-Control 'no-store no-cache, must-revalidate, proxy-revalidate, max-age=0';
|
||||||
|
if_modified_since off;
|
||||||
|
expires off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Выбор способа аутентификации. Фронтенд на старте делает
|
||||||
|
# fetch("/static/config.json") и, если там есть auth_type,
|
||||||
|
# БЕЗУСЛОВНО перекрывает им режим, зашитый в сборку
|
||||||
|
# (src/Model/api/http-service.ts). Значения ровно два:
|
||||||
|
# "zitadel" и "original"; original — классический вход по
|
||||||
|
# логину и паролю с JWT, ради которого backend и получает из
|
||||||
|
# Vault ключи RS512.
|
||||||
|
#
|
||||||
|
# В образе sarex-frontend-dev:contour_* этот файл лежит со
|
||||||
|
# значением zitadel и хостом zitadel.contour.infra.sarex.tech —
|
||||||
|
# чужой контур, из aero недоступен. Из-за него платформа
|
||||||
|
# редиректила на /oauth/v2/authorize и получала
|
||||||
|
# invalid_request: The requested redirect_uri is missing
|
||||||
|
# in the client configuration
|
||||||
|
# то есть войти было нельзя в принципе.
|
||||||
|
#
|
||||||
|
# Отдаём файл прямо из nginx, не подкладывая том: сам
|
||||||
|
# static/config.json в sarex-frontend лежит в .gitignore и
|
||||||
|
# заполняется на каждом стенде своим — это штатная точка
|
||||||
|
# расширения, а не обход. В апстримном nginx.conf под него уже
|
||||||
|
# заведён такой же location =, только читающий с диска.
|
||||||
|
#
|
||||||
|
# Подменить через localStorage нельзя: оверрайд оттуда
|
||||||
|
# действует лишь для endpoint'ов stage, preprod и local,
|
||||||
|
# contour в этом списке нет.
|
||||||
|
#
|
||||||
|
# ВАЖНО ПРИ ПРОВЕРКЕ: правка НЕ видна с первой перезагрузки.
|
||||||
|
# Класс Config (src/shared/config/config.ts) держит копию в
|
||||||
|
# localStorage под ключом "config" и на старте берёт СИНХРОННО
|
||||||
|
# кеш, а файл сверяет уже асинхронно:
|
||||||
|
# if (this.cacheData) { this.data = this.cacheData;
|
||||||
|
# this.invalidateCache(); }
|
||||||
|
# а http-service.ts читает auth_type в момент импорта модуля.
|
||||||
|
# Поэтому первая загрузка после правки лишь обновляет кеш, а
|
||||||
|
# применяется он со второй. Ускорить —
|
||||||
|
# localStorage.removeItem('config')
|
||||||
|
# Консоль удобно открыть на /static/config.json: тот же origin,
|
||||||
|
# но JS не выполняется и редиректа на Zitadel не будет.
|
||||||
|
location = /static/config.json {
|
||||||
|
default_type application/json;
|
||||||
|
add_header Cache-Control 'no-store no-cache, must-revalidate, proxy-revalidate, max-age=0';
|
||||||
|
if_modified_since off;
|
||||||
|
expires off;
|
||||||
|
return 200 '{"auth_type":"original"}';
|
||||||
|
}
|
||||||
|
|
||||||
|
# location /api/pm/ из base удалён: сервис pm контуру не нужен.
|
||||||
|
|
||||||
|
location ~^/(api|admin)/ {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_pass http://backend-svc.django.svc.cluster.local:80;
|
||||||
|
}
|
||||||
|
|
||||||
|
# workspaces появится позже — резолвим лениво, через переменную.
|
||||||
|
location ~^/workspaces-v2/(.+).js {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
set $up_workspaces frontend-svc.workspaces.svc.cluster.local;
|
||||||
|
rewrite /workspaces-v2/(.+) /$1 break;
|
||||||
|
proxy_pass http://$up_workspaces:80;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~^/workspaces-v2/(.+)\.wasm$ {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
set $up_workspaces_wasm frontend-svc.workspaces.svc.cluster.local;
|
||||||
|
rewrite ^/workspaces-v2/(.+) /$1 break;
|
||||||
|
proxy_pass http://$up_workspaces_wasm:80;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @index {
|
||||||
|
add_header Cache-Control 'no-cache, must-revalidate, proxy-revalidate, max-age=0';
|
||||||
|
if_modified_since off;
|
||||||
|
expires off;
|
||||||
|
try_files /static/index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# processing появится позже — тоже лениво.
|
||||||
|
location ~^/workflows/(.+).js {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
set $up_processing frontend-svc.processing.svc.cluster.local;
|
||||||
|
rewrite /workflows/(.+) /$1 break;
|
||||||
|
proxy_pass http://$up_processing:80;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /service-worker.js {
|
||||||
|
try_files /static/$uri @index;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri @index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
apps/measurements/aero/kustomization.yaml
Normal file
41
apps/measurements/aero/kustomization.yaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: measurements
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- target: {kind: HelmRelease, name: measurements}
|
||||||
|
patch: |
|
||||||
|
# Имя сервиса приводим к measurements-service, как в brusnika-*.
|
||||||
|
#
|
||||||
|
# В base сервис называется measurements-svc, а django ходит на
|
||||||
|
# MEASUREMENTS_HOST=http://measurements-service.measurements.svc.cluster.local:8000/api
|
||||||
|
# (apps/django/base/backend.yaml). Переименовать сервис дешевле, чем
|
||||||
|
# патчить переменную в двух релизах django (backend и celery), и заодно
|
||||||
|
# совпадает с тем, как это сделано в остальных кластерах.
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/service/name/_default
|
||||||
|
value: measurements-service
|
||||||
|
|
||||||
|
# S3: бакет берём из секрета, а не из шаблона.
|
||||||
|
#
|
||||||
|
# В base имя бакета ЗАШИТО прямо в vault-шаблоне — "buckets":["measurements"].
|
||||||
|
# Из-за этого measurements складывал файлы в собственный бакет, тогда как
|
||||||
|
# медиахранилище контура одно и называется sarex-media-storage: туда пишет
|
||||||
|
# django (S3_BUCKET) и оттуда же раздаёт s3-proxy на маршруте /media/.
|
||||||
|
# Разные бакеты означали, что ссылка на файл измерения, отданная через
|
||||||
|
# django, вела бы в пустоту.
|
||||||
|
#
|
||||||
|
# Endpoint в base уже приезжает из секрета (.Data.data.client.endpoint) —
|
||||||
|
# достраиваем до той же схемы и бакет, чтобы имя задавалось в ОДНОМ месте
|
||||||
|
# (vault_app_secrets в aero/roles/sarex_stack/defaults/main.yml), а не
|
||||||
|
# дублировалось в манифесте. Заменить можно только аннотацию целиком:
|
||||||
|
# значение зашито внутри шаблона, через values его не перекрыть.
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-measurements-s3
|
||||||
|
value: |-
|
||||||
|
{{- with secret "secrets/data/minio/apps/measurements" -}}
|
||||||
|
S3_JSON_SETTINGS='{"host":"{{ index .Data.data.client "endpoint" }}","login":"{{ index .Data.data "access_key" }}","password":"{{ index .Data.data "secret_key" }}","verify":false,"buckets":["{{ index .Data.data "bucket" }}"]}'
|
||||||
|
{{- end -}}
|
||||||
309
apps/processing/aero/kustomization.yaml
Normal file
309
apps/processing/aero/kustomization.yaml
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: processing
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
|
||||||
|
patches:
|
||||||
|
# --- Состав: только api и engine-low ---------------------------------------
|
||||||
|
# engine удалён: в base у него replicaCount 0, то есть он и там выключен, а
|
||||||
|
# образ прибит к тегу с опечаткой в имени (workflows-endigne_prod). Рабочий
|
||||||
|
# обработчик — engine-low, он же на более свежем образе contour_*.
|
||||||
|
- target: {kind: HelmRelease, name: engine}
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: engine
|
||||||
|
namespace: processing
|
||||||
|
|
||||||
|
# frontend идёт из base без патчей. Адреса бэкендов вшиты в бандл на этапе
|
||||||
|
# сборки (константа __BUILD_ENV__), но сборка ugok2 обращается к тому же
|
||||||
|
# origin относительными путями — иначе в остальных кластерах не понадобился
|
||||||
|
# бы отдельный маршрут /workflows/api/ перед /workflows/. Поэтому фронтенд
|
||||||
|
# публикуется на домене платформы, а не на своём поддомене: маршруты в
|
||||||
|
# infrastructure/istio-config/aero, VirtualService platform.
|
||||||
|
|
||||||
|
# --- workflows-api ----------------------------------------------------------
|
||||||
|
- target: {kind: HelmRelease, name: workflows-api}
|
||||||
|
patch: |
|
||||||
|
# S3 приезжает из Vault отдельным ФАЙЛОМ, а не парой переменных:
|
||||||
|
# приложение читает по этому пути целый JSON. В base путь ведёт в
|
||||||
|
# /etc/sarex/yc-s3/, куда в контуре ничего не смонтировано.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/3/name
|
||||||
|
value: S3_SERVICE_ACCOUNT
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/3/value/_default
|
||||||
|
value: /vault/secrets/processing-s3
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-processing-s3
|
||||||
|
value: secrets/data/minio/apps/processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-processing-s3
|
||||||
|
value: |-
|
||||||
|
{{- with secret "secrets/data/minio/apps/processing" -}}
|
||||||
|
{
|
||||||
|
"host": "{{ index .Data.data "host" }}",
|
||||||
|
"bucket": "{{ index .Data.data "bucket" }}",
|
||||||
|
"access_key_id": "{{ index .Data.data "access_key_id" }}",
|
||||||
|
"secret_access_key": "{{ index .Data.data "secret_access_key" }}",
|
||||||
|
"region": "{{ index .Data.data "region" }}",
|
||||||
|
"verify": false
|
||||||
|
}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
# --- engine-low -------------------------------------------------------------
|
||||||
|
# Патчи адресуются по индексу, поэтому перед каждым replace идёт op: test на
|
||||||
|
# имя переменной. Если в base порядок envs изменится, сборка упадёт явно,
|
||||||
|
# вместо того чтобы молча перезаписать соседнее значение.
|
||||||
|
- target: {kind: HelmRelease, name: engine-low}
|
||||||
|
patch: |
|
||||||
|
# Sentry — внешний домен, из контура недостижим. Значение прокидывается
|
||||||
|
# ещё и в каждый под задачи, поэтому зануляем в одном месте.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/2/name
|
||||||
|
value: WORKFLOWS_SENTRY_DSN
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/2/value/_default
|
||||||
|
value: ""
|
||||||
|
|
||||||
|
# В base адрес django неверный даже для кластеров, где он есть: сервис
|
||||||
|
# называется backend-svc и слушает 80, а не backend:8000. У api то же
|
||||||
|
# значение уже правильное.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/5/name
|
||||||
|
value: DJANGO_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/5/value/_default
|
||||||
|
value: http://backend-svc.django.svc.cluster.local:80
|
||||||
|
|
||||||
|
# S3 — как у api выше.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/6/name
|
||||||
|
value: S3_SERVICE_ACCOUNT
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/6/value/_default
|
||||||
|
value: /vault/secrets/processing-s3
|
||||||
|
|
||||||
|
# SMTP выключаем целиком: почтового релея в контуре нет. Вместе с флагом
|
||||||
|
# убираем аннотацию инжекции secrets/vault/common/smtp_auth — секрета в
|
||||||
|
# Vault нет, а vault-agent-init на несуществующем секрете не отрендерит
|
||||||
|
# шаблон и оставит под в Init:Error, до флага дело не дойдёт.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/23/name
|
||||||
|
value: ENABLE_SMTP
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/23/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-processing-smtp
|
||||||
|
- op: remove
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-processing-smtp
|
||||||
|
|
||||||
|
# Смежные сервисы, которых в контуре нет: documentations (PDM,
|
||||||
|
# filestream), resources, bim-api, workspace, issues, comparisons,
|
||||||
|
# mailgun. Оставляем только S3 — единственное внешнее хранилище, которое
|
||||||
|
# в aero действительно есть.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/22/name
|
||||||
|
value: ENABLE_PDM_STORAGE
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/22/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/26/name
|
||||||
|
value: ENABLE_BIM_API_V2_DB
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/26/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/27/name
|
||||||
|
value: ENABLE_WORKSPACE_API_DB
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/27/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/28/name
|
||||||
|
value: ENABLE_ISSUE_API_DB
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/28/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/29/name
|
||||||
|
value: ENABLE_RESOURCES_API
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/29/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/31/name
|
||||||
|
value: ENABLE_PDM_API_DB
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/31/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/32/name
|
||||||
|
value: ENABLE_COMPARISONS_API_DB
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/32/value/_default
|
||||||
|
value: "0"
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/33/name
|
||||||
|
value: ENABLE_MAIL_GUN
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/33/value/_default
|
||||||
|
value: "0"
|
||||||
|
|
||||||
|
# Планирование подов задач. engine работает kubernetes-исполнителем и сам
|
||||||
|
# создаёт Job'ы, подставляя им nodeSelector dedicated=processing и
|
||||||
|
# соответствующий toleration. Выделенных нод в контуре нет — все три
|
||||||
|
# равнозначны, — поэтому поды задач висли бы в Pending с
|
||||||
|
# didn't match Pod's node affinity/selector
|
||||||
|
# ровно как это было у postgresql (см. postRenderers в его релизе).
|
||||||
|
#
|
||||||
|
# ENABLE_TOLERATION ЗДЕСЬ НЕ ВЫКЛЮЧАТЬ. Сначала я поставил его в "0" — и
|
||||||
|
# это тихо сломало запуск задач совсем: в движке классы ресурсов
|
||||||
|
# регистрируются ПОД ЭТИМ ЖЕ ФЛАГОМ,
|
||||||
|
# if resCfg.EnableToleration { services[HighResources] = ... }
|
||||||
|
# (pkg/kube_services/services.go), поэтому задача с service_request
|
||||||
|
# "high-resources" падала не на планировании, а раньше — на разборе:
|
||||||
|
# ERROR create k8s job: create k8s job: unknown service
|
||||||
|
# Ни одного пода при этом не создавалось, и в логе не было ни слова про
|
||||||
|
# nodeSelector, так что связь с этим флагом неочевидна.
|
||||||
|
#
|
||||||
|
# Вместо выключения переводим селектор на метку, которая есть на всех
|
||||||
|
# нодах, — kubernetes.io/os=linux. Класс сервисов остаётся
|
||||||
|
# зарегистрированным, nodeSelector совпадает с любой нодой, а toleration
|
||||||
|
# на несуществующий taint безвреден. Запрашиваемые ресурсы трогать не
|
||||||
|
# надо: в base это 1 CPU и 1Gi.
|
||||||
|
#
|
||||||
|
# IGNORE_TAINTS_AND_NODE_SELECTOR (появился в base из master) полагаться
|
||||||
|
# на него не стоит: в master workflows-engine такой переменной нет. Образ
|
||||||
|
# при этом contour_a03d37da-dirty2 — ручная сборка, поднятая тем же
|
||||||
|
# коммитом master, что добавил переменную, так что она там, вероятно,
|
||||||
|
# поддержана. Проверить нечем, поэтому селектор задан явно, а не через
|
||||||
|
# неё; вреда от того, что она выставлена в base, нет.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/53/name
|
||||||
|
value: TOLERATION_KEY
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/53/value/_default
|
||||||
|
value: kubernetes.io/os
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/54/name
|
||||||
|
value: TOLERATION_VALUE
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/54/value/_default
|
||||||
|
value: linux
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/55/name
|
||||||
|
value: TOLERATION_KEY_HIGH_MEM
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/55/value/_default
|
||||||
|
value: kubernetes.io/os
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/56/name
|
||||||
|
value: TOLERATION_VALUE_HIGH_MEM
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/56/value/_default
|
||||||
|
value: linux
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/57/name
|
||||||
|
value: TOLERATION_KEY_PERSISTENT
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/57/value/_default
|
||||||
|
value: kubernetes.io/os
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/58/name
|
||||||
|
value: TOLERATION_VALUE_PERSISTENT
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/58/value/_default
|
||||||
|
value: linux
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/70/name
|
||||||
|
value: DEFAULT_NODE_SELECTOR_KEY
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/70/value/_default
|
||||||
|
value: kubernetes.io/os
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/71/name
|
||||||
|
value: DEFAULT_NODE_SELECTOR_VALUE
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/71/value/_default
|
||||||
|
value: linux
|
||||||
|
|
||||||
|
# --- Vault для ПОДОВ ЗАДАЧ, а не для самого движка ----------------------
|
||||||
|
# В сборке contour_a03d37da-dirty2 движок умеет не монтировать в Job
|
||||||
|
# готовый Secret, а вешать на него аннотации vault-agent — тогда файл
|
||||||
|
# с кредами кладёт агент, и Secret в namespace задач не нужен вовсе.
|
||||||
|
# Именно так это работает в UGMK, где никакого yc-s3 в namespace нет.
|
||||||
|
#
|
||||||
|
# В master workflows-engine этого кода нет (сборка с невлитой ветки),
|
||||||
|
# поэтому набор переменных восстановлен по строкам бинарника:
|
||||||
|
# VAULT_ROLE, VAULT_SA, VAULT_MOUNT_PATH
|
||||||
|
# vault.hashicorp.com/{role,secret-volume-path,agent-inject-file-*,
|
||||||
|
# agent-inject-secret-*,agent-inject-template-*}
|
||||||
|
# yc-s3, yc-s3-service-account.json, /etc/sarex/yc-s3
|
||||||
|
#
|
||||||
|
# В base задан только VAULT_USE=true — остальных трёх нет НИ В ОДНОМ
|
||||||
|
# кластере репозитория, поэтому аннотации выходили неполными, агент в под
|
||||||
|
# задачи ничего не клал, и файл читался как пустой:
|
||||||
|
# ValueError: SERVICE_S3 is not valid
|
||||||
|
# Expecting value: line 1 column 1 (char 0)
|
||||||
|
#
|
||||||
|
# Переменных нет в base, поэтому op: add с "-" (добавление в конец
|
||||||
|
# списка), а не replace по индексу.
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/envs/-
|
||||||
|
value:
|
||||||
|
name: VAULT_ROLE
|
||||||
|
value:
|
||||||
|
_default: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/envs/-
|
||||||
|
value:
|
||||||
|
name: VAULT_SA
|
||||||
|
value:
|
||||||
|
_default: processing-vault
|
||||||
|
# VAULT_MOUNT_PATH — вопреки имени НЕ корень KV, а ПОЛНЫЙ путь секрета:
|
||||||
|
# движок подставляет значение в шаблон целиком. Проверено по аннотациям
|
||||||
|
# созданного пода задачи:
|
||||||
|
# agent-inject-secret-yc-s3: <VAULT_MOUNT_PATH>
|
||||||
|
# agent-inject-template-yc-s3:
|
||||||
|
# {{- with secret "<VAULT_MOUNT_PATH>" -}}
|
||||||
|
# {{ index .Data.data "yc-s3-service-account.json" }}
|
||||||
|
# Отсюда два следствия: путь пишется в форме чтения KV v2 (с /data/), а в
|
||||||
|
# секрете обязан быть ключ с именем ФАЙЛА.
|
||||||
|
#
|
||||||
|
# Путь ОДИН НА ВСЕ ФАЙЛЫ, которые движок кладёт в поды задач: он
|
||||||
|
# подставляет VAULT_MOUNT_PATH в каждую аннотацию, меняя только имя
|
||||||
|
# ключа. Так, для django-auth аннотация ссылается на этот же путь и ищет
|
||||||
|
# в нём ключ django-auth.json. Поэтому имя пути нейтральное (job-files),
|
||||||
|
# а не по имени одного из файлов, — иначе оно врёт о содержимом.
|
||||||
|
# Сам секрет кладёт ansible, vault_app_secrets.
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/envs/-
|
||||||
|
value:
|
||||||
|
name: VAULT_MOUNT_PATH
|
||||||
|
value:
|
||||||
|
_default: secrets/data/apps/processing/job-files
|
||||||
|
|
||||||
|
# S3 из Vault, как у api.
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-secret-processing-s3
|
||||||
|
value: secrets/data/minio/apps/processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/values/services/backend/podAnnotations/_default/vault.hashicorp.com~1agent-inject-template-processing-s3
|
||||||
|
value: |-
|
||||||
|
{{- with secret "secrets/data/minio/apps/processing" -}}
|
||||||
|
{
|
||||||
|
"host": "{{ index .Data.data "host" }}",
|
||||||
|
"bucket": "{{ index .Data.data "bucket" }}",
|
||||||
|
"access_key_id": "{{ index .Data.data "access_key_id" }}",
|
||||||
|
"secret_access_key": "{{ index .Data.data "secret_access_key" }}",
|
||||||
|
"region": "{{ index .Data.data "region" }}",
|
||||||
|
"verify": false
|
||||||
|
}
|
||||||
|
{{- end -}}
|
||||||
86
apps/workspaces/aero/kustomization.yaml
Normal file
86
apps/workspaces/aero/kustomization.yaml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: workspaces
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
|
||||||
|
patches:
|
||||||
|
# --- workspaces-api ---------------------------------------------------------
|
||||||
|
# Патчи адресуются по индексу, поэтому перед каждым replace идёт op: test на
|
||||||
|
# имя переменной. Если в base порядок envs изменится, сборка упадёт явно,
|
||||||
|
# вместо того чтобы молча перезаписать соседнее значение.
|
||||||
|
- target: {kind: HelmRelease, name: backend}
|
||||||
|
patch: |
|
||||||
|
# В base адрес django неверный ДВАЖДЫ: сервиса backend не существует
|
||||||
|
# (он называется backend-svc), и порт 8000 — это targetPort контейнера,
|
||||||
|
# а сам сервис принимает на 80. Проверено в кластере:
|
||||||
|
# backend-svc...:80 -> HTTP 200
|
||||||
|
# backend-svc...:8000 -> таймаут
|
||||||
|
# backend...:8000 -> имя не резолвится
|
||||||
|
# Ровно та же ошибка была в base у processing/engine-low.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/11/name
|
||||||
|
value: DJANGO_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/11/value/_default
|
||||||
|
value: http://backend-svc.django.svc.cluster.local:80
|
||||||
|
|
||||||
|
# ENVIRONMENT/ORIGINATOR в base говорят «prod» — это контур, а не прод.
|
||||||
|
# Значение уезжает в Sentry и в заголовки запросов к смежным сервисам.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/10/name
|
||||||
|
value: ENVIRONMENT
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/10/value/_default
|
||||||
|
value: aero
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/12/name
|
||||||
|
value: DJANGO_ORIGINATOR
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/12/value/_default
|
||||||
|
value: aero_ws
|
||||||
|
|
||||||
|
# documentations в контуре нет и не будет. Выключить интеграцию целиком
|
||||||
|
# нечем: DOCUMENTATION_LOGGER_FEATURE (уже "0" в base) гасит только
|
||||||
|
# логирование, а GetDocumentByWS вызывается из обработчика GET workspace
|
||||||
|
# безусловно. Поэтому адрес оставлен «говорящим»: в логах будет видно имя
|
||||||
|
# несуществующего сервиса, а не молчаливый таймаут в никуда.
|
||||||
|
# Практическое следствие: карточка воркспейса отдаёт данные без документа.
|
||||||
|
- op: test
|
||||||
|
path: /spec/values/services/backend/envs/7/name
|
||||||
|
value: DOCUMENTATION_HOST
|
||||||
|
- op: replace
|
||||||
|
path: /spec/values/services/backend/envs/7/value/_default
|
||||||
|
value: http://documentations-api-svc.documentations.svc.cluster.local:80
|
||||||
|
|
||||||
|
# --- frontend ---------------------------------------------------------------
|
||||||
|
# Идёт из base без единого патча — и это осознанно, а не «руки не дошли».
|
||||||
|
#
|
||||||
|
# Фронтенд workspaces — это Module Federation remote с именем srx_workspaces,
|
||||||
|
# точка входа /module/remoteEntry.js. Его подгружает главный фронтенд
|
||||||
|
# платформы, поэтому наружу он НЕ публикуется: маршрут ему не нужен, нужен
|
||||||
|
# проксирующий location внутри nginx главного фронтенда. Он уже есть —
|
||||||
|
# apps/django/aero/nginx-configmap.yaml, location ~^/workspaces-v2/(.+).js,
|
||||||
|
# rewrite снимает префикс и проксирует в frontend-svc.workspaces:80.
|
||||||
|
#
|
||||||
|
# Версия — именно v2, хотя в репозитории есть и v1 (workspaces-frontend-static
|
||||||
|
# в apps/workspaces/dsinv). Так решает не выбор, а собранный бандл платформы.
|
||||||
|
# Проверено по трём независимым источникам:
|
||||||
|
#
|
||||||
|
# 1. В образе sarex-frontend-dev:contour_5.22.0 из 18 упоминаний
|
||||||
|
# remoteEntry.js воркспейсовое ровно одно — /workspaces-v2/module/...,
|
||||||
|
# а из имён ремоутов встречается только srx_workspacesV2
|
||||||
|
# (у v1 имя без суффикса — srx_workspaces, вхождений ноль).
|
||||||
|
# 2. Отдаваемый контуром /module/remoteEntry.js объявляет srx_workspacesV2.
|
||||||
|
# 3. В cr.yandex у образа workspaces-v2-frontend 61 тег contour_*,
|
||||||
|
# у workspaces-frontend (v1) — ни одного, только preprod и stage.
|
||||||
|
# Репозитория workspace-frontend в реестре нет вовсе.
|
||||||
|
#
|
||||||
|
# Тег в base — contour_2a4ce3fd — и есть САМАЯ СВЕЖАЯ contour-сборка:
|
||||||
|
# по датам создания образов в реестре она от 2026-07-24, следующая за ней
|
||||||
|
# contour_09b9baba от 2026-07-19. Собираются такие образы джобой
|
||||||
|
# build_contour из generic/build-contour-frontend, и её правило —
|
||||||
|
# ветка master при ENABLE_BUILD_IMAGE_CONTOUR=true, то есть contour_* по
|
||||||
|
# определению приезжает с мастера. Поэтому тег здесь не переопределяется:
|
||||||
|
# base уже указывает на нужное, и bump в base автоматически приедет сюда.
|
||||||
24
clusters/aero/apps.yaml
Normal file
24
clusters/aero/apps.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Слой приложений. Отделён от инфраструктуры по той же причине, что controllers
|
||||||
|
# от configs (см. infrastructure.yaml): приложениям нужны уже готовые CRD и
|
||||||
|
# работающие вебхуки.
|
||||||
|
#
|
||||||
|
# dependsOn: infra-controllers ставит istiod и vault-agent-injector, а под
|
||||||
|
# тестового приложения без них не поднимется — MutatingWebhook инжектора
|
||||||
|
# перехватывает создание пода, и пока вебхук не готов, под не создастся.
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: apps
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 2m0s
|
||||||
|
path: ./clusters/aero/apps
|
||||||
|
prune: true
|
||||||
|
wait: true
|
||||||
|
timeout: 10m0s
|
||||||
|
dependsOn:
|
||||||
|
- name: infra-controllers
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
17
clusters/aero/apps/kustomization.yaml
Normal file
17
clusters/aero/apps/kustomization.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Слой 3: прикладные сервисы контура.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../../../apps/django/aero
|
||||||
|
# Сервис измерений. Django обращается к нему при MEASUREMENTS_USE_MEASUREMENTS=1
|
||||||
|
# (значение по умолчанию в base), поэтому без него часть API отвечает ошибкой.
|
||||||
|
- ../../../apps/measurements/aero
|
||||||
|
# Движок workflow. Развёрнут частично: только api и engine-low, без
|
||||||
|
# собственного фронтенда — подробности в apps/processing/aero.
|
||||||
|
- ../../../apps/processing/aero
|
||||||
|
# Воркспейсы: api и фронтенд-ремоут. Фронтенд наружу не публикуется — его
|
||||||
|
# подгружает главный фронтенд платформы через свой nginx (/workspaces-v2/).
|
||||||
|
- ../../../apps/workspaces/aero
|
||||||
|
# BIM: api и worker из aero/bimbackend. Не путать с apps/bim/base — там
|
||||||
|
# другое приложение (platform/bim-backend-v2), в контуре оно не нужно.
|
||||||
|
- ../../../apps/bim/aero
|
||||||
8
clusters/aero/configs/kustomization.yaml
Normal file
8
clusters/aero/configs/kustomization.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Слой 2: ресурсы, которым нужны CRD из слоя controllers.
|
||||||
|
# Применяется только после того, как infra-controllers дошёл до Ready
|
||||||
|
# (dependsOn в clusters/aero/infrastructure.yaml).
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
# Внутренний удостоверяющий центр контура: selfsigned → корневой CA → издатель.
|
||||||
|
- ../../../infrastructure/cert-manager/aero/configs
|
||||||
31
clusters/aero/controllers/kustomization.yaml
Normal file
31
clusters/aero/controllers/kustomization.yaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Слой 1: чарты инфраструктуры. Только HelmRelease и Namespace — никаких
|
||||||
|
# ресурсов, чьи CRD ставятся этими же чартами (см. clusters/aero/infrastructure.yaml).
|
||||||
|
#
|
||||||
|
# Порядок внутри слоя обеспечивают dependsOn в самих HelmRelease:
|
||||||
|
# istiod и ingressgateway ждут istio-base, istio-config ждёт всех троих.
|
||||||
|
#
|
||||||
|
# local-path-provisioner подключён ЯВНО, а встроенный в k3s — отключён флагом
|
||||||
|
# --disable=local-storage (см. docker-compose.yaml). Два провижинера с одним
|
||||||
|
# именем StorageClass дрались бы за одни и те же PVC, поэтому ровно один из них
|
||||||
|
# должен быть активен. Каталог данных не изменился: чарту задан тот же путь
|
||||||
|
# /var/lib/rancher/k3s/storage, что смонтирован с хоста.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
# Хранилище идёт первым: PVC rabbitmq и minio без StorageClass не создадутся.
|
||||||
|
- ../../../infrastructure/local-path-provisioner/aero
|
||||||
|
- ../../../infrastructure/cert-manager/aero
|
||||||
|
- ../../../infrastructure/istio-base/aero
|
||||||
|
- ../../../infrastructure/istio-pilot/aero
|
||||||
|
- ../../../infrastructure/istio-gateway/aero
|
||||||
|
- ../../../infrastructure/istio-config/aero
|
||||||
|
- ../../../infrastructure/dashboard/aero
|
||||||
|
# Только Vault Agent Injector: сам Vault живёт в compose, а не в кластере.
|
||||||
|
- ../../../infrastructure/vault-injector/aero
|
||||||
|
# СУБД, брокер и объектное хранилище. Креды берут из Vault через injector,
|
||||||
|
# поэтому идут после него. Админки rabbitmq и minio выставлены наружу через
|
||||||
|
# istio-config/aero; postgresql наружу не публикуется — доступ только изнутри
|
||||||
|
# кластера.
|
||||||
|
- ../../../infrastructure/postgresql/aero
|
||||||
|
- ../../../infrastructure/rabbitmq/aero
|
||||||
|
- ../../../infrastructure/minio/aero
|
||||||
6432
clusters/aero/flux-system/gotk-components.yaml
Normal file
6432
clusters/aero/flux-system/gotk-components.yaml
Normal file
File diff suppressed because it is too large
Load Diff
27
clusters/aero/flux-system/gotk-sync.yaml
Normal file
27
clusters/aero/flux-system/gotk-sync.yaml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# This manifest was generated by flux. DO NOT EDIT.
|
||||||
|
---
|
||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: GitRepository
|
||||||
|
metadata:
|
||||||
|
name: flux-system
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 1m0s
|
||||||
|
ref:
|
||||||
|
branch: master
|
||||||
|
secretRef:
|
||||||
|
name: flux-system
|
||||||
|
url: http://gitea.flux-system.svc.cluster.local:3000/infra/iac.git
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: flux-system
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
path: ./clusters/aero
|
||||||
|
prune: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
5
clusters/aero/flux-system/kustomization.yaml
Normal file
5
clusters/aero/flux-system/kustomization.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- gotk-components.yaml
|
||||||
|
- gotk-sync.yaml
|
||||||
14
clusters/aero/helm-repositories.yaml
Normal file
14
clusters/aero/helm-repositories.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: HelmRepository
|
||||||
|
metadata:
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
type: oci
|
||||||
|
interval: 10m0s
|
||||||
|
url: oci://cr.yandex/crp3ccidau046kdj8g9q/charts
|
||||||
|
# Секрет создаёт роль sarex_stack из ключа сервисного аккаунта
|
||||||
|
# (см. aero/roles/sarex_stack/templates/registry-secret.yaml.j2).
|
||||||
|
secretRef:
|
||||||
|
name: yc-cr-auth
|
||||||
67
clusters/aero/infrastructure.yaml
Normal file
67
clusters/aero/infrastructure.yaml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Инфраструктура разделена на два слоя, и это не косметика.
|
||||||
|
#
|
||||||
|
# Flux делает dry-run всей Kustomization целиком перед применением. Если
|
||||||
|
# положить cert-manager (который СТАВИТ CRD) и ClusterIssuer/Certificate
|
||||||
|
# (которые ЭТИ CRD используют) в один слой, dry-run падает с
|
||||||
|
# no matches for kind "Certificate" in version "cert-manager.io/v1"
|
||||||
|
# и не применяется НИЧЕГО — включая сам cert-manager. Дедлок: CRD никогда не
|
||||||
|
# появятся, потому что слой не может пройти проверку.
|
||||||
|
#
|
||||||
|
# Поэтому: controllers ставят чарты (и CRD), configs содержат только
|
||||||
|
# пользовательские ресурсы.
|
||||||
|
#
|
||||||
|
# ПОЧЕМУ У infra-configs НЕТ dependsOn (было — и создавало дедлок).
|
||||||
|
#
|
||||||
|
# В configs лежат ClusterIssuer'ы. Чарты из слоя controllers создают
|
||||||
|
# Certificate, а Flux после установки релиза ждёт готовности его ресурсов.
|
||||||
|
# С dependsOn получался круг:
|
||||||
|
# чарт с Certificate ждёт выпуска
|
||||||
|
# → выпуск ждёт ClusterIssuer
|
||||||
|
# → ClusterIssuer в configs
|
||||||
|
# → configs ждёт готовности controllers
|
||||||
|
# → а там висит тот самый чарт
|
||||||
|
# Наступали на это дважды: сначала istio-config, следом rabbitmq (тот провисел
|
||||||
|
# в "Running 'install' action" полчаса).
|
||||||
|
#
|
||||||
|
# Жёсткий порядок заменён сходимостью через повторы: пока cert-manager не
|
||||||
|
# поставил CRD, configs будет падать на dry-run и переприменяться каждые
|
||||||
|
# 10 минут, а как только CRD появятся — применится сам. Издатели при этом
|
||||||
|
# больше не зависят от здоровья чартов, которые в них нуждаются.
|
||||||
|
#
|
||||||
|
# apps по-прежнему ждёт controllers, и это правильно: без istiod и
|
||||||
|
# vault-agent-injector его поды физически не поднимутся.
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: infra-controllers
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
# 2 минуты, а не дефолтные 10: интервал определяет, как быстро слой заметит
|
||||||
|
# новую ревизию и как быстро повторит попытку после неудачи. С 10 минутами
|
||||||
|
# цикл «правка → проверка» растягивался до получаса, а сломанный релиз
|
||||||
|
# держал слой всё это время.
|
||||||
|
interval: 2m0s
|
||||||
|
path: ./clusters/aero/controllers
|
||||||
|
prune: true
|
||||||
|
wait: true
|
||||||
|
timeout: 10m0s
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: infra-configs
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 2m0s
|
||||||
|
path: ./clusters/aero/configs
|
||||||
|
prune: true
|
||||||
|
# dependsOn намеренно отсутствует — см. комментарий в начале файла.
|
||||||
|
# Повторные попытки до появления CRD дешевле, чем дедлок.
|
||||||
|
retryInterval: 1m0s
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
32
clusters/aero/kustomization.yaml
Normal file
32
clusters/aero/kustomization.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ./flux-system
|
||||||
|
- ./helm-repositories.yaml
|
||||||
|
# Два слоя инфраструктуры с dependsOn — см. комментарий внутри файла.
|
||||||
|
- ./infrastructure.yaml
|
||||||
|
# Слой приложений. Пока пуст — сюда приедут сервисы при переносе из compose.
|
||||||
|
- ./apps.yaml
|
||||||
|
|
||||||
|
# Корневая Kustomization применяет этот же каталог, поэтому здесь она правит
|
||||||
|
# сама себя — и это единственный способ задать ей интервал.
|
||||||
|
#
|
||||||
|
# Файл flux-system/gotk-sync.yaml генерирует `flux bootstrap` и помечает
|
||||||
|
# "DO NOT EDIT": правка руками затрётся на следующем прогоне. Флаг
|
||||||
|
# --interval=2m у bootstrap (см. docker-compose.yaml) тоже не помогает — он
|
||||||
|
# задаёт периодичность только GitRepository, а Kustomization оставляет
|
||||||
|
# дефолтные 10 минут. Патч же применяется поверх сгенерированного файла и
|
||||||
|
# переживает регенерацию.
|
||||||
|
#
|
||||||
|
# Без этого определения самих слоёв (infrastructure.yaml, apps.yaml)
|
||||||
|
# доезжали бы до кластера с задержкой до 10 минут, даже когда все нижние
|
||||||
|
# слои уже проверяются раз в 2 минуты.
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: Kustomization
|
||||||
|
name: flux-system
|
||||||
|
namespace: flux-system
|
||||||
|
patch: |
|
||||||
|
- op: replace
|
||||||
|
path: /spec/interval
|
||||||
|
value: 2m0s
|
||||||
@ -0,0 +1,499 @@
|
|||||||
|
# workflows-engine через k3s (планирование Job'ов) — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Добавить в контур `workflows-engine` как сервис docker-compose, который через kubeconfig-контекст создаёт Job'ы в k3s; Job-поды резолвят `postgres`/`minio` из compose-сети через k8s DNS-мост.
|
||||||
|
|
||||||
|
**Architecture:** engine — обычный compose-контейнер (до `workflow_db` ходит по compose-DNS), в k3s ходит out-of-cluster (`KUBE_CONFIG`+`KUBE_ADDR`). В namespace `processing` k3s заводятся Service+Endpoints `postgres`/`minio` на статические IP compose-контейнеров, чтобы Job-поды достукивались до БД и S3. Поднимаем только `k3s-server`.
|
||||||
|
|
||||||
|
**Tech Stack:** Docker Compose, k3s (rancher/k3s v1.36), kubectl, Ansible (роль `sarex_stack`), Go-сервис workflows-engine (конфиг через env, cleanenv).
|
||||||
|
|
||||||
|
**Спека:** `docs/superpowers/specs/2026-07-30-processing-engine-k3s-scheduling-design.md`
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Целевой хост — RedOS 8 (SELinux → volume-флаги `:z`/`:ro,z`), деплой из WSL через ansible роль `sarex_stack`.
|
||||||
|
- Все образы параметризуются `${VAR:-default}`; секреты — через `.env`, генерятся ролью.
|
||||||
|
- Только `k3s-server` (без `k3s-worker`). Downstream `ENABLE_*` engine выключены, кроме `ENABLE_S3_STORAGE`.
|
||||||
|
- Статические IP в compose: `postgres` = `172.28.0.10`, `minio` = `172.28.0.11`, subnet `172.28.0.0/16`.
|
||||||
|
- Имена в k8s = compose-имена: `postgres`, `minio`, namespace `processing`.
|
||||||
|
- Образ engine: `${SAREX_WORKFLOWS_ENGINE_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0}`.
|
||||||
|
- Проверки инфраструктуры — не pytest: валидация `docker compose config -q`, `kubectl --dry-run`, коннект-чек по логам. Каждая задача завершается коммитом.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
- `iac/docker-compose.yaml` (Modify) — top-level `networks.default` IPAM; static IP у `postgres`/`minio`; сервисы `engine`, `processing-k8s-init`.
|
||||||
|
- `iac/.env.example` (Modify) — `SAREX_WORKFLOWS_ENGINE_IMAGE`.
|
||||||
|
- `iac/k3s/manifests/processing/namespace.yaml` (Create) — namespace `processing`.
|
||||||
|
- `iac/k3s/manifests/processing/bridge-postgres.yaml` (Create) — Service+Endpoints `postgres`.
|
||||||
|
- `iac/k3s/manifests/processing/bridge-minio.yaml` (Create) — Service+Endpoints `minio`.
|
||||||
|
- `iac/backend/yc-s3-service-account.json` → нет; S3 SA кладём в `iac/engine/yc-s3-service-account.json` (Create, плейсхолдер на `http://minio:9000`).
|
||||||
|
- `iac/aero/roles/sarex_stack/tasks/main.yml` (Modify) — копирование `k3s/manifests` и `engine/`.
|
||||||
|
- `iac/aero/roles/sarex_stack/defaults/main.yml` (Modify) — `sarex_services` += `k3s-server`, `processing-k8s-init`, `engine`.
|
||||||
|
- `iac/aero/pyproject.toml` (Modify) — poe-таск проверки k8s-моста (опц.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Параметр образа engine + S3 SA-плейсхолдер
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `iac/.env.example`
|
||||||
|
- Create: `iac/engine/yc-s3-service-account.json`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: env-переменная `SAREX_WORKFLOWS_ENGINE_IMAGE`; файл SA `iac/engine/yc-s3-service-account.json`, монтируемый в engine на `/etc/sarex/yc-s3/yc-s3-service-account.json`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Добавить переменную образа в `.env.example`**
|
||||||
|
|
||||||
|
В секцию образов `iac/.env.example` добавить строку:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
# workflows-engine (processing) — оркестратор Job'ов в k3s
|
||||||
|
SAREX_WORKFLOWS_ENGINE_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Создать плейсхолдер S3 service-account JSON**
|
||||||
|
|
||||||
|
`iac/engine/yc-s3-service-account.json` (endpoint на прямой MinIO; app-креды подставит рантайм/ansible — тут плейсхолдер для планирования):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"host": "http://minio:9000",
|
||||||
|
"bucket": "sarex-media-storage",
|
||||||
|
"access_key_id": "sarex-app",
|
||||||
|
"secret_access_key": "sarex-app-secret",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"verify": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Проверка — файл валидный JSON**
|
||||||
|
|
||||||
|
Run: `python -c "import json;json.load(open('iac/engine/yc-s3-service-account.json'));print('ok')"`
|
||||||
|
Expected: `ok`
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add iac/.env.example iac/engine/yc-s3-service-account.json
|
||||||
|
git commit -m "feat(engine): образ workflows-engine + S3 SA-плейсхолдер"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Compose-сеть со статическими IP для postgres/minio
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `iac/docker-compose.yaml`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `postgres` доступен по `172.28.0.10`, `minio` по `172.28.0.11` в сети `default` (subnet `172.28.0.0/16`). На эти IP ссылаются k8s-Endpoints (Task 4).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Добавить top-level `networks` c IPAM**
|
||||||
|
|
||||||
|
В конец `iac/docker-compose.yaml` (рядом с `volumes:`) добавить:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.28.0.0/16
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Пин статического IP у `postgres`**
|
||||||
|
|
||||||
|
В сервис `postgres` добавить блок `networks`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: 172.28.0.10
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Пин статического IP у `minio`**
|
||||||
|
|
||||||
|
В сервис `minio` добавить:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: 172.28.0.11
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Проверка — конфиг валиден и IP на месте**
|
||||||
|
|
||||||
|
Run (в `iac/`): `docker compose --env-file .env.example config | grep -A3 -E "ipv4_address|subnet"`
|
||||||
|
Expected: видно `subnet: 172.28.0.0/16`, `ipv4_address: 172.28.0.10` и `172.28.0.11`.
|
||||||
|
(Если docker недоступен локально — выполнить на сервере в `deploy_dir` после копирования.)
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add iac/docker-compose.yaml
|
||||||
|
git commit -m "feat(net): статические IP postgres/minio для k8s-моста"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: k8s-манифесты DNS-моста (namespace + Service/Endpoints)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `iac/k3s/manifests/processing/namespace.yaml`
|
||||||
|
- Create: `iac/k3s/manifests/processing/bridge-postgres.yaml`
|
||||||
|
- Create: `iac/k3s/manifests/processing/bridge-minio.yaml`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: статические IP из Task 2 (`172.28.0.10`, `172.28.0.11`).
|
||||||
|
- Produces: в namespace `processing` резолвятся имена `postgres:5432` и `minio:9000` (→ compose-контейнеры).
|
||||||
|
|
||||||
|
- [ ] **Step 1: namespace**
|
||||||
|
|
||||||
|
`iac/k3s/manifests/processing/namespace.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: processing
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: мост postgres (Service без селектора + Endpoints)**
|
||||||
|
|
||||||
|
`iac/k3s/manifests/processing/bridge-postgres.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: processing
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: pg
|
||||||
|
port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: processing
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: 172.28.0.10
|
||||||
|
ports:
|
||||||
|
- name: pg
|
||||||
|
port: 5432
|
||||||
|
protocol: TCP
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: мост minio**
|
||||||
|
|
||||||
|
`iac/k3s/manifests/processing/bridge-minio.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: minio
|
||||||
|
namespace: processing
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: s3
|
||||||
|
port: 9000
|
||||||
|
targetPort: 9000
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: minio
|
||||||
|
namespace: processing
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: 172.28.0.11
|
||||||
|
ports:
|
||||||
|
- name: s3
|
||||||
|
port: 9000
|
||||||
|
protocol: TCP
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Проверка — YAML валиден (клиентский dry-run)**
|
||||||
|
|
||||||
|
Run (при наличии kubectl; иначе отложить на сервер):
|
||||||
|
`kubectl apply --dry-run=client -f iac/k3s/manifests/processing/`
|
||||||
|
Expected: `namespace/processing created (dry run)`, `service/postgres ...`, `endpoints/postgres ...`, `service/minio ...`, `endpoints/minio ...`.
|
||||||
|
Fallback без kubectl: `python -c "import glob,yaml; [list(yaml.safe_load_all(open(f))) for f in glob.glob('iac/k3s/manifests/processing/*.yaml')]; print('ok')"` → `ok`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add iac/k3s/manifests/processing/
|
||||||
|
git commit -m "feat(k3s): DNS-мост postgres/minio в namespace processing"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Сервис `processing-k8s-init` (применение манифестов)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `iac/docker-compose.yaml`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: манифесты `./k3s/manifests/processing/` (Task 3), kubeconfig `./k3s/kubeconfig.yaml` (пишется `k3s-server`).
|
||||||
|
- Produces: applied namespace + мост в k3s; сервис завершается успешно (barrier для engine).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Добавить сервис-хелпер (переиспользуем образ k3s — в нём есть kubectl)**
|
||||||
|
|
||||||
|
В `iac/docker-compose.yaml` (после `k3s-worker`/до `gitea` или рядом с processing-сервисами) добавить:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Одноразовый провижининг k3s: namespace processing + DNS-мост postgres/minio.
|
||||||
|
# Образ k3s содержит kubectl; server/tls перекрываем на k3s-server:6443.
|
||||||
|
processing-k8s-init:
|
||||||
|
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
|
||||||
|
container_name: processing-k8s-init
|
||||||
|
restart: "no"
|
||||||
|
depends_on:
|
||||||
|
k3s-server:
|
||||||
|
condition: service_healthy
|
||||||
|
entrypoint: ["/bin/sh", "-ec"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
kubectl --kubeconfig /kube/config \
|
||||||
|
--server https://k3s-server:6443 --insecure-skip-tls-verify \
|
||||||
|
apply -f /manifests/
|
||||||
|
volumes:
|
||||||
|
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
|
||||||
|
- ./k3s/manifests/processing:/manifests:ro,z
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Проверка — конфиг валиден**
|
||||||
|
|
||||||
|
Run (в `iac/`): `docker compose --env-file .env.example config --services | grep processing-k8s-init`
|
||||||
|
Expected: `processing-k8s-init`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add iac/docker-compose.yaml
|
||||||
|
git commit -m "feat(k3s): processing-k8s-init — применение DNS-моста через kubectl"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Сервис `engine` в docker-compose
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `iac/docker-compose.yaml`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `k3s-server` (kube-API), `processing-k8s-init` (мост applied), `postgres`/`postgres-init` (`workflow_db`), kubeconfig `./k3s/kubeconfig.yaml`, S3 SA `./engine/yc-s3-service-account.json` (Task 1).
|
||||||
|
- Produces: работающий оркестратор, создающий Job'ы в namespace `processing`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Добавить сервис `engine`**
|
||||||
|
|
||||||
|
В `iac/docker-compose.yaml` рядом с processing-сервисами:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# workflows-engine — оркестратор: читает workflow_db, создаёт Job'ы в k3s.
|
||||||
|
# Ходит в k3s out-of-cluster (KUBE_CONFIG+KUBE_ADDR). K8s-исполнитель; AMQP выкл.
|
||||||
|
engine:
|
||||||
|
image: ${SAREX_WORKFLOWS_ENGINE_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0}
|
||||||
|
container_name: engine
|
||||||
|
restart: unless-stopped
|
||||||
|
entrypoint: ["/engine"]
|
||||||
|
environment:
|
||||||
|
APP_NAME: workflows-engine
|
||||||
|
LOG_LEVEL: info
|
||||||
|
ENVIRONMENT: contour
|
||||||
|
# исполнители
|
||||||
|
ENABLE_KUBERNETES_EXECUTOR: "1"
|
||||||
|
ENABLE_AMQP_EXECUTOR: "0"
|
||||||
|
COUNT_RUNNING_WORKERS: "1"
|
||||||
|
COUNT_CANCELING_WORKERS: "1"
|
||||||
|
COUNT_HANDLE_JOB_WORKERS: "1"
|
||||||
|
WORKFLOW_PRIORITY: low
|
||||||
|
MAX_WORKFLOWS_LIMIT: "5"
|
||||||
|
# доступ в k3s (out-of-cluster)
|
||||||
|
KUBE_CONFIG: /kube/config
|
||||||
|
KUBE_CONTEXT: default
|
||||||
|
KUBE_ADDR: https://k3s-server:6443
|
||||||
|
JOBS_NAMESPACE: processing
|
||||||
|
# свой доступ к workflow_db (compose-DNS)
|
||||||
|
POSTGRES_ADDRESS: postgres
|
||||||
|
POSTGRES_PORT: "5432"
|
||||||
|
POSTGRES_DB: ${SAREX_PROCESSING_DB:-workflow_db}
|
||||||
|
POSTGRES_USER: ${SAREX_PROCESSING_DB_USER:-processing}
|
||||||
|
POSTGRES_PASSWORD: ${SAREX_PROCESSING_DB_PASSWORD:-processing-secret}
|
||||||
|
POSTGRES_POOL_SIZE: "20"
|
||||||
|
POSTGRES_SSL_USE: "0"
|
||||||
|
# хранилища: только S3, напрямую в minio
|
||||||
|
ENABLE_S3_STORAGE: "1"
|
||||||
|
S3_SERVICE_ACCOUNT: /etc/sarex/yc-s3/yc-s3-service-account.json
|
||||||
|
# дефолты планирования подов
|
||||||
|
DEFAULT_IMAGE_PULL_POLICY: IfNotPresent
|
||||||
|
DEFAULT_CPU_REQUESTS: 100m
|
||||||
|
DEFAULT_MEMORY_REQUESTS: 64Mi
|
||||||
|
volumes:
|
||||||
|
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
|
||||||
|
- ./engine/yc-s3-service-account.json:/etc/sarex/yc-s3/yc-s3-service-account.json:ro,z
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
postgres-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
k3s-server:
|
||||||
|
condition: service_healthy
|
||||||
|
processing-k8s-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Проверка — конфиг рендерится, образ и зависимости на месте**
|
||||||
|
|
||||||
|
Run (в `iac/`): `docker compose --env-file .env.example config | grep -A2 "container_name: engine"`
|
||||||
|
Expected: сервис `engine` с образом `workflows-endigne_prod`.
|
||||||
|
Run: `docker compose --env-file .env.example config | grep -E "KUBE_ADDR|JOBS_NAMESPACE"`
|
||||||
|
Expected: `KUBE_ADDR: https://k3s-server:6443`, `JOBS_NAMESPACE: processing`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add iac/docker-compose.yaml
|
||||||
|
git commit -m "feat(engine): сервис workflows-engine (k8s-исполнитель через kubeconfig)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Интеграция в Ansible-роль
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `iac/aero/roles/sarex_stack/defaults/main.yml`
|
||||||
|
- Modify: `iac/aero/roles/sarex_stack/tasks/main.yml`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: файлы Task 1-5 в репозитории `iac/`.
|
||||||
|
- Produces: `poe stack`/`install` копирует манифесты и `engine/`, поднимает `k3s-server`, `processing-k8s-init`, `engine`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Добавить сервисы в `sarex_services`**
|
||||||
|
|
||||||
|
В `iac/aero/roles/sarex_stack/defaults/main.yml`, список `sarex_services`, добавить (порядок не критичен — `depends_on` рулит; но добавим осмысленно):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- k3s-server
|
||||||
|
- processing-k8s-init
|
||||||
|
```
|
||||||
|
и после `celery` (или рядом с processing) добавить:
|
||||||
|
```yaml
|
||||||
|
- engine
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Копировать каталог `k3s/manifests` на хост**
|
||||||
|
|
||||||
|
В `iac/aero/roles/sarex_stack/tasks/main.yml`, рядом с копированием nginx/backend, добавить задачу:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Скопировать k3s-манифесты (DNS-мост processing)
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ deploy_src_root }}/k3s/manifests/"
|
||||||
|
dest: "{{ deploy_dir }}/k3s/manifests/"
|
||||||
|
mode: "0644"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Копировать каталог `engine/` (S3 SA) на хост**
|
||||||
|
|
||||||
|
Там же добавить:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Скопировать конфиги engine (S3 service-account)
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ deploy_src_root }}/engine/"
|
||||||
|
dest: "{{ deploy_dir }}/engine/"
|
||||||
|
mode: "0644"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Проверка — YAML роли валиден и сервисы в списке**
|
||||||
|
|
||||||
|
Run (в `iac/aero/`): `uv run python -c "import yaml;d=yaml.safe_load(open('roles/sarex_stack/defaults/main.yml'));assert {'k3s-server','processing-k8s-init','engine'} <= set(d['sarex_services']);print('services ok')"`
|
||||||
|
Expected: `services ok`.
|
||||||
|
Run: `uv run python -c "import yaml;list(yaml.safe_load_all(open('roles/sarex_stack/tasks/main.yml')));print('tasks ok')"`
|
||||||
|
Expected: `tasks ok`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add iac/aero/roles/sarex_stack/defaults/main.yml iac/aero/roles/sarex_stack/tasks/main.yml
|
||||||
|
git commit -m "feat(ansible): деплой engine + k3s DNS-моста в роли sarex_stack"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: Развёртывание и проверка планирования на сервере
|
||||||
|
|
||||||
|
**Files:** (нет изменений кода — деплой и верификация)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: всё выше, ветка `aero`, доступ к хосту через ansible из WSL.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Задеплоить файлы и поднять стек**
|
||||||
|
|
||||||
|
Run (в `iac/aero/`, WSL): `uv run poe stack`
|
||||||
|
(копирует compose/манифесты/.env, `docker compose up -d` включая `k3s-server`, `processing-k8s-init`, `engine`.)
|
||||||
|
Expected: playbook завершается без ошибок; `changed`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Проверить, что k3s-server поднялся**
|
||||||
|
|
||||||
|
Run: `uv run poe ps` (или ad-hoc `docker compose ps`)
|
||||||
|
Expected: `k3s-server` в статусе `Up (healthy)`; `processing-k8s-init` — `Exited (0)`; `engine` — `Up`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Проверить DNS-мост в k3s**
|
||||||
|
|
||||||
|
Run (ad-hoc на хосте): `docker exec k3s-server kubectl -n processing get svc,endpoints`
|
||||||
|
Expected: Service `postgres`, `minio` и Endpoints с адресами `172.28.0.10:5432`, `172.28.0.11:9000`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Проверить коннект engine к workflow_db и kube-API**
|
||||||
|
|
||||||
|
Run: `uv run poe logs engine --tail 120`
|
||||||
|
Expected: в логах — успешное подключение к Postgres и к kube-API, нет паник (`failed to parse`, `connection refused`, `panic`).
|
||||||
|
Если engine падает на старте — сверить с риском №2 спеки (downstream `ENABLE_*`) и №1 (`KUBE_ADDR` vs правка `server:` в kubeconfig).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Проверить резолв имён из тестового пода в k3s**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
docker exec k3s-server kubectl -n processing run netcheck --rm -it --restart=Never \
|
||||||
|
--image=busybox:1.36 -- sh -c "nslookup postgres; nc -z -w3 postgres 5432 && echo PG_OK; nc -z -w3 minio 9000 && echo S3_OK"
|
||||||
|
```
|
||||||
|
Expected: `postgres` резолвится в ClusterIP, `PG_OK`, `S3_OK` (под достучался до compose-postgres/minio через мост).
|
||||||
|
|
||||||
|
- [ ] **Step 6: Проверить планирование Job'а**
|
||||||
|
|
||||||
|
Инициировать workflow (через processing-api/фронт или тестовую запись в `workflow_db`), затем:
|
||||||
|
Run: `docker exec k3s-server kubectl -n processing get jobs,pods`
|
||||||
|
Expected: появляется Job-объект, под запланирован на ноду (статус `Running`/`ContainerCreating`/`ImagePullBackOff` — любой из них подтверждает планирование; успешный прогон конвертации — вне рамок).
|
||||||
|
|
||||||
|
- [ ] **Step 7: Зафиксировать результат**
|
||||||
|
|
||||||
|
Никаких изменений кода; при необходимости — записать в память проекта факт готовности планирования и оставшиеся follow-up (валидный S3 SA, downstream-сервисы, образы задач).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review
|
||||||
|
|
||||||
|
**1. Spec coverage:**
|
||||||
|
- Компонент A (engine compose) → Task 1, 5. ✓
|
||||||
|
- Компонент B (только k3s-server) → Task 6 (`sarex_services`), Task 7 (проверка). ✓
|
||||||
|
- Компонент C (DNS-мост postgres/minio) → Task 3, 4. ✓
|
||||||
|
- Сеть/статические IP → Task 2. ✓
|
||||||
|
- Ansible-интеграция → Task 6. ✓
|
||||||
|
- Порядок провижининга → `depends_on` (Task 4, 5) + Task 7. ✓
|
||||||
|
- Риски (KUBE_ADDR, стартовые проверки, S3 SA, образ задачи, cgroup/SELinux) → Task 7 Step 4/6 отсылают к рискам спеки. ✓
|
||||||
|
- Проверка результата (svc/endpoints, логи, jobs) → Task 7. ✓
|
||||||
|
|
||||||
|
**2. Placeholder scan:** S3 SA — намеренный плейсхолдер (задокументирован в спеке как приемлемый для планирования), не «TODO в требовании». Остальное — конкретный контент. ✓
|
||||||
|
|
||||||
|
**3. Type consistency:** имена согласованы сквозняком — `172.28.0.10`/`172.28.0.11`, namespace `processing`, Service/Endpoints `postgres`/`minio`, env `KUBE_ADDR=https://k3s-server:6443`, `JOBS_NAMESPACE=processing`, образ `workflows-endigne_prod:075fc0`. ✓
|
||||||
|
|
||||||
|
**Открытый нюанс для исполнителя:** если engine НЕ принимает `KUBE_ADDR` как override (риск №1), fallback — в `processing-k8s-init`/отдельном шаге sed-правкой заменить `server: https://127.0.0.1:6443` на `https://k3s-server:6443` в `./k3s/kubeconfig.yaml` и снять `KUBE_ADDR`, оставив `KUBE_CONFIG`+`KUBE_CONTEXT`.
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
# Дизайн: workflows-engine в контуре через k3s (планирование Job'ов)
|
||||||
|
|
||||||
|
Дата: 2026-07-30
|
||||||
|
Ветка: `aero`
|
||||||
|
Статус: согласован, готов к плану реализации
|
||||||
|
|
||||||
|
## Цель и рамки
|
||||||
|
|
||||||
|
Добавить в контур **workflows-engine** — фоновый оркестратор processing-подсистемы,
|
||||||
|
который вычитывает workflow из `workflow_db` и запускает задачи как **Kubernetes Job'ы**.
|
||||||
|
|
||||||
|
**Успех = планирование Job'ов:** engine стартует, подключается к `workflow_db`,
|
||||||
|
получает доступ к kube-API k3s и создаёт Job-объекты в namespace `processing`;
|
||||||
|
Job-поды планируются на ноду и достукиваются до `postgres`/`minio` из compose-сети.
|
||||||
|
|
||||||
|
**Вне рамок (принятые ограничения):**
|
||||||
|
- Полное end-to-end выполнение конвертаций НЕ гарантируется: engine тянет множество
|
||||||
|
downstream-сервисов (documentations/PDM, resources, bim-api-v2, workspace-api,
|
||||||
|
issue-api, comparisons, SMTP/Mailgun), которых в контуре нет. Все их `ENABLE_*`
|
||||||
|
выключаем; включаем только `ENABLE_S3_STORAGE`.
|
||||||
|
- Валидный S3 service-account для рантайма джоб не требуется для *планирования*
|
||||||
|
(S3 нужен на этапе выполнения задачи, не создания Job-объекта) — допускается плейсхолдер.
|
||||||
|
- Только одна нода k3s (`k3s-server`), без `k3s-worker`.
|
||||||
|
|
||||||
|
## Ключевые находки (обоснование дизайна)
|
||||||
|
|
||||||
|
Источники: `apps/processing/base/{api,engine,engine-low}.yaml`,
|
||||||
|
`apps/processing/workflows-engine.CONFIGURATION.md`.
|
||||||
|
|
||||||
|
1. **Джобы НЕ ходят в `workflow_db`.** В списке env, прокидываемых engine в Job-поды
|
||||||
|
(`pkg/kube_services/services.go`, CONFIGURATION.md §«…пробрасываемые в под'ы задач»),
|
||||||
|
`POSTGRES_*` отсутствует. В `workflow_db` ходит только сам engine. Джобам достаётся
|
||||||
|
S3 (через yc-s3 SA JSON при `ENABLE_S3_STORAGE`) и опционально JSON-конфиги прикладных
|
||||||
|
БД (bim/workspace/…) — только при соответствующих `ENABLE_*`.
|
||||||
|
2. **Доступ compose→k3s штатный.** У engine есть `KUBE_CONFIG` (путь; пусто = in-cluster,
|
||||||
|
задан = out-of-cluster), `KUBE_CONTEXT`, `KUBE_ADDR` (адрес apiserver + InsecureSkipTLSVerify),
|
||||||
|
`JOBS_NAMESPACE`. RBAC/ServiceAccount не нужны — аутентификация кредами из kubeconfig.
|
||||||
|
3. **RabbitMQ не нужен.** `pkg/rabbitmq` используется только при `ENABLE_AMQP_EXECUTOR=1`.
|
||||||
|
При K8s-исполнителе брокер не дёргается.
|
||||||
|
4. **`postgres:12-alpine`** генерит `pg_hba` как `host all all all md5` → пускает с любого
|
||||||
|
адреса по паролю. SNAT-источник (IP ноды k3s) не блокируется — нужен лишь верный
|
||||||
|
`processing`/пароль.
|
||||||
|
5. **`k3s-server` в compose уже готов:** `privileged`, `cgroup: host`, `--disable=traefik`,
|
||||||
|
kubeconfig пишется в `./k3s/kubeconfig.yaml`, в общей (default) compose-сети с `postgres`/`minio`.
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
Три части, все в существующем `iac/docker-compose.yaml` + новые k8s-манифесты.
|
||||||
|
|
||||||
|
### A. engine — сервис docker-compose
|
||||||
|
|
||||||
|
Образ: `${SAREX_WORKFLOWS_ENGINE_IMAGE:-cr.yandex/crp3ccidau046kdj8g9q/workflows-endigne_prod:075fc0}`.
|
||||||
|
|
||||||
|
Ключевой env:
|
||||||
|
- Исполнители: `ENABLE_KUBERNETES_EXECUTOR=1`, `ENABLE_AMQP_EXECUTOR=0`.
|
||||||
|
- Воркеры: `COUNT_RUNNING_WORKERS=1`, `COUNT_CANCELING_WORKERS=1`, `COUNT_HANDLE_JOB_WORKERS=1`
|
||||||
|
(иначе задачи не разбираются).
|
||||||
|
- Доступ в k3s: `KUBE_CONFIG=/kube/config`, `KUBE_CONTEXT=default`,
|
||||||
|
`KUBE_ADDR=https://k3s-server:6443`, `JOBS_NAMESPACE=processing`.
|
||||||
|
(`--tls-san=k3s-server` уже задан у k3s-server; при использовании `KUBE_ADDR` TLS
|
||||||
|
проверка отключается, креды берутся из смонтированного kubeconfig.)
|
||||||
|
- Свой доступ к БД: `POSTGRES_ADDRESS=postgres`, `POSTGRES_PORT=5432`,
|
||||||
|
`POSTGRES_DB=${SAREX_PROCESSING_DB:-workflow_db}`, `POSTGRES_USER=${SAREX_PROCESSING_DB_USER:-processing}`,
|
||||||
|
`POSTGRES_PASSWORD=${SAREX_PROCESSING_DB_PASSWORD}`, `POSTGRES_SSL_USE=0`.
|
||||||
|
- Хранилища: `ENABLE_S3_STORAGE=1`, `S3_SERVICE_ACCOUNT=/etc/sarex/yc-s3/yc-s3-service-account.json`
|
||||||
|
(JSON указывает на `http://minio:9000` — прямой доступ к S3, мимо s3-proxy).
|
||||||
|
Остальные `ENABLE_*` = 0.
|
||||||
|
- Прочее по умолчанию: `WORKFLOW_PRIORITY=low`, `MAX_WORKFLOWS_LIMIT=5`, `MAX_RUNNING_JOBS`
|
||||||
|
дефолт, ресурсные `DEFAULT_*_REQUESTS`.
|
||||||
|
|
||||||
|
Монтирования:
|
||||||
|
- `./k3s/kubeconfig.yaml:/kube/config:ro,z` — kubeconfig из k3s-server.
|
||||||
|
- yc-s3 SA JSON (плейсхолдер/рабочий) → `/etc/sarex/yc-s3/yc-s3-service-account.json`.
|
||||||
|
|
||||||
|
`depends_on`:
|
||||||
|
- `postgres` (service_healthy), `postgres-init` (service_completed_successfully),
|
||||||
|
- `k3s-server` (service_healthy),
|
||||||
|
- `processing-k8s-init` (service_completed_successfully) — DNS-мост применён до старта engine.
|
||||||
|
|
||||||
|
### B. k3s — только сервер
|
||||||
|
|
||||||
|
Поднимаем существующий `k3s-server` (несёт kubelet → поды планируются на него).
|
||||||
|
`k3s-worker` НЕ поднимаем. Добавляем `k3s-server` (и `processing-k8s-init`, `engine`)
|
||||||
|
в список поднимаемых сервисов ансибла.
|
||||||
|
|
||||||
|
### C. DNS-мост `postgres` + `minio` в k3s
|
||||||
|
|
||||||
|
Чтобы Job-поды резолвили те же имена, что и в compose:
|
||||||
|
- Namespace `processing`.
|
||||||
|
- `Service` (ClusterIP, без селектора) `postgres` и `minio` + ручные `Endpoints`,
|
||||||
|
указывающие на статические IP compose-контейнеров.
|
||||||
|
- Бареме-имена `postgres`/`minio` резолвятся в подах namespace `processing` через
|
||||||
|
search-domain `…processing.svc.cluster.local`.
|
||||||
|
|
||||||
|
Применение — одноразовый helper-контейнер **`processing-k8s-init`** (по образцу `postgres-init`):
|
||||||
|
образ с `kubectl`, монтирует `./k3s/kubeconfig.yaml` и каталог манифестов
|
||||||
|
(`./k3s/manifests/processing/`), выполняет `kubectl --kubeconfig … apply -f`.
|
||||||
|
Идемпотентно, `restart: "no"`. Ждёт `k3s-server` (service_healthy).
|
||||||
|
|
||||||
|
Манифесты (новый каталог `iac/k3s/manifests/processing/`):
|
||||||
|
- `namespace.yaml` — namespace `processing`.
|
||||||
|
- `bridge-postgres.yaml` — Service+Endpoints `postgres` → `172.28.0.10:5432`.
|
||||||
|
- `bridge-minio.yaml` — Service+Endpoints `minio` → `172.28.0.11:9000`.
|
||||||
|
|
||||||
|
## Сеть и стабильность адресов
|
||||||
|
|
||||||
|
Решение: **статические IP в compose** (прочтение «адреса как в docker-compose»).
|
||||||
|
|
||||||
|
- Верхнеуровневой `networks.default` задаём IPAM с фиксированным subnet, напр. `172.28.0.0/16`.
|
||||||
|
- `postgres` → `172.28.0.10`, `minio` → `172.28.0.11` (пиним только их; остальным IP динамический из того же subnet — их не трогаем).
|
||||||
|
- k8s `Endpoints` ссылаются на эти статические IP → не «плывут» при рестарте, декларативно.
|
||||||
|
|
||||||
|
Маршрут Job-под → `postgres`/`minio`:
|
||||||
|
под выходит через ноду `k3s-server` (её интерфейс в той же `172.28.0.0/16`),
|
||||||
|
трафик доходит до контейнера напрямую/по SNAT ноды. Аутентификация postgres —
|
||||||
|
по паролю с любого хоста (см. находку 4). S3 — по app-кредам MinIO.
|
||||||
|
|
||||||
|
## Интеграция с Ansible (`aero/roles/sarex_stack`)
|
||||||
|
|
||||||
|
- `sarex_services`: добавить `k3s-server`, `processing-k8s-init`, `engine`
|
||||||
|
(в правильном порядке; `depends_on` обеспечит последовательность).
|
||||||
|
- Копирование `iac/k3s/manifests/` на хост рядом с compose (в `deploy_dir`).
|
||||||
|
- Секреты: переиспользуем `SAREX_PROCESSING_DB_PASSWORD`, `SAREX_MINIO_APP_USER/PASSWORD`.
|
||||||
|
Новый образ — переменная `SAREX_WORKFLOWS_ENGINE_IMAGE` в `.env.example`.
|
||||||
|
- kubeconfig генерится k3s-server'ом в `./k3s/kubeconfig.yaml` при первом старте —
|
||||||
|
helper и engine его переиспользуют (server-адрес перекрывается `KUBE_ADDR`).
|
||||||
|
|
||||||
|
## Порядок провижининга
|
||||||
|
|
||||||
|
1. `k3s-server` up → healthy (apiserver `/readyz`).
|
||||||
|
2. `processing-k8s-init` → `kubectl apply` namespace + DNS-мост (Service/Endpoints).
|
||||||
|
3. `postgres` healthy + `postgres-init` completed (роль/БД `workflow_db`/`processing`).
|
||||||
|
4. `engine` up → коннектится к `workflow_db` + kube-API, пуллит workflow, создаёт Job'ы.
|
||||||
|
|
||||||
|
## Риски и точки проверки при реализации
|
||||||
|
|
||||||
|
1. **`KUBE_ADDR` vs правка server-URL.** Проверить, что engine с `KUBE_ADDR=https://k3s-server:6443`
|
||||||
|
+ кредами из kubeconfig реально ходит в apiserver (InsecureSkipTLSVerify). Fallback —
|
||||||
|
sed-правка `server:` в kubeconfig на `https://k3s-server:6443` (SAN уже покрывает имя).
|
||||||
|
2. **Стартовые проверки engine.** Убедиться, что engine поднимается с выключенными downstream
|
||||||
|
`ENABLE_*` и не падает на отсутствии их конфигов/URL при старте.
|
||||||
|
3. **Формат yc-s3 SA JSON.** Точная схема, которую ждёт S3-либа engine/джоб. Для *планирования*
|
||||||
|
не критично (S3 нужен в рантайме джобы) — плейсхолдер допустим, помечаем как follow-up.
|
||||||
|
4. **Образ Job-задачи.** Поды задач тянут приватные образы конверторов; при их отсутствии
|
||||||
|
под уйдёт в `ImagePullBackOff`. Для критерия «планирование» это допустимо — важно, что
|
||||||
|
Job-объект создан и под запланирован.
|
||||||
|
5. **cgroup/SELinux на RedOS.** `k3s-server` уже сконфигурен (`privileged`, `cgroup: host`),
|
||||||
|
но первый реальный старт k3s на хосте надо проверить (dmesg/логи kubelet).
|
||||||
|
|
||||||
|
## Проверка результата
|
||||||
|
|
||||||
|
- `kubectl --kubeconfig ./k3s/kubeconfig.yaml -n processing get svc,endpoints` → `postgres`, `minio` присутствуют.
|
||||||
|
- Логи engine: успешный коннект к `workflow_db` и kube-API, нет паник.
|
||||||
|
- Создать/инициировать workflow → `kubectl -n processing get jobs,pods` показывает созданный Job.
|
||||||
|
- (follow-up) end-to-end выполнение — отдельная задача с поднятием downstream-сервисов.
|
||||||
27
hack/entrypoint.sh
Normal file
27
hack/entrypoint.sh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Точка входа образа с собранными манифестами.
|
||||||
|
#
|
||||||
|
# list — что собралось
|
||||||
|
# cat [префикс] — весь контур (или его часть) одним потоком
|
||||||
|
# export [путь] — скопировать манифесты в примонтированный каталог
|
||||||
|
# <команда> — выполнить как есть
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
case "${1:-list}" in
|
||||||
|
list)
|
||||||
|
ls -1 /manifests
|
||||||
|
;;
|
||||||
|
cat)
|
||||||
|
shift
|
||||||
|
cat /manifests/"${1:-}"*.yaml
|
||||||
|
;;
|
||||||
|
export)
|
||||||
|
dst="${2:-/export}"
|
||||||
|
[ -d "$dst" ] || { echo "Каталог ${dst} не примонтирован" >&2; exit 1; }
|
||||||
|
cp /manifests/*.yaml "$dst"/
|
||||||
|
echo "Выгружено в ${dst}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exec "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
61
hack/render-contour.sh
Normal file
61
hack/render-contour.sh
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Рендер всех kustomization'ов, относящихся к одному контуру.
|
||||||
|
#
|
||||||
|
# render-contour.sh <контур> <каталог-результата>
|
||||||
|
#
|
||||||
|
# Используется из Dockerfile, но работает и локально — нужен только kustomize.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
contour="${1:?не задан контур}"
|
||||||
|
out="${2:?не задан каталог результата}"
|
||||||
|
|
||||||
|
if [ ! -d "clusters/${contour}" ]; then
|
||||||
|
echo "Неизвестный контур: ${contour}" >&2
|
||||||
|
echo "Доступные:" >&2
|
||||||
|
ls clusters | sed 's/^/ - /' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$out"
|
||||||
|
|
||||||
|
build() {
|
||||||
|
dir="$1"
|
||||||
|
# Имя файла повторяет путь: clusters/aero/apps -> clusters.aero.apps.yaml
|
||||||
|
name=$(echo "$dir" | tr '/' '.')
|
||||||
|
echo " build ${dir}"
|
||||||
|
# --load-restrictor LoadRestrictionsNone: слои ссылаются на apps/ и
|
||||||
|
# infrastructure/ через ../../.., то есть выходят за корень kustomization.
|
||||||
|
kustomize build --load-restrictor LoadRestrictionsNone "$dir" > "${out}/${name}.yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Каталоги контура: сам clusters/<contour> и все его слои (controllers, configs,
|
||||||
|
# apps, infrastructure — набор у контуров разный, поэтому ищем, а не перечисляем).
|
||||||
|
# flux-system исключён: gotk-components.yaml генерирует `flux bootstrap`,
|
||||||
|
# он и так лежит в репозитории готовым.
|
||||||
|
echo "Контур ${contour}: слои Flux"
|
||||||
|
find "clusters/${contour}" -name kustomization.yaml -not -path '*/flux-system/*' \
|
||||||
|
| sed 's#/kustomization.yaml$##' | sort > /tmp/layers.txt
|
||||||
|
while read -r dir; do
|
||||||
|
build "$dir"
|
||||||
|
done < /tmp/layers.txt
|
||||||
|
|
||||||
|
# Оверлеи приложений и инфраструктуры. Через слои они уже отрендерились, но
|
||||||
|
# отдельные файлы дают поресурсный diff и ловят оверлей, который в слои пока
|
||||||
|
# не подключён, — такой ломался бы уже при подключении.
|
||||||
|
for root in apps infrastructure; do
|
||||||
|
[ -d "$root" ] || continue
|
||||||
|
echo "Контур ${contour}: оверлеи ${root}/*/${contour}"
|
||||||
|
for dir in "$root"/*/"$contour"; do
|
||||||
|
[ -f "${dir}/kustomization.yaml" ] || continue
|
||||||
|
build "$dir"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Пустой результат почти наверняка означает опечатку в имени контура —
|
||||||
|
# лучше упасть на сборке, чем отдать пустой образ.
|
||||||
|
if [ -z "$(ls -A "$out")" ]; then
|
||||||
|
echo "Для контура ${contour} не собрано ни одного манифеста" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Готово: $(ls -1 "$out" | wc -l) файлов в ${out}"
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
# Внутренний удостоверяющий центр контура aero.
|
||||||
|
#
|
||||||
|
# Зачем отдельный издатель: в репозитории все ClusterIssuer — ACME (Let's Encrypt)
|
||||||
|
# с решателем http01 и ingress class nginx. Контур закрытый, публичного DNS и
|
||||||
|
# входящего HTTP от ACME-сервера нет, поэтому ACME здесь принципиально нерабочий.
|
||||||
|
#
|
||||||
|
# Схема стандартная для внутренней PKI, в три шага:
|
||||||
|
# 1. selfsigned-issuer — самоподписывающий издатель, нужен только чтобы
|
||||||
|
# выпустить корневой сертификат;
|
||||||
|
# 2. aero-ca — сам корневой сертификат (isCA: true). Его приватный
|
||||||
|
# ключ попадает в secret aero-ca-key-pair в namespace cert-manager;
|
||||||
|
# 3. aero-ca-issuer — издатель, который подписывает этим корнем сертификаты
|
||||||
|
# для сервисов кластера (istio-gateway, dashboard и далее).
|
||||||
|
#
|
||||||
|
# Корневой сертификат нужно раздать клиентам (браузеры, curl), иначе они будут
|
||||||
|
# ругаться на недоверенный CA. Выгрузить его можно так:
|
||||||
|
# kubectl -n cert-manager get secret aero-ca-key-pair -o jsonpath='{.data.tls\.crt}' | base64 -d
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: selfsigned-issuer
|
||||||
|
spec:
|
||||||
|
selfSigned: {}
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: aero-ca
|
||||||
|
# Namespace обязателен и должен совпадать с cert-manager --cluster-resource-namespace
|
||||||
|
# (по умолчанию cert-manager): именно там ClusterIssuer ищет свой секрет.
|
||||||
|
namespace: cert-manager
|
||||||
|
spec:
|
||||||
|
isCA: true
|
||||||
|
commonName: aero-ca
|
||||||
|
secretName: aero-ca-key-pair
|
||||||
|
duration: 87600h # 10 лет — корень контура, ротация вручную
|
||||||
|
renewBefore: 720h # 30 суток
|
||||||
|
privateKey:
|
||||||
|
algorithm: ECDSA
|
||||||
|
size: 256
|
||||||
|
issuerRef:
|
||||||
|
name: selfsigned-issuer
|
||||||
|
kind: ClusterIssuer
|
||||||
|
group: cert-manager.io
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: aero-ca-issuer
|
||||||
|
spec:
|
||||||
|
ca:
|
||||||
|
secretName: aero-ca-key-pair
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
# Боевой издатель контура: Let's Encrypt через ACME.
|
||||||
|
#
|
||||||
|
# Раньше здесь был только внутренний CA (clusterissuer-ca.yaml) — исходили из
|
||||||
|
# того, что контур закрытый и до ACME-сервера не достучаться. Это оказалось не
|
||||||
|
# так: домены контура резолвятся в публичном DNS
|
||||||
|
# sarex.local.lonsdaleites.ru → 158.160.38.249
|
||||||
|
# *.sarex.local.lonsdaleites.ru → 158.160.38.249
|
||||||
|
# а порты 80/443 хоста опубликованы наружу (см. docker-compose.yaml, k3s-server).
|
||||||
|
# Значит http01-проверка проходит, и сертификаты можно брать настоящие.
|
||||||
|
#
|
||||||
|
# ВАЖНО ПРО WILDCARD: решатель http01 принципиально НЕ выдаёт wildcard-
|
||||||
|
# сертификаты — Let's Encrypt требует для них dns01. Поэтому сертификат контура
|
||||||
|
# перечисляет имена явно (см. infrastructure/istio-config/aero). Wildcard
|
||||||
|
# остался только в hosts самого Gateway, где сертификат не нужен.
|
||||||
|
#
|
||||||
|
# Внутренний CA (aero-ca-issuer) НЕ удалён: он остаётся запасным вариантом на
|
||||||
|
# случай, когда ACME недоступен, и для внутренних имён, которых нет в публичном
|
||||||
|
# DNS.
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
# На этот адрес Let's Encrypt шлёт предупреждения об истечении.
|
||||||
|
email: "emelinda1990@gmail.com"
|
||||||
|
# ОТДЕЛЬНОЕ имя секрета, НЕ общепринятое в репозитории letsencrypt-secret-key.
|
||||||
|
#
|
||||||
|
# Тот секрет создаёт сам чарт cert-manager-infra (метки managed-by=Helm,
|
||||||
|
# аннотация helm.sh/resource-policy: keep) и кладёт в поле tls.key
|
||||||
|
# ЗАГЛУШКУ — невалидный PEM. cert-manager читает её как ключ ACME-аккаунта
|
||||||
|
# и падает:
|
||||||
|
# Account private key is invalid: error decoding private key PEM block
|
||||||
|
# Reason: ErrVerifyACMEAccount
|
||||||
|
# Удалить заглушку нельзя: чарт пересоздаст её на следующем апгрейде и
|
||||||
|
# затрёт настоящий ключ аккаунта. Поэтому издателю выдан собственный
|
||||||
|
# секрет — его создаёт и владеет им cert-manager, Helm его не трогает.
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-issuer-istio-account-key
|
||||||
|
server: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
solvers:
|
||||||
|
# class istio — challenge-Ingress обслуживает istio-ingressgateway,
|
||||||
|
# тот самый, что занимает hostPort 80/443 на k3s-server.
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: istio
|
||||||
10
infrastructure/cert-manager/aero/configs/kustomization.yaml
Normal file
10
infrastructure/cert-manager/aero/configs/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Пользовательские ресурсы cert-manager: применяются слоем infra-configs,
|
||||||
|
# после того как чарт из слоя infra-controllers поставил CRD.
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
# Внутренний CA — запасной издатель для имён, которых нет в публичном DNS.
|
||||||
|
- clusterissuer-ca.yaml
|
||||||
|
# Боевой издатель: Let's Encrypt, http01 через istio-ingressgateway.
|
||||||
|
- clusterissuer-letsencrypt.yaml
|
||||||
32
infrastructure/cert-manager/aero/kustomization.yaml
Normal file
32
infrastructure/cert-manager/aero/kustomization.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
|
||||||
|
# Здесь только чарт и namespace. Корневой CA (clusterissuer-ca.yaml) лежит
|
||||||
|
# рядом, но подключается СЛОЕМ ВЫШЕ — из clusters/aero/configs: его ресурсы
|
||||||
|
# требуют CRD, которые ставит этот же чарт.
|
||||||
|
#
|
||||||
|
# ACME-издатели из base выключены удалением, а не форком base: overlay
|
||||||
|
# остаётся патчем и не расходится при обновлении base. Плюс без этого они
|
||||||
|
# ломали бы dry-run слоя controllers — их CRD тоже ещё не существует.
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: letsencrypt-issuer
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-issuer
|
||||||
|
- target:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
59
infrastructure/dashboard/aero/admin-user.yaml
Normal file
59
infrastructure/dashboard/aero/admin-user.yaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Учётка для входа в Kubernetes Dashboard.
|
||||||
|
#
|
||||||
|
# Чарт дашборда своего пользователя НЕ создаёт: он лишь показывает форму
|
||||||
|
# ввода токена и ходит в API от имени того, чей токен введён. Без этих трёх
|
||||||
|
# объектов дашборд открывается, но войти в него нечем.
|
||||||
|
#
|
||||||
|
# --- Про бессрочность токена -------------------------------------------------
|
||||||
|
# Начиная с Kubernetes 1.24 ServiceAccount больше не получает вечный токен
|
||||||
|
# автоматически, а `kubectl create token` выдаёт короткоживущий (по умолчанию
|
||||||
|
# час). Единственный штатный способ получить токен без срока действия —
|
||||||
|
# завести Secret типа kubernetes.io/service-account-token с аннотацией
|
||||||
|
# kubernetes.io/service-account.name: контроллер токенов сам положит в него
|
||||||
|
# поле token, и это будет legacy-токен, действующий, пока существует Secret.
|
||||||
|
#
|
||||||
|
# Значение поля token в манифесте НЕ указывается и в репозиторий не попадает:
|
||||||
|
# его проставляет кластер. Прочитать —
|
||||||
|
# kubectl -n kubernetes-dashboard get secret dashboard-admin-token \
|
||||||
|
# -o go-template='{{.data.token | base64decode}}'
|
||||||
|
#
|
||||||
|
# --- Про объём прав ----------------------------------------------------------
|
||||||
|
# Привязка идёт к встроенной роли cluster-admin: дашборд задуман как
|
||||||
|
# инструмент оператора — смотреть логи, заходить в контейнеры, перезапускать
|
||||||
|
# нагрузки, — и с меньшими правами половина его экранов отдаёт ошибки доступа.
|
||||||
|
#
|
||||||
|
# Плата за это честная: токен бессрочный, прав максимум, а дашборд опубликован
|
||||||
|
# наружу на dashboard.sarex.local.lonsdaleites.ru. Для закрытого тестового
|
||||||
|
# контура приемлемо; если контур перестанет быть одноразовым — либо сузить
|
||||||
|
# roleRef до встроенной view (дашборд станет read-only), либо удалить Secret,
|
||||||
|
# и токен мгновенно перестанет действовать.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: dashboard-admin
|
||||||
|
namespace: kubernetes-dashboard
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: dashboard-admin
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
# Встроенная роль, своей не заводим: повторять cluster-admin вручную —
|
||||||
|
# это гарантированно отстать от списка ресурсов при обновлении кластера.
|
||||||
|
name: cluster-admin
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: dashboard-admin
|
||||||
|
namespace: kubernetes-dashboard
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: dashboard-admin-token
|
||||||
|
namespace: kubernetes-dashboard
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/service-account.name: dashboard-admin
|
||||||
|
type: kubernetes.io/service-account-token
|
||||||
38
infrastructure/dashboard/aero/dashboard.yaml
Normal file
38
infrastructure/dashboard/aero/dashboard.yaml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: dashboard
|
||||||
|
namespace: kubernetes-dashboard
|
||||||
|
spec:
|
||||||
|
interval: 2m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
# Kong слушает 8000 открытым HTTP и ничего не знает про istio. Без этого
|
||||||
|
# DestinationRule sidecar инициирует к нему mTLS, Kong видит сырое TLS-
|
||||||
|
# рукопожатие на плейнтекстовом порту, отвечает 400 — а наружу istio отдаёт
|
||||||
|
# 503. В логах Kong это выглядит как "\x16\x03\x01..." с SNI
|
||||||
|
# outbound_.80_._.dashboard-kong-proxy...
|
||||||
|
# tlsMode: DISABLE переводит обращение к нему в обычный HTTP.
|
||||||
|
destinationRule:
|
||||||
|
enabled: true
|
||||||
|
host: "dashboard-kong-proxy"
|
||||||
|
tlsMode: "DISABLE"
|
||||||
|
# Собственные VirtualService и Gateway чарта выключены: маршрутизация контура
|
||||||
|
# описана централизованно в infrastructure/istio-config/aero. Плюс родной VS
|
||||||
|
# чарта всё равно нерабочий — он ссылается на сервис
|
||||||
|
# kubernetes-dashboard-kong-proxy, которого не существует (реальное имя —
|
||||||
|
# dashboard-kong-proxy), и на домен dashboard.preprod.sarex.io, к контуру
|
||||||
|
# отношения не имеющий.
|
||||||
|
virtualService:
|
||||||
|
enabled: false
|
||||||
|
gateway:
|
||||||
|
enabled: false
|
||||||
|
# Образы лежат в приватном cr.yandex.
|
||||||
|
app:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- regcred
|
||||||
|
kong:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- regcred
|
||||||
13
infrastructure/dashboard/aero/kustomization.yaml
Normal file
13
infrastructure/dashboard/aero/kustomization.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
# ServiceAccount + ClusterRoleBinding + Secret с бессрочным токеном.
|
||||||
|
# Своего пользователя чарт дашборда не создаёт — обоснование в шапке файла.
|
||||||
|
- admin-user.yaml
|
||||||
|
patches:
|
||||||
|
- path: dashboard.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: dashboard
|
||||||
5
infrastructure/istio-base/aero/kustomization.yaml
Normal file
5
infrastructure/istio-base/aero/kustomization.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
237
infrastructure/istio-config/aero/istio-config.yaml
Normal file
237
infrastructure/istio-config/aero/istio-config.yaml
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: istio-config
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
interval: 2m
|
||||||
|
timeout: 10m
|
||||||
|
# disableWait РАЗРЫВАЕТ ДЕДЛОК, не украшательство.
|
||||||
|
#
|
||||||
|
# Чарт создаёт Certificate, а Flux после установки релиза ждёт готовности его
|
||||||
|
# ресурсов — в том числе этого сертификата. Выпустить его нельзя, пока нет
|
||||||
|
# ClusterIssuer, а издатели живут в слое infra-configs, который по dependsOn
|
||||||
|
# ждёт готовности infra-controllers, где и находится этот самый релиз. Круг:
|
||||||
|
# istio-config → Certificate → ClusterIssuer → infra-configs
|
||||||
|
# → infra-controllers → istio-config
|
||||||
|
# На практике это выглядело как вечное "Running 'upgrade' action" при
|
||||||
|
# Released: True и Certificate в состоянии
|
||||||
|
# "Issuing certificate as Secret does not exist".
|
||||||
|
#
|
||||||
|
# Ждать здесь всё равно нечего: чарт разворачивает только конфигурационные
|
||||||
|
# объекты (Gateway, VirtualService, Certificate), а не рабочие нагрузки.
|
||||||
|
# Сертификат выпустится асинхронно, как только появится издатель.
|
||||||
|
install:
|
||||||
|
disableWait: true
|
||||||
|
upgrade:
|
||||||
|
disableWait: true
|
||||||
|
# Список продублирован из base целиком: для CRD kustomize заменяет списки,
|
||||||
|
# а не сливает, поэтому частичный патч потерял бы зависимости от istio.
|
||||||
|
# cert-manager добавлен сверх base — чарт istio-config рендерит Certificate,
|
||||||
|
# и без готовых CRD установка падает.
|
||||||
|
dependsOn:
|
||||||
|
- name: istio-base
|
||||||
|
namespace: istio-system
|
||||||
|
- name: istiod
|
||||||
|
namespace: istio-system
|
||||||
|
- name: ingressgateway
|
||||||
|
namespace: istio-system
|
||||||
|
- name: cert-manager
|
||||||
|
namespace: cert-manager
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
env: aero
|
||||||
|
environments:
|
||||||
|
aero:
|
||||||
|
namespaces: []
|
||||||
|
# Блок обязателен, даже пустой: шаблоны gateway.yaml и virtualservice.yaml
|
||||||
|
# разыменовывают $env.istio.gateways / .virtualServices без проверки на
|
||||||
|
# nil и падают с "nil pointer evaluating interface {}".
|
||||||
|
istio:
|
||||||
|
# Один общий Gateway на весь контур вместо отдельного на каждый сервис.
|
||||||
|
# Так сделано потому, что у контура есть wildcard-домен: сертификат и
|
||||||
|
# Gateway покрывают *.sarex.local.lonsdaleites.ru целиком, а новый
|
||||||
|
# сервис публикуется добавлением ОДНОГО VirtualService — без правки
|
||||||
|
# сертификатов и Gateway. В прод-кластерах домены выписаны поштучно,
|
||||||
|
# там wildcard'а нет.
|
||||||
|
gateways:
|
||||||
|
contour:
|
||||||
|
name: contour-gateway
|
||||||
|
namespace: gateway
|
||||||
|
servers:
|
||||||
|
# ПОРЯДОК ЗНАЧИМ: первым обязан идти хост без звёздочки.
|
||||||
|
# Чарт собирает из hosts[0] имя порта
|
||||||
|
# name: {{ printf "%s-https-443" (index $server.hosts 0 ...) }}
|
||||||
|
# и НЕ заключает результат в кавычки. Хост "*.domain" дал бы
|
||||||
|
# токен "*-domain-https-443", который YAML читает как ссылку на
|
||||||
|
# якорь, и Helm падает с
|
||||||
|
# MalformedYAMLError: yaml: unknown anchor ... referenced
|
||||||
|
# Сами hosts чарт квотирует, поэтому wildcard ниже безопасен.
|
||||||
|
- hosts:
|
||||||
|
- sarex.local.lonsdaleites.ru
|
||||||
|
- "*.sarex.local.lonsdaleites.ru"
|
||||||
|
tls:
|
||||||
|
credentialName: contour-tls
|
||||||
|
# ВНИМАНИЕ: имя ресурса VirtualService чарт берёт из КЛЮЧА map
|
||||||
|
# ({{ $vsName }}), а поле name внутри — игнорирует, в отличие от
|
||||||
|
# gateways, где .name учитывается. Поэтому здесь его нет: ключ и есть
|
||||||
|
# имя. В остальных кластерах репозитория name у virtualServices
|
||||||
|
# присутствует, но ни на что не влияет.
|
||||||
|
virtualServices:
|
||||||
|
# Kubernetes Dashboard. Свой VirtualService чарт дашборда создаёт на
|
||||||
|
# домен dashboard.preprod.sarex.io — он к контуру aero отношения не
|
||||||
|
# имеет и никуда не резолвится. Этот выводит дашборд на домен
|
||||||
|
# контура; точка входа — kong-proxy, как во всех остальных кластерах.
|
||||||
|
dashboard:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- dashboard.sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: dashboard-kong-proxy.kubernetes-dashboard.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
# Платформа sarex: frontend в корне, backend на /api/ и /admin/.
|
||||||
|
# Порядок маршрутов значим — более специфичные префиксы идут
|
||||||
|
# первыми, иначе их перехватит правило для /.
|
||||||
|
platform:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
# Медиафайлы отдаёт s3-proxy: он добавляет CORS и поддерживает
|
||||||
|
# Range-запросы, которых у S3 API «как есть» нет. Backend
|
||||||
|
# формирует ссылки вида /media/<key>, поэтому маршрут должен
|
||||||
|
# идти ПЕРЕД правилом для /.
|
||||||
|
#
|
||||||
|
# rewrite ОБЯЗАТЕЛЕН. s3-proxy не срезает префикс сам — он
|
||||||
|
# отображает путь URL прямо в ключ объекта. Без rewrite он
|
||||||
|
# искал бы media/orthophotos/... , тогда как в бакете лежит
|
||||||
|
# orthophotos/... , и отвечал бы
|
||||||
|
# NoSuchKey: The specified key does not exist (404)
|
||||||
|
# Проверено на живом объекте: /orthophotos/... отдаёт 200,
|
||||||
|
# /media/orthophotos/... — 404.
|
||||||
|
- path:
|
||||||
|
prefix: /media/
|
||||||
|
rewrite: /
|
||||||
|
service: s3-proxy-svc.django.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
# API микрофронтенда workflows. Висит на домене платформы, а
|
||||||
|
# не на отдельном поддомене: адреса вшиты в JS-бандл на этапе
|
||||||
|
# сборки и указывают на тот же origin относительными путями.
|
||||||
|
#
|
||||||
|
# Маршрут живёт в ЭТОМ VirtualService, а не в своём: Istio не
|
||||||
|
# сливает несколько VirtualService, привязанных к одной паре
|
||||||
|
# хост+gateway, — часть правил молча потерялась бы.
|
||||||
|
#
|
||||||
|
# ЗДЕСЬ НЕТ МАРШРУТА /workflows/ НА frontend-svc.processing, и
|
||||||
|
# это осознанно. Такой маршрут был и ломал навигацию: адрес
|
||||||
|
# вида /workflows/<uuid> — это путь ВНУТРИ SPA платформы,
|
||||||
|
# который должен отдать её index.html, а istio уводил его в
|
||||||
|
# nginx микрофронтенда, где такого файла нет:
|
||||||
|
# 404 Not Found — nginx/1.19.6
|
||||||
|
# Статика микрофронтенда при этом доезжает и без него: nginx
|
||||||
|
# платформы сам проксирует /workflows/*.js в processing
|
||||||
|
# (apps/django/aero/nginx-configmap.yaml). Проверено — 200.
|
||||||
|
- path:
|
||||||
|
prefix: /workflows/api/
|
||||||
|
rewrite: /api/
|
||||||
|
service: backend-svc.processing.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
- path:
|
||||||
|
prefix: /api/
|
||||||
|
service: backend-svc.django.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
- path:
|
||||||
|
prefix: /admin/
|
||||||
|
service: backend-svc.django.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: frontend-svc.django.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
# BIM API (aero/bimbackend). Отдельный поддомен, а не префикс на
|
||||||
|
# домене платформы: свой фронтенд у сервиса отсутствует, а его
|
||||||
|
# пути на общем домене конфликтовали бы с маршрутом /api/ django.
|
||||||
|
# Тот же адрес приложение знает про себя само —
|
||||||
|
# BIM_API_EXTERNAL_HOST в apps/bim/aero, оно строит по нему ссылки.
|
||||||
|
#
|
||||||
|
# Имя добавлено в dnsNames сертификата ниже: без этого браузер
|
||||||
|
# получит сертификат, в котором его нет.
|
||||||
|
bim:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- bim.sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: bim-api-service.bim.svc.cluster.local
|
||||||
|
port: 5555
|
||||||
|
# Management UI RabbitMQ.
|
||||||
|
rabbitmq:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- rabbitmq.sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: rabbitmq.rabbitmq.svc.cluster.local
|
||||||
|
port: 15672
|
||||||
|
# MinIO: консоль на /console/, S3 API в корне. Порядок маршрутов
|
||||||
|
# значим — более специфичный префикс должен идти первым, иначе его
|
||||||
|
# перехватит правило для /.
|
||||||
|
minio:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- minio.sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /console/
|
||||||
|
service: minio-console.minio.svc.cluster.local
|
||||||
|
port: 9001
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: minio.minio.svc.cluster.local
|
||||||
|
port: 9000
|
||||||
|
requestAuthentications: {}
|
||||||
|
authorizationPolicies: {}
|
||||||
|
certManager:
|
||||||
|
certificates:
|
||||||
|
# Издатель — внутренний CA контура, а не ACME: до Let's Encrypt
|
||||||
|
# из закрытого контура не достучаться.
|
||||||
|
# См. infrastructure/cert-manager/aero/clusterissuer-ca.yaml
|
||||||
|
#
|
||||||
|
# Домены продублированы из aero/roles/sarex_stack/defaults/main.yml
|
||||||
|
# (platform_domain и производные). Единого источника нет — так же
|
||||||
|
# захардкожено во всех остальных кластерах репозитория.
|
||||||
|
#
|
||||||
|
# Один сертификат на весь контур, общий для всех его сервисов.
|
||||||
|
contour-tls:
|
||||||
|
# Имена перечислены ЯВНО, а не wildcard'ом: решатель http01
|
||||||
|
# принципиально не выдаёт wildcard-сертификаты, для них Let's
|
||||||
|
# Encrypt требует dns01, а webhook DNS-провайдера в контуре не
|
||||||
|
# настроен. Wildcard остался в hosts Gateway — там он про
|
||||||
|
# маршрутизацию, а не про TLS.
|
||||||
|
#
|
||||||
|
# Публикуешь новый сервис — добавь его имя сюда, иначе браузер
|
||||||
|
# получит сертификат, в котором этого имени нет.
|
||||||
|
dnsNames:
|
||||||
|
- sarex.local.lonsdaleites.ru
|
||||||
|
- dashboard.sarex.local.lonsdaleites.ru
|
||||||
|
- rabbitmq.sarex.local.lonsdaleites.ru
|
||||||
|
- minio.sarex.local.lonsdaleites.ru
|
||||||
|
- bim.sarex.local.lonsdaleites.ru
|
||||||
|
issuerRef:
|
||||||
|
# Боевой издатель, см. infrastructure/cert-manager/aero/configs.
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
kind: ClusterIssuer
|
||||||
10
infrastructure/istio-config/aero/kustomization.yaml
Normal file
10
infrastructure/istio-config/aero/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: istio-config.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: istio-config
|
||||||
17
infrastructure/istio-gateway/aero/istio-gateway.yaml
Normal file
17
infrastructure/istio-gateway/aero/istio-gateway.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: ingressgateway
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
# Ключ _internal_defaults_do_not_set — так значения именует сам чарт
|
||||||
|
# istio-gateway, это не ошибка.
|
||||||
|
_internal_defaults_do_not_set:
|
||||||
|
# Дефолт чарта — 3 реплики, но больше одной не влезает физически:
|
||||||
|
# под занимает hostPort 80/443, а nodeAffinity разрешает только
|
||||||
|
# control-plane, и такая нода в контуре одна (k3s-server). Остальные
|
||||||
|
# реплики висли бы в Pending навсегда. То же значение в yc-k8s-test.
|
||||||
|
replicaCount: 1
|
||||||
10
infrastructure/istio-gateway/aero/kustomization.yaml
Normal file
10
infrastructure/istio-gateway/aero/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: istio-gateway.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: ingressgateway
|
||||||
44
infrastructure/istio-pilot/aero/istio-pilot.yaml
Normal file
44
infrastructure/istio-pilot/aero/istio-pilot.yaml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Ожидание готовности sidecar'а перед стартом приложения.
|
||||||
|
#
|
||||||
|
# ЗАЧЕМ. Envoy перехватывает исходящий трафик пода через iptables-правила,
|
||||||
|
# которые ставит init-контейнер istio-init. Правила появляются РАНЬШЕ, чем
|
||||||
|
# сам envoy начинает слушать, и приложение, дёрнувшее сеть в первые секунды,
|
||||||
|
# получает не таймаут, а connection refused: пакет уже завёрнут на порт
|
||||||
|
# envoy, а там ещё никого нет.
|
||||||
|
#
|
||||||
|
# Так падал workflows-api: контейнер стартовал и умирал в ту же секунду —
|
||||||
|
# INFO Starting workflows migrations
|
||||||
|
# INFO init database connection...
|
||||||
|
# FATAL [dial tcp 10.43.195.4:5432: connect: connection refused]
|
||||||
|
# при restartCount=0 у istio-proxy. Kubernetes перезапускал его, со второй
|
||||||
|
# попытки envoy успевал подняться, и дальше всё работало. То есть симптом —
|
||||||
|
# ровно один рестарт на старте, легко принимаемый за случайность.
|
||||||
|
#
|
||||||
|
# ПОЧЕМУ НА УРОВНЕ MESH, А НЕ АННОТАЦИЕЙ НА ПОДЕ. Уязвимы все, кто ходит в
|
||||||
|
# БД или брокер сразу при запуске, а это почти каждый бэкенд контура:
|
||||||
|
# django катит migrate первым делом, celery подключается к rabbitmq,
|
||||||
|
# workspaces-api и engine-low — к postgres. Они не падали не потому, что
|
||||||
|
# устроены иначе, а потому что успевали. Чинить это по одному поду —
|
||||||
|
# значит ждать, пока каждый однажды не повезёт.
|
||||||
|
#
|
||||||
|
# ЦЕНА. Каждый под стартует на время готовности envoy дольше (обычно 1-2 с):
|
||||||
|
# istio добавляет в sidecar postStart-хук, который блокирует запуск
|
||||||
|
# остальных контейнеров. Для контура это несопоставимо дешевле, чем
|
||||||
|
# ложные падения на старте.
|
||||||
|
#
|
||||||
|
# На Job'ы не влияет: у всех, что есть в контуре, инжекция отключена
|
||||||
|
# аннотацией sidecar.istio.io/inject: "false" — иначе envoy не давал бы им
|
||||||
|
# завершиться.
|
||||||
|
#
|
||||||
|
# Настройка действует В МОМЕНТ ИНЖЕКЦИИ, поэтому на уже запущенные поды не
|
||||||
|
# распространяется: хук появится у них при следующем пересоздании.
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: istiod
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
values:
|
||||||
|
meshConfig:
|
||||||
|
defaultConfig:
|
||||||
|
holdApplicationUntilProxyStarts: true
|
||||||
8
infrastructure/istio-pilot/aero/kustomization.yaml
Normal file
8
infrastructure/istio-pilot/aero/kustomization.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
# holdApplicationUntilProxyStarts — обоснование в шапке файла.
|
||||||
|
- path: istio-pilot.yaml
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: local-path-provisioner.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: local-path-provisioner
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: local-path-provisioner
|
||||||
|
namespace: local-path-provisioner
|
||||||
|
spec:
|
||||||
|
interval: 2m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
replicaCount: 1
|
||||||
|
image:
|
||||||
|
repository: cr.yandex/crp3ccidau046kdj8g9q/contour/local-path-provisioner-nn/local-path-provisioner
|
||||||
|
tag: v0.0.24
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
helperImage:
|
||||||
|
repository: cr.yandex/crp3ccidau046kdj8g9q/contour/local-path-provisioner-nn/busybox
|
||||||
|
tag: latest
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
defaultSettings:
|
||||||
|
registrySecret: null
|
||||||
|
privateRegistry:
|
||||||
|
registryUrl: null
|
||||||
|
registryUser: null
|
||||||
|
registryPasswd: null
|
||||||
|
storageClass:
|
||||||
|
create: true
|
||||||
|
# Класс по умолчанию: встроенный в k3s провижинер отключён
|
||||||
|
# (--disable=local-storage в docker-compose.yaml), и default-класса в
|
||||||
|
# кластере иначе не остаётся — PVC без явного storageClassName висели бы
|
||||||
|
# в Pending.
|
||||||
|
defaultClass: true
|
||||||
|
name: local-path
|
||||||
|
reclaimPolicy: Delete
|
||||||
|
# Путь ВНУТРИ контейнера ноды. Взят тот же, что использовал встроенный
|
||||||
|
# провижинер, потому что он уже смонтирован с хоста:
|
||||||
|
# ./k3s-storage/<нода> -> /var/lib/rancher/k3s/storage
|
||||||
|
# Благодаря этому данные PVC переживают пересоздание контейнеров нод и
|
||||||
|
# доступны для бэкапа обычными средствами хоста. Дефолт чарта
|
||||||
|
# (/opt/local-path-provisioner) никуда не смонтирован — данные исчезли бы
|
||||||
|
# вместе с контейнером ноды.
|
||||||
|
nodePathMap:
|
||||||
|
- node: DEFAULT_PATH_FOR_NON_LISTED_NODES
|
||||||
|
paths:
|
||||||
|
- /var/lib/rancher/k3s/storage
|
||||||
|
rbac:
|
||||||
|
create: true
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
name: ""
|
||||||
|
resources: {}
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
affinity: {}
|
||||||
|
configmap:
|
||||||
|
name: local-path-config
|
||||||
|
setup: |-
|
||||||
|
set -eu
|
||||||
|
mkdir -m 0777 -p "$VOL_DIR"
|
||||||
|
teardown: |-
|
||||||
|
set -eu
|
||||||
|
rm -rf "$VOL_DIR"
|
||||||
87
infrastructure/minio/aero/buckets-job.yaml
Normal file
87
infrastructure/minio/aero/buckets-job.yaml
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
# Заведение бакетов. Чарт MinIO этого не умеет — форк не принимает ни buckets,
|
||||||
|
# ни provisioning, — а приложения ждут бакет готовым: django падает на загрузке
|
||||||
|
# файлов, measurements — на старте, если бакета из его vault-шаблона нет.
|
||||||
|
#
|
||||||
|
# До этого Job'а бакеты заводились руками через mc. Такой бакет не переживает
|
||||||
|
# пересоздание контура и не виден в репозитории: развернув всё с нуля, ты
|
||||||
|
# получишь работающий MinIO и приложения, падающие на отсутствующем бакете.
|
||||||
|
#
|
||||||
|
# Идемпотентность даёт mc mb --ignore-existing: Job гоняется при каждом
|
||||||
|
# применении релиза (ttl удаляет завершившийся), повторный запуск на готовых
|
||||||
|
# бакетах отрабатывает вхолостую и завершается нулём.
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: minio-buckets
|
||||||
|
namespace: minio
|
||||||
|
annotations:
|
||||||
|
# Пересоздавать Job при правке: spec.template у Job неизменяем, обычный
|
||||||
|
# apply на изменившемся списке бакетов падает с "field is immutable".
|
||||||
|
#
|
||||||
|
# ЗНАЧЕНИЕ ИМЕННО enabled. Аннотации Flux — force, prune, ssa — принимают
|
||||||
|
# enabled/disabled, а не булево. Написанное здесь раньше "true" Flux молча
|
||||||
|
# игнорировал: пока Job'а не существовало, обычный apply проходил и всё
|
||||||
|
# выглядело рабочим, а на первой же правке скрипта слой infra-controllers
|
||||||
|
# встал целиком —
|
||||||
|
# Job/minio/minio-buckets dry-run failed (Invalid): field is immutable
|
||||||
|
# и вместе с ним встал зависящий от него слой apps.
|
||||||
|
kustomize.toolkit.fluxcd.io/force: enabled
|
||||||
|
spec:
|
||||||
|
# Job живёт час после завершения и удаляется. Не 0: если он упадёт, логи
|
||||||
|
# должны остаться доступными для разбора.
|
||||||
|
ttlSecondsAfterFinished: 3600
|
||||||
|
backoffLimit: 6
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# Sidecar istio не даёт Job завершиться: контейнер задачи выходит, а
|
||||||
|
# envoy продолжает работать, и под навсегда остаётся Running.
|
||||||
|
sidecar.istio.io/inject: "false"
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
|
vault.hashicorp.com/auth-path: auth/kubernetes
|
||||||
|
vault.hashicorp.com/role: minio
|
||||||
|
vault.hashicorp.com/agent-inject-secret-admin: secrets/data/minio/admin
|
||||||
|
vault.hashicorp.com/agent-inject-template-admin: |-
|
||||||
|
{{- with secret "secrets/data/minio/admin" -}}
|
||||||
|
MINIO_ROOT_USER={{ index .Data.data "rootUser" }}
|
||||||
|
MINIO_ROOT_PASSWORD={{ index .Data.data "rootPassword" }}
|
||||||
|
{{- end -}}
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
serviceAccountName: minio-sa
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
containers:
|
||||||
|
- name: mc
|
||||||
|
# Тот же образ, которым бакеты заводил compose-стек (SAREX_MC_IMAGE
|
||||||
|
# в docker-compose.yaml). Тег фиксированный: latest в приватном
|
||||||
|
# реестре не публикуется.
|
||||||
|
image: cr.yandex/crp3ccidau046kdj8g9q/mc:250724
|
||||||
|
command: ["/bin/sh", "-ec"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
. /vault/secrets/admin
|
||||||
|
|
||||||
|
# Ждём готовности API: Job стартует вместе с релизом и обгоняет
|
||||||
|
# готовность самого MinIO.
|
||||||
|
until mc alias set contour http://minio.minio.svc.cluster.local:9000 \
|
||||||
|
"$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null 2>&1; do
|
||||||
|
echo "жду MinIO..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
# Бакет в контуре ОДИН: в него пишет django, из него раздаёт
|
||||||
|
# s3-proxy на маршруте /media/, туда же переведён measurements.
|
||||||
|
# Раньше у measurements в vault-шаблоне был зашит собственный
|
||||||
|
# бакет, и файл, положенный им, по ссылке из django не отдавался.
|
||||||
|
#
|
||||||
|
# Ранее созданный бакет measurements этой правкой НЕ удаляется:
|
||||||
|
# Job умеет только mc mb --ignore-existing. Убирать его — отдельное
|
||||||
|
# осознанное решение, здесь мы лишь перестаём его заводить.
|
||||||
|
for b in sarex-media-storage; do
|
||||||
|
mc mb --ignore-existing "contour/$b"
|
||||||
|
echo "бакет $b готов"
|
||||||
|
done
|
||||||
12
infrastructure/minio/aero/kustomization.yaml
Normal file
12
infrastructure/minio/aero/kustomization.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
# Заведение бакетов: чарт этого не умеет, см. шапку файла.
|
||||||
|
- buckets-job.yaml
|
||||||
|
patches:
|
||||||
|
- path: minio.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: minio
|
||||||
40
infrastructure/minio/aero/minio.yaml
Normal file
40
infrastructure/minio/aero/minio.yaml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: minio
|
||||||
|
namespace: minio
|
||||||
|
spec:
|
||||||
|
interval: 2m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
nameOverride: "minio"
|
||||||
|
mode: standalone
|
||||||
|
replicas: 1
|
||||||
|
drivesPerNode: 1
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
environment:
|
||||||
|
# Внешние адреса: консоль отдаётся с /console/ того же домена,
|
||||||
|
# маршрутизацию задаёт infrastructure/istio-config/aero.
|
||||||
|
MINIO_SERVER_URL: "https://minio.sarex.local.lonsdaleites.ru"
|
||||||
|
MINIO_BROWSER_REDIRECT_URL: "https://minio.sarex.local.lonsdaleites.ru/console/"
|
||||||
|
MINIO_API_CORS_ALLOW_ORIGIN: "https://minio.sarex.local.lonsdaleites.ru"
|
||||||
|
# Root-креды приезжают из Vault через vault-agent-injector: секрет кладёт
|
||||||
|
# ansible (роль sarex_stack, vault_app_secrets), роль minio в auth/kubernetes
|
||||||
|
# создаётся там же. В манифестах и в git паролей нет.
|
||||||
|
vaultRoot:
|
||||||
|
enabled: true
|
||||||
|
role: minio
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secretPath: secrets/data/minio/admin
|
||||||
|
rootUserKey: rootUser
|
||||||
|
rootPasswordKey: rootPassword
|
||||||
|
# В yc-k8s-test minio прибит к выделенным нодам (nodeSelector/tolerations
|
||||||
|
# dedicated=s3). В контуре aero таких нод нет — всего три, и планировщику
|
||||||
|
# ничего ограничивать не нужно.
|
||||||
|
persistence:
|
||||||
|
storageClass: local-path
|
||||||
|
size: 20Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
10
infrastructure/postgresql/aero/kustomization.yaml
Normal file
10
infrastructure/postgresql/aero/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: postgresql.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: postgresql
|
||||||
187
infrastructure/postgresql/aero/postgresql.yaml
Normal file
187
infrastructure/postgresql/aero/postgresql.yaml
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: postgresql
|
||||||
|
namespace: postgresql
|
||||||
|
spec:
|
||||||
|
interval: 2m
|
||||||
|
# Разворачиваем пустой инстанс: базы создаются с нуля, дампы не
|
||||||
|
# восстанавливаются. Поэтому таймауты обычные, а не двухчасовые как в
|
||||||
|
# yc-k8s-test — там они заданы под restoreFromDump.
|
||||||
|
#
|
||||||
|
# 10 минут, а не 20: таймаут — это ещё и цена одной неудачной итерации.
|
||||||
|
# Пока стояло 20, цикл «install → таймаут → откат → install» занимал по
|
||||||
|
# 20 минут на круг, и правка, приехавшая в середине круга, подхватывалась
|
||||||
|
# только на следующем. Здоровый инстанс поднимается примерно за две минуты,
|
||||||
|
# так что 10 — с большим запасом.
|
||||||
|
timeout: 10m
|
||||||
|
install:
|
||||||
|
timeout: 10m
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
timeout: 10m
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
# Снимаем nodeSelector, который чарт ставит по умолчанию.
|
||||||
|
#
|
||||||
|
# В дефолтах чарта (primary.nodeSelector) прописано dedicated: sts — расчёт на
|
||||||
|
# кластер с выделенным пулом нод под StatefulSet'ы. В контуре aero таких нод
|
||||||
|
# нет, все три равнозначны, и под навсегда повисал в Pending:
|
||||||
|
# 0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector
|
||||||
|
# а следом застревал PVC, потому что у local-path режим WaitForFirstConsumer
|
||||||
|
# и том создаётся только после планирования пода.
|
||||||
|
#
|
||||||
|
# Через values это не снять: Helm сливает карты по ключам, поэтому и {}, и
|
||||||
|
# любая другая метка оставят dedicated: sts на месте. В yc-k8s-test дефолт не
|
||||||
|
# убирают, а перекрывают на dedicated: db — там выделенные ноды есть.
|
||||||
|
# Тут остаётся убрать ключ из уже отрендеренного манифеста.
|
||||||
|
#
|
||||||
|
# Tolerations чарта не трогаем: они разрешают taint dedicated=sts, которого на
|
||||||
|
# наших нодах нет, и ни на что не влияют.
|
||||||
|
postRenderers:
|
||||||
|
- kustomize:
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: StatefulSet
|
||||||
|
name: postgresql
|
||||||
|
patch: |
|
||||||
|
- op: remove
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
security:
|
||||||
|
allowInsecureImages: true
|
||||||
|
defaultStorageClass: local-path
|
||||||
|
imagePullSecrets:
|
||||||
|
- regcred
|
||||||
|
postgresql:
|
||||||
|
auth:
|
||||||
|
# Пусто осознанно: пользователя и базу заводит не чарт-бутстрап
|
||||||
|
# bitnami, а contour-логика ниже (contour.databases). Оставь здесь
|
||||||
|
# значения — получишь лишнюю базу-пустышку.
|
||||||
|
username: ""
|
||||||
|
database: ""
|
||||||
|
secretKeys:
|
||||||
|
userPasswordKey: "postgres-password"
|
||||||
|
auth:
|
||||||
|
username: ""
|
||||||
|
database: ""
|
||||||
|
secretKeys:
|
||||||
|
userPasswordKey: "postgres-password"
|
||||||
|
image:
|
||||||
|
registry: cr.yandex/crp3ccidau046kdj8g9q
|
||||||
|
repository: contour/postgresql
|
||||||
|
tag: 17.0.7
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
metrics:
|
||||||
|
enabled: false
|
||||||
|
prometheusRule:
|
||||||
|
enabled: false
|
||||||
|
primary:
|
||||||
|
# Токен ServiceAccount нужен vault-agent'у для логина в Vault.
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
containerSecurityContext:
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
persistence:
|
||||||
|
storageClass: local-path
|
||||||
|
size: 20Gi
|
||||||
|
# В yc-k8s-test здесь nodeSelector/tolerations dedicated=db. В контуре
|
||||||
|
# aero выделенных под БД нод нет — все три равнозначны, ограничивать
|
||||||
|
# планировщик нечем и незачем.
|
||||||
|
customLivenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 6
|
||||||
|
customReadinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 6
|
||||||
|
customStartupProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 6
|
||||||
|
# contour-логика: создаёт роли, базы и расширения по списку ниже, пароли
|
||||||
|
# берёт из Vault через vault-agent-injector.
|
||||||
|
contour:
|
||||||
|
enabled: true
|
||||||
|
adminUser: "postgres"
|
||||||
|
vault:
|
||||||
|
enabled: true
|
||||||
|
role: postgresql
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
# Пароль администратора.
|
||||||
|
secretPath: secrets/data/postgresql/admin
|
||||||
|
secretKey: postgres-password
|
||||||
|
# Пароли пользователей баз: ключи внутри секрета сопоставляются с
|
||||||
|
# passwordKey каждой базы. Чарт требует этот путь при vault.enabled.
|
||||||
|
# Секрет кладёт ansible (роль sarex_stack, vault_app_secrets).
|
||||||
|
usersSecretPath: secrets/data/postgresql/users
|
||||||
|
# timescaledb включён заранее, хотя нашим четырём базам он не нужен:
|
||||||
|
# shared_preload_libraries читается только при старте, и добавить его
|
||||||
|
# позже — значит перезапустить СУБД. Сервисы, которым он понадобится
|
||||||
|
# (issues, subscriptions, system-log), уже есть в списке на перенос.
|
||||||
|
sharedPreloadLibraries: "timescaledb,pg_stat_statements"
|
||||||
|
# Набор баз повторяет то, что сейчас провижинит postgres-init в
|
||||||
|
# docker-compose.yaml, — контур переезжает в k3s тем же составом.
|
||||||
|
# passwordKey должен совпадать с ключом в секрете Vault
|
||||||
|
# secrets/postgresql/users.
|
||||||
|
databases:
|
||||||
|
- name: sarex_db
|
||||||
|
user: django
|
||||||
|
passwordKey: django
|
||||||
|
extensions:
|
||||||
|
- ltree
|
||||||
|
restoreFromDump: false
|
||||||
|
- name: workflow_db
|
||||||
|
user: processing
|
||||||
|
passwordKey: processing
|
||||||
|
# Роль processing не суперпользователь, а её миграции делают
|
||||||
|
# CREATE EXTENSION — поэтому расширения создаёт бутстрап заранее.
|
||||||
|
extensions:
|
||||||
|
- uuid-ossp
|
||||||
|
- ltree
|
||||||
|
- hstore
|
||||||
|
restoreFromDump: false
|
||||||
|
# Имя ОБЯЗАНО быть bimapidb: в aero/bimbackend оно захардкожено и
|
||||||
|
# переменной не задаётся — и в entrypoint_api.sh (yoyo apply
|
||||||
|
# ... /bimapidb), и в models/database.py (PooledPostgresqlExtDatabase
|
||||||
|
# ('bimapidb', ...)). С любым другим именем приложение падает на
|
||||||
|
# старте: FATAL: database "bimapidb" does not exist.
|
||||||
|
- name: bimapidb
|
||||||
|
user: bim
|
||||||
|
passwordKey: bim
|
||||||
|
# ltree обязателен: models/database.py при инициализации вызывает
|
||||||
|
# register_ltree(conn) и падает с
|
||||||
|
# psycopg2.ProgrammingError: ltree type not found in the database
|
||||||
|
# Роль bim не суперпользователь и CREATE EXTENSION сама не сделает,
|
||||||
|
# поэтому расширение заводит бутстрап — как у sarex_db и workflow_db.
|
||||||
|
extensions:
|
||||||
|
- ltree
|
||||||
|
restoreFromDump: false
|
||||||
|
- name: workspace_db
|
||||||
|
user: workspace
|
||||||
|
passwordKey: workspace
|
||||||
|
extensions: []
|
||||||
|
restoreFromDump: false
|
||||||
14
infrastructure/rabbitmq/aero/kustomization.yaml
Normal file
14
infrastructure/rabbitmq/aero/kustomization.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: rabbitmq.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: rabbitmq
|
||||||
|
# JSON6902-патча с null здесь БОЛЬШЕ НЕТ — он не работал.
|
||||||
|
# kustomize нормализовал null обратно в {}, а пустую карту Helm сливает с
|
||||||
|
# дефолтами чарта, возвращая их целиком. Отключение перенесено в postRenderers
|
||||||
|
# внутри rabbitmq.yaml, где ресурсы вырезаются уже из готового манифеста.
|
||||||
117
infrastructure/rabbitmq/aero/rabbitmq.yaml
Normal file
117
infrastructure/rabbitmq/aero/rabbitmq.yaml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: rabbitmq
|
||||||
|
namespace: rabbitmq
|
||||||
|
spec:
|
||||||
|
interval: 2m
|
||||||
|
timeout: 10m
|
||||||
|
# Вырезаем ресурсы ПОСЛЕ рендера чарта. Через values это недостижимо:
|
||||||
|
# * `null` до Helm не доезжает — kustomize нормализует его в {};
|
||||||
|
# * `{}` Helm сливает с дефолтом (coalesceMaps), и дефолт возвращается.
|
||||||
|
# postRenderers работает уже с готовым манифестом, где коалесинг позади.
|
||||||
|
#
|
||||||
|
# Что вырезаем: собственные Gateway/VirtualService/Certificate чарта на
|
||||||
|
# посторонний домен rabbitmq.infra.sarex.io. Маршрутизация контура описана
|
||||||
|
# централизованно в infrastructure/istio-config/aero.
|
||||||
|
#
|
||||||
|
# Особенно важен rmq-tls: домен не наш, ACME отдаёт 503 и проверка не пройдёт
|
||||||
|
# никогда. Пока он был в релизе, Helm ждал его выпуска, install падал по
|
||||||
|
# таймауту, срабатывал откат — и весь слой infra-controllers стоял.
|
||||||
|
postRenderers:
|
||||||
|
- kustomize:
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: Gateway
|
||||||
|
name: rmq-gateway
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: networking.istio.io/v1beta1
|
||||||
|
kind: Gateway
|
||||||
|
metadata:
|
||||||
|
name: rmq-gateway
|
||||||
|
namespace: gateway
|
||||||
|
- target:
|
||||||
|
kind: VirtualService
|
||||||
|
name: rmq-virt-service
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: networking.istio.io/v1
|
||||||
|
kind: VirtualService
|
||||||
|
metadata:
|
||||||
|
name: rmq-virt-service
|
||||||
|
namespace: rabbitmq
|
||||||
|
- target:
|
||||||
|
kind: Certificate
|
||||||
|
name: rmq-tls
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: rmq-tls
|
||||||
|
namespace: gateway
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
security:
|
||||||
|
allowInsecureImages: true
|
||||||
|
# Здесь НЕТ ключей virtualService/gateway/certificate, и это намеренно:
|
||||||
|
# через values дефолты чарта не убираются вообще никак.
|
||||||
|
#
|
||||||
|
# Helm : удалить дефолт можно ТОЛЬКО значением null;
|
||||||
|
# {} он глубоко сливает с дефолтом, и дефолт остаётся;
|
||||||
|
# kustomize : в strategic-merge-патче null означает УДАЛИТЬ КЛЮЧ,
|
||||||
|
# в JSON6902 — нормализуется обратно в {}.
|
||||||
|
#
|
||||||
|
# То есть до Helm настоящий null не доходит ни одним путём. Поэтому ресурсы
|
||||||
|
# вырезаются postRenderers'ом выше, уже из отрендеренного манифеста.
|
||||||
|
metrics:
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
default:
|
||||||
|
enabled: false
|
||||||
|
perObject:
|
||||||
|
enabled: false
|
||||||
|
detailed:
|
||||||
|
enabled: false
|
||||||
|
extraServiceMonitors: []
|
||||||
|
replicaCount: 1
|
||||||
|
persistence:
|
||||||
|
storageClass: local-path
|
||||||
|
size: 8Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
|
# Логин/пароль приезжают из Vault через vault-agent-injector: секрет кладёт
|
||||||
|
# ansible (роль sarex_stack, vault_app_secrets), роль rabbitmq в
|
||||||
|
# auth/kubernetes создаётся там же.
|
||||||
|
auth:
|
||||||
|
securePassword: true
|
||||||
|
# Без этого администратор пускается только с localhost, и любое
|
||||||
|
# подключение из кластера отбивается на аутентификации:
|
||||||
|
# PLAIN login refused: user 'rabbit' can only connect via localhost
|
||||||
|
# Диагностика сбивает с толку: клиент видит 403 ACCESS_REFUSED, будто
|
||||||
|
# не сошёлся пароль, хотя пароль верный — rabbitmqctl authenticate_user
|
||||||
|
# с тем же значением проходит. Так у celery не поднимался worker, и по
|
||||||
|
# той же причине в management UI нельзя было войти снаружи.
|
||||||
|
enableLoopbackUser: false
|
||||||
|
existingPasswordSecret: ""
|
||||||
|
vault:
|
||||||
|
enabled: true
|
||||||
|
role: rabbitmq
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secretPath: secrets/data/rabbitmq/auth
|
||||||
|
usernameKey: username
|
||||||
|
passwordKey: password
|
||||||
|
# Дублирует enableLoopbackUser: false намеренно, тем же способом, что и
|
||||||
|
# d8-ugmk-prod. Одного флага мало: он лишь заменяет в rabbitmq.conf строку
|
||||||
|
# loopback_users.<user> на "= false", а брокер её не применяет — в рантайме
|
||||||
|
# rabbitmqctl eval "application:get_env(rabbit, loopback_users)." всё равно
|
||||||
|
# отдавал {ok,[<<"rabbit">>]}. advanced.config разбирается позже и задаёт
|
||||||
|
# значение явным списком, поэтому именно он и решает.
|
||||||
|
advancedConfiguration: |-
|
||||||
|
[
|
||||||
|
{rabbit, [
|
||||||
|
{loopback_users, []}
|
||||||
|
]}
|
||||||
|
].
|
||||||
5
infrastructure/vault-injector/aero/kustomization.yaml
Normal file
5
infrastructure/vault-injector/aero/kustomization.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
49
infrastructure/vault-injector/base/helmrelease.yaml
Normal file
49
infrastructure/vault-injector/base/helmrelease.yaml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Vault Agent Injector контура aero.
|
||||||
|
#
|
||||||
|
# Тот же чарт vault-contour, что и в остальных кластерах (это официальный
|
||||||
|
# hashicorp/vault-helm, перепубликованный в cr.yandex), но в режиме «внешний
|
||||||
|
# Vault»: сервер Vault здесь живёт в docker-compose, а в кластер ставится
|
||||||
|
# ТОЛЬКО injector.
|
||||||
|
#
|
||||||
|
# global.externalVaultAddr сам по себе отключает деплой vault-сервера — это
|
||||||
|
# документированное поведение чарта, отдельный server.enabled=false не нужен.
|
||||||
|
# Адрес указывает на DNS-мост (Service vault в namespace vault), который
|
||||||
|
# заводит ansible: k3s/manifests/vault/bridge-vault.yaml.
|
||||||
|
#
|
||||||
|
# Имя релиза — vault-injector, а не vault (как в прод-кластерах): чарт
|
||||||
|
# составляет имена ресурсов из имени релиза, и релиз `vault` рисковал бы
|
||||||
|
# перекрыть Service `vault` моста, оставив Vault недоступным.
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: vault-injector
|
||||||
|
namespace: vault
|
||||||
|
spec:
|
||||||
|
interval: 2m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: vault-contour
|
||||||
|
version: "0.2.3"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
createNamespace: true
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
# Отключает vault-сервер и задаёт адрес внешнего Vault для агентов.
|
||||||
|
externalVaultAddr: http://vault.vault.svc.cluster.local:8200
|
||||||
|
# Образы чарта лежат в приватном cr.yandex; секрет создаёт роль
|
||||||
|
# sarex_stack (registry_secrets), см. registry-secret.yaml.j2.
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
tlsDisable: true
|
||||||
|
injector:
|
||||||
|
enabled: true
|
||||||
11
infrastructure/vault-injector/base/kustomization.yaml
Normal file
11
infrastructure/vault-injector/base/kustomization.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- helmrelease.yaml
|
||||||
|
|
||||||
|
# namespace vault здесь намеренно НЕ объявлен: его создаёт ansible вместе с
|
||||||
|
# DNS-мостом и token reviewer'ом (k3s/manifests/vault/), потому что Service
|
||||||
|
# моста нужно куда-то положить ещё до того, как Flux дойдёт до этого слоя.
|
||||||
|
# Дублировать объект в двух источниках правды — значит спорить за владение,
|
||||||
|
# поэтому чарту достаточно install.createNamespace на случай чистого кластера.
|
||||||
Loading…
Reference in New Issue
Block a user