diff --git a/artifacts/flagger/crd.yaml b/artifacts/flagger/crd.yaml index 6e2092bf..e79d9c4b 100644 --- a/artifacts/flagger/crd.yaml +++ b/artifacts/flagger/crd.yaml @@ -191,11 +191,18 @@ spec: appProtocol: description: Application protocol of the port type: string + trafficDistribution: + description: Traffic distribution of the service + type: string + enum: + - PreferClose + - PreferSameZone + - PreferSameNode targetPort: description: Container target port name x-kubernetes-int-or-string: true portDiscovery: - description: Enable port dicovery + description: Enable port discovery type: boolean headless: description: Headless if set to true, generates headless Kubernetes services. diff --git a/charts/flagger/crds/crd.yaml b/charts/flagger/crds/crd.yaml index 6e2092bf..e79d9c4b 100644 --- a/charts/flagger/crds/crd.yaml +++ b/charts/flagger/crds/crd.yaml @@ -191,11 +191,18 @@ spec: appProtocol: description: Application protocol of the port type: string + trafficDistribution: + description: Traffic distribution of the service + type: string + enum: + - PreferClose + - PreferSameZone + - PreferSameNode targetPort: description: Container target port name x-kubernetes-int-or-string: true portDiscovery: - description: Enable port dicovery + description: Enable port discovery type: boolean headless: description: Headless if set to true, generates headless Kubernetes services. diff --git a/docs/gitbook/usage/how-it-works.md b/docs/gitbook/usage/how-it-works.md index 31f109dd..78cb9c08 100644 --- a/docs/gitbook/usage/how-it-works.md +++ b/docs/gitbook/usage/how-it-works.md @@ -148,6 +148,7 @@ spec: targetPort: 9898 portDiscovery: true headless: false + trafficDistribution: PreferClose ``` The container port from the target workload should match the `service.port` or `service.targetPort`. @@ -155,7 +156,7 @@ The `service.name` is optional, defaults to `spec.targetRef.name`. 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). - +The `service.trafficDistribution` is optional, more details can be found [here](https://kubernetes.io/docs/concepts/services-networking/service/#traffic-distribution). 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. diff --git a/kustomize/base/flagger/crd.yaml b/kustomize/base/flagger/crd.yaml index 6e2092bf..e79d9c4b 100644 --- a/kustomize/base/flagger/crd.yaml +++ b/kustomize/base/flagger/crd.yaml @@ -191,11 +191,18 @@ spec: appProtocol: description: Application protocol of the port type: string + trafficDistribution: + description: Traffic distribution of the service + type: string + enum: + - PreferClose + - PreferSameZone + - PreferSameNode targetPort: description: Container target port name x-kubernetes-int-or-string: true portDiscovery: - description: Enable port dicovery + description: Enable port discovery type: boolean headless: description: Headless if set to true, generates headless Kubernetes services. diff --git a/pkg/apis/flagger/v1beta1/canary.go b/pkg/apis/flagger/v1beta1/canary.go index 0b88ed16..82fd00ac 100644 --- a/pkg/apis/flagger/v1beta1/canary.go +++ b/pkg/apis/flagger/v1beta1/canary.go @@ -20,10 +20,11 @@ import ( "fmt" "time" - "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1" - istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/fluxcd/flagger/pkg/apis/gatewayapi/v1beta1" + istiov1beta1 "github.com/fluxcd/flagger/pkg/apis/istio/v1beta1" ) const ( @@ -150,6 +151,11 @@ type CanaryService struct { // +optional AppProtocol string `json:"appProtocol,omitempty"` + // TrafficDistribution of the service + // https://kubernetes.io/docs/concepts/services-networking/service/#traffic-distribution + // +optional + TrafficDistribution string `json:"trafficDistribution,omitempty"` + // PortDiscovery adds all container ports to the generated Kubernetes service PortDiscovery bool `json:"portDiscovery"` diff --git a/pkg/canary/service_controller.go b/pkg/canary/service_controller.go index 97de616a..68b70593 100644 --- a/pkg/canary/service_controller.go +++ b/pkg/canary/service_controller.go @@ -165,6 +165,11 @@ func buildService(canary *flaggerv1.Canary, name string, src *corev1.Service) *c // Operation cannot be fulfilled on services "mysvc-canary": the object has been modified; please apply your changes to the latest version and try again delete(svc.ObjectMeta.Annotations, "kubectl.kubernetes.io/last-applied-configuration") } + + if v := canary.Spec.Service.TrafficDistribution; v != "" { + svc.Spec.TrafficDistribution = &v + } + return svc } diff --git a/pkg/router/kubernetes_default.go b/pkg/router/kubernetes_default.go index ffdd8059..f6c9fcb3 100644 --- a/pkg/router/kubernetes_default.go +++ b/pkg/router/kubernetes_default.go @@ -121,6 +121,10 @@ func (c *KubernetesDefaultRouter) reconcileService(canary *flaggerv1.Canary, nam svcSpec.Ports[0].AppProtocol = &v } + if v := canary.Spec.Service.TrafficDistribution; v != "" { + svcSpec.TrafficDistribution = &v + } + // set additional ports for n, p := range c.ports { cp := corev1.ServicePort{ diff --git a/pkg/router/kubernetes_default_test.go b/pkg/router/kubernetes_default_test.go index e252c877..c9864f77 100644 --- a/pkg/router/kubernetes_default_test.go +++ b/pkg/router/kubernetes_default_test.go @@ -64,6 +64,39 @@ func TestServiceRouter_Create(t *testing.T) { assert.Equal(t, "None", primarySvc.Spec.ClusterIP) } +func TestServiceRouter_TrafficDistribution(t *testing.T) { + mocks := newFixture(nil) + trafficDistribution := "PreferClose" + mocks.canary.Spec.Service.TrafficDistribution = trafficDistribution + + router := &KubernetesDefaultRouter{ + kubeClient: mocks.kubeClient, + flaggerClient: mocks.flaggerClient, + logger: mocks.logger, + } + + err := router.Initialize(mocks.canary) + require.NoError(t, err) + + err = router.Reconcile(mocks.canary) + require.NoError(t, err) + + canarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo-canary", metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, canarySvc.Spec.TrafficDistribution) + assert.Equal(t, trafficDistribution, *canarySvc.Spec.TrafficDistribution) + + primarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, primarySvc.Spec.TrafficDistribution) + assert.Equal(t, trafficDistribution, *primarySvc.Spec.TrafficDistribution) + + apexSvc, err := mocks.kubeClient.CoreV1().Services("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, apexSvc.Spec.TrafficDistribution) + assert.Equal(t, trafficDistribution, *apexSvc.Spec.TrafficDistribution) +} + func TestServiceRouter_Update(t *testing.T) { mocks := newFixture(nil) router := &KubernetesDefaultRouter{