Gardener on Cleura

Gardener shoot cluster reference — garden/seed/shoot model, networking on OpenStack, TCP and HTTP via Kubernetes Gateway API with Envoy Gateway.

Gardener is a Kubernetes-as-a-Service framework that runs on Kubernetes and manages the lifecycle of other clusters declaratively. Rather than managing control planes by hand, Gardener treats clusters as a resource — defined, created, upgraded, and deleted via the Gardener API.


Concepts

Gardener uses three layers:

LayerWhat it is
Garden clusterRuns Gardener itself — the management control plane
Seed clusterHosts the control planes of shoot clusters (as pods)
Shoot clusterThe cluster you actually use — nodes run on the target cloud

The shoot cluster’s API server does not run on the shoot nodes. It runs as a pod inside the seed cluster. From the outside it behaves like any other Kubernetes cluster; internally the control plane is isolated from the data plane.

Shoot clusters are defined as Shoot resources applied to the garden cluster:

apiVersion: core.gardener.cloud/v1beta1
kind: Shoot
metadata:
  name: my-cluster
  namespace: garden-my-project
spec:
  cloudProfileName: openstack
  region: sto2
  provider:
    type: openstack
    workers:
      - name: worker-pool
        machine:
          type: l2.c2r4
        minimum: 1
        maximum: 3
  kubernetes:
    version: "1.30"
  networking:
    type: calico
    pods: 100.128.0.0/11
    nodes: 10.250.0.0/16
    services: 100.112.0.0/13

Shoot cluster on Cleura

Cleura is a European OpenStack provider. Gardener provisions shoot nodes as OpenStack VMs via the OpenStack machine controller.

Key integrations:

ComponentImplementation
Node provisioningOpenStack VMs via Gardener machine controller
Load balancersOctavia via cloud-controller-manager
Block storageCinder via CSI driver
DNSManual or external-dns
CNICalico (default) or configurable

Gardener on Cleura does not provide an ingress controller or API gateway — these are brought in separately.


Networking

Gardener manages the cluster network configuration as part of the shoot spec. Pod, node, and service CIDRs are defined at cluster creation and must not overlap with the OpenStack network.

On Cleura, nodes get OpenStack floating IPs for egress. Pod-to-pod traffic stays within the cluster overlay network (Calico by default). Traffic entering from outside the cluster goes through a LoadBalancer service — either directly for raw TCP, or via a gateway controller for HTTP.


Ingress — classic vs Gateway API

The classic Kubernetes Ingress resource is HTTP-only, has no TCP support, and its feature set varies across implementations via non-standard annotations. The NGINX Ingress Controller — the most widely used implementation — is deprecated; NGINX now focuses on their Gateway API implementation instead.

The Kubernetes Gateway API is the forward path — a set of CRDs (Gateway, HTTPRoute, TCPRoute, TLSRoute) with a standardized spec and first-class support for both HTTP and TCP.

ResourceProtocolAPIStatus
IngressHTTP onlyKubernetesStable, legacy
HTTPRouteHTTP/HTTPSGateway APIStable
TCPRouteRaw TCPGateway APIExperimental
TLSRouteTLS passthroughGateway APIExperimental

Envoy Gateway

Envoy Gateway is the CNCF implementation of the Kubernetes Gateway API using Envoy as the data plane. It supports HTTPRoute, TCPRoute, and TLSRoute through a single Gateway resource — one entry point, both protocols.

Octavia LB  ←  one LoadBalancer service per Gateway listener
    |
Envoy Gateway pod
    |
+------------------+------------------+
|                                     |
HTTPRoute → ClusterIP pods       TCPRoute → ClusterIP pods

Envoy Gateway is deployed into the shoot cluster and exposes a LoadBalancer service via Octavia, the same as any other service. The Gateway API resources then declare what routes through it.


TCPRoute — declaring TCP services

TCPRoute attaches to a Gateway listener and routes raw TCP traffic to a backend service. This is how a non-HTTP workload (e.g. a game server, a database proxy, a custom protocol service) gets exposed through the Gateway API rather than a standalone LoadBalancer service.

apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute
metadata:
  name: my-tcp-service
  namespace: my-app
spec:
  parentRefs:
    - name: my-gateway
      sectionName: tcp-listener
  rules:
    - backendRefs:
        - name: my-service
          port: 1234

The corresponding Gateway listener:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: my-app
spec:
  gatewayClassName: envoy-gateway
  listeners:
    - name: tcp-listener
      protocol: TCP
      port: 1234
    - name: http-listener
      protocol: HTTP
      port: 80

One Gateway, both protocols declared explicitly. The TCPRoute API is in the experimental channel and requires opting in when installing Envoy Gateway.


HTTPRoute — HTTP services

HTTPRoute handles HTTP and HTTPS traffic with routing by hostname, path, header, or method — without annotations.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-http-service
  namespace: my-app
spec:
  parentRefs:
    - name: my-gateway
      sectionName: http-listener
  hostnames:
    - my-app.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: my-service
          port: 8080

LoadBalancer — direct TCP via Octavia

