Skip to main content
Version: v0.15

Gateway

The Gateway custom resource is the platform-level entry point. A single Gateway object reconciles into a complete runtime stack: a Deployment running Goma Gateway, a Service exposing it, a ConfigMap holding its static configuration, and — when enabled — an HorizontalPodAutoscaler and the goma-k8s-provider sidecar that hot-reloads Route and Middleware changes.

  • API group: gateway.jkaninda.dev
  • Version: v1alpha1
  • Kind: Gateway

How it works

When a Gateway is applied, the operator creates the following resources, all named after the Gateway:

ResourcePurpose
DeploymentRuns the gateway container (and the goma-k8s-provider sidecar by default).
ServiceExposes container ports 8080 (HTTP) and 8443 (HTTPS). Configurable via spec.service.
ConfigMapHolds the static portion of the gateway config.
HorizontalPodAutoscalerCreated when spec.autoScaling.enabled: true.

The container always listens on 8080 and 8443. The Service ports are independent — set spec.service.httpPort: 80 and httpsPort: 443 for Ingress-style exposure on standard ports.

Minimal example

The smallest valid Gateway. Defaults are sensible: a single replica, ClusterIP Service, and the K8s provider sidecar enabled.

apiVersion: gateway.jkaninda.dev/v1alpha1
kind: Gateway
metadata:
name: gateway
namespace: default
spec:
image: jkaninda/goma-gateway:latest
replicas: 1
server:
logLevel: info

Exposing the gateway

The spec.service block controls how the gateway is reached from outside the cluster.

LoadBalancer (cloud)

spec:
replicas: 2
service:
type: LoadBalancer
httpPort: 80
httpsPort: 443
externalTrafficPolicy: Local # preserve client source IPs
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb

NodePort (bare metal / dev)

spec:
service:
type: NodePort
httpPort: 8080
httpsPort: 8443
httpNodePort: 30080
httpsNodePort: 30443

Ingress in front

Leave the Service as the default ClusterIP and route to it from your existing Ingress controller. Ports remain 8080 / 8443.

TLS

Goma Gateway supports two TLS strategies, which can be combined.

1. Bring-your-own certificates

Reference one or more Kubernetes TLS secrets (type: kubernetes.io/tls) in spec.server.tls:

spec:
server:
tls:
- secretName: example-com-tls
- secretName: api-example-com-tls

2. Built-in ACME / Let's Encrypt

Enable the gateway's certificate manager and issue certs automatically.

HTTP-01 (gateway must be publicly reachable on port 80):

spec:
service:
type: LoadBalancer
httpPort: 80
httpsPort: 443
certManager:
provider: acme
acme:
email: ops@example.com
termsAccepted: true
challengeType: http-01
# For testing, switch to staging to avoid rate limits:
# directoryUrl: https://acme-staging-v02.api.letsencrypt.org/directory

DNS-01 (required for wildcard certs, no public ingress needed):

apiVersion: v1
kind: Secret
metadata:
name: cloudflare-credentials
type: Opaque
stringData:
apiToken: REPLACE_ME
---
apiVersion: gateway.jkaninda.dev/v1alpha1
kind: Gateway
metadata:
name: gateway-wildcard
spec:
certManager:
provider: acme
acme:
email: ops@example.com
termsAccepted: true
challengeType: dns-01
dnsProvider: cloudflare
credentialsSecret: cloudflare-credentials

Scaling

Static replicas

spec:
replicas: 3

Horizontal Pod Autoscaler

Requires the metrics-server to be installed in the cluster.

spec:
replicas: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1
memory: 512Mi
autoScaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80

Shared state with Redis

When running multiple replicas, Redis lets stateful middlewares (rate limiting, ACME store coordination) share state across pods.

apiVersion: v1
kind: Secret
metadata:
name: redis-auth
type: Opaque
stringData:
password: changeme
---
apiVersion: gateway.jkaninda.dev/v1alpha1
kind: Gateway
metadata:
name: gateway
spec:
replicas: 3
server:
redis:
addr: redis.default.svc.cluster.local:6379
password: changeme

Observability

Prometheus metrics are off by default. Enable and optionally protect them:

spec:
server:
monitoring:
enableMetrics: true
metricsPath: /metrics
visitorTTL: 5m
host: metrics.internal.example.com # restrict by Host header
middleware:
metrics:
- metrics-basic-auth # protect /metrics with a Middleware CR

/healthz and /readyz are always enabled — the operator wires them to the pod's liveness and readiness probes.

Dynamic configuration providers

By default the operator injects the goma-k8s-provider sidecar, which watches Route and Middleware CRs and hot-reloads them into the gateway. You can disable it (in which case routes are delivered through the static ConfigMap, requiring a pod restart on changes) or use HTTP / Git providers instead.

spec:
providers:
kubernetes:
enabled: true # default
image: jkaninda/goma-k8s-provider:latest
http:
enabled: false
endpoint: https://config.example.com/goma.yaml
interval: 60s
git:
enabled: false
url: https://github.com/example/gateway-config.git
branch: main
path: config
interval: 60s
auth:
type: token
secretName: git-credentials

Spec reference

