Merge pull request #416 from weaveworks/service-name

Implement service name override
This commit is contained in:
Stefan Prodan
2020-01-28 21:22:41 +02:00
committed by GitHub
18 changed files with 171 additions and 154 deletions
+2
View File
@@ -86,6 +86,8 @@ spec:
kind: HorizontalPodAutoscaler
name: podinfo
service:
# service name (optional)
name: podinfo
# ClusterIP port number
port: 9898
# container port name or number (optional)
+3
View File
@@ -115,6 +115,9 @@ spec:
type: object
required: ["port"]
properties:
name:
description: Kubernetes service name
type: string
port:
description: Container port number
type: number
+3
View File
@@ -116,6 +116,9 @@ spec:
type: object
required: ['port']
properties:
name:
description: Kubernetes service name
type: string
port:
description: Container port number
type: number
+8 -4
View File
@@ -188,6 +188,8 @@ spec:
kind: Deployment
name: podinfo
service:
# service name (optional)
name: podinfo
# ClusterIP port number (required)
port: 9898
# container port name or number
@@ -196,19 +198,21 @@ spec:
portName: http
```
If the `service.name` is not specified, then `targetRef.name` is used for the apex domain and canary/primary services name prefix.
You should treat the service name as an immutable field, changing it could result in routing conflicts.
Based on the canary spec service, Flagger generates the following Kubernetes ClusterIP service:
* `<targetRef.name>.<namespace>.svc.cluster.local`
* `<service.name>.<namespace>.svc.cluster.local`
selector `app=<name>-primary`
* `<targetRef.name>-primary.<namespace>.svc.cluster.local`
* `<service.name>-primary.<namespace>.svc.cluster.local`
selector `app=<name>-primary`
* `<targetRef.name>-canary.<namespace>.svc.cluster.local`
* `<service.name>-canary.<namespace>.svc.cluster.local`
selector `app=<name>`
This ensures that traffic coming from a namespace outside the mesh to `podinfo.test:9898`
will be routed to the latest stable release of your app.
```yaml
apiVersion: v1
kind: Service
+2
View File
@@ -32,6 +32,8 @@ spec:
kind: HorizontalPodAutoscaler
name: podinfo
service:
# service name (optional)
name: podinfo
# ClusterIP port number
port: 9898
# ClusterIP port name can be http or grpc (default http)
+4 -4
View File
@@ -15,10 +15,10 @@ require (
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.10.0
gopkg.in/h2non/gock.v1 v1.0.14
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.1-beta.0
k8s.io/client-go v0.17.0
k8s.io/code-generator v0.17.0
k8s.io/api v0.17.1
k8s.io/apimachinery v0.17.1
k8s.io/client-go v0.17.1
k8s.io/code-generator v0.17.1
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f
)
+8 -9
View File
@@ -290,15 +290,14 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.1-beta.0 h1:0Wl/KpAiFOMe9to5h8x2Y6JnjV+BEWJiTcUk1Vx7zdE=
k8s.io/apimachinery v0.17.1-beta.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
k8s.io/code-generator v0.17.0 h1:y+KWtDWNqlJzJu/kUy8goJZO0X71PGIpAHLX8a0JYk0=
k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/api v0.17.1 h1:i46MidoDOE9tvQ0TTEYggf3ka/pziP1+tHI/GFVeJao=
k8s.io/api v0.17.1/go.mod h1:zxiAc5y8Ngn4fmhWUtSxuUlkfz1ixT7j9wESokELzOg=
k8s.io/apimachinery v0.17.1 h1:zUjS3szTxoUjTDYNvdFkYt2uMEXLcthcbp+7uZvWhYM=
k8s.io/apimachinery v0.17.1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/client-go v0.17.1 h1:LbbuZ5tI7OYx4et5DfRFcJuoojvpYO0c7vps2rgJsHY=
k8s.io/client-go v0.17.1/go.mod h1:HZtHJSC/VuSHcETN9QA5QDZky1tXiYrkF/7t7vRpO1A=
k8s.io/code-generator v0.17.1 h1:e3B1UqRzRUWygp7WD+QTRT3ZUahPIaRKF0OFa7duQwI=
k8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20190822140433-26a664648505 h1:ZY6yclUKVbZ+SdWnkfY+Je5vrMpKOxmGeKRbsXVmqYM=
+3
View File
@@ -115,6 +115,9 @@ spec:
type: object
required: ["port"]
properties:
name:
description: Kubernetes service name
type: string
port:
description: Container port number
type: number
+16 -3
View File
@@ -17,6 +17,7 @@ limitations under the License.
package v1alpha3
import (
"fmt"
"time"
hpav1 "k8s.io/api/autoscaling/v1"
@@ -69,7 +70,7 @@ type CanarySpec struct {
// virtual service spec
Service CanaryService `json:"service"`
// metrics and thresholds
// metrics, thresholds and webhooks spec
CanaryAnalysis CanaryAnalysis `json:"canaryAnalysis"`
// the maximum time in seconds for a canary deployment to make progress
@@ -92,8 +93,9 @@ type CanaryList struct {
}
// CanaryService is used to create ClusterIP services
// and Istio Virtual Service
// and service mesh or ingress routing objects
type CanaryService struct {
Name string `json:"name,omitempty"`
Port int32 `json:"port"`
PortName string `json:"portName,omitempty"`
TargetPort intstr.IntOrString `json:"targetPort,omitempty"`
@@ -126,7 +128,7 @@ type CanaryAnalysis struct {
Iterations int `json:"iterations,omitempty"`
}
// CanaryMetric holds the reference to Istio metrics used for canary analysis
// CanaryMetric holds the reference to metrics used for canary analysis
type CanaryMetric struct {
Name string `json:"name"`
Interval string `json:"interval,omitempty"`
@@ -171,6 +173,17 @@ type CanaryWebhookPayload struct {
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetServiceNames returns the apex, primary and canary Kubernetes service names
func (c *Canary) GetServiceNames() (apexName, primaryName, canaryName string) {
apexName = c.Spec.TargetRef.Name
if c.Spec.Service.Name != "" {
apexName = c.Spec.Service.Name
}
primaryName = fmt.Sprintf("%s-primary", apexName)
canaryName = fmt.Sprintf("%s-canary", apexName)
return
}
// GetProgressDeadlineSeconds returns the progress deadline (default 600s)
func (c *Canary) GetProgressDeadlineSeconds() int {
if c.Spec.ProgressDeadlineSeconds != nil {
+12 -4
View File
@@ -105,17 +105,25 @@ func NewController(
flaggerInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: ctrl.enqueue,
UpdateFunc: func(old, new interface{}) {
oldRoll, ok := checkCustomResourceType(old, logger)
oldCanary, ok := checkCustomResourceType(old, logger)
if !ok {
return
}
newRoll, ok := checkCustomResourceType(new, logger)
newCanary, ok := checkCustomResourceType(new, logger)
if !ok {
return
}
if diff := cmp.Diff(newRoll.Spec, oldRoll.Spec); diff != "" {
ctrl.logger.Debugf("Diff detected %s.%s %s", oldRoll.Name, oldRoll.Namespace, diff)
if diff := cmp.Diff(newCanary.Spec, oldCanary.Spec); diff != "" {
ctrl.logger.Debugf("Diff detected %s.%s %s", oldCanary.Name, oldCanary.Namespace, diff)
// warn about routing conflicts when service name changes
if oldCanary.Spec.Service.Name != "" && oldCanary.Spec.Service.Name != newCanary.Spec.Service.Name {
ctrl.logger.With("canary", fmt.Sprintf("%s.%s", oldCanary.Name, oldCanary.Namespace)).
Warnf("The service name changed to %s, remove %s objects to avoid routing conflicts",
newCanary.Spec.Service.Name, oldCanary.Spec.Service.Name)
}
ctrl.enqueue(new)
}
},
+19 -21
View File
@@ -33,16 +33,14 @@ func (ar *AppMeshRouter) Reconcile(canary *flaggerv1.Canary) error {
return fmt.Errorf("mesh name cannot be empty")
}
targetName := canary.Spec.TargetRef.Name
targetHost := fmt.Sprintf("%s.%s", targetName, canary.Namespace)
primaryName := fmt.Sprintf("%s-primary", targetName)
apexName, primaryName, canaryName := canary.GetServiceNames()
targetHost := fmt.Sprintf("%s.%s", apexName, canary.Namespace)
primaryHost := fmt.Sprintf("%s.%s", primaryName, canary.Namespace)
canaryName := fmt.Sprintf("%s-canary", targetName)
canaryHost := fmt.Sprintf("%s.%s", canaryName, canary.Namespace)
// sync virtual node e.g. app-namespace
// DNS app.namespace
err := ar.reconcileVirtualNode(canary, targetName, primaryHost)
err := ar.reconcileVirtualNode(canary, apexName, primaryHost)
if err != nil {
return err
}
@@ -162,14 +160,14 @@ func (ar *AppMeshRouter) reconcileVirtualNode(canary *flaggerv1.Canary, name str
// reconcileVirtualService creates or updates a virtual service
func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name string, canaryWeight int64) error {
targetName := canary.Spec.TargetRef.Name
canaryVirtualNode := fmt.Sprintf("%s-canary", targetName)
primaryVirtualNode := fmt.Sprintf("%s-primary", targetName)
apexName, _, _ := canary.GetServiceNames()
canaryVirtualNode := fmt.Sprintf("%s-canary", apexName)
primaryVirtualNode := fmt.Sprintf("%s-primary", apexName)
protocol := ar.getProtocol(canary)
routerName := targetName
routerName := apexName
if canaryWeight > 0 {
routerName = fmt.Sprintf("%s-canary", targetName)
routerName = fmt.Sprintf("%s-canary", apexName)
}
// App Mesh supports only URI prefix
routePrefix := "/"
@@ -208,7 +206,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
if len(canary.Spec.CanaryAnalysis.Match) > 0 && canaryWeight == 0 {
routes = []appmeshv1.Route{
{
Name: fmt.Sprintf("%s-a", targetName),
Name: fmt.Sprintf("%s-a", apexName),
Priority: int64p(10),
Http: &appmeshv1.HttpRoute{
Match: appmeshv1.HttpRouteMatch{
@@ -231,7 +229,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
},
},
{
Name: fmt.Sprintf("%s-b", targetName),
Name: fmt.Sprintf("%s-b", apexName),
Priority: int64p(20),
Http: &appmeshv1.HttpRoute{
Match: appmeshv1.HttpRouteMatch{
@@ -341,8 +339,8 @@ func (ar *AppMeshRouter) GetRoutes(canary *flaggerv1.Canary) (
mirrored bool,
err error,
) {
targetName := canary.Spec.TargetRef.Name
vsName := fmt.Sprintf("%s.%s", targetName, canary.Namespace)
apexName, _, _ := canary.GetServiceNames()
vsName := fmt.Sprintf("%s.%s", apexName, canary.Namespace)
vs, err := ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Get(vsName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
@@ -360,17 +358,17 @@ func (ar *AppMeshRouter) GetRoutes(canary *flaggerv1.Canary) (
targets := vs.Spec.Routes[0].Http.Action.WeightedTargets
for _, t := range targets {
if t.VirtualNodeName == fmt.Sprintf("%s-canary", targetName) {
if t.VirtualNodeName == fmt.Sprintf("%s-canary", apexName) {
canaryWeight = int(t.Weight)
}
if t.VirtualNodeName == fmt.Sprintf("%s-primary", targetName) {
if t.VirtualNodeName == fmt.Sprintf("%s-primary", apexName) {
primaryWeight = int(t.Weight)
}
}
if primaryWeight == 0 && canaryWeight == 0 {
err = fmt.Errorf("VirtualService %s does not contain routes for %s-primary and %s-canary",
vsName, targetName, targetName)
vsName, apexName, apexName)
}
mirrored = false
@@ -385,8 +383,8 @@ func (ar *AppMeshRouter) SetRoutes(
canaryWeight int,
mirrored bool,
) error {
targetName := canary.Spec.TargetRef.Name
vsName := fmt.Sprintf("%s.%s", targetName, canary.Namespace)
apexName, _, _ := canary.GetServiceNames()
vsName := fmt.Sprintf("%s.%s", apexName, canary.Namespace)
vs, err := ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Get(vsName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
@@ -399,11 +397,11 @@ func (ar *AppMeshRouter) SetRoutes(
vsClone.Spec.Routes[0].Http.Action = appmeshv1.HttpRouteAction{
WeightedTargets: []appmeshv1.WeightedTarget{
{
VirtualNodeName: fmt.Sprintf("%s-canary", targetName),
VirtualNodeName: fmt.Sprintf("%s-canary", apexName),
Weight: int64(canaryWeight),
},
{
VirtualNodeName: fmt.Sprintf("%s-primary", targetName),
VirtualNodeName: fmt.Sprintf("%s-primary", apexName),
Weight: int64(primaryWeight),
},
},
+17 -22
View File
@@ -26,9 +26,7 @@ type ContourRouter struct {
// 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)
apexName, primaryName, canaryName := canary.GetServiceNames()
newSpec := contourv1.HTTPProxySpec{
Routes: []contourv1.Route{
@@ -131,11 +129,11 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
}
}
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(targetName, metav1.GetOptions{})
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, metav1.GetOptions{})
if errors.IsNotFound(err) {
proxy = &contourv1.HTTPProxy{
ObjectMeta: metav1.ObjectMeta{
Name: targetName,
Name: apexName,
Namespace: canary.Namespace,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(canary, schema.GroupVersionKind{
@@ -154,7 +152,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
_, 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)
return fmt.Errorf("HTTPProxy %s.%s create error %v", apexName, canary.Namespace, err)
}
cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("HTTPProxy %s.%s created", proxy.GetName(), canary.Namespace)
@@ -162,7 +160,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
}
if err != nil {
return fmt.Errorf("HTTPProxy %s.%s query error %v", targetName, canary.Namespace, err)
return fmt.Errorf("HTTPProxy %s.%s query error %v", apexName, canary.Namespace, err)
}
// update HTTPProxy but keep the original destination weights
@@ -177,7 +175,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
_, 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)
return fmt.Errorf("HTTPProxy %s.%s update error %v", apexName, canary.Namespace, err)
}
cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("HTTPProxy %s.%s updated", proxy.GetName(), canary.Namespace)
@@ -194,21 +192,20 @@ func (cr *ContourRouter) GetRoutes(canary *flaggerv1.Canary) (
mirrored bool,
err error,
) {
targetName := canary.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", targetName)
apexName, primaryName, _ := canary.GetServiceNames()
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(targetName, metav1.GetOptions{})
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
err = fmt.Errorf("HTTPProxy %s.%s not found", targetName, canary.Namespace)
err = fmt.Errorf("HTTPProxy %s.%s not found", apexName, canary.Namespace)
return
}
err = fmt.Errorf("HTTPProxy %s.%s query error %v", targetName, canary.Namespace, err)
err = fmt.Errorf("HTTPProxy %s.%s query error %v", apexName, 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)
err = fmt.Errorf("HTTPProxy %s.%s services not found", apexName, canary.Namespace)
return
}
@@ -230,21 +227,19 @@ func (cr *ContourRouter) SetRoutes(
canaryWeight int,
mirrored bool,
) error {
targetName := canary.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", targetName)
canaryName := fmt.Sprintf("%s-canary", targetName)
apexName, primaryName, canaryName := canary.GetServiceNames()
if primaryWeight == 0 && canaryWeight == 0 {
return fmt.Errorf("HTTPProxy %s.%s update failed: no valid weights", targetName, canary.Namespace)
return fmt.Errorf("HTTPProxy %s.%s update failed: no valid weights", apexName, canary.Namespace)
}
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(targetName, metav1.GetOptions{})
proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, 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 not found", apexName, canary.Namespace)
}
return fmt.Errorf("HTTPProxy %s.%s query error %v", targetName, canary.Namespace, err)
return fmt.Errorf("HTTPProxy %s.%s query error %v", apexName, canary.Namespace, err)
}
proxy.Spec = contourv1.HTTPProxySpec{
@@ -349,7 +344,7 @@ func (cr *ContourRouter) SetRoutes(
_, 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 fmt.Errorf("HTTPProxy %s.%s update error %v", apexName, canary.Namespace, err)
}
return nil
}
+21 -21
View File
@@ -27,9 +27,9 @@ type GlooRouter struct {
// Reconcile creates or updates the Istio virtual service
func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
targetName := canary.Spec.TargetRef.Name
canaryName := fmt.Sprintf("%s-%s-canary-%v", canary.Namespace, canary.Spec.TargetRef.Name, canary.Spec.Service.Port)
primaryName := fmt.Sprintf("%s-%s-primary-%v", canary.Namespace, canary.Spec.TargetRef.Name, canary.Spec.Service.Port)
apexName, _, _ := canary.GetServiceNames()
canaryName := fmt.Sprintf("%s-%s-canary-%v", canary.Namespace, apexName, canary.Spec.Service.Port)
primaryName := fmt.Sprintf("%s-%s-primary-%v", canary.Namespace, apexName, canary.Spec.Service.Port)
newSpec := gloov1.UpstreamGroupSpec{
Destinations: []gloov1.WeightedDestination{
@@ -54,11 +54,11 @@ func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
},
}
upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(targetName, metav1.GetOptions{})
upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(apexName, metav1.GetOptions{})
if errors.IsNotFound(err) {
upstreamGroup = &gloov1.UpstreamGroup{
ObjectMeta: metav1.ObjectMeta{
Name: targetName,
Name: apexName,
Namespace: canary.Namespace,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(canary, schema.GroupVersionKind{
@@ -73,7 +73,7 @@ func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Create(upstreamGroup)
if err != nil {
return fmt.Errorf("UpstreamGroup %s.%s create error %v", targetName, canary.Namespace, err)
return fmt.Errorf("UpstreamGroup %s.%s create error %v", apexName, canary.Namespace, err)
}
gr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("UpstreamGroup %s.%s created", upstreamGroup.GetName(), canary.Namespace)
@@ -81,7 +81,7 @@ func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
}
if err != nil {
return fmt.Errorf("UpstreamGroup %s.%s query error %v", targetName, canary.Namespace, err)
return fmt.Errorf("UpstreamGroup %s.%s query error %v", apexName, canary.Namespace, err)
}
// update upstreamGroup but keep the original destination weights
@@ -96,7 +96,7 @@ func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Update(clone)
if err != nil {
return fmt.Errorf("UpstreamGroup %s.%s update error %v", targetName, canary.Namespace, err)
return fmt.Errorf("UpstreamGroup %s.%s update error %v", apexName, canary.Namespace, err)
}
gr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("UpstreamGroup %s.%s updated", upstreamGroup.GetName(), canary.Namespace)
@@ -113,21 +113,21 @@ func (gr *GlooRouter) GetRoutes(canary *flaggerv1.Canary) (
mirrored bool,
err error,
) {
targetName := canary.Spec.TargetRef.Name
apexName := canary.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-%s-primary-%v", canary.Namespace, canary.Spec.TargetRef.Name, canary.Spec.Service.Port)
upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(targetName, metav1.GetOptions{})
upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
err = fmt.Errorf("UpstreamGroup %s.%s not found", targetName, canary.Namespace)
err = fmt.Errorf("UpstreamGroup %s.%s not found", apexName, canary.Namespace)
return
}
err = fmt.Errorf("UpstreamGroup %s.%s query error %v", targetName, canary.Namespace, err)
err = fmt.Errorf("UpstreamGroup %s.%s query error %v", apexName, canary.Namespace, err)
return
}
if len(upstreamGroup.Spec.Destinations) < 2 {
err = fmt.Errorf("UpstreamGroup %s.%s destinations not found", targetName, canary.Namespace)
err = fmt.Errorf("UpstreamGroup %s.%s destinations not found", apexName, canary.Namespace)
return
}
@@ -149,21 +149,21 @@ func (gr *GlooRouter) SetRoutes(
canaryWeight int,
mirrored bool,
) error {
targetName := canary.Spec.TargetRef.Name
canaryName := fmt.Sprintf("%s-%s-canary-%v", canary.Namespace, canary.Spec.TargetRef.Name, canary.Spec.Service.Port)
primaryName := fmt.Sprintf("%s-%s-primary-%v", canary.Namespace, canary.Spec.TargetRef.Name, canary.Spec.Service.Port)
apexName, _, _ := canary.GetServiceNames()
canaryName := fmt.Sprintf("%s-%s-canary-%v", canary.Namespace, apexName, canary.Spec.Service.Port)
primaryName := fmt.Sprintf("%s-%s-primary-%v", canary.Namespace, apexName, canary.Spec.Service.Port)
if primaryWeight == 0 && canaryWeight == 0 {
return fmt.Errorf("RoutingRule %s.%s update failed: no valid weights", targetName, canary.Namespace)
return fmt.Errorf("RoutingRule %s.%s update failed: no valid weights", apexName, canary.Namespace)
}
upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(targetName, metav1.GetOptions{})
upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("UpstreamGroup %s.%s not found", targetName, canary.Namespace)
return fmt.Errorf("UpstreamGroup %s.%s not found", apexName, canary.Namespace)
}
return fmt.Errorf("UpstreamGroup %s.%s query error %v", targetName, canary.Namespace, err)
return fmt.Errorf("UpstreamGroup %s.%s query error %v", apexName, canary.Namespace, err)
}
upstreamGroup.Spec = gloov1.UpstreamGroupSpec{
@@ -191,7 +191,7 @@ func (gr *GlooRouter) SetRoutes(
_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Update(upstreamGroup)
if err != nil {
return fmt.Errorf("UpstreamGroup %s.%s update error %v", targetName, canary.Namespace, err)
return fmt.Errorf("UpstreamGroup %s.%s update error %v", apexName, canary.Namespace, err)
}
return nil
}
+4 -4
View File
@@ -27,8 +27,8 @@ func (i *IngressRouter) Reconcile(canary *flaggerv1.Canary) error {
return fmt.Errorf("ingress selector is empty")
}
targetName := canary.Spec.TargetRef.Name
canaryName := fmt.Sprintf("%s-canary", targetName)
apexName, _, _ := canary.GetServiceNames()
canaryName := fmt.Sprintf("%s-canary", apexName)
canaryIngressName := fmt.Sprintf("%s-canary", canary.Spec.IngressRef.Name)
ingress, err := i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Get(canary.Spec.IngressRef.Name, metav1.GetOptions{})
@@ -42,7 +42,7 @@ func (i *IngressRouter) Reconcile(canary *flaggerv1.Canary) error {
backendExists := false
for k, v := range ingressClone.Spec.Rules {
for x, y := range v.HTTP.Paths {
if y.Backend.ServiceName == targetName {
if y.Backend.ServiceName == apexName {
ingressClone.Spec.Rules[k].HTTP.Paths[x].Backend.ServiceName = canaryName
backendExists = true
break
@@ -51,7 +51,7 @@ func (i *IngressRouter) Reconcile(canary *flaggerv1.Canary) error {
}
if !backendExists {
return fmt.Errorf("backend %s not found in ingress %s", targetName, canary.Spec.IngressRef.Name)
return fmt.Errorf("backend %s not found in ingress %s", apexName, canary.Spec.IngressRef.Name)
}
canaryIngress, err := i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Get(canaryIngressName, metav1.GetOptions{})
+22 -27
View File
@@ -26,8 +26,7 @@ type IstioRouter struct {
// Reconcile creates or updates the Istio virtual service and destination rules
func (ir *IstioRouter) Reconcile(canary *flaggerv1.Canary) error {
canaryName := fmt.Sprintf("%s-canary", canary.Spec.TargetRef.Name)
primaryName := fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name)
_, primaryName, canaryName := canary.GetServiceNames()
err := ir.reconcileDestinationRule(canary, canaryName)
if err != nil {
@@ -101,19 +100,19 @@ func (ir *IstioRouter) reconcileDestinationRule(canary *flaggerv1.Canary, name s
}
func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
targetName := canary.Spec.TargetRef.Name
apexName, primaryName, canaryName := canary.GetServiceNames()
// set hosts and add the ClusterIP service host if it doesn't exists
hosts := canary.Spec.Service.Hosts
var hasServiceHost bool
for _, h := range hosts {
if h == targetName || h == "*" {
if h == apexName || h == "*" {
hasServiceHost = true
break
}
}
if !hasServiceHost {
hosts = append(hosts, targetName)
hosts = append(hosts, apexName)
}
// set gateways and add the mesh gateway if it doesn't exists
@@ -132,8 +131,6 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
}
// create destinations with primary weight 100% and canary weight 0%
primaryName := fmt.Sprintf("%s-primary", targetName)
canaryName := fmt.Sprintf("%s-canary", targetName)
canaryRoute := []istiov1alpha3.DestinationWeight{
makeDestination(canary, primaryName, 100),
makeDestination(canary, canaryName, 0),
@@ -181,12 +178,12 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
}
}
virtualService, err := ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(targetName, metav1.GetOptions{})
virtualService, err := ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(apexName, metav1.GetOptions{})
// insert
if errors.IsNotFound(err) {
virtualService = &istiov1alpha3.VirtualService{
ObjectMeta: metav1.ObjectMeta{
Name: targetName,
Name: apexName,
Namespace: canary.Namespace,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(canary, schema.GroupVersionKind{
@@ -200,7 +197,7 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
}
_, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Create(virtualService)
if err != nil {
return fmt.Errorf("VirtualService %s.%s create error %v", targetName, canary.Namespace, err)
return fmt.Errorf("VirtualService %s.%s create error %v", apexName, canary.Namespace, err)
}
ir.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("VirtualService %s.%s created", virtualService.GetName(), canary.Namespace)
@@ -208,7 +205,7 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
}
if err != nil {
return fmt.Errorf("VirtualService %s.%s query error %v", targetName, canary.Namespace, err)
return fmt.Errorf("VirtualService %s.%s query error %v", apexName, canary.Namespace, err)
}
// update service but keep the original destination weights and mirror
@@ -224,7 +221,7 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
_, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Update(vtClone)
if err != nil {
return fmt.Errorf("VirtualService %s.%s update error %v", targetName, canary.Namespace, err)
return fmt.Errorf("VirtualService %s.%s update error %v", apexName, canary.Namespace, err)
}
ir.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("VirtualService %s.%s updated", virtualService.GetName(), canary.Namespace)
@@ -241,22 +238,22 @@ func (ir *IstioRouter) GetRoutes(canary *flaggerv1.Canary) (
mirrored bool,
err error,
) {
targetName := canary.Spec.TargetRef.Name
apexName, primaryName, canaryName := canary.GetServiceNames()
vs := &istiov1alpha3.VirtualService{}
vs, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(targetName, metav1.GetOptions{})
vs, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
err = fmt.Errorf("VirtualService %s.%s not found", targetName, canary.Namespace)
err = fmt.Errorf("VirtualService %s.%s not found", apexName, canary.Namespace)
return
}
err = fmt.Errorf("VirtualService %s.%s query error %v", targetName, canary.Namespace, err)
err = fmt.Errorf("VirtualService %s.%s query error %v", apexName, canary.Namespace, err)
return
}
var httpRoute istiov1alpha3.HTTPRoute
for _, http := range vs.Spec.Http {
for _, r := range http.Route {
if r.Destination.Host == fmt.Sprintf("%s-canary", targetName) {
if r.Destination.Host == canaryName {
httpRoute = http
break
}
@@ -264,10 +261,10 @@ func (ir *IstioRouter) GetRoutes(canary *flaggerv1.Canary) (
}
for _, route := range httpRoute.Route {
if route.Destination.Host == fmt.Sprintf("%s-primary", targetName) {
if route.Destination.Host == primaryName {
primaryWeight = route.Weight
}
if route.Destination.Host == fmt.Sprintf("%s-canary", targetName) {
if route.Destination.Host == canaryName {
canaryWeight = route.Weight
}
}
@@ -277,7 +274,7 @@ func (ir *IstioRouter) GetRoutes(canary *flaggerv1.Canary) (
if primaryWeight == 0 && canaryWeight == 0 {
err = fmt.Errorf("VirtualService %s.%s does not contain routes for %s-primary and %s-canary",
targetName, canary.Namespace, targetName, targetName)
apexName, canary.Namespace, apexName, apexName)
}
return
@@ -290,17 +287,15 @@ func (ir *IstioRouter) SetRoutes(
canaryWeight int,
mirrored bool,
) error {
targetName := canary.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", targetName)
canaryName := fmt.Sprintf("%s-canary", targetName)
apexName, primaryName, canaryName := canary.GetServiceNames()
vs, err := ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(targetName, metav1.GetOptions{})
vs, err := ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("VirtualService %s.%s not found", targetName, canary.Namespace)
return fmt.Errorf("VirtualService %s.%s not found", apexName, canary.Namespace)
}
return fmt.Errorf("VirtualService %s.%s query error %v", targetName, canary.Namespace, err)
return fmt.Errorf("VirtualService %s.%s query error %v", apexName, canary.Namespace, err)
}
vsCopy := vs.DeepCopy()
@@ -360,7 +355,7 @@ func (ir *IstioRouter) SetRoutes(
vs, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Update(vsCopy)
if err != nil {
return fmt.Errorf("VirtualService %s.%s update failed: %v", targetName, canary.Namespace, err)
return fmt.Errorf("VirtualService %s.%s update failed: %v", apexName, canary.Namespace, err)
}
return nil
+7 -10
View File
@@ -29,18 +29,16 @@ type KubernetesDeploymentRouter struct {
// Initialize creates the primary and canary services
func (c *KubernetesDeploymentRouter) Initialize(canary *flaggerv1.Canary) error {
targetName := canary.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", targetName)
canaryName := fmt.Sprintf("%s-canary", targetName)
_, primaryName, canaryName := canary.GetServiceNames()
// canary svc
err := c.reconcileService(canary, canaryName, targetName)
err := c.reconcileService(canary, canaryName, canary.Spec.TargetRef.Name)
if err != nil {
return err
}
// primary svc
err = c.reconcileService(canary, primaryName, primaryName)
err = c.reconcileService(canary, primaryName, fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name))
if err != nil {
return err
}
@@ -50,11 +48,10 @@ func (c *KubernetesDeploymentRouter) Initialize(canary *flaggerv1.Canary) error
// Reconcile creates or updates the main service
func (c *KubernetesDeploymentRouter) Reconcile(canary *flaggerv1.Canary) error {
targetName := canary.Spec.TargetRef.Name
primaryName := fmt.Sprintf("%s-primary", targetName)
apexName, _, _ := canary.GetServiceNames()
// main svc
err := c.reconcileService(canary, targetName, primaryName)
err := c.reconcileService(canary, apexName, fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name))
if err != nil {
return err
}
@@ -70,7 +67,7 @@ func (c *KubernetesDeploymentRouter) GetRoutes(canary *flaggerv1.Canary) (primar
return 0, 0, nil
}
func (c *KubernetesDeploymentRouter) reconcileService(canary *flaggerv1.Canary, name string, target string) error {
func (c *KubernetesDeploymentRouter) reconcileService(canary *flaggerv1.Canary, name string, podSelector string) error {
portName := canary.Spec.Service.PortName
if portName == "" {
portName = "http"
@@ -87,7 +84,7 @@ func (c *KubernetesDeploymentRouter) reconcileService(canary *flaggerv1.Canary,
svcSpec := corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
Selector: map[string]string{c.labelSelector: target},
Selector: map[string]string{c.labelSelector: podSelector},
Ports: []corev1.ServicePort{
{
Name: portName,
+17 -23
View File
@@ -28,15 +28,13 @@ type SmiRouter struct {
// Reconcile creates or updates the SMI traffic split
func (sr *SmiRouter) Reconcile(canary *flaggerv1.Canary) error {
targetName := canary.Spec.TargetRef.Name
canaryName := fmt.Sprintf("%s-canary", targetName)
primaryName := fmt.Sprintf("%s-primary", targetName)
apexName, primaryName, canaryName := canary.GetServiceNames()
var host string
if len(canary.Spec.Service.Hosts) > 0 {
host = canary.Spec.Service.Hosts[0]
} else {
host = targetName
host = apexName
}
tsSpec := smiv1.TrafficSplitSpec{
@@ -53,12 +51,12 @@ func (sr *SmiRouter) Reconcile(canary *flaggerv1.Canary) error {
},
}
ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(targetName, metav1.GetOptions{})
ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(apexName, metav1.GetOptions{})
// create traffic split
if errors.IsNotFound(err) {
t := &smiv1.TrafficSplit{
ObjectMeta: metav1.ObjectMeta{
Name: targetName,
Name: apexName,
Namespace: canary.Namespace,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(canary, schema.GroupVersionKind{
@@ -83,7 +81,7 @@ func (sr *SmiRouter) Reconcile(canary *flaggerv1.Canary) error {
}
if err != nil {
return fmt.Errorf("traffic split %s query error %v", targetName, err)
return fmt.Errorf("traffic split %s query error %v", apexName, err)
}
// update traffic split
@@ -93,11 +91,11 @@ func (sr *SmiRouter) Reconcile(canary *flaggerv1.Canary) error {
_, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Update(tsClone)
if err != nil {
return fmt.Errorf("TrafficSplit %s update error %v", targetName, err)
return fmt.Errorf("TrafficSplit %s update error %v", apexName, err)
}
sr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
Infof("TrafficSplit %s.%s updated", targetName, canary.Namespace)
Infof("TrafficSplit %s.%s updated", apexName, canary.Namespace)
return nil
}
@@ -111,16 +109,14 @@ func (sr *SmiRouter) GetRoutes(canary *flaggerv1.Canary) (
mirrored bool,
err error,
) {
targetName := canary.Spec.TargetRef.Name
canaryName := fmt.Sprintf("%s-canary", targetName)
primaryName := fmt.Sprintf("%s-primary", targetName)
ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(targetName, metav1.GetOptions{})
apexName, primaryName, canaryName := canary.GetServiceNames()
ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
err = fmt.Errorf("TrafficSplit %s.%s not found", targetName, canary.Namespace)
err = fmt.Errorf("TrafficSplit %s.%s not found", apexName, canary.Namespace)
return
}
err = fmt.Errorf("TrafficSplit %s.%s query error %v", targetName, canary.Namespace, err)
err = fmt.Errorf("TrafficSplit %s.%s query error %v", apexName, canary.Namespace, err)
return
}
@@ -136,7 +132,7 @@ func (sr *SmiRouter) GetRoutes(canary *flaggerv1.Canary) (
if primaryWeight == 0 && canaryWeight == 0 {
err = fmt.Errorf("TrafficSplit %s.%s does not contain routes for %s and %s",
targetName, canary.Namespace, primaryName, canaryName)
apexName, canary.Namespace, primaryName, canaryName)
}
mirrored = false
@@ -151,16 +147,14 @@ func (sr *SmiRouter) SetRoutes(
canaryWeight int,
mirrored bool,
) error {
targetName := canary.Spec.TargetRef.Name
canaryName := fmt.Sprintf("%s-canary", targetName)
primaryName := fmt.Sprintf("%s-primary", targetName)
ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(targetName, metav1.GetOptions{})
apexName, primaryName, canaryName := canary.GetServiceNames()
ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(apexName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return fmt.Errorf("TrafficSplit %s.%s not found", targetName, canary.Namespace)
return fmt.Errorf("TrafficSplit %s.%s not found", apexName, canary.Namespace)
}
return fmt.Errorf("TrafficSplit %s.%s query error %v", targetName, canary.Namespace, err)
return fmt.Errorf("TrafficSplit %s.%s query error %v", apexName, canary.Namespace, err)
}
backends := []smiv1.TrafficSplitBackend{
@@ -179,7 +173,7 @@ func (sr *SmiRouter) SetRoutes(
_, err = sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Update(tsClone)
if err != nil {
return fmt.Errorf("TrafficSplit %s update error %v", targetName, err)
return fmt.Errorf("TrafficSplit %s update error %v", apexName, err)
}
return nil
+3 -2
View File
@@ -33,6 +33,7 @@ spec:
service:
port: 80
targetPort: 9898
name: podinfo-svc
portDiscovery: true
canaryAnalysis:
interval: 15s
@@ -55,13 +56,13 @@ spec:
timeout: 10s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary/token | grep token"
cmd: "curl -sd 'test' http://podinfo-svc-canary/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "hey -z 10m -q 10 -c 2 http://podinfo-canary.test/"
cmd: "hey -z 10m -q 10 -c 2 http://podinfo-svc-canary.test/"
logCmdOutput: "true"
EOF