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:
| Resource | Purpose |
|---|---|
Deployment | Runs the gateway container (and the goma-k8s-provider sidecar by default). |
Service | Exposes container ports 8080 (HTTP) and 8443 (HTTPS). Configurable via spec.service. |
ConfigMap | Holds the static portion of the gateway config. |
HorizontalPodAutoscaler | Created 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
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
| Field | Type | Description |
|---|---|---|
image | string | Gateway container image. Default: jkaninda/goma-gateway:latest. |
replicas | int32 | Number of gateway pods (ignored when autoScaling.enabled: true). |
imagePullSecrets | []LocalObjectReference | Secrets used to pull the gateway image. |
resources | ResourceRequirements | CPU/memory requests and limits for the gateway container. |
affinity | corev1.Affinity | Pod scheduling constraints. |
autoScaling | object | HPA configuration (see below). |
server | object | Server runtime configuration (see below). |
service | object | Kubernetes Service exposure (see below). |
certManager | object | Built-in ACME / Let’s Encrypt certificate manager. |
providers | object | Dynamic configuration providers (Kubernetes sidecar, HTTP, Git). |
spec.server
| Field | Type | Default | Description |
|---|---|---|---|
logLevel | enum | info | One of info, debug, trace, off. |
timeouts.read | int | 30 | Read timeout in seconds. |
timeouts.write | int | 60 | Write timeout in seconds. |
timeouts.idle | int | 90 | Idle timeout in seconds. |
tls[].secretName | string | — | Name of a kubernetes.io/tls Secret. |
redis.addr | string | — | Redis host:port. |
redis.password | string | — | Redis password (consider using a Secret). |
monitoring.enableMetrics | bool | false | Expose Prometheus metrics. |
monitoring.metricsPath | string | /metrics | Path of the metrics endpoint. |
monitoring.host | string | — | Restrict metrics endpoints to this Host header. |
monitoring.middleware.metrics | []string | — | Middleware CR names applied to /metrics. |
networking.dnsCache.ttl | int | — | DNS cache TTL in seconds. |
networking.transport.maxIdleConns | int | 512 | Max idle connections. |
networking.transport.maxIdleConnsPerHost | int | 256 | Max idle connections per host. |
networking.transport.maxConnsPerHost | int | 256 | Max total connections per host. |
spec.service
| Field | Type | Default | Description |
|---|---|---|---|
type | enum | ClusterIP | ClusterIP, NodePort, or LoadBalancer. |
httpPort | int32 | 8080 | Service-level HTTP port. Set to 80 for Ingress-style exposure. |
httpsPort | int32 | 8443 | Service-level HTTPS port. Set to 443 for Ingress-style exposure. |
httpNodePort | int32 | — | NodePort for HTTP (type: NodePort only). |
httpsNodePort | int32 | — | NodePort for HTTPS (type: NodePort only). |
loadBalancerIP | string | — | Request a specific static IP (cloud-dependent). |
loadBalancerSourceRanges | []string | — | Restrict access to specific CIDRs. |
loadBalancerClass | string | — | Select a specific LB implementation (e.g. service.k8s.aws/nlb). |
externalTrafficPolicy | enum | Cluster | Cluster or Local (Local preserves client source IPs). |
sessionAffinity | enum | None | None or ClientIP. |
annotations | map | — | Merged onto the Service (use for cloud LB tuning). |
labels | map | — | Merged onto the Service. |
ipFamilyPolicy | enum | — | SingleStack, PreferDualStack, or RequireDualStack. |
ipFamilies | []string | — | List of IP families: IPv4, IPv6. |
spec.certManager
| Field | Type | Default | Description |
|---|---|---|---|
provider | enum | acme | Currently only acme is supported. |
acme.email | string | — | Required. Contact email for the ACME account. |
acme.directoryUrl | string | Let’s Encrypt prod | ACME directory endpoint. |
acme.termsAccepted | bool | true | Acceptance of the ACME provider’s ToS. |
acme.challengeType | enum | http-01 | http-01 or dns-01 (use dns-01 for wildcards). |
acme.dnsProvider | string | — | DNS-01 provider (e.g. cloudflare, route53). |
acme.credentialsSecret | string | — | Secret name containing DNS provider credentials. |
spec.autoScaling
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Whether the HPA is created. |
minReplicas | int32 | 1 | Lower bound. |
maxReplicas | int32 | 10 | Upper bound. |
targetCPUUtilizationPercentage | int32 | — | Target average CPU. |
targetMemoryUtilizationPercentage | int32 | — | Target average memory. |
spec.providers
| Field | Type | Description |
|---|---|---|
kubernetes.enabled | bool | Enables the goma-k8s-provider sidecar (default: true). |
kubernetes.image | string | Sidecar image. Default: jkaninda/goma-k8s-provider:latest. |
http.enabled | bool | Enables a remote HTTP provider. |
http.endpoint | string | URL of the remote config. |
http.interval | string | Pull interval (e.g. 60s). |
http.headersSecret | string | Secret with header values referenced via ${VAR}. |
git.enabled | bool | Enables the Git provider. |
git.url | string | Repository URL. |
git.branch | string | Branch to check out. |
git.path | string | Subdirectory inside the repo. |
git.auth | object | type (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