Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement service name override #416

Merged
merged 6 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions artifacts/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ spec:
type: object
required: ["port"]
properties:
name:
description: Kubernetes service name
type: string
port:
description: Container port number
type: number
Expand Down
3 changes: 3 additions & 0 deletions charts/flagger/templates/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ spec:
type: object
required: ['port']
properties:
name:
description: Kubernetes service name
type: string
port:
description: Container port number
type: number
Expand Down
12 changes: 8 additions & 4 deletions docs/gitbook/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/gitbook/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
17 changes: 8 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
3 changes: 3 additions & 0 deletions kustomize/base/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ spec:
type: object
required: ["port"]
properties:
name:
description: Kubernetes service name
type: string
port:
description: Container port number
type: number
Expand Down
19 changes: 16 additions & 3 deletions pkg/apis/flagger/v1alpha3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1alpha3

import (
"fmt"
"time"

hpav1 "k8s.io/api/autoscaling/v1"
Expand Down Expand Up @@ -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
Expand All @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 12 additions & 4 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
},
Expand Down
40 changes: 19 additions & 21 deletions pkg/router/appmesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 := "/"
Expand Down Expand Up @@ -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{
Expand All @@ -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{
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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),
},
},
Expand Down
Loading