generated from coulomb/repo-seed
feat(statehub): add railiance deployment manifests
This commit is contained in:
88
deploy/railiance/README.md
Normal file
88
deploy/railiance/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# State Hub Railiance Deployment Handoff
|
||||
|
||||
This directory contains the State Hub deployment handoff for `CUST-WP-0011`.
|
||||
It is source-owned by `state-hub` and split along the Railiance ownership
|
||||
boundaries used for the actual cluster rollout.
|
||||
|
||||
## Ownership
|
||||
|
||||
- `deploy/railiance/platform/` is the `railiance-platform` handoff for the
|
||||
`state-hub-db` CloudNativePG cluster, database bootstrap credential, and
|
||||
database NetworkPolicies in the `databases` namespace.
|
||||
- `deploy/railiance/apps/` is the `railiance-apps` handoff for the State Hub API
|
||||
Helm chart, non-secret production values, and app namespace runtime Secret
|
||||
template.
|
||||
- Runtime secret values are not stored here. Replace placeholder passwords only
|
||||
in an operator-controlled file, then encrypt or deliver through the approved
|
||||
platform secret path.
|
||||
|
||||
## Image
|
||||
|
||||
The current image is pinned to:
|
||||
|
||||
```text
|
||||
gitea.coulomb.social/coulomb/state-hub:b536741
|
||||
```
|
||||
|
||||
railiance01 has already pulled this tag with `crictl`, and the image serves
|
||||
`GET /state/health` against the local WSL database in smoke testing.
|
||||
|
||||
## Render And Dry-Run
|
||||
|
||||
Render the app chart without touching the cluster:
|
||||
|
||||
```bash
|
||||
make railiance-state-hub-render
|
||||
```
|
||||
|
||||
Run client-side Kubernetes validation for the platform manifests, app Secret
|
||||
template, and rendered chart:
|
||||
|
||||
```bash
|
||||
make railiance-state-hub-client-dry-run
|
||||
```
|
||||
|
||||
Run server-side dry-run against the configured representative cluster:
|
||||
|
||||
```bash
|
||||
KUBECONFIG=~/.kube/config-hosteurope make railiance-state-hub-server-dry-run
|
||||
```
|
||||
|
||||
Server-side dry-run requires the CNPG CRDs, namespace permissions, and dry-run
|
||||
permission for resources in `databases` and `state-hub`.
|
||||
Before the `state-hub` namespace exists, Kubernetes cannot server-dry-run namespaced app
|
||||
objects into that namespace because dry-run Namespace creation is not persisted.
|
||||
The Make target therefore server-validates the platform and Namespace manifests,
|
||||
then falls back to client dry-run for namespaced app manifests with an explicit
|
||||
notice.
|
||||
|
||||
## Promotion Notes
|
||||
|
||||
Platform promotion into `railiance-platform`:
|
||||
|
||||
- copy `platform/state-hub-db-credentials.sops.yaml.template` to a real SOPS
|
||||
secret file with an operator-generated password;
|
||||
- apply or GitOps-manage `platform/state-hub-db-cluster.yaml`;
|
||||
- apply or GitOps-manage `platform/state-hub-db-networkpolicies.yaml`.
|
||||
|
||||
App promotion into `railiance-apps`:
|
||||
|
||||
- copy `apps/charts/state-hub/` to `charts/state-hub/`;
|
||||
- copy `apps/helm/state-hub-values.yaml` to `helm/state-hub-values.yaml`;
|
||||
- create `state-hub-env` in the `state-hub` namespace from the approved
|
||||
secret-delivery path;
|
||||
- deploy with Helm only after `state-hub-db` is healthy.
|
||||
|
||||
## Runtime Secret Contract
|
||||
|
||||
The app chart expects a Kubernetes Secret named `state-hub-env` in the
|
||||
`state-hub` namespace with at least:
|
||||
|
||||
```text
|
||||
DATABASE_URL=postgresql+asyncpg://state_hub:<url-encoded-password>@state-hub-db-rw.databases.svc.cluster.local:5432/state_hub
|
||||
```
|
||||
|
||||
Optional runtime settings such as `CORS_ORIGINS` can live in the chart
|
||||
ConfigMap. The default chart keeps public ingress disabled; access should use
|
||||
the existing private tunnel/ops-bridge path until a separate exposure decision
|
||||
is recorded.
|
||||
6
deploy/railiance/apps/charts/state-hub/Chart.yaml
Normal file
6
deploy/railiance/apps/charts/state-hub/Chart.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: state-hub
|
||||
description: State Hub API service for private Railiance operation
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "b536741"
|
||||
@@ -0,0 +1,26 @@
|
||||
{{- define "statehub.fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s" $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "statehub.labels" -}}
|
||||
app: {{ include "statehub.fullname" . }}
|
||||
app.kubernetes.io/name: {{ include "statehub.fullname" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
|
||||
railiance.io/layer: s5-app
|
||||
{{- end -}}
|
||||
|
||||
{{- define "statehub.selectorLabels" -}}
|
||||
app: {{ include "statehub.fullname" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "statehub.image" -}}
|
||||
{{- if not .Values.image.tag -}}
|
||||
{{- fail "image.tag is required - pin it in deploy/railiance/apps/helm/state-hub-values.yaml or pass --set image.tag=<sha>" -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,9 @@
|
||||
{{- if .Values.config.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.config.name }}
|
||||
labels: {{- include "statehub.labels" . | nindent 4 }}
|
||||
data:
|
||||
CORS_ORIGINS: {{ .Values.config.corsOrigins | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,66 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "statehub.fullname" . }}
|
||||
labels: {{- include "statehub.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels: {{- include "statehub.selectorLabels" . | nindent 6 }}
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels: {{- include "statehub.labels" . | nindent 8 }}
|
||||
spec:
|
||||
securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: state-hub
|
||||
image: {{ include "statehub.image" . | quote }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext: {{- toYaml .Values.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.targetPort }}
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
{{- if .Values.config.enabled }}
|
||||
- configMapRef:
|
||||
name: {{ .Values.config.name | quote }}
|
||||
{{- end }}
|
||||
- secretRef:
|
||||
name: {{ .Values.secret.name | quote }}
|
||||
{{- if .Values.probes.enabled }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.path }}
|
||||
port: {{ .Values.probes.port }}
|
||||
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.path }}
|
||||
port: {{ .Values.probes.port }}
|
||||
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
|
||||
{{- end }}
|
||||
resources: {{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,28 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "statehub.fullname" . }}
|
||||
labels: {{- include "statehub.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- toYaml .Values.ingress.annotations | nindent 4 }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.ingress.host }}
|
||||
secretName: {{ include "statehub.fullname" . }}-tls
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.ingress.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "statehub.fullname" . }}
|
||||
port:
|
||||
number: {{ .Values.service.port }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,8 @@
|
||||
{{- if .Values.namespace.create }}
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- toYaml .Values.namespace.labels | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "statehub.fullname" . }}
|
||||
labels: {{- include "statehub.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: {{ .Values.service.targetPort }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector: {{- include "statehub.selectorLabels" . | nindent 4 }}
|
||||
67
deploy/railiance/apps/charts/state-hub/values.yaml
Normal file
67
deploy/railiance/apps/charts/state-hub/values.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
image:
|
||||
repository: gitea.coulomb.social/coulomb/state-hub
|
||||
tag: ""
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
namespace:
|
||||
create: true
|
||||
labels:
|
||||
railiance.io/postgres-client: state-hub-db
|
||||
railiance.io/layer: s5-app
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
|
||||
config:
|
||||
enabled: true
|
||||
name: state-hub-config
|
||||
corsOrigins: "http://localhost:3000,http://127.0.0.1:3000,http://localhost:3001,http://127.0.0.1:3001"
|
||||
|
||||
secret:
|
||||
name: state-hub-env
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: traefik
|
||||
host: state-hub.coulomb.social
|
||||
tls: true
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
|
||||
probes:
|
||||
enabled: true
|
||||
path: /state/health
|
||||
port: 8000
|
||||
liveness:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readiness:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
podSecurityContext: {}
|
||||
securityContext: {}
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
8
deploy/railiance/apps/helm/state-hub-values.yaml
Normal file
8
deploy/railiance/apps/helm/state-hub-values.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# Production values for the State Hub Railiance chart handoff.
|
||||
# Non-secret values only. DATABASE_URL comes from the Secret `state-hub-env`.
|
||||
|
||||
image:
|
||||
tag: "b536741"
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
@@ -0,0 +1,18 @@
|
||||
# Template for the State Hub runtime Secret in the state-hub namespace.
|
||||
# DO NOT commit this file with real credentials.
|
||||
# Encrypt with: sops -e -i state-hub-env.sops.yaml
|
||||
# Apply with: kubectl apply -f <(sops -d state-hub-env.sops.yaml)
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: state-hub-env
|
||||
namespace: state-hub
|
||||
labels:
|
||||
app.kubernetes.io/name: state-hub
|
||||
app.kubernetes.io/component: runtime-env
|
||||
app.kubernetes.io/managed-by: manual
|
||||
railiance.io/layer: s5-app
|
||||
type: Opaque
|
||||
stringData:
|
||||
DATABASE_URL: postgresql+asyncpg://state_hub:REPLACE_WITH_URL_ENCODED_PASSWORD@state-hub-db-rw.databases.svc.cluster.local:5432/state_hub
|
||||
28
deploy/railiance/platform/state-hub-db-cluster.yaml
Normal file
28
deploy/railiance/platform/state-hub-db-cluster.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
# Dedicated CNPG Cluster for State Hub episodic memory.
|
||||
# Owned by railiance-platform (S3). Operator lives in cnpg-system.
|
||||
#
|
||||
# Pre-condition: state-hub-db-credentials Secret exists in databases namespace.
|
||||
# Runtime app Secret is separate and lives in the state-hub namespace.
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: state-hub-db
|
||||
namespace: databases
|
||||
labels:
|
||||
app.kubernetes.io/name: state-hub-db
|
||||
app.kubernetes.io/component: database
|
||||
app.kubernetes.io/managed-by: manual
|
||||
railiance.io/layer: s3-platform
|
||||
railiance.io/role: state-hub-database
|
||||
spec:
|
||||
instances: 1
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16
|
||||
storage:
|
||||
size: 10Gi
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: state_hub
|
||||
owner: state_hub
|
||||
secret:
|
||||
name: state-hub-db-credentials
|
||||
@@ -0,0 +1,19 @@
|
||||
# Template for the state-hub-db bootstrap Secret.
|
||||
# DO NOT commit this file with real credentials.
|
||||
# Encrypt with: sops -e -i state-hub-db-credentials.sops.yaml
|
||||
# Apply with: kubectl apply -f <(sops -d state-hub-db-credentials.sops.yaml)
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: state-hub-db-credentials
|
||||
namespace: databases
|
||||
labels:
|
||||
app.kubernetes.io/name: state-hub-db
|
||||
app.kubernetes.io/component: database-bootstrap
|
||||
app.kubernetes.io/managed-by: manual
|
||||
railiance.io/layer: s3-platform
|
||||
type: kubernetes.io/basic-auth
|
||||
stringData:
|
||||
username: state_hub
|
||||
password: REPLACE_WITH_PASSWORD
|
||||
74
deploy/railiance/platform/state-hub-db-networkpolicies.yaml
Normal file
74
deploy/railiance/platform/state-hub-db-networkpolicies.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
# NetworkPolicies for the dedicated State Hub CNPG cluster.
|
||||
# Namespaces that need database access must carry:
|
||||
# railiance.io/postgres-client: state-hub-db
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-egress-kube-api-state-hub-db
|
||||
namespace: databases
|
||||
labels:
|
||||
app.kubernetes.io/name: state-hub-db
|
||||
railiance.io/layer: s3-platform
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
cnpg.io/cluster: state-hub-db
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector: {}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6443
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-ingress-from-cnpg-operator-state-hub-db
|
||||
namespace: databases
|
||||
labels:
|
||||
app.kubernetes.io/name: state-hub-db
|
||||
railiance.io/layer: s3-platform
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
cnpg.io/cluster: state-hub-db
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: cnpg-system
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
- protocol: TCP
|
||||
port: 8000
|
||||
- protocol: TCP
|
||||
port: 9187
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-ingress-from-state-hub-namespace-state-hub-db
|
||||
namespace: databases
|
||||
labels:
|
||||
app.kubernetes.io/name: state-hub-db
|
||||
railiance.io/layer: s3-platform
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
cnpg.io/cluster: state-hub-db
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
railiance.io/postgres-client: state-hub-db
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
Reference in New Issue
Block a user