Compare commits

...

24 Commits

Author SHA1 Message Date
Stefan Prodan
360dd63e49 Merge pull request #282 from weaveworks/prep-0.18.3
Release 0.18.3
2019-08-22 18:53:15 +03:00
stefanprodan
f1670dbe6a Add 0.18.3 changelog 2019-08-22 18:39:47 +03:00
stefanprodan
e7ad5c0381 Release load tester v0.7.0 2019-08-22 18:31:05 +03:00
stefanprodan
2cfe2a105a Release Flagger v0.18.3 2019-08-22 18:30:46 +03:00
Stefan Prodan
bc83cee503 Merge pull request #278 from mjallday/patch-1
Embedding Health Check Protobuf
2019-08-22 18:19:58 +03:00
Stefan Prodan
5091d3573c Merge pull request #281 from weaveworks/fix-appmesh-crd
Fix App Mesh backends validation in CRD
2019-08-22 10:02:38 +03:00
Marshall Jones
ffe5dd91c5 Add an example and fix path to downloaded proto file 2019-08-21 15:15:01 -07:00
stefanprodan
d76b560967 Bump podinfo version in the App Mesh demo 2019-08-21 21:52:36 +03:00
stefanprodan
f062ef3a57 Fix App Mesh backends validation in CRD 2019-08-21 21:45:36 +03:00
Stefan Prodan
5fc1baf4df Merge pull request #280 from vbehar/loadtester-helm-tillerless
loadtester: add support for tillerless helm
2019-08-21 17:25:44 +03:00
Vincent Behar
777b77b69e loadtester: add support for tillerless helm
- upgrade helm to 2.14, and install the [helm-tiller](https://github.com/rimusz/helm-tiller) plugin to run in "tillerless" mode - with a local tiller instance
- also add support to create RBAC resources in the loadtester chart, because when running in tillerless mode, the pod service account will be used instead of the tiller one - so we need to give him specific permissions

this allow the use of the loadtester to run `helm test` in tillerless mode, with `helm tiller run -- helm test` for example
2019-08-21 15:54:49 +02:00
Marshall Jones
5d221e781a Propose Embedding Health Check Proto
Copy this file https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto into the ghz folder for use when promoting a canary running a grpc service. 

This repo describes the file:

> This repository contains common protocol definitions for peripheral services around gRPC such as health checking, load balancing etc..

Any app that chooses to implement this interface (which imo should be any grpc service exposing a health check) will then be able to use this without providing reflection. 

I'm not a grpc expert so I'm not sure what the best practices are around allowing reflection on the server but this seems like a simple solution for those who choose not to enable it.

Slack discussion on the weave users slack is here - https://weave-community.slack.com/archives/CGLQLLH9Q/p1566358441123400

You can utilize this file like so 

`/ghz --proto=/tmp/health.proto --call=grpc.health.v1.Health/Check ...`
2019-08-20 20:47:30 -07:00
Stefan Prodan
ddab72cd59 Merge pull request #276 from weaveworks/podinfo
Update podinfo to v2.0
2019-08-14 10:46:06 +03:00
stefanprodan
87d0b33327 Add provider field to nginx and gloo docs 2019-08-14 10:14:00 +03:00
stefanprodan
225a9015bb Update podinfo to v2.0 2019-08-14 09:28:36 +03:00
Stefan Prodan
c0b60b1497 Merge pull request #272 from weaveworks/appmesh
Set HTTP listeners for AppMesh virtual routers
2019-08-13 09:48:49 +03:00
Stefan Prodan
0463c19825 Merge pull request #275 from hiddeco/build/codegen
Support non `$GOPATH/src` location for codegen
2019-08-13 09:48:27 +03:00
Hidde Beydals
8e70aa90c1 Support non $GOPATH/src location for codegen
This commit fixes two things:

- it ensures the code generation works no matter the location of the
  project directory
- as a side effect; fixes the `hack/verify-codegen.sh` ran during CI

The previous script (or more specific: the `code-generator` library)
made the assumption during execution that the project was placed
inside `$GOPATH/src` and made the modifications there.

The idea of Go Modules is however that a project and/or package can
be placed anywhere, and this is also what the CI did, resulting in a
comparison of two identical `cp -r` copied directories. Giving a
false green light on every CI run.

To work around this limitation in `code-generator`: create a
temporary directory, use this as an output base and copy
everything back once generated.
2019-08-12 22:41:10 +02:00
stefanprodan
0a418eb88a Add notifier tests 2019-08-12 09:47:11 +03:00
stefanprodan
040dbb8d03 Add http listener to virtual router reconciliation 2019-08-10 11:04:15 +03:00
stefanprodan
64f2288bdd Add listeners to AppMesh virtual router 2019-08-10 10:58:20 +03:00
Stefan Prodan
8008562a33 Merge pull request #271 from weaveworks/crd
Add missing fields to CRD validation spec
2019-08-07 11:09:07 +03:00
stefanprodan
a39652724d Add confirm and pre rollout hooks to e2e tests 2019-08-07 10:55:15 +03:00
stefanprodan
691c3c4f36 Add missing fields to CRD validation spec 2019-08-07 10:22:07 +03:00
39 changed files with 671 additions and 79 deletions

View File

@@ -2,6 +2,24 @@
All notable changes to this project are documented in this file.
## 0.18.3 (2019-08-22)
Adds support for tillerless helm tests and protobuf health checking
#### Features
- loadtester: add support for tillerless helm [#280](https://github.com/weaveworks/flagger/pull/280)
- loadtester: add support for protobuf health checking [#280](https://github.com/weaveworks/flagger/pull/280)
#### Improvements
- Set HTTP listeners for AppMesh virtual routers [#272](https://github.com/weaveworks/flagger/pull/272)
#### Fixes
- Add missing fields to CRD validation spec [#271](https://github.com/weaveworks/flagger/pull/271)
- Fix App Mesh backends validation in CRD [#281](https://github.com/weaveworks/flagger/pull/281)
## 0.18.2 (2019-08-05)
Fixes multi-port support for Istio

View File

@@ -9,13 +9,16 @@ WORKDIR /home/app
RUN curl -sSLo hey "https://storage.googleapis.com/jblabs/dist/hey_linux_v0.1.2" && \
chmod +x hey && mv hey /usr/local/bin/hey
RUN curl -sSL "https://get.helm.sh/helm-v2.12.3-linux-amd64.tar.gz" | tar xvz && \
RUN curl -sSL "https://get.helm.sh/helm-v2.14.3-linux-amd64.tar.gz" | tar xvz && \
chmod +x linux-amd64/helm && mv linux-amd64/helm /usr/local/bin/helm && \
chmod +x linux-amd64/tiller && mv linux-amd64/tiller /usr/local/bin/tiller && \
rm -rf linux-amd64
RUN curl -sSL "https://github.com/bojand/ghz/releases/download/v0.39.0/ghz_0.39.0_Linux_x86_64.tar.gz" | tar xz -C /tmp && \
mv /tmp/ghz /usr/local/bin && chmod +x /usr/local/bin/ghz && rm -rf /tmp/ghz-web
ADD https://raw.githubusercontent.com/grpc/grpc-proto/master/grpc/health/v1/health.proto /tmp/ghz/health.proto
RUN ls /tmp
COPY ./bin/loadtester .
@@ -24,4 +27,7 @@ RUN chown -R app:app ./
USER app
RUN curl -sSL "https://github.com/rimusz/helm-tiller/archive/v0.8.3.tar.gz" | tar xvz && \
helm init --client-only && helm plugin install helm-tiller-0.8.3 && helm plugin list
ENTRYPOINT ["./loadtester"]

View File

@@ -25,7 +25,7 @@ spec:
spec:
containers:
- name: podinfod
image: quay.io/stefanprodan/podinfo:1.7.0
image: quay.io/stefanprodan/podinfo:2.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9898

View File

@@ -25,7 +25,7 @@ spec:
spec:
containers:
- name: podinfod
image: quay.io/stefanprodan/podinfo:1.7.0
image: stefanprodan/podinfo:2.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9898

View File

@@ -46,10 +46,13 @@ spec:
- canaryAnalysis
properties:
provider:
description: Traffic managent provider
type: string
progressDeadlineSeconds:
description: Deployment progress deadline
type: number
targetRef:
description: Deployment selector
type: object
required: ['apiVersion', 'kind', 'name']
properties:
@@ -60,6 +63,7 @@ spec:
name:
type: string
autoscalerRef:
description: HPA selector
anyOf:
- type: string
- type: object
@@ -72,6 +76,7 @@ spec:
name:
type: string
ingressRef:
description: NGINX ingress selector
anyOf:
- type: string
- type: object
@@ -88,15 +93,60 @@ spec:
required: ['port']
properties:
port:
description: Container port number
type: number
portName:
description: Container port name
type: string
portDiscovery:
description: Enable port dicovery
type: boolean
meshName:
description: AppMesh mesh name
type: string
backends:
description: AppMesh backend array
anyOf:
- type: string
- type: array
timeout:
description: Istio HTTP or gRPC request timeout
type: string
trafficPolicy:
description: Istio traffic policy
anyOf:
- type: string
- type: object
match:
description: Istio URL match conditions
anyOf:
- type: string
- type: array
rewrite:
description: Istio URL rewrite
anyOf:
- type: string
- type: object
headers:
description: Istio headers operations
anyOf:
- type: string
- type: object
corsPolicy:
description: Istio CORS policy
anyOf:
- type: string
- type: object
gateways:
description: Istio gateways list
anyOf:
- type: string
- type: array
hosts:
description: Istio hosts list
anyOf:
- type: string
- type: array
skipAnalysis:
type: boolean
canaryAnalysis:
@@ -117,6 +167,11 @@ spec:
stepWeight:
description: Canary incremental traffic percentage step
type: number
match:
description: A/B testing match conditions
anyOf:
- type: string
- type: array
metrics:
description: Prometheus query list for this canary
type: array
@@ -154,6 +209,7 @@ spec:
type: string
enum:
- ""
- confirm-rollout
- pre-rollout
- rollout
- post-rollout
@@ -165,6 +221,11 @@ spec:
description: Request timeout for this webhook
type: string
pattern: "^[0-9]+(m|s)"
metadata:
description: Metadata (key-value pairs) for this webhook
anyOf:
- type: string
- type: object
status:
properties:
phase:

View File

@@ -22,7 +22,7 @@ spec:
serviceAccountName: flagger
containers:
- name: flagger
image: weaveworks/flagger:0.18.2
image: weaveworks/flagger:0.18.3
imagePullPolicy: IfNotPresent
ports:
- name: http

View File

@@ -1,7 +1,7 @@
apiVersion: v1
name: flagger
version: 0.18.2
appVersion: 0.18.2
version: 0.18.3
appVersion: 0.18.3
kubeVersion: ">=1.11.0-0"
engine: gotpl
description: Flagger is a Kubernetes operator that automates the promotion of canary deployments using Istio, Linkerd, App Mesh, Gloo or NGINX routing for traffic shifting and Prometheus metrics for canary analysis.

View File

@@ -47,10 +47,13 @@ spec:
- canaryAnalysis
properties:
provider:
description: Traffic managent provider
type: string
progressDeadlineSeconds:
description: Deployment progress deadline
type: number
targetRef:
description: Deployment selector
type: object
required: ['apiVersion', 'kind', 'name']
properties:
@@ -61,6 +64,7 @@ spec:
name:
type: string
autoscalerRef:
description: HPA selector
anyOf:
- type: string
- type: object
@@ -73,6 +77,7 @@ spec:
name:
type: string
ingressRef:
description: NGINX ingress selector
anyOf:
- type: string
- type: object
@@ -89,15 +94,60 @@ spec:
required: ['port']
properties:
port:
description: Container port number
type: number
portName:
description: Container port name
type: string
portDiscovery:
description: Enable port dicovery
type: boolean
meshName:
description: AppMesh mesh name
type: string
backends:
description: AppMesh backend array
anyOf:
- type: string
- type: array
timeout:
description: Istio HTTP or gRPC request timeout
type: string
trafficPolicy:
description: Istio traffic policy
anyOf:
- type: string
- type: object
match:
description: Istio URL match conditions
anyOf:
- type: string
- type: array
rewrite:
description: Istio URL rewrite
anyOf:
- type: string
- type: object
headers:
description: Istio headers operations
anyOf:
- type: string
- type: object
corsPolicy:
description: Istio CORS policy
anyOf:
- type: string
- type: object
gateways:
description: Istio gateways list
anyOf:
- type: string
- type: array
hosts:
description: Istio hosts list
anyOf:
- type: string
- type: array
skipAnalysis:
type: boolean
canaryAnalysis:
@@ -118,6 +168,11 @@ spec:
stepWeight:
description: Canary incremental traffic percentage step
type: number
match:
description: A/B testing match conditions
anyOf:
- type: string
- type: array
metrics:
description: Prometheus query list for this canary
type: array
@@ -155,6 +210,7 @@ spec:
type: string
enum:
- ""
- confirm-rollout
- pre-rollout
- rollout
- post-rollout
@@ -166,6 +222,11 @@ spec:
description: Request timeout for this webhook
type: string
pattern: "^[0-9]+(m|s)"
metadata:
description: Metadata (key-value pairs) for this webhook
anyOf:
- type: string
- type: object
status:
properties:
phase:

View File

@@ -2,7 +2,7 @@
image:
repository: weaveworks/flagger
tag: 0.18.2
tag: 0.18.3
pullPolicy: IfNotPresent
pullSecret:

View File

@@ -1,7 +1,7 @@
apiVersion: v1
name: loadtester
version: 0.6.0
appVersion: 0.6.1
version: 0.7.0
appVersion: 0.7.0
kubeVersion: ">=1.11.0-0"
engine: gotpl
description: Flagger's load testing services based on rakyll/hey and bojand/ghz that generates traffic during canary analysis when configured as a webhook.

View File

@@ -21,6 +21,8 @@ spec:
spec:
{{- if .Values.serviceAccountName }}
serviceAccountName: {{ .Values.serviceAccountName }}
{{- else if .Values.rbac.create }}
serviceAccountName: {{ include "loadtester.fullname" . }}
{{- end }}
containers:
- name: {{ .Chart.Name }}

View File

@@ -0,0 +1,54 @@
---
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if eq .Values.rbac.scope "cluster" }}
kind: ClusterRole
{{- else }}
kind: Role
{{- end }}
metadata:
name: {{ template "loadtester.fullname" . }}
labels:
helm.sh/chart: {{ template "loadtester.chart" . }}
app.kubernetes.io/name: {{ template "loadtester.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
rules:
{{ toYaml .Values.rbac.rules | indent 2 }}
---
apiVersion: rbac.authorization.k8s.io/v1
{{- if eq .Values.rbac.scope "cluster" }}
kind: ClusterRoleBinding
{{- else }}
kind: RoleBinding
{{- end }}
metadata:
name: {{ template "loadtester.fullname" . }}
labels:
helm.sh/chart: {{ template "loadtester.chart" . }}
app.kubernetes.io/name: {{ template "loadtester.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
{{- if eq .Values.rbac.scope "cluster" }}
kind: ClusterRole
{{- else }}
kind: Role
{{- end }}
name: {{ template "loadtester.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ template "loadtester.fullname" . }}
namespace: {{ .Release.Namespace }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "loadtester.fullname" . }}
labels:
helm.sh/chart: {{ template "loadtester.chart" . }}
app.kubernetes.io/name: {{ template "loadtester.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@@ -2,7 +2,7 @@ replicaCount: 1
image:
repository: weaveworks/flagger-loadtester
tag: 0.6.1
tag: 0.7.0
pullPolicy: IfNotPresent
logLevel: info
@@ -27,6 +27,20 @@ tolerations: []
affinity: {}
rbac:
# rbac.create: `true` if rbac resources should be created
create: false
# rbac.scope: `cluster` to create cluster-scope rbac resources (ClusterRole/ClusterRoleBinding)
# otherwise, namespace-scope rbac resources will be created (Role/RoleBinding)
scope:
# rbac.rules: array of rules to apply to the role. example:
# rules:
# - apiGroups: [""]
# resources: ["pods"]
# verbs: ["list", "get"]
rules: []
# name of an existing service account to use - if not creating rbac resources
serviceAccountName: ""
# App Mesh virtual node settings

View File

@@ -1,6 +1,6 @@
apiVersion: v1
version: 2.3.0
appVersion: 1.7.0
version: 3.0.0
appVersion: 2.0.0
name: podinfo
engine: gotpl
description: Flagger canary deployment demo chart

View File

@@ -24,7 +24,7 @@ spec:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: {{ .Chart.Name }}
- name: podinfo
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:

View File

@@ -1,7 +1,7 @@
# Default values for podinfo.
image:
repository: quay.io/stefanprodan/podinfo
tag: 1.7.0
repository: stefanprodan/podinfo
tag: 2.0.0
pullPolicy: IfNotPresent
service:

View File

@@ -10,7 +10,7 @@ import (
"time"
)
var VERSION = "0.6.1"
var VERSION = "0.7.0"
var (
logLevel string
port string

View File

@@ -798,6 +798,18 @@ webhooks:
cmd: "ghz -z 1m -q 10 -c 2 --insecure podinfo.test:9898"
```
`ghz` uses reflection to identify which gRPC method to call. If you do not wish to enable reflection for your gRPC service you can implement a standardized health check from the [grpc-proto](https://github.com/grpc/grpc-proto) library. To use this [health check schema](https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto) without reflection you can pass a parameter to `ghz` like this
```yaml
webhooks:
- name: grpc-load-test-no-reflection
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "ghz --insecure --proto=/tmp/ghz/health.proto --call=grpc.health.v1.Health/Check podinfo.test:9898"
```
The load tester can run arbitrary commands as long as the binary is present in the container image.
For example if you you want to replace `hey` with another CLI, you can create your own Docker image:

View File

@@ -138,7 +138,7 @@ helm upgrade -i frontend flagger/podinfo/ \
--reuse-values \
--set canary.loadtest.enabled=true \
--set canary.helmtest.enabled=true \
--set image.tag=1.7.1
--set image.tag=2.0.1
```
Flagger detects that the deployment revision changed and starts the canary analysis:
@@ -283,17 +283,17 @@ metadata:
namespace: test
annotations:
flux.weave.works/automated: "true"
flux.weave.works/tag.chart-image: semver:~1.7
flux.weave.works/tag.chart-image: semver:~2.0
spec:
releaseName: frontend
chart:
repository: https://stefanprodan.github.io/flagger/
name: podinfo
version: 2.3.0
git: https://github.com/weaveowrks/flagger
ref: master
path: charts/podinfo
values:
image:
repository: quay.io/stefanprodan/podinfo
tag: 1.7.0
repository: stefanprodan/podinfo
tag: 2.0.0
backend: http://backend-podinfo:9898/echo
canary:
enabled: true
@@ -311,26 +311,26 @@ In the `chart` section I've defined the release source by specifying the Helm re
In the `values` section I've overwritten the defaults set in values.yaml.
With the `flux.weave.works` annotations I instruct Flux to automate this release.
When an image tag in the sem ver range of `1.7.0 - 1.7.99` is pushed to Quay,
When an image tag in the sem ver range of `2.0.0 - 2.0.99` is pushed to Quay,
Flux will upgrade the Helm release and from there Flagger will pick up the change and start a canary deployment.
Install [Weave Flux](https://github.com/weaveworks/flux) and its Helm Operator by specifying your Git repo URL:
```bash
helm repo add weaveworks https://weaveworks.github.io/flux
helm repo add fluxcd https://charts.fluxcd.io
helm install --name flux \
--set helmOperator.create=true \
--set helmOperator.createCRD=true \
--set git.url=git@github.com:<USERNAME>/<REPOSITORY> \
--namespace flux \
weaveworks/flux
--namespace fluxcd \
fluxcd/flux
```
At startup Flux generates a SSH key and logs the public key. Find the SSH public key with:
```bash
kubectl -n flux logs deployment/flux | grep identity.pub | cut -d '"' -f2
kubectl -n fluxcd logs deployment/flux | grep identity.pub | cut -d '"' -f2
```
In order to sync your cluster state with Git you need to copy the public key and create a
@@ -344,9 +344,9 @@ launch the `frontend` and `backend` apps.
A CI/CD pipeline for the `frontend` release could look like this:
* cut a release from the master branch of the podinfo code repo with the git tag `1.7.1`
* CI builds the image and pushes the `podinfo:1.7.1` image to the container registry
* Flux scans the registry and updates the Helm release `image.tag` to `1.7.1`
* cut a release from the master branch of the podinfo code repo with the git tag `2.0.1`
* CI builds the image and pushes the `podinfo:2.0.1` image to the container registry
* Flux scans the registry and updates the Helm release `image.tag` to `2.0.1`
* Flux commits and push the change to the cluster repo
* Flux applies the updated Helm release on the cluster
* Flux Helm Operator picks up the change and calls Tiller to upgrade the release
@@ -356,7 +356,7 @@ A CI/CD pipeline for the `frontend` release could look like this:
* Based on the analysis result the canary deployment is promoted to production or rolled back
* Flagger sends a Slack notification with the canary result
If the canary fails, fix the bug, do another patch release eg `1.7.2` and the whole process will run again.
If the canary fails, fix the bug, do another patch release eg `2.0.2` and the whole process will run again.
A canary deployment can fail due to any of the following reasons:

View File

@@ -131,7 +131,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/abtest \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
podinfod=stefanprodan/podinfo:2.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:

View File

@@ -178,7 +178,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
podinfod=stefanprodan/podinfo:2.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
@@ -241,7 +241,7 @@ Trigger a canary deployment:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.4.2
podinfod=stefanprodan/podinfo:2.0.2
```
Exec into the load tester pod with:

View File

@@ -172,7 +172,7 @@ Trigger a deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
podinfod=stefanprodan/podinfo:2.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
@@ -297,7 +297,7 @@ Trigger a deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.3
podinfod=stefanprodan/podinfo:2.0.3
```
Generate 404s:

View File

@@ -103,6 +103,7 @@ metadata:
name: podinfo
namespace: test
spec:
provider: gloo
# deployment reference
targetRef:
apiVersion: apps/v1
@@ -197,7 +198,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
podinfod=stefanprodan/podinfo:2.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
@@ -251,7 +252,7 @@ Trigger another canary deployment:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.4.2
podinfod=stefanprodan/podinfo:2.0.2
```
Generate HTTP 500 errors:
@@ -334,7 +335,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.4.3
podinfod=stefanprodan/podinfo:2.0.3
```
Generate 404s:
@@ -362,5 +363,3 @@ Canary failed! Scaling down podinfo.test
```
If you have Slack configured, Flagger will send a notification with the reason why the canary failed.

View File

@@ -150,7 +150,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
podinfod=stefanprodan/podinfo:2.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
@@ -208,7 +208,7 @@ Trigger another canary deployment:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.2
podinfod=stefanprodan/podinfo:2.0.2
```
Exec into the load tester pod with:
@@ -297,7 +297,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.3
podinfod=stefanprodan/podinfo:2.0.3
```
Generate 404s:
@@ -444,7 +444,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.4
podinfod=stefanprodan/podinfo:2.0.4
```
Flagger detects that the deployment revision changed and starts the A/B testing:

View File

@@ -104,6 +104,7 @@ metadata:
name: podinfo
namespace: test
spec:
provider: nginx
# deployment reference
targetRef:
apiVersion: apps/v1
@@ -189,7 +190,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
podinfod=stefanprodan/podinfo:2.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
@@ -243,7 +244,7 @@ Trigger another canary deployment:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.4.2
podinfod=stefanprodan/podinfo:2.0.2
```
Generate HTTP 500 errors:
@@ -313,7 +314,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.4.3
podinfod=stefanprodan/podinfo:2.0.3
```
Generate high response latency:
@@ -387,7 +388,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.5.0
podinfod=stefanprodan/podinfo:2.0.4
```
Flagger detects that the deployment revision changed and starts the A/B testing:

View File

@@ -79,8 +79,15 @@ spec:
# milliseconds
threshold: 500
interval: 30s
# generate traffic during analysis
# testing (optional)
webhooks:
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 30s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
@@ -94,6 +101,9 @@ Save the above resource as podinfo-canary.yaml and then apply it:
kubectl apply -f ./podinfo-canary.yaml
```
When the canary analysis starts, Flagger will call the pre-rollout webhooks before routing traffic to the canary.
The canary analysis will run for five minutes while validating the HTTP metrics and rollout hooks every minute.
After a couple of seconds Flagger will create the canary objects:
```bash
@@ -119,7 +129,7 @@ Trigger a canary deployment by updating the container image:
```bash
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
podinfod=stefanprodan/podinfo:2.0.1
```
Flagger detects that the deployment revision changed and starts a new rollout:
@@ -169,14 +179,17 @@ prod backend Failed 0 2019-01-14T17:05:07Z
During the canary analysis you can generate HTTP 500 errors and high latency to test if Flagger pauses the rollout.
Create a tester pod and exec into it:
Trigger another canary deployment:
```bash
kubectl -n test run tester \
--image=quay.io/stefanprodan/podinfo:1.2.1 \
-- ./podinfo --port=9898
kubectl -n test set image deployment/podinfo \
podinfod=stefanprodan/podinfo:2.0.2
```
kubectl -n test exec -it tester-xx-xx sh
Exec into the load tester pod with:
```bash
kubectl -n test exec -it flagger-loadtester-xx-xx sh
```
Generate HTTP 500 errors:
@@ -216,4 +229,3 @@ Events:
Warning Synced 1m flagger Rolling back podinfo.test failed checks threshold reached 10
Warning Synced 1m flagger Canary failed! Scaling down podinfo.test
```

View File

@@ -1,23 +1,38 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
REPO_ROOT=$(git rev-parse --show-toplevel)
CODEGEN_VERSION="@v0.0.0-20190620073620-d55040311883"
CODEGEN_PKG=${CODEGEN_PKG:-$(echo `go env GOPATH`"/pkg/mod/k8s.io/code-generator${CODEGEN_VERSION}")}
SCRIPT_ROOT=$(realpath $(dirname ${BASH_SOURCE})/..)
if [[ "$CIRCLECI" ]]; then
ls $(echo `go env GOPATH`'/pkg/mod/k8s.io/');
mkdir -p ${REPO_ROOT}/bin;
cp -r ${CODEGEN_PKG} ${REPO_ROOT}/bin/;
CODEGEN_PKG=${REPO_ROOT}/bin/code-generator${CODEGEN_VERSION};
echo ">> $CODEGEN_PKG";
fi
# Grab code-generator version from go.sum.
CODEGEN_VERSION=$(grep 'k8s.io/code-generator' go.sum | awk '{print $2}' | head -1)
CODEGEN_PKG=$(echo `go env GOPATH`"/pkg/mod/k8s.io/code-generator@${CODEGEN_VERSION}")
echo ">> Using ${CODEGEN_PKG}"
# code-generator does work with go.mod but makes assumptions about
# the project living in `$GOPATH/src`. To work around this and support
# any location; create a temporary directory, use this as an output
# base, and copy everything back once generated.
TEMP_DIR=$(mktemp -d)
cleanup() {
echo ">> Removing ${TEMP_DIR}"
rm -rf ${TEMP_DIR}
}
trap "cleanup" EXIT SIGINT
echo ">> Temporary output directory ${TEMP_DIR}"
# Ensure we can execute.
chmod +x ${CODEGEN_PKG}/generate-groups.sh
${CODEGEN_PKG}/generate-groups.sh all \
github.com/weaveworks/flagger/pkg/client github.com/weaveworks/flagger/pkg/apis \
"appmesh:v1beta1 istio:v1alpha3 flagger:v1alpha3 smi:v1alpha1" \
--go-header-file ${REPO_ROOT}/hack/boilerplate.go.txt
--output-base "${TEMP_DIR}" \
--go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt
# Copy everything back.
cp -r "${TEMP_DIR}/github.com/weaveworks/flagger/." "${SCRIPT_ROOT}/"

View File

@@ -46,10 +46,13 @@ spec:
- canaryAnalysis
properties:
provider:
description: Traffic managent provider
type: string
progressDeadlineSeconds:
description: Deployment progress deadline
type: number
targetRef:
description: Deployment selector
type: object
required: ['apiVersion', 'kind', 'name']
properties:
@@ -60,6 +63,7 @@ spec:
name:
type: string
autoscalerRef:
description: HPA selector
anyOf:
- type: string
- type: object
@@ -72,6 +76,7 @@ spec:
name:
type: string
ingressRef:
description: NGINX ingress selector
anyOf:
- type: string
- type: object
@@ -88,15 +93,60 @@ spec:
required: ['port']
properties:
port:
description: Container port number
type: number
portName:
description: Container port name
type: string
portDiscovery:
description: Enable port dicovery
type: boolean
meshName:
description: AppMesh mesh name
type: string
backends:
description: AppMesh backend array
anyOf:
- type: string
- type: array
timeout:
description: Istio HTTP or gRPC request timeout
type: string
trafficPolicy:
description: Istio traffic policy
anyOf:
- type: string
- type: object
match:
description: Istio URL match conditions
anyOf:
- type: string
- type: array
rewrite:
description: Istio URL rewrite
anyOf:
- type: string
- type: object
headers:
description: Istio headers operations
anyOf:
- type: string
- type: object
corsPolicy:
description: Istio CORS policy
anyOf:
- type: string
- type: object
gateways:
description: Istio gateways list
anyOf:
- type: string
- type: array
hosts:
description: Istio hosts list
anyOf:
- type: string
- type: array
skipAnalysis:
type: boolean
canaryAnalysis:
@@ -117,6 +167,11 @@ spec:
stepWeight:
description: Canary incremental traffic percentage step
type: number
match:
description: A/B testing match conditions
anyOf:
- type: string
- type: array
metrics:
description: Prometheus query list for this canary
type: array
@@ -154,6 +209,7 @@ spec:
type: string
enum:
- ""
- confirm-rollout
- pre-rollout
- rollout
- post-rollout
@@ -165,6 +221,11 @@ spec:
description: Request timeout for this webhook
type: string
pattern: "^[0-9]+(m|s)"
metadata:
description: Metadata (key-value pairs) for this webhook
anyOf:
- type: string
- type: object
status:
properties:
phase:

View File

@@ -8,4 +8,4 @@ resources:
- deployment.yaml
images:
- name: weaveworks/flagger
newTag: 0.18.2
newTag: 0.18.3

View File

@@ -24,7 +24,7 @@ spec:
spec:
containers:
- name: podinfod
image: quay.io/stefanprodan/podinfo:1.7.0
image: stefanprodan/podinfo:2.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9898

View File

@@ -102,13 +102,18 @@ type VirtualServiceSpec struct {
Routes []Route `json:"routes,omitempty"`
}
// VirtualRouter is the spec for a VirtualRouter resource
type VirtualRouter struct {
Name string `json:"name"`
Name string `json:"name"`
Listeners []Listener `json:"listeners,omitempty"`
}
type Route struct {
Name string `json:"name"`
Http HttpRoute `json:"http"`
Name string `json:"name"`
// +optional
Http *HttpRoute `json:"http,omitempty"`
// +optional
Tcp *TcpRoute `json:"tcp,omitempty"`
}
type HttpRoute struct {
@@ -124,6 +129,14 @@ type HttpRouteAction struct {
WeightedTargets []WeightedTarget `json:"weightedTargets"`
}
type TcpRoute struct {
Action TcpRouteAction `json:"action"`
}
type TcpRouteAction struct {
WeightedTargets []WeightedTarget `json:"weightedTargets"`
}
type WeightedTarget struct {
VirtualNodeName string `json:"virtualNodeName"`
Weight int64 `json:"weight"`
@@ -203,6 +216,8 @@ type VirtualNodeSpec struct {
ServiceDiscovery *ServiceDiscovery `json:"serviceDiscovery,omitempty"`
// +optional
Backends []Backend `json:"backends,omitempty"`
// +optional
Logging *Logging `json:"logging,omitempty"`
}
type Listener struct {
@@ -237,6 +252,21 @@ type VirtualServiceBackend struct {
VirtualServiceName string `json:"virtualServiceName"`
}
// Logging refers to https://docs.aws.amazon.com/app-mesh/latest/APIReference/API_Logging.html
type Logging struct {
AccessLog *AccessLog `json:"accessLog"`
}
// AccessLog refers to https://docs.aws.amazon.com/app-mesh/latest/APIReference/API_AccessLog.html
type AccessLog struct {
File *FileAccessLog `json:"file"`
}
// FileAccessLog refers to https://docs.aws.amazon.com/app-mesh/latest/APIReference/API_FileAccessLog.html
type FileAccessLog struct {
Path string `json:"path"`
}
// VirtualNodeStatus is the status for a VirtualNode resource
type VirtualNodeStatus struct {
MeshArn *string `json:"meshArn,omitempty"`

View File

@@ -24,6 +24,27 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AccessLog) DeepCopyInto(out *AccessLog) {
*out = *in
if in.File != nil {
in, out := &in.File, &out.File
*out = new(FileAccessLog)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessLog.
func (in *AccessLog) DeepCopy() *AccessLog {
if in == nil {
return nil
}
out := new(AccessLog)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Backend) DeepCopyInto(out *Backend) {
*out = *in
@@ -73,6 +94,22 @@ func (in *DnsServiceDiscovery) DeepCopy() *DnsServiceDiscovery {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FileAccessLog) DeepCopyInto(out *FileAccessLog) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FileAccessLog.
func (in *FileAccessLog) DeepCopy() *FileAccessLog {
if in == nil {
return nil
}
out := new(FileAccessLog)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HttpRoute) DeepCopyInto(out *HttpRoute) {
*out = *in
@@ -145,6 +182,27 @@ func (in *Listener) DeepCopy() *Listener {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Logging) DeepCopyInto(out *Logging) {
*out = *in
if in.AccessLog != nil {
in, out := &in.AccessLog, &out.AccessLog
*out = new(AccessLog)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Logging.
func (in *Logging) DeepCopy() *Logging {
if in == nil {
return nil
}
out := new(Logging)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Mesh) DeepCopyInto(out *Mesh) {
*out = *in
@@ -304,7 +362,16 @@ func (in *PortMapping) DeepCopy() *PortMapping {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Route) DeepCopyInto(out *Route) {
*out = *in
in.Http.DeepCopyInto(&out.Http)
if in.Http != nil {
in, out := &in.Http, &out.Http
*out = new(HttpRoute)
(*in).DeepCopyInto(*out)
}
if in.Tcp != nil {
in, out := &in.Tcp, &out.Tcp
*out = new(TcpRoute)
(*in).DeepCopyInto(*out)
}
return
}
@@ -344,6 +411,44 @@ func (in *ServiceDiscovery) DeepCopy() *ServiceDiscovery {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TcpRoute) DeepCopyInto(out *TcpRoute) {
*out = *in
in.Action.DeepCopyInto(&out.Action)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcpRoute.
func (in *TcpRoute) DeepCopy() *TcpRoute {
if in == nil {
return nil
}
out := new(TcpRoute)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TcpRouteAction) DeepCopyInto(out *TcpRouteAction) {
*out = *in
if in.WeightedTargets != nil {
in, out := &in.WeightedTargets, &out.WeightedTargets
*out = make([]WeightedTarget, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TcpRouteAction.
func (in *TcpRouteAction) DeepCopy() *TcpRouteAction {
if in == nil {
return nil
}
out := new(TcpRouteAction)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VirtualNode) DeepCopyInto(out *VirtualNode) {
*out = *in
@@ -453,6 +558,11 @@ func (in *VirtualNodeSpec) DeepCopyInto(out *VirtualNodeSpec) {
*out = make([]Backend, len(*in))
copy(*out, *in)
}
if in.Logging != nil {
in, out := &in.Logging, &out.Logging
*out = new(Logging)
(*in).DeepCopyInto(*out)
}
return
}
@@ -514,6 +624,11 @@ func (in *VirtualNodeStatus) DeepCopy() *VirtualNodeStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VirtualRouter) DeepCopyInto(out *VirtualRouter) {
*out = *in
if in.Listeners != nil {
in, out := &in.Listeners, &out.Listeners
*out = make([]Listener, len(*in))
copy(*out, *in)
}
return
}
@@ -640,7 +755,7 @@ func (in *VirtualServiceSpec) DeepCopyInto(out *VirtualServiceSpec) {
if in.VirtualRouter != nil {
in, out := &in.VirtualRouter, &out.VirtualRouter
*out = new(VirtualRouter)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.Routes != nil {
in, out := &in.Routes, &out.Routes

View File

@@ -0,0 +1,30 @@
package notifier
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func Test_postMessage(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
var payload = make(map[string]string)
err = json.Unmarshal(b, &payload)
if payload["status"] != "success" {
t.Fatal("wrong payload")
}
}))
defer ts.Close()
err := postMessage(ts.URL, map[string]string{"status": "success"})
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,35 @@
package notifier
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestSlack_Post(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
var payload = SlackPayload{}
err = json.Unmarshal(b, &payload)
if payload.Attachments[0].AuthorName != "podinfo.test" {
t.Fatal("wrong author name")
}
}))
defer ts.Close()
slack, err := NewSlack(ts.URL, "test", "test")
if err != nil {
t.Fatal(err)
}
err = slack.Post("podinfo", "test", "test", nil, true)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,35 @@
package notifier
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestTeams_Post(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
var payload = MSTeamsPayload{}
err = json.Unmarshal(b, &payload)
if payload.Sections[0].ActivitySubtitle != "podinfo.test" {
t.Fatal("wrong activity subtitle")
}
}))
defer ts.Close()
teams, err := NewMSTeams(ts.URL)
if err != nil {
t.Fatal(err)
}
err = teams.Post("podinfo", "test", "test", nil, true)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -165,11 +165,19 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
MeshName: canary.Spec.Service.MeshName,
VirtualRouter: &AppmeshV1beta1.VirtualRouter{
Name: fmt.Sprintf("%s-router", targetName),
Listeners: []AppmeshV1beta1.Listener{
{
PortMapping: AppmeshV1beta1.PortMapping{
Port: int64(canary.Spec.Service.Port),
Protocol: "http",
},
},
},
},
Routes: []AppmeshV1beta1.Route{
{
Name: fmt.Sprintf("%s-route", targetName),
Http: AppmeshV1beta1.HttpRoute{
Http: &AppmeshV1beta1.HttpRoute{
Match: AppmeshV1beta1.HttpRouteMatch{
Prefix: routePrefix,
},

View File

@@ -1,4 +1,4 @@
package version
var VERSION = "0.18.2"
var VERSION = "0.18.3"
var REVISION = "unknown"

View File

@@ -45,6 +45,7 @@ metadata:
name: podinfo
namespace: test
spec:
provider: gloo
targetRef:
apiVersion: apps/v1
kind: Deployment
@@ -62,12 +63,22 @@ spec:
threshold: 99
interval: 1m
webhooks:
- name: "gate"
type: confirm-rollout
url: http://flagger-loadtester.test/gate/approve
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 10s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "hey -z 10m -q 10 -c 2 -host app.example.com http://gateway-proxy.gloo-system"
cmd: "hey -z 2m -q 5 -c 2 -host app.example.com http://gateway-proxy-v2.gloo-system"
logCmdOutput: "true"
EOF
@@ -103,7 +114,9 @@ until ${ok}; do
if [[ ${count} -eq ${retries} ]]; then
kubectl -n test describe deployment/podinfo
kubectl -n test describe deployment/podinfo-primary
kubectl -n test logs deployment/flagger-loadtester
kubectl -n gloo-system logs deployment/flagger
kubectl -n gloo-system get all
kubectl -n gloo-system get virtualservice podinfo -oyaml
echo "No more retries left"
exit 1

View File

@@ -46,6 +46,16 @@ spec:
threshold: 500
interval: 30s
webhooks:
- name: "gate"
type: confirm-rollout
url: http://flagger-loadtester.test/gate/approve
- name: acceptance-test
type: pre-rollout
url: http://flagger-loadtester.test/
timeout: 10s
metadata:
type: bash
cmd: "curl -sd 'test' http://podinfo-canary:9898/token | grep token"
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s