feat: add option to generate headless services

Add a new field `.spec.service.headless` which if set to true results in
Flagger generating headless Services, i.e. with the Service's
`.spec.clusterIP` set to None.

Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
This commit is contained in:
Sanskar Jaiswal 2025-01-14 00:15:52 +05:30
parent ff4051f728
commit 45618b90db
No known key found for this signature in database
GPG Key ID: 5982D0279C227FFD
7 changed files with 26 additions and 0 deletions

View File

@ -198,6 +198,9 @@ spec:
portDiscovery:
description: Enable port dicovery
type: boolean
headless:
description: Headless if set to true, generates headless Kubernetes services.
type: boolean
timeout:
description: HTTP or gRPC request timeout
type: string

View File

@ -198,6 +198,9 @@ spec:
portDiscovery:
description: Enable port dicovery
type: boolean
headless:
description: Headless if set to true, generates headless Kubernetes services.
type: boolean
timeout:
description: HTTP or gRPC request timeout
type: string

View File

@ -147,6 +147,7 @@ spec:
appProtocol: http
targetPort: 9898
portDiscovery: true
headless: false
```
The container port from the target workload should match the `service.port` or `service.targetPort`.
@ -155,6 +156,7 @@ The `service.targetPort` can be a container port number or name.
The `service.portName` is optional (defaults to `http`), if your workload uses gRPC then set the port name to `grpc`.
The `service.appProtocol` is optional, more details can be found [here](https://kubernetes.io/docs/concepts/services-networking/service/#application-protocol).
If port discovery is enabled, Flagger scans the target workload and extracts the containers ports
excluding the port specified in the canary service and service mesh sidecar ports.
These ports will be used when generating the ClusterIP services.
@ -204,6 +206,9 @@ Note that the `apex` annotations are added to both the generated Kubernetes Serv
generated service mesh/ingress object. This allows using external-dns with Istio `VirtualServices`
and `TraefikServices`. Beware of configuration conflicts [here](../faq.md#ExternalDNS).
If you want for the generated Kubernetes ClusterIP services to be [headless](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services),
then set `service.headless` to true.
Besides port mapping and metadata, the service specification can
contain URI match and rewrite rules, timeout and retry polices:

View File

@ -198,6 +198,9 @@ spec:
portDiscovery:
description: Enable port dicovery
type: boolean
headless:
description: Headless if set to true, generates headless Kubernetes services.
type: boolean
timeout:
description: HTTP or gRPC request timeout
type: string

View File

@ -146,6 +146,11 @@ type CanaryService struct {
// PortDiscovery adds all container ports to the generated Kubernetes service
PortDiscovery bool `json:"portDiscovery"`
// Headless if set to true, generates headless Kubernetes services.
// ref: https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
// +optional
Headless bool `json:"headless,omitempty"`
// Timeout of the HTTP or gRPC request
// +optional
Timeout string `json:"timeout,omitempty"`

View File

@ -113,6 +113,9 @@ func (c *KubernetesDefaultRouter) reconcileService(canary *flaggerv1.Canary, nam
},
},
}
if canary.Spec.Service.Headless {
svcSpec.ClusterIP = "None"
}
if v := canary.Spec.Service.AppProtocol; v != "" {
svcSpec.Ports[0].AppProtocol = &v

View File

@ -34,6 +34,8 @@ import (
func TestServiceRouter_Create(t *testing.T) {
mocks := newFixture(nil)
mocks.canary.Spec.Service.Headless = true
router := &KubernetesDefaultRouter{
kubeClient: mocks.kubeClient,
flaggerClient: mocks.flaggerClient,
@ -53,11 +55,13 @@ func TestServiceRouter_Create(t *testing.T) {
assert.Equal(t, &appProtocol, canarySvc.Spec.Ports[0].AppProtocol)
assert.Equal(t, "http", canarySvc.Spec.Ports[0].Name)
assert.Equal(t, int32(9898), canarySvc.Spec.Ports[0].Port)
assert.Equal(t, "None", canarySvc.Spec.ClusterIP)
primarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{})
require.NoError(t, err)
assert.Equal(t, "http", primarySvc.Spec.Ports[0].Name)
assert.Equal(t, int32(9898), primarySvc.Spec.Ports[0].Port)
assert.Equal(t, "None", primarySvc.Spec.ClusterIP)
}
func TestServiceRouter_Update(t *testing.T) {