Перейти к основному содержимому

Deployment Guide — Infrastructure & CI/CD

Версия: 1.0
Дата: 19.04.2026
Статус: Черновик

Обзор

Deployment Guide определяет полную инфраструктуру и процессы deployment для vitrip.store согласно технологическому стеку "Infrastructure | Docker + Kubernetes | Контейнеризация, горизонтальное масштабирование" из Архитектура платформы. Включает Kubernetes manifests для всех микросервисов из Business Services, PostgreSQL и Redis из Database Schema и Storage Layer, Traefik API Gateway из API Layer, и Prometheus monitoring. Покрывает CI/CD pipeline для deployment процессов и infrastructure as code.

Infrastructure Topology: См. диаграмму vitrip_infrastructure_topology.jpg — полная схема Kubernetes кластера с сервисами, networking и storage.

Infrastructure Topology vitrip.store

CI/CD Pipeline: См. диаграмму vitrip_cicd_pipeline.jpg — автоматизированный pipeline от git push до production deployment с тестированием и мониторингом.

CI/CD Pipeline vitrip.store

Monitoring & Observability: См. диаграмму vitrip_monitoring_stack.jpg — Prometheus, Grafana, logging и alerting для production operations.

Monitoring Stack vitrip.store

Kubernetes Cluster Setup

Cluster Requirements

# Минимальные требования согласно overview/index.md
Kubernetes Version: >= 1.28
Node Requirements:
Control Plane: 3 nodes (2 CPU, 4GB RAM, 50GB SSD)
Worker Nodes: 5+ nodes (4 CPU, 8GB RAM, 100GB SSD)

Storage Classes:
- fast-ssd: NVMe SSD для баз данных
- standard: HDD для логов и backups

Network Requirements:
- CNI: Calico or Cilium
- LoadBalancer: MetalLB или cloud provider LB
- Ingress: Traefik (согласно overview/layers.md)

Security:
- RBAC enabled
- Pod Security Standards: restricted
- Network Policies enabled
- Secrets encryption at rest

Namespace Configuration

# namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
name: vitrip-platform
labels:
environment: production
application: vitrip
---
apiVersion: v1
kind: Namespace
metadata:
name: vitrip-data
labels:
environment: production
tier: data
---
apiVersion: v1
kind: Namespace
metadata:
name: vitrip-monitoring
labels:
environment: production
tier: monitoring
---
apiVersion: v1
kind: Namespace
metadata:
name: vitrip-ingestion
labels:
environment: production
tier: processing

Database Infrastructure

PostgreSQL Deployment

Согласно Database Schema PostgreSQL с PostGIS, ltree extensions.

# postgresql.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgresql
namespace: vitrip-data
spec:
serviceName: postgresql
replicas: 3 # Primary + 2 read replicas
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- name: postgresql
image: postgis/postgis:15-3.3
env:
- name: POSTGRES_DB
value: "vitrip"
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
- name: POSTGRES_SHARED_PRELOAD_LIBRARIES
value: "pg_stat_statements,auto_explain"
ports:
- containerPort: 5432
name: postgres
resources:
requests:
memory: "4Gi"
cpu: "1000m"
limits:
memory: "8Gi"
cpu: "2000m"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
- name: postgres-config
mountPath: /etc/postgresql/postgresql.conf
subPath: postgresql.conf
livenessProbe:
exec:
command:
- pg_isready
- -U
- $(POSTGRES_USER)
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- $(POSTGRES_USER)
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: postgres-config
configMap:
name: postgres-config
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 500Gi

---
apiVersion: v1
kind: Service
metadata:
name: postgresql
namespace: vitrip-data
spec:
clusterIP: None
selector:
app: postgresql
ports:
- port: 5432
targetPort: 5432

---
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
namespace: vitrip-data
data:
postgresql.conf: |
# Performance tuning для vitrip workload
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 64MB
maintenance_work_mem = 512MB

# Connection settings
max_connections = 200

# Logging согласно monitoring requirements
log_statement = 'mod'
log_min_duration_statement = 1000

# Extensions для reference/database-schema.md
shared_preload_libraries = 'pg_stat_statements,auto_explain'

# PostGIS settings
max_locks_per_transaction = 256

PostgreSQL Initialization Job

# postgres-init-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: postgres-init
namespace: vitrip-data
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: postgres-init
image: postgis/postgis:15-3.3
command:
- /bin/bash
- -c
- |
# Ожидание готовности PostgreSQL
until pg_isready -h postgresql -U $POSTGRES_USER; do
echo "Waiting for PostgreSQL..."
sleep 2
done

