├── docs └── img │ ├── podinfo-500.png │ ├── gitops-appmesh.png │ └── gitops-appmesh-stack.png ├── infrastructure ├── cluster-addons │ ├── namespaces │ │ ├── appmesh-system.yaml │ │ └── appmesh-gateway.yaml │ ├── sources │ │ ├── flagger.yaml │ │ ├── eks.yaml │ │ └── bitnami.yaml │ └── controllers │ │ ├── appmesh-controller.yaml │ │ └── metrics-server.yaml ├── mesh-addons │ ├── kustomization.yaml │ ├── prometheus.yaml │ ├── gateway.yaml │ └── flagger.yaml └── mesh │ └── appmesh.yaml ├── .sourceignore ├── apps ├── namespace.yaml ├── kustomizeconfig.yaml ├── loadtester │ ├── kustomization.yaml │ ├── service.yaml │ ├── virtual-node.yaml │ └── deployment.yaml ├── podinfo │ ├── gateway-route.yaml │ ├── kustomization.yaml │ ├── hpa.yaml │ ├── deployment.yaml │ ├── canary.yaml │ └── abtest.yaml └── kustomization.yaml ├── .github ├── actions │ └── tools │ │ ├── Dockerfile │ │ ├── action.yml │ │ └── entrypoint.sh └── workflows │ └── test.yaml ├── .gitignore ├── clusters └── appmesh │ ├── apps.yaml │ └── infrastructure.yaml ├── .eksctl └── config.yaml ├── scripts └── validate.sh ├── README.md └── LICENSE /docs/img/podinfo-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-appmesh/HEAD/docs/img/podinfo-500.png -------------------------------------------------------------------------------- /docs/img/gitops-appmesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-appmesh/HEAD/docs/img/gitops-appmesh.png -------------------------------------------------------------------------------- /docs/img/gitops-appmesh-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/gitops-appmesh/HEAD/docs/img/gitops-appmesh-stack.png -------------------------------------------------------------------------------- /infrastructure/cluster-addons/namespaces/appmesh-system.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: appmesh-system 5 | -------------------------------------------------------------------------------- /.sourceignore: -------------------------------------------------------------------------------- 1 | # Flux ignore 2 | # https://toolkit.fluxcd.io/components/source/gitrepositories/#excluding-files 3 | .eksctl/ 4 | docs/ 5 | scripts/ 6 | *.md 7 | -------------------------------------------------------------------------------- /apps/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: apps 5 | labels: 6 | appmesh.k8s.aws/sidecarInjectorWebhook: enabled 7 | -------------------------------------------------------------------------------- /apps/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | varReference: 2 | - path: spec/analysis/webhooks/url 3 | kind: Canary 4 | - path: spec/analysis/webhooks/metadata/cmd 5 | kind: Canary 6 | -------------------------------------------------------------------------------- /.github/actions/tools/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM stefanprodan/alpine-base:latest 2 | 3 | COPY entrypoint.sh /entrypoint.sh 4 | RUN chmod +x /entrypoint.sh 5 | 6 | ENTRYPOINT ["/entrypoint.sh"] 7 | -------------------------------------------------------------------------------- /apps/loadtester/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - deployment.yaml 5 | - service.yaml 6 | - virtual-node.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/mesh-addons/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - gateway.yaml 5 | - flagger.yaml 6 | - prometheus.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/cluster-addons/namespaces/appmesh-gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: appmesh-gateway 5 | labels: 6 | appmesh.k8s.aws/sidecarInjectorWebhook: enabled 7 | -------------------------------------------------------------------------------- /infrastructure/mesh/appmesh.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appmesh.k8s.aws/v1beta2 2 | kind: Mesh 3 | metadata: 4 | name: appmesh 5 | spec: 6 | namespaceSelector: 7 | matchLabels: 8 | appmesh.k8s.aws/sidecarInjectorWebhook: enabled 9 | -------------------------------------------------------------------------------- /infrastructure/cluster-addons/sources/flagger.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta1 2 | kind: HelmRepository 3 | metadata: 4 | name: flagger 5 | namespace: flux-system 6 | spec: 7 | interval: 30m 8 | url: https://flagger.app 9 | -------------------------------------------------------------------------------- /infrastructure/cluster-addons/sources/eks.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta1 2 | kind: HelmRepository 3 | metadata: 4 | name: eks 5 | namespace: flux-system 6 | spec: 7 | interval: 1h 8 | url: https://aws.github.io/eks-charts 9 | -------------------------------------------------------------------------------- /.github/actions/tools/action.yml: -------------------------------------------------------------------------------- 1 | name: 'kustomize' 2 | description: 'A GitHub Action with Kubernetes tools' 3 | author: 'Stefan Prodan' 4 | branding: 5 | icon: 'command' 6 | color: 'blue' 7 | runs: 8 | using: 'docker' 9 | image: 'Dockerfile' 10 | -------------------------------------------------------------------------------- /infrastructure/cluster-addons/sources/bitnami.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta1 2 | kind: HelmRepository 3 | metadata: 4 | name: bitnami 5 | namespace: flux-system 6 | spec: 7 | interval: 30m 8 | url: https://charts.bitnami.com/bitnami 9 | -------------------------------------------------------------------------------- /apps/podinfo/gateway-route.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appmesh.k8s.aws/v1beta2 2 | kind: GatewayRoute 3 | metadata: 4 | name: podinfo 5 | spec: 6 | httpRoute: 7 | match: 8 | prefix: "/" 9 | action: 10 | target: 11 | virtualService: 12 | virtualServiceRef: 13 | name: podinfo 14 | -------------------------------------------------------------------------------- /apps/podinfo/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - canary.yaml 5 | - deployment.yaml 6 | - hpa.yaml 7 | - gateway-route.yaml 8 | images: 9 | - name: ghcr.io/stefanprodan/podinfo 10 | newName: ghcr.io/stefanprodan/podinfo 11 | newTag: 5.0.0 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /apps/loadtester/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: flagger-loadtester 5 | labels: 6 | app: flagger-loadtester 7 | spec: 8 | type: ClusterIP 9 | selector: 10 | app: flagger-loadtester 11 | ports: 12 | - name: http 13 | port: 80 14 | protocol: TCP 15 | targetPort: http 16 | -------------------------------------------------------------------------------- /clusters/appmesh/apps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: apps 5 | namespace: flux-system 6 | spec: 7 | interval: 10m 8 | dependsOn: 9 | - name: mesh-addons 10 | sourceRef: 11 | kind: GitRepository 12 | name: flux-system 13 | path: ./apps 14 | prune: true 15 | validation: client 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - 'main' 8 | 9 | jobs: 10 | manifests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Setup tools 16 | uses: ./.github/actions/tools 17 | - name: Validate manifests 18 | run: ./scripts/validate.sh 19 | -------------------------------------------------------------------------------- /apps/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: apps 4 | resources: 5 | - namespace.yaml 6 | - loadtester 7 | - podinfo 8 | vars: 9 | - name: NAMESPACE 10 | objref: 11 | kind: Namespace 12 | name: apps 13 | apiVersion: v1 14 | fieldref: 15 | fieldpath: metadata.name 16 | configurations: 17 | - kustomizeconfig.yaml 18 | 19 | -------------------------------------------------------------------------------- /.eksctl/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eksctl.io/v1alpha5 2 | kind: ClusterConfig 3 | metadata: 4 | name: appmesh 5 | region: us-west-2 6 | version: "1.18" 7 | nodeGroups: 8 | - name: default 9 | instanceType: m5.large 10 | desiredCapacity: 2 11 | volumeSize: 120 12 | iam: 13 | withAddonPolicies: 14 | appMesh: true 15 | xRay: true 16 | certManager: true 17 | albIngress: true 18 | -------------------------------------------------------------------------------- /apps/podinfo/hpa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2beta2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: podinfo 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: podinfo 10 | minReplicas: 2 11 | maxReplicas: 4 12 | metrics: 13 | - type: Resource 14 | resource: 15 | name: cpu 16 | target: 17 | type: Utilization 18 | # scale up if usage is above 19 | # 99% of the requested CPU (100m) 20 | averageUtilization: 99 21 | -------------------------------------------------------------------------------- /apps/loadtester/virtual-node.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appmesh.k8s.aws/v1beta2 2 | kind: VirtualNode 3 | metadata: 4 | name: flagger-loadtester 5 | labels: 6 | app: flagger-loadtester 7 | spec: 8 | podSelector: 9 | matchLabels: 10 | app: flagger-loadtester 11 | logging: 12 | accessLog: 13 | file: 14 | path: /dev/stdout 15 | backends: 16 | - virtualService: 17 | virtualServiceRef: 18 | name: podinfo 19 | - virtualService: 20 | virtualServiceRef: 21 | name: podinfo-canary 22 | -------------------------------------------------------------------------------- /infrastructure/cluster-addons/controllers/appmesh-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: appmesh-controller 5 | namespace: appmesh-system 6 | spec: 7 | releaseName: appmesh-controller 8 | chart: 9 | spec: 10 | chart: appmesh-controller 11 | sourceRef: 12 | kind: HelmRepository 13 | name: eks 14 | namespace: flux-system 15 | interval: 5m 16 | install: 17 | remediation: 18 | retries: -1 19 | upgrade: 20 | remediation: 21 | retries: 3 22 | -------------------------------------------------------------------------------- /infrastructure/mesh-addons/prometheus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: appmesh-prometheus 5 | namespace: appmesh-system 6 | spec: 7 | releaseName: appmesh-prometheus 8 | chart: 9 | spec: 10 | chart: appmesh-prometheus 11 | sourceRef: 12 | kind: HelmRepository 13 | name: eks 14 | namespace: flux-system 15 | interval: 5m 16 | install: 17 | remediation: 18 | retries: 3 19 | upgrade: 20 | remediation: 21 | retries: 3 22 | values: 23 | retention: 2h 24 | -------------------------------------------------------------------------------- /infrastructure/mesh-addons/gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: appmesh-gateway 5 | namespace: appmesh-gateway 6 | spec: 7 | releaseName: appmesh-gateway 8 | chart: 9 | spec: 10 | chart: appmesh-gateway 11 | sourceRef: 12 | kind: HelmRepository 13 | name: eks 14 | namespace: flux-system 15 | interval: 5m 16 | install: 17 | remediation: 18 | retries: -1 19 | upgrade: 20 | remediation: 21 | retries: 3 22 | values: 23 | appmesh: 24 | gateway: true 25 | -------------------------------------------------------------------------------- /infrastructure/cluster-addons/controllers/metrics-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: metrics-server 5 | namespace: kube-system 6 | spec: 7 | releaseName: metrics-server 8 | chart: 9 | spec: 10 | chart: metrics-server 11 | sourceRef: 12 | kind: HelmRepository 13 | name: bitnami 14 | namespace: flux-system 15 | interval: 5m 16 | install: 17 | remediation: 18 | retries: 3 19 | upgrade: 20 | remediation: 21 | retries: 3 22 | values: 23 | apiService: 24 | create: true 25 | -------------------------------------------------------------------------------- /infrastructure/mesh-addons/flagger.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: flagger 5 | namespace: appmesh-system 6 | spec: 7 | releaseName: flagger 8 | chart: 9 | spec: 10 | chart: flagger 11 | sourceRef: 12 | kind: HelmRepository 13 | name: flagger 14 | namespace: flux-system 15 | interval: 5m 16 | install: 17 | remediation: 18 | retries: 3 19 | upgrade: 20 | remediation: 21 | retries: 3 22 | values: 23 | metricsServer: http://appmesh-prometheus:9090 24 | meshProvider: appmesh:v1beta2 25 | -------------------------------------------------------------------------------- /.github/actions/tools/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | YQ_VERSION="3.4.1" 6 | KUSTOMIZE_VERSION="3.8.6" 7 | KUBEVAL_VERSION="0.15.0" 8 | 9 | mkdir -p $GITHUB_WORKSPACE/bin 10 | 11 | curl -sL https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64 -o yq 12 | 13 | cp ./yq $GITHUB_WORKSPACE/bin 14 | chmod +x $GITHUB_WORKSPACE/bin/yq 15 | 16 | kustomize_url=https://github.com/kubernetes-sigs/kustomize/releases/download && \ 17 | curl -sL ${kustomize_url}/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz | \ 18 | tar xz 19 | 20 | cp ./kustomize $GITHUB_WORKSPACE/bin 21 | chmod +x $GITHUB_WORKSPACE/bin/kustomize 22 | 23 | curl -sL https://github.com/instrumenta/kubeval/releases/download/${KUBEVAL_VERSION}/kubeval-linux-amd64.tar.gz | \ 24 | tar xz 25 | 26 | cp ./kubeval $GITHUB_WORKSPACE/bin 27 | chmod +x $GITHUB_WORKSPACE/bin/kubeval 28 | 29 | echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH 30 | echo "$RUNNER_WORKSPACE/$(basename $GITHUB_REPOSITORY)/bin" >> $GITHUB_PATH 31 | -------------------------------------------------------------------------------- /scripts/validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Flux authors. All rights reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | 19 | find . -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file; 20 | do 21 | echo "INFO - Validating $file" 22 | yq validate -d'*' "$file" 23 | done 24 | 25 | k="kustomization.yaml" 26 | find . -type f -name $k -print0 | while IFS= read -r -d $'\0' file; 27 | do 28 | echo "INFO - Validating kustomization ${file/%$k}" 29 | kustomize build "${file/%$k}" | kubeval --ignore-missing-schemas 30 | done 31 | -------------------------------------------------------------------------------- /clusters/appmesh/infrastructure.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 2 | kind: Kustomization 3 | metadata: 4 | name: cluster-addons 5 | namespace: flux-system 6 | spec: 7 | interval: 30m 8 | timeout: 5m 9 | sourceRef: 10 | kind: GitRepository 11 | name: flux-system 12 | path: ./infrastructure/cluster-addons 13 | prune: true 14 | validation: client 15 | healthChecks: 16 | - apiVersion: v1 17 | kind: Service 18 | name: appmesh-controller-webhook-service 19 | namespace: appmesh-system 20 | - apiVersion: apps/v1 21 | kind: Deployment 22 | name: appmesh-controller 23 | namespace: appmesh-system 24 | --- 25 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 26 | kind: Kustomization 27 | metadata: 28 | name: mesh 29 | namespace: flux-system 30 | spec: 31 | interval: 1m 32 | dependsOn: 33 | - name: cluster-addons 34 | sourceRef: 35 | kind: GitRepository 36 | name: flux-system 37 | path: ./infrastructure/mesh 38 | prune: true 39 | validation: client 40 | --- 41 | apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 42 | kind: Kustomization 43 | metadata: 44 | name: mesh-addons 45 | namespace: flux-system 46 | spec: 47 | interval: 30m 48 | timeout: 1m 49 | dependsOn: 50 | - name: mesh 51 | sourceRef: 52 | kind: GitRepository 53 | name: flux-system 54 | path: ./infrastructure/mesh-addons 55 | prune: true 56 | validation: client 57 | healthChecks: 58 | - apiVersion: v1 59 | kind: Service 60 | name: appmesh-gateway 61 | namespace: appmesh-gateway 62 | - apiVersion: apps/v1 63 | kind: Deployment 64 | name: appmesh-gateway 65 | namespace: appmesh-gateway 66 | -------------------------------------------------------------------------------- /apps/loadtester/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: flagger-loadtester 5 | labels: 6 | app: flagger-loadtester 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: flagger-loadtester 11 | template: 12 | metadata: 13 | labels: 14 | app: flagger-loadtester 15 | annotations: 16 | prometheus.io/scrape: "true" 17 | prometheus.io/port: "8080" 18 | spec: 19 | containers: 20 | - name: loadtester 21 | image: weaveworks/flagger-loadtester:0.18.0 22 | imagePullPolicy: IfNotPresent 23 | ports: 24 | - name: http 25 | containerPort: 8080 26 | command: 27 | - ./loadtester 28 | - -port=8080 29 | - -log-level=info 30 | - -timeout=1h 31 | livenessProbe: 32 | exec: 33 | command: 34 | - wget 35 | - --quiet 36 | - --tries=1 37 | - --timeout=4 38 | - --spider 39 | - http://localhost:8080/healthz 40 | timeoutSeconds: 5 41 | readinessProbe: 42 | exec: 43 | command: 44 | - wget 45 | - --quiet 46 | - --tries=1 47 | - --timeout=4 48 | - --spider 49 | - http://localhost:8080/healthz 50 | timeoutSeconds: 5 51 | resources: 52 | limits: 53 | memory: "512Mi" 54 | cpu: "1000m" 55 | requests: 56 | memory: "32Mi" 57 | cpu: "10m" 58 | securityContext: 59 | readOnlyRootFilesystem: true 60 | runAsUser: 10001 61 | -------------------------------------------------------------------------------- /apps/podinfo/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: podinfo 5 | spec: 6 | minReadySeconds: 3 7 | revisionHistoryLimit: 5 8 | progressDeadlineSeconds: 60 9 | strategy: 10 | rollingUpdate: 11 | maxUnavailable: 0 12 | type: RollingUpdate 13 | selector: 14 | matchLabels: 15 | app: podinfo 16 | template: 17 | metadata: 18 | annotations: 19 | prometheus.io/scrape: "true" 20 | prometheus.io/port: "9797" 21 | labels: 22 | app: podinfo 23 | spec: 24 | containers: 25 | - name: podinfod 26 | image: ghcr.io/stefanprodan/podinfo:5.0.0 27 | imagePullPolicy: IfNotPresent 28 | ports: 29 | - name: http 30 | containerPort: 9898 31 | protocol: TCP 32 | - name: http-metrics 33 | containerPort: 9797 34 | protocol: TCP 35 | - name: grpc 36 | containerPort: 9999 37 | protocol: TCP 38 | command: 39 | - ./podinfo 40 | - --port=9898 41 | - --port-metrics=9797 42 | - --grpc-port=9999 43 | - --grpc-service-name=podinfo 44 | - --level=info 45 | - --random-delay=false 46 | - --random-error=false 47 | env: 48 | - name: PODINFO_UI_COLOR 49 | value: "#34577c" 50 | livenessProbe: 51 | exec: 52 | command: 53 | - podcli 54 | - check 55 | - http 56 | - localhost:9898/healthz 57 | initialDelaySeconds: 5 58 | timeoutSeconds: 5 59 | readinessProbe: 60 | exec: 61 | command: 62 | - podcli 63 | - check 64 | - http 65 | - localhost:9898/readyz 66 | initialDelaySeconds: 5 67 | timeoutSeconds: 5 68 | resources: 69 | limits: 70 | cpu: 2000m 71 | memory: 512Mi 72 | requests: 73 | cpu: 100m 74 | memory: 64Mi 75 | -------------------------------------------------------------------------------- /apps/podinfo/canary.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: flagger.app/v1beta1 2 | kind: Canary 3 | metadata: 4 | name: podinfo 5 | spec: 6 | # App Mesh API reference 7 | provider: appmesh:v1beta2 8 | # deployment reference 9 | targetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: podinfo 13 | # HPA reference (optional) 14 | autoscalerRef: 15 | apiVersion: autoscaling/v2beta2 16 | kind: HorizontalPodAutoscaler 17 | name: podinfo 18 | # the maximum time in seconds for the canary deployment 19 | # to make progress before it is rollback (default 600s) 20 | progressDeadlineSeconds: 60 21 | service: 22 | # container port 23 | port: 9898 24 | # App Mesh ingress timeout (optional) 25 | timeout: 15s 26 | # App Mesh retry policy (optional) 27 | retries: 28 | attempts: 3 29 | perTryTimeout: 5s 30 | retryOn: "gateway-error,client-error,stream-error" 31 | # App Mesh URI settings 32 | match: 33 | - uri: 34 | prefix: / 35 | rewrite: 36 | uri: / 37 | # define the canary analysis timing and KPIs 38 | analysis: 39 | # schedule interval (default 60s) 40 | interval: 15s 41 | # max number of failed metric checks before rollback 42 | threshold: 5 43 | # max traffic percentage routed to canary 44 | # percentage (0-100) 45 | maxWeight: 30 46 | # canary increment step 47 | # percentage (0-100) 48 | stepWeight: 5 49 | # App Mesh Prometheus checks 50 | metrics: 51 | - name: request-success-rate 52 | # minimum req success rate (non 5xx responses) 53 | # percentage (0-100) 54 | thresholdRange: 55 | min: 99 56 | interval: 1m 57 | - name: request-duration 58 | # maximum req duration P99 59 | # milliseconds 60 | thresholdRange: 61 | max: 500 62 | interval: 30s 63 | # testing (optional) 64 | webhooks: 65 | - name: acceptance-test 66 | type: pre-rollout 67 | url: http://flagger-loadtester.$(NAMESPACE)/ 68 | timeout: 30s 69 | metadata: 70 | type: bash 71 | cmd: "curl -sd 'test' http://podinfo-canary.$(NAMESPACE):9898/token | grep token" 72 | - name: load-test 73 | url: http://flagger-loadtester.$(NAMESPACE)/ 74 | timeout: 5s 75 | metadata: 76 | cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.$(NAMESPACE):9898/" 77 | -------------------------------------------------------------------------------- /apps/podinfo/abtest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: flagger.app/v1beta1 2 | kind: Canary 3 | metadata: 4 | name: podinfo 5 | spec: 6 | # App Mesh API reference 7 | provider: appmesh:v1beta2 8 | # deployment reference 9 | targetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: podinfo 13 | # HPA reference (optional) 14 | autoscalerRef: 15 | apiVersion: autoscaling/v2beta2 16 | kind: HorizontalPodAutoscaler 17 | name: podinfo 18 | # the maximum time in seconds for the canary deployment 19 | # to make progress before it is rollback (default 600s) 20 | progressDeadlineSeconds: 60 21 | service: 22 | # container port 23 | port: 9898 24 | # App Mesh ingress timeout (optional) 25 | timeout: 15s 26 | # App Mesh retry policy (optional) 27 | retries: 28 | attempts: 3 29 | perTryTimeout: 5s 30 | retryOn: "gateway-error,client-error,stream-error" 31 | # App Mesh URI settings 32 | match: 33 | - uri: 34 | prefix: / 35 | rewrite: 36 | uri: / 37 | # define the canary analysis timing and KPIs 38 | analysis: 39 | # schedule interval (default 60s) 40 | interval: 15s 41 | # max number of failed metric checks before rollback 42 | threshold: 5 43 | # A/B test iterations 44 | iterations: 10 45 | # User segmentation 46 | match: 47 | - headers: 48 | user-agent: 49 | regex: ".*(Firefox|curl).*" 50 | # App Mesh Prometheus checks 51 | metrics: 52 | - name: request-success-rate 53 | # minimum req success rate (non 5xx responses) 54 | # percentage (0-100) 55 | thresholdRange: 56 | min: 99 57 | interval: 1m 58 | - name: request-duration 59 | # maximum req duration P99 60 | # milliseconds 61 | thresholdRange: 62 | max: 500 63 | interval: 30s 64 | # testing (optional) 65 | webhooks: 66 | - name: acceptance-test 67 | type: pre-rollout 68 | url: http://flagger-loadtester.$(NAMESPACE)/ 69 | timeout: 30s 70 | metadata: 71 | type: bash 72 | cmd: "curl -sd 'test' http://podinfo-canary.$(NAMESPACE):9898/token | grep token" 73 | - name: load-test 74 | url: http://flagger-loadtester.$(NAMESPACE)/ 75 | timeout: 5s 76 | metadata: 77 | cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.$(NAMESPACE):9898/" 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitops-appmesh 2 | 3 | Welcome to the EKS Progressive Delivery hands-on featuring Flux v2, Flagger and AWS App Mesh. 4 | 5 | ![](docs/img/gitops-appmesh.png) 6 | 7 | ## Prerequisites 8 | 9 | Install [eksctl](https://eksctl.io/), [yq](https://mikefarah.gitbook.io/yq/) 10 | and the [Flux](https://github.com/fluxcd/flux2) CLI: 11 | 12 | ```sh 13 | brew install eksctl yq fluxcd/tap/flux 14 | ``` 15 | 16 | In order to follow the guide you'll need a GitHub account and a 17 | [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) 18 | that can create repositories (check all permissions under `repo`). 19 | 20 | Fork this repository on your personal GitHub account and export your access token, username and repo: 21 | 22 | ```sh 23 | export GITHUB_TOKEN= 24 | export GITHUB_USER= 25 | export GITHUB_REPO=gitops-appmesh 26 | ``` 27 | 28 | Clone the repository on your local machine: 29 | 30 | ```sh 31 | git clone https://github.com/${GITHUB_USER}/${GITHUB_REPO}.git 32 | cd ${GITHUB_REPO} 33 | ``` 34 | 35 | ## Cluster bootstrap 36 | 37 | Create a cluster with eksctl: 38 | 39 | ```sh 40 | eksctl create cluster -f .eksctl/config.yaml 41 | ``` 42 | 43 | The above command with create a Kubernetes cluster v1.18 with two `m5.large` nodes in the us-west-2 region. 44 | 45 | Verify that your EKS cluster satisfies the prerequisites with: 46 | 47 | ```console 48 | $ flux check --pre 49 | ► checking prerequisites 50 | ✔ kubectl 1.19.4 >=1.18.0 51 | ✔ Kubernetes 1.18.9-eks-d1db3c >=1.16.0 52 | ✔ prerequisites checks passed 53 | ``` 54 | 55 | Install Flux on your cluster with: 56 | 57 | ```sh 58 | flux bootstrap github \ 59 | --owner=${GITHUB_USER} \ 60 | --repository=${GITHUB_REPO} \ 61 | --branch=main \ 62 | --personal \ 63 | --path=clusters/appmesh 64 | ``` 65 | 66 | The bootstrap command commits the manifests for the Flux components in `clusters/appmesh/flux-system` dir 67 | and creates a deploy key with read-only access on GitHub, so it can pull changes inside the cluster. 68 | 69 | Wait for the cluster reconciliation to finish: 70 | 71 | ```console 72 | $ watch flux get kustomizations 73 | NAME REVISION READY 74 | apps main/582872832315ffca8cf24232b0f6bcb942131a1f True 75 | cluster-addons main/582872832315ffca8cf24232b0f6bcb942131a1f True 76 | flux-system main/582872832315ffca8cf24232b0f6bcb942131a1f True 77 | mesh main/582872832315ffca8cf24232b0f6bcb942131a1f True 78 | mesh-addons main/582872832315ffca8cf24232b0f6bcb942131a1f True 79 | ``` 80 | 81 | Verify that Flagger, Prometheus, AppMesh controller and gateway Helm releases have been installed: 82 | 83 | ```console 84 | $ flux get helmreleases --all-namespaces 85 | NAMESPACE NAME REVISION READY 86 | appmesh-gateway appmesh-gateway 0.1.5 True 87 | appmesh-system appmesh-controller 1.2.0 True 88 | appmesh-system appmesh-prometheus 1.0.0 True 89 | appmesh-system flagger 1.2.0 True 90 | kube-system metrics-server 5.0.1 True 91 | ``` 92 | 93 | ## Application bootstrap 94 | 95 | To experiment with progressive delivery, you'll be using a small Go application called 96 | [podinfo](https://github.com/stefanprodan/podinfo). 97 | The demo app is exposed outside the cluster with AppMesh Gateway. 98 | The communication between the gateway and podinfo is managed by Flagger and AppMesh. 99 | 100 | The application manifests are comprised of a Kubernetes deployment, a horizontal pod autoscaler, 101 | a gateway route (AppMesh custom resource) and release polices (Flagger custom resources). 102 | 103 | ``` 104 | ./apps/podinfo/ 105 | ├── abtest.yaml 106 | ├── canary.yaml 107 | ├── deployment.yaml 108 | ├── gateway-route.yaml 109 | ├── hpa.yaml 110 | └── kustomization.yaml 111 | ``` 112 | 113 | Based on the release policy, Flagger configures the mesh and bootstraps the application inside the cluster. 114 | 115 | Wait for Flagger to initialize the canary: 116 | 117 | ```console 118 | $ watch kubectl -n apps get canary 119 | NAME STATUS WEIGHT LASTTRANSITIONTIME 120 | podinfo Initialized 0 2020-11-14T12:03:39Z 121 | ``` 122 | 123 | Find the AppMesh Gateway public address with: 124 | 125 | ```sh 126 | export URL="http://$(kubectl -n appmesh-gateway get svc/appmesh-gateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')" 127 | echo $URL 128 | ``` 129 | 130 | Wait for the DNS to propagate and podinfo to become accessible: 131 | 132 | ```console 133 | $ watch curl -s ${URL} 134 | { 135 | "hostname": "podinfo-primary-5cf44b9799-lgq79", 136 | "version": "5.0.0" 137 | } 138 | ``` 139 | 140 | When the URL becomes available, open it in a browser and you'll see the podinfo UI. 141 | 142 | ![](docs/img/podinfo-500.png) 143 | 144 | ## Automated canary promotion 145 | 146 | When you deploy a new podinfo version, Flagger gradually shifts traffic to the canary, 147 | and at the same time, measures the requests success rate as well as the average response duration. 148 | Based on an analysis of these App Mesh provided metrics, a canary deployment is either promoted or rolled back. 149 | 150 | ![](docs/img/gitops-appmesh-stack.png) 151 | 152 | The canary analysis is defined in [apps/podinfo/canary.yaml](apps/podinfo/canary.yaml): 153 | 154 | ```yaml 155 | analysis: 156 | # max traffic percentage routed to canary 157 | maxWeight: 50 158 | # canary increment step 159 | stepWeight: 5 160 | # time to wait between traffic increments 161 | interval: 15s 162 | # max number of failed metric checks before rollback 163 | threshold: 5 164 | # AppMesh Prometheus checks 165 | metrics: 166 | - name: request-success-rate 167 | # minimum req success rate percentage (non 5xx) 168 | thresholdRange: 169 | min: 99 170 | interval: 1m 171 | - name: request-duration 172 | # maximum req duration in milliseconds 173 | thresholdRange: 174 | max: 500 175 | interval: 1m 176 | ``` 177 | 178 | Pull the changes from GitHub: 179 | 180 | ```sh 181 | git pull origin main 182 | ``` 183 | 184 | Bump podinfo version from `5.0.0` to `5.0.1`: 185 | 186 | ```sh 187 | yq e '.images[0].newTag="5.0.1"' -i ./apps/podinfo/kustomization.yaml 188 | ``` 189 | 190 | Commit and push changes: 191 | 192 | ```sh 193 | git add -A && \ 194 | git commit -m "podinfo 5.0.1" && \ 195 | git push origin main 196 | ``` 197 | 198 | Tell Flux to pull the changes or wait one minute for Flux to detect the changes: 199 | 200 | ```sh 201 | flux reconcile source git flux-system 202 | ``` 203 | 204 | Wait for the cluster reconciliation to finish: 205 | 206 | ```sh 207 | watch flux get kustomizations 208 | ``` 209 | 210 | When Flagger detects that the deployment revision changed, it will start a new rollout. 211 | You can monitor the traffic shifting with: 212 | 213 | ```sh 214 | watch kubectl -n apps get canary 215 | ``` 216 | 217 | Watch Flagger logs: 218 | 219 | ```console 220 | $ kubectl -n appmesh-system logs deployment/flagger -f | jq .msg 221 | New revision detected! Scaling up podinfo.apps 222 | Starting canary analysis for podinfo.apps 223 | Pre-rollout check acceptance-test passed 224 | Advance podinfo.apps canary weight 5 225 | Advance podinfo.apps canary weight 10 226 | Advance podinfo.apps canary weight 15 227 | Advance podinfo.apps canary weight 20 228 | Advance podinfo.apps canary weight 25 229 | Advance podinfo.apps canary weight 30 230 | Advance podinfo.apps canary weight 35 231 | Advance podinfo.apps canary weight 40 232 | Advance podinfo.apps canary weight 45 233 | Advance podinfo.apps canary weight 50 234 | Copying podinfo.apps template spec to podinfo-primary.apps 235 | Routing all traffic to primary 236 | Promotion completed! Scaling down podinfo.apps 237 | ``` 238 | 239 | Lastly, open up podinfo in the browser. You'll see that as Flagger shifts more traffic 240 | to the canary according to the policy in the Canary object, 241 | we see requests going to our new version of the app. 242 | 243 | ## A/B testing 244 | 245 | Besides weighted routing, Flagger can be configured to route traffic to the canary based on HTTP match conditions. 246 | In an A/B testing scenario, you'll be using HTTP headers or cookies to target a certain segment of your users. 247 | This is particularly useful for frontend applications that require session affinity. 248 | 249 | Enable A/B testing: 250 | 251 | ```sh 252 | yq e '.resources[0]="abtest.yaml"' -i ./apps/podinfo/kustomization.yaml 253 | ``` 254 | 255 | The above configuration will run a canary analysis targeting users based on their browser user-agent. 256 | 257 | The A/B test routing is defined in [apps/podinfo/abtest.yaml](apps/podinfo/abtest.yaml): 258 | 259 | ```yaml 260 | analysis: 261 | # number of iterations 262 | iterations: 10 263 | # time to wait between iterations 264 | interval: 15s 265 | # max number of failed metric checks before rollback 266 | threshold: 5 267 | # user segmentation 268 | match: 269 | - headers: 270 | user-agent: 271 | regex: ".*(Firefox|curl).*" 272 | ``` 273 | 274 | Bump podinfo version to `5.0.2`: 275 | 276 | ```sh 277 | yq e '.images[0].newTag="5.0.2"' -i ./apps/podinfo/kustomization.yaml 278 | ``` 279 | 280 | Commit and push changes: 281 | 282 | ```sh 283 | git add -A && \ 284 | git commit -m "podinfo 5.0.2" && \ 285 | git push origin main 286 | ``` 287 | 288 | Tell Flux to pull changes: 289 | 290 | ```sh 291 | flux reconcile source git flux-system 292 | ``` 293 | 294 | Wait for Flagger to start the A/B test: 295 | 296 | ```console 297 | $ kubectl -n appmesh-system logs deploy/flagger -f | jq .msg 298 | New revision detected! Scaling up podinfo.apps 299 | Starting canary analysis for podinfo.apps 300 | Pre-rollout check acceptance-test passed 301 | Advance podinfo.apps canary iteration 1/10 302 | ``` 303 | 304 | Open the podinfo URL in Firefox and you will be routed to version `5.0.2` or use curl: 305 | 306 | ```console 307 | $ curl ${URL} 308 | { 309 | "hostname": "podinfo-6cf9c5fd49-9fzbt", 310 | "version": "5.0.2" 311 | } 312 | ``` 313 | 314 | ## Automated rollback 315 | 316 | During the canary analysis you can generate HTTP 500 errors and high latency 317 | to test if Flagger pauses and rolls back the faulted version. 318 | 319 | Generate HTTP 500 errors every 30s with curl: 320 | 321 | ```sh 322 | watch -n 0.5 curl ${URL}/status/500 323 | ``` 324 | 325 | When the number of failed checks reaches the canary analysis threshold, 326 | the traffic is routed back to the primary and the canary is scaled to zero. 327 | 328 | ```console 329 | $ kubectl -n appmesh-system logs deploy/flagger -f | jq .msg 330 | Advance podinfo.apps canary iteration 2/10 331 | Halt podinfo.apps advancement success rate 98.82% < 99% 332 | Halt podinfo.apps advancement success rate 97.93% < 99% 333 | Halt podinfo.apps advancement success rate 97.51% < 99% 334 | Halt podinfo.apps advancement success rate 98.08% < 99% 335 | Halt podinfo.apps advancement success rate 96.88% < 99% 336 | Rolling back podinfo.apps failed checks threshold reached 5 337 | Canary failed! Scaling down podinfo.apps 338 | ``` 339 | 340 | If you go back to Firefox, you'll see that the podinfo version has been rollback to `5.0.1`. 341 | Note that on Chrome or Safari, users haven't been affected by the faulty version, 342 | as they were not routed to `5.0.2` during the analysis. 343 | 344 | ## Cleanup 345 | 346 | Suspend the cluster reconciliation: 347 | 348 | ```sh 349 | flux suspend kustomization cluster-addons 350 | ``` 351 | 352 | Delete the demo app and mesh addons: 353 | 354 | ```sh 355 | flux delete kustomization apps -s 356 | flux delete kustomization mesh-addons -s 357 | ``` 358 | 359 | Delete the AppMesh mesh: 360 | 361 | ```sh 362 | kubectl delete mesh --all 363 | ``` 364 | 365 | Delete the EKS cluster: 366 | 367 | ```sh 368 | eksctl delete cluster -f .eksctl/config.yaml 369 | ``` 370 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------