mirror of
https://github.com/fluxcd/flagger.git
synced 2026-04-15 06:57:34 +00:00
improve apisix router and metric observer
Signed-off-by: Gallardot <tttick@163.com>
This commit is contained in:
committed by
Sanskar Jaiswal
parent
badf7b9a4f
commit
ce52408bbc
@@ -87,6 +87,7 @@ type MetricTemplateModel struct {
|
||||
Target string `json:"target"`
|
||||
Service string `json:"service"`
|
||||
Ingress string `json:"ingress"`
|
||||
Route string `json:"route"`
|
||||
Interval string `json:"interval"`
|
||||
}
|
||||
|
||||
@@ -98,6 +99,7 @@ func (mtm *MetricTemplateModel) TemplateFunctions() template.FuncMap {
|
||||
"target": func() string { return mtm.Target },
|
||||
"service": func() string { return mtm.Service },
|
||||
"ingress": func() string { return mtm.Ingress },
|
||||
"route": func() string { return mtm.Route },
|
||||
"interval": func() string { return mtm.Interval },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,12 +319,17 @@ func toMetricModel(r *flaggerv1.Canary, interval string) flaggerv1.MetricTemplat
|
||||
if r.Spec.IngressRef != nil {
|
||||
ingress = r.Spec.IngressRef.Name
|
||||
}
|
||||
route := r.Spec.TargetRef.Name
|
||||
if r.Spec.RouteRef != nil {
|
||||
route = r.Spec.RouteRef.Name
|
||||
}
|
||||
return flaggerv1.MetricTemplateModel{
|
||||
Name: r.Name,
|
||||
Namespace: r.Namespace,
|
||||
Target: r.Spec.TargetRef.Name,
|
||||
Service: service,
|
||||
Ingress: ingress,
|
||||
Route: route,
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ var apisixQueries = map[string]string{
|
||||
sum(
|
||||
rate(
|
||||
apisix_http_status{
|
||||
route=~"{{ namespace }}_{{ target }}-canary_.+",
|
||||
route=~"{{ namespace }}_{{ route }}-{{ target }}-canary_.+",
|
||||
code!~"5.."
|
||||
}[{{ interval }}]
|
||||
)
|
||||
@@ -38,7 +38,7 @@ var apisixQueries = map[string]string{
|
||||
sum(
|
||||
rate(
|
||||
apisix_http_status{
|
||||
route=~"{{ namespace }}_{{ target }}-canary_.+"
|
||||
route=~"{{ namespace }}_{{ route }}-{{ target }}-canary_.+"
|
||||
}[{{ interval }}]
|
||||
)
|
||||
) * 100`,
|
||||
@@ -49,7 +49,7 @@ var apisixQueries = map[string]string{
|
||||
rate(
|
||||
apisix_http_latency_bucket{
|
||||
type=~"request",
|
||||
route=~"{{ namespace }}_{{ target }}-canary_.+"
|
||||
route=~"{{ namespace }}_{{ route }}-{{ target }}-canary_.+"
|
||||
}[{{ interval }}]
|
||||
)
|
||||
) by (le)
|
||||
@@ -61,7 +61,6 @@ type ApisixObserver struct {
|
||||
}
|
||||
|
||||
func (ob *ApisixObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
|
||||
|
||||
query, err := RenderQuery(apisixQueries["request-success-rate"], model)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("rendering query failed: %w", err)
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
func TestApisixObserver_GetRequestSuccessRate(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
expected := ` sum( rate( apisix_http_status{ route=~"default_podinfo-canary_.+", code!~"5.." }[1m] ) ) / sum( rate( apisix_http_status{ route=~"default_podinfo-canary_.+" }[1m] ) ) * 100`
|
||||
expected := ` sum( rate( apisix_http_status{ route=~"default_podinfo-podinfo-canary_.+", code!~"5.." }[1m] ) ) / sum( rate( apisix_http_status{ route=~"default_podinfo-podinfo-canary_.+" }[1m] ) ) * 100`
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
promql := r.URL.Query()["query"][0]
|
||||
@@ -56,6 +56,7 @@ func TestApisixObserver_GetRequestSuccessRate(t *testing.T) {
|
||||
Namespace: "default",
|
||||
Target: "podinfo",
|
||||
Service: "podinfo",
|
||||
Route: "podinfo",
|
||||
Interval: "1m",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -84,7 +85,7 @@ func TestApisixObserver_GetRequestSuccessRate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApisixObserver_GetRequestDuration(t *testing.T) {
|
||||
expected := ` histogram_quantile( 0.99, sum( rate( apisix_http_latency_bucket{ type=~"request", route=~"default_podinfo-canary_.+" }[1m] ) ) by (le) )`
|
||||
expected := ` histogram_quantile( 0.99, sum( rate( apisix_http_latency_bucket{ type=~"request", route=~"default_podinfo-podinfo-canary_.+" }[1m] ) ) by (le) )`
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
promql := r.URL.Query()["query"][0]
|
||||
@@ -109,6 +110,7 @@ func TestApisixObserver_GetRequestDuration(t *testing.T) {
|
||||
Namespace: "default",
|
||||
Target: "podinfo",
|
||||
Service: "podinfo",
|
||||
Route: "podinfo",
|
||||
Interval: "1m",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
+43
-37
@@ -49,24 +49,46 @@ func (ar *ApisixRouter) Reconcile(canary *flaggerv1.Canary) error {
|
||||
|
||||
apisixRoute, err := ar.apisixClient.ApisixV2().ApisixRoutes(canary.Namespace).Get(context.TODO(), canary.Spec.RouteRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("apisix route %s.%s get query error: %w", canary.Spec.RouteRef.Name, canary.Namespace, err)
|
||||
return fmt.Errorf("APISIX route %s.%s get query error: %w",
|
||||
canary.Spec.RouteRef.Name, canary.Namespace, err)
|
||||
}
|
||||
|
||||
apisixRouteClone := apisixRoute.DeepCopy()
|
||||
if len(apisixRouteClone.Spec.HTTP) != 1 && len(apisixRouteClone.Spec.HTTP[0].Backends) != 1 {
|
||||
return fmt.Errorf("apisix route %s.%s only one http backend is supported", canary.Spec.RouteRef.Name, canary.Namespace)
|
||||
if len(apisixRouteClone.Spec.HTTP) == 0 {
|
||||
return fmt.Errorf("APISIX route %s.%s's spec.http is empty",
|
||||
canary.Spec.RouteRef.Name, canary.Namespace)
|
||||
}
|
||||
|
||||
httpBackend := apisixRouteClone.Spec.HTTP[0]
|
||||
httpBackend.Priority = maxPriority
|
||||
apexName, primaryName, canaryName := canary.GetServiceNames()
|
||||
var targetHttpRoute *a6v2.ApisixRouteHTTP
|
||||
var targetIndex int
|
||||
for index, item := range apisixRouteClone.Spec.HTTP {
|
||||
for _, backend := range item.Backends {
|
||||
if backend.ServiceName == apexName {
|
||||
targetHttpRoute = &item
|
||||
targetIndex = index
|
||||
goto found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, primaryName, canaryName := canary.GetServiceNames()
|
||||
found:
|
||||
if targetHttpRoute == nil {
|
||||
return fmt.Errorf("Can not find %s backend on apisix route %s.%s ",
|
||||
primaryName, canary.Spec.RouteRef.Name, canary.Namespace)
|
||||
}
|
||||
if len(targetHttpRoute.Backends) != 1 {
|
||||
return fmt.Errorf("APISIX route %s.%s's http route %s only one http backend is supported",
|
||||
canary.Spec.RouteRef.Name, canary.Namespace, targetHttpRoute.Name)
|
||||
}
|
||||
|
||||
primaryBackend := httpBackend.Backends[0]
|
||||
targetHttpRoute.Priority = maxPriority
|
||||
|
||||
primaryBackend := targetHttpRoute.Backends[0]
|
||||
primaryBackend.ServiceName = primaryName
|
||||
primaryWeight := 100
|
||||
primaryBackend.Weight = &primaryWeight
|
||||
httpBackend.Backends[0] = primaryBackend
|
||||
targetHttpRoute.Backends[0] = primaryBackend
|
||||
|
||||
canaryWeight := 0
|
||||
canaryBackend := a6v2.ApisixRouteHTTPBackend{
|
||||
@@ -76,10 +98,11 @@ func (ar *ApisixRouter) Reconcile(canary *flaggerv1.Canary) error {
|
||||
Weight: &canaryWeight,
|
||||
Subset: primaryBackend.Subset,
|
||||
}
|
||||
httpBackend.Backends = append(httpBackend.Backends, canaryBackend)
|
||||
|
||||
apisixRouteClone.Spec.HTTP[0] = httpBackend
|
||||
canaryApisixRouteName := fmt.Sprintf("%s-canary", canary.Spec.RouteRef.Name)
|
||||
targetHttpRoute.Backends = append(targetHttpRoute.Backends, canaryBackend)
|
||||
apisixRouteClone.Spec.HTTP[targetIndex] = *targetHttpRoute
|
||||
|
||||
canaryApisixRouteName := fmt.Sprintf("%s-%s-canary", canary.Spec.RouteRef.Name, apexName)
|
||||
canaryApisixRoute, err := ar.apisixClient.ApisixV2().ApisixRoutes(canary.Namespace).Get(context.TODO(), canaryApisixRouteName, metav1.GetOptions{})
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
@@ -105,41 +128,24 @@ func (ar *ApisixRouter) Reconcile(canary *flaggerv1.Canary) error {
|
||||
|
||||
_, err := ar.apisixClient.ApisixV2().ApisixRoutes(canary.Namespace).Create(context.TODO(), route, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("apisix route %s.%s create error: %w", route.Name, route.Namespace, err)
|
||||
return fmt.Errorf("APISIX route %s.%s create error: %w", route.Name, route.Namespace, err)
|
||||
}
|
||||
|
||||
ar.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
|
||||
Infof("apisix route %s.%s created", route.GetName(), canary.Namespace)
|
||||
Infof("APISIX route %s.%s created", route.GetName(), canary.Namespace)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("apisix route %s.%s query error: %w", canaryApisixRouteName, canary.Namespace, err)
|
||||
return fmt.Errorf("APISIX route %s.%s query error: %w", canaryApisixRouteName, canary.Namespace, err)
|
||||
}
|
||||
|
||||
diffHttpSpec := "placeholder"
|
||||
diffBackend := "placeholder"
|
||||
diffCanaryBackend := "placeholder"
|
||||
if len(canaryApisixRoute.Spec.HTTP) == 1 &&
|
||||
len(canaryApisixRoute.Spec.HTTP[0].Backends) == 2 {
|
||||
diffHttpSpec = cmp.Diff(apisixRouteClone.Spec.HTTP[0],
|
||||
canaryApisixRoute.Spec.HTTP[0],
|
||||
cmpopts.IgnoreFields(a6v2.ApisixRouteHTTP{}, "Backends"))
|
||||
|
||||
diffBackend = cmp.Diff(apisixRouteClone.Spec.HTTP[0].Backends[0],
|
||||
canaryApisixRoute.Spec.HTTP[0].Backends[0],
|
||||
cmpopts.IgnoreFields(a6v2.ApisixRouteHTTPBackend{}, "Weight"))
|
||||
|
||||
diffCanaryBackend = cmp.Diff(apisixRouteClone.Spec.HTTP[0].Backends[1],
|
||||
canaryApisixRoute.Spec.HTTP[0].Backends[1],
|
||||
cmpopts.IgnoreFields(a6v2.ApisixRouteHTTPBackend{}, "Weight"))
|
||||
}
|
||||
|
||||
if diffHttpSpec != "" || diffBackend != "" || diffCanaryBackend != "" {
|
||||
if diff := cmp.Diff(canaryApisixRoute.Spec, apisixRouteClone.Spec,
|
||||
cmpopts.IgnoreFields(a6v2.ApisixRouteHTTPBackend{}, "Weight")); diff != "" {
|
||||
iClone := canaryApisixRoute.DeepCopy()
|
||||
iClone.Spec = apisixRouteClone.Spec
|
||||
|
||||
_, err := ar.apisixClient.ApisixV2().ApisixRoutes(canary.Namespace).Update(context.TODO(), iClone, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("apisix route %s.%s update error: %w", canaryApisixRouteName, iClone.Namespace, err)
|
||||
return fmt.Errorf("APISIX route %s.%s update error: %w", canaryApisixRouteName, iClone.Namespace, err)
|
||||
}
|
||||
ar.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
|
||||
Infof("Apisix route %s updated", canaryApisixRouteName)
|
||||
@@ -155,13 +161,13 @@ func (ar *ApisixRouter) GetRoutes(canary *flaggerv1.Canary) (
|
||||
mirrored bool,
|
||||
err error,
|
||||
) {
|
||||
canaryApisixRouteName := fmt.Sprintf("%s-canary", canary.Spec.RouteRef.Name)
|
||||
apexName, primaryName, _ := canary.GetServiceNames()
|
||||
canaryApisixRouteName := fmt.Sprintf("%s-%s-canary", canary.Spec.RouteRef.Name, apexName)
|
||||
apisixRoute, err := ar.apisixClient.ApisixV2().ApisixRoutes(canary.Namespace).Get(context.TODO(), canaryApisixRouteName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("apisix route %s.%s query error: %w", canaryApisixRouteName, canary.Namespace, err)
|
||||
return
|
||||
}
|
||||
_, primaryName, _ := canary.GetServiceNames()
|
||||
|
||||
for _, backend := range apisixRoute.Spec.HTTP[0].Backends {
|
||||
if backend.ServiceName == primaryName {
|
||||
@@ -187,7 +193,7 @@ func (ar *ApisixRouter) SetRoutes(
|
||||
return fmt.Errorf("RoutingRule %s.%s update failed: no valid weights", apexName, canary.Namespace)
|
||||
}
|
||||
|
||||
canaryApisixRouteName := fmt.Sprintf("%s-canary", canary.Spec.RouteRef.Name)
|
||||
canaryApisixRouteName := fmt.Sprintf("%s-%s-canary", canary.Spec.RouteRef.Name, apexName)
|
||||
apisixRoute, err := ar.apisixClient.ApisixV2().ApisixRoutes(canary.Namespace).Get(context.TODO(), canaryApisixRouteName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("apisix route %s.%s query error: %w", canaryApisixRouteName, canary.Namespace, err)
|
||||
|
||||
@@ -38,9 +38,10 @@ func TestApisixRouter_Reconcile(t *testing.T) {
|
||||
apisixClient: mocks.flaggerClient,
|
||||
logger: mocks.logger,
|
||||
}
|
||||
apexName, _, _ := mocks.canary.GetServiceNames()
|
||||
err := router.Reconcile(mocks.canary)
|
||||
require.NoError(t, err)
|
||||
canaryName := fmt.Sprintf("%s-canary", mocks.canary.Spec.RouteRef.Name)
|
||||
canaryName := fmt.Sprintf("%s-%s-canary", mocks.canary.Spec.RouteRef.Name, apexName)
|
||||
arCanary, err := router.apisixClient.ApisixV2().ApisixRoutes("default").Get(context.TODO(), canaryName, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(arCanary.Spec.HTTP[0].Backends))
|
||||
@@ -77,7 +78,8 @@ func TestApisixRouter_GetSetRoutes(t *testing.T) {
|
||||
assert.Equal(t, 50, c)
|
||||
assert.False(t, m)
|
||||
|
||||
canaryName := fmt.Sprintf("%s-canary", mocks.canary.Spec.RouteRef.Name)
|
||||
apexName, _, _ := mocks.canary.GetServiceNames()
|
||||
canaryName := fmt.Sprintf("%s-%s-canary", mocks.canary.Spec.RouteRef.Name, apexName)
|
||||
arRouter, err := router.apisixClient.ApisixV2().ApisixRoutes("default").Get(context.TODO(), canaryName, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(arRouter.Spec.HTTP[0].Backends))
|
||||
|
||||
Reference in New Issue
Block a user