FieldTypeDescription
imagestringGateway container image. Default: jkaninda/goma-gateway:latest.
replicasint32Number of gateway pods (ignored when autoScaling.enabled: true).
imagePullSecrets[]LocalObjectReferenceSecrets used to pull the gateway image.
resourcesResourceRequirementsCPU/memory requests and limits for the gateway container.
affinitycorev1.AffinityPod scheduling constraints.
autoScalingobjectHPA configuration (see below).
serverobjectServer runtime configuration (see below).
serviceobjectKubernetes Service exposure (see below).
certManagerobjectBuilt-in ACME / Let's Encrypt certificate manager.
providersobjectDynamic configuration providers (Kubernetes sidecar, HTTP, Git).

spec.server

FieldTypeDefaultDescription
logLevelenuminfoOne of info, debug, trace, off.
timeouts.readint30Read timeout in seconds.
timeouts.writeint60Write timeout in seconds.
timeouts.idleint90Idle timeout in seconds.
tls[].secretNamestringName of a kubernetes.io/tls Secret.
redis.addrstringRedis host:port.
redis.passwordstringRedis password (consider using a Secret).
monitoring.enableMetricsboolfalseExpose Prometheus metrics.
monitoring.metricsPathstring/metricsPath of the metrics endpoint.
monitoring.visitorTTLstring5mHow long a visitor counts towards the real-time visitors gauge.
monitoring.hoststringRestrict metrics endpoints to this Host header.
monitoring.middleware.metrics[]stringMiddleware CR names applied to /metrics.
networking.dnsCache.ttlint300DNS cache TTL in seconds.
networking.dnsCache.clearOnReloadboolfalseFlush the local DNS cache after the routes are reloaded (auto-reload / config changes).
networking.dnsCache.resolver[]stringCustom DNS server addresses (e.g. 1.1.1.1, 8.8.8.8:53). Empty uses the system resolver. Applied at startup.
networking.transport.maxIdleConnsint512Max idle connections.
networking.transport.maxIdleConnsPerHostint256Max idle connections per host.
networking.transport.maxConnsPerHostint256Max total connections per host.
reload.enabledboolfalseExpose the token-protected on-demand config reload endpoint.
reload.pathstring/gateway/reloadPath of the reload endpoint.
reload.tokenstringBearer token required (Authorization: Bearer <token>). Prefer the GOMA_RELOAD_TOKEN env var.
reload.hoststringRestrict the reload endpoint to this Host header.

On-demand reload. These fields expose a token-protected endpoint that reloads the gateway configuration immediately. See On-Demand Reload in the User Manual for the endpoint path, request format, and response codes.

spec.service

FieldTypeDefaultDescription
typeenumClusterIPClusterIP, NodePort, or LoadBalancer.
httpPortint328080Service-level HTTP port. Set to 80 for Ingress-style exposure.
httpsPortint328443Service-level HTTPS port. Set to 443 for Ingress-style exposure.
httpNodePortint32NodePort for HTTP (type: NodePort only).
httpsNodePortint32NodePort for HTTPS (type: NodePort only).
loadBalancerIPstringRequest a specific static IP (cloud-dependent).
loadBalancerSourceRanges[]stringRestrict access to specific CIDRs.
loadBalancerClassstringSelect a specific LB implementation (e.g. service.k8s.aws/nlb).
externalTrafficPolicyenumClusterCluster or Local (Local preserves client source IPs).
sessionAffinityenumNoneNone or ClientIP.
annotationsmapMerged onto the Service (use for cloud LB tuning).
labelsmapMerged onto the Service.
ipFamilyPolicyenumSingleStack, PreferDualStack, or RequireDualStack.
ipFamilies[]stringList of IP families: IPv4, IPv6.

spec.certManager

FieldTypeDefaultDescription
providerenumacmeCurrently only acme is supported.
acme.emailstringRequired. Contact email for the ACME account.
acme.directoryUrlstringLet's Encrypt prodACME directory endpoint.
acme.termsAcceptedbooltrueAcceptance of the ACME provider's ToS.
acme.challengeTypeenumhttp-01http-01 or dns-01 (use dns-01 for wildcards).
acme.dnsProviderstringDNS-01 provider (e.g. cloudflare, route53).
acme.credentialsSecretstringSecret name containing DNS provider credentials.

spec.autoScaling

FieldTypeDefaultDescription
enabledboolfalseWhether the HPA is created.
minReplicasint321Lower bound.
maxReplicasint3210Upper bound.
targetCPUUtilizationPercentageint32Target average CPU.
targetMemoryUtilizationPercentageint32Target average memory.

spec.providers

FieldTypeDescription
kubernetes.enabledboolEnables the goma-k8s-provider sidecar (default: true).
kubernetes.imagestringSidecar image. Default: jkaninda/goma-k8s-provider:latest.
http.enabledboolEnables a remote HTTP provider.
http.endpointstringURL of the remote config.
http.intervalstringPull interval (e.g. 60s).
http.headersSecretstringSecret with header values referenced via ${VAR}.
git.enabledboolEnables the Git provider.
git.urlstringRepository URL.
git.branchstringBranch to check out.
git.pathstringSubdirectory inside the repo.
git.authobjecttype (token/basic/ssh) and secretName.

Status

kubectl get gateway shows the address (when a LoadBalancer is provisioned), replica counts, and route count:

NAME TYPE ADDRESS REPLICAS READY ROUTES AGE
gateway LoadBalancer 34.120.10.42 3 3 5 4m

Inspect full conditions with:

kubectl describe gateway gateway