Skip to main content

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). Supported dnsProvider value: cloudflare (the gateway accepts route53 but does not implement it yet):

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
Known limitation

The generated config references the DNS token as ${GOMA_CREDENTIALS_API_TOKEN}, but the current operator does not yet inject credentialsSecret into the gateway pod, so DNS-01 issuance does not work out of the box.

Multiple certificate providers​

note

certManager.providers, certManager.defaultProvider and the Route's tls.provider require an operator release newer than v0.2.0.

Declare named providers when different routes need different ACME accounts or challenge types. Each provider gets its own ACME account and storage file. A Route picks one with spec.tls.provider; routes that name none use defaultProvider.

spec:
certManager:
defaultProvider: letsencrypt
providers:
letsencrypt:
type: acme
acme:
email: ops@example.com
challengeType: http-01
letsencrypt-dns:
type: acme
acme:
email: ops@example.com
challengeType: dns-01
dnsProvider: cloudflare
credentialsSecret: cloudflare-credentials

When providers is set, the top-level provider / acme fields are ignored.

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 such as rate limiting share state across pods. See Distributed Instances.

apiVersion: gateway.jkaninda.dev/v1alpha1
kind: Gateway
metadata:
name: gateway
spec:
replicas: 3
server:
redis:
addr: redis.default.svc.cluster.local:6379
password: changeme

redis.password is a plain string rendered into the gateway ConfigMap; the Gateway CRD has no Secret reference for it.

Observability​

Prometheus metrics are off by default. Enable and optionally protect them (see Monitoring for the exposed metrics):

spec:
server:
monitoring:
enableMetrics: true
metricsPath: /metrics
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
Known limitation

The current operator does not yet mount the Secrets named by http.headersSecret or git.auth.secretName into the gateway pod, so authenticated HTTP and Git providers do not work out of the box.

Spec reference​

FieldTypeDescription
imagestringGateway container image. Default: jkaninda/goma-gateway:latest.
replicasint32Number of gateway pods. Default: 1.
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[].secretNamestring—Name of a kubernetes.io/tls Secret.
redis.addrstring—Redis host:port.
redis.passwordstring—Redis password (consider using a Secret).
monitoring.enableMetricsboolfalseExpose Prometheus metrics.
monitoring.metricsPathstring/metricsPath of the metrics endpoint.
monitoring.hoststring—Restrict /metrics to this Host header (/healthz and /readyz are never host-restricted).
monitoring.middleware.metrics[]string—Middleware CR names applied to /metrics.
networking.dnsCache.ttlint300DNS cache TTL in seconds (gateway default when unset).
networking.transport.maxIdleConnsint512Max idle connections.
networking.transport.maxIdleConnsPerHostint256Max idle connections per host.
networking.transport.maxConnsPerHostint256Max total connections per host.

The Gateway CRD exposes only the fields above. Other gateway settings — for example log.format, monitoring.visitorTTL, analytics, reload, or networking.dnsCache.resolver — are not part of the CRD; the API server rejects or prunes unknown fields.

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.
httpNodePortint32—NodePort for HTTP (type: NodePort only).
httpsNodePortint32—NodePort for HTTPS (type: NodePort only).
loadBalancerIPstring—Request a specific static IP (cloud-dependent).
loadBalancerSourceRanges[]string—Restrict access to specific CIDRs.
loadBalancerClassstring—Select a specific LB implementation (e.g. service.k8s.aws/nlb).
externalTrafficPolicyenumClusterCluster or Local (Local preserves client source IPs).
sessionAffinityenumNoneNone or ClientIP.
annotationsmap—Merged onto the Service (use for cloud LB tuning).
labelsmap—Merged onto the Service.
ipFamilyPolicyenum—SingleStack, PreferDualStack, or RequireDualStack.
ipFamilies[]string—List of IP families: IPv4, IPv6.

spec.certManager​

FieldTypeDefaultDescription
defaultProviderstring—Entry in providers used by Routes that set no tls.provider.
providers.<name>.typeenumacmeCurrently only acme is supported.
providers.<name>.acmeobject—Same fields as acme below.
providerenumacmeSingle-provider form. Currently only acme is supported. Prefer providers.
acme.emailstring—Required. 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.dnsProviderstring—DNS-01 provider: cloudflare (the only one implemented by the gateway).
acme.credentialsSecretstring—Secret with the DNS provider credentials (key apiToken).

spec.autoScaling​

FieldTypeDefaultDescription
enabledboolfalseWhether the HPA is created.
minReplicasint321Lower bound.
maxReplicasint3210Upper bound.
targetCPUUtilizationPercentageint32—Target average CPU.
targetMemoryUtilizationPercentageint32—Target 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. Default: 60s.
http.timeoutstringPer-request timeout. Default: 10s.
http.headersmapExtra request headers.
http.headersSecretstringSecret with header values referenced via ${VAR}.
http.insecureSkipVerifyboolSkip TLS verification of the endpoint.
http.retryAttemptsintRetries on failure. Default: 3.
http.retryDelaystringDelay between retries. Default: 2s.
http.cacheDirstringWhere fetched responses are cached.
git.enabledboolEnables the Git provider.
git.urlstringRepository URL.
git.branchstringBranch to check out. Default: main.
git.pathstringSubdirectory inside the repo.
git.intervalstringPull interval. Default: 60s.
git.cloneDirstringLocal clone directory.
git.authobjecttype (token/basic/ssh) and secretName. The Secret holds token, username/password, or ssh-privatekey.

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