chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
FROM golang:1.26.6-alpine AS build
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY cmd ./cmd
|
||||
COPY internal ./internal
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT=unknown
|
||||
ARG BUILD_TIME=unknown
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w \
|
||||
-X data-terminal-cloud/internal/buildinfo.Version=${VERSION} \
|
||||
-X data-terminal-cloud/internal/buildinfo.Commit=${COMMIT} \
|
||||
-X data-terminal-cloud/internal/buildinfo.BuildTime=${BUILD_TIME}" -o /out/api ./cmd/api && \
|
||||
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w \
|
||||
-X data-terminal-cloud/internal/buildinfo.Version=${VERSION} \
|
||||
-X data-terminal-cloud/internal/buildinfo.Commit=${COMMIT} \
|
||||
-X data-terminal-cloud/internal/buildinfo.BuildTime=${BUILD_TIME}" -o /out/callbacks ./cmd/callbacks && \
|
||||
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w \
|
||||
-X data-terminal-cloud/internal/buildinfo.Version=${VERSION} \
|
||||
-X data-terminal-cloud/internal/buildinfo.Commit=${COMMIT} \
|
||||
-X data-terminal-cloud/internal/buildinfo.BuildTime=${BUILD_TIME}" -o /out/worker ./cmd/worker && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/sim-device ./cmd/sim-device && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/firmware-conformance ./cmd/firmware-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/alert-receiver-conformance ./cmd/alert-receiver-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/kms-rotation-conformance ./cmd/kms-rotation-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/recovery-conformance ./cmd/recovery-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/infrastructure-conformance ./cmd/infrastructure-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/safari-conformance ./cmd/safari-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/rtc-conformance ./cmd/rtc-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/agent-models-conformance ./cmd/agent-models-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/ci-supply-chain-conformance ./cmd/ci-supply-chain-conformance && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/mock-provider ./cmd/mock-provider && \
|
||||
CGO_ENABLED=0 go build -trimpath -o /out/mock-alert-receiver ./cmd/mock-alert-receiver
|
||||
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
WORKDIR /app
|
||||
COPY --from=build /out/ /app/
|
||||
EXPOSE 8080 8081 8082 8083 8084
|
||||
USER nonroot:nonroot
|
||||
ENTRYPOINT ["/app/api"]
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"uid": "voice-business", "title": "业务仪表盘", "schemaVersion": 41, "version": 1,
|
||||
"refresh": "10s", "time": {"from": "now-6h", "to": "now"},
|
||||
"tags": ["voice-platform", "business"],
|
||||
"panels": [
|
||||
{"id": 1, "type": "stat", "title": "进行中通话", "gridPos": {"x":0,"y":0,"w":6,"h":5}, "datasource":{"uid":"prometheus","type":"prometheus"}, "targets":[{"refId":"A","expr":"sum(voice_platform_conversations{status=~\"PREPARING|ROOM_ISSUED|STARTING_AGENT|ACTIVE|STOPPING\"})"}]},
|
||||
{"id": 2, "type": "stat", "title": "隐私请求部分失败", "gridPos": {"x":6,"y":0,"w":6,"h":5}, "datasource":{"uid":"prometheus","type":"prometheus"}, "targets":[{"refId":"A","expr":"voice_platform_privacy_requests{status=\"PARTIAL_FAILED\"}"}]},
|
||||
{"id": 3, "type": "stat", "title": "固件失败设备", "gridPos": {"x":12,"y":0,"w":6,"h":5}, "datasource":{"uid":"prometheus","type":"prometheus"}, "targets":[{"refId":"A","expr":"voice_platform_firmware_assignments{status=\"FAILED\"}"}]},
|
||||
{"id": 4, "type": "stat", "title": "活动业务告警", "gridPos": {"x":18,"y":0,"w":6,"h":5}, "datasource":{"uid":"prometheus","type":"prometheus"}, "targets":[{"refId":"A","expr":"voice_platform_alert_incidents{category=\"BUSINESS\"}"}]},
|
||||
{"id": 5, "type": "timeseries", "title": "业务接口请求速率", "gridPos": {"x":0,"y":5,"w":12,"h":8}, "datasource":{"uid":"prometheus","type":"prometheus"}, "targets":[{"refId":"A","expr":"sum by (route) (rate(voice_platform_http_requests_total{service=\"api\",route=~\"/api/v1/.*\"}[5m]))","legendFormat":"{{route}}"}]},
|
||||
{"id": 6, "type": "timeseries", "title": "通话状态分布", "gridPos": {"x":12,"y":5,"w":12,"h":8}, "datasource":{"uid":"prometheus","type":"prometheus"}, "targets":[{"refId":"A","expr":"voice_platform_conversations","legendFormat":"{{status}}"}]}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"uid": "voice-security", "title": "安全仪表盘", "schemaVersion": 41, "version": 1,
|
||||
"refresh": "30s", "time": {"from": "now-24h", "to": "now"},
|
||||
"tags": ["voice-platform", "security"],
|
||||
"panels": [
|
||||
{"id":1,"type":"stat","title":"7天内到期证书","gridPos":{"x":0,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"voice_platform_security_certificates_expiring{window=\"7d\"}"}]},
|
||||
{"id":2,"type":"stat","title":"30天内到期证书","gridPos":{"x":6,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"voice_platform_security_certificates_expiring{window=\"30d\"}"}]},
|
||||
{"id":3,"type":"stat","title":"活动安全告警","gridPos":{"x":12,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"voice_platform_alert_incidents{category=\"SECURITY\"}"}]},
|
||||
{"id":4,"type":"stat","title":"当前限流拒绝速率","gridPos":{"x":18,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"sum(rate(voice_platform_http_requests_total{status=\"429\"}[5m]))"}]},
|
||||
{"id":5,"type":"timeseries","title":"身份与权限拒绝","gridPos":{"x":0,"y":5,"w":12,"h":8},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"sum by (status,route) (rate(voice_platform_http_requests_total{status=~\"401|403\"}[5m]))","legendFormat":"{{status}} {{route}}"}]},
|
||||
{"id":6,"type":"timeseries","title":"设备与供应商入口异常响应","gridPos":{"x":12,"y":5,"w":12,"h":8},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"sum by (status,route) (rate(voice_platform_http_requests_total{route=~\"/device/.*|/callbacks/.*\",status!~\"2..\"}[5m]))","legendFormat":"{{status}} {{route}}"}]}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"uid": "voice-technical", "title": "技术仪表盘", "schemaVersion": 41, "version": 1,
|
||||
"refresh": "10s", "time": {"from": "now-1h", "to": "now"},
|
||||
"tags": ["voice-platform", "technical"],
|
||||
"panels": [
|
||||
{"id":1,"type":"stat","title":"依赖可用性","gridPos":{"x":0,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"min(voice_platform_dependency_up)"}]},
|
||||
{"id":2,"type":"stat","title":"可靠任务死信","gridPos":{"x":6,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"voice_platform_outbox_events{status=\"dead\"}"}]},
|
||||
{"id":3,"type":"stat","title":"供应商通知积压","gridPos":{"x":12,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"voice_platform_provider_notification_backlog"}]},
|
||||
{"id":4,"type":"stat","title":"活动技术告警","gridPos":{"x":18,"y":0,"w":6,"h":5},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"voice_platform_alert_incidents{category=\"TECHNICAL\"}"}]},
|
||||
{"id":5,"type":"timeseries","title":"HTTP P95 延迟","gridPos":{"x":0,"y":5,"w":12,"h":8},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"histogram_quantile(0.95, sum by (le,service) (rate(voice_platform_http_request_duration_seconds_bucket[5m])))","legendFormat":"{{service}}"}]},
|
||||
{"id":6,"type":"timeseries","title":"HTTP 5xx 速率","gridPos":{"x":12,"y":5,"w":12,"h":8},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"sum by (service) (rate(voice_platform_http_requests_total{status=~\"5..\"}[5m]))","legendFormat":"{{service}}"}]},
|
||||
{"id":7,"type":"timeseries","title":"待处理与执行中可靠任务","gridPos":{"x":0,"y":13,"w":24,"h":8},"datasource":{"uid":"prometheus","type":"prometheus"},"targets":[{"refId":"A","expr":"voice_platform_outbox_events{status=~\"pending|processing\"}","legendFormat":"{{status}}"}]}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: 1
|
||||
providers:
|
||||
- name: data-terminal-cloud
|
||||
orgId: 1
|
||||
folder: 数据采集终端云平台
|
||||
type: file
|
||||
disableDeletion: true
|
||||
updateIntervalSeconds: 30
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
uid: prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
editable: false
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"realm": "voice-local",
|
||||
"enabled": true,
|
||||
"displayName": "长者语音平台本地身份服务",
|
||||
"sslRequired": "none",
|
||||
"registrationAllowed": false,
|
||||
"loginWithEmailAllowed": true,
|
||||
"duplicateEmailsAllowed": false,
|
||||
"resetPasswordAllowed": false,
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "voice-platform-web",
|
||||
"name": "本地Web客户端",
|
||||
"enabled": true,
|
||||
"publicClient": true,
|
||||
"standardFlowEnabled": true,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"redirectUris": ["http://localhost:3100/*", "http://localhost:5173/*"],
|
||||
"webOrigins": ["http://localhost:3100", "http://localhost:5173"],
|
||||
"protocol": "openid-connect",
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "voice-platform-audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.client.audience": "voice-platform-web",
|
||||
"access.token.claim": "true",
|
||||
"id.token.claim": "false"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000101",
|
||||
"username": "owner",
|
||||
"firstName": "本地",
|
||||
"lastName": "所有者",
|
||||
"email": "owner@example.invalid",
|
||||
"emailVerified": true,
|
||||
"enabled": true,
|
||||
"credentials": [{"type": "password", "value": "LocalOnly123!", "temporary": false}]
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000102",
|
||||
"username": "editor",
|
||||
"firstName": "本地",
|
||||
"lastName": "编辑者",
|
||||
"email": "editor@example.invalid",
|
||||
"emailVerified": true,
|
||||
"enabled": true,
|
||||
"credentials": [{"type": "password", "value": "LocalOnly123!", "temporary": false}]
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000103",
|
||||
"username": "outsider",
|
||||
"firstName": "隔离租户",
|
||||
"lastName": "查看者",
|
||||
"email": "outsider@example.invalid",
|
||||
"emailVerified": true,
|
||||
"enabled": true,
|
||||
"credentials": [{"type": "password", "value": "LocalOnly123!", "temporary": false}]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: api
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-api
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: callbacks
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-callbacks
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-worker
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: web
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
minAvailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-web
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: api
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: api
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: callbacks
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: callbacks
|
||||
minReplicas: 2
|
||||
maxReplicas: 8
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: worker
|
||||
minReplicas: 2
|
||||
maxReplicas: 8
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: web
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: web
|
||||
minReplicas: 2
|
||||
maxReplicas: 6
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
@@ -0,0 +1,73 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: voice-platform-config
|
||||
namespace: voice-platform
|
||||
data:
|
||||
APP_ENV: stage
|
||||
LOG_LEVEL: info
|
||||
REDIS_ADDR: redis.example.invalid:6379
|
||||
REDIS_DB: "0"
|
||||
AUTH_ISSUER_URL: https://identity.example.invalid/realms/voice
|
||||
AUTH_JWKS_URL: https://identity.example.invalid/realms/voice/protocol/openid-connect/certs
|
||||
AUTH_AUDIENCE: voice-platform-web
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: otel-collector.monitoring.svc.cluster.local:4317
|
||||
DEPENDENCY_TIMEOUT: 3s
|
||||
EXTERNAL_PROBE_INTERVAL: 30s
|
||||
SHUTDOWN_TIMEOUT: 20s
|
||||
RATE_LIMIT_RPS: "50"
|
||||
RATE_LIMIT_BURST: "100"
|
||||
RATE_LIMIT_USER_RPS: "20"
|
||||
RATE_LIMIT_USER_BURST: "40"
|
||||
RATE_LIMIT_TENANT_RPS: "200"
|
||||
RATE_LIMIT_TENANT_BURST: "400"
|
||||
RATE_LIMIT_DEVICE_RPS: "10"
|
||||
RATE_LIMIT_DEVICE_BURST: "20"
|
||||
PROVIDER_START_RPS: "20"
|
||||
PROVIDER_START_BURST: "40"
|
||||
PROVIDER_MAX_ACTIVE_TASKS: "100"
|
||||
DEVICE_CLOCK_SKEW: 5m
|
||||
RTC_APP_ID: "000000000000000000000000"
|
||||
RTC_TOKEN_ISSUER: volcengine
|
||||
RTC_TOKEN_TTL: 5m
|
||||
CONVERSATION_ALLOCATION_TTL: 30s
|
||||
HEARTBEAT_INTERVAL_SECONDS: "15"
|
||||
PROVIDER_CALLBACK_CLOCK_SKEW: 5m
|
||||
PROVIDER_REQUEST_TIMEOUT: 5s
|
||||
PROVIDER_FAILURE_THRESHOLD: "3"
|
||||
PROVIDER_CIRCUIT_COOLDOWN: 1m
|
||||
RECOVERY_SCAN_INTERVAL: 5s
|
||||
RECOVERY_STARTING_AFTER: 20s
|
||||
RECOVERY_STOPPING_AFTER: 20s
|
||||
OBJECT_STORE_ENDPOINT: object-storage.example.invalid:443
|
||||
OBJECT_STORE_PUBLIC_ENDPOINT: downloads.example.invalid:443
|
||||
OBJECT_STORE_EXPORT_BUCKET: private-exports
|
||||
OBJECT_STORE_REGION: cn-beijing
|
||||
OBJECT_STORE_SECURE: "true"
|
||||
EXPORT_DOWNLOAD_TTL: 10m
|
||||
EXPORT_OBJECT_TTL: 24h
|
||||
VOICE_PROVIDER: volcengine
|
||||
VOLC_RTC_BASE_URL: https://rtc.volcengineapi.com
|
||||
VOLC_RTC_REGION: cn-north-1
|
||||
VOLC_RTC_API_VERSION: "2024-12-01"
|
||||
CONTENT_GENERATOR: ark
|
||||
CONTENT_SAFETY_PROVIDER: managed_http
|
||||
CONTENT_SAFETY_ENDPOINT: https://content-safety.example.invalid/v1/classify
|
||||
CONTENT_SAFETY_POLICY_REF: replace-with-approved-content-safety-policy
|
||||
CONTENT_SAFETY_TIMEOUT: 5s
|
||||
EMBEDDING_PROVIDER: ark
|
||||
ARK_BASE_URL: https://ark.cn-beijing.volces.com/api/v3
|
||||
ARK_CONTENT_MODEL: replace-with-ark-content-endpoint-id
|
||||
ARK_EMBEDDING_MODEL: replace-with-ark-embedding-endpoint-id
|
||||
ARK_REQUEST_TIMEOUT: 30s
|
||||
WORKER_POLL_INTERVAL: 2s
|
||||
ALERT_WEBHOOK_TIMEOUT: 5s
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: voice-platform-web-config
|
||||
namespace: voice-platform
|
||||
data:
|
||||
WEB_OIDC_AUTHORITY: https://identity.example.invalid/realms/voice
|
||||
WEB_OIDC_CLIENT_ID: voice-platform-web
|
||||
@@ -0,0 +1,452 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-api
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/path: /metrics
|
||||
prometheus.io/port: "8080"
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-api
|
||||
app.kubernetes.io/component: api
|
||||
spec:
|
||||
serviceAccountName: voice-platform-api
|
||||
automountServiceAccountToken: false
|
||||
terminationGracePeriodSeconds: 30
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-api
|
||||
containers:
|
||||
- name: api
|
||||
image: registry.example.invalid/voice-platform/backend:0.1.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/app/api"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: voice-platform-config
|
||||
env:
|
||||
- name: HTTP_ADDR
|
||||
value: 0.0.0.0:8080
|
||||
- name: DATABASE_URL_FILE
|
||||
value: /var/run/secrets/voice-platform/common/database-url
|
||||
- name: REDIS_PASSWORD_FILE
|
||||
value: /var/run/secrets/voice-platform/common/redis-password
|
||||
- name: DEVICE_SECRET_DIRECTORY
|
||||
value: /var/run/secrets/voice-platform/devices
|
||||
- name: RTC_APP_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/api/rtc-app-key
|
||||
- name: CURSOR_SIGNING_SECRET_FILE
|
||||
value: /var/run/secrets/voice-platform/api/cursor-signing-secret
|
||||
- name: SHARE_TOKEN_SIGNING_SECRET_FILE
|
||||
value: /var/run/secrets/voice-platform/api/share-token-signing-secret
|
||||
- name: OBJECT_STORE_ACCESS_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/model-storage/object-store-access-key
|
||||
- name: OBJECT_STORE_SECRET_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/model-storage/object-store-secret-key
|
||||
- name: ARK_API_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/model-storage/ark-api-key
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: http
|
||||
periodSeconds: 2
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: http
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: http
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 768Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
volumeMounts:
|
||||
- name: common-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/common
|
||||
readOnly: true
|
||||
- name: api-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/api
|
||||
readOnly: true
|
||||
- name: model-storage-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/model-storage
|
||||
readOnly: true
|
||||
- name: device-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/devices
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: common-secrets
|
||||
secret:
|
||||
secretName: voice-platform-common-secrets
|
||||
- name: api-secrets
|
||||
secret:
|
||||
secretName: voice-platform-api-secrets
|
||||
- name: model-storage-secrets
|
||||
secret:
|
||||
secretName: voice-platform-model-storage-secrets
|
||||
- name: device-secrets
|
||||
secret:
|
||||
secretName: voice-platform-device-secrets
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: callbacks
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-callbacks
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/path: /metrics
|
||||
prometheus.io/port: "8081"
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-callbacks
|
||||
app.kubernetes.io/component: callbacks
|
||||
spec:
|
||||
serviceAccountName: voice-platform-callbacks
|
||||
automountServiceAccountToken: false
|
||||
terminationGracePeriodSeconds: 30
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-callbacks
|
||||
containers:
|
||||
- name: callbacks
|
||||
image: registry.example.invalid/voice-platform/backend:0.1.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/app/callbacks"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: voice-platform-config
|
||||
env:
|
||||
- name: HTTP_ADDR
|
||||
value: 0.0.0.0:8081
|
||||
- name: DATABASE_URL_FILE
|
||||
value: /var/run/secrets/voice-platform/common/database-url
|
||||
- name: REDIS_PASSWORD_FILE
|
||||
value: /var/run/secrets/voice-platform/common/redis-password
|
||||
- name: PROVIDER_CALLBACK_SECRET_FILE
|
||||
value: /var/run/secrets/voice-platform/callbacks/provider-callback-secret
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8081
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: http
|
||||
periodSeconds: 2
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: http
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 2
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: http
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 2
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 384Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
volumeMounts:
|
||||
- name: common-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/common
|
||||
readOnly: true
|
||||
- name: callbacks-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/callbacks
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: common-secrets
|
||||
secret:
|
||||
secretName: voice-platform-common-secrets
|
||||
- name: callbacks-secrets
|
||||
secret:
|
||||
secretName: voice-platform-callbacks-secrets
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-worker
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/path: /metrics
|
||||
prometheus.io/port: "8082"
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-worker
|
||||
app.kubernetes.io/component: worker
|
||||
spec:
|
||||
serviceAccountName: voice-platform-worker
|
||||
automountServiceAccountToken: false
|
||||
terminationGracePeriodSeconds: 30
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-worker
|
||||
containers:
|
||||
- name: worker
|
||||
image: registry.example.invalid/voice-platform/backend:0.1.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/app/worker"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: voice-platform-config
|
||||
env:
|
||||
- name: HTTP_ADDR
|
||||
value: 0.0.0.0:8082
|
||||
- name: DATABASE_URL_FILE
|
||||
value: /var/run/secrets/voice-platform/common/database-url
|
||||
- name: REDIS_PASSWORD_FILE
|
||||
value: /var/run/secrets/voice-platform/common/redis-password
|
||||
- name: VOLC_ACCESS_KEY_ID_FILE
|
||||
value: /var/run/secrets/voice-platform/worker/volc-access-key-id
|
||||
- name: VOLC_SECRET_ACCESS_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/worker/volc-secret-access-key
|
||||
- name: VOLC_VOICE_CHAT_TEMPLATE_FILE
|
||||
value: /var/run/secrets/voice-platform/worker/voice-chat-template.json
|
||||
- name: OBJECT_STORE_ACCESS_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/model-storage/object-store-access-key
|
||||
- name: OBJECT_STORE_SECRET_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/model-storage/object-store-secret-key
|
||||
- name: ARK_API_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/model-storage/ark-api-key
|
||||
- name: CONTENT_SAFETY_API_KEY_FILE
|
||||
value: /var/run/secrets/voice-platform/model-storage/content-safety-api-key
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8082
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: http
|
||||
periodSeconds: 2
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: http
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 2
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: http
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 2
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 768Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
volumeMounts:
|
||||
- name: common-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/common
|
||||
readOnly: true
|
||||
- name: worker-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/worker
|
||||
readOnly: true
|
||||
- name: model-storage-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/model-storage
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: common-secrets
|
||||
secret:
|
||||
secretName: voice-platform-common-secrets
|
||||
- name: worker-secrets
|
||||
secret:
|
||||
secretName: voice-platform-worker-secrets
|
||||
- name: model-storage-secrets
|
||||
secret:
|
||||
secretName: voice-platform-model-storage-secrets
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-web
|
||||
app.kubernetes.io/component: web
|
||||
spec:
|
||||
serviceAccountName: voice-platform-web
|
||||
automountServiceAccountToken: false
|
||||
terminationGracePeriodSeconds: 30
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
fsGroup: 101
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: voice-platform-web
|
||||
containers:
|
||||
- name: web
|
||||
image: registry.example.invalid/voice-platform/web:0.1.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh", "-c"]
|
||||
args: ["/docker-entrypoint.d/40-runtime-config.sh && exec nginx -g 'daemon off;'"]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: voice-platform-web-config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /runtime-config.js
|
||||
port: http
|
||||
periodSeconds: 2
|
||||
failureThreshold: 15
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /runtime-config.js
|
||||
port: http
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 2
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /runtime-config.js
|
||||
port: http
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 2
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 192Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
sizeLimit: 32Mi
|
||||
@@ -0,0 +1,55 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: voice-platform
|
||||
namespace: voice-platform
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: 10m
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- voice-stage.example.invalid
|
||||
secretName: voice-platform-tls
|
||||
rules:
|
||||
- host: voice-stage.example.invalid
|
||||
http:
|
||||
paths:
|
||||
- path: /callbacks
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: callbacks
|
||||
port:
|
||||
number: 8081
|
||||
- path: /device
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 8080
|
||||
- path: /health
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 8080
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: web
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- configmap.yaml
|
||||
- serviceaccounts.yaml
|
||||
- deployments.yaml
|
||||
- services.yaml
|
||||
- ingress.yaml
|
||||
- availability.yaml
|
||||
- network-policies.yaml
|
||||
labels:
|
||||
- pairs:
|
||||
app.kubernetes.io/part-of: voice-platform
|
||||
includeSelectors: true
|
||||
includeTemplates: true
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: voice-platform
|
||||
labels:
|
||||
app.kubernetes.io/part-of: voice-platform
|
||||
pod-security.kubernetes.io/enforce: restricted
|
||||
pod-security.kubernetes.io/audit: restricted
|
||||
pod-security.kubernetes.io/warn: restricted
|
||||
@@ -0,0 +1,59 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny-ingress
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-ingress-controller
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/component
|
||||
operator: In
|
||||
values: [api, callbacks, web]
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
- protocol: TCP
|
||||
port: 8081
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-monitoring
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
podSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/component
|
||||
operator: In
|
||||
values: [api, callbacks, worker]
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: monitoring
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
- protocol: TCP
|
||||
port: 8081
|
||||
- protocol: TCP
|
||||
port: 8082
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: voice-platform-api
|
||||
namespace: voice-platform
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: voice-platform-callbacks
|
||||
namespace: voice-platform
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: voice-platform-worker
|
||||
namespace: voice-platform
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: voice-platform-web
|
||||
namespace: voice-platform
|
||||
automountServiceAccountToken: false
|
||||
@@ -0,0 +1,59 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api
|
||||
namespace: voice-platform
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-api
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: voice-platform-api
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: callbacks
|
||||
namespace: voice-platform
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-callbacks
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: voice-platform-callbacks
|
||||
ports:
|
||||
- name: http
|
||||
port: 8081
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: voice-platform
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-worker
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: voice-platform-worker
|
||||
ports:
|
||||
- name: http
|
||||
port: 8082
|
||||
targetPort: http
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web
|
||||
namespace: voice-platform
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-web
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: voice-platform-web
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
@@ -0,0 +1,62 @@
|
||||
# Copy this file into an environment-owned directory and replace every provider
|
||||
# and parameters block according to the installed Secrets Store CSI provider.
|
||||
# Every provider object must use the exact mounted filename documented below.
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: voice-platform-common
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
provider: replace-with-installed-provider
|
||||
parameters:
|
||||
objects: replace-with-provider-specific-database-url-and-redis-password
|
||||
---
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: voice-platform-api
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
provider: replace-with-installed-provider
|
||||
parameters:
|
||||
objects: replace-with-provider-specific-rtc-app-key-cursor-and-share-secrets
|
||||
---
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: voice-platform-callbacks
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
provider: replace-with-installed-provider
|
||||
parameters:
|
||||
objects: replace-with-provider-specific-callback-secret
|
||||
---
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: voice-platform-worker
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
provider: replace-with-installed-provider
|
||||
parameters:
|
||||
objects: replace-with-provider-specific-volc-credentials-and-voice-template
|
||||
---
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: voice-platform-model-storage
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
provider: replace-with-installed-provider
|
||||
parameters:
|
||||
objects: replace-with-provider-specific-object-store-ark-and-content-safety-credentials
|
||||
---
|
||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
kind: SecretProviderClass
|
||||
metadata:
|
||||
name: voice-platform-devices
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
provider: replace-with-installed-provider
|
||||
parameters:
|
||||
objects: replace-with-provider-specific-per-device-secrets
|
||||
@@ -0,0 +1,35 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../migration
|
||||
patches:
|
||||
- target:
|
||||
group: batch
|
||||
version: v1
|
||||
kind: Job
|
||||
name: voice-platform-migrate
|
||||
patch: |-
|
||||
- op: remove
|
||||
path: /spec/template/spec/containers/0/env
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/command
|
||||
value: ["/bin/sh", "-ec"]
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/args
|
||||
value:
|
||||
- 'database_url=""; IFS= read -r database_url < /var/run/secrets/voice-platform/common/database-url || [ -n "$database_url" ]; exec migrate -path=/migrations -database "$database_url" up'
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/volumeMounts
|
||||
value:
|
||||
- name: common-secrets
|
||||
mountPath: /var/run/secrets/voice-platform/common
|
||||
readOnly: true
|
||||
- op: add
|
||||
path: /spec/template/spec/volumes
|
||||
value:
|
||||
- name: common-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-common
|
||||
@@ -0,0 +1,52 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: voice-platform-migrate
|
||||
namespace: voice-platform
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-migrate
|
||||
app.kubernetes.io/part-of: voice-platform
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: 600
|
||||
ttlSecondsAfterFinished: 600
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: voice-platform-migrate
|
||||
app.kubernetes.io/part-of: voice-platform
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: migrate
|
||||
image: registry.example.invalid/voice-platform/migrate:0.1.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- -path=/migrations
|
||||
- -database=$(DATABASE_URL)
|
||||
- up
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: voice-platform-common-secrets
|
||||
key: database-url
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 192Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- job.yaml
|
||||
images:
|
||||
- name: registry.example.invalid/voice-platform/migrate
|
||||
newName: registry.example.invalid/voice-platform/migrate
|
||||
newTag: production
|
||||
@@ -0,0 +1,86 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- $patch: replace
|
||||
- name: common-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-common
|
||||
- name: api-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-api
|
||||
- name: model-storage-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-model-storage
|
||||
- name: device-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-devices
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: callbacks
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- $patch: replace
|
||||
- name: common-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-common
|
||||
- name: callbacks-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-callbacks
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: worker
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- $patch: replace
|
||||
- name: common-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-common
|
||||
- name: worker-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-worker
|
||||
- name: model-storage-secrets
|
||||
csi:
|
||||
driver: secrets-store.csi.k8s.io
|
||||
readOnly: true
|
||||
volumeAttributes:
|
||||
secretProviderClass: voice-platform-model-storage
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../production
|
||||
patches:
|
||||
- path: csi-volumes.yaml
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: voice-platform-config
|
||||
namespace: voice-platform
|
||||
data:
|
||||
APP_ENV: prod
|
||||
@@ -0,0 +1,49 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: voice-platform
|
||||
namespace: voice-platform
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- voice.example.invalid
|
||||
secretName: voice-platform-tls
|
||||
rules:
|
||||
- host: voice.example.invalid
|
||||
http:
|
||||
paths:
|
||||
- path: /callbacks
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: callbacks
|
||||
port:
|
||||
number: 8081
|
||||
- path: /device
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 8080
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 8080
|
||||
- path: /health
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 8080
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: web
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
images:
|
||||
- name: registry.example.invalid/voice-platform/backend
|
||||
newName: registry.example.invalid/voice-platform/backend
|
||||
newTag: production
|
||||
- name: registry.example.invalid/voice-platform/web
|
||||
newName: registry.example.invalid/voice-platform/web
|
||||
newTag: production
|
||||
patches:
|
||||
- path: config-patch.yaml
|
||||
- path: ingress-patch.yaml
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: voice-platform-config
|
||||
namespace: voice-platform
|
||||
data:
|
||||
APP_ENV: stage
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
images:
|
||||
- name: registry.example.invalid/voice-platform/backend
|
||||
newName: registry.example.invalid/voice-platform/backend
|
||||
newTag: staging
|
||||
- name: registry.example.invalid/voice-platform/web
|
||||
newName: registry.example.invalid/voice-platform/web
|
||||
newTag: staging
|
||||
patches:
|
||||
- path: config-patch.yaml
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM migrate/migrate:v4.18.3
|
||||
|
||||
COPY migrations /migrations
|
||||
@@ -0,0 +1,19 @@
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
|
||||
processors:
|
||||
batch: {}
|
||||
|
||||
exporters:
|
||||
debug:
|
||||
verbosity: basic
|
||||
|
||||
service:
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [debug]
|
||||
@@ -0,0 +1,12 @@
|
||||
global:
|
||||
scrape_interval: 10s
|
||||
evaluation_interval: 10s
|
||||
|
||||
rule_files:
|
||||
- /etc/prometheus/rules/*.yml
|
||||
|
||||
scrape_configs:
|
||||
- job_name: voice-platform
|
||||
metrics_path: /metrics
|
||||
static_configs:
|
||||
- targets: [api:8080, callbacks:8081, worker:8082]
|
||||
@@ -0,0 +1,96 @@
|
||||
groups:
|
||||
- name: data-terminal-cloud-availability
|
||||
rules:
|
||||
- alert: PlatformServiceDown
|
||||
expr: up{job="voice-platform"} == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Platform service is unavailable"
|
||||
description: "Prometheus has failed to scrape {{ $labels.instance }} for more than two minutes."
|
||||
|
||||
- alert: PlatformHighHTTP5xxRate
|
||||
expr: |
|
||||
(
|
||||
sum by (service) (rate(voice_platform_http_requests_total{status=~"5.."}[5m]))
|
||||
/
|
||||
clamp_min(sum by (service) (rate(voice_platform_http_requests_total[5m])), 0.01)
|
||||
) > 0.05
|
||||
and
|
||||
sum by (service) (rate(voice_platform_http_requests_total[5m])) > 0.1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "HTTP 5xx rate exceeds 5%"
|
||||
description: "{{ $labels.service }} has sustained a 5xx rate above 5%."
|
||||
|
||||
- alert: PlatformHighP95Latency
|
||||
expr: |
|
||||
histogram_quantile(
|
||||
0.95,
|
||||
sum by (service, route, le) (rate(voice_platform_http_request_duration_seconds_bucket[5m]))
|
||||
) > 1.5
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "HTTP p95 latency exceeds 1.5 seconds"
|
||||
description: "{{ $labels.service }} route {{ $labels.route }} has sustained high latency."
|
||||
|
||||
- alert: PlatformExternalDependencyUnavailable
|
||||
expr: voice_platform_dependency_up{dependency=~"oidc_jwks|ark_tls|volc_rtc_tls|content_safety_tls"} == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "External platform dependency is unavailable"
|
||||
description: "{{ $labels.service }} cannot reach or validate {{ $labels.dependency }} for more than five minutes."
|
||||
|
||||
- name: data-terminal-cloud-workflows
|
||||
rules:
|
||||
- alert: OutboxDeadLetterPresent
|
||||
expr: voice_platform_outbox_events{status="dead"} > 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Outbox dead-letter events require intervention"
|
||||
description: "At least one reliable background task exhausted its retry budget."
|
||||
|
||||
- alert: OutboxPendingBacklog
|
||||
expr: voice_platform_outbox_events{status="pending"} > 100
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Outbox pending backlog is growing"
|
||||
description: "More than 100 events have remained pending for ten minutes."
|
||||
|
||||
- alert: PrivacyDeletionPartialFailure
|
||||
expr: voice_platform_privacy_requests{status="PARTIAL_FAILED"} > 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "A privacy request has partially failed"
|
||||
description: "A subject-level privacy workflow requires retry or manual reconciliation."
|
||||
|
||||
- alert: FirmwareAssignmentFailure
|
||||
expr: voice_platform_firmware_assignments{status="FAILED"} > 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "A firmware assignment failed"
|
||||
description: "Pause the rollout, inspect the device result, and select a verified rollback release if necessary."
|
||||
|
||||
- alert: FirmwareRolloutStalled
|
||||
expr: voice_platform_firmware_stalled_rollouts > 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "A firmware rollout is stalled"
|
||||
description: "A running rollout has not changed for at least 15 minutes."
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"schema_version": "voice-chat-template-bundle-v1",
|
||||
"templates": {
|
||||
"s2s_pure": {
|
||||
"AgentConfig": {
|
||||
"TargetUserId": [],
|
||||
"UserId": "",
|
||||
"WelcomeMessage": "您好,我会陪您聊聊天。",
|
||||
"EnableConversationStateCallback": false
|
||||
},
|
||||
"Config": {
|
||||
"TemplateBodyMustBeReplaced": "replace-with-official-s2s-pure-Config-object"
|
||||
}
|
||||
},
|
||||
"s2s_hybrid": {
|
||||
"AgentConfig": {
|
||||
"TargetUserId": [],
|
||||
"UserId": "",
|
||||
"WelcomeMessage": "您好,我会陪您聊聊天。",
|
||||
"EnableConversationStateCallback": false
|
||||
},
|
||||
"Config": {
|
||||
"TemplateBodyMustBeReplaced": "replace-with-official-s2s-hybrid-Config-object"
|
||||
}
|
||||
},
|
||||
"cascade": {
|
||||
"AgentConfig": {
|
||||
"TargetUserId": [],
|
||||
"UserId": "",
|
||||
"WelcomeMessage": "您好,我会陪您聊聊天。",
|
||||
"EnableConversationStateCallback": false
|
||||
},
|
||||
"Config": {
|
||||
"TemplateBodyMustBeReplaced": "replace-with-official-cascade-Config-object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
json_escape() {
|
||||
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
||||
}
|
||||
|
||||
oidc_authority=$(json_escape "${WEB_OIDC_AUTHORITY:-http://localhost:58180/realms/voice-local}")
|
||||
oidc_client_id=$(json_escape "${WEB_OIDC_CLIENT_ID:-voice-platform-web}")
|
||||
|
||||
printf 'window.__VOICE_PLATFORM_CONFIG__ = {\n oidcAuthority: "%s",\n oidcClientId: "%s"\n};\n' \
|
||||
"$oidc_authority" "$oidc_client_id" > /tmp/runtime-config.js
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node:24-alpine AS build
|
||||
WORKDIR /src
|
||||
COPY web/package*.json ./
|
||||
RUN npm ci
|
||||
COPY openapi /openapi
|
||||
COPY web ./
|
||||
RUN npm run generate:api && npm run build
|
||||
|
||||
FROM nginxinc/nginx-unprivileged:1.30.4-alpine
|
||||
USER root
|
||||
RUN apk upgrade --no-cache
|
||||
COPY --chown=101:101 deploy/web.nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --chmod=755 --chown=101:101 deploy/web-runtime-config.sh /docker-entrypoint.d/40-runtime-config.sh
|
||||
COPY --chown=101:101 --from=build /src/dist /usr/share/nginx/html
|
||||
USER 101
|
||||
EXPOSE 8080
|
||||
@@ -0,0 +1,54 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /health/ {
|
||||
proxy_pass http://api:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Request-Id $request_id;
|
||||
}
|
||||
|
||||
location /assets/ {
|
||||
try_files $uri =404;
|
||||
expires 1y;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||
}
|
||||
|
||||
location = /index.html {
|
||||
expires -1;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; connect-src 'self' https: http://localhost:*; img-src 'self' data: blob: https:; media-src 'self' blob: https: http://localhost:*; style-src 'self'; script-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' https: http://localhost:*" always;
|
||||
}
|
||||
|
||||
location = /runtime-config.js {
|
||||
alias /tmp/runtime-config.js;
|
||||
expires -1;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user