Creating HTTP/S Routing Rules Programmatically
We understand the structure of the Ingress resource and the necessity of an Ingress Controller. Now, let's use client-go to define the core routing logic – the rules that map incoming hostnames and paths to backend Kubernetes Services.
This involves constructing the networkingv1.Ingress Go struct, paying close attention to the spec.rules and spec.tls fields, and then using the client-go clientset to create this resource in the cluster.
Constructing the Ingress Struct in Go
Let's build an example where we want to achieve the following routing:
Requests to
app.example.com/loginshould go tologin-serviceon its named porthttp.Requests to
app.example.com/api/usersshould go touser-api-serviceon port8080.Requests to
metrics.example.com/should go tometrics-serviceon port9090.Enable TLS for
app.example.comusing a Secret namedapp-tls-secret.
First, we need to import the necessary packages, including k8s.io/api/networking/v1. Then, we construct the Ingress object in Go:
package main
import (
"context"
"flag"
"fmt"
"log"
"path/filepath"
// Kubernetes API imports
networkingv1 "k8s.io/api/networking/v1" // Import networking v1 package
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// client-go imports
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
func main() {
// --- Setup Kubeconfig and Flags ---
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) kubeconfig path")
} else {
kubeconfig = flag.String("kubeconfig", "", "kubeconfig path")
}
namespace := flag.String("namespace", "default", "namespace to create the ingress in")
ingressName := flag.String("ingress-name", "example-ingress", "name for the new ingress")
ingressClassName := flag.String("ingress-class", "", "(required) name of the IngressClass") // Make class name required
flag.Parse()
if *ingressClassName == "" {
log.Fatal("Error: --ingress-class flag is required")
}
// --- Load Config and Create Clientset ---
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
log.Fatalf("Error building kubeconfig: %s", err.Error())
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatalf("Error creating clientset: %s", err.Error())
}
// --- Define the Ingress Object ---
pathTypePrefix := networkingv1.PathTypePrefix // Define path type constant for clarity
ingressSpec := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: *ingressName,
Namespace: *namespace,
Annotations: map[string]string{ // Optional: Add annotations if needed by controller
// "nginx.ingress.kubernetes.io/rewrite-target": "/",
},
},
Spec: networkingv1.IngressSpec{
// Link to the specific Ingress Controller
IngressClassName: ingressClassName,
// --- Define Routing Rules ---
Rules: []networkingv1.IngressRule{
// Rule 1: Target app.example.com
{
Host: "app.example.com",
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
// Path 1.1: /login
{
Path: "/login",
PathType: &pathTypePrefix, // Use Prefix match
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "login-service", // Target Service name
Port: networkingv1.ServiceBackendPort{
Name: "http", // Target Service port name
},
},
},
},
// Path 1.2: /api/users
{
Path: "/api/users",
PathType: &pathTypePrefix,
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "user-api-service", // Target Service name
Port: networkingv1.ServiceBackendPort{
Number: 8080, // Target Service port number
},
},
},
},
},
},
},
// Rule 2: Target metrics.example.com
{
Host: "metrics.example.com",
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
// Path 2.1: / (root path)
{
Path: "/",
PathType: &pathTypePrefix,
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "metrics-service",
Port: networkingv1.ServiceBackendPort{
Number: 9090,
},
},
},
},
},
},
},
}, // End of Rules
// --- Define TLS Configuration ---
TLS: []networkingv1.IngressTLS{
{
Hosts: []string{ // Hosts covered by this TLS cert
"app.example.com",
},
SecretName: "app-tls-secret", // Name of the Secret containing TLS cert/key
},
// Could add another entry for metrics.example.com if it needed TLS
// { Hosts: []string{"metrics.example.com"}, SecretName: "metrics-tls-secret",},
}, // End of TLS
}, // End of Spec
} // End of Ingress definition
// --- Create the Ingress Resource ---
fmt.Printf("Creating Ingress '%s' in namespace '%s'...\n", *ingressName, *namespace)
// Access the NetworkingV1 client
ingressClient := clientset.NetworkingV1().Ingresses(*namespace)
createdIngress, err := ingressClient.Create(context.TODO(), ingressSpec, metav1.CreateOptions{})
// --- Handle Errors ---
if err != nil {
if k8serrors.IsAlreadyExists(err) {
log.Printf("Ingress '%s' already exists in namespace '%s'.\n", *ingressName, *namespace)
// Optionally Get/Update existing Ingress
} else {
log.Fatalf("Error creating Ingress '%s': %s\n", *ingressName, err.Error())
}
} else {
fmt.Printf("Successfully created Ingress '%s'.\n", createdIngress.Name)
// Note: It might take a moment for the Ingress Controller to process this
// and for the external endpoint (if using type: LoadBalancer for the controller)
// to become available and reflect in `kubectl get ingress`.
fmt.Printf("Run `kubectl describe ingress %s -n %s` to see details.\n", *ingressName, *namespace)
}
fmt.Println("---------------")
}Key Go Constructs:
Import
networking/v1: We usek8s.io/api/networking/v1.IngressClassName: Crucial for directing the resource to the correct controller.RulesSlice: We create a slice ofnetworkingv1.IngressRule.Host: Set the target hostname within each rule.HTTPandPaths: We define theHTTPIngressRuleValueand itsPathsslice ([]networkingv1.HTTPIngressPath).PathandPathType: Specify the URL path and how it should be matched (e.g.,Prefix). Note thatPathTyperequires a pointer, so we assign&pathTypePrefix.BackendandService: Define the targetServiceusingnetworkingv1.IngressBackendandnetworkingv1.IngressServiceBackend.Service Port (Name vs. Number): We specify the target port on the Service using eitherPort.NameorPort.Number.TLSSlice: We define a slice ofnetworkingv1.IngressTLS.HostsandSecretName: Link the hostnames to the Kubernetes Secret containing the certificate and key. Prerequisite: The Secret (app-tls-secretin this case) must exist in the same namespace and be of typekubernetes.io/tls.CreateCall: We useclientset.NetworkingV1().Ingresses(namespace).Create(...)to submit the definedIngressobject to the API server.
By constructing these Ingress objects programmatically, you gain the ability to automate the exposure of your applications, potentially integrating with CI/CD pipelines, service discovery systems, or custom dashboards to manage external access dynamically based on application lifecycle events or other triggers.
Last updated
Was this helpful?