Merge pull request #325 from weaveworks/appmesh-grcp

Allow gPRC protocol for App Mesh
This commit is contained in:
Stefan Prodan
2019-10-05 12:49:07 +03:00
committed by GitHub
6 changed files with 89 additions and 67 deletions
+5 -2
View File
@@ -70,7 +70,6 @@ metadata:
spec:
# service mesh provider (optional)
# can be: kubernetes, istio, linkerd, appmesh, nginx, gloo, supergloo
# use the kubernetes provider for Blue/Green style deployments
provider: istio
# deployment reference
targetRef:
@@ -94,6 +93,10 @@ spec:
# Istio virtual service host names (optional)
hosts:
- podinfo.example.com
# Istio traffic policy (optional)
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
# HTTP match conditions (optional)
match:
- uri:
@@ -144,7 +147,7 @@ spec:
topic="podinfo"
}[1m]
)
# external checks (optional)
# testing (optional)
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
+3
View File
@@ -20,6 +20,9 @@ spec:
service:
# container port
port: 9898
# container port name (optional)
# can be http or grpc
portName: http
# App Mesh reference
meshName: global
# define the canary analysis timing and KPIs
+23 -28
View File
@@ -19,7 +19,6 @@ metadata:
spec:
# service mesh provider (optional)
# can be: kubernetes, istio, linkerd, appmesh, nginx, gloo, supergloo
# use the kubernetes provider for Blue/Green style deployments
provider: istio
# deployment reference
targetRef:
@@ -38,13 +37,7 @@ spec:
# container port
port: 9898
# service port name (optional, will default to "http")
portName: http-podinfo
# Istio gateways (optional)
gateways:
- public-gateway.istio-system.svc.cluster.local
# Istio virtual service host names (optional)
hosts:
- podinfo.example.com
portName: http
# promote the canary without analysing it (default false)
skipAnalysis: false
# define the canary analysis timing and KPIs
@@ -71,15 +64,13 @@ spec:
# milliseconds
threshold: 500
interval: 30s
# external checks (optional)
# testing (optional)
webhooks:
- name: integration-tests
url: http://podinfo.test:9898/echo
timeout: 1m
# key-value pairs (optional)
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
test: "all"
token: "16688eb5e9f289f1991c"
cmd: "hey -z 1m -q 10 -c 2 http://podinfo.test:9898/"
```
**Note** that the target deployment must have a single label selector in the format `app: <DEPLOYMENT-NAME>`:
@@ -102,8 +93,8 @@ spec:
Besides `app` Flagger supports `name` and `app.kubernetes.io/name` selectors. If you use a different
convention you can specify your label with the `-selector-labels` flag.
The target deployment should expose a TCP port that will be used by Flagger to create the ClusterIP Service and
the Istio Virtual Service. The container port from the target deployment should match the `service.port` value.
The target deployment should expose a TCP port that will be used by Flagger to create the ClusterIP Services.
The container port from the target deployment should match the `service.port` value.
### Canary status
@@ -201,10 +192,11 @@ spec:
# Istio virtual service host names (optional)
hosts:
- frontend.example.com
# Istio traffic policy (optional)
# Istio traffic policy
trafficPolicy:
loadBalancer:
simple: LEAST_CONN
tls:
# use ISTIO_MUTUAL when mTLS is enabled
mode: DISABLE
# HTTP match conditions (optional)
match:
- uri:
@@ -291,8 +283,8 @@ metadata:
spec:
host: frontend-primary
trafficPolicy:
loadBalancer:
simple: LEAST_CONN
tls:
mode: DISABLE
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
@@ -302,15 +294,15 @@ metadata:
spec:
host: frontend-canary
trafficPolicy:
loadBalancer:
simple: LEAST_CONN
tls:
mode: DISABLE
```
Flagger keeps in sync the virtual service and destination rules with the canary service spec.
Any direct modification to the virtual service spec will be overwritten.
To expose a workload inside the mesh on `http://backend.test.svc.cluster.local:9898`,
the service spec can contain only the container port:
the service spec can contain only the container port and the traffic policy:
```yaml
apiVersion: flagger.app/v1alpha3
@@ -321,6 +313,9 @@ metadata:
spec:
service:
port: 9898
trafficPolicy:
tls:
mode: DISABLE
```
Based on the above spec, Flagger will create several ClusterIP services like:
@@ -531,7 +526,7 @@ sum(
)
```
App Mesh query:
Envoy query (App Mesh or Gloo):
```javascript
sum(
@@ -539,7 +534,7 @@ sum(
envoy_cluster_upstream_rq{
kubernetes_namespace="$namespace",
kubernetes_pod_name=~"$workload",
response_code!~"5.*"
envoy_response_code!~"5.*"
}[$interval]
)
)
@@ -584,7 +579,7 @@ histogram_quantile(0.99,
)
```
App Mesh query:
Envoy query (App Mesh or Gloo):
```javascript
histogram_quantile(0.99,
@@ -17,8 +17,7 @@ The App Mesh integration with EKS is made out of the following components:
### Create a Kubernetes cluster
In order to create an EKS cluster you can use [eksctl](https://eksctl.io).
Eksctl is an open source command-line utility made by Weaveworks in collaboration with Amazon,
its a Kubernetes-native tool written in Go.
Eksctl is an open source command-line utility made by Weaveworks in collaboration with Amazon.
On MacOS you can install eksctl with Homebrew:
@@ -137,7 +136,17 @@ Status:
Type: MeshActive
```
### Install Flagger, Prometheus and Grafana
In order to collect the App Mesh metrics that Flagger needs to run the canary analysis,
you'll need to setup a Prometheus instance to scrape the Envoy sidecars.
Install the App Mesh Prometheus:
```sh
helm upgrade -i appmesh-prometheus eks/appmesh-prometheus \
--wait --namespace appmesh-system
```
### Install Flagger and Grafana
Add Flagger Helm repository:
@@ -151,20 +160,17 @@ Install Flagger's Canary CRD:
kubectl apply -f https://raw.githubusercontent.com/weaveworks/flagger/master/artifacts/flagger/crd.yaml
```
Deploy Flagger and Prometheus in the _**appmesh-system**_ namespace:
Deploy Flagger in the _**appmesh-system**_ namespace:
```bash
helm upgrade -i flagger flagger/flagger \
--namespace=appmesh-system \
--set crd.create=false \
--set meshProvider=appmesh \
--set prometheus.install=true
--set metricsServer=appmesh-prometheus:9090
```
In order to collect the App Mesh metrics that Flagger needs to run the canary analysis,
you'll need to setup a Prometheus instance to scrape the Envoy sidecars.
You can enable **Slack** notifications with:
You can enable Slack or MS Teams notifications with:
```bash
helm upgrade -i flagger flagger/flagger \
@@ -181,7 +187,7 @@ Deploy Grafana in the _**appmesh-system**_ namespace:
```bash
helm upgrade -i flagger-grafana flagger/grafana \
--namespace=appmesh-system \
--set url=http://flagger-prometheus.appmesh-system:9090
--set url=http://appmesh-prometheus:9090
```
You can access Grafana using port forwarding:
@@ -67,6 +67,9 @@ spec:
service:
# container port
port: 9898
# container port name (optional)
# can be http or grpc
portName: http
# App Mesh reference
meshName: global
# App Mesh egress (optional)
+39 -27
View File
@@ -2,16 +2,19 @@ package router
import (
"fmt"
"strings"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
AppmeshV1beta1 "github.com/weaveworks/flagger/pkg/apis/appmesh/v1beta1"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
"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"
appmeshv1 "github.com/weaveworks/flagger/pkg/apis/appmesh/v1beta1"
flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1alpha3"
clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
)
// AppMeshRouter is managing AppMesh virtual services
@@ -76,27 +79,28 @@ 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 {
vnSpec := AppmeshV1beta1.VirtualNodeSpec{
protocol := getProtocol(canary)
vnSpec := appmeshv1.VirtualNodeSpec{
MeshName: canary.Spec.Service.MeshName,
Listeners: []AppmeshV1beta1.Listener{
Listeners: []appmeshv1.Listener{
{
PortMapping: AppmeshV1beta1.PortMapping{
PortMapping: appmeshv1.PortMapping{
Port: int64(canary.Spec.Service.Port),
Protocol: "http",
Protocol: protocol,
},
},
},
ServiceDiscovery: &AppmeshV1beta1.ServiceDiscovery{
Dns: &AppmeshV1beta1.DnsServiceDiscovery{
ServiceDiscovery: &appmeshv1.ServiceDiscovery{
Dns: &appmeshv1.DnsServiceDiscovery{
HostName: host,
},
},
}
backends := []AppmeshV1beta1.Backend{}
backends := []appmeshv1.Backend{}
for _, b := range canary.Spec.Service.Backends {
backend := AppmeshV1beta1.Backend{
VirtualService: AppmeshV1beta1.VirtualServiceBackend{
backend := appmeshv1.Backend{
VirtualService: appmeshv1.VirtualServiceBackend{
VirtualServiceName: b,
},
}
@@ -110,7 +114,7 @@ func (ar *AppMeshRouter) reconcileVirtualNode(canary *flaggerv1.Canary, name str
// create virtual node
if errors.IsNotFound(err) {
virtualnode = &AppmeshV1beta1.VirtualNode{
virtualnode = &appmeshv1.VirtualNode{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: canary.Namespace,
@@ -159,6 +163,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)
// App Mesh supports only URI prefix
routePrefix := "/"
@@ -168,28 +173,28 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
routePrefix = canary.Spec.Service.Match[0].Uri.Prefix
}
vsSpec := AppmeshV1beta1.VirtualServiceSpec{
vsSpec := appmeshv1.VirtualServiceSpec{
MeshName: canary.Spec.Service.MeshName,
VirtualRouter: &AppmeshV1beta1.VirtualRouter{
VirtualRouter: &appmeshv1.VirtualRouter{
Name: fmt.Sprintf("%s-router", targetName),
Listeners: []AppmeshV1beta1.Listener{
Listeners: []appmeshv1.Listener{
{
PortMapping: AppmeshV1beta1.PortMapping{
PortMapping: appmeshv1.PortMapping{
Port: int64(canary.Spec.Service.Port),
Protocol: "http",
Protocol: protocol,
},
},
},
},
Routes: []AppmeshV1beta1.Route{
Routes: []appmeshv1.Route{
{
Name: fmt.Sprintf("%s-route", targetName),
Http: &AppmeshV1beta1.HttpRoute{
Match: AppmeshV1beta1.HttpRouteMatch{
Http: &appmeshv1.HttpRoute{
Match: appmeshv1.HttpRouteMatch{
Prefix: routePrefix,
},
Action: AppmeshV1beta1.HttpRouteAction{
WeightedTargets: []AppmeshV1beta1.WeightedTarget{
Action: appmeshv1.HttpRouteAction{
WeightedTargets: []appmeshv1.WeightedTarget{
{
VirtualNodeName: canaryVirtualNode,
Weight: canaryWeight,
@@ -209,7 +214,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
// create virtual service
if errors.IsNotFound(err) {
virtualService = &AppmeshV1beta1.VirtualService{
virtualService = &appmeshv1.VirtualService{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: canary.Namespace,
@@ -238,7 +243,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
// update virtual service but keep the original target weights
if virtualService != nil {
if diff := cmp.Diff(vsSpec, virtualService.Spec, cmpopts.IgnoreTypes(AppmeshV1beta1.WeightedTarget{})); diff != "" {
if diff := cmp.Diff(vsSpec, virtualService.Spec, cmpopts.IgnoreTypes(appmeshv1.WeightedTarget{})); diff != "" {
vsClone := virtualService.DeepCopy()
vsClone.Spec = vsSpec
vsClone.Spec.Routes[0].Http.Action = virtualService.Spec.Routes[0].Http.Action
@@ -317,8 +322,8 @@ func (ar *AppMeshRouter) SetRoutes(
}
vsClone := vs.DeepCopy()
vsClone.Spec.Routes[0].Http.Action = AppmeshV1beta1.HttpRouteAction{
WeightedTargets: []AppmeshV1beta1.WeightedTarget{
vsClone.Spec.Routes[0].Http.Action = appmeshv1.HttpRouteAction{
WeightedTargets: []appmeshv1.WeightedTarget{
{
VirtualNodeName: fmt.Sprintf("%s-canary", targetName),
Weight: int64(canaryWeight),
@@ -337,3 +342,10 @@ func (ar *AppMeshRouter) SetRoutes(
return nil
}
func getProtocol(canary *flaggerv1.Canary) string {
if strings.Contains(canary.Spec.Service.PortName, "grpc") {
return "grpc"
}
return "http"
}