server { listen 80; server_name _; # Resolver — coredns (10.233.0.3 на kubespray-кластерах). nodelocal-dns # (169.254.25.10) при первом cold lookup отвечает SERVFAIL, поэтому # обращаемся напрямую к ClusterDNS service. resolver ${RESOLVER_IP} valid=30s ipv6=off; # Real client IP behind ingress-nginx. set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; real_ip_recursive on; root /usr/share/nginx/html; index index.html; # /api/v1/dictionaries/* — все admin endpoints (schema, raw record, mutations) идут # на writer. Read-api держит /api/v1/{name}/records/* (другой URL pattern, flatten i18n). # Regex-location имеет приоритет над префиксом ниже. location ~ ^/api/v1/dictionaries/ { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # /api/v1/version — VersionController в ordinis-rest-api → подключён ТОЛЬКО в # writer (см. ordinis-app/pom.xml). Без явного правила generic /api/v1/ ниже # роутит GET → read-api → 500 NoResourceFoundException → пустой traceId. # Тот же класс баги что для /drafts/me ниже. location = /api/v1/version { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # /api/v1/search — SearchController (global trigram-search across all dicts) # тоже в ordinis-rest-api → только writer. Без route'а — 500 в Smart Search. location ~ ^/api/v1/search(/|$|\?) { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # /api/v1/records/{dict}/{businessKey}/dependents — record-level lineage # endpoint в LineageController (ordinis-rest-api → writer). Префикс /records/ # на root уровне отличается от /api/v1/{dict}/records/* read-api endpoint'а # — нужно явное правило, иначе fallback на read-api → 500. location ~ ^/api/v1/records/ { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # /api/v1/admin/* — audit + outbox admin endpoints на writer. location ~ ^/api/v1/admin/ { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # /api/v1/drafts/* — Approval Workflow v2 endpoints (submit/approve/reject/withdraw) # + GET /drafts/me (maker self-tracking) + GET /drafts/{id}. Все живут на writer — # read-api не имеет DraftController. Без явного правила GET /drafts/me ловится # generic /api/v1/ ниже → read-api → 500 NoResourceFoundException. location ~ ^/api/v1/drafts(/|$) { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # /api/v1/me/* — current-user self-scope endpoints (notifications + future # preferences). Все на writer (MeNotificationsController в ordinis-rest-api). # Тот же шаблон что для /drafts: без явного правила GET → read-api → 500 # NoResourceFoundException → bell badge на каждой странице бомбит "Внутренняя # ошибка сервера". Закрывает observed bug в v2.27.2 (TODO 7). location ~ ^/api/v1/me(/|$) { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # Swagger UI + OpenAPI spec на writer (там обвязаны контроллеры). location ~ ^/(swagger-ui|v3/api-docs|swagger-resources|webjars/swagger-ui) { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # GET → read-api, мутирующие → writer. # FQDN в proxy_pass нужен потому что nginx resolver не использует search list. # Namespace и cluster-domain инжектятся через ENV (см. admin-ui Deployment). location /api/v1/ { set $backend "ordinis-read-api.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; if ($request_method ~ ^(POST|PUT|PATCH|DELETE)$) { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; } proxy_pass http://$backend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass_request_headers on; } # SPA history fallback. location / { try_files $uri $uri/ /index.html; } }