Skip to main content
Version: 0.4.x

XTenantApp

Tenant application scaffold — provisions a Deployment + Service + optional IngressRoute + optional ServiceAccount for an application workload, following app.kubernetes.io/* label conventions.

tenant-app deliberately stops at the application workload. Vault-backed secrets and databases (XTenantDatabase) are provisioned separately and referenced via secretsFrom.

Groupplatform.wxops.cloud
KindXTenantApp
Pluralxtenantapps
ScopeCluster
API versionv1alpha1 (served, storage)
Composition functionfunction-kcl

What it composes

Composed resourceCondition
DeploymentAlways
Serviceservice.enabled: true (default)
IngressRoute (Traefik)ingress.enabled: true
TraefikService (weighted split)darlane.trafficWeight > 0
Certificate (cert-manager)ingress.tls.clusterIssuer set
ServiceAccountserviceAccount.create: true
PersistentVolumeClaimvolumes[].create: true
Darlane Deployment (<app>-dev)darlane.enabled: true

spec.parameters

Core identity

FieldTypeRequiredDefaultDescription
appNamestringyesApplication name. Used as Deployment/Service/IngressRoute name and app.kubernetes.io/name label.
namespacestringyesTarget namespace for all composed resources.
environmentstring"dev"One of dev, staging, prod. Applied as wxops.cloud/environment label — metadata for dashboards and Kyverno policies; does not affect composed resource behavior.
appFlavorstring"webapp"One of webapp, ai, ai-webapp, geo-webapp, search-webapp. Applied as wxops.cloud/app-flavor label for platform automation (e.g. selecting pgvector/postgis extensions). Does not affect this XR's composed resources.
templateIdstringScaffold template identifier. Applied as wxops.cloud/template-id annotation for catalog linking.
repository.urlstringSource repository URL. Applied as wxops.cloud/repo-url annotation.

Container

FieldTypeRequiredDefaultDescription
imagestringyesContainer image reference (repository:tag). Use the single-string form so ArgoCD Image Updater can target it via Kustomize image-transformer.
imagePullPolicystring"IfNotPresent"One of Always, IfNotPresent, Never.
imagePullSecretsarray<string>[]Names of existing kubernetes.io/dockerconfigjson Secrets in the target namespace.
replicasinteger1
containerPortinteger8080
terminationGracePeriodSecondsinteger30Seconds between SIGTERM and SIGKILL. Increase for apps with long-running requests or connection draining.
resourcesobjectrequests/limits × cpu/memory, passed through verbatim.
envarray<{name, value}>[]Plain (non-secret) environment variables.
envFromarray<{secretRef|configMapRef: {name}}>[]Additional envFrom sources, merged after secretsFrom-managed refs.
podAnnotationsobject{}Annotations on the pod template (e.g. Prometheus scraping).
deploymentAnnotationsobject{}Extra annotations on the Deployment metadata.
labelsobject{}Extra labels on all composed resources. Standard app.kubernetes.io/* and wxops.cloud/* labels always win — selectors depend on them.
commandarray<string>image defaultOverride container ENTRYPOINT.
argsarray<string>image defaultOverride container CMD.

rolloutStrategy

FieldTypeDefaultDescription
rolloutStrategy.typestring"RollingUpdate"One of RollingUpdate, Recreate.
rolloutStrategy.rollingUpdate.maxSurgestring|integer"25%"Only when type: RollingUpdate.
rolloutStrategy.rollingUpdate.maxUnavailablestring|integer"25%"Only when type: RollingUpdate.

serviceAccount

FieldTypeDefaultDescription
serviceAccount.createbooleanfalseIf true, compose a new ServiceAccount in the target namespace.
serviceAccount.namestringappNameSA name. When create: false, must reference an existing SA.
serviceAccount.annotationsobject{}Annotations on the created SA. Use for workload identity bindings (eks.amazonaws.com/role-arn, iam.gke.io/gcp-service-account). Ignored when create: false.

securityContext

Pod-level fields apply to all containers; container-level fields apply to the main app container (and darlane when enabled).

FieldTypeDefaultDescription
securityContext.runAsNonRootbooleanPod-level. Require all containers to run as non-root.
securityContext.runAsUserintegerPod-level. UID for all containers.
securityContext.runAsGroupintegerPod-level. Primary GID for all containers.
securityContext.fsGroupintegerPod-level. GID applied to mounted volumes.
securityContext.readOnlyRootFilesystembooleanContainer-level. Mount root filesystem read-only.
securityContext.allowPrivilegeEscalationbooleanContainer-level. Set false for hardened workloads.
securityContext.capabilities.droparray<string>[]Container-level. Capabilities to drop. Use ["ALL"] for the recommended baseline.
securityContext.capabilities.addarray<string>[]Container-level. Capabilities to add back (e.g. ["NET_BIND_SERVICE"]).

secretsFrom

Wires envFrom.secretRef entries by name. This XR only adds the reference — it does not provision or wait for the Secret. The Deployment will fail to start (CreateContainerConfigError) until referenced Secrets exist.

FieldTypeDefaultDescription
secretsFrom.app.enabledbooleanfalseAdds envFrom.secretRefsecretsFrom.app.secretName.
secretsFrom.app.secretNamestring"{appName}-env"Name of the app env Secret.
secretsFrom.database.enabledbooleanfalseAdds envFrom.secretRefsecretsFrom.database.secretName.
secretsFrom.database.secretNamestring"{appName}-db-creds"Name of the database credentials Secret, written by XTenantDatabase.

reloader

FieldTypeDefaultDescription
reloader.enabledbooleanfalseAdds reloader.stakater.com/auto: "true" to the Deployment. Triggers a rolling restart when referenced ConfigMaps/Secrets change. Requires Stakater Reloader in-cluster.

volumes

Mount volumes into the main app container. Supply exactly one discriminator per entry.

DiscriminatorSource
claimNamePersistentVolumeClaim — reference existing, or create with create: true
configMapNameNamed ConfigMap
secretNameNamed Secret
FieldTypeDefaultDescription
volumes[].namestringrequiredVolume name.
volumes[].mountPathstringrequiredAbsolute mount path inside the container.
volumes[].subPathstringSubpath within the volume.
volumes[].readOnlybooleanfalse
volumes[].claimNamestringPVC name. Mutually exclusive with configMapName/secretName.
volumes[].createbooleanfalseIf true, compose a new PVC named claimName.
volumes[].storageClassstringStorageClass for the new PVC. Required when create: true.
volumes[].sizestringPVC capacity (e.g. "10Gi"). Required when create: true.
volumes[].accessModesarray<string>["ReadWriteOnce"]Only used when create: true.
volumes[].configMapNamestringName of an existing ConfigMap. Mutually exclusive with claimName/secretName.
volumes[].secretNamestringName of an existing Secret. Mutually exclusive with claimName/configMapName.
volumes[].items[]arrayKey-to-path projections. Each entry: {key, path}.

service

FieldTypeDefaultDescription
service.enabledbooleantrue
service.typestring"ClusterIP"One of ClusterIP, NodePort, LoadBalancer.
service.portinteger80Service port. targetPort is always containerPort.

probes

HTTP GET probes against containerPort. liveness and readiness default to enabled when service.enabled is true — implementing GET /healthz and GET /readyz on containerPort is a golden-path contract for all scaffolded services.

FieldTypeDefaultDescription
probes.liveness.enabledbooleanservice.enabled
probes.liveness.pathstring"/healthz"
probes.liveness.initialDelaySecondsinteger10
probes.liveness.periodSecondsinteger10
probes.liveness.timeoutSecondsinteger1
probes.liveness.failureThresholdinteger3
probes.readiness.enabledbooleanservice.enabled
probes.readiness.pathstring"/readyz"
probes.readiness.initialDelaySecondsinteger5
probes.readiness.periodSecondsinteger10
probes.readiness.timeoutSecondsinteger1
probes.readiness.failureThresholdinteger3
probes.startup.enabledbooleanfalseGates liveness/readiness until the first successful check. Useful for slow-starting apps.
probes.startup.pathstring"/healthz"
probes.startup.periodSecondsinteger10
probes.startup.failureThresholdinteger30

ingress

ingress.enabled: true always emits a Traefik IngressRoute — never a standard Ingress. This is required for darlane.trafficWeight to switch between a plain Service and a TraefikService weighted split with zero downtime.

FieldTypeDefaultDescription
ingress.enabledbooleanfalseEmits a Traefik IngressRoute.
ingress.hoststringRequired when enabled: true. Used as the hostname in the Traefik Host(...) match rule on the IngressRoute.
ingress.pathstring"/"URL path prefix used in the Traefik PathPrefix(...) match rule.
ingress.annotationsobject{}Merged onto IngressRoute metadata.
ingress.tls.enabledbooleanfalseSets entryPoints: [websecure] and spec.tls.secretName.
ingress.tls.secretNamestring"{appName}-tls"TLS Secret name. When clusterIssuer is set, cert-manager writes into this name.
ingress.tls.clusterIssuerstringIf set (and tls.enabled), emits a cert-manager Certificate CR. cert-manager auto-provisions the Secret. Requires cert-manager and the named ClusterIssuer in-cluster.
ingress.auth.enabledbooleanfalseAdds auth-errors and forward-auth-redirect Traefik Middleware references — SSO via oauth2-proxy ForwardAuth. Both Middleware CRDs must exist in kube-system.

darlane — debug twin Deployment

When enabled, a second <appName>-dev Deployment is created alongside the main one — same image/env/secrets, scaled to 0 by default, with no Service/IngressRoute of its own. Scale it on-demand, route real traffic via trafficWeight for A/B testing, or use mirrord CLI directly against the pod for local development with real cluster env and secrets.

Full guide

Darlane Overview — exec, file sync, traffic mirroring, A/B testing, and SRE agent workflow.

Core

FieldTypeDefaultDescription
darlane.enabledbooleanfalse
darlane.replicasinteger0Scale to 1 on-demand to start the debug pod.
darlane.imagestringmain imageOptional image override.
darlane.commandarray<string>["sleep", "infinity"]Keeps the pod alive for kubectl exec/port-forward.
darlane.argsarray<string>Args override.
darlane.containerPortintegermain containerPortOverride when dev server starts on a different port (e.g. hot-reload on 3000 vs production on 8080).
darlane.envarray<{name,value}>[]Extra env vars layered on top of main app env. Darlane values win on key collision.
darlane.resourcesobjectIndependent resource requests/limits. Omit to inherit main app resources.
darlane.securityContextobjectApplied on top of main securityContext. readOnlyRootFilesystem defaults to false so apt/pip installs work.
darlane.productionOverridebooleanfalseRequired when environment: prod to enable darlane — double opt-in, visible in XR diffs.
darlane.ttlstringAdds wxops.cloud/darlane-ttl annotation (e.g. "4h"). Enforcement via separate Kyverno policy.

Traffic routing

Route a slice of real IngressRoute traffic to the darlane pod. Three controls compose freely.

FieldTypeDefaultDescription
darlane.trafficWeightinteger00 = debug only · 1–99 = A/B split · 100 = full canary. Emits a ClusterIP Service + Traefik TraefikService weighted split. Requires ingress.enabled: true.
darlane.stickySession.enabledbooleanfalsePin browser sessions to the same backend via a Traefik cookie. Requires trafficWeight > 0.
darlane.stickySession.cookieNamestring"darlane-ab"Override if multiple apps share the same domain.
darlane.stickySession.securebooleantrueSet the Secure flag (HTTPS only).
darlane.stickySession.sameSitestring"lax"lax · strict · none (requires secure: true).
darlane.headerRouting.enabledbooleanfalsePin requests with a specific header directly to darlane — bypasses trafficWeight entirely. Requires ingress.enabled: true.
darlane.headerRouting.headerstringHTTP header name (e.g. X-Target-Env). Required when enabled: true.
darlane.headerRouting.valuestringHeader value to match (e.g. darlane). Required when enabled: true.
darlane.telemetryPortintegerEmits {appName}-darlane-telemetry Service for OTEL/Prometheus. Common: 4317 (gRPC), 4318 (HTTP), 9090.

darlane.volumes

Same discriminator pattern as main volumes[] — set exactly one of claimName, configMapName, or secretName. PVC creation (create: true) is not supported in darlane volumes; reference existing claims only.

FieldTypeDefaultDescription
darlane.volumes[].namestringrequiredVolume name.
darlane.volumes[].mountPathstringrequiredAbsolute mount path.
darlane.volumes[].subPathstringSubpath within the volume.
darlane.volumes[].readOnlybooleanfalse
darlane.volumes[].claimNamestringName of an existing PVC.
darlane.volumes[].configMapNamestringName of an existing ConfigMap.
darlane.volumes[].secretNamestringName of an existing Secret.
darlane.volumes[].items[]arrayKey-to-path projections: {key, path}.

darlane.serviceAccount

FieldTypeDefaultDescription
darlane.serviceAccount.createbooleanfalseEmits a dedicated ServiceAccount named {appName}-darlane (or name).
darlane.serviceAccount.namestring{appName}-darlaneWhen create: false, must reference an existing SA.
darlane.serviceAccount.annotationsobject{}Workload identity bindings (eks.amazonaws.com/role-arn, iam.gke.io/gcp-service-account). Ignored when create: false.

Minimal example

apiVersion: platform.wxops.cloud/v1alpha1
kind: XTenantApp
metadata:
name: payment-api
spec:
parameters:
appName: payment-api
namespace: tenant-wxops
environment: dev
image: gitea.example.com/rocket-team/payment-api:dev-2025-01-15_10-30-00-abc1234
replicas: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
secretsFrom:
app:
enabled: true # envFrom → payment-api-env
database:
enabled: true # envFrom → payment-api-db-creds
ingress:
enabled: true
host: payment-api.dev.example.com
tls:
enabled: true
clusterIssuer: letsencrypt-prod
darlane:
enabled: true
trafficWeight: 10 # 10% of real traffic to debug pod

See also