For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Manage resources with the Go client
Use the kgateway-client Go library to programmatically create and manage Solo Enterprise for kgateway resources from Go code.
About the kgateway-client
The kgateway-client library provides typed clients that mirror the Solo Enterprise for kgateway CRDs. You use this client alongside the Gateway API, the upstream kgateway, and the standard Kubernetes clients to manage the lifecycle of Solo Enterprise for kgateway and Kubernetes Gateway API resources with Go.
The following tables summarize the resources that you can manage with each client.
Kubernetes (k8s.io/client-go/kubernetes)
| Resource | API group / version | Client method |
|---|---|---|
Secret | v1 | k8sClient.CoreV1().Secrets() |
ConfigMap | v1 | k8sClient.CoreV1().ConfigMaps() |
Service | v1 | k8sClient.CoreV1().Services() |
Deployment | apps/v1 | k8sClient.AppsV1().Deployments() |
Namespace | v1 | k8sClient.CoreV1().Namespaces() |
Kubernetes Gateway API (sigs.k8s.io/gateway-api/pkg/client/clientset/versioned)
| Resource | API group / version | Client method |
|---|---|---|
Gateway | gateway.networking.k8s.io/v1 | gatewayClient.GatewayV1().Gateways() |
HTTPRoute | gateway.networking.k8s.io/v1 | gatewayClient.GatewayV1().HTTPRoutes() |
kgateway OSS (github.com/kgateway-dev/kgateway/v2/pkg/client/clientset/versioned)
| Resource | API group / version | Client method |
|---|---|---|
TrafficPolicy | kgateway.solo.io/v1alpha1 | upstreamClient.GatewayKgateway().TrafficPolicies() |
Solo Enterprise for kgateway (github.com/solo-io/kgateway-client/v2/clientset/versioned)
| Resource | API group / version | Client method |
|---|---|---|
EnterpriseKgatewayTrafficPolicy | enterprisekgateway.solo.io/v1alpha1 | client.EnterprisekgatewayEnterprisekgateway().EnterpriseKgatewayTrafficPolicies() |
EnterpriseKgatewayParameters | enterprisekgateway.solo.io/v1alpha1 | client.EnterprisekgatewayEnterprisekgateway().EnterpriseKgatewayParameters() |
AuthConfig | extauth.solo.io/v1 | dynamic.NewForConfig() — the typed client.ExtauthV1().AuthConfigs() client exists but cannot be used due to a protobuf marshaling conflict |
RateLimitConfig | ratelimit.solo.io/v1alpha1 | client.RatelimitV1alpha1().RateLimitConfigs() |
WAFPolicy | waf.solo.io/v1alpha1 | client.EnterprisekgatewayWaf().WAFPolicies() |
EnterpriseListenerSet | enterprisesolo.solo.io/v1alpha1 | client.EnterprisekgatewayEnterprisesolo().EnterpriseListenerSets() |
Considerations when managing resource lifecycles
When you use Go to update a resource, make sure to always fetch the latest version of the resource before performing the update. Every Kubernetes object carries a resourceVersion field that must match the current version that is stored in etcd. If another controller or user modified the resource since your last Get, the versions do not match and the API server returns a 409 Conflict.
Concurrent write handling:
To handle concurrent writes, you can use a function, such as retry.RetryOnConflict that fetches the latest version of the object and retries the update when a 409 response is received.
retry.RetryOnConflict(retry.DefaultRetry, func() error {
latest, err := policies.Get(ctx, "my-policy", metav1.GetOptions{})
if err != nil {
return err
}
// modify latest ...
_, err = policies.Update(ctx, latest, metav1.UpdateOptions{})
return err
})k8serrors.IsAlreadyExists errors:
If your program can run more than once, such as in a retry loop or a CI pipeline, check for existing resources with the IsAlreadyExists function.
_, err = client.ExtauthV1().AuthConfigs("kgateway-system").Create(ctx, authConfig, metav1.CreateOptions{})
if k8serrors.IsAlreadyExists(err) {
return nil
}
return errAbout this guide
This guide walks you through how to set up the kgateway-client Go client and the corresponding Gateway API, the upstream kgateway, and the standard Kubernetes clients. Then, you explore how to use these clients to create different types of resources, including:
- Gateway
- HTTPRoute
- EnterpriseKgatewayTrafficPolicy with different policies, including API key auth and local rate limiting
- AuthConfig
You also explore how to use the test client that is built-in by default to verify your Go code without running it in a cluster.
Before you begin
Follow the Get started guide to install Solo Enterprise for kgateway.
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
Get the external address of the gateway and save it in an environment variable.
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESSkubectl port-forward deployment/http -n kgateway-system 8080:8080
- Install Go 1.25.7 or later.
Step 1: Install the Go client library
Add the Solo Enterprise for kgateway Go client and its upstream dependencies to your Go module.
Create a directory and use this directory to initialize a Go module. This command creates a
go.modfile in your directory.mkdir kgateway-go && cd kgateway-go go mod init kgateway-goExample output:
go: creating new go.mod: module kgateway-goInstall the
kgateway-clientGo client.go get github.com/solo-io/kgateway-client/v2@latestInstall the upstream kgateway, Kubernetes Gateway API, and Kubernetes clients.
go get github.com/kgateway-dev/kgateway/v2@latest go get sigs.k8s.io/gateway-api@latest go get k8s.io/client-go@latestResolve and record all transitive dependencies in the
go.sumfile. Without this step, Go cannot find the checksum entries it needs to build your program.go mod tidyVerify that your
go.modfile includes the dependency.grep -E "kgateway|gateway-api|client-go" go.modExample output:
module kgateway-go github.com/solo-io/kgateway-client/v2 v2.1.2 k8s.io/client-go v0.36.0 github.com/kgateway-dev/kgateway/v2 v2.2.4 // indirect sigs.k8s.io/gateway-api v1.5.1 // indirectThe library provides the following clients that you typically use together:
Clientset Import path What it manages Gateway API sigs.k8s.io/gateway-api/pkg/client/clientset/versionedGateway,HTTPRoute, and other standard Gateway API resourcesUpstream kgateway github.com/kgateway-dev/kgateway/v2/pkg/client/clientset/versionedTrafficPolicy,GatewayExtension, and other upstream OSS resourcesSolo Enterprise kgateway github.com/solo-io/kgateway-client/v2/clientset/versionedEnterpriseKgatewayTrafficPolicy,AuthConfig,RateLimitConfig, and other enterprise resourcesKubernetes client k8s.io/client-goNot a resource clientset — provides foundational utilities used throughout this guide: rest.Config(cluster connection),clientcmd(kubeconfig loading),homedir, andretry.RetryOnConflict
Step 2: Connect to your cluster
Create a Go program that connects to your cluster and lists EnterpriseKgatewayTrafficPolicy resources.
Create a
main.gofile.In Go, the
main.gofile is the entry point of your program. Themain.goprogram in this example connects to your cluster and lists existing EnterpriseKgatewayTrafficPolicy resources. The example assumes the Go program runs outside the cluster, such as on your local machine or a CI runner, and connects to the cluster by using a kubeconfig file (typically~/.kube/config). This setup is the most common one for development and automation workflows.cat <<'EOF' > main.go package main import ( "context" "flag" "fmt" "path/filepath" enterpriseclientset "github.com/solo-io/kgateway-client/v2/clientset/versioned" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) func main() { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "path to kubeconfig") } else { kubeconfig = flag.String("kubeconfig", "", "path to kubeconfig") } flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err) } client, err := enterpriseclientset.NewForConfig(config) if err != nil { panic(err) } ctx := context.Background() list, err := client.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies(""). List(ctx, metav1.ListOptions{}) if err != nil { panic(err) } fmt.Printf("Found %d EnterpriseKgatewayTrafficPolicies\n", len(list.Items)) } EOFIn-cluster usage: If the Go program runs inside a Kubernetes pod, replace the kubeconfig setup withrest.InClusterConfig(). The pod’s ServiceAccount must have RBAC permissions for the resources it manages. For more information, see the Kubernetes RBAC docs.Run the program to verify that you can connect to your cluster and list Solo Enterprise for kgateway resources.
go run .Example output:
Found 0 EnterpriseKgatewayTrafficPolicies
Step 3: Create a Gateway and HTTPRoute
Create Gateway API and an HTTPRoute for the httpbin sample app.
Create a
gateway.gofile with thecreateGatewayfunction. In Go, all.gofiles in the same directory share the same package, so this function is automatically accessible frommain.go. When you call this function, an HTTP Gateway with the namehttp-gois created in thekgateway-systemnamespace.cat <<'EOF' > gateway.go package main import ( "context" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" gwv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" ) func createGateway(ctx context.Context, config *rest.Config, namespace string) error { client, err := gatewayclientset.NewForConfig(config) if err != nil { return err } gw := &gwv1.Gateway{ TypeMeta: metav1.TypeMeta{ APIVersion: gwv1.GroupVersion.String(), Kind: "Gateway", }, ObjectMeta: metav1.ObjectMeta{ Name: "http-go", Namespace: namespace, }, Spec: gwv1.GatewaySpec{ GatewayClassName: "enterprise-kgateway", Listeners: []gwv1.Listener{ { Name: "http", Protocol: gwv1.HTTPProtocolType, Port: 8080, AllowedRoutes: &gwv1.AllowedRoutes{ Namespaces: &gwv1.RouteNamespaces{ From: ptr(gwv1.NamespacesFromAll), }, }, }, }, }, } _, err = client.GatewayV1().Gateways(namespace).Create(ctx, gw, metav1.CreateOptions{}) if k8serrors.IsAlreadyExists(err) { return nil } return err } func ptr[T any](v T) *T { return &v } EOFCreate an
httproute.gofile with thecreateHTTPRoutefunction. When called, the function creates an HTTPRoute resource with the namehttpbin-goin thehttpbinnamespace. The route accepts requests for theapi.example.comhostname on the/anythingpath prefix and forwards them to thehttpbinservice on port 8000.cat <<'EOF' > httproute.go package main import ( "context" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" gwv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" ) func createHTTPRoute(ctx context.Context, config *rest.Config, namespace string) error { client, err := gatewayclientset.NewForConfig(config) if err != nil { return err } pathPrefix := gwv1.PathMatchPathPrefix pathValue := "/anything" port := gwv1.PortNumber(8000) route := &gwv1.HTTPRoute{ TypeMeta: metav1.TypeMeta{ APIVersion: gwv1.GroupVersion.String(), Kind: "HTTPRoute", }, ObjectMeta: metav1.ObjectMeta{ Name: "httpbin-go", Namespace: namespace, }, Spec: gwv1.HTTPRouteSpec{ CommonRouteSpec: gwv1.CommonRouteSpec{ ParentRefs: []gwv1.ParentReference{ { Name: "http-go", Namespace: ptr(gwv1.Namespace("kgateway-system")), }, }, }, Hostnames: []gwv1.Hostname{"api.example.com"}, Rules: []gwv1.HTTPRouteRule{ { Matches: []gwv1.HTTPRouteMatch{ { Path: &gwv1.HTTPPathMatch{ Type: &pathPrefix, Value: &pathValue, }, }, }, BackendRefs: []gwv1.HTTPBackendRef{ { BackendRef: gwv1.BackendRef{ BackendObjectReference: gwv1.BackendObjectReference{ Name: "httpbin", Port: &port, }, }, }, }, }, }, }, } _, err = client.GatewayV1().HTTPRoutes(namespace).Create(ctx, route, metav1.CreateOptions{}) if k8serrors.IsAlreadyExists(err) { return nil } return err } EOFUpdate the
main.gofile to call thecreateGatewayandcreateHTTPRoutefunctions.cat <<'EOF' > main.go package main import ( "context" "flag" "fmt" "path/filepath" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" enterpriseclientset "github.com/solo-io/kgateway-client/v2/clientset/versioned" ) func main() { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "path to kubeconfig") } else { kubeconfig = flag.String("kubeconfig", "", "path to kubeconfig") } flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err) } client, err := enterpriseclientset.NewForConfig(config) if err != nil { panic(err) } ctx := context.Background() if err := createGateway(ctx, config, "kgateway-system"); err != nil { panic(err) } if err := createHTTPRoute(ctx, config, "httpbin"); err != nil { panic(err) } fmt.Println("Gateway and HTTPRoute created successfully") list, err := client.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies(""). List(ctx, metav1.ListOptions{}) if err != nil { panic(err) } fmt.Printf("Found %d EnterpriseKgatewayTrafficPolicies\n", len(list.Items)) } EOFRun the program to create the resources. Use
.to compile all.gofiles in the current directory, not justmain.go.go run .Example output:
Gateway and HTTPRoute created successfully Found 0 EnterpriseKgatewayTrafficPoliciesVerify that the resources were created in the cluster.
kubectl get gateway -n kgateway-system && kubectl get httproute -n httpbinExample output:
NAME CLASS ADDRESS PROGRAMMED AGE gateway.gateway.networking.k8s.io/http-go enterprise-kgateway True 10s NAME HOSTNAMES AGE httproute.gateway.networking.k8s.io/httpbin-go ["api.example.com"] 10sGet the external address of the
http-gogateway and save it in an environment variable.export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http-go -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESSkubectl port-forward deployment/http-go -n kgateway-system 8080:8080Send a request to the httpbin app to verify the gateway and HTTPRoute are routing traffic correctly.
curl -i http://$INGRESS_GW_ADDRESS:8080/anything -H "host: api.example.com"curl -i localhost:8080/anything -H "host: api.example.com"Example output:
HTTP/1.1 200 OK access-control-allow-credentials: true access-control-allow-origin: * content-type: application/json date: Mon, 05 May 2025 10:00:00 GMT server: envoy
Step 4: Configure API key auth
In this example, you set up API key auth for the httpbin app. This setup requires three resources: a Kubernetes Secret that stores the API key, an AuthConfig that defines the API key authentication method, and an EnterpriseKgatewayTrafficPolicy that attaches the AuthConfig to the httpbin route.
Although kgateway-client exposes a typed client.ExtauthV1().AuthConfigs() client, the AuthConfigSpec type uses a custom protobuf-based JSON marshaler (jsonpb.Marshaler) that conflicts with the google.golang.org/protobuf version that is pulled in by k8s.io/client-go. Passing the typed struct to the Kubernetes API server causes a runtime panic. For AuthConfig, use the dynamic client (k8s.io/client-go/dynamic) instead. It encodes the resource as a plain map[string]interface{}, which bypasses the protobuf marshaler entirely.
Create an
extauth.gofile with the following three functions.createAPIKeySecret(): Creates the Kubernetes Secret for your API key by using the standardkubernetesclient. The example uses a pre-defined API key that must be used to successfully authenticate.createAuthConfig(): Creates the AuthConfig resource by using the dynamic client.createExtAuthPolicy(): Creates the EnterpriseKgatewayTrafficPolicy by using thekgateway-client.
cat <<'EOF' > extauth.go package main import ( "context" upstreamkgateway "github.com/kgateway-dev/kgateway/v2/api/v1alpha1/kgateway" upstreamshared "github.com/kgateway-dev/kgateway/v2/api/v1alpha1/shared" enterprisev1alpha1 "github.com/solo-io/kgateway-client/v2/api/v1alpha1/enterprisekgateway" enterpriseshared "github.com/solo-io/kgateway-client/v2/api/v1alpha1/shared" enterpriseclientset "github.com/solo-io/kgateway-client/v2/clientset/versioned" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" gwv1 "sigs.k8s.io/gateway-api/apis/v1" ) func createAPIKeySecret(ctx context.Context, config *rest.Config) error { client, err := kubernetes.NewForConfig(config) if err != nil { return err } secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "my-apikey", Namespace: "kgateway-system", Labels: map[string]string{"team": "infrastructure"}, }, Type: "extauth.solo.io/apikey", StringData: map[string]string{ "api-key": "N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy", }, } _, err = client.CoreV1().Secrets("kgateway-system").Create(ctx, secret, metav1.CreateOptions{}) if k8serrors.IsAlreadyExists(err) { return nil } return err } func createAuthConfig(ctx context.Context, config *rest.Config) error { client, err := dynamic.NewForConfig(config) if err != nil { return err } gvr := schema.GroupVersionResource{ Group: "extauth.solo.io", Version: "v1", Resource: "authconfigs", } authConfig := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "extauth.solo.io/v1", "kind": "AuthConfig", "metadata": map[string]interface{}{ "name": "apikey-auth", "namespace": "kgateway-system", }, "spec": map[string]interface{}{ "configs": []interface{}{ map[string]interface{}{ "apiKeyAuth": map[string]interface{}{ "headerName": "api-key", "labelSelector": map[string]interface{}{ "team": "infrastructure", }, }, }, }, }, }, } _, err = client.Resource(gvr).Namespace("kgateway-system").Create(ctx, authConfig, metav1.CreateOptions{}) if k8serrors.IsAlreadyExists(err) { return nil } return err } func createExtAuthPolicy(ctx context.Context, config *rest.Config) error { client, err := enterpriseclientset.NewForConfig(config) if err != nil { return err } ns := gwv1.Namespace("kgateway-system") policy := &enterprisev1alpha1.EnterpriseKgatewayTrafficPolicy{ TypeMeta: metav1.TypeMeta{ APIVersion: enterprisev1alpha1.SchemeGroupVersion.String(), Kind: "EnterpriseKgatewayTrafficPolicy", }, ObjectMeta: metav1.ObjectMeta{ Name: "extauth-policy", Namespace: "kgateway-system", }, Spec: enterprisev1alpha1.EnterpriseKgatewayTrafficPolicySpec{ TrafficPolicySpec: upstreamkgateway.TrafficPolicySpec{ TargetRefs: []upstreamshared.LocalPolicyTargetReferenceWithSectionName{ { LocalPolicyTargetReference: upstreamshared.LocalPolicyTargetReference{ Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "http-go", }, }, }, }, EntExtAuth: &enterprisev1alpha1.EntExtAuth{ AuthConfigRef: &enterpriseshared.AuthConfigRef{ Name: "apikey-auth", Namespace: &ns, }, }, }, } _, err = client.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies("kgateway-system"). Create(ctx, policy, metav1.CreateOptions{}) if k8serrors.IsAlreadyExists(err) { return nil } return err } EOFUpdate the
main.gofile to call the three extauth functions in order.cat <<'EOF' > main.go package main import ( "context" "flag" "fmt" "path/filepath" enterpriseclientset "github.com/solo-io/kgateway-client/v2/clientset/versioned" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) func main() { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "path to kubeconfig") } else { kubeconfig = flag.String("kubeconfig", "", "path to kubeconfig") } flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err) } client, err := enterpriseclientset.NewForConfig(config) if err != nil { panic(err) } ctx := context.Background() if err := createGateway(ctx, config, "kgateway-system"); err != nil { panic(err) } if err := createHTTPRoute(ctx, config, "httpbin"); err != nil { panic(err) } fmt.Println("Gateway and HTTPRoute created successfully") list, err := client.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies(""). List(ctx, metav1.ListOptions{}) if err != nil { panic(err) } fmt.Printf("Found %d EnterpriseKgatewayTrafficPolicies\n", len(list.Items)) if err := createAPIKeySecret(ctx, config); err != nil { panic(err) } fmt.Println("API key secret created") if err := createAuthConfig(ctx, config); err != nil { panic(err) } fmt.Println("AuthConfig created") if err := createExtAuthPolicy(ctx, config); err != nil { panic(err) } fmt.Println("ExtAuth policy created") } EOFRun the program and verify the output.
go run .Example output:
Gateway and HTTPRoute created successfully Found 0 EnterpriseKgatewayTrafficPolicies API key secret created AuthConfig created ExtAuth policy createdVerify that the policy is applied to the gateway.
kubectl get enterprisekgatewaytrafficpolicy extauth-policy -n kgateway-system -o yamlSend a request to the httpbin app without an API key. Verify that the request is denied with a
401 Unauthorizedresponse.curl -i http://$INGRESS_GW_ADDRESS:8080/anything -H "host: api.example.com"curl -i localhost:8080/anything -H "host: api.example.com"Example output:
HTTP/1.1 401 Unauthorized date: Mon, 05 May 2025 10:00:00 GMT server: envoy content-length: 0Send a request with the valid API key in the
api-keyheader. Verify that the request succeeds with a200 OKresponse.curl -i http://$INGRESS_GW_ADDRESS:8080/anything -H "host: api.example.com" \ -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"curl -i localhost:8080/anything -H "host: api.example.com" \ -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"Example output:
HTTP/1.1 200 OK access-control-allow-credentials: true access-control-allow-origin: * date: Mon, 05 May 2025 10:00:01 GMT content-length: 0 server: envoy
Step 5: Configure local rate limiting
In this step, you configure local rate limiting for the httpbin app. Local rate limiting is enforced by the gateway proxy itself and does not require an external rate limiting service.
The createLocalRateLimitPolicy function in this examples creates an EnterpriseKgatewayTrafficPolicy with the following rate limiting settings:
maxTokens: 1: A maximum of 1 token is available at any given time.tokensPerFill: 1: 1 token is added per fill interval.fillInterval: 1m: The bucket refills once per minute. Combined withtokensPerFill, this allows exactly 1 request per minute. Requests that arrive when the bucket is empty receive a429 Too Many Requestsresponse.
Create a
ratelimit.gofile with thecreateLocalRateLimitPolicyfunction.cat <<'EOF' > ratelimit.go package main import ( "context" "time" upstreamkgateway "github.com/kgateway-dev/kgateway/v2/api/v1alpha1/kgateway" upstreamshared "github.com/kgateway-dev/kgateway/v2/api/v1alpha1/shared" enterprisev1alpha1 "github.com/solo-io/kgateway-client/v2/api/v1alpha1/enterprisekgateway" enterpriseclientset "github.com/solo-io/kgateway-client/v2/clientset/versioned" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" ) func createLocalRateLimitPolicy(ctx context.Context, config *rest.Config) error { client, _ := enterpriseclientset.NewForConfig(config) maxTokens := int32(1) tokensPerFill := int32(1) fillInterval := metav1.Duration{Duration: 1 * time.Minute} policy := &enterprisev1alpha1.EnterpriseKgatewayTrafficPolicy{ TypeMeta: metav1.TypeMeta{ APIVersion: enterprisev1alpha1.SchemeGroupVersion.String(), Kind: "EnterpriseKgatewayTrafficPolicy", }, ObjectMeta: metav1.ObjectMeta{ Name: "local-ratelimit", Namespace: "kgateway-system", }, Spec: enterprisev1alpha1.EnterpriseKgatewayTrafficPolicySpec{ TrafficPolicySpec: upstreamkgateway.TrafficPolicySpec{ TargetRefs: []upstreamshared.LocalPolicyTargetReferenceWithSectionName{ { LocalPolicyTargetReference: upstreamshared.LocalPolicyTargetReference{ Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "http-go", }, }, }, RateLimit: &upstreamkgateway.RateLimit{ Local: &upstreamkgateway.LocalRateLimitPolicy{ TokenBucket: &upstreamkgateway.TokenBucket{ MaxTokens: maxTokens, TokensPerFill: &tokensPerFill, FillInterval: fillInterval, }, }, }, }, }, } _, err := client.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies("kgateway-system"). Create(ctx, policy, metav1.CreateOptions{}) if k8serrors.IsAlreadyExists(err) { return nil } return err } EOFUpdate
main.goto call thecreateLocalRateLimitPolicyfunction.cat <<'EOF' > main.go package main import ( "context" "flag" "fmt" "path/filepath" enterpriseclientset "github.com/solo-io/kgateway-client/v2/clientset/versioned" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) func main() { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "path to kubeconfig") } else { kubeconfig = flag.String("kubeconfig", "", "path to kubeconfig") } flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err) } client, err := enterpriseclientset.NewForConfig(config) if err != nil { panic(err) } ctx := context.Background() if err := createGateway(ctx, config, "kgateway-system"); err != nil { panic(err) } if err := createHTTPRoute(ctx, config, "httpbin"); err != nil { panic(err) } fmt.Println("Gateway and HTTPRoute created successfully") list, err := client.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies(""). List(ctx, metav1.ListOptions{}) if err != nil { panic(err) } fmt.Printf("Found %d EnterpriseKgatewayTrafficPolicies\n", len(list.Items)) if err := createAPIKeySecret(ctx, config); err != nil { panic(err) } fmt.Println("API key secret created") if err := createAuthConfig(ctx, config); err != nil { panic(err) } fmt.Println("AuthConfig created") if err := createExtAuthPolicy(ctx, config); err != nil { panic(err) } fmt.Println("ExtAuth policy created") if err := createLocalRateLimitPolicy(ctx, config); err != nil { panic(err) } fmt.Println("Rate limit policy created") } EOFRun the program.
go run .Example output:
Gateway and HTTPRoute created successfully Found 0 EnterpriseKgatewayTrafficPolicies API key secret created AuthConfig created ExtAuth policy created Rate limit policy createdVerify the rate limit policy.
kubectl get enterprisekgatewaytrafficpolicy local-ratelimit -n kgateway-system -o yamlTest rate limiting by sending 3 requests in quick succession. The first request consumes the single token and returns a
200 OKresponse. The remaining requests find the bucket empty and return429 Too Many Requests.for i in $(seq 1 3); do curl -s -o /dev/null -w "%{http_code}\n" http://$INGRESS_GW_ADDRESS:8080/anything \ -H "host: api.example.com" \ -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy" donefor i in $(seq 1 3); do curl -s -o /dev/null -w "%{http_code}\n" localhost:8080/anything \ -H "host: api.example.com" \ -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy" doneExample output:
200 429 429
Step 6: Write unit tests with the fake client
The library ships with a fake clientset that stores objects in memory. You can use this clientset in unit tests to verify your policy logic without applying the resources in a cluster. The tests assert on the actual policy fields, so a misconfigured token bucket or wrong targetRef causes the test to fail.
Create a
policy_test.gofile that defines your test functions. This example file implements the following test functions:TestLocalRateLimitPolicy()- Verify the token bucket is stored with
MaxTokens: 1,TokensPerFill: 1, andFillInterval: 1m. - Verify that the
targetRefpoints to thehttp-goGateway.
- Verify the token bucket is stored with
TestExtAuthPolicy():- Verify that the
targetRefpoints to thehttp-goGateway. - Verify that
EntExtAuth.AuthConfigRef.Nameisapikey-auth. - Verify that
EntExtAuth.AuthConfigRef.Namespaceiskgateway-system.
- Verify that the
cat <<'EOF' > policy_test.go package main_test import ( "context" "testing" "time" upstreamkgateway "github.com/kgateway-dev/kgateway/v2/api/v1alpha1/kgateway" upstreamshared "github.com/kgateway-dev/kgateway/v2/api/v1alpha1/shared" enterprisev1alpha1 "github.com/solo-io/kgateway-client/v2/api/v1alpha1/enterprisekgateway" enterpriseshared "github.com/solo-io/kgateway-client/v2/api/v1alpha1/shared" fakeclientset "github.com/solo-io/kgateway-client/v2/clientset/versioned/fake" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gwv1 "sigs.k8s.io/gateway-api/apis/v1" ) func TestLocalRateLimitPolicy(t *testing.T) { fakeClient := fakeclientset.NewSimpleClientset() ctx := context.Background() maxTokens := int32(1) tokensPerFill := int32(1) fillInterval := metav1.Duration{Duration: 1 * time.Minute} policy := &enterprisev1alpha1.EnterpriseKgatewayTrafficPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "local-ratelimit", Namespace: "kgateway-system", }, Spec: enterprisev1alpha1.EnterpriseKgatewayTrafficPolicySpec{ TrafficPolicySpec: upstreamkgateway.TrafficPolicySpec{ TargetRefs: []upstreamshared.LocalPolicyTargetReferenceWithSectionName{ { LocalPolicyTargetReference: upstreamshared.LocalPolicyTargetReference{ Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "http-go", }, }, }, RateLimit: &upstreamkgateway.RateLimit{ Local: &upstreamkgateway.LocalRateLimitPolicy{ TokenBucket: &upstreamkgateway.TokenBucket{ MaxTokens: maxTokens, TokensPerFill: &tokensPerFill, FillInterval: fillInterval, }, }, }, }, }, } _, err := fakeClient.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies("kgateway-system"). Create(ctx, policy, metav1.CreateOptions{}) if err != nil { t.Fatalf("Create() error = %v", err) } got, err := fakeClient.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies("kgateway-system"). Get(ctx, "local-ratelimit", metav1.GetOptions{}) if err != nil { t.Fatalf("Get() error = %v", err) } tb := got.Spec.TrafficPolicySpec.RateLimit.Local.TokenBucket if tb.MaxTokens != 1 { t.Errorf("expected MaxTokens 1, got %d", tb.MaxTokens) } if *tb.TokensPerFill != 1 { t.Errorf("expected TokensPerFill 1, got %d", *tb.TokensPerFill) } if tb.FillInterval.Duration != time.Minute { t.Errorf("expected FillInterval 1m, got %s", tb.FillInterval.Duration) } refs := got.Spec.TrafficPolicySpec.TargetRefs if len(refs) != 1 || string(refs[0].Name) != "http-go" || string(refs[0].Kind) != "Gateway" { t.Errorf("unexpected targetRef: %+v", refs) } } func TestExtAuthPolicy(t *testing.T) { fakeClient := fakeclientset.NewSimpleClientset() ctx := context.Background() ns := gwv1.Namespace("kgateway-system") policy := &enterprisev1alpha1.EnterpriseKgatewayTrafficPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "extauth-policy", Namespace: "kgateway-system", }, Spec: enterprisev1alpha1.EnterpriseKgatewayTrafficPolicySpec{ TrafficPolicySpec: upstreamkgateway.TrafficPolicySpec{ TargetRefs: []upstreamshared.LocalPolicyTargetReferenceWithSectionName{ { LocalPolicyTargetReference: upstreamshared.LocalPolicyTargetReference{ Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "http-go", }, }, }, }, EntExtAuth: &enterprisev1alpha1.EntExtAuth{ AuthConfigRef: &enterpriseshared.AuthConfigRef{ Name: "apikey-auth", Namespace: &ns, }, }, }, } _, err := fakeClient.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies("kgateway-system"). Create(ctx, policy, metav1.CreateOptions{}) if err != nil { t.Fatalf("Create() error = %v", err) } got, err := fakeClient.EnterprisekgatewayEnterprisekgateway(). EnterpriseKgatewayTrafficPolicies("kgateway-system"). Get(ctx, "extauth-policy", metav1.GetOptions{}) if err != nil { t.Fatalf("Get() error = %v", err) } refs := got.Spec.TrafficPolicySpec.TargetRefs if len(refs) != 1 || string(refs[0].Name) != "http-go" || string(refs[0].Kind) != "Gateway" { t.Errorf("unexpected targetRef: %+v", refs) } authRef := got.Spec.EntExtAuth.AuthConfigRef if authRef.Name != "apikey-auth" { t.Errorf("expected AuthConfigRef.Name apikey-auth, got %s", authRef.Name) } if authRef.Namespace == nil || string(*authRef.Namespace) != "kgateway-system" { t.Errorf("expected AuthConfigRef.Namespace kgateway-system, got %v", authRef.Namespace) } } EOFRun the tests.
go test ./...Example output:
ok kgateway-go 0.003sokmeans all tests passed.kgateway-gois the module name from yourgo.modfile, and0.003sis the total run time. Because the fake clientset stores objects in memory, no cluster connection is made and the tests complete in milliseconds.If a test fails, Go prints the test name, the file and line number of the failing assertion, and the actual value that was found. For example, if
MaxTokenswas accidentally set to100instead of1, you see a result similar to the following error:--- FAIL: TestLocalRateLimitPolicy (0.00s) policy_test.go:65: expected MaxTokens 1, got 100 FAIL FAIL kgateway-go 0.003s
Cleanup
You can optionally remove the resources that you set up as part of this guide.kubectl delete enterprisekgatewaytrafficpolicy --all -n kgateway-system
kubectl delete enterprisekgatewaytrafficpolicy --all -n httpbin
kubectl delete authconfig oauth-oidc -n kgateway-system --ignore-not-found
kubectl delete ratelimitconfig global-ratelimit -n kgateway-system --ignore-not-found
kubectl delete httproute httpbin-go -n httpbin --ignore-not-found
kubectl delete gateway http-go -n kgateway-system --ignore-not-found