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/login
should go tologin-service
on its named porthttp
.Requests to
app.example.com/api/users
should go touser-api-service
on port8080
.Requests to
metrics.example.com/
should go tometrics-service
on port9090
.Enable TLS for
app.example.com
using 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.Rules
Slice: We create a slice ofnetworkingv1.IngressRule
.Host
: Set the target hostname within each rule.HTTP
andPaths
: We define theHTTPIngressRuleValue
and itsPaths
slice ([]networkingv1.HTTPIngressPath
).Path
andPathType
: Specify the URL path and how it should be matched (e.g.,Prefix
). Note thatPathType
requires a pointer, so we assign&pathTypePrefix
.Backend
andService
: Define the targetService
usingnetworkingv1.IngressBackend
andnetworkingv1.IngressServiceBackend
.Service Port (Name vs. Number)
: We specify the target port on the Service using eitherPort.Name
orPort.Number
.TLS
Slice: We define a slice ofnetworkingv1.IngressTLS
.Hosts
andSecretName
: Link the hostnames to the Kubernetes Secret containing the certificate and key. Prerequisite: The Secret (app-tls-secret
in this case) must exist in the same namespace and be of typekubernetes.io/tls
.Create
Call: We useclientset.NetworkingV1().Ingresses(namespace).Create(...)
to submit the definedIngress
object 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?