Files
k3k/pkg/controller/cluster/server/service.go
T
Kevin McDermott f1dbf33435 Support annotations on exposed Services
This adds a new .spec.expose.annotations field which is applied to the
service when it's created.
2026-07-06 08:19:30 +01:00

177 lines
5.5 KiB
Go

package server
import (
"maps"
"k8s.io/apimachinery/pkg/util/intstr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
"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{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: ServiceName(cluster.Name),
Namespace: cluster.Namespace,
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"cluster": cluster.Name,
"role": "server",
},
},
}
k3sServerPort := corev1.ServicePort{
Name: "k3s-server-port",
Protocol: corev1.ProtocolTCP,
Port: httpsPort,
TargetPort: intstr.FromInt32(k3sServerPort),
}
etcdPort := corev1.ServicePort{
Name: "k3s-etcd-port",
Protocol: corev1.ProtocolTCP,
Port: etcdPort,
}
// If no expose is specified, default to ClusterIP
if cluster.Spec.Expose == nil {
service.Spec.Type = corev1.ServiceTypeClusterIP
service.Spec.Ports = append(service.Spec.Ports, k3sServerPort, etcdPort)
}
// 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:
service.Spec.Type = corev1.ServiceTypeLoadBalancer
addLoadBalancerPorts(service, *expose.LoadBalancer, k3sServerPort, etcdPort)
case expose.NodePort != nil:
service.Spec.Type = corev1.ServiceTypeNodePort
addNodePortPorts(service, *expose.NodePort, k3sServerPort, etcdPort)
default:
// default to clusterIP for ingress or empty expose config
service.Spec.Type = corev1.ServiceTypeClusterIP
service.Spec.Ports = append(service.Spec.Ports, k3sServerPort, etcdPort)
}
}
return service
}
// addLoadBalancerPorts adds the load balancer ports to the service
func addLoadBalancerPorts(service *corev1.Service, loadbalancerConfig v1beta1.LoadBalancerConfig, k3sServerPort, etcdPort corev1.ServicePort) {
// If the server port is not specified, use the default port
if loadbalancerConfig.ServerPort == nil {
service.Spec.Ports = append(service.Spec.Ports, k3sServerPort)
} else if *loadbalancerConfig.ServerPort > 0 {
// If the server port is specified, set the port, otherwise the service will not be exposed
k3sServerPort.Port = *loadbalancerConfig.ServerPort
service.Spec.Ports = append(service.Spec.Ports, k3sServerPort)
}
// If the etcd port is not specified, use the default port
if loadbalancerConfig.ETCDPort == nil {
service.Spec.Ports = append(service.Spec.Ports, etcdPort)
} else if *loadbalancerConfig.ETCDPort > 0 {
// If the etcd port is specified, set the port, otherwise the service will not be exposed
etcdPort.Port = *loadbalancerConfig.ETCDPort
service.Spec.Ports = append(service.Spec.Ports, etcdPort)
}
}
// addNodePortPorts adds the node port ports to the service
func addNodePortPorts(service *corev1.Service, nodePortConfig v1beta1.NodePortConfig, k3sServerPort, etcdPort corev1.ServicePort) {
// If the server port is not specified Kubernetes will set the node port to a random port between 30000-32767
if nodePortConfig.ServerPort == nil {
service.Spec.Ports = append(service.Spec.Ports, k3sServerPort)
} else {
serverNodePort := *nodePortConfig.ServerPort
// If the server port is in the range of 30000-32767, set the node port
// otherwise the service will not be exposed
if serverNodePort >= 30000 && serverNodePort <= 32767 {
k3sServerPort.NodePort = serverNodePort
service.Spec.Ports = append(service.Spec.Ports, k3sServerPort)
}
}
// If the etcd port is not specified Kubernetes will set the node port to a random port between 30000-32767
if nodePortConfig.ETCDPort == nil {
service.Spec.Ports = append(service.Spec.Ports, etcdPort)
} else {
etcdNodePort := *nodePortConfig.ETCDPort
// If the etcd port is in the range of 30000-32767, set the node port
// otherwise the service will not be exposed
if etcdNodePort >= 30000 && etcdNodePort <= 32767 {
etcdPort.NodePort = etcdNodePort
service.Spec.Ports = append(service.Spec.Ports, etcdPort)
}
}
}
func (s *Server) StatefulServerService() *corev1.Service {
return &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: headlessServiceName(s.cluster.Name),
Namespace: s.cluster.Namespace,
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
ClusterIP: corev1.ClusterIPNone,
Selector: map[string]string{
"cluster": s.cluster.Name,
"role": "server",
},
Ports: []corev1.ServicePort{
{
Name: "k3s-server-port",
Protocol: corev1.ProtocolTCP,
Port: httpsPort,
TargetPort: intstr.FromInt32(k3sServerPort),
},
{
Name: "k3s-etcd-port",
Protocol: corev1.ProtocolTCP,
Port: etcdPort,
},
},
},
}
}
func ServiceName(clusterName string) string {
return controller.SafeConcatNameWithPrefix(clusterName, "service")
}
func headlessServiceName(clusterName string) string {
return controller.SafeConcatNameWithPrefix(clusterName, "service", "headless")
}