mirror of
https://github.com/fluxcd/flagger.git
synced 2026-04-15 06:57:34 +00:00
Implement Contour router for traffic shifting
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"go.uber.org/zap"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
|
||||
contourv1 "github.com/weaveworks/flagger/pkg/apis/projectcontour/v1"
|
||||
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
// ContourRouter is managing HTTPProxy objects
|
||||
type ContourRouter struct {
|
||||
kubeClient kubernetes.Interface
|
||||
contourClient clientset.Interface
|
||||
flaggerClient clientset.Interface
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
// Reconcile creates or updates the HTTP proxy
|
||||
func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
|
||||
targetName := canary.Spec.TargetRef.Name
|
||||
primaryName := fmt.Sprintf("%s-primary", targetName)
|
||||
canaryName := fmt.Sprintf("%s-canary", targetName)
|
||||
|
||||
newSpec := contourv1.HTTPProxySpec{
|
||||
Routes: []contourv1.Route{
|
||||
{
|
||||
Conditions: []contourv1.Condition{
|
||||
{
|
||||
Prefix: "/",
|
||||
},
|
||||
},
|
||||
Services: []contourv1.Service{
|
||||
{
|
||||
Name: primaryName,
|
||||
Port: int(canary.Spec.Service.Port),
|
||||
Weight: uint32(100),
|
||||
},
|
||||
{
|
||||
Name: canaryName,
|
||||
Port: int(canary.Spec.Service.Port),
|
||||
Weight: uint32(0),
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
proxy = &contourv1.HTTPProxy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: targetName,
|
||||
Namespace: canary.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(canary, schema.GroupVersionKind{
|
||||
Group: flaggerv1.SchemeGroupVersion.Group,
|
||||
Version: flaggerv1.SchemeGroupVersion.Version,
|
||||
Kind: flaggerv1.CanaryKind,
|
||||
}),
|
||||
},
|
||||
},
|
||||
Spec: newSpec,
|
||||
}
|
||||
|
||||
_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Create(proxy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTPProxy %s.%s create error %v", targetName, canary.Namespace, err)
|
||||
}
|
||||
cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
|
||||
Infof("HTTPProxy %s.%s created", proxy.GetName(), canary.Namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTPProxy %s.%s query error %v", targetName, canary.Namespace, err)
|
||||
}
|
||||
|
||||
// update HTTPProxy but keep the original destination weights
|
||||
if proxy != nil {
|
||||
if diff := cmp.Diff(
|
||||
newSpec,
|
||||
proxy.Spec,
|
||||
cmpopts.IgnoreFields(contourv1.Service{}, "Weight"),
|
||||
); diff != "" {
|
||||
clone := proxy.DeepCopy()
|
||||
clone.Spec = newSpec
|
||||
|
||||
_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Update(clone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTPProxy %s.%s update error %v", targetName, canary.Namespace, err)
|
||||
}
|
||||
cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
|
||||
Infof("HTTPProxy %s.%s updated", proxy.GetName(), canary.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRoutes returns the service weight for primary and canary
|
||||
func (cr *ContourRouter) GetRoutes(canary *flaggerv1.Canary) (
|
||||
primaryWeight int,
|
||||
canaryWeight int,
|
||||
mirrored bool,
|
||||
err error,
|
||||
) {
|
||||
targetName := canary.Spec.TargetRef.Name
|
||||
primaryName := fmt.Sprintf("%s-primary", targetName)
|
||||
|
||||
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
err = fmt.Errorf("HTTPProxy %s.%s not found", targetName, canary.Namespace)
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("HTTPProxy %s.%s query error %v", targetName, canary.Namespace, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(proxy.Spec.Routes) < 1 || len(proxy.Spec.Routes[0].Services) < 2 {
|
||||
err = fmt.Errorf("HTTPProxy %s.%s services not found", targetName, canary.Namespace)
|
||||
return
|
||||
}
|
||||
|
||||
for _, dst := range proxy.Spec.Routes[0].Services {
|
||||
if dst.Name == primaryName {
|
||||
primaryWeight = int(dst.Weight)
|
||||
canaryWeight = 100 - primaryWeight
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetRoutes updates the service weight for primary and canary
|
||||
func (cr *ContourRouter) SetRoutes(
|
||||
canary *flaggerv1.Canary,
|
||||
primaryWeight int,
|
||||
canaryWeight int,
|
||||
mirrored bool,
|
||||
) error {
|
||||
targetName := canary.Spec.TargetRef.Name
|
||||
primaryName := fmt.Sprintf("%s-primary", targetName)
|
||||
canaryName := fmt.Sprintf("%s-canary", targetName)
|
||||
|
||||
if primaryWeight == 0 && canaryWeight == 0 {
|
||||
return fmt.Errorf("HTTPProxy %s.%s update failed: no valid weights", targetName, canary.Namespace)
|
||||
}
|
||||
|
||||
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(targetName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return fmt.Errorf("HTTPProxy %s.%s not found", targetName, canary.Namespace)
|
||||
|
||||
}
|
||||
return fmt.Errorf("HTTPProxy %s.%s query error %v", targetName, canary.Namespace, err)
|
||||
}
|
||||
|
||||
proxy.Spec = contourv1.HTTPProxySpec{
|
||||
Routes: []contourv1.Route{
|
||||
{
|
||||
Conditions: []contourv1.Condition{
|
||||
{
|
||||
Prefix: "/",
|
||||
},
|
||||
},
|
||||
Services: []contourv1.Service{
|
||||
{
|
||||
Name: primaryName,
|
||||
Port: int(canary.Spec.Service.Port),
|
||||
Weight: uint32(primaryWeight),
|
||||
},
|
||||
{
|
||||
Name: canaryName,
|
||||
Port: int(canary.Spec.Service.Port),
|
||||
Weight: uint32(canaryWeight),
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Update(proxy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTPProxy %s.%s update error %v", targetName, canary.Namespace, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -93,6 +93,13 @@ func (factory *Factory) MeshRouter(provider string) Interface {
|
||||
smiClient: factory.meshClient,
|
||||
targetMesh: "linkerd",
|
||||
}
|
||||
case provider == "contour":
|
||||
return &ContourRouter{
|
||||
logger: factory.logger,
|
||||
flaggerClient: factory.flaggerClient,
|
||||
kubeClient: factory.kubeClient,
|
||||
contourClient: factory.meshClient,
|
||||
}
|
||||
case strings.HasPrefix(provider, "gloo"):
|
||||
upstreamDiscoveryNs := "gloo-system"
|
||||
if strings.HasPrefix(provider, "gloo:") {
|
||||
|
||||
Reference in New Issue
Block a user