mirror of
https://github.com/fluxcd/flagger.git
synced 2026-03-02 17:51:00 +00:00
Expose canaries on public domains with App Mesh Gateway
- map canary service hosts to domain gateway annotation - map canary retries and timeout to gateway annotations
This commit is contained in:
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -80,7 +81,7 @@ func (ar *AppMeshRouter) Reconcile(canary *flaggerv1.Canary) error {
|
||||
// reconcileVirtualNode creates or updates a virtual node
|
||||
// the virtual node naming format is name-role-namespace
|
||||
func (ar *AppMeshRouter) reconcileVirtualNode(canary *flaggerv1.Canary, name string, host string) error {
|
||||
protocol := getProtocol(canary)
|
||||
protocol := ar.getProtocol(canary)
|
||||
vnSpec := appmeshv1.VirtualNodeSpec{
|
||||
MeshName: canary.Spec.Service.MeshName,
|
||||
Listeners: []appmeshv1.Listener{
|
||||
@@ -164,7 +165,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
|
||||
targetName := canary.Spec.TargetRef.Name
|
||||
canaryVirtualNode := fmt.Sprintf("%s-canary", targetName)
|
||||
primaryVirtualNode := fmt.Sprintf("%s-primary", targetName)
|
||||
protocol := getProtocol(canary)
|
||||
protocol := ar.getProtocol(canary)
|
||||
|
||||
routerName := targetName
|
||||
if canaryWeight > 0 {
|
||||
@@ -212,7 +213,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
|
||||
Http: &appmeshv1.HttpRoute{
|
||||
Match: appmeshv1.HttpRouteMatch{
|
||||
Prefix: routePrefix,
|
||||
Headers: makeHeaders(canary),
|
||||
Headers: ar.makeHeaders(canary),
|
||||
},
|
||||
RetryPolicy: makeRetryPolicy(canary),
|
||||
Action: appmeshv1.HttpRouteAction{
|
||||
@@ -284,6 +285,15 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
|
||||
},
|
||||
Spec: vsSpec,
|
||||
}
|
||||
|
||||
// set App Mesh Gateway annotation on primary virtual service
|
||||
if canaryWeight == 0 {
|
||||
a := ar.gatewayAnnotations(canary)
|
||||
if len(a) > 0 {
|
||||
virtualService.ObjectMeta.Annotations = a
|
||||
}
|
||||
}
|
||||
|
||||
_, err = ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Create(virtualService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("VirtualService %s create error %v", name, err)
|
||||
@@ -304,6 +314,14 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
|
||||
vsClone.Spec = vsSpec
|
||||
vsClone.Spec.Routes[0].Http.Action = virtualService.Spec.Routes[0].Http.Action
|
||||
|
||||
// update App Mesh Gateway annotation on primary virtual service
|
||||
if canaryWeight == 0 {
|
||||
a := ar.gatewayAnnotations(canary)
|
||||
if len(a) > 0 {
|
||||
vsClone.ObjectMeta.Annotations = a
|
||||
}
|
||||
}
|
||||
|
||||
_, err = ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Update(vsClone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("VirtualService %s update error %v", name, err)
|
||||
@@ -432,7 +450,7 @@ func makeRetryPolicy(canary *flaggerv1.Canary) *appmeshv1.HttpRetryPolicy {
|
||||
}
|
||||
|
||||
// makeRetryPolicy creates an App Mesh HttpRouteHeader from the Canary.CanaryAnalysis.Match
|
||||
func makeHeaders(canary *flaggerv1.Canary) []appmeshv1.HttpRouteHeader {
|
||||
func (ar *AppMeshRouter) makeHeaders(canary *flaggerv1.Canary) []appmeshv1.HttpRouteHeader {
|
||||
headers := []appmeshv1.HttpRouteHeader{}
|
||||
|
||||
for _, m := range canary.Spec.CanaryAnalysis.Match {
|
||||
@@ -453,13 +471,32 @@ func makeHeaders(canary *flaggerv1.Canary) []appmeshv1.HttpRouteHeader {
|
||||
return headers
|
||||
}
|
||||
|
||||
func getProtocol(canary *flaggerv1.Canary) string {
|
||||
func (ar *AppMeshRouter) getProtocol(canary *flaggerv1.Canary) string {
|
||||
if strings.Contains(canary.Spec.Service.PortName, "grpc") {
|
||||
return "grpc"
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (ar *AppMeshRouter) gatewayAnnotations(canary *flaggerv1.Canary) map[string]string {
|
||||
a := make(map[string]string)
|
||||
domains := ""
|
||||
for _, value := range canary.Spec.Service.Hosts {
|
||||
domains += value + ","
|
||||
}
|
||||
if domains != "" {
|
||||
a["gateway.appmesh.k8s.aws/expose"] = "true"
|
||||
a["gateway.appmesh.k8s.aws/domain"] = domains
|
||||
if canary.Spec.Service.Timeout != "" {
|
||||
a["gateway.appmesh.k8s.aws/timeout"] = canary.Spec.Service.Timeout
|
||||
}
|
||||
if canary.Spec.Service.Retries != nil && canary.Spec.Service.Retries.Attempts > 0 {
|
||||
a["gateway.appmesh.k8s.aws/retries"] = strconv.Itoa(canary.Spec.Service.Retries.Attempts)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func int64p(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -226,3 +228,45 @@ func TestAppmeshRouter_ABTest(t *testing.T) {
|
||||
t.Errorf("Got http match header exact %v wanted %v", exactMatch, "test")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppmeshRouter_Gateway(t *testing.T) {
|
||||
mocks := setupfakeClients()
|
||||
router := &AppMeshRouter{
|
||||
logger: mocks.logger,
|
||||
flaggerClient: mocks.flaggerClient,
|
||||
appmeshClient: mocks.meshClient,
|
||||
kubeClient: mocks.kubeClient,
|
||||
}
|
||||
|
||||
err := router.Reconcile(mocks.appmeshCanary)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// check virtual service
|
||||
vsName := fmt.Sprintf("%s.%s", mocks.appmeshCanary.Spec.TargetRef.Name, mocks.appmeshCanary.Namespace)
|
||||
vs, err := router.appmeshClient.AppmeshV1beta1().VirtualServices("default").Get(vsName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
expose := vs.Annotations["gateway.appmesh.k8s.aws/expose"]
|
||||
if expose != "true" {
|
||||
t.Errorf("Got gateway expose annotation %v wanted %v", expose, "true")
|
||||
}
|
||||
|
||||
domain := vs.Annotations["gateway.appmesh.k8s.aws/domain"]
|
||||
if !strings.Contains(domain, mocks.appmeshCanary.Spec.Service.Hosts[0]) {
|
||||
t.Errorf("Got gateway domain annotation %v wanted %v", domain, mocks.appmeshCanary.Spec.Service.Hosts[0])
|
||||
}
|
||||
|
||||
timeout := vs.Annotations["gateway.appmesh.k8s.aws/timeout"]
|
||||
if timeout != mocks.appmeshCanary.Spec.Service.Timeout {
|
||||
t.Errorf("Got gateway timeout annotation %v wanted %v", timeout, mocks.appmeshCanary.Spec.Service.Timeout)
|
||||
}
|
||||
|
||||
retries := vs.Annotations["gateway.appmesh.k8s.aws/retries"]
|
||||
if retries != strconv.Itoa(mocks.appmeshCanary.Spec.Service.Retries.Attempts) {
|
||||
t.Errorf("Got gateway retries annotation %v wanted %v", retries, strconv.Itoa(mocks.appmeshCanary.Spec.Service.Retries.Attempts))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,14 @@ func newMockCanaryAppMesh() *flaggerv1.Canary {
|
||||
Service: flaggerv1.CanaryService{
|
||||
Port: 9898,
|
||||
MeshName: "global",
|
||||
Hosts: []string{"*"},
|
||||
Backends: []string{"backend.default"},
|
||||
Timeout: "25",
|
||||
Retries: &istiov1alpha3.HTTPRetry{
|
||||
Attempts: 5,
|
||||
PerTryTimeout: "gateway-error",
|
||||
RetryOn: "5s",
|
||||
},
|
||||
}, CanaryAnalysis: flaggerv1.CanaryAnalysis{
|
||||
Threshold: 10,
|
||||
StepWeight: 10,
|
||||
|
||||
Reference in New Issue
Block a user