# Создание extensions согласно reference/database-schema.md
PGPASSWORD=$POSTGRES_PASSWORD psql -h postgresql -U $POSTGRES_USER -d vitrip -c "
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
CREATE EXTENSION IF NOT EXISTS ltree;
CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS btree_gin;
CREATE EXTENSION IF NOT EXISTS btree_gist;
"

# Создание схем из reference/database-schema.md
PGPASSWORD=$POSTGRES_PASSWORD psql -h postgresql -U $POSTGRES_USER -d vitrip -c "
CREATE SCHEMA IF NOT EXISTS hotels;
CREATE SCHEMA IF NOT EXISTS content;
CREATE SCHEMA IF NOT EXISTS bookings;
CREATE SCHEMA IF NOT EXISTS users;
"

echo "PostgreSQL initialization completed"
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password

Redis Cluster

Согласно Storage Layer Redis кеширование с TTL стратегиями.

# redis.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: vitrip-data
spec:
serviceName: redis
replicas: 6 # 3 masters + 3 replicas for HA
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.2-alpine
command:
- redis-server
- /etc/redis/redis.conf
ports:
- containerPort: 6379
name: redis
- containerPort: 16379
name: cluster-bus
resources:
requests:
memory: "2Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "1000m"
volumeMounts:
- name: redis-storage
mountPath: /data
- name: redis-config
mountPath: /etc/redis
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: redis-config
configMap:
name: redis-config
volumeClaimTemplates:
- metadata:
name: redis-storage
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi

---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: vitrip-data
spec:
clusterIP: None
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
name: redis
- port: 16379
targetPort: 16379
name: cluster-bus

---
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
namespace: vitrip-data
data:
redis.conf: |
# Cluster configuration
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000

# Persistence для production
save 900 1
save 300 10
save 60 10000

# Memory management согласно reference/storage.md
maxmemory 3gb
maxmemory-policy allkeys-lru

# Performance tuning
tcp-keepalive 300
timeout 0

Message Queue Infrastructure

NATS Cluster

Согласно технологическому стеку "Message queue | NATS → Kafka" из Архитектура платформы.

# nats.yaml
apiVersion: nats.io/v1alpha2
kind: NatsCluster
metadata:
name: nats
namespace: vitrip-platform
spec:
size: 3
version: "2.10.7"

pod:
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"

auth:
enableServiceAccounts: true

config:
cluster:
port: 6222

jetstream:
enabled: true
memStore:
enabled: true
maxSize: "1Gi"
fileStore:
enabled: true
maxSize: "10Gi"

storage:
storageClassName: fast-ssd
size: "20Gi"

Kafka Cluster

# kafka.yaml
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: kafka-cluster
namespace: vitrip-platform
spec:
kafka:
version: 3.6.0
replicas: 3

listeners:
- name: plain
port: 9092
type: internal
tls: false
- name: tls
port: 9093
type: internal
tls: true

config:
offsets.topic.replication.factor: 3
transaction.state.log.replication.factor: 3
transaction.state.log.min.isr: 2
default.replication.factor: 3
min.insync.replicas: 2
inter.broker.protocol.version: "3.6"

storage:
type: persistent-claim
size: 100Gi
class: fast-ssd

resources:
requests:
memory: 2Gi
cpu: "1000m"
limits:
memory: 4Gi
cpu: "2000m"

zookeeper:
replicas: 3
storage:
type: persistent-claim
size: 10Gi
class: fast-ssd
resources:
requests:
memory: 1Gi
cpu: "500m"
limits:
memory: 2Gi
cpu: "1000m"

entityOperator:
topicOperator: {}
userOperator: {}

Business Services Deployment

Search Service

Согласно SearchService из Business Services на Go с gRPC.

# search-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: search-service
namespace: vitrip-platform
labels:
app: search-service
component: business-service
spec:
replicas: 3
selector:
matchLabels:
app: search-service
template:
metadata:
labels:
app: search-service
component: business-service
spec:
containers:
- name: search-service
image: vitrip/search-service:v1.0
ports:
- containerPort: 8080
name: grpc
- containerPort: 9090
name: metrics
env:
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: postgres-credentials
key: url
- name: REDIS_URL
value: "redis:6379"
- name: NATS_URL
value: "nats://nats:4222"
- name: LOG_LEVEL
value: "info"
- name: GOMAXPROCS
value: "2"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
grpc:
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
grpc:
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 65534
capabilities:
drop:
- ALL