For cases where a TCPRoute is not appropriate (or the Gateway API experimental channel is not enabled), a LoadBalancer service provisions an Octavia LB directly:

apiVersion: v1
kind: Service
metadata:
  name: my-tcp-service
  namespace: my-app
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - port: 1234
      targetPort: 1234
      protocol: TCP

Annotations control Octavia behaviour — timeouts, health check parameters, internal vs external. These are provider-specific and not standardised across OpenStack deployments.


Storage

Cinder block volumes are available via the CSI driver. A PersistentVolumeClaim provisions a Cinder volume automatically using the cluster’s default storage class.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

Cinder volumes are ReadWriteOnce — they attach to a single node. For stateful workloads, use StatefulSet rather than Deployment to get stable volume binding across pod restarts.


Provisioning a shoot cluster on Cleura

Cleura wraps Gardener behind their own REST API at rest.cleura.cloud. The garden cluster kubeconfig is not exposed — gardenctl does not work directly. Cluster lifecycle is managed through HTTP calls.

Authentication

Every call requires a token obtained once per session:

curl -s -X POST https://rest.cleura.cloud/auth/v1/tokens \
  -H "Content-Type: application/json" \
  -d '{"auth": {"login": "you@example.com", "password": "yourpass"}}' \
  | jq '{token: .token}'

Pass X-AUTH-LOGIN and X-AUTH-TOKEN headers on all subsequent calls.

Bootstrap (once per project/region)

Before creating any clusters, the project must be bootstrapped — this wires up the OpenStack credentials that Gardener uses to provision nodes:

curl -X POST \
  https://rest.cleura.cloud/gardener/v1/public/secret/kna1/{projectId}/bootstrap \
  -H "X-AUTH-LOGIN: ..." -H "X-AUTH-TOKEN: ..."

Safe to call repeatedly; idempotent.

Create a shoot cluster

curl -X POST \
  https://rest.cleura.cloud/gardener/v1/public/shoot/kna1/{projectId} \
  -H "X-AUTH-LOGIN: ..." -H "X-AUTH-TOKEN: ..." \
  -H "Content-Type: application/json" \
  -d '{
    "shoot": {
      "name": "my-cluster",
      "kubernetes": {"version": "1.31.0"},
      "provider": {
        "infrastructureConfig": {"floatingPoolName": "ext-net"},
        "workers": [{
          "name": "default",
          "machine": {
            "type": "4C-8GB-50GB",
            "image": {"name": "ubuntu", "version": "22.4.20230301"}
          },
          "minimum": 1,
          "maximum": 3,
          "volume": {"size": "50Gi"}
        }]
      }
    }
  }'

Poll until ready

curl https://rest.cleura.cloud/gardener/v1/public/shoot/kna1/{projectId}/my-cluster \
  -H "X-AUTH-LOGIN: ..." -H "X-AUTH-TOKEN: ..." \
  | jq '.lastOperation | {state, description, progress}'

Poll until lastOperation.state == "Succeeded". Takes roughly 10–15 minutes on first provision.

Fetch kubeconfig

The Cleura docs reference two kubeconfig paths — GET /kubeconfig (lowercase) and POST /Kubeconfig (uppercase, different casing). Neither worked reliably in practice. The endpoint that actually returns a kubeconfig is:

curl -s -X POST \
  https://rest.cleura.cloud/gardener/v1/public/shoot/kna1/{projectId}/my-cluster/adminkubeconfig \
  -H "X-AUTH-LOGIN: ..." -H "X-AUTH-TOKEN: ..." \
  -H "Content-Type: application/json" \
  -d '{"config": {"expirationSeconds": 3600}}' \
  | jq -r > my-cluster-kubeconfig.yaml

The expirationSeconds field controls credential lifetime. A bug report has been filed with Cleura about the endpoint inconsistency — the adminkubeconfig path is not documented.

PathMethodDocumentedWorks
/kubeconfigGETyesunclear
/KubeconfigPOSTyesunclear
/adminkubeconfigPOSTnoyes

Cleura docs issue #534 — kubeconfig endpoint inconsistencies in Gardener REST API

Script

A bash script wrapping the full workflow (list, create, wait, kubeconfig, delete) is available: cleura-shoot.sh

export CLEURA_LOGIN="you@example.com"
export CLEURA_PASSWORD="yourpass"

./cleura-shoot.sh list
./cleura-shoot.sh create my-cluster
./cleura-shoot.sh wait my-cluster
./cleura-shoot.sh kubeconfig my-cluster
./cleura-shoot.sh delete my-cluster

IaC options

No native Terraform provider exists for Cleura’s Gardener REST API. The Gardener Terraform provider (registry.terraform.io/providers/gardener/gardener) requires the garden cluster kubeconfig, which Cleura does not expose. Options:

ApproachNotes
Bash + curlMinimal deps — just curl and jq
Crossplane provider-httpDeclarative, Kubernetes-native, reconciliation loop
Custom Terraform providerFull plan/apply semantics — requires Go provider development
Pulumi custom dynamic providerPython/TypeScript, similar effort to custom Terraform provider
Built with Hugo
Theme Stack designed by Jimmy