Support annotations on exposed Services

This adds a new .spec.expose.annotations field which is applied to the
service when it's created.
This commit is contained in:
Kevin McDermott
2026-07-06 08:19:30 +01:00
parent d94b9d685e
commit f1dbf33435
12 changed files with 338 additions and 8 deletions
+13
View File
@@ -1,6 +1,8 @@
package server
import (
"maps"
"k8s.io/apimachinery/pkg/util/intstr"
corev1 "k8s.io/api/core/v1"
@@ -10,6 +12,10 @@ import (
"github.com/rancher/k3k/pkg/controller"
)
// Service creates a Kubernetes Service for the given cluster.
//
// It sets the service type based on the cluster's expose configuration and adds the
// appropriate ports for k3s server and etcd.
func Service(cluster *v1beta1.Cluster) *corev1.Service {
service := &corev1.Service{
TypeMeta: metav1.TypeMeta{
@@ -50,6 +56,13 @@ func Service(cluster *v1beta1.Cluster) *corev1.Service {
// If expose is specified, set the type to the appropriate type
if cluster.Spec.Expose != nil {
expose := cluster.Spec.Expose
if expose.Annotations != nil {
if service.Annotations == nil {
service.Annotations = map[string]string{}
}
maps.Copy(service.Annotations, expose.Annotations)
}
switch {
case expose.LoadBalancer != nil:
@@ -0,0 +1,208 @@
package server
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/ptr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
)
func TestNoExposeService(t *testing.T) {
cluster := &v1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "test-namespace",
},
Spec: v1beta1.ClusterSpec{},
}
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "k3k-test-cluster-service",
Namespace: cluster.Namespace,
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"cluster": "test-cluster",
"role": "server",
},
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Name: "k3s-server-port",
Protocol: corev1.ProtocolTCP,
Port: int32(443),
TargetPort: intstr.FromInt(6443),
},
{
Name: "k3s-etcd-port",
Protocol: corev1.ProtocolTCP,
Port: int32(2379),
},
},
},
}
service := Service(cluster)
assert.Equal(t, want, service)
}
func TestExposeLoadBalancerService(t *testing.T) {
cluster := &v1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "test-namespace",
},
Spec: v1beta1.ClusterSpec{
Expose: &v1beta1.ExposeConfig{
LoadBalancer: &v1beta1.LoadBalancerConfig{
ServerPort: ptr.To[int32](9443),
},
},
},
}
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "k3k-test-cluster-service",
Namespace: cluster.Namespace,
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"cluster": "test-cluster",
"role": "server",
},
Type: corev1.ServiceTypeLoadBalancer,
Ports: []corev1.ServicePort{
{
Name: "k3s-server-port",
Protocol: corev1.ProtocolTCP,
Port: int32(9443),
TargetPort: intstr.FromInt(6443),
},
{
Name: "k3s-etcd-port",
Protocol: corev1.ProtocolTCP,
Port: int32(2379),
},
},
},
}
service := Service(cluster)
assert.Equal(t, want, service)
}
func TestExposeLoadBalancerServiceWithAnnotations(t *testing.T) {
cluster := &v1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "test-namespace",
},
Spec: v1beta1.ClusterSpec{
Expose: &v1beta1.ExposeConfig{
Annotations: map[string]string{
"example.com/testing": "test-annotation",
},
LoadBalancer: &v1beta1.LoadBalancerConfig{
ServerPort: ptr.To[int32](9443),
},
},
},
}
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "k3k-test-cluster-service",
Namespace: cluster.Namespace,
Annotations: map[string]string{
"example.com/testing": "test-annotation",
},
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"cluster": "test-cluster",
"role": "server",
},
Type: corev1.ServiceTypeLoadBalancer,
Ports: []corev1.ServicePort{
{
Name: "k3s-server-port",
Protocol: corev1.ProtocolTCP,
Port: int32(9443),
TargetPort: intstr.FromInt(6443),
},
{
Name: "k3s-etcd-port",
Protocol: corev1.ProtocolTCP,
Port: int32(2379),
},
},
},
}
service := Service(cluster)
assert.Equal(t, want, service)
}
func TestExposeNodePortService(t *testing.T) {
cluster := &v1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
Namespace: "test-namespace",
},
Spec: v1beta1.ClusterSpec{
Expose: &v1beta1.ExposeConfig{
NodePort: &v1beta1.NodePortConfig{
ServerPort: ptr.To[int32](7443),
},
},
},
}
want := &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "k3k-test-cluster-service",
Namespace: cluster.Namespace,
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"cluster": "test-cluster",
"role": "server",
},
Type: corev1.ServiceTypeNodePort,
Ports: []corev1.ServicePort{
{
Name: "k3s-etcd-port",
Protocol: corev1.ProtocolTCP,
Port: int32(2379),
},
},
},
}
service := Service(cluster)
assert.Equal(t, want, service)
}