---
apiVersion: v1
kind: Service
metadata:
name: search-service
namespace: vitrip-platform
labels:
app: search-service
spec:
selector:
app: search-service
ports:
- port: 8080
targetPort: 8080
name: grpc
- port: 9090
targetPort: 9090
name: metrics

Pricing Service

# pricing-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pricing-service
namespace: vitrip-platform
labels:
app: pricing-service
component: business-service
spec:
replicas: 2
selector:
matchLabels:
app: pricing-service
template:
metadata:
labels:
app: pricing-service
component: business-service
spec:
containers:
- name: pricing-service
image: vitrip/pricing-service:v1.0
ports:
- containerPort: 8080
name: grpc
- containerPort: 9090
name: metrics
env:
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: postgres-credentials
key: url
- name: REDIS_URL
value: "redis:6379"
- name: NATS_URL
value: "nats://nats:4222"
- name: EXTERNAL_EXCHANGE_API
valueFrom:
secretKeyRef:
name: external-apis
key: exchange-api-key
resources:
requests:
memory: "256Mi"
cpu: "125m"
limits:
memory: "512Mi"
cpu: "250m"
livenessProbe:
grpc:
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
grpc:
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
name: pricing-service
namespace: vitrip-platform
spec:
selector:
app: pricing-service
ports:
- port: 8080
targetPort: 8080
name: grpc
- port: 9090
targetPort: 9090
name: metrics

Booking Service

# booking-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: booking-service
namespace: vitrip-platform
labels:
app: booking-service
component: business-service
spec:
replicas: 4 # Critical service, более высокая доступность
selector:
matchLabels:
app: booking-service
template:
metadata:
labels:
app: booking-service
component: business-service
spec:
containers:
- name: booking-service
image: vitrip/booking-service:v1.0
ports:
- containerPort: 8080
name: grpc
- containerPort: 9090
name: metrics
env:
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: postgres-credentials
key: url
- name: REDIS_URL
value: "redis:6379"
- name: NATS_URL
value: "nats://nats:4222"
- name: SUPPLIER_STUBA_URL
valueFrom:
secretKeyRef:
name: supplier-apis
key: stuba-url
- name: SUPPLIER_HOMETOGO_URL
valueFrom:
secretKeyRef:
name: supplier-apis
key: hometogo-url
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
grpc:
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
grpc:
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
name: booking-service
namespace: vitrip-platform
spec:
selector:
app: booking-service
ports:
- port: 8080
targetPort: 8080
name: grpc
- port: 9090
targetPort: 9090
name: metrics

Tour Builder Service

# tour-builder-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: tour-builder-service
namespace: vitrip-platform
labels:
app: tour-builder-service
component: business-service
spec:
replicas: 2
selector:
matchLabels:
app: tour-builder-service
template:
metadata:
labels:
app: tour-builder-service
component: business-service
spec:
containers:
- name: tour-builder-service
image: vitrip/tour-builder-service:v1.0
ports:
- containerPort: 8080
name: grpc
- containerPort: 9090
name: metrics
env:
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: postgres-credentials
key: url
- name: REDIS_URL
value: "redis:6379"
- name: NATS_URL
value: "nats://nats:4222"
- name: PDF_GENERATOR_URL
value: "http://pdf-generator:8080"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
grpc:
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
grpc:
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
name: tour-builder-service
namespace: vitrip-platform
spec:
selector:
app: tour-builder-service
ports:
- port: 8080
targetPort: 8080
name: grpc
- port: 9090
targetPort: 9090
name: metrics

Data Processing Services

Ingestion Processor

Согласно Rust data processing из Ingestion Layer.

# ingestion-processor.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingestion-processor
namespace: vitrip-ingestion
labels:
app: ingestion-processor
component: data-processing
spec:
replicas: 3
selector:
matchLabels:
app: ingestion-processor
template:
metadata:
labels:
app: ingestion-processor
component: data-processing
spec:
containers:
- name: processor
image: vitrip/ingestion-processor:v1.0
env:
- name: KAFKA_BROKERS
value: "kafka-cluster-kafka-bootstrap:9092"
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: postgres-credentials
key: url
- name: REDIS_URL
value: "redis:6379"
- name: RUST_LOG
value: "info"
- name: HOTEL_MATCHING_THRESHOLD
value: "0.8"
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 9090
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 9090
initialDelaySeconds: 5
periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
name: ingestion-processor
namespace: vitrip-ingestion
spec:
selector:
app: ingestion-processor
ports:
- port: 9090
targetPort: 9090
name: metrics

