feat(gateway): сервис pcp-gateway-service + сворачивание маршрутизации фронта на него

- новый Spring Cloud Gateway (WebFlux): доменные маршруты /api/pcp-<x>/**
  со StripPrefix=2, /api/stations без среза пути, catch-all /api/** → ui-service;
  опциональная JWT-валидация на edge (gateway.security.jwt-enabled, по умолчанию off)
- config-repo: pcp-gateway-service.yaml (маршруты) + порт/адрес gateway в реестрах
  application-{local,docker-local,dev}
- build: репозитории переведены на mavenCentral (nstart-proxy закомментирован),
  включены mavenLocal/gradlePluginPortal; settings: include модуля
- pcp-tgu-ui-service: vite-proxy и nginx.conf свёрнуты на единый /api → gateway
  (вместо набора per-service правил)
- deploy: сервис gateway в docker-compose; helm-чарт pcp-gateway-service
- CI: .gitlab/ci/pcp-gateway-service.yml + проводка в .gitlab-ci.yml
This commit is contained in:
Дмитрий Соловьев
2026-06-10 13:34:16 +03:00
parent 6c2491cbb9
commit ab3a787da5
24 changed files with 652 additions and 95 deletions
+7
View File
@@ -20,6 +20,7 @@ workflow:
- services/pcp-tgu-service/**/* - services/pcp-tgu-service/**/*
- services/pcp-tgu-ui-service/**/* - services/pcp-tgu-ui-service/**/*
- services/pcp-dynamic-plan-service/**/* - services/pcp-dynamic-plan-service/**/*
- services/pcp-gateway-service/**/*
- libs/pcp-types-lib/**/* - libs/pcp-types-lib/**/*
- libs/ballistics-lib/**/* - libs/ballistics-lib/**/*
- build.gradle.kts - build.gradle.kts
@@ -43,6 +44,7 @@ workflow:
- helm/pcp-tgu-service/**/* - helm/pcp-tgu-service/**/*
- helm/pcp-tgu-ui-service/**/* - helm/pcp-tgu-ui-service/**/*
- helm/pcp-dynamic-plan-service/**/* - helm/pcp-dynamic-plan-service/**/*
- helm/pcp-gateway-service/**/*
- if: '$CI_PIPELINE_SOURCE == "push"' - if: '$CI_PIPELINE_SOURCE == "push"'
changes: changes:
- .gitlab-ci.yml - .gitlab-ci.yml
@@ -64,6 +66,7 @@ workflow:
- services/pcp-tgu-service/**/* - services/pcp-tgu-service/**/*
- services/pcp-tgu-ui-service/**/* - services/pcp-tgu-ui-service/**/*
- services/pcp-dynamic-plan-service/**/* - services/pcp-dynamic-plan-service/**/*
- services/pcp-gateway-service/**/*
- libs/pcp-types-lib/**/* - libs/pcp-types-lib/**/*
- libs/ballistics-lib/**/* - libs/ballistics-lib/**/*
- build.gradle.kts - build.gradle.kts
@@ -87,6 +90,7 @@ workflow:
- helm/pcp-tgu-service/**/* - helm/pcp-tgu-service/**/*
- helm/pcp-tgu-ui-service/**/* - helm/pcp-tgu-ui-service/**/*
- helm/pcp-dynamic-plan-service/**/* - helm/pcp-dynamic-plan-service/**/*
- helm/pcp-gateway-service/**/*
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "master"' - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "master"'
changes: changes:
- services/pcp-ballistics-service/**/* - services/pcp-ballistics-service/**/*
@@ -104,6 +108,7 @@ workflow:
- services/pcp-tgu-service/**/* - services/pcp-tgu-service/**/*
- services/pcp-tgu-ui-service/**/* - services/pcp-tgu-ui-service/**/*
- services/pcp-dynamic-plan-service/**/* - services/pcp-dynamic-plan-service/**/*
- services/pcp-gateway-service/**/*
- libs/pcp-types-lib/**/* - libs/pcp-types-lib/**/*
- libs/ballistics-lib/**/* - libs/ballistics-lib/**/*
- build.gradle.kts - build.gradle.kts
@@ -127,6 +132,7 @@ workflow:
- helm/pcp-tgu-service/**/* - helm/pcp-tgu-service/**/*
- helm/pcp-tgu-ui-service/**/* - helm/pcp-tgu-ui-service/**/*
- helm/pcp-dynamic-plan-service/**/* - helm/pcp-dynamic-plan-service/**/*
- helm/pcp-gateway-service/**/*
- when: never - when: never
default: default:
@@ -168,3 +174,4 @@ include:
- local: .gitlab/ci/pcp-tgu-service.yml - local: .gitlab/ci/pcp-tgu-service.yml
- local: .gitlab/ci/pcp-tgu-ui-service.yml - local: .gitlab/ci/pcp-tgu-ui-service.yml
- local: .gitlab/ci/pcp-dynamic-plan-service.yml - local: .gitlab/ci/pcp-dynamic-plan-service.yml
- local: .gitlab/ci/pcp-gateway-service.yml
+191
View File
@@ -0,0 +1,191 @@
variables:
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
GATEWAY_SERVICE_DIR: "services/pcp-gateway-service"
GATEWAY_HELM_CHART_DIR: "helm/pcp-gateway-service"
GATEWAY_HELM_RELEASE: "pcp-gateway-service"
GATEWAY_IMAGE_REPOSITORY: "$INTERNAL_REGISTRY/pcp-gateway-service"
GATEWAY_IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"
GATEWAY_K8S_NAMESPACE_DEV: "pcp-dev"
.gateway_changes: &gateway_changes
- services/pcp-gateway-service/**/*
- build.gradle.kts
- settings.gradle.kts
- .gitlab/ci/pcp-gateway-service.yml
- helm/pcp-gateway-service/**/*
- deploy/**/*
.gateway:rules:build-and-image:
rules:
- if: '$RUN_ALL == "true"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes: *gateway_changes
- if: '$CI_COMMIT_BRANCH == "dev"'
changes: *gateway_changes
- if: '$CI_COMMIT_BRANCH == "master"'
changes: *gateway_changes
- when: never
.gateway:rules:test:
rules:
- if: '$RUN_ALL == "true"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes: *gateway_changes
- if: '$CI_COMMIT_BRANCH == "dev"'
changes: *gateway_changes
when: manual
- if: '$CI_COMMIT_BRANCH == "master"'
changes: *gateway_changes
- when: never
.gateway:rules:deploy-dev:
rules:
- if: '$RUN_ALL == "true"'
when: manual
- if: '$CI_COMMIT_BRANCH == "dev"'
changes: *gateway_changes
when: manual
- when: never
.gateway:gradle-cache:
cache:
key: "gradle-gateway-$CI_DEFAULT_BRANCH"
paths:
- .gradle/wrapper
- .gradle/caches
policy: pull-push
.gateway:docker-build:
image: repo.nstart.cloud/library/docker:26
services:
- name: repo.nstart.cloud/library/docker:26-dind
alias: docker
command: ["--tls=false", "--insecure-registry=registry.k8s.265.nstart.local"]
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
DOCKER_BUILDKIT: "1"
before_script:
- docker version
- docker info
.gateway:helm-deploy:
image: repo.nstart.cloud/dtzar/helm-kubectl:3.14.0
before_script:
- mkdir -p /root/.kube
- helm version
after_script:
- rm -f /root/.kube/config
gateway:test:
stage: test
image: repo.nstart.cloud/library/gradle:jdk21-ubi10
extends:
- .gateway:rules:test
- .gateway:gradle-cache
script:
- gradle --no-daemon :services:pcp-gateway-service:test
artifacts:
when: always
reports:
junit:
- services/pcp-gateway-service/build/test-results/test/*.xml
paths:
- services/pcp-gateway-service/build/reports/tests/
expire_in: 7 days
gateway:build:
stage: build
image: repo.nstart.cloud/library/gradle:jdk21-ubi10
extends:
- .gateway:rules:build-and-image
- .gateway:gradle-cache
needs:
- job: gateway:test
artifacts: true
script:
- gradle --no-daemon :services:pcp-gateway-service:bootJar
artifacts:
when: always
paths:
- services/pcp-gateway-service/build/libs/*.jar
expire_in: 7 days
gateway:package:
stage: package
extends:
- .gateway:rules:build-and-image
- .gateway:docker-build
needs:
- job: gateway:build
artifacts: true
script:
- 'echo "GATEWAY_IMAGE_REPOSITORY=$GATEWAY_IMAGE_REPOSITORY"'
- |
JAR_COUNT="$(find "$GATEWAY_SERVICE_DIR/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | wc -l)"
if [ "$JAR_COUNT" -ne 1 ]; then
echo "Expected exactly one executable jar, found: $JAR_COUNT"
find "$GATEWAY_SERVICE_DIR/build/libs" -maxdepth 1 -type f -name '*.jar' -print || true
exit 1
fi
JAR_FILE="$(find "$GATEWAY_SERVICE_DIR/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | head -n 1)"
- rm -rf docker-context
- mkdir -p docker-context/build/libs
- cp "$GATEWAY_SERVICE_DIR/Dockerfile" docker-context/Dockerfile
- cp "$JAR_FILE" docker-context/build/libs/app.jar
- find docker-context -maxdepth 4 -type f -print -exec ls -la {} \;
- docker build --file docker-context/Dockerfile --tag "$GATEWAY_IMAGE_REPOSITORY:$GATEWAY_IMAGE_TAG" docker-context
- docker push "$GATEWAY_IMAGE_REPOSITORY:$GATEWAY_IMAGE_TAG"
- printf "GATEWAY_IMAGE=%s\n" "$GATEWAY_IMAGE_REPOSITORY:$GATEWAY_IMAGE_TAG" > gateway-image.env
artifacts:
reports:
dotenv: gateway-image.env
expire_in: 7 days
gateway:deploy:dev:
stage: deploy
extends:
- .gateway:rules:deploy-dev
- .gateway:helm-deploy
needs:
- job: gateway:package
artifacts: true
environment:
name: dev/pcp-gateway-service
script:
- |
if [ -n "${KUBE_CONFIG_DEV:-}" ]; then
printf '%s' "$KUBE_CONFIG_DEV" > /root/.kube/config
elif [ -n "${KUBE_CONFIG_DEV_B64:-}" ]; then
if printf '%s' "$KUBE_CONFIG_DEV_B64" | grep -qE '(^apiVersion:|^kind: Config|^clusters:)'; then
printf '%s' "$KUBE_CONFIG_DEV_B64" > /root/.kube/config
elif ! printf '%s' "$KUBE_CONFIG_DEV_B64" | base64 -d > /root/.kube/config; then
echo "KUBE_CONFIG_DEV_B64 is not valid base64. Set KUBE_CONFIG_DEV to raw kubeconfig or KUBE_CONFIG_DEV_B64 to base64 kubeconfig."
exit 1
fi
else
echo "KUBE_CONFIG_DEV or KUBE_CONFIG_DEV_B64 must be set."
exit 1
fi
- chmod 600 /root/.kube/config
- export KUBECONFIG=/root/.kube/config
- 'export IMAGE_REPOSITORY="${GATEWAY_IMAGE%:*}"'
- 'export IMAGE_TAG="${GATEWAY_IMAGE##*:}"'
- 'echo "Deploy image: $IMAGE_REPOSITORY:$IMAGE_TAG"'
- >
helm upgrade --install "$GATEWAY_HELM_RELEASE"
"$GATEWAY_HELM_CHART_DIR"
--namespace "$GATEWAY_K8S_NAMESPACE_DEV"
--create-namespace
--values "$GATEWAY_HELM_CHART_DIR/values.yaml"
--values "$GATEWAY_HELM_CHART_DIR/values-dev.yaml"
--set-string image.repository="$IMAGE_REPOSITORY"
--set-string image.tag="$IMAGE_TAG"
--set-string app.profile=dev
--set-string app.configLabel=dev
--atomic
--wait
--timeout 15m
+6 -6
View File
@@ -16,18 +16,18 @@ allprojects {
group = "space.nstart.pcp" group = "space.nstart.pcp"
ext.set("kotlinVersion", "2.2.20") ext.set("kotlinVersion", "2.2.20")
repositories { repositories {
maven { // maven {
url = uri("https://repo.nstart.cloud/repository/maven-proxy/") // url = uri("https://repo.nstart.cloud/repository/maven-proxy/")
} // }
mavenCentral { mavenCentral {
content { content {
includeGroup("org.jacoco") includeGroup("org.jacoco")
includeGroup("org.ow2.asm") includeGroup("org.ow2.asm")
} }
} }
// mavenCentral() mavenCentral()
// mavenLocal() mavenLocal()
// gradlePluginPortal() gradlePluginPortal()
maven { url = uri("https://jitpack.io") } maven { url = uri("https://jitpack.io") }
exclusiveContent { exclusiveContent {
+2
View File
@@ -29,6 +29,7 @@ pcp:
coverage-scheme: 8080 coverage-scheme: 8080
satellite-catalog: 8080 satellite-catalog: 8080
dynamic-plan: 8080 dynamic-plan: 8080
gateway: 8080
services: services:
config: http://${pcp.network.host}:${pcp.ports.config} config: http://${pcp.network.host}:${pcp.ports.config}
@@ -47,6 +48,7 @@ pcp:
coverage-scheme: http://pcp-coverage-scheme-service:${pcp.ports.coverage-scheme} coverage-scheme: http://pcp-coverage-scheme-service:${pcp.ports.coverage-scheme}
satellite-catalog: http://pcp-satellite-catalog-service:${pcp.ports.satellite-catalog} satellite-catalog: http://pcp-satellite-catalog-service:${pcp.ports.satellite-catalog}
dynamic-plan: http://pcp-dynamic-plan-service:${pcp.ports.dynamic-plan} dynamic-plan: http://pcp-dynamic-plan-service:${pcp.ports.dynamic-plan}
gateway: http://pcp-gateway-service:${pcp.ports.gateway}
spring: spring:
boot: boot:
@@ -27,6 +27,7 @@ pcp:
coverage-scheme: 7012 coverage-scheme: 7012
satellite-catalog: 7013 satellite-catalog: 7013
dynamic-plan: 7014 dynamic-plan: 7014
gateway: 7080
services: services:
config: http://spring-cloud-config-server:${pcp.ports.config} config: http://spring-cloud-config-server:${pcp.ports.config}
@@ -45,6 +46,7 @@ pcp:
coverage-scheme: http://pcp-coverage-scheme-service:${pcp.ports.coverage-scheme} coverage-scheme: http://pcp-coverage-scheme-service:${pcp.ports.coverage-scheme}
satellite-catalog: http://pcp-satellite-catalog-service:${pcp.ports.satellite-catalog} satellite-catalog: http://pcp-satellite-catalog-service:${pcp.ports.satellite-catalog}
dynamic-plan: http://pcp-dynamic-plan-service:${pcp.ports.dynamic-plan} dynamic-plan: http://pcp-dynamic-plan-service:${pcp.ports.dynamic-plan}
gateway: http://pcp-gateway-service:${pcp.ports.gateway}
spring: spring:
kafka: kafka:
+2
View File
@@ -31,6 +31,7 @@ pcp:
coverage-scheme: 7012 coverage-scheme: 7012
satellite-catalog: 7013 satellite-catalog: 7013
dynamic-plan: 7014 dynamic-plan: 7014
gateway: 7080
services: services:
config: http://${pcp.network.host}:${pcp.ports.config} config: http://${pcp.network.host}:${pcp.ports.config}
@@ -49,6 +50,7 @@ pcp:
coverage-scheme: http://${pcp.network.host}:${pcp.ports.coverage-scheme} coverage-scheme: http://${pcp.network.host}:${pcp.ports.coverage-scheme}
satellite-catalog: http://${pcp.network.host}:${pcp.ports.satellite-catalog} satellite-catalog: http://${pcp.network.host}:${pcp.ports.satellite-catalog}
dynamic-plan: http://${pcp.network.host}:${pcp.ports.dynamic-plan} dynamic-plan: http://${pcp.network.host}:${pcp.ports.dynamic-plan}
gateway: http://${pcp.network.host}:${pcp.ports.gateway}
spring: spring:
boot: boot:
+88
View File
@@ -0,0 +1,88 @@
spring:
boot:
admin:
client:
instance:
service-base-url: ${pcp.services.gateway}
management-base-url: ${pcp.services.gateway}
cloud:
gateway:
server:
# WebFlux-вариант Spring Cloud Gateway (Boot 4 / Spring Cloud 2025.1).
webflux:
routes:
# --- Доменные сервисы. Префикс /api/pcp-<x> СРЕЗАЕТСЯ (StripPrefix=2 убирает
# 2 сегмента: api + pcp-<x>), т.е. /api/pcp-tgu/api/plans -> бэкенд /api/plans.
# Это ровно семантика прежнего vite-rewrite. Цели берём из реестра pcp.services.*.
- id: tgu
uri: ${pcp.services.tgu}
predicates:
- Path=/api/pcp-tgu/**
filters:
- StripPrefix=2
- id: ballistics
uri: ${pcp.services.ballistics}
predicates:
- Path=/api/pcp-ballistics/**
filters:
- StripPrefix=2
- id: mission
uri: ${pcp.services.mission}
predicates:
- Path=/api/pcp-mission/**
filters:
- StripPrefix=2
- id: request
uri: ${pcp.services.request}
predicates:
- Path=/api/pcp-request/**
filters:
- StripPrefix=2
- id: complex
uri: ${pcp.services.complex-mission-service}
predicates:
- Path=/api/pcp-complex/**
filters:
- StripPrefix=2
# --- Станции: путь СОХРАНЯЕМ (в vite было без rewrite) — без StripPrefix.
- id: stations
uri: ${pcp.services.stations}
predicates:
- Path=/api/stations/**
# --- Общий catch-all /api/** -> ui-service. Матчится ПОСЛЕДНИМ (order выше).
- id: ui-catch-all
uri: ${pcp.services.ui}
predicates:
- Path=/api/**
order: 1
# Валидация JWT на edge. По умолчанию ВЫКЛЮЧЕНА (anonymous, как сейчас работает фронт).
# Для закрытого контура: GATEWAY_JWT_ENABLED=true И задать issuer через env
# SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=<realm new-start>.
# Issuer намеренно НЕ объявлен здесь: пустое значение заставило бы автоконфиг поднимать
# JwtDecoder и падать на старте.
gateway:
security:
jwt-enabled: ${GATEWAY_JWT_ENABLED:false}
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
---
spring:
config:
activate:
on-profile: local
server:
port: ${pcp.ports.gateway:7080}
---
spring:
config:
activate:
on-profile: dev
server:
port: ${pcp.ports.gateway:8080}
+8
View File
@@ -182,6 +182,14 @@ services:
ports: ports:
- "${PCP_DYNAMIC_PLAN_PORT:-7014}:7014" - "${PCP_DYNAMIC_PLAN_PORT:-7014}:7014"
pcp-gateway-service:
<<: *pcp-service-common
image: pcp/pcp-gateway-service:${PCP_IMAGE_TAG:-local}
environment:
<<: *pcp-service-env
ports:
- "${PCP_GATEWAY_PORT:-7080}:7080"
volumes: volumes:
postgres-data: postgres-data:
kafka-data: kafka-data:
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v2
name: pcp-gateway-service
description: Helm chart for deploying pcp-gateway-service from GitLab CI/CD.
type: application
version: 0.1.0
appVersion: "1.0.0"
@@ -0,0 +1,18 @@
{{- define "pcp-gateway-service.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "pcp-gateway-service.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- include "pcp-gateway-service.name" . | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "pcp-gateway-service.labels" -}}
app.kubernetes.io/name: {{ include "pcp-gateway-service.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
@@ -0,0 +1,68 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "pcp-gateway-service.fullname" . }}
labels:
{{- include "pcp-gateway-service.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "pcp-gateway-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
{{- include "pcp-gateway-service.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.image.pullSecrets }}
imagePullSecrets:
{{- range . }}
- name: {{ . }}
{{- end }}
{{- end }}
containers:
- name: {{ include "pcp-gateway-service.name" . }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
env:
- name: SPRING_PROFILES_ACTIVE
value: {{ .Values.app.profile | quote }}
- name: CONFIG_SERVER_URI
value: {{ .Values.app.configServerUri | quote }}
- name: SPRING_CLOUD_CONFIG_LABEL
value: {{ .Values.app.configLabel | quote }}
- name: CONFIG_SERVER_FAIL_FAST
value: {{ .Values.app.configServerFailFast | quote }}
- name: SERVER_PORT
value: {{ .Values.service.port | quote }}
{{- with .Values.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
readinessProbe:
httpGet:
path: /actuator/health
port: http
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health
port: http
initialDelaySeconds: 40
periodSeconds: 20
timeoutSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
@@ -0,0 +1,31 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "pcp-gateway-service.fullname" . }}
labels:
{{- include "pcp-gateway-service.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className | quote }}
{{- end }}
{{- with .Values.ingress.tls }}
tls:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: {{ .Values.ingress.path | quote }}
pathType: {{ .Values.ingress.pathType }}
backend:
service:
name: {{ include "pcp-gateway-service.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- end }}
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "pcp-gateway-service.fullname" . }}
labels:
{{- include "pcp-gateway-service.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
app.kubernetes.io/name: {{ include "pcp-gateway-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
+10
View File
@@ -0,0 +1,10 @@
app:
profile: dev
configLabel: dev
service:
port: 8080
ingress:
enabled: true
host: pcp-gateway-service.dev.k8s.264.nstart.cloud
@@ -0,0 +1,10 @@
app:
profile: master
configLabel: master
service:
port: 8080
ingress:
enabled: true
host: pcp-gateway-service.master.k8s.264.nstart.cloud
+36
View File
@@ -0,0 +1,36 @@
replicaCount: 1
image:
repository: registry.example.com/pcp/pcp-gateway-service
tag: latest
pullPolicy: IfNotPresent
pullSecrets: []
service:
type: ClusterIP
port: 8080
app:
profile: master
configServerUri: "http://spring-cloud-config-server:38888"
configServerFailFast: "true"
configLabel: master
ingress:
enabled: false
className: ""
annotations: {}
host: ""
path: /
pathType: Prefix
tls: []
extraEnv: []
resources: {}
podAnnotations: {}
podLabels: {}
nameOverride: ""
fullnameOverride: ""
+9
View File
@@ -0,0 +1,9 @@
ARG RUNTIME_IMAGE=repo.nstart.cloud/bellsoft/liberica-openjre-alpine:21.0.5
FROM ${RUNTIME_IMAGE}
ENV JAVA_OPTS=""
ADD ./build/libs/*.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]
@@ -0,0 +1,50 @@
group = "space.nstart.pcp"
plugins {
kotlin("jvm")
kotlin("plugin.spring")
id("org.springframework.boot")
id("io.spring.dependency-management")
}
version = "0.0.1"
java {
toolchain {
languageVersion = JavaLanguageVersion.of((property("versions.java") as String).toInt())
}
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
}
dependencies {
// Реактивный edge: Spring Cloud Gateway (WebFlux-вариант) + конфиг из config-server.
// Маршруты живут в config-repo/pcp-gateway-service.yaml — единый источник правды.
implementation("org.springframework.cloud:spring-cloud-starter-gateway-server-webflux")
implementation("org.springframework.cloud:spring-cloud-starter-config")
implementation("org.springframework.boot:spring-boot-starter-actuator")
// Валидация JWT на edge (Keycloak/new-start-id) — включается флагом gateway.security.jwt-enabled.
// Тянет reactive Spring Security (ServerHttpSecurity/@EnableWebFluxSecurity).
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.withType<Test> {
useJUnitPlatform()
}
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("versions.spring.cloud")}")
}
}
@@ -0,0 +1,16 @@
package space.nstart.pcp_gateway_service
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
/**
* Единая точка входа в бэкенд для фронта (pcp-tgu-ui-service): reverse-proxy поверх
* микросервисов. Маршрутная таблица в config-repo/pcp-gateway-service.yaml и ссылается
* на реестр `pcp.services.*`, так что топологию знает только gateway, а не браузер.
*/
@SpringBootApplication
class GatewayApplication
fun main(args: Array<String>) {
runApplication<GatewayApplication>(*args)
}
@@ -0,0 +1,51 @@
package space.nstart.pcp_gateway_service.config
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.SecurityWebFilterChain
/**
* Политика edge. Две взаимоисключающие цепочки, переключаемые флагом
* `gateway.security.jwt-enabled` (завязан на режим входа фронта):
* - выключено (anonymous, текущий дефолт) пропускаем всё: поведение как при прямых вызовах;
* - включено (закрытый контур new-start-id) валидируем Bearer JWT один раз здесь, дальше
* gateway по умолчанию пробрасывает заголовок Authorization вниз к сервисам.
*
* Когда JWT выключен, `spring.security.oauth2.resourceserver.jwt.issuer-uri` не задаётся,
* поэтому JwtDecoder не создаётся и старт не падает из-за пустого issuer.
*/
@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
@ConditionalOnProperty(prefix = "gateway.security", name = ["jwt-enabled"], havingValue = "true")
fun securedChain(http: ServerHttpSecurity): SecurityWebFilterChain =
http
.csrf { it.disable() }
.authorizeExchange { exchanges ->
exchanges
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.pathMatchers("/healthz", "/actuator/**").permitAll()
.anyExchange().authenticated()
}
.oauth2ResourceServer { it.jwt {} }
.build()
@Bean
@ConditionalOnProperty(
prefix = "gateway.security",
name = ["jwt-enabled"],
havingValue = "false",
matchIfMissing = true,
)
fun openChain(http: ServerHttpSecurity): SecurityWebFilterChain =
http
.csrf { it.disable() }
.authorizeExchange { it.anyExchange().permitAll() }
.build()
}
@@ -0,0 +1,13 @@
spring:
application:
name: pcp-gateway-service
profiles:
default: local
config:
import: "configserver:"
cloud:
config:
uri: ${CONFIG_SERVER_URI:http://localhost:8888}
fail-fast: ${CONFIG_SERVER_FAIL_FAST:true}
profile: ${SPRING_CLOUD_CONFIG_PROFILE:${SPRING_PROFILES_ACTIVE:${spring.profiles.default}}}
label: ${SPRING_CLOUD_CONFIG_LABEL:master}
+7 -57
View File
@@ -21,64 +21,14 @@ server {
try_files $uri =404; try_files $uri =404;
} }
# --- ТГУ: маршрутизация на бэкенды (зеркало server.proxy из vite.config.ts). # --- ТГУ: вся маршрутизация /api/* уходит на gateway (Spring Cloud Gateway).
# СЕМАНТИКА (важно повторить точно, иначе бэкенды получат неверный путь): # Gateway сам срезает префиксы pcp-* (StripPrefix) и выбирает бэкенд
# - префиксы pcp-* СРЕЗАЮТСЯ: trailing-slash в proxy_pass отбрасывает префикс # см. config-repo/pcp-gateway-service.yaml. Здесь прозрачный reverse-proxy:
# location (как rewrite в vite). Пример: /api/pcp-tgu/api/plans бэкенд /api/plans. # proxy_pass БЕЗ URI-части путь /api/... передаётся gateway без изменений
# - /api/stations путь СОХРАНЯЕТ vite без rewrite): proxy_pass без URI-части. # (он разбирает его сам). Это заменяет прежний набор per-service location'ов.
# - общий /api/ catch-all (tgu-planning, current-plans, auth) на ui-service/gateway. # Адрес gateway в кластере pcp-gateway-service:8080 (dev/master-профиль).
# Порядок важен: специфичные префиксы идут РАНЬШЕ общего /api/.
# Цели (UPSTREAM_*) заглушки: подставьте реальные адреса кластера ЛИБО перенесите
# маршрутизацию на gateway/ingress. Порты dev (vite) даны как ориентир в комментариях.
# Раскомментируйте после подстановки реальных upstream (нерезолвимый хост ломает старт nginx).
# Заголовки proxy_set_header в каждом блоке те же, что в активном /api/ ниже.
#
# location /api/pcp-request/ { # dev :7005
# proxy_pass http://UPSTREAM_REQUEST/;
# 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;
# }
# location /api/pcp-ballistics/ { # dev :7003
# proxy_pass http://UPSTREAM_BALLISTICS/;
# 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;
# }
# location /api/pcp-mission/ { # dev :7010
# proxy_pass http://UPSTREAM_MISSION/;
# 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;
# }
# location /api/pcp-tgu/ { # dev :7011
# proxy_pass http://UPSTREAM_TGU/;
# 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;
# }
# location /api/pcp-complex/ { # dev :7002
# proxy_pass http://UPSTREAM_COMPLEX/;
# 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;
# }
# location /api/stations/ { # dev :7009 путь СОХРАНЯЕМ (proxy_pass без / и без URI)
# proxy_pass http://UPSTREAM_STATIONS;
# 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;
# }
#
# Общий catch-all. В деве это ui-service (:7008): /api/tgu-planning, /api/current-plans, /api/auth.
location /api/ { location /api/ {
proxy_pass http://127.0.0.1:8000/api/; # ЗАГЛУШКА: замените на ui-service/gateway proxy_pass http://pcp-gateway-service:8080;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+4 -32
View File
@@ -21,40 +21,12 @@ export default defineConfig({
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
port: 5174, port: 5174,
// ТГУ ходит в несколько бэкендов через префиксы. В проде эти маршруты // Всё /api/* уходит на gateway (Spring Cloud Gateway, dev :7080). Gateway сам
// повторяются в nginx.conf (зеркало) либо на gateway/ingress. // срезает префиксы pcp-* (StripPrefix) и выбирает бэкенд — ровно то, что раньше
// делали правила-зеркала здесь. В проде то же делает nginx.conf.
proxy: { proxy: {
'/api/pcp-request': {
target: 'http://localhost:7005',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api\/pcp-request/, ''),
},
'/api/pcp-ballistics': {
target: 'http://localhost:7003',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api\/pcp-ballistics/, ''),
},
'/api/pcp-mission': {
target: 'http://localhost:7010',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api\/pcp-mission/, ''),
},
'/api/pcp-tgu': {
target: 'http://localhost:7011',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api\/pcp-tgu/, ''),
},
'/api/pcp-complex': {
target: 'http://localhost:7002',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api\/pcp-complex/, ''),
},
'/api/stations': {
target: 'http://localhost:7009',
changeOrigin: true,
},
'/api': { '/api': {
target: 'http://localhost:7008', target: 'http://localhost:7080',
changeOrigin: true, changeOrigin: true,
}, },
}, },
+1
View File
@@ -24,6 +24,7 @@ include(":services:pcp-coverage-scheme-service")
include(":services:pcp-dynamic-plan-service") include(":services:pcp-dynamic-plan-service")
include(":services:spring-cloud-config-server") include(":services:spring-cloud-config-server")
include(":services:pcp-srpring-boot-admin-server") include(":services:pcp-srpring-boot-admin-server")
include(":services:pcp-gateway-service")
pluginManagement { pluginManagement {
repositories { repositories {