generated from coulomb/repo-seed
feat(statehub): add railiance deployment manifests
This commit is contained in:
58
Makefile
58
Makefile
@@ -1,7 +1,17 @@
|
||||
.PHONY: install install-cli dashboard-install dashboard-check db db-tools migrate seed api dashboard check test test-python clean register-project register-codex-project register-mcp bootstrap-env validate-adr add-domain rename-domain add-repo list-repos register-path register-from-classification register-from-classification-all cleanup-stale tunnels-up tunnels-status tunnels-check bridges install-hooks install-hooks-all gitea-inventory token-reconcile
|
||||
.PHONY: install install-cli dashboard-install dashboard-check db db-tools migrate seed api dashboard check test test-python clean register-project register-codex-project register-mcp bootstrap-env validate-adr add-domain rename-domain add-repo list-repos register-path register-from-classification register-from-classification-all cleanup-stale tunnels-up tunnels-status tunnels-check bridges install-hooks install-hooks-all gitea-inventory token-reconcile railiance-state-hub-render railiance-state-hub-client-dry-run railiance-state-hub-server-dry-run
|
||||
|
||||
COMPOSE = docker compose -f infra/docker-compose.yml --env-file .env
|
||||
PYTHON ?= python3
|
||||
HELM ?= $(shell command -v helm 2>/dev/null || if [ -x "$$HOME/.local/bin/helm" ]; then printf "%s" "$$HOME/.local/bin/helm"; else printf "%s" "helm"; fi)
|
||||
KUBECTL ?= $(shell command -v kubectl 2>/dev/null || if [ -x "$$HOME/.local/bin/kubectl" ]; then printf "%s" "$$HOME/.local/bin/kubectl"; else printf "%s" "kubectl"; fi)
|
||||
|
||||
RAILIANCE_STATE_HUB_RELEASE ?= state-hub
|
||||
RAILIANCE_STATE_HUB_NAMESPACE ?= state-hub
|
||||
RAILIANCE_STATE_HUB_CHART ?= deploy/railiance/apps/charts/state-hub
|
||||
RAILIANCE_STATE_HUB_VALUES ?= deploy/railiance/apps/helm/state-hub-values.yaml
|
||||
RAILIANCE_STATE_HUB_IMAGE_TAG ?= b536741
|
||||
RAILIANCE_STATE_HUB_PLATFORM_DIR ?= deploy/railiance/platform
|
||||
RAILIANCE_STATE_HUB_APP_MANIFESTS ?= deploy/railiance/apps/manifests
|
||||
# Codex/WSL non-login shells may not source ~/.profile; keep uv discoverable.
|
||||
UV ?= $(shell command -v uv 2>/dev/null || if [ -x "$$HOME/.local/bin/uv" ]; then printf "%s" "$$HOME/.local/bin/uv"; else printf "%s" "uv"; fi)
|
||||
|
||||
@@ -61,6 +71,52 @@ dashboard:
|
||||
check:
|
||||
curl -sf http://127.0.0.1:8000/state/health | python3 -m json.tool
|
||||
|
||||
railiance-state-hub-render:
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG)
|
||||
|
||||
railiance-state-hub-client-dry-run:
|
||||
@set -e; \
|
||||
tmpdir="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$tmpdir"' EXIT; \
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -f "$$tmpdir/state-hub.yaml"
|
||||
|
||||
railiance-state-hub-server-dry-run:
|
||||
@set -e; \
|
||||
tmpdir="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$tmpdir"' EXIT; \
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) \
|
||||
--show-only templates/namespace.yaml > "$$tmpdir/state-hub-namespace.yaml"; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \
|
||||
$(KUBECTL) apply --dry-run=server -f "$$tmpdir/state-hub-namespace.yaml"; \
|
||||
if $(KUBECTL) get namespace $(RAILIANCE_STATE_HUB_NAMESPACE) >/dev/null 2>&1; then \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=server -f "$$tmpdir/state-hub.yaml"; \
|
||||
else \
|
||||
echo "Namespace $(RAILIANCE_STATE_HUB_NAMESPACE) does not exist; validating namespaced app manifests with client dry-run."; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -f "$$tmpdir/state-hub.yaml"; \
|
||||
fi
|
||||
|
||||
test: test-python dashboard-check
|
||||
|
||||
test-python:
|
||||
|
||||
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
|
||||
@@ -225,8 +225,9 @@ and verified railiance01 can pull it with `sudo crictl pull`.
|
||||
|
||||
```task
|
||||
id: CUST-WP-0011-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
completed: "2026-06-25"
|
||||
state_hub_task_id: "a7baf2eb-abd7-4aa3-b2cb-a5370ac09844"
|
||||
```
|
||||
|
||||
@@ -241,6 +242,24 @@ Create the cluster-side deployment assets using current Railiance boundaries:
|
||||
**Done when:** manifests lint/apply in a non-destructive dry run and ownership
|
||||
boundaries are documented.
|
||||
|
||||
Completed 2026-06-25: added a source-owned Railiance deployment handoff under
|
||||
`deploy/railiance/` with platform manifests for `state-hub-db` CNPG, database
|
||||
credentials, database NetworkPolicies, an app Helm chart, production values, and
|
||||
a `state-hub-env` Secret template. Added Make targets for rendering,
|
||||
client-side dry-run validation, and namespace-aware server-side dry-run
|
||||
validation. Verified:
|
||||
|
||||
- `make railiance-state-hub-render`
|
||||
- `make railiance-state-hub-client-dry-run`
|
||||
- `make railiance-state-hub-server-dry-run`
|
||||
|
||||
The server dry-run validates platform resources and the Namespace manifest
|
||||
against the live cluster API. Because the `state-hub` namespace does not yet
|
||||
exist, it explicitly falls back to client dry-run for namespaced app manifests;
|
||||
Kubernetes cannot persist a dry-run Namespace for subsequent namespaced
|
||||
server-side validation. Ownership boundaries and promotion notes are documented
|
||||
in `deploy/railiance/README.md`.
|
||||
|
||||
---
|
||||
|
||||
### T05 — Deploy empty State Hub and run migrations on railiance01
|
||||
|
||||
Reference in New Issue
Block a user