API Gateway & Load Balancer

Traefik Deployment

Согласно API Layer Traefik API Gateway с Let's Encrypt.

# traefik.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik
namespace: vitrip-platform

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: traefik
rules:
- apiGroups:
- ""
resources:
- services
- secrets
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io
resources:
- ingresses
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- traefik.containo.us
resources:
- middlewares
- tlsoptions
- tlsstores
- ingressroutes
- traefikservices
- serverstransports
verbs:
- get
- list
- watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: traefik
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: traefik
subjects:
- kind: ServiceAccount
name: traefik
namespace: vitrip-platform

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: traefik
namespace: vitrip-platform
labels:
app: traefik
component: api-gateway
spec:
replicas: 3
selector:
matchLabels:
app: traefik
template:
metadata:
labels:
app: traefik
component: api-gateway
spec:
serviceAccountName: traefik
containers:
- name: traefik
image: traefik:v3.0
args:
- --api.dashboard=true
- --api.insecure=false
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --providers.kubernetes=true
- --providers.kubernetes.ingressclass=traefik
- --certificatesresolvers.letsencrypt.acme.email=admin@vitrip.store
- --certificatesresolvers.letsencrypt.acme.storage=/acme/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
- --metrics.prometheus=true
- --metrics.prometheus.addrouters=true
- --log.level=INFO
ports:
- containerPort: 80
name: web
- containerPort: 443
name: websecure
- containerPort: 8080
name: dashboard
- containerPort: 9100
name: metrics
resources:
requests:
memory: "256Mi"
cpu: "125m"
limits:
memory: "512Mi"
cpu: "250m"
volumeMounts:
- name: acme
mountPath: /acme
livenessProbe:
httpGet:
path: /ping
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ping
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: acme
persistentVolumeClaim:
claimName: traefik-acme

---
apiVersion: v1
kind: Service
metadata:
name: traefik
namespace: vitrip-platform
spec:
type: LoadBalancer
selector:
app: traefik
ports:
- port: 80
targetPort: 80
name: web
- port: 443
targetPort: 443
name: websecure

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: traefik-acme
namespace: vitrip-platform
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 1Gi

API Gateway Routes

# api-routes.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: api-gateway
namespace: vitrip-platform
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.vitrip.store`) && PathPrefix(`/v1`)
kind: Rule
services:
- name: api-gateway-service
port: 8080
middlewares:
- name: auth-chain
- name: rate-limit
- name: cors-headers
tls:
certResolver: letsencrypt

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: auth-chain
namespace: vitrip-platform
spec:
chain:
middlewares:
- name: api-auth
- name: rate-limit-check

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: rate-limit
namespace: vitrip-platform
spec:
rateLimit:
average: 100
burst: 200
period: 1m

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: cors-headers
namespace: vitrip-platform
spec:
headers:
accessControlAllowOriginList:
- "https://vitrip.store"
- "https://app.vitrip.store"
accessControlAllowMethods:
- "GET"
- "POST"
- "PUT"
- "PATCH"
- "DELETE"
- "OPTIONS"
accessControlAllowHeaders:
- "Authorization"
- "Content-Type"
- "X-API-Key"
accessControlExposeHeaders:
- "X-RateLimit-Remaining"
- "X-RateLimit-Reset"

Frontend Application

Next.js Application

Согласно Clients Layer React/Next.js frontend.

# frontend-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-app
namespace: vitrip-platform
labels:
app: frontend-app
component: frontend
spec:
replicas: 3
selector:
matchLabels:
app: frontend-app
template:
metadata:
labels:
app: frontend-app
component: frontend
spec:
containers:
- name: frontend-app
image: vitrip/frontend-app:v1.0
ports:
- containerPort: 3000
name: http
env:
- name: NEXT_PUBLIC_API_URL
value: "https://api.vitrip.store/v1"
- name: NODE_ENV
value: "production"
- name: NEXT_TELEMETRY_DISABLED
value: "1"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
name: frontend-app
namespace: vitrip-platform
spec:
selector:
app: frontend-app
ports:
- port: 3000
targetPort: 3000
name: http

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: frontend-route
namespace: vitrip-platform
spec:
entryPoints:
- websecure
routes:
- match: Host(`vitrip.store`)
kind: Rule
services:
- name: frontend-app
port: 3000
tls:
certResolver: letsencrypt

Monitoring Infrastructure

Prometheus Setup

Согласно "Monitoring | Prometheus + Grafana" из Архитектура платформы.

# prometheus.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: vitrip-monitoring
labels:
app: prometheus
component: monitoring
spec:
replicas: 2
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
component: monitoring
spec:
containers:
- name: prometheus
image: prom/prometheus:v2.48.0
args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --web.console.libraries=/etc/prometheus/console_libraries
- --web.console.templates=/etc/prometheus/consoles
- --web.enable-lifecycle
- --web.enable-admin-api
ports:
- containerPort: 9090
name: prometheus
resources:
requests:
memory: "2Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "1000m"
volumeMounts:
- name: prometheus-config
mountPath: /etc/prometheus
- name: prometheus-storage
mountPath: /prometheus
livenessProbe:
httpGet:
path: /-/healthy
port: 9090
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /-/ready
port: 9090
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: prometheus-config
configMap:
name: prometheus-config
- name: prometheus-storage
persistentVolumeClaim:
claimName: prometheus-storage

---
apiVersion: v1
kind: Service
metadata:
name: prometheus
namespace: vitrip-monitoring
spec:
selector:
app: prometheus
ports:
- port: 9090
targetPort: 9090
name: prometheus

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: prometheus-storage
namespace: vitrip-monitoring
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi

---
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: vitrip-monitoring
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s

rule_files:
- "alert_rules.yml"

alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093

scrape_configs:
# Business Services metrics
- job_name: 'search-service'
static_configs:
- targets: ['search-service.vitrip-platform:9090']

- job_name: 'pricing-service'
static_configs:
- targets: ['pricing-service.vitrip-platform:9090']

- job_name: 'booking-service'
static_configs:
- targets: ['booking-service.vitrip-platform:9090']

- job_name: 'tour-builder-service'
static_configs:
- targets: ['tour-builder-service.vitrip-platform:9090']

# Infrastructure metrics
- job_name: 'traefik'
static_configs:
- targets: ['traefik.vitrip-platform:9100']

- job_name: 'postgresql'
static_configs:
- targets: ['postgres-exporter.vitrip-data:9187']

- job_name: 'redis'
static_configs:
- targets: ['redis-exporter.vitrip-data:9121']

# Kubernetes cluster monitoring
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true

alert_rules.yml: |
groups:
- name: vitrip-alerts
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected"
description: "Error rate is above 10% for 5 minutes"

- alert: DatabaseDown
expr: up{job="postgresql"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "PostgreSQL is down"

- alert: HighMemoryUsage
expr: (container_memory_usage_bytes / container_spec_memory_limit_bytes) > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "High memory usage"

Grafana Dashboard

# grafana.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: vitrip-monitoring
labels:
app: grafana
component: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
component: monitoring
spec:
containers:
- name: grafana
image: grafana/grafana:10.2.0
ports:
- containerPort: 3000
name: grafana
env:
- name: GF_SECURITY_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: grafana-credentials
key: password
- name: GF_INSTALL_PLUGINS
value: "grafana-piechart-panel,grafana-worldmap-panel"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
volumeMounts:
- name: grafana-storage
mountPath: /var/lib/grafana
- name: grafana-config
mountPath: /etc/grafana/provisioning
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: grafana-storage
persistentVolumeClaim:
claimName: grafana-storage
- name: grafana-config
configMap:
name: grafana-config

---
apiVersion: v1
kind: Service
metadata:
name: grafana
namespace: vitrip-monitoring
spec:
selector:
app: grafana
ports:
- port: 3000
targetPort: 3000
name: grafana

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-storage
namespace: vitrip-monitoring
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 10Gi

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: grafana-route
namespace: vitrip-monitoring
spec:
entryPoints:
- websecure
routes:
- match: Host(`monitoring.vitrip.store`)
kind: Rule
services:
- name: grafana
port: 3000
tls:
certResolver: letsencrypt

CI/CD Pipeline

GitLab CI Configuration

# .gitlab-ci.yml
stages:
- test
- build
- deploy-staging
- deploy-production

variables:
DOCKER_REGISTRY: registry.vitrip.store
KUBECONFIG: /tmp/kubeconfig

# Test Stage
test-go-services:
stage: test
image: golang:1.21
script:
- cd services/
- go mod tidy
- go test ./... -v -race -coverprofile=coverage.out
- go tool cover -html=coverage.out -o coverage.html
artifacts:
reports:
coverage_format: cobertura
coverage_path: services/coverage.out
paths:
- services/coverage.html
coverage: '/coverage: \d+\.\d+% of statements/'

test-rust-ingestion:
stage: test
image: rust:1.75
script:
- cd ingestion/
- cargo test --verbose
- cargo clippy -- -D warnings
- cargo fmt -- --check
cache:
paths:
- ingestion/target/

test-frontend:
stage: test
image: node:20-alpine
script:
- cd frontend/
- npm ci
- npm run test:coverage
- npm run lint
- npm run type-check
artifacts:
reports:
coverage_format: cobertura
coverage_path: frontend/coverage/cobertura-coverage.xml
cache:
paths:
- frontend/node_modules/

# Security Scanning
security-scan:
stage: test
image: aquasecurity/trivy:latest
script:
- trivy fs --exit-code 1 --no-progress --severity HIGH,CRITICAL .
allow_failure: true

# Build Stage
build-search-service:
stage: build
image: docker:24.0.7
services:
- docker:24.0.7-dind
script:
- docker build -t $DOCKER_REGISTRY/search-service:$CI_COMMIT_SHA services/search/
- docker build -t $DOCKER_REGISTRY/search-service:latest services/search/
- echo $REGISTRY_PASSWORD | docker login $DOCKER_REGISTRY -u $REGISTRY_USER --password-stdin
- docker push $DOCKER_REGISTRY/search-service:$CI_COMMIT_SHA
- docker push $DOCKER_REGISTRY/search-service:latest
only:
changes:
- services/search/**/*
- services/shared/**/*

build-pricing-service:
stage: build
image: docker:24.0.7
services:
- docker:24.0.7-dind
script:
- docker build -t $DOCKER_REGISTRY/pricing-service:$CI_COMMIT_SHA services/pricing/
- docker build -t $DOCKER_REGISTRY/pricing-service:latest services/pricing/
- echo $REGISTRY_PASSWORD | docker login $DOCKER_REGISTRY -u $REGISTRY_USER --password-stdin
- docker push $DOCKER_REGISTRY/pricing-service:$CI_COMMIT_SHA
- docker push $DOCKER_REGISTRY/pricing-service:latest
only:
changes:
- services/pricing/**/*
- services/shared/**/*

build-booking-service:
stage: build
image: docker:24.0.7
services:
- docker:24.0.7-dind
script:
- docker build -t $DOCKER_REGISTRY/booking-service:$CI_COMMIT_SHA services/booking/
- docker build -t $DOCKER_REGISTRY/booking-service:latest services/booking/
- echo $REGISTRY_PASSWORD | docker login $DOCKER_REGISTRY -u $REGISTRY_USER --password-stdin
- docker push $DOCKER_REGISTRY/booking-service:$CI_COMMIT_SHA
- docker push $DOCKER_REGISTRY/booking-service:latest
only:
changes:
- services/booking/**/*
- services/shared/**/*

build-tour-builder-service:
stage: build
image: docker:24.0.7
services:
- docker:24.0.7-dind
script:
- docker build -t $DOCKER_REGISTRY/tour-builder-service:$CI_COMMIT_SHA services/tour-builder/
- docker build -t $DOCKER_REGISTRY/tour-builder-service:latest services/tour-builder/
- echo $REGISTRY_PASSWORD | docker login $DOCKER_REGISTRY -u $REGISTRY_USER --password-stdin
- docker push $DOCKER_REGISTRY/tour-builder-service:$CI_COMMIT_SHA
- docker push $DOCKER_REGISTRY/tour-builder-service:latest
only:
changes:
- services/tour-builder/**/*
- services/shared/**/*

build-ingestion-processor:
stage: build
image: docker:24.0.7
services:
- docker:24.0.7-dind
script:
- docker build -t $DOCKER_REGISTRY/ingestion-processor:$CI_COMMIT_SHA ingestion/
- docker build -t $DOCKER_REGISTRY/ingestion-processor:latest ingestion/
- echo $REGISTRY_PASSWORD | docker login $DOCKER_REGISTRY -u $REGISTRY_USER --password-stdin
- docker push $DOCKER_REGISTRY/ingestion-processor:$CI_COMMIT_SHA
- docker push $DOCKER_REGISTRY/ingestion-processor:latest
only:
changes:
- ingestion/**/*

build-frontend-app:
stage: build
image: docker:24.0.7
services:
- docker:24.0.7-dind
script:
- docker build -t $DOCKER_REGISTRY/frontend-app:$CI_COMMIT_SHA frontend/
- docker build -t $DOCKER_REGISTRY/frontend-app:latest frontend/
- echo $REGISTRY_PASSWORD | docker login $DOCKER_REGISTRY -u $REGISTRY_USER --password-stdin
- docker push $DOCKER_REGISTRY/frontend-app:$CI_COMMIT_SHA
- docker push $DOCKER_REGISTRY/frontend-app:latest
only:
changes:
- frontend/**/*

# Staging Deployment
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
script:
- echo $KUBE_CONFIG | base64 -d > $KUBECONFIG
- kubectl config use-context staging
# Update image tags to current commit
- kubectl set image deployment/search-service search-service=$DOCKER_REGISTRY/search-service:$CI_COMMIT_SHA -n vitrip-platform
- kubectl set image deployment/pricing-service pricing-service=$DOCKER_REGISTRY/pricing-service:$CI_COMMIT_SHA -n vitrip-platform
- kubectl set image deployment/booking-service booking-service=$DOCKER_REGISTRY/booking-service:$CI_COMMIT_SHA -n vitrip-platform
- kubectl set image deployment/tour-builder-service tour-builder-service=$DOCKER_REGISTRY/tour-builder-service:$CI_COMMIT_SHA -n vitrip-platform
- kubectl set image deployment/ingestion-processor processor=$DOCKER_REGISTRY/ingestion-processor:$CI_COMMIT_SHA -n vitrip-ingestion
- kubectl set image deployment/frontend-app frontend-app=$DOCKER_REGISTRY/frontend-app:$CI_COMMIT_SHA -n vitrip-platform
# Wait for rollout to complete
- kubectl rollout status deployment/search-service -n vitrip-platform --timeout=300s
- kubectl rollout status deployment/booking-service -n vitrip-platform --timeout=300s
# Run health checks
- kubectl run health-check --image=curlimages/curl:8.5.0 --rm -i --restart=Never -- curl -f http://search-service:9090/health
environment:
name: staging
url: https://staging.vitrip.store
only:
- develop

# Production Deployment
deploy-production:
stage: deploy-production
image: bitnami/kubectl:latest
script:
- echo $KUBE_CONFIG_PROD | base64 -d > $KUBECONFIG
- kubectl config use-context production
# Blue-Green Deployment Strategy
- |
for service in search-service pricing-service booking-service tour-builder-service; do
kubectl patch deployment $service -n vitrip-platform -p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":"50%","maxUnavailable":"0%"}}}}'
kubectl set image deployment/$service $service=$DOCKER_REGISTRY/$service:$CI_COMMIT_SHA -n vitrip-platform
kubectl rollout status deployment/$service -n vitrip-platform --timeout=600s
done
- kubectl set image deployment/frontend-app frontend-app=$DOCKER_REGISTRY/frontend-app:$CI_COMMIT_SHA -n vitrip-platform
- kubectl rollout status deployment/frontend-app -n vitrip-platform --timeout=600s
# Post-deployment health checks
- sleep 30
- kubectl run prod-health-check --image=curlimages/curl:8.5.0 --rm -i --restart=Never -- curl -f https://api.vitrip.store/v1/health
environment:
name: production
url: https://vitrip.store
when: manual
only:
- master

Backup & Disaster Recovery

PostgreSQL Backup Strategy

# postgres-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: postgres-backup
namespace: vitrip-data
spec:
schedule: "0 2 * * *" # Daily at 2 AM
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: postgres-backup
image: postgres:15
command:
- /bin/bash
- -c
- |
# Full database backup
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="vitrip_backup_${TIMESTAMP}.sql.gz"

PGPASSWORD=$POSTGRES_PASSWORD pg_dump \
-h postgresql \
-U $POSTGRES_USER \
-d vitrip \
--verbose \
--no-owner \
--no-acl \
| gzip > /backup/${BACKUP_FILE}

# Upload to cloud storage (S3-compatible)
aws s3 cp /backup/${BACKUP_FILE} s3://vitrip-backups/postgres/

# Cleanup local files older than 7 days
find /backup -name "vitrip_backup_*.sql.gz" -mtime +7 -delete

echo "Backup completed: ${BACKUP_FILE}"
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: s3-credentials
key: access-key-id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: s3-credentials
key: secret-access-key
volumeMounts:
- name: backup-storage
mountPath: /backup
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-storage

Redis Backup

# redis-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: redis-backup
namespace: vitrip-data
spec:
schedule: "0 3 * * *" # Daily at 3 AM
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: redis-backup
image: redis:7.2-alpine
command:
- /bin/sh
- -c
- |
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# Create Redis backup
redis-cli -h redis BGSAVE

# Wait for backup to complete
while [ $(redis-cli -h redis LASTSAVE) -eq $(redis-cli -h redis LASTSAVE) ]; do
sleep 5
done

# Copy RDB file
cp /data/dump.rdb /backup/redis_backup_${TIMESTAMP}.rdb

# Compress and upload
gzip /backup/redis_backup_${TIMESTAMP}.rdb
aws s3 cp /backup/redis_backup_${TIMESTAMP}.rdb.gz s3://vitrip-backups/redis/

echo "Redis backup completed"
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: s3-credentials
key: access-key-id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: s3-credentials
key: secret-access-key
volumeMounts:
- name: redis-data
mountPath: /data
- name: backup-storage
mountPath: /backup
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-storage-redis-0
- name: backup-storage
persistentVolumeClaim:
claimName: backup-storage

Security Configuration

Network Policies

# network-policies.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vitrip-platform-network-policy
namespace: vitrip-platform
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
# Allow Traefik to reach services
- from:
- podSelector:
matchLabels:
app: traefik
ports:
- protocol: TCP
port: 8080
# Allow inter-service communication
- from:
- namespaceSelector:
matchLabels:
name: vitrip-platform
ports:
- protocol: TCP
port: 8080
egress:
# Allow access to data namespace
- to:
- namespaceSelector:
matchLabels:
name: vitrip-data
ports:
- protocol: TCP
port: 5432 # PostgreSQL
- protocol: TCP
port: 6379 # Redis
# Allow external API calls
- to: []
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 80

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vitrip-data-network-policy
namespace: vitrip-data
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
# Only allow access from platform namespace
- from:
- namespaceSelector:
matchLabels:
name: vitrip-platform
- from:
- namespaceSelector:
matchLabels:
name: vitrip-ingestion

Pod Security Policy

# pod-security-policy.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: vitrip-restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: vitrip-restricted
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames: ['vitrip-restricted']

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vitrip-restricted
roleRef:
kind: ClusterRole
name: vitrip-restricted
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: default
namespace: vitrip-platform
- kind: ServiceAccount
name: default
namespace: vitrip-data
- kind: ServiceAccount
name: default
namespace: vitrip-ingestion

Secrets Management

External Secrets Operator

# external-secrets.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-secret-store
namespace: vitrip-platform
spec:
provider:
vault:
server: "https://vault.vitrip.store"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "vitrip-platform"

---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: postgres-credentials
namespace: vitrip-data
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-secret-store
kind: SecretStore
target:
name: postgres-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: database/postgres
property: username
- secretKey: password
remoteRef:
key: database/postgres
property: password
- secretKey: url
remoteRef:
key: database/postgres
property: url

---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: supplier-apis
namespace: vitrip-platform
spec:
refreshInterval: 6h
secretStoreRef:
name: vault-secret-store
kind: SecretStore
target:
name: supplier-apis
creationPolicy: Owner
data:
- secretKey: stuba-url
remoteRef:
key: suppliers/stuba
property: api-url
- secretKey: hometogo-url
remoteRef:
key: suppliers/hometogo
property: api-url
- secretKey: stuba-auth
remoteRef:
key: suppliers/stuba
property: auth-token
- secretKey: hometogo-auth
remoteRef:
key: suppliers/hometogo
property: auth-token

Связанная документация

  • Технологический стек: "Infrastructure | Docker + Kubernetes" и "Monitoring | Prometheus + Grafana" из Архитектура платформы
  • Микросервисы: 4 Go services (SearchService, PricingService, BookingService, TourBuilderService) из Business Services
  • API Gateway: Traefik конфигурация с Let's Encrypt из API Layer
  • Базы данных: PostgreSQL схемы и структуры из Database Schema
  • Кеширование: Redis кластер и TTL стратегии из Storage Layer
  • Data Processing: Rust ingestion processor с Kafka из Ingestion Layer
  • Frontend: React/Next.js deployment из Clients Layer
  • API спецификации: REST endpoints и OpenAPI spec из API Contracts