├── workshop ├── apps │ ├── catalog_detail │ │ ├── readiness.txt │ │ ├── .dockerignore │ │ ├── package.json │ │ ├── Dockerfile │ │ └── app.js │ ├── sku │ │ ├── helm-chart │ │ │ ├── templates │ │ │ │ ├── NOTES.txt │ │ │ │ ├── namespace.yaml │ │ │ │ ├── service.yaml │ │ │ │ ├── tests │ │ │ │ │ └── test-connection.yaml │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── ingress.yaml │ │ │ │ └── deployment.yaml │ │ │ ├── Chart.yaml │ │ │ ├── .helmignore │ │ │ └── values.yaml │ │ └── app │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── Dockerfile │ │ │ ├── app.js │ │ │ ├── views │ │ │ └── index.ejs │ │ │ └── public │ │ │ └── css │ │ │ └── styles.css │ ├── frontend_node │ │ ├── .dockerignore │ │ ├── public │ │ │ ├── arch.png │ │ │ ├── architecture.png │ │ │ └── css │ │ │ │ └── styles.css │ │ ├── index.html │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── views │ │ │ └── index.ejs │ │ └── server.js │ └── product_catalog │ │ ├── .dockerignore │ │ ├── bootstrap.sh │ │ ├── requirements.txt │ │ ├── Dockerfile │ │ ├── app.py │ │ ├── app_efs.py │ │ ├── app_secrets.py │ │ ├── app_ebs.py │ │ └── app_aurora.py ├── helm-chart │ ├── values-ebs.yaml │ ├── Chart.yaml │ ├── productcatalog_workshop-1.0.0.tgz │ ├── values-efs.yaml │ ├── security │ │ ├── values-psa-pss-priv.yaml │ │ ├── values-psa-pss.yaml │ │ ├── values-psa-pss-baseline.yaml │ │ ├── values-psa-pss-baseline-ns.yaml │ │ ├── values-psa-pss-restricted-ns.yaml │ │ └── values-psa-pss-restricted.yaml │ ├── templates │ │ ├── namespace.yaml │ │ ├── detail_service.yaml │ │ ├── catalog_service.yaml │ │ ├── frontend_service.yaml │ │ ├── tests │ │ │ └── test-connection.yaml │ │ ├── _helpers.tpl │ │ ├── ingress.yaml │ │ ├── NOTES.txt │ │ ├── detail_deployment.yaml │ │ ├── frontend_deployment.yaml │ │ └── catalog_deployment.yaml │ ├── .helmignore │ ├── values-k8s-secret.yaml │ ├── values-secrets-manager.yaml │ ├── values.yaml │ └── values-aurora.yaml ├── images │ ├── lbui.png │ ├── workshopui.png │ ├── addproducts.png │ └── lbfrontend-2.png ├── productcatalog_workshop-1.0.0.tgz ├── script │ └── build.sh ├── efs-pvc.yaml ├── collector-config-opentelemetry.yaml ├── xray-eks.yaml ├── cloudformation │ └── alb_deployment.yaml ├── spinnaker │ ├── spinnakerservice.yml │ └── installspinnaker.sh ├── aws_lbc_iam_policy.json ├── mysql-statefulset.yaml ├── mysql-statefulset-with-secret.yaml ├── cluster-autoscaler.yaml └── otel-collector-config.yaml ├── apps ├── catalog_detail │ ├── .dockerignore │ ├── proddetail-0.1.0.tgz │ ├── package.json │ ├── version2 │ │ └── Dockerfile │ ├── Dockerfile │ ├── app.js │ ├── app2.js │ └── deployment.yaml ├── frontend_node │ ├── .dockerignore │ ├── public │ │ ├── architecture.png │ │ └── css │ │ │ └── styles.css │ ├── index.html │ ├── Dockerfile │ ├── package.json │ ├── server.js │ └── views │ │ └── index.ejs └── product_catalog │ ├── .dockerignore │ ├── bootstrap.sh │ ├── requirements.txt │ ├── Dockerfile │ └── app.py ├── images └── lbfrontend-2.png ├── deployment ├── clusterconfig.yaml ├── mesh.yaml ├── fluentbit-config.yaml ├── envoy-iam-policy.json ├── virtual_gateway.yaml ├── canary.yaml ├── meshed_app.yaml └── base_app.yaml ├── .gitignore ├── LICENSE └── README.md /workshop/apps/catalog_detail/readiness.txt: -------------------------------------------------------------------------------- 1 | ready -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/catalog_detail/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /apps/frontend_node/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /apps/product_catalog/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /workshop/helm-chart/values-ebs.yaml: -------------------------------------------------------------------------------- 1 | catalog: 2 | image: 3 | tag: "5.3" -------------------------------------------------------------------------------- /workshop/apps/catalog_detail/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /workshop/apps/frontend_node/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /workshop/apps/product_catalog/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | npm-debug.log -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/templates/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: sku -------------------------------------------------------------------------------- /apps/product_catalog/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export FLASK_APP=./app.py 3 | export FLASK_DEBUG=1 4 | flask run -h 0.0.0.0 -------------------------------------------------------------------------------- /images/lbfrontend-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/images/lbfrontend-2.png -------------------------------------------------------------------------------- /workshop/images/lbui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/images/lbui.png -------------------------------------------------------------------------------- /workshop/apps/product_catalog/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export FLASK_APP=./app.py 3 | export FLASK_DEBUG=1 4 | flask run -h 0.0.0.0 -------------------------------------------------------------------------------- /workshop/images/workshopui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/images/workshopui.png -------------------------------------------------------------------------------- /workshop/images/addproducts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/images/addproducts.png -------------------------------------------------------------------------------- /workshop/images/lbfrontend-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/images/lbfrontend-2.png -------------------------------------------------------------------------------- /apps/catalog_detail/proddetail-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/apps/catalog_detail/proddetail-0.1.0.tgz -------------------------------------------------------------------------------- /apps/frontend_node/public/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/apps/frontend_node/public/architecture.png -------------------------------------------------------------------------------- /workshop/apps/frontend_node/public/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/apps/frontend_node/public/arch.png -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: Helm Chart for SKU application 4 | name: sku 5 | version: 1.0.0 6 | -------------------------------------------------------------------------------- /workshop/productcatalog_workshop-1.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/productcatalog_workshop-1.0.0.tgz -------------------------------------------------------------------------------- /workshop/apps/frontend_node/public/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/apps/frontend_node/public/architecture.png -------------------------------------------------------------------------------- /workshop/helm-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.0" 3 | description: Helm Chart for Product Catalog Workshop 4 | name: productcatalog_workshop 5 | version: 1.0.0 6 | -------------------------------------------------------------------------------- /workshop/helm-chart/productcatalog_workshop-1.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-containers/eks-app-mesh-polyglot-demo/HEAD/workshop/helm-chart/productcatalog_workshop-1.0.0.tgz -------------------------------------------------------------------------------- /workshop/helm-chart/values-efs.yaml: -------------------------------------------------------------------------------- 1 | catalog: 2 | volume: 3 | enabled: true 4 | name: "efs-pvc" 5 | path: "/products" 6 | claim: "efs-storage-claim" 7 | 8 | image: 9 | tag: "3.6" -------------------------------------------------------------------------------- /workshop/helm-chart/security/values-psa-pss-priv.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm-chart. 2 | frontend: 3 | security: 4 | enabled: true 5 | securityContext: 6 | privileged: true 7 | runAsUser: 0 8 | -------------------------------------------------------------------------------- /workshop/helm-chart/security/values-psa-pss.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm-chart. 2 | frontend: 3 | security: 4 | enabled: true 5 | securityContext: 6 | allowPrivilegeEscalation: false 7 | runAsUser: 1000 8 | -------------------------------------------------------------------------------- /workshop/apps/sku/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sku Catalog 6 | 7 | 8 |

SKU Catalog

9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /workshop/helm-chart/templates/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: workshop 5 | {{- if .Values.namespace.security }} 6 | labels: 7 | {{- toYaml .Values.namespace.labels | nindent 4 }} 8 | {{- end }} 9 | -------------------------------------------------------------------------------- /workshop/helm-chart/security/values-psa-pss-baseline.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm-chart. 2 | frontend: 3 | security: 4 | enabled: true 5 | securityContext: 6 | allowPrivilegeEscalation: false 7 | privileged: false 8 | runAsUser: 1000 9 | -------------------------------------------------------------------------------- /workshop/apps/product_catalog/requirements.txt: -------------------------------------------------------------------------------- 1 | flask-restplus==0.12.1 2 | flask==1.1.4 3 | markupsafe==2.0.1 4 | werkzeug==1.0.1 5 | gunicorn==20.0.4 6 | requests==v2.24.0 7 | aws-xray-sdk==2.6.0 8 | flask-cors==3.0.10 9 | PyMySQL==1.0.2 10 | boto3==1.9.220 11 | -------------------------------------------------------------------------------- /workshop/helm-chart/security/values-psa-pss-baseline-ns.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm-chart. 2 | namespace: 3 | security: 4 | enabled: true 5 | labels: 6 | pod-security.kubernetes.io/audit: baseline 7 | pod-security.kubernetes.io/enforce: baseline 8 | pod-security.kubernetes.io/warn: baseline 9 | -------------------------------------------------------------------------------- /workshop/helm-chart/security/values-psa-pss-restricted-ns.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm-chart. 2 | namespace: 3 | security: 4 | enabled: true 5 | labels: 6 | pod-security.kubernetes.io/audit: restricted 7 | pod-security.kubernetes.io/enforce: restricted 8 | pod-security.kubernetes.io/warn: restricted 9 | -------------------------------------------------------------------------------- /apps/product_catalog/requirements.txt: -------------------------------------------------------------------------------- 1 | flask-restplus==0.12.1 2 | Flask==1.1.4 3 | werkzeug==1.0.1 4 | gunicorn==20.0.4 5 | requests==v2.24.0 6 | aws-xray-sdk==2.6.0 7 | flask-cors==3.0.0 8 | boto3==1.9.22 9 | markupsafe==2.0.1 10 | opentelemetry-distro[otlp] 11 | opentelemetry-sdk-extension-aws 12 | opentelemetry-propagator-aws-xray -------------------------------------------------------------------------------- /deployment/clusterconfig.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: eksctl.io/v1alpha5 3 | kind: ClusterConfig 4 | 5 | metadata: 6 | name: eksworkshop-eksctl 7 | region: ${AWS_REGION} 8 | 9 | fargateProfiles: 10 | - name: fargate-productcatalog 11 | selectors: 12 | - namespace: prodcatalog-ns 13 | labels: 14 | app: prodcatalog 15 | -------------------------------------------------------------------------------- /apps/catalog_detail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs_app_2", 3 | "version": "1.0.0", 4 | "description": "catalog detail", 5 | "main": " app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Praseeda Sathaye", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "aws-xray-sdk": "3.2.0", 13 | "express": "^4.17.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /workshop/helm-chart/security/values-psa-pss-restricted.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm-chart. 2 | frontend: 3 | security: 4 | enabled: true 5 | securityContext: 6 | allowPrivilegeEscalation: false 7 | capabilities: 8 | drop: 9 | - ALL 10 | readOnlyRootFilesystem: false 11 | runAsNonRoot: true 12 | runAsUser: 1000 13 | seccompProfile: 14 | type: RuntimeDefault 15 | -------------------------------------------------------------------------------- /workshop/apps/sku/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs_app_2", 3 | "version": "1.0.0", 4 | "description": "sku detail", 5 | "main": " app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Ratnopam Chakrabarti", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "aws-xray-sdk": "3.2.0", 13 | "express": "^4.17.1", 14 | "ejs": "^3.1.5" 15 | } 16 | } -------------------------------------------------------------------------------- /workshop/helm-chart/templates/detail_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ .Values.detail.name }} 5 | namespace: {{ .Values.detail.namespace }} 6 | labels: 7 | app: {{ .Values.detail.name }} 8 | spec: 9 | type: {{ .Values.detail.service.type }} 10 | ports: 11 | - port: {{ .Values.detail.service.targetPort }} 12 | name: http 13 | selector: 14 | app: {{ .Values.detail.name }} -------------------------------------------------------------------------------- /deployment/mesh.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: prodcatalog-ns 6 | labels: 7 | mesh: prodcatalog-mesh 8 | gateway: ingress-gw 9 | appmesh.k8s.aws/sidecarInjectorWebhook: enabled 10 | --- 11 | apiVersion: appmesh.k8s.aws/v1beta2 12 | kind: Mesh 13 | metadata: 14 | name: prodcatalog-mesh 15 | spec: 16 | namespaceSelector: 17 | matchLabels: 18 | mesh: prodcatalog-mesh 19 | --- -------------------------------------------------------------------------------- /workshop/helm-chart/templates/catalog_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ .Values.catalog.name }} 5 | namespace: {{ .Values.catalog.namespace }} 6 | labels: 7 | app: {{ .Values.catalog.name }} 8 | spec: 9 | type: {{ .Values.catalog.service.type }} 10 | ports: 11 | - port: {{ .Values.catalog.service.targetPort }} 12 | name: http 13 | selector: 14 | app: {{ .Values.catalog.name }} -------------------------------------------------------------------------------- /workshop/apps/catalog_detail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs_app_2", 3 | "version": "1.0.0", 4 | "description": "catalog detail", 5 | "main": " app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Praseeda Sathaye", 10 | "license": "MIT-0", 11 | "dependencies": { 12 | "aws-xray-sdk": "3.2.0", 13 | "aws-sdk": "^2.649.0", 14 | "express": "^4.17.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/frontend_node/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Product Catalog 6 | 7 | 8 |

Product Catalog

9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /workshop/apps/frontend_node/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Product Catalog 6 | 7 | 8 |

Product Catalog

9 | 10 |
11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /workshop/helm-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /workshop/helm-chart/values-k8s-secret.yaml: -------------------------------------------------------------------------------- 1 | catalog: 2 | image: 3 | tag: "10.0" 4 | 5 | env: 6 | - name: AGG_APP_URL 7 | value: "http://proddetail.workshop:3000/catalogDetail" 8 | - name: AWS_XRAY_DAEMON_ADDRESS 9 | value: xray-service.default:2000 10 | - name: DATABASE_SERVICE_URL 11 | value: mysql.workshop 12 | - name: MYSQL_ROOT_PASSWORD 13 | valueFrom: 14 | secretKeyRef: 15 | name: mysql-secret 16 | key: password 17 | -------------------------------------------------------------------------------- /apps/catalog_detail/version2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | 10 | COPY package.json package-lock.json ./ 11 | 12 | # RUN npm install 13 | # If you are building your code for production 14 | RUN npm ci --only=production 15 | 16 | # Bundle app source 17 | COPY . . 18 | 19 | EXPOSE 3000 20 | CMD [ "node", "app2.js" ] -------------------------------------------------------------------------------- /deployment/fluentbit-config.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: aws-observability 5 | labels: 6 | aws-observability: enabled 7 | --- 8 | kind: ConfigMap 9 | apiVersion: v1 10 | metadata: 11 | name: aws-logging 12 | namespace: aws-observability 13 | data: 14 | output.conf: | 15 | [OUTPUT] 16 | Name cloudwatch_logs 17 | Match * 18 | region ${AWS_REGION} 19 | log_group_name fluent-bit-cloudwatch 20 | log_stream_prefix from-fluent-bit- 21 | auto_create_group true -------------------------------------------------------------------------------- /workshop/script/build.sh: -------------------------------------------------------------------------------- 1 | cd workshop 2 | #aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 3 | aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/u2g6w7p2 4 | PROJECT_NAME=eks-workshop-demo 5 | export APP_VERSION=1.0 6 | for app in catalog_detail product_catalog frontend_node; do 7 | TARGET=public.ecr.aws/u2g6w7p2/$PROJECT_NAME/$app:$APP_VERSION 8 | docker build -t $TARGET apps/$app 9 | docker push $TARGET 10 | done -------------------------------------------------------------------------------- /apps/catalog_detail/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | FROM node:14 4 | 5 | # Create app directory 6 | WORKDIR /usr/src/app 7 | 8 | # Install app dependencies 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 10 | # where available (npm@5+) 11 | COPY package.json package-lock.json ./ 12 | 13 | # RUN npm install 14 | # If you are building your code for production 15 | RUN npm ci --only=production 16 | 17 | # Bundle app source 18 | COPY . . 19 | 20 | EXPOSE 3000 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /apps/frontend_node/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | FROM node:14 4 | 5 | # Create app directory 6 | WORKDIR /usr/src/app 7 | 8 | # Install app dependencies 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 10 | # where available (npm@5+) 11 | COPY package.json package-lock.json ./ 12 | 13 | # RUN npm install 14 | # If you are building your code for production 15 | RUN npm ci --only=production 16 | 17 | # Bundle app source 18 | COPY . . 19 | 20 | EXPOSE 9000 21 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /workshop/apps/sku/app/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | FROM node:14 4 | 5 | # Create app directory 6 | WORKDIR /usr/src/app 7 | 8 | # Install app dependencies 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 10 | # where available (npm@5+) 11 | COPY package.json package-lock.json ./ 12 | 13 | # RUN npm install 14 | # If you are building your code for production 15 | RUN npm ci --only=production 16 | 17 | # Bundle app source 18 | COPY . . 19 | 20 | EXPOSE 3000 21 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /workshop/apps/frontend_node/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | FROM node:14 4 | 5 | # Create app directory 6 | WORKDIR /usr/src/app 7 | 8 | # Install app dependencies 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 10 | # where available (npm@5+) 11 | COPY package.json package-lock.json ./ 12 | 13 | RUN npm install 14 | # If you are building your code for production 15 | # RUN npm ci --only=production 16 | 17 | # Bundle app source 18 | COPY . . 19 | 20 | EXPOSE 9000 21 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ .Values.sku.name }} 5 | namespace: {{ .Values.sku.namespace }} 6 | labels: 7 | app: {{ .Values.sku.name }} 8 | {{- with .Values.sku.service.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | spec: 13 | type: {{ .Values.sku.service.type }} 14 | ports: 15 | - port: {{ .Values.sku.service.port }} 16 | name: {{ .Values.sku.service.name }} 17 | targetPort: {{ .Values.sku.service.targetPort }} 18 | selector: 19 | app: {{ .Values.sku.name }} -------------------------------------------------------------------------------- /workshop/apps/catalog_detail/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | FROM node:14 4 | 5 | # Create app directory 6 | WORKDIR /usr/src/app 7 | 8 | # Install app dependencies 9 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 10 | # where available (npm@5+) 11 | COPY readiness.txt package.json package-lock.json ./ 12 | 13 | 14 | RUN npm install 15 | # If you are building your code for production 16 | # RUN npm ci --only=production 17 | 18 | # Bundle app source 19 | COPY . . 20 | 21 | EXPOSE 3000 22 | CMD [ "node", "app.js" ] -------------------------------------------------------------------------------- /workshop/helm-chart/templates/frontend_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ .Values.frontend.name }} 5 | annotations: 6 | service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" 7 | namespace: {{ .Values.frontend.namespace }} 8 | labels: 9 | app: {{ .Values.frontend.name }} 10 | spec: 11 | type: {{ .Values.frontend.service.type }} 12 | ports: 13 | - targetPort: {{ .Values.frontend.service.targetPort }} 14 | port: {{ .Values.frontend.service.port }} 15 | name: {{ .Values.frontend.service.name }} 16 | selector: 17 | app: {{ .Values.frontend.name }} 18 | -------------------------------------------------------------------------------- /apps/frontend_node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-node", 3 | "version": "1.0.0", 4 | "description": "front end application for microservices in app mesh", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Praseeda Sathaye", 11 | "license": "MIT-0", 12 | "dependencies": { 13 | "aws-xray-sdk": "3.2.0", 14 | "axios": "^0.21.1", 15 | "body-parser": "^1.19.0", 16 | "ejs": "^3.1.5", 17 | "express": "^4.17.1", 18 | "prom-client": "^12.0.0" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^2.0.7" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /workshop/helm-chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "helm-chart.fullname" . }}-test-connection" 5 | labels: 6 | app.kubernetes.io/name: {{ include "helm-chart.name" . }} 7 | helm.sh/chart: {{ include "helm-chart.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | annotations: 11 | "helm.sh/hook": test-success 12 | spec: 13 | containers: 14 | - name: wget 15 | image: busybox 16 | command: ['wget'] 17 | args: ['{{ include "helm-chart.fullname" . }}:{{ .Values.detail.service.port }}'] 18 | restartPolicy: Never 19 | -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "helm-chart.fullname" . }}-test-connection" 5 | labels: 6 | app.kubernetes.io/name: {{ include "helm-chart.name" . }} 7 | helm.sh/chart: {{ include "helm-chart.chart" . }} 8 | app.kubernetes.io/instance: {{ .Release.Name }} 9 | app.kubernetes.io/managed-by: {{ .Release.Service }} 10 | annotations: 11 | "helm.sh/hook": test-success 12 | spec: 13 | containers: 14 | - name: wget 15 | image: busybox 16 | command: ['wget'] 17 | args: ['{{ include "helm-chart.fullname" . }}:{{ .Values.sku.service.port }}'] 18 | restartPolicy: Never 19 | -------------------------------------------------------------------------------- /workshop/apps/frontend_node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-node", 3 | "version": "1.0.0", 4 | "description": "front end application for microservices in app mesh", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Praseeda Sathaye", 11 | "license": "MIT-0", 12 | "dependencies": { 13 | "aws-xray-sdk": "3.2.0", 14 | "axios": "^0.21.1", 15 | "aws-sdk": "^2.657.0", 16 | "body-parser": "^1.19.0", 17 | "ejs": "^3.1.5", 18 | "express": "^4.17.1", 19 | "prom-client": "^12.0.0" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^2.0.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /workshop/apps/product_catalog/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # Use an official Python runtime as an image 4 | FROM python:3.9-slim 5 | 6 | RUN apt-get update \ 7 | && apt-get install curl -y \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN mkdir /app 11 | WORKDIR /app 12 | 13 | # We copy just the requirements.txt first to leverage Docker cache 14 | COPY requirements.txt /app 15 | RUN pip install -r requirements.txt 16 | 17 | COPY . /app 18 | 19 | # ENV AGG_APP_URL='http://prodinfo.octank-mesh-ns.svc.cluster.local:3000/productAgreement' 20 | 21 | #WORKDIR /docker_app 22 | EXPOSE 8080 23 | ENTRYPOINT ["/app/bootstrap.sh"] -------------------------------------------------------------------------------- /apps/catalog_detail/app.js: -------------------------------------------------------------------------------- 1 | //Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | //SPDX-License-Identifier: MIT-0 3 | 4 | var express = require("express"); 5 | var app = express(); 6 | var AWSXRay = require('aws-xray-sdk'); 7 | 8 | app.use(AWSXRay.express.openSegment('Product-Detail-V1')); 9 | 10 | app.get("/catalogDetail", (req, res, next) => { 11 | console.log("Catalog Detail Version 1 Get Request Successful"); 12 | res.json({ 13 | "version":"1", 14 | "vendors":[ "ABC.com" ] 15 | } ) 16 | }); 17 | 18 | app.get("/ping", (req, res, next) => { 19 | res.json("Healthy") 20 | }); 21 | 22 | app.use(AWSXRay.express.closeSegment()); 23 | 24 | app.listen(3000, () => { 25 | console.log("Server running on port 3000"); 26 | }); -------------------------------------------------------------------------------- /deployment/envoy-iam-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "appmesh:StreamAggregatedResources", 8 | "appmesh:*", 9 | "xray:*" 10 | ], 11 | "Resource": "*" 12 | }, 13 | { 14 | "Effect": "Allow", 15 | "Action": [ 16 | "acm:ExportCertificate", 17 | "acm-pca:GetCertificateAuthorityCertificate" 18 | ], 19 | "Resource": "*" 20 | }, 21 | { 22 | "Action": [ 23 | "logs:*" 24 | ], 25 | "Effect": "Allow", 26 | "Resource": "*" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /apps/catalog_detail/app2.js: -------------------------------------------------------------------------------- 1 | //Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | //SPDX-License-Identifier: MIT-0 3 | 4 | var express = require("express"); 5 | var app = express(); 6 | var AWSXRay = require('aws-xray-sdk'); 7 | 8 | app.use(AWSXRay.express.openSegment('Product-Detail-V2')); 9 | 10 | app.get("/catalogDetail", (req, res, next) => { 11 | console.log("Catalog Detail Version 2 Get Request Successful"); 12 | res.json({ 13 | "version":"2", 14 | "vendors":[ "ABC.com", "XYZ.com" ] 15 | } ) 16 | }); 17 | 18 | app.get("/ping", (req, res, next) => { 19 | res.json("Healthy") 20 | }); 21 | 22 | app.use(AWSXRay.express.closeSegment()); 23 | 24 | app.listen(3000, () => { 25 | console.log("Server running on port 3000"); 26 | }); -------------------------------------------------------------------------------- /workshop/efs-pvc.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1 3 | metadata: 4 | name: efs-sc 5 | provisioner: efs.csi.aws.com 6 | 7 | --- 8 | apiVersion: v1 9 | kind: PersistentVolume 10 | metadata: 11 | name: efs-pvc 12 | spec: 13 | capacity: 14 | storage: 5Gi 15 | volumeMode: Filesystem 16 | accessModes: 17 | - ReadWriteMany 18 | persistentVolumeReclaimPolicy: Retain 19 | storageClassName: efs-sc 20 | csi: 21 | driver: efs.csi.aws.com 22 | volumeHandle: EFS_VOLUME_ID 23 | 24 | --- 25 | apiVersion: v1 26 | kind: PersistentVolumeClaim 27 | metadata: 28 | name: efs-storage-claim 29 | namespace: workshop 30 | spec: 31 | accessModes: 32 | - ReadWriteMany 33 | storageClassName: efs-sc 34 | resources: 35 | requests: 36 | storage: 5Gi -------------------------------------------------------------------------------- /workshop/helm-chart/values-secrets-manager.yaml: -------------------------------------------------------------------------------- 1 | catalog: 2 | serviceAccountName: 3 | enabled: true 4 | name: catalog-deployment-sa 5 | volume: 6 | enabled: true 7 | name: secrets-store-volumne 8 | path: "/mnt/secrets" 9 | csi: 10 | driver: secrets-store.csi.k8s.io 11 | volumeAttributes: 12 | secretProviderClass: catalog-deployment-spc-k8s-secrets 13 | 14 | image: 15 | tag: "10.0" 16 | 17 | env: 18 | - name: AGG_APP_URL 19 | value: "http://proddetail.workshop:3000/catalogDetail" 20 | - name: AWS_XRAY_DAEMON_ADDRESS 21 | value: xray-service.default:2000 22 | - name: DATABASE_SERVICE_URL 23 | value: mysql.workshop 24 | - name: MYSQL_ROOT_PASSWORD 25 | valueFrom: 26 | secretKeyRef: 27 | name: mysql-secret-from-secrets-manager 28 | key: db_password 29 | -------------------------------------------------------------------------------- /workshop/apps/sku/app/app.js: -------------------------------------------------------------------------------- 1 | //Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | //SPDX-License-Identifier: MIT-0 3 | 4 | const express = require("express"); 5 | const app = express(); 6 | 7 | 8 | app.set('view engine', 'ejs') 9 | 10 | app.get("/sku", (req, res) => { 11 | console.log("SKU Detail Get Request Successful"); 12 | res.render('index.ejs', {skus: [{"id": "SKU-BK-2502967", "name": "Road Bike SKU"},{"id": "SKU-BK-2502968", "name": "Mountain Bike SKU"}, {"id": "SKU-BK-2502969", "name": "Dirt Bike SKU"}, {"id": "SKU-BK-2502970", "name": "Racer Bike SKU"}, {"id": "SKU-BK-250271", "name": "Premuim Bike SKU"}, {"id": "SKU-BK-2502972", "name": "Custom Bike SKU"}]}) 13 | 14 | } 15 | ); 16 | 17 | 18 | 19 | app.get("/ping", (req, res, next) => { 20 | res.json("Healthy") 21 | }); 22 | 23 | 24 | app.listen(3000, () => { 25 | console.log("Server running on port 3000"); 26 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | apps/.DS_Store 4 | apps/catalog_detail/.DS_Store 5 | apps/frontend_node/.DS_Store 6 | apps/product_catalog/.DS_Store 7 | apps/frontend_node/.idea/ 8 | apps/frontend_node/public/.DS_Store 9 | deployment/.DS_Store 10 | apps/catalog_detail/node_modules/ 11 | apps/catalog_detail/output.log 12 | apps/frontend_node/node_modules/ 13 | apps/product_catalog/__pycache__/ 14 | workshop/apps/.DS_Store 15 | workshop/apps/catalog_detail/.DS_Store 16 | workshop/apps/frontend_node/.DS_Store 17 | workshop/apps/product_catalog/.DS_Store 18 | workshop/apps/frontend_node/.idea/ 19 | workshop/apps/frontend_node/public/.DS_Store 20 | workshop/deployment/.DS_Store 21 | workshop/apps/catalog_detail/node_modules/ 22 | workshop/apps/catalog_detail/output.log 23 | workshop/apps/frontend_node/node_modules/ 24 | workshop/apps/product_catalog/__pycache__/ 25 | workshop/apps/product_catalog/venv/ 26 | workshop/apps/sku/app/node_modules/ -------------------------------------------------------------------------------- /apps/product_catalog/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # Use an official Python runtime as an image 4 | FROM python:3.9-slim 5 | 6 | RUN apt-get update \ 7 | && apt-get install curl -y \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN mkdir /app 11 | WORKDIR /app 12 | 13 | # We copy just the requirements.txt first to leverage Docker cache 14 | COPY requirements.txt /app 15 | RUN pip install -r requirements.txt 16 | 17 | RUN opentelemetry-bootstrap --action=install 18 | 19 | COPY . /app 20 | 21 | # ENV AGG_APP_URL='http://prodinfo.octank-mesh-ns.svc.cluster.local:3000/productAgreement' 22 | 23 | #WORKDIR /docker_app 24 | EXPOSE 8080 25 | CMD opentelemetry-instrument \ 26 | # --traces_exporter=console \ 27 | --log_level=debug \ 28 | # --logs_exporter=console \ 29 | # --metrics_exporter=none \ 30 | python3 -m flask run -h 0.0.0.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /workshop/helm-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "helm-chart.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "helm-chart.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "helm-chart.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "helm-chart.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "helm-chart.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "helm-chart.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /workshop/helm-chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.detail.ingress.enabled -}} 2 | {{- $fullName := include "helm-chart.fullname" . -}} 3 | apiVersion: extensions/v1beta1 4 | kind: Ingress 5 | metadata: 6 | name: {{ $fullName }} 7 | labels: 8 | app.kubernetes.io/name: {{ include "helm-chart.name" . }} 9 | helm.sh/chart: {{ include "helm-chart.chart" . }} 10 | app.kubernetes.io/instance: {{ .Release.Name }} 11 | app.kubernetes.io/managed-by: {{ .Release.Service }} 12 | {{- with .Values.detail.ingress.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | {{- if .Values.detail.ingress.tls }} 18 | tls: 19 | {{- range .Values.detail.ingress.tls }} 20 | - hosts: 21 | {{- range .hosts }} 22 | - {{ . | quote }} 23 | {{- end }} 24 | secretName: {{ .secretName }} 25 | {{- end }} 26 | {{- end }} 27 | rules: 28 | {{- range .Values.detail.ingress.hosts }} 29 | - host: {{ .host | quote }} 30 | http: 31 | paths: 32 | {{- range .paths }} 33 | - path: {{ . }} 34 | backend: 35 | serviceName: {{ $fullName }} 36 | servicePort: http 37 | {{- end }} 38 | {{- end }} 39 | {{- end }} 40 | -------------------------------------------------------------------------------- /workshop/collector-config-opentelemetry.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: opentelemetry.io/v1alpha1 2 | kind: OpenTelemetryCollector 3 | metadata: 4 | name: my-collector-xray 5 | namespace: prodcatalog-ns 6 | spec: 7 | mode: sidecar 8 | resources: 9 | requests: 10 | cpu: "1" 11 | limits: 12 | cpu: "1" 13 | serviceAccount: adot-collector 14 | image: public.ecr.aws/aws-observability/aws-otel-collector:latest 15 | config: | 16 | extensions: 17 | health_check: 18 | pprof: 19 | endpoint: 0.0.0.0:1777 20 | 21 | receivers: 22 | otlp: 23 | protocols: 24 | grpc: 25 | endpoint: 0.0.0.0:4317 26 | http: 27 | endpoint: 0.0.0.0:4318 28 | 29 | processors: 30 | batch: 31 | 32 | exporters: 33 | logging: 34 | loglevel: debug 35 | awsxray: 36 | region: ${AWS_REGION} 37 | awsemf: 38 | region: ${AWS_REGION} 39 | 40 | service: 41 | pipelines: 42 | traces: 43 | receivers: [otlp] 44 | exporters: [awsxray, logging] 45 | metrics: 46 | receivers: [otlp] 47 | exporters: [awsemf, logging] 48 | 49 | extensions: [pprof] 50 | telemetry: 51 | logs: 52 | level: debug 53 | -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.sku.ingress.enabled -}} 2 | {{- $fullName := include "helm-chart.fullname" . -}} 3 | apiVersion: networking.k8s.io/v1 4 | kind: Ingress 5 | metadata: 6 | name: {{ $fullName }} 7 | namespace: {{ $fullName }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "helm-chart.name" . }} 10 | helm.sh/chart: {{ include "helm-chart.chart" . }} 11 | app.kubernetes.io/instance: {{ .Release.Name }} 12 | app.kubernetes.io/managed-by: {{ .Release.Service }} 13 | {{- with .Values.sku.ingress.annotations }} 14 | annotations: 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | spec: 18 | ingressClassName: alb 19 | {{- if .Values.sku.ingress.tls }} 20 | tls: 21 | {{- range .Values.sku.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.sku.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ . }} 36 | pathType: Prefix 37 | backend: 38 | service: 39 | name: {{ $fullName }} 40 | port: 41 | number: 80 42 | {{- end }} 43 | {{- end }} 44 | {{- end }} 45 | -------------------------------------------------------------------------------- /apps/catalog_detail/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: proddetail 6 | namespace: detail 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: proddetail 12 | template: 13 | metadata: 14 | labels: 15 | app: proddetail 16 | spec: 17 | containers: 18 | - name: proddetail 19 | image: "405710966773.dkr.ecr.us-east-2.amazonaws.com/eks-app-mesh-demo/catalog_detail:2.0" 20 | imagePullPolicy: Always 21 | livenessProbe: 22 | httpGet: 23 | path: /ping 24 | port: 3000 25 | initialDelaySeconds: 0 26 | periodSeconds: 10 27 | timeoutSeconds: 1 28 | failureThreshold: 3 29 | readinessProbe: 30 | httpGet: 31 | path: /ping 32 | port: 3000 33 | successThreshold: 3 34 | ports: 35 | - containerPort: 3000 36 | --- 37 | apiVersion: v1 38 | kind: Service 39 | metadata: 40 | #annotations: 41 | # This annotation is only required if you are creating an internal facing ELB. Remove this annotation to create public facing ELB. 42 | #service.beta.kubernetes.io/aws-load-balancer-internal: "true" 43 | name: proddetail 44 | namespace: detail 45 | labels: 46 | app: proddetail 47 | spec: 48 | type: NodePort 49 | ports: 50 | - name: "http" 51 | port: 3000 52 | targetPort: 3000 53 | nodePort: 30036 54 | selector: 55 | app: proddetail -------------------------------------------------------------------------------- /workshop/apps/sku/app/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SKU Catalog 8 | 34 | 35 | 36 | 42 | 43 | 44 |

SKU Catalog Application

45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | <% for(var i = 0; i < skus.length; i++) { %> 54 | 55 | 56 | 57 | 58 | <% }; %> 59 | 60 |
SKU IDName
<%= skus[i].id %> <%= skus[i].name %>
61 | 62 | 63 | -------------------------------------------------------------------------------- /workshop/helm-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.frontend.ingress.enabled }} 3 | {{- range $host := .Values.frontend.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.frontend.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.frontend.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "helm-chart.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.frontend.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Values.frontend.namespace }} svc -w {{ .Values.frontend.name }}' 15 | export LB_NAME=$(kubectl get svc --namespace {{ .Values.frontend.namespace }} {{ .Values.frontend.name }} -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") 16 | echo http://$LB_NAME:{{ .Values.frontend.service.port }} 17 | {{- else if contains "ClusterIP" .Values.frontend.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "helm-chart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:3000/catalogDetail to use your application" 20 | kubectl port-forward $POD_NAME 3000:3000 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /workshop/apps/catalog_detail/app.js: -------------------------------------------------------------------------------- 1 | //Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | //SPDX-License-Identifier: MIT-0 3 | 4 | var express = require("express"); 5 | var app = express(); 6 | var XRay = require('aws-xray-sdk'); 7 | var AWS = XRay.captureAWS(require('aws-sdk')); 8 | XRay.captureHTTPsGlobal(require('http')); 9 | var http = require('http'); 10 | var os = require("os"); 11 | 12 | var responseStatus = 200; 13 | 14 | app.use(XRay.express.openSegment('Product-Detail')); 15 | 16 | app.get("/catalogDetail", (req, res, next) => { 17 | res.status(responseStatus) 18 | if (responseStatus == 200) { 19 | console.log("Catalog Detail Get Request Successful"); 20 | res.json({ 21 | "version":"1", 22 | "vendors":[ "ABC.com" ] 23 | } ) 24 | } else { 25 | console.log("Catalog Detail Get Request has error 500"); 26 | res.json("Error") 27 | } 28 | }); 29 | 30 | app.get("/ping", (req, res, next) => { 31 | res.status(responseStatus) 32 | if (responseStatus == 200) { 33 | res.json("Healthy") 34 | } else { 35 | console.log("Returning unhealthy"); 36 | res.json("UnHealthy") 37 | } 38 | }); 39 | 40 | app.get("/injectFault", (req, res, next) => { 41 | console.log("host: " + os.hostname() + " will now respond with 500 error."); 42 | responseStatus=500; 43 | res.status(500); 44 | next(new Error("host: " + os.hostname() + " will now respond with 500 error.")); 45 | }); 46 | 47 | app.get("/resetFault", (req, res, next) => { 48 | console.log("Removed fault injection from host: " + os.hostname()); 49 | responseStatus=200; 50 | res.json("Removed fault injection from host: " + os.hostname()); 51 | }); 52 | 53 | app.use(XRay.express.closeSegment()); 54 | 55 | app.listen(3000, () => { 56 | console.log("Server running on port 3000"); 57 | }); -------------------------------------------------------------------------------- /deployment/virtual_gateway.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: appmesh.k8s.aws/v1beta2 3 | kind: VirtualGateway 4 | metadata: 5 | name: ingress-gw 6 | namespace: prodcatalog-ns 7 | spec: 8 | namespaceSelector: 9 | matchLabels: 10 | gateway: ingress-gw 11 | podSelector: 12 | matchLabels: 13 | app: ingress-gw 14 | listeners: 15 | - portMapping: 16 | port: 8088 17 | protocol: http 18 | logging: 19 | accessLog: 20 | file: 21 | path: /dev/stdout 22 | --- 23 | apiVersion: appmesh.k8s.aws/v1beta2 24 | kind: GatewayRoute 25 | metadata: 26 | name: gateway-route-frontend 27 | namespace: prodcatalog-ns 28 | spec: 29 | httpRoute: 30 | match: 31 | prefix: "/" 32 | action: 33 | target: 34 | virtualService: 35 | virtualServiceRef: 36 | name: frontend-node 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | name: ingress-gw 42 | namespace: prodcatalog-ns 43 | annotations: 44 | service.beta.kubernetes.io/aws-load-balancer-type: "nlb" 45 | service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" 46 | service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip" 47 | spec: 48 | type: LoadBalancer 49 | ports: 50 | - port: 80 51 | targetPort: 8088 52 | name: http 53 | selector: 54 | app: ingress-gw 55 | --- 56 | apiVersion: apps/v1 57 | kind: Deployment 58 | metadata: 59 | name: ingress-gw 60 | namespace: prodcatalog-ns 61 | spec: 62 | replicas: 1 63 | selector: 64 | matchLabels: 65 | app: ingress-gw 66 | template: 67 | metadata: 68 | labels: 69 | app: ingress-gw 70 | spec: 71 | serviceAccountName: prodcatalog-envoy-proxies 72 | securityContext: 73 | fsGroup: 65534 74 | containers: 75 | - name: envoy 76 | image: public.ecr.aws/appmesh/aws-appmesh-envoy:v1.23.1.0-prod 77 | ports: 78 | - containerPort: 8088 79 | --- 80 | -------------------------------------------------------------------------------- /apps/frontend_node/public/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lato", "Helvetica", Helvetica, sans-serif; 3 | background: #E6E6E6; 4 | color: #000000B; 5 | padding: 15px; 6 | letter-spacing: 1px; 7 | } 8 | table, th, td { 9 | border: 1px solid black; 10 | border-collapse: collapse; 11 | } 12 | th, td { 13 | padding: 8px; 14 | text-align: left; 15 | } 16 | tr:nth-child(even){background-color: #F0F8FF} 17 | tr:nth-child(odd){background-color: #E6E6FA} 18 | th { 19 | background-color: #1E90FF; 20 | color: white; 21 | } 22 | 23 | h3 { 24 | color: #800000; 25 | } 26 | button { 27 | background-color: #00BFFF; /* blue */ 28 | border: none; 29 | color: white; 30 | padding: 6px 9px; 31 | text-align: center; 32 | text-decoration: none; 33 | display: inline-block; 34 | font-size: 16px; 35 | } 36 | input[type=id], select { 37 | width: 20%; 38 | padding: 5px 8px; 39 | margin: 8px 0; 40 | display: inline-block; 41 | border: 1px solid #ccc; 42 | border-radius: 4px; 43 | box-sizing: border-box; 44 | } 45 | input[type=name], select { 46 | width: 50%; 47 | padding: 5px 8px; 48 | margin: 8px 0; 49 | display: inline-block; 50 | border: 1px solid #ccc; 51 | border-radius: 4px; 52 | box-sizing: border-box; 53 | } 54 | 55 | input, 56 | input::-webkit-input-placeholder { 57 | font-size: 16px; 58 | line-height: 1; 59 | } 60 | input:read-only { 61 | width: 50%; 62 | padding: 12px 15px; 63 | margin: 8px 0; 64 | display: inline-block; 65 | border: 1px solid #ccc; 66 | border-radius: 4px; 67 | box-sizing: border-box; 68 | color: #000000B; 69 | background-color: #E6E6FA; 70 | } 71 | 72 | mark.red { 73 | color:#ff0000; 74 | background: none; 75 | } 76 | 77 | mark.blue { 78 | color:#0000FF; 79 | background: none; 80 | } 81 | 82 | label{ 83 | display: inline-block; 84 | vertical-align: middle; 85 | padding: 10px 5px; 86 | margin-right: 15px; 87 | color: #000000B; 88 | background-color: #E6E6FA; 89 | } 90 | -------------------------------------------------------------------------------- /workshop/apps/sku/app/public/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lato", "Helvetica", Helvetica, sans-serif; 3 | background: #E6E6E6; 4 | color: #000000B; 5 | padding: 15px; 6 | letter-spacing: 1px; 7 | } 8 | table, th, td { 9 | border: 1px solid black; 10 | border-collapse: collapse; 11 | } 12 | th, td { 13 | padding: 8px; 14 | text-align: left; 15 | } 16 | tr:nth-child(even){background-color: #F0F8FF} 17 | tr:nth-child(odd){background-color: #E6E6FA} 18 | th { 19 | background-color: #1E90FF; 20 | color: white; 21 | } 22 | 23 | h3 { 24 | color: #800000; 25 | } 26 | button { 27 | background-color: #00BFFF; /* blue */ 28 | border: none; 29 | color: white; 30 | padding: 6px 9px; 31 | text-align: center; 32 | text-decoration: none; 33 | display: inline-block; 34 | font-size: 16px; 35 | } 36 | input[type=id], select { 37 | width: 20%; 38 | padding: 5px 8px; 39 | margin: 8px 0; 40 | display: inline-block; 41 | border: 1px solid #ccc; 42 | border-radius: 4px; 43 | box-sizing: border-box; 44 | } 45 | input[type=name], select { 46 | width: 50%; 47 | padding: 5px 8px; 48 | margin: 8px 0; 49 | display: inline-block; 50 | border: 1px solid #ccc; 51 | border-radius: 4px; 52 | box-sizing: border-box; 53 | } 54 | 55 | input, 56 | input::-webkit-input-placeholder { 57 | font-size: 16px; 58 | line-height: 1; 59 | } 60 | input:read-only { 61 | width: 50%; 62 | padding: 12px 15px; 63 | margin: 8px 0; 64 | display: inline-block; 65 | border: 1px solid #ccc; 66 | border-radius: 4px; 67 | box-sizing: border-box; 68 | color: #000000B; 69 | background-color: #E6E6FA; 70 | } 71 | 72 | mark.red { 73 | color:#ff0000; 74 | background: none; 75 | } 76 | 77 | mark.blue { 78 | color:#0000FF; 79 | background: none; 80 | } 81 | 82 | label{ 83 | display: inline-block; 84 | vertical-align: middle; 85 | padding: 10px 5px; 86 | margin-right: 15px; 87 | color: #000000B; 88 | background-color: #E6E6FA; 89 | } 90 | -------------------------------------------------------------------------------- /workshop/apps/frontend_node/public/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lato", "Helvetica", Helvetica, sans-serif; 3 | background: #E6E6E6; 4 | color: #000000B; 5 | padding: 15px; 6 | letter-spacing: 1px; 7 | } 8 | table, th, td { 9 | border: 1px solid black; 10 | border-collapse: collapse; 11 | } 12 | th, td { 13 | padding: 8px; 14 | text-align: left; 15 | } 16 | tr:nth-child(even){background-color: #F0F8FF} 17 | tr:nth-child(odd){background-color: #E6E6FA} 18 | th { 19 | background-color: #1E90FF; 20 | color: white; 21 | } 22 | 23 | h3 { 24 | color: #800000; 25 | } 26 | button { 27 | background-color: #00BFFF; /* blue */ 28 | border: none; 29 | color: white; 30 | padding: 6px 9px; 31 | text-align: center; 32 | text-decoration: none; 33 | display: inline-block; 34 | font-size: 16px; 35 | } 36 | input[type=id], select { 37 | width: 20%; 38 | padding: 5px 8px; 39 | margin: 8px 0; 40 | display: inline-block; 41 | border: 1px solid #ccc; 42 | border-radius: 4px; 43 | box-sizing: border-box; 44 | } 45 | input[type=name], select { 46 | width: 50%; 47 | padding: 5px 8px; 48 | margin: 8px 0; 49 | display: inline-block; 50 | border: 1px solid #ccc; 51 | border-radius: 4px; 52 | box-sizing: border-box; 53 | } 54 | 55 | input, 56 | input::-webkit-input-placeholder { 57 | font-size: 16px; 58 | line-height: 1; 59 | } 60 | input:read-only { 61 | width: 50%; 62 | padding: 12px 15px; 63 | margin: 8px 0; 64 | display: inline-block; 65 | border: 1px solid #ccc; 66 | border-radius: 4px; 67 | box-sizing: border-box; 68 | color: #000000B; 69 | background-color: #E6E6FA; 70 | } 71 | 72 | mark.red { 73 | color:#ff0000; 74 | background: none; 75 | } 76 | 77 | mark.blue { 78 | color:#0000FF; 79 | background: none; 80 | } 81 | 82 | label{ 83 | display: inline-block; 84 | vertical-align: middle; 85 | padding: 10px 5px; 86 | margin-right: 15px; 87 | color: #000000B; 88 | background-color: #E6E6FA; 89 | } 90 | -------------------------------------------------------------------------------- /workshop/xray-eks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: DaemonSet 4 | metadata: 5 | name: xray-daemon 6 | namespace: default 7 | spec: 8 | updateStrategy: 9 | type: RollingUpdate 10 | selector: 11 | matchLabels: 12 | app: xray-daemon 13 | template: 14 | metadata: 15 | labels: 16 | app: xray-daemon 17 | spec: 18 | serviceAccountName: xray-daemon 19 | volumes: 20 | - name: config-volume 21 | configMap: 22 | name: "xray-config" 23 | containers: 24 | - name: xray-daemon 25 | image: amazon/aws-xray-daemon 26 | command: ["/usr/bin/xray", "-c", "/aws/xray/config.yaml"] 27 | resources: 28 | requests: 29 | cpu: 256m 30 | memory: 32Mi 31 | limits: 32 | cpu: 512m 33 | memory: 64Mi 34 | ports: 35 | - name: xray-ingest 36 | containerPort: 2000 37 | hostPort: 2000 38 | protocol: UDP 39 | - name: xray-tcp 40 | containerPort: 2000 41 | hostPort: 2000 42 | protocol: TCP 43 | volumeMounts: 44 | - name: config-volume 45 | mountPath: /aws/xray 46 | readOnly: true 47 | --- 48 | # Configuration for AWS X-Ray daemon 49 | apiVersion: v1 50 | kind: ConfigMap 51 | metadata: 52 | name: xray-config 53 | namespace: default 54 | data: 55 | config.yaml: |- 56 | TotalBufferSizeMB: 24 57 | Socket: 58 | UDPAddress: "0.0.0.0:2000" 59 | TCPAddress: "0.0.0.0:2000" 60 | Version: 2 61 | --- 62 | # k8s service definition for AWS X-Ray daemon headless service 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: xray-service 67 | namespace: default 68 | spec: 69 | selector: 70 | app: xray-daemon 71 | clusterIP: None 72 | ports: 73 | - name: xray-ingest 74 | port: 2000 75 | protocol: UDP 76 | - name: xray-tcp 77 | port: 2000 78 | protocol: TCP -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository is used for many AWS EKS workshops in https://catalog.workshops.aws/eks-immersionday/en-US 2 | 3 | ## 1. Workshop on Polyglot Microservices in EKS 4 | 5 | ![fronteend](workshop/images/lbui.png) 6 | 7 | To Run this workshop,follow the below steps: 8 | 9 | ### Clone the Repository 10 | ``` 11 | git clone https://github.com/aws-containers/eks-app-mesh-polyglot-demo.git 12 | cd eks-app-mesh-polyglot-demo/workshop 13 | ``` 14 | 15 | ### Install the Helm chart 16 | ``` 17 | helm install workshop helm-chart/ 18 | ``` 19 | You should see below output 20 | ``` 21 | NOTES: 22 | 1. Get the application URL by running these commands: 23 | NOTE: It may take a few minutes for the LoadBalancer to be available. 24 | You can watch the status of by running 'kubectl get --namespace workshop svc -w frontend' 25 | export LB_NAME=$(kubectl get svc --namespace workshop frontend -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") 26 | echo http://$LB_NAME:9000 27 | ``` 28 | 29 | ### Get the LoadBalancer url. 30 | ``` 31 | export LB_NAME=$(kubectl get svc frontend -n workshop -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") 32 | echo $LB_NAME:9000 33 | ``` 34 | Go to the browser and paste this url, you should see below screen 35 | ![fronteend](workshop/images/workshopui.png) 36 | 37 | You can add products and see the below details 38 | ![fronteend](workshop/images/addproducts.png) 39 | 40 | 41 | ## 2. Workshop on Polyglot Microservices in App Mesh and EKS plus Fargate 42 | 43 | In this tutorial, we’ll walk you through the following, which are popular App Mesh use cases using the example of below Product Catalog Application deployment. For complete documentation checkout [Service Mesh using App Mesh](https://www.eksworkshop.com/advanced/330_servicemesh_using_appmesh/) 44 | 45 | * Deploy a microservices-based application in Amazon EKS using AWS Fargate 46 | * Configure an App Mesh Virtual Gateway to route traffic to the application services 47 | * Create a Canary Deployment using App Mesh 48 | * Enable observability features with App Mesh, including logging for Fargate, Amazon Cloudwatch Container Insights, and AWS X-Ray tracing 49 | 50 | ![fronteend](images/lbfrontend-2.png) 51 | -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for helm-chart. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | sku: 6 | replicaCount: 3 7 | namespace: sku 8 | name: sku 9 | 10 | image: 11 | repository: public.ecr.aws/e1x6y8e1/sku 12 | tag: "5" 13 | pullPolicy: Always 14 | 15 | nameOverride: "" 16 | fullnameOverride: "" 17 | 18 | service: 19 | type: ClusterIP 20 | targetPort: 3000 21 | port: 80 22 | name: "http" 23 | annotations: 24 | alb.ingress.kubernetes.io/healthcheck-path: /ping 25 | 26 | selector: 27 | app: sku 28 | 29 | ingress: 30 | enabled: true 31 | annotations: 32 | alb.ingress.kubernetes.io/scheme: internet-facing 33 | alb.ingress.kubernetes.io/target-type: ip 34 | alb.ingress.kubernetes.io/group.name: product-catalog 35 | hosts: 36 | - host: 37 | paths: [/sku] 38 | 39 | tls: [] 40 | # - secretName: chart-example-tls 41 | # hosts: 42 | # - chart-example.local 43 | 44 | livenessProbe: 45 | enabled: true 46 | path: /ping 47 | port: 3000 48 | initialDelaySeconds: 5 49 | periodSeconds: 5 50 | timeoutSeconds: 1 51 | failureThreshold: 3 52 | successThreshold: 1 53 | readinessProbe: 54 | enabled: true 55 | path: /ping 56 | port: 3000 57 | initialDelaySeconds: 5 58 | periodSeconds: 3 59 | timeoutSeconds: 1 60 | failureThreshold: 3 61 | successThreshold: 1 62 | startupProbe: 63 | enabled: false 64 | # port: 9000 65 | # path: /ping 66 | # initialDelaySeconds: 10 67 | # periodSeconds: 10 68 | # timeoutSeconds: 5 69 | # failureThreshold: 1 70 | # successThreshold: 1 71 | 72 | 73 | resources: {} 74 | # We usually recommend not to specify default resources and to leave this as a conscious 75 | # choice for the user. This also increases chances charts run on environments with little 76 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 77 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 78 | # limits: 79 | # cpu: 100m 80 | # memory: 128Mi 81 | # requests: 82 | # cpu: 100m 83 | # memory: 128Mi 84 | 85 | nodeSelector: {} 86 | 87 | tolerations: [] 88 | 89 | affinity: {} 90 | -------------------------------------------------------------------------------- /workshop/cloudformation/alb_deployment.yaml: -------------------------------------------------------------------------------- 1 | Description: 2 | This template deploys an Application Load Balancer, associates it with a security group, creates a listener and also a target Group and associate them with the load balancer 3 | 4 | Parameters: 5 | VPC: 6 | Description: Choose which VPC the Application Load Balancer should be deployed in 7 | Type: AWS::EC2::VPC::Id 8 | 9 | Subnets: 10 | Description: Choose which subnets the Application Load Balancer should be deployed to 11 | Type: List 12 | 13 | NodeSecurityGroup: 14 | Description : Choose the Security Group Name of the Worker Nodes (eks-cluster-sg-eksworkshop-eksctl-xxxxx) 15 | Type: AWS::EC2::SecurityGroup::Id 16 | 17 | Resources: 18 | 19 | ALBSecurityGroup: 20 | Type: AWS::EC2::SecurityGroup 21 | Properties: 22 | GroupName: eksworkshopalbsg 23 | GroupDescription: ALB Security Group created by CloudFormation 24 | VpcId: !Ref VPC 25 | Tags: [ 26 | {"Key": "Name", "Value": "eksworkshopalbsg"}, 27 | ] 28 | SecurityGroupIngress: 29 | - IpProtocol: tcp 30 | FromPort: 80 31 | ToPort: 80 32 | CidrIp: 0.0.0.0/0 33 | 34 | NodeSecurityGroupIngressRule : 35 | Type: AWS::EC2::SecurityGroupIngress 36 | Properties: 37 | Description: Rule to allow traffic from ALB to the TestApp Pods 38 | GroupId: !Ref NodeSecurityGroup 39 | IpProtocol: TCP 40 | SourceSecurityGroupId: !Ref ALBSecurityGroup 41 | FromPort: 80 42 | ToPort: 80 43 | 44 | LoadBalancer: 45 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 46 | Properties: 47 | Name: eksworkshopalb 48 | Subnets: !Ref Subnets 49 | SecurityGroups: 50 | - !Ref ALBSecurityGroup 51 | Tags: 52 | - Key: Name 53 | Value: eksworkshop 54 | 55 | LoadBalancerListener: 56 | Type: AWS::ElasticLoadBalancingV2::Listener 57 | Properties: 58 | LoadBalancerArn: !Ref LoadBalancer 59 | Port: 80 60 | Protocol: HTTP 61 | DefaultActions: 62 | - Type: forward 63 | TargetGroupArn: !Ref TargetGroup 64 | 65 | TargetGroup: 66 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 67 | Properties: 68 | Name: eksworkshoptg 69 | TargetType: ip 70 | VpcId: !Ref VPC 71 | Port: 80 72 | Protocol: HTTP 73 | 74 | Outputs: 75 | 76 | LoadBalancerDNSName: 77 | Description: A reference to the LoadBalancer DNS Name 78 | Value: !GetAtt LoadBalancer.DNSName 79 | 80 | TargetGroupARN: 81 | Description: A reference to the TargetGroup ARN Value 82 | Value: !Ref TargetGroup 83 | -------------------------------------------------------------------------------- /deployment/canary.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: appmesh.k8s.aws/v1beta2 3 | kind: VirtualNode 4 | metadata: 5 | name: proddetail-v2 6 | namespace: prodcatalog-ns 7 | spec: 8 | podSelector: 9 | matchLabels: 10 | app: proddetail2 11 | listeners: 12 | - portMapping: 13 | port: 3000 14 | protocol: http 15 | healthCheck: 16 | protocol: http 17 | path: '/ping' 18 | healthyThreshold: 2 19 | unhealthyThreshold: 2 20 | timeoutMillis: 2000 21 | intervalMillis: 5000 22 | serviceDiscovery: 23 | dns: 24 | hostname: proddetail2.prodcatalog-ns.svc.cluster.local 25 | --- 26 | apiVersion: appmesh.k8s.aws/v1beta2 27 | kind: VirtualRouter 28 | metadata: 29 | name: proddetail-router 30 | namespace: prodcatalog-ns 31 | spec: 32 | listeners: 33 | - portMapping: 34 | port: 3000 35 | protocol: http 36 | routes: 37 | - name: proddetail-route 38 | httpRoute: 39 | match: 40 | prefix: / 41 | action: 42 | weightedTargets: 43 | - virtualNodeRef: 44 | name: proddetail-v1 45 | weight: 90 46 | - virtualNodeRef: 47 | name: proddetail-v2 48 | weight: 10 49 | --- 50 | apiVersion: apps/v1 51 | kind: Deployment 52 | metadata: 53 | name: proddetail2 54 | namespace: prodcatalog-ns 55 | spec: 56 | replicas: 1 57 | selector: 58 | matchLabels: 59 | app: proddetail2 60 | template: 61 | metadata: 62 | labels: 63 | app: proddetail2 64 | spec: 65 | serviceAccountName: prodcatalog-envoy-proxies 66 | containers: 67 | - name: proddetail 68 | image: "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eks-app-mesh-demo/catalog_detail:${APP_VERSION_2}" 69 | imagePullPolicy: Always 70 | livenessProbe: 71 | httpGet: 72 | path: /ping 73 | port: 3000 74 | initialDelaySeconds: 0 75 | periodSeconds: 10 76 | timeoutSeconds: 1 77 | failureThreshold: 3 78 | readinessProbe: 79 | httpGet: 80 | path: /ping 81 | port: 3000 82 | successThreshold: 3 83 | ports: 84 | - containerPort: 3000 85 | --- 86 | apiVersion: v1 87 | kind: Service 88 | metadata: 89 | name: proddetail2 90 | namespace: prodcatalog-ns 91 | labels: 92 | app: proddetail2 93 | spec: 94 | ports: 95 | - name: "http" 96 | port: 3000 97 | targetPort: 3000 98 | selector: 99 | app: proddetail2 100 | --- -------------------------------------------------------------------------------- /workshop/apps/frontend_node/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Product Catalog 8 | 9 | 10 | 11 | 16 | 17 | 18 |

Product Catalog Application

19 | 20 | 21 | 65 | 69 | 70 |
22 |

Product Catalog

23 |

24 |

25 | 26 | 27 | 28 |
29 |

30 | <%if (Object.keys(products).length > 0) { %> 31 | 32 | 34 | 35 | 36 | 37 | <% for(var i = 0; i < Object.keys(products).length; i++) { %> 38 | 39 | 40 | 41 | 42 | 43 | <% }; %> 44 | 45 |
Product IDProduct Name
<%= Object.keys(products)[i] %> <%= Object.values(products)[i] %>
46 | <% } else { %> 47 |

No Products found in the Product Catalog

48 | <% } %> 49 | <%if (Object.keys(products).length > 0) { %> 50 |

Catalog Detail

51 |

52 | 62 | <% } %> 63 | 64 |

66 |

Architecture

67 | architecture 68 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /apps/frontend_node/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser= require('body-parser') 3 | const axios = require('axios') 4 | const app = express() 5 | const path = require("path"); 6 | const Prometheus = require('prom-client') 7 | 8 | var AWSXRay = require('aws-xray-sdk'); 9 | app.use(AWSXRay.express.openSegment('Frontend-Node')); 10 | 11 | Prometheus.collectDefaultMetrics(); 12 | 13 | var baseProductUrl = process.env.BASE_URL; 14 | 15 | if(baseProductUrl === undefined) { 16 | baseProductUrl = 'http://localhost:5000/products/'; 17 | } 18 | 19 | console.log(baseProductUrl); 20 | 21 | // ======================== 22 | // Middlewares 23 | // ======================== 24 | app.set('view engine', 'ejs') 25 | app.use(express.static(path.join(__dirname, "public"))); 26 | 27 | app.use(bodyParser.urlencoded({extended: true})) 28 | 29 | app.get('/', (req, res) => { 30 | let query = req.query.queryStr; 31 | 32 | const requestOne = axios.get(baseProductUrl); 33 | //const requestTwo = axios.get(baseSummaryUrl); 34 | //axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => { 35 | axios.all([requestOne]).then(axios.spread((...responses) => { 36 | const responseOne = responses[0] 37 | // const responseTwo = responses[1] 38 | 39 | // console.log(responseOne.data.products, responseOne.data.details.vendors, responseOne.data.details.version); 40 | res.render('index.ejs', {products: responseOne.data.products, vendors:responseOne.data.details.vendors, version:responseOne.data.details.version}) 41 | console.log("Product Catalog get call was Successful from frontend"); 42 | })).catch(errors => { 43 | 44 | // console.log("baseSummaryUrl " + baseSummaryUrl); 45 | console.log(errors); 46 | console.log("There was error in Product Catalog get call from frontend"); 47 | }) 48 | 49 | }) 50 | 51 | app.post('/products', (req, res) => { 52 | var headers = { 53 | 'Content-Type': 'application/json' 54 | } 55 | axios 56 | .post(`${baseProductUrl}${req.body.id}`, JSON.stringify({ name: `${req.body.name}` }), {"headers" : headers}) 57 | .then(response => { 58 | //console.log(`statusCode: ${response}`) 59 | //console.log(response) 60 | res.redirect(req.get('referer')); 61 | console.log("Product Catalog post call was Successful from frontend"); 62 | }) 63 | .catch(error => { 64 | console.error(error) 65 | }) 66 | 67 | }) 68 | 69 | app.get("/ping", (req, res, next) => { 70 | res.json("Healthy") 71 | }); 72 | 73 | // Export Prometheus metrics from /stats/prometheus endpoint 74 | app.get('/stats/prometheus', (req, res, next) => { 75 | res.set('Content-Type', Prometheus.register.contentType) 76 | res.end(Prometheus.register.metrics()) 77 | }) 78 | 79 | 80 | app.use(AWSXRay.express.closeSegment()); 81 | 82 | app.listen(9000, function() { 83 | console.log('listening on 9000') 84 | }) -------------------------------------------------------------------------------- /workshop/helm-chart/templates/detail_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ .Values.detail.name }} 5 | namespace: {{ .Values.detail.namespace }} 6 | spec: 7 | replicas: {{ .Values.detail.replicaCount }} 8 | selector: 9 | matchLabels: 10 | app: {{ .Values.detail.name }} 11 | template: 12 | metadata: 13 | labels: 14 | app: {{ .Values.detail.name }} 15 | spec: 16 | containers: 17 | - name: {{ .Values.detail.name }} 18 | image: "{{ .Values.detail.image.repository }}:{{ .Values.detail.image.tag }}" 19 | imagePullPolicy: {{ .Values.detail.image.pullPolicy }} 20 | ports: 21 | - name: http 22 | containerPort: {{ .Values.detail.service.targetPort }} 23 | protocol: TCP 24 | {{- if .Values.detail.livenessProbe.enabled }} 25 | livenessProbe: 26 | httpGet: 27 | path: {{ .Values.detail.livenessProbe.path }} 28 | port: {{ .Values.detail.livenessProbe.port }} 29 | initialDelaySeconds: {{ .Values.detail.livenessProbe.initialDelaySeconds }} 30 | periodSeconds: {{ .Values.detail.livenessProbe.periodSeconds }} 31 | timeoutSeconds: {{ .Values.detail.livenessProbe.timeoutSeconds }} 32 | successThreshold: {{ .Values.detail.livenessProbe.successThreshold }} 33 | failureThreshold: {{ .Values.detail.livenessProbe.failureThreshold }} 34 | {{- end }} 35 | readinessProbe: 36 | exec: 37 | command: 38 | - /bin/bash 39 | - -c 40 | - cat readiness.txt | grep ready 41 | initialDelaySeconds: 15 42 | periodSeconds: 3 43 | {{- if .Values.detail.startupProbe.enabled }} 44 | startupProbe: 45 | httpGet: 46 | path: {{ .Values.detail.startupProbe.path }} 47 | port: {{ .Values.detail.startupProbe.port }} 48 | initialDelaySeconds: {{ .Values.detail.startupProbe.initialDelaySeconds }} 49 | periodSeconds: {{ .Values.detail.startupProbe.periodSeconds }} 50 | timeoutSeconds: {{ .Values.detail.startupProbe.timeoutSeconds }} 51 | successThreshold: {{ .Values.detail.startupProbe.successThreshold }} 52 | failureThreshold: {{ .Values.detail.startupProbe.failureThreshold }} 53 | {{- end }} 54 | env: 55 | {{- toYaml .Values.detail.env | nindent 12 }} 56 | resources: 57 | {{- toYaml .Values.detail.resources | nindent 12 }} 58 | {{- with .Values.detail.nodeSelector }} 59 | nodeSelector: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | {{- with .Values.detail.affinity }} 63 | affinity: 64 | {{- toYaml . | nindent 8 }} 65 | {{- end }} 66 | {{- with .Values.detail.tolerations }} 67 | tolerations: 68 | {{- toYaml . | nindent 8 }} 69 | {{- end }} 70 | -------------------------------------------------------------------------------- /workshop/apps/frontend_node/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser= require('body-parser') 3 | const axios = require('axios') 4 | const app = express() 5 | const path = require("path"); 6 | const Prometheus = require('prom-client') 7 | 8 | var XRay = require('aws-xray-sdk'); 9 | var AWS = XRay.captureAWS(require('aws-sdk')); 10 | XRay.captureHTTPsGlobal(require('http')); 11 | var http = require('http'); 12 | 13 | app.use(bodyParser.urlencoded({extended: false})); 14 | app.use(XRay.express.openSegment('Frontend')); 15 | 16 | Prometheus.collectDefaultMetrics(); 17 | 18 | var baseProductUrl = process.env.BASE_URL; 19 | 20 | if(baseProductUrl === undefined) { 21 | baseProductUrl = 'http://localhost:5000/products/'; 22 | } 23 | 24 | console.log(baseProductUrl); 25 | 26 | // ======================== 27 | // Middlewares 28 | // ======================== 29 | app.set('view engine', 'ejs') 30 | app.use(express.static(path.join(__dirname, "public"))); 31 | 32 | app.use(bodyParser.urlencoded({extended: true})) 33 | 34 | app.get('/', (req, res) => { 35 | var seg = XRay.getSegment(); 36 | seg.addAnnotation('service', 'prodcatalog-request'); 37 | let query = req.query.queryStr; 38 | 39 | const requestOne = axios.get(baseProductUrl); 40 | //const requestTwo = axios.get(baseSummaryUrl); 41 | //axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => { 42 | axios.all([requestOne]).then(axios.spread((...responses) => { 43 | const responseOne = responses[0] 44 | // const responseTwo = responses[1] 45 | 46 | // console.log(responseOne.data.products, responseOne.data.details.vendors, responseOne.data.details.version); 47 | res.render('index.ejs', {products: responseOne.data.products, vendors:responseOne.data.details.vendors, version:responseOne.data.details.version}) 48 | console.log("Product Catalog get call was Successful from frontend"); 49 | })).catch(errors => { 50 | 51 | // console.log("baseSummaryUrl " + baseSummaryUrl); 52 | console.log(errors); 53 | console.log("There was error in Product Catalog get call from frontend"); 54 | }) 55 | 56 | }) 57 | 58 | app.post('/products', (req, res) => { 59 | var headers = { 60 | 'Content-Type': 'application/json' 61 | } 62 | axios 63 | .post(`${baseProductUrl}${req.body.id}`, JSON.stringify({ name: `${req.body.name}` }), {"headers" : headers}) 64 | .then(response => { 65 | //console.log(`statusCode: ${response}`) 66 | //console.log(response) 67 | res.redirect(req.get('referer')); 68 | console.log("Product Catalog post call was Successful from frontend"); 69 | }) 70 | .catch(error => { 71 | console.error(error) 72 | }) 73 | 74 | }) 75 | 76 | app.get("/ping", (req, res, next) => { 77 | res.json("Healthy") 78 | }); 79 | 80 | // Export Prometheus metrics from /metrics endpoint 81 | app.get('/metrics', (req, res, next) => { 82 | res.set('Content-Type', Prometheus.register.contentType) 83 | res.end(Prometheus.register.metrics()) 84 | }) 85 | 86 | 87 | app.use(XRay.express.closeSegment()); 88 | 89 | app.listen(9000, function() { 90 | console.log('listening on 9000') 91 | }) -------------------------------------------------------------------------------- /workshop/spinnaker/spinnakerservice.yml: -------------------------------------------------------------------------------- 1 | apiVersion: spinnaker.io/v1alpha2 2 | kind: SpinnakerService 3 | metadata: 4 | name: spinnaker 5 | spec: 6 | spinnakerConfig: 7 | config: 8 | version: $SPINNAKER_VERSION # the version of Spinnaker to be deployed 9 | persistentStorage: 10 | persistentStoreType: s3 11 | s3: 12 | bucket: $S3_BUCKET 13 | rootFolder: front50 14 | region: $AWS_REGION 15 | deploymentEnvironment: 16 | sidecars: 17 | spin-clouddriver: 18 | - name: token-refresh 19 | dockerImage: quay.io/skuid/ecr-token-refresh:latest 20 | mountPath: /etc/passwords 21 | configMapVolumeMounts: 22 | - configMapName: token-refresh-config 23 | mountPath: /opt/config/ecr-token-refresh 24 | features: 25 | artifacts: true 26 | artifacts: 27 | github: 28 | enabled: true 29 | accounts: 30 | - name: $GITHUB_USER 31 | token: $GITHUB_TOKEN # GitHub's personal access token. This fields supports `encrypted` references to secrets. 32 | providers: 33 | dockerRegistry: 34 | enabled: true 35 | kubernetes: 36 | enabled: true 37 | accounts: 38 | - name: spinnaker-workshop 39 | requiredGroupMembership: [] 40 | providerVersion: V2 41 | permissions: 42 | dockerRegistries: 43 | - accountName: my-ecr-registry 44 | configureImagePullSecrets: true 45 | cacheThreads: 1 46 | namespaces: [spinnaker,workshop] 47 | omitNamespaces: [] 48 | kinds: [] 49 | omitKinds: [] 50 | customResources: [] 51 | cachingPolicies: [] 52 | oAuthScopes: [] 53 | onlySpinnakerManaged: false 54 | kubeconfigFile: kubeconfig-sp # File name must match "files" key 55 | primaryAccount: spinnaker-workshop # Change to a desired account from the accounts array 56 | files: 57 | kubeconfig-sp: | 58 | 59 | profiles: 60 | clouddriver: 61 | dockerRegistry: 62 | enabled: true 63 | primaryAccount: my-ecr-registry 64 | accounts: 65 | - name: my-ecr-registry 66 | address: https://$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 67 | username: AWS 68 | passwordFile: /etc/passwords/my-ecr-registry.pass 69 | trackDigests: true 70 | repositories: 71 | - $ECR_REPOSITORY 72 | igor: 73 | docker-registry: 74 | enabled: true 75 | service-settings: 76 | front50: 77 | kubernetes: 78 | serviceAccountName: $S3_SERVICE_ACCOUNT 79 | securityContext: 80 | fsGroup: 100 81 | # spec.expose - This section defines how Spinnaker should be publicly exposed 82 | expose: 83 | type: service # Kubernetes LoadBalancer type (service/ingress), note: only "service" is supported for now 84 | service: 85 | type: LoadBalancer -------------------------------------------------------------------------------- /workshop/apps/sku/helm-chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ .Values.sku.name }} 5 | namespace: {{ .Values.sku.namespace }} 6 | spec: 7 | replicas: {{ .Values.sku.replicaCount }} 8 | selector: 9 | matchLabels: 10 | app: {{ .Values.sku.name }} 11 | template: 12 | metadata: 13 | labels: 14 | app: {{ .Values.sku.name }} 15 | spec: 16 | containers: 17 | - name: {{ .Values.sku.name }} 18 | image: "{{ .Values.sku.image.repository }}:{{ .Values.sku.image.tag }}" 19 | imagePullPolicy: {{ .Values.sku.image.pullPolicy }} 20 | ports: 21 | - name: http 22 | containerPort: {{ .Values.sku.service.targetPort }} 23 | protocol: TCP 24 | {{- if .Values.sku.livenessProbe.enabled }} 25 | livenessProbe: 26 | httpGet: 27 | path: {{ .Values.sku.livenessProbe.path }} 28 | port: {{ .Values.sku.livenessProbe.port }} 29 | initialDelaySeconds: {{ .Values.sku.livenessProbe.initialDelaySeconds }} 30 | periodSeconds: {{ .Values.sku.livenessProbe.periodSeconds }} 31 | timeoutSeconds: {{ .Values.sku.livenessProbe.timeoutSeconds }} 32 | successThreshold: {{ .Values.sku.livenessProbe.successThreshold }} 33 | failureThreshold: {{ .Values.sku.livenessProbe.failureThreshold }} 34 | {{- end }} 35 | {{- if .Values.sku.readinessProbe.enabled }} 36 | readinessProbe: 37 | httpGet: 38 | path: {{ .Values.sku.readinessProbe.path }} 39 | port: {{ .Values.sku.readinessProbe.port }} 40 | initialDelaySeconds: {{ .Values.sku.readinessProbe.initialDelaySeconds }} 41 | periodSeconds: {{ .Values.sku.readinessProbe.periodSeconds }} 42 | timeoutSeconds: {{ .Values.sku.readinessProbe.timeoutSeconds }} 43 | successThreshold: {{ .Values.sku.readinessProbe.successThreshold }} 44 | failureThreshold: {{ .Values.sku.readinessProbe.failureThreshold }} 45 | {{- end }} 46 | {{- if .Values.sku.startupProbe.enabled }} 47 | startupProbe: 48 | httpGet: 49 | path: {{ .Values.sku.startupProbe.path }} 50 | port: {{ .Values.sku.startupProbe.port }} 51 | initialDelaySeconds: {{ .Values.sku.startupProbe.initialDelaySeconds }} 52 | periodSeconds: {{ .Values.sku.startupProbe.periodSeconds }} 53 | timeoutSeconds: {{ .Values.sku.startupProbe.timeoutSeconds }} 54 | successThreshold: {{ .Values.sku.startupProbe.successThreshold }} 55 | failureThreshold: {{ .Values.sku.startupProbe.failureThreshold }} 56 | {{- end }} 57 | env: 58 | {{- toYaml .Values.sku.env | nindent 12 }} 59 | resources: 60 | {{- toYaml .Values.sku.resources | nindent 12 }} 61 | {{- with .Values.sku.nodeSelector }} 62 | nodeSelector: 63 | {{- toYaml . | nindent 8 }} 64 | {{- end }} 65 | {{- with .Values.sku.affinity }} 66 | affinity: 67 | {{- toYaml . | nindent 8 }} 68 | {{- end }} 69 | {{- with .Values.sku.tolerations }} 70 | tolerations: 71 | {{- toYaml . | nindent 8 }} 72 | {{- end }} 73 | -------------------------------------------------------------------------------- /apps/frontend_node/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Product Catalog 8 | 9 | 10 | 11 | 16 | 17 | 18 |

Product Catalog

19 | 20 | 21 | 72 | 76 | 77 |
22 | <%if (Object.keys(products).length > 0) { %> 23 |

Catalog Detail

24 | (From Nodegroup Nodejs backend) 25 |
26 |

27 | 37 |

38 | <% } %> 39 |

Products

40 | <%if (Object.keys(products).length > 0) { %> 41 | (From Fargate Python backend) 42 |
43 | <% } %> 44 |

45 |

46 | 47 | 48 | 49 |
50 |

51 | <%if (Object.keys(products).length > 0) { %> 52 | 53 | 55 | 56 | 57 | 58 | <% for(var i = 0; i < Object.keys(products).length; i++) { %> 59 | 60 | 61 | 62 | 63 | 64 | <% }; %> 65 | 66 |
Product IDProduct Name
<%= Object.keys(products)[i] %> <%= Object.values(products)[i] %>
67 | <% } else { %> 68 |

No Products found in the Product Catalog

69 | <% } %> 70 | 71 |
73 |

Architecture

74 | architecture 75 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /workshop/helm-chart/templates/frontend_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ .Values.frontend.name }} 5 | namespace: {{ .Values.frontend.namespace }} 6 | spec: 7 | replicas: {{ .Values.frontend.replicaCount }} 8 | selector: 9 | matchLabels: 10 | app: {{ .Values.frontend.name }} 11 | template: 12 | metadata: 13 | annotations: 14 | prometheus.io/scrape: 'true' 15 | prometheus.io/path: '/metrics' 16 | prometheus.io/port: '9000' 17 | labels: 18 | app: {{ .Values.frontend.name }} 19 | spec: 20 | containers: 21 | - name: {{ .Values.frontend.name }} 22 | image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" 23 | imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} 24 | ports: 25 | - name: http 26 | containerPort: {{ .Values.frontend.service.targetPort }} 27 | protocol: TCP 28 | {{- if .Values.frontend.livenessProbe.enabled }} 29 | livenessProbe: 30 | httpGet: 31 | path: {{ .Values.frontend.livenessProbe.path }} 32 | port: {{ .Values.frontend.livenessProbe.port }} 33 | initialDelaySeconds: {{ .Values.frontend.livenessProbe.initialDelaySeconds }} 34 | periodSeconds: {{ .Values.frontend.livenessProbe.periodSeconds }} 35 | timeoutSeconds: {{ .Values.frontend.livenessProbe.timeoutSeconds }} 36 | successThreshold: {{ .Values.frontend.livenessProbe.successThreshold }} 37 | failureThreshold: {{ .Values.frontend.livenessProbe.failureThreshold }} 38 | {{- end }} 39 | {{- if .Values.frontend.readinessProbe.enabled }} 40 | readinessProbe: 41 | httpGet: 42 | path: {{ .Values.frontend.readinessProbe.path }} 43 | port: {{ .Values.frontend.readinessProbe.port }} 44 | initialDelaySeconds: {{ .Values.frontend.readinessProbe.initialDelaySeconds }} 45 | periodSeconds: {{ .Values.frontend.readinessProbe.periodSeconds }} 46 | timeoutSeconds: {{ .Values.frontend.readinessProbe.timeoutSeconds }} 47 | successThreshold: {{ .Values.frontend.readinessProbe.successThreshold }} 48 | failureThreshold: {{ .Values.frontend.readinessProbe.failureThreshold }} 49 | {{- end }} 50 | {{- if .Values.frontend.startupProbe.enabled }} 51 | startupProbe: 52 | httpGet: 53 | path: {{ .Values.frontend.startupProbe.path }} 54 | port: {{ .Values.frontend.startupProbe.port }} 55 | initialDelaySeconds: {{ .Values.frontend.startupProbe.initialDelaySeconds }} 56 | periodSeconds: {{ .Values.frontend.startupProbe.periodSeconds }} 57 | timeoutSeconds: {{ .Values.frontend.startupProbe.timeoutSeconds }} 58 | successThreshold: {{ .Values.frontend.startupProbe.successThreshold }} 59 | failureThreshold: {{ .Values.frontend.startupProbe.failureThreshold }} 60 | {{- end }} 61 | {{- if .Values.frontend.security }} 62 | securityContext: 63 | {{- toYaml .Values.frontend.securityContext | nindent 12}} 64 | {{- end }} 65 | env: 66 | {{- toYaml .Values.frontend.env | nindent 12 }} 67 | resources: 68 | {{- toYaml .Values.frontend.resources | nindent 12 }} 69 | {{- with .Values.frontend.nodeSelector }} 70 | nodeSelector: 71 | {{- toYaml . | nindent 8 }} 72 | {{- end }} 73 | {{- with .Values.frontend.affinity }} 74 | affinity: 75 | {{- toYaml . | nindent 8 }} 76 | {{- end }} 77 | {{- with .Values.frontend.tolerations }} 78 | tolerations: 79 | {{- toYaml . | nindent 8 }} 80 | {{- end }} 81 | -------------------------------------------------------------------------------- /deployment/meshed_app.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: appmesh.k8s.aws/v1beta2 3 | kind: VirtualNode 4 | metadata: 5 | name: prodcatalog 6 | namespace: prodcatalog-ns 7 | spec: 8 | podSelector: 9 | matchLabels: 10 | app: prodcatalog 11 | listeners: 12 | - portMapping: 13 | port: 5000 14 | protocol: http 15 | healthCheck: 16 | protocol: http 17 | path: '/products/ping' 18 | healthyThreshold: 2 19 | unhealthyThreshold: 2 20 | timeoutMillis: 2000 21 | intervalMillis: 5000 22 | backends: 23 | - virtualService: 24 | virtualServiceRef: 25 | name: proddetail 26 | serviceDiscovery: 27 | dns: 28 | hostname: prodcatalog.prodcatalog-ns.svc.cluster.local 29 | logging: 30 | accessLog: 31 | file: 32 | path: /dev/stdout 33 | --- 34 | apiVersion: appmesh.k8s.aws/v1beta2 35 | kind: VirtualService 36 | metadata: 37 | name: prodcatalog 38 | namespace: prodcatalog-ns 39 | spec: 40 | awsName: prodcatalog.prodcatalog-ns.svc.cluster.local 41 | provider: 42 | virtualRouter: 43 | virtualRouterRef: 44 | name: prodcatalog-router 45 | --- 46 | apiVersion: appmesh.k8s.aws/v1beta2 47 | kind: VirtualService 48 | metadata: 49 | name: proddetail 50 | namespace: prodcatalog-ns 51 | spec: 52 | awsName: proddetail.prodcatalog-ns.svc.cluster.local 53 | provider: 54 | virtualRouter: 55 | virtualRouterRef: 56 | name: proddetail-router 57 | --- 58 | apiVersion: appmesh.k8s.aws/v1beta2 59 | kind: VirtualRouter 60 | metadata: 61 | name: proddetail-router 62 | namespace: prodcatalog-ns 63 | spec: 64 | listeners: 65 | - portMapping: 66 | port: 3000 67 | protocol: http 68 | routes: 69 | - name: proddetail-route 70 | httpRoute: 71 | match: 72 | prefix: / 73 | action: 74 | weightedTargets: 75 | - virtualNodeRef: 76 | name: proddetail-v1 77 | weight: 100 78 | --- 79 | apiVersion: appmesh.k8s.aws/v1beta2 80 | kind: VirtualRouter 81 | metadata: 82 | name: prodcatalog-router 83 | namespace: prodcatalog-ns 84 | spec: 85 | listeners: 86 | - portMapping: 87 | port: 5000 88 | protocol: http 89 | routes: 90 | - name: prodcatalog-route 91 | httpRoute: 92 | match: 93 | prefix: / 94 | action: 95 | weightedTargets: 96 | - virtualNodeRef: 97 | name: prodcatalog 98 | weight: 100 99 | --- 100 | apiVersion: appmesh.k8s.aws/v1beta2 101 | kind: VirtualNode 102 | metadata: 103 | name: proddetail-v1 104 | namespace: prodcatalog-ns 105 | spec: 106 | podSelector: 107 | matchLabels: 108 | app: proddetail 109 | listeners: 110 | - portMapping: 111 | port: 3000 112 | protocol: http 113 | healthCheck: 114 | protocol: http 115 | path: '/ping' 116 | healthyThreshold: 2 117 | unhealthyThreshold: 2 118 | timeoutMillis: 2000 119 | intervalMillis: 5000 120 | serviceDiscovery: 121 | dns: 122 | hostname: proddetail.prodcatalog-ns.svc.cluster.local 123 | logging: 124 | accessLog: 125 | file: 126 | path: /dev/stdout 127 | --- 128 | apiVersion: appmesh.k8s.aws/v1beta2 129 | kind: VirtualNode 130 | metadata: 131 | name: frontend-node 132 | namespace: prodcatalog-ns 133 | spec: 134 | podSelector: 135 | matchLabels: 136 | app: frontend-node 137 | listeners: 138 | - portMapping: 139 | port: 9000 140 | protocol: http 141 | backends: 142 | - virtualService: 143 | virtualServiceRef: 144 | name: prodcatalog 145 | - virtualService: 146 | virtualServiceRef: 147 | name: proddetail 148 | serviceDiscovery: 149 | dns: 150 | hostname: frontend-node.prodcatalog-ns.svc.cluster.local 151 | logging: 152 | accessLog: 153 | file: 154 | path: /dev/stdout 155 | --- 156 | apiVersion: appmesh.k8s.aws/v1beta2 157 | kind: VirtualService 158 | metadata: 159 | name: frontend-node 160 | namespace: prodcatalog-ns 161 | spec: 162 | awsName: frontend-node.prodcatalog-ns.svc.cluster.local 163 | provider: 164 | virtualNode: 165 | virtualNodeRef: 166 | name: frontend-node 167 | --- -------------------------------------------------------------------------------- /workshop/helm-chart/templates/catalog_deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ .Values.catalog.name }} 5 | namespace: {{ .Values.catalog.namespace }} 6 | spec: 7 | replicas: {{ .Values.catalog.replicaCount }} 8 | selector: 9 | matchLabels: 10 | app: {{ .Values.catalog.name }} 11 | template: 12 | metadata: 13 | labels: 14 | app: {{ .Values.catalog.name }} 15 | spec: 16 | {{- if .Values.catalog.serviceAccountName.enabled }} 17 | serviceAccountName: {{ .Values.catalog.serviceAccountName.name }} 18 | {{- end }} 19 | containers: 20 | - name: {{ .Values.catalog.name }} 21 | image: "{{ .Values.catalog.image.repository }}:{{ .Values.catalog.image.tag }}" 22 | imagePullPolicy: {{ .Values.catalog.image.pullPolicy }} 23 | {{- if .Values.catalog.volume.enabled }} 24 | volumeMounts: 25 | - name: {{ .Values.catalog.volume.name }} 26 | mountPath: {{ .Values.catalog.volume.path }} 27 | {{- end }} 28 | ports: 29 | - name: http 30 | containerPort: {{ .Values.catalog.service.targetPort }} 31 | protocol: TCP 32 | {{- if .Values.catalog.livenessProbe.enabled }} 33 | livenessProbe: 34 | httpGet: 35 | path: {{ .Values.catalog.livenessProbe.path }} 36 | port: {{ .Values.catalog.livenessProbe.port }} 37 | initialDelaySeconds: {{ .Values.catalog.livenessProbe.initialDelaySeconds }} 38 | periodSeconds: {{ .Values.catalog.livenessProbe.periodSeconds }} 39 | timeoutSeconds: {{ .Values.catalog.livenessProbe.timeoutSeconds }} 40 | successThreshold: {{ .Values.catalog.livenessProbe.successThreshold }} 41 | failureThreshold: {{ .Values.catalog.livenessProbe.failureThreshold }} 42 | {{- end }} 43 | {{- if .Values.catalog.readinessProbe.enabled }} 44 | readinessProbe: 45 | httpGet: 46 | path: {{ .Values.catalog.readinessProbe.path }} 47 | port: {{ .Values.catalog.readinessProbe.port }} 48 | initialDelaySeconds: {{ .Values.catalog.readinessProbe.initialDelaySeconds }} 49 | periodSeconds: {{ .Values.catalog.readinessProbe.periodSeconds }} 50 | timeoutSeconds: {{ .Values.catalog.readinessProbe.timeoutSeconds }} 51 | successThreshold: {{ .Values.catalog.readinessProbe.successThreshold }} 52 | failureThreshold: {{ .Values.catalog.readinessProbe.failureThreshold }} 53 | {{- end }} 54 | {{- if .Values.catalog.startupProbe.enabled }} 55 | startupProbe: 56 | httpGet: 57 | path: {{ .Values.catalog.startupProbe.path }} 58 | port: {{ .Values.catalog.startupProbe.port }} 59 | initialDelaySeconds: {{ .Values.catalog.startupProbe.initialDelaySeconds }} 60 | periodSeconds: {{ .Values.catalog.startupProbe.periodSeconds }} 61 | timeoutSeconds: {{ .Values.catalog.startupProbe.timeoutSeconds }} 62 | successThreshold: {{ .Values.catalog.startupProbe.successThreshold }} 63 | failureThreshold: {{ .Values.catalog.startupProbe.failureThreshold }} 64 | {{- end }} 65 | env: 66 | {{- toYaml .Values.catalog.env | nindent 12 }} 67 | resources: 68 | {{- toYaml .Values.catalog.resources | nindent 12 }} 69 | {{- if .Values.catalog.volume.enabled }} 70 | volumes: 71 | - name: {{ .Values.catalog.volume.name }} 72 | {{ if .Values.catalog.volume.claim }} 73 | persistentVolumeClaim: 74 | claimName: {{ .Values.catalog.volume.claim }} 75 | {{- end }} 76 | {{ if .Values.catalog.volume.csi }} 77 | csi: 78 | driver: {{ .Values.catalog.volume.csi.driver }} 79 | readOnly: true 80 | volumeAttributes: 81 | secretProviderClass: {{ .Values.catalog.volume.csi.volumeAttributes.secretProviderClass }} 82 | {{- end }} 83 | {{- end }} 84 | {{- with .Values.catalog.nodeSelector }} 85 | nodeSelector: 86 | {{- toYaml . | nindent 8 }} 87 | {{- end }} 88 | {{- with .Values.catalog.affinity }} 89 | affinity: 90 | {{- toYaml . | nindent 8 }} 91 | {{- end }} 92 | {{- with .Values.catalog.tolerations }} 93 | tolerations: 94 | {{- toYaml . | nindent 8 }} 95 | {{- end }} 96 | -------------------------------------------------------------------------------- /workshop/aws_lbc_iam_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole" 8 | ], 9 | "Resource": "*", 10 | "Condition": { 11 | "StringEquals": { 12 | "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" 13 | } 14 | } 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Action": [ 19 | "ec2:DescribeAccountAttributes", 20 | "ec2:DescribeAddresses", 21 | "ec2:DescribeAvailabilityZones", 22 | "ec2:DescribeInternetGateways", 23 | "ec2:DescribeVpcs", 24 | "ec2:DescribeVpcPeeringConnections", 25 | "ec2:DescribeSubnets", 26 | "ec2:DescribeSecurityGroups", 27 | "ec2:DescribeInstances", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:DescribeTags", 30 | "ec2:GetCoipPoolUsage", 31 | "ec2:DescribeCoipPools", 32 | "elasticloadbalancing:DescribeLoadBalancers", 33 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 34 | "elasticloadbalancing:DescribeListeners", 35 | "elasticloadbalancing:DescribeListenerCertificates", 36 | "elasticloadbalancing:DescribeSSLPolicies", 37 | "elasticloadbalancing:DescribeRules", 38 | "elasticloadbalancing:DescribeTargetGroups", 39 | "elasticloadbalancing:DescribeTargetGroupAttributes", 40 | "elasticloadbalancing:DescribeTargetHealth", 41 | "elasticloadbalancing:DescribeTags", 42 | "elasticloadbalancing:DescribeListenerAttributes", 43 | "elasticloadbalancing:ModifyListenerAttributes" 44 | ], 45 | "Resource": "*" 46 | }, 47 | { 48 | "Effect": "Allow", 49 | "Action": [ 50 | "cognito-idp:DescribeUserPoolClient", 51 | "acm:ListCertificates", 52 | "acm:DescribeCertificate", 53 | "iam:ListServerCertificates", 54 | "iam:GetServerCertificate", 55 | "waf-regional:GetWebACL", 56 | "waf-regional:GetWebACLForResource", 57 | "waf-regional:AssociateWebACL", 58 | "waf-regional:DisassociateWebACL", 59 | "wafv2:GetWebACL", 60 | "wafv2:GetWebACLForResource", 61 | "wafv2:AssociateWebACL", 62 | "wafv2:DisassociateWebACL", 63 | "shield:GetSubscriptionState", 64 | "shield:DescribeProtection", 65 | "shield:CreateProtection", 66 | "shield:DeleteProtection" 67 | ], 68 | "Resource": "*" 69 | }, 70 | { 71 | "Effect": "Allow", 72 | "Action": [ 73 | "ec2:AuthorizeSecurityGroupIngress", 74 | "ec2:RevokeSecurityGroupIngress" 75 | ], 76 | "Resource": "*" 77 | }, 78 | { 79 | "Effect": "Allow", 80 | "Action": [ 81 | "ec2:CreateSecurityGroup" 82 | ], 83 | "Resource": "*" 84 | }, 85 | { 86 | "Effect": "Allow", 87 | "Action": [ 88 | "ec2:CreateTags" 89 | ], 90 | "Resource": "arn:aws:ec2:*:*:security-group/*", 91 | "Condition": { 92 | "StringEquals": { 93 | "ec2:CreateAction": "CreateSecurityGroup" 94 | }, 95 | "Null": { 96 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 97 | } 98 | } 99 | }, 100 | { 101 | "Effect": "Allow", 102 | "Action": [ 103 | "ec2:CreateTags", 104 | "ec2:DeleteTags" 105 | ], 106 | "Resource": "arn:aws:ec2:*:*:security-group/*", 107 | "Condition": { 108 | "Null": { 109 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 110 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 111 | } 112 | } 113 | }, 114 | { 115 | "Effect": "Allow", 116 | "Action": [ 117 | "ec2:AuthorizeSecurityGroupIngress", 118 | "ec2:RevokeSecurityGroupIngress", 119 | "ec2:DeleteSecurityGroup" 120 | ], 121 | "Resource": "*", 122 | "Condition": { 123 | "Null": { 124 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 125 | } 126 | } 127 | }, 128 | { 129 | "Effect": "Allow", 130 | "Action": "*", 131 | "Resource": "arn:aws:elasticloadbalancing:*:*:*" 132 | } 133 | ] 134 | } 135 | -------------------------------------------------------------------------------- /deployment/base_app.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: frontend-node 6 | namespace: prodcatalog-ns 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: frontend-node 12 | template: 13 | metadata: 14 | annotations: 15 | prometheus.io/scrape: 'true' 16 | prometheus.io/path: '/stats/prometheus' 17 | labels: 18 | app: frontend-node 19 | spec: 20 | serviceAccountName: prodcatalog-envoy-proxies 21 | containers: 22 | - name: frontend-node 23 | image: "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eks-app-mesh-demo/frontend_node:${APP_VERSION}" 24 | imagePullPolicy: Always 25 | livenessProbe: 26 | httpGet: 27 | path: /ping 28 | port: 9000 29 | initialDelaySeconds: 0 30 | periodSeconds: 10 31 | timeoutSeconds: 1 32 | failureThreshold: 3 33 | readinessProbe: 34 | httpGet: 35 | path: /ping 36 | port: 9000 37 | successThreshold: 3 38 | env: 39 | - name: BASE_URL 40 | value: "http://prodcatalog.prodcatalog-ns.svc.cluster.local:5000/products/" 41 | ports: 42 | - containerPort: 9000 43 | --- 44 | apiVersion: v1 45 | kind: Service 46 | metadata: 47 | #annotations: 48 | # This annotation is only required if you are creating an internal facing ELB. Remove this annotation to create public facing ELB. 49 | #service.beta.kubernetes.io/aws-load-balancer-internal: "true" 50 | name: frontend-node 51 | namespace: prodcatalog-ns 52 | labels: 53 | app: frontend-node 54 | spec: 55 | ports: 56 | - name: "http" 57 | port: 9000 58 | targetPort: 9000 59 | selector: 60 | app: frontend-node 61 | --- 62 | apiVersion: apps/v1 63 | kind: Deployment 64 | metadata: 65 | name: proddetail 66 | namespace: prodcatalog-ns 67 | spec: 68 | replicas: 1 69 | selector: 70 | matchLabels: 71 | app: proddetail 72 | template: 73 | metadata: 74 | labels: 75 | app: proddetail 76 | spec: 77 | serviceAccountName: prodcatalog-envoy-proxies 78 | containers: 79 | - name: proddetail 80 | image: "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eks-app-mesh-demo/catalog_detail:${APP_VERSION}" 81 | imagePullPolicy: Always 82 | livenessProbe: 83 | httpGet: 84 | path: /ping 85 | port: 3000 86 | initialDelaySeconds: 0 87 | periodSeconds: 10 88 | timeoutSeconds: 1 89 | failureThreshold: 3 90 | readinessProbe: 91 | httpGet: 92 | path: /ping 93 | port: 3000 94 | successThreshold: 3 95 | ports: 96 | - containerPort: 3000 97 | --- 98 | apiVersion: v1 99 | kind: Service 100 | metadata: 101 | #annotations: 102 | # This annotation is only required if you are creating an internal facing ELB. Remove this annotation to create public facing ELB. 103 | #service.beta.kubernetes.io/aws-load-balancer-internal: "true" 104 | name: proddetail 105 | namespace: prodcatalog-ns 106 | labels: 107 | app: proddetail 108 | spec: 109 | ports: 110 | - name: "http" 111 | port: 3000 112 | targetPort: 3000 113 | selector: 114 | app: proddetail 115 | --- 116 | apiVersion: apps/v1 117 | kind: Deployment 118 | metadata: 119 | name: prodcatalog 120 | namespace: prodcatalog-ns 121 | spec: 122 | replicas: 1 123 | selector: 124 | matchLabels: 125 | app: prodcatalog 126 | template: 127 | metadata: 128 | labels: 129 | app: prodcatalog 130 | annotations: 131 | sidecar.opentelemetry.io/inject: "true" 132 | spec: 133 | serviceAccountName: prodcatalog-envoy-proxies 134 | containers: 135 | - name: prodcatalog 136 | image: "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/eks-app-mesh-demo/product_catalog:${APP_VERSION}" 137 | imagePullPolicy: Always 138 | env: 139 | - name: AGG_APP_URL 140 | value: "http://proddetail.prodcatalog-ns.svc.cluster.local:3000/catalogDetail" 141 | - name: AWS_REGION 142 | value: "${AWS_REGION}" 143 | - name: OTEL_PROPAGATORS 144 | value: xray 145 | - name: OTEL_PYTHON_ID_GENERATOR 146 | value: xray 147 | - name: OTEL_RESOURCE_ATTRIBUTES 148 | value: service.namespace=eks-app-mesh-demo,service.name=prodcatalog 149 | livenessProbe: 150 | httpGet: 151 | path: /products/ping 152 | port: 5000 153 | initialDelaySeconds: 0 154 | periodSeconds: 10 155 | timeoutSeconds: 1 156 | failureThreshold: 3 157 | readinessProbe: 158 | httpGet: 159 | path: /products/ping 160 | port: 5000 161 | successThreshold: 3 162 | ports: 163 | - containerPort: 5000 164 | --- 165 | apiVersion: v1 166 | kind: Service 167 | metadata: 168 | name: prodcatalog 169 | namespace: prodcatalog-ns 170 | labels: 171 | app: prodcatalog 172 | spec: 173 | ports: 174 | - name: "http" 175 | port: 5000 176 | targetPort: 5000 177 | selector: 178 | app: prodcatalog 179 | --- -------------------------------------------------------------------------------- /workshop/apps/product_catalog/app.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import werkzeug 4 | werkzeug.cached_property = werkzeug.utils.cached_property 5 | from flask import Flask, request, url_for 6 | from flask_restplus import Api, Resource, fields 7 | from flask_cors import CORS 8 | import requests 9 | import os 10 | import logging 11 | from aws_xray_sdk.core import xray_recorder 12 | from aws_xray_sdk.ext.flask.middleware import XRayMiddleware 13 | xray_recorder.configure(context_missing='LOG_ERROR') 14 | from aws_xray_sdk.core import patch_all 15 | 16 | patch_all() 17 | 18 | flask_app = Flask(__name__) 19 | 20 | log_level = logging.INFO 21 | flask_app.logger.setLevel(log_level) 22 | # enable CORS 23 | CORS(flask_app, resources={r'/*': {'origins': '*'}}) 24 | 25 | #configure SDK code 26 | xray_recorder.configure(service='Product-Catalog') 27 | XRayMiddleware(flask_app, xray_recorder) 28 | 29 | AGG_APP_URL = os.environ.get("AGG_APP_URL") 30 | 31 | if AGG_APP_URL is None: 32 | AGG_APP_URL="http://localhost:3000/catalogDetail" 33 | 34 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 35 | 36 | # Fix of returning swagger.json on HTTP 37 | @property 38 | def specs_url(self): 39 | """ 40 | The Swagger specifications absolute url (ie. `swagger.json`) 41 | 42 | :rtype: str 43 | """ 44 | return url_for(self.endpoint('specs'), _external=False) 45 | 46 | Api.specs_url = specs_url 47 | app = Api(app = flask_app, 48 | version = "1.0", 49 | title = "Product Catalog", 50 | description = "Complete dictionary of Products available in the Product Catalog") 51 | 52 | name_space = app.namespace('products', description='Products from Product Catalog') 53 | 54 | model = app.model('Name Model', 55 | {'name': fields.String(required = True, 56 | description="Name of the Product", 57 | help="Product Name cannot be blank.")}) 58 | 59 | list_of_names = {} 60 | 61 | @name_space.route('/') 62 | class Products(Resource): 63 | """ 64 | Manipulations with products. 65 | """ 66 | def get(self): 67 | """ 68 | List of products. 69 | Returns a list of products 70 | """ 71 | try: 72 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 73 | response = requests.get(str(AGG_APP_URL)) 74 | content = response.json() 75 | flask_app.logger.info('Get-All Request succeeded') 76 | return { 77 | "products": list_of_names, 78 | "details" : content 79 | } 80 | except KeyError as e: 81 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 82 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 83 | except Exception as e: 84 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 85 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 86 | 87 | @name_space.route('/ping') 88 | class Ping(Resource): 89 | def get(self): 90 | return "healthy" 91 | 92 | @name_space.route("/") 93 | @name_space.param('id', 'Specify the ProductId') 94 | class MainClass(Resource): 95 | 96 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 97 | def get(self, id=None): 98 | try: 99 | name = list_of_names[id] 100 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 101 | response = requests.get(str(AGG_APP_URL)) 102 | content = response.json() 103 | flask_app.logger.info('Get Request succeeded ' + list_of_names[id]) 104 | return { 105 | "status": "Product Details retrieved", 106 | "name" : list_of_names[id], 107 | "details" : content['details'] 108 | } 109 | except KeyError as e: 110 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 111 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 112 | except Exception as e: 113 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 114 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 115 | 116 | 117 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 118 | @app.expect(model) 119 | def post(self, id): 120 | try: 121 | list_of_names[id] = request.json['name'] 122 | flask_app.logger.info('Post Request succeeded ' + list_of_names[id]) 123 | return { 124 | "status": "New Product added to Product Catalog", 125 | "name": list_of_names[id] 126 | } 127 | except KeyError as e: 128 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 129 | name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500") 130 | except Exception as e: 131 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 132 | name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400") 133 | 134 | if __name__ == '__main__': 135 | app.run(host="0.0.0.0", debug=True) -------------------------------------------------------------------------------- /apps/product_catalog/app.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import werkzeug 4 | werkzeug.cached_property = werkzeug.utils.cached_property 5 | from flask import Flask, request, url_for 6 | from flask_restplus import Api, Resource, fields 7 | from flask_cors import CORS 8 | import requests 9 | import os 10 | import logging 11 | # from aws_xray_sdk.core import xray_recorder 12 | # from aws_xray_sdk.ext.flask.middleware import XRayMiddleware 13 | # xray_recorder.configure(context_missing='LOG_ERROR') 14 | # from aws_xray_sdk.core import patch_all 15 | 16 | flask_app = Flask(__name__) 17 | flask_app.debug = True 18 | log_level = logging.INFO 19 | flask_app.logger.setLevel(log_level) 20 | # enable CORS 21 | CORS(flask_app, resources={r'/*': {'origins': '*'}}) 22 | 23 | #configure SDK code 24 | # xray_recorder.configure(service='Product-Catalog') 25 | # XRayMiddleware(flask_app, xray_recorder) 26 | 27 | AGG_APP_URL = os.environ.get("AGG_APP_URL") 28 | 29 | if AGG_APP_URL is None: 30 | AGG_APP_URL="http://localhost:3000/catalogDetail" 31 | 32 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 33 | 34 | # Fix of returning swagger.json on HTTP 35 | @property 36 | def specs_url(self): 37 | """ 38 | The Swagger specifications absolute url (ie. `swagger.json`) 39 | 40 | :rtype: str 41 | """ 42 | return url_for(self.endpoint('specs'), _external=False) 43 | 44 | Api.specs_url = specs_url 45 | app = Api(app = flask_app, 46 | version = "1.0", 47 | title = "Product Catalog", 48 | description = "Complete dictionary of Products available in the Product Catalog") 49 | 50 | name_space = app.namespace('products', description='Products from Product Catalog') 51 | 52 | model = app.model('Name Model', 53 | {'name': fields.String(required = True, 54 | description="Name of the Product", 55 | help="Product Name cannot be blank.")}) 56 | 57 | list_of_names = {} 58 | 59 | @name_space.route('/') 60 | class Products(Resource): 61 | """ 62 | Manipulations with products. 63 | """ 64 | def get(self): 65 | """ 66 | List of products. 67 | Returns a list of products 68 | """ 69 | try: 70 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 71 | response = requests.get(str(AGG_APP_URL)) 72 | content = response.json() 73 | flask_app.logger.info('Get-All Request succeeded') 74 | return { 75 | "products": list_of_names, 76 | "details" : content 77 | } 78 | except KeyError as e: 79 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 80 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 81 | except Exception as e: 82 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 83 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 84 | 85 | @name_space.route('/ping') 86 | class Ping(Resource): 87 | def get(self): 88 | return "healthy" 89 | 90 | @name_space.route("/") 91 | @name_space.param('id', 'Specify the ProductId') 92 | class MainClass(Resource): 93 | 94 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 95 | def get(self, id=None): 96 | try: 97 | name = list_of_names[id] 98 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 99 | response = requests.get(str(AGG_APP_URL)) 100 | content = response.json() 101 | flask_app.logger.info('Get Request succeeded ' + list_of_names[id]) 102 | return { 103 | "status": "Product Details retrieved", 104 | "name" : list_of_names[id], 105 | "details" : content['details'] 106 | } 107 | except KeyError as e: 108 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 109 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 110 | except Exception as e: 111 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 112 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 113 | 114 | 115 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 116 | @app.expect(model) 117 | def post(self, id): 118 | try: 119 | list_of_names[id] = request.json['name'] 120 | flask_app.logger.info('Post Request succeeded ' + list_of_names[id]) 121 | return { 122 | "status": "New Product added to Product Catalog", 123 | "name": list_of_names[id] 124 | } 125 | except KeyError as e: 126 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 127 | name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500") 128 | except Exception as e: 129 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 130 | name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400") 131 | 132 | if __name__ == '__main__': 133 | app.run(host="0.0.0.0", debug=True, use_reloader=False) -------------------------------------------------------------------------------- /workshop/apps/product_catalog/app_efs.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import werkzeug 4 | import glob 5 | werkzeug.cached_property = werkzeug.utils.cached_property 6 | from flask import Flask, request, url_for 7 | from flask_restplus import Api, Resource, fields 8 | from flask_cors import CORS 9 | import requests 10 | import os 11 | import logging 12 | from aws_xray_sdk.core import xray_recorder 13 | from aws_xray_sdk.ext.flask.middleware import XRayMiddleware 14 | xray_recorder.configure(context_missing='LOG_ERROR') 15 | from aws_xray_sdk.core import patch_all 16 | 17 | patch_all() 18 | 19 | flask_app = Flask(__name__) 20 | 21 | log_level = logging.INFO 22 | flask_app.logger.setLevel(log_level) 23 | # enable CORS 24 | CORS(flask_app, resources={r'/*': {'origins': '*'}}) 25 | 26 | #configure SDK code 27 | xray_recorder.configure(service='Product-Catalog') 28 | XRayMiddleware(flask_app, xray_recorder) 29 | 30 | AGG_APP_URL = os.environ.get("AGG_APP_URL") 31 | 32 | if AGG_APP_URL is None: 33 | AGG_APP_URL="http://localhost:3000/catalogDetail" 34 | 35 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 36 | 37 | filepath = os.path.join('/products', 'products.txt') 38 | 39 | # Fix of returning swagger.json on HTTP 40 | @property 41 | def specs_url(self): 42 | """ 43 | The Swagger specifications absolute url (ie. `swagger.json`) 44 | 45 | :rtype: str 46 | """ 47 | return url_for(self.endpoint('specs'), _external=False) 48 | 49 | Api.specs_url = specs_url 50 | app = Api(app = flask_app, 51 | version = "1.0", 52 | title = "Product Catalog", 53 | description = "Complete dictionary of Products available in the Product Catalog") 54 | 55 | name_space = app.namespace('products', description='Products from Product Catalog') 56 | 57 | model = app.model('Name Model', 58 | {'name': fields.String(required = True, 59 | description="Name of the Product", 60 | help="Product Name cannot be blank.")}) 61 | 62 | list_of_names = {} 63 | 64 | def read_file(): 65 | flask_app.logger.info(filepath) 66 | if not os.path.exists(filepath): 67 | open(filepath, 'w').close() 68 | else: 69 | with open(filepath, "r") as f: 70 | for line in f: 71 | (key, val) = line.split() 72 | list_of_names[int(key)] = val 73 | 74 | @name_space.route('/') 75 | class Products(Resource): 76 | """ 77 | Manipulations with products. 78 | """ 79 | def get(self): 80 | """ 81 | List of products. 82 | Returns a list of products 83 | """ 84 | try: 85 | read_file() 86 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 87 | response = requests.get(str(AGG_APP_URL)) 88 | content = response.json() 89 | flask_app.logger.info('Get-All Request succeeded') 90 | return { 91 | "products": list_of_names, 92 | "details" : content 93 | } 94 | except KeyError as e: 95 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 96 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 97 | except Exception as e: 98 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 99 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 100 | 101 | @name_space.route('/ping') 102 | class Ping(Resource): 103 | def get(self): 104 | return "healthy" 105 | 106 | @name_space.route("/") 107 | @name_space.param('id', 'Specify the ProductId') 108 | class MainClass(Resource): 109 | 110 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 111 | def get(self, id=None): 112 | try: 113 | name = list_of_names[id] 114 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 115 | response = requests.get(str(AGG_APP_URL)) 116 | content = response.json() 117 | flask_app.logger.info('Get Request succeeded ' + list_of_names[id]) 118 | return { 119 | "status": "Product Details retrieved", 120 | "name" : list_of_names[id], 121 | "details" : content['details'] 122 | } 123 | except KeyError as e: 124 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 125 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 126 | except Exception as e: 127 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 128 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 129 | 130 | 131 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 132 | @app.expect(model) 133 | def post(self, id): 134 | try: 135 | f = open(filepath, "a") 136 | flask_app.logger.info(id, request.json['name']) 137 | f.write('{} {}'.format(id, request.json['name'])) 138 | f.write('\n') 139 | list_of_names[id] = request.json['name'] 140 | flask_app.logger.info('Post Request succeeded ' + list_of_names[id]) 141 | return { 142 | "status": "New Product added to Product Catalog", 143 | "name": list_of_names[id] 144 | } 145 | except KeyError as e: 146 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 147 | name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500") 148 | except Exception as e: 149 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 150 | name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400") 151 | 152 | if __name__ == '__main__': 153 | app.run(host="0.0.0.0", debug=True) -------------------------------------------------------------------------------- /workshop/mysql-statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | namespace: workshop 5 | name: mysql 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: mysql 10 | serviceName: mysql 11 | replicas: 2 12 | template: 13 | metadata: 14 | labels: 15 | app: mysql 16 | spec: 17 | initContainers: 18 | - name: init-mysql 19 | image: mysql:5.7.37 20 | command: 21 | - bash 22 | - "-c" 23 | - | 24 | set -ex 25 | # Generate mysql server-id from pod ordinal index. 26 | hostname=$(hostname) || hostname=$(cat /etc/hostname) 27 | [[ $hostname =~ -([0-9]+)$ ]] || exit 1 28 | ordinal=${BASH_REMATCH[1]} 29 | echo [mysqld] > /mnt/conf.d/server-id.cnf 30 | # Add an offset to avoid reserved server-id=0 value. 31 | echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf 32 | # Copy appropriate conf.d files from config-map to emptyDir. 33 | if [[ $ordinal -eq 0 ]]; then 34 | cp /mnt/config-map/leader.cnf /mnt/conf.d/ 35 | else 36 | cp /mnt/config-map/follower.cnf /mnt/conf.d/ 37 | fi 38 | volumeMounts: 39 | - name: conf 40 | mountPath: /mnt/conf.d 41 | - name: config-map 42 | mountPath: /mnt/config-map 43 | - name: clone-mysql 44 | image: gcr.io/google-samples/xtrabackup:1.0 45 | command: 46 | - bash 47 | - "-c" 48 | - | 49 | set -ex 50 | # Skip the clone if data already exists. 51 | [[ -d /var/lib/mysql/mysql ]] && exit 0 52 | # Skip the clone on leader (ordinal index 0). 53 | [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 54 | ordinal=${BASH_REMATCH[1]} 55 | [[ $ordinal -eq 0 ]] && exit 0 56 | # Clone data from previous peer. 57 | ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql 58 | # Prepare the backup. 59 | xtrabackup --prepare --target-dir=/var/lib/mysql 60 | volumeMounts: 61 | - name: data 62 | mountPath: /var/lib/mysql 63 | subPath: mysql 64 | - name: conf 65 | mountPath: /etc/mysql/conf.d 66 | containers: 67 | - name: mysql 68 | image: mysql:5.7.37 69 | env: 70 | - name: MYSQL_ALLOW_EMPTY_PASSWORD 71 | value: "1" 72 | ports: 73 | - name: mysql 74 | containerPort: 3306 75 | volumeMounts: 76 | - name: data 77 | mountPath: /var/lib/mysql 78 | subPath: mysql 79 | - name: conf 80 | mountPath: /etc/mysql/conf.d 81 | resources: 82 | requests: 83 | cpu: 500m 84 | memory: 1Gi 85 | livenessProbe: 86 | exec: 87 | command: ["mysqladmin", "ping"] 88 | initialDelaySeconds: 30 89 | periodSeconds: 10 90 | timeoutSeconds: 5 91 | readinessProbe: 92 | exec: 93 | # Check we can execute queries over TCP (skip-networking is off). 94 | command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"] 95 | initialDelaySeconds: 5 96 | periodSeconds: 2 97 | timeoutSeconds: 1 98 | - name: xtrabackup 99 | image: gcr.io/google-samples/xtrabackup:1.0 100 | ports: 101 | - name: xtrabackup 102 | containerPort: 3307 103 | command: 104 | - bash 105 | - "-c" 106 | - | 107 | set -ex 108 | cd /var/lib/mysql 109 | 110 | # Determine binlog position of cloned data, if any. 111 | if [[ -f xtrabackup_slave_info ]]; then 112 | # XtraBackup already generated a partial "CHANGE MASTER TO" query 113 | # because we're cloning from an existing follower. 114 | mv xtrabackup_slave_info change_master_to.sql.in 115 | # Ignore xtrabackup_binlog_info in this case (it's useless). 116 | rm -f xtrabackup_binlog_info 117 | elif [[ -f xtrabackup_binlog_info ]]; then 118 | # We're cloning directly from leader. Parse binlog position. 119 | [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1 120 | rm xtrabackup_binlog_info 121 | echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\ 122 | MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in 123 | fi 124 | 125 | # Check if we need to complete a clone by starting replication. 126 | if [[ -f change_master_to.sql.in ]]; then 127 | echo "Waiting for mysqld to be ready (accepting connections)" 128 | until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done 129 | 130 | echo "Initializing replication from clone position" 131 | # In case of container restart, attempt this at-most-once. 132 | mv change_master_to.sql.in change_master_to.sql.orig 133 | mysql -h 127.0.0.1 < /mnt/conf.d/server-id.cnf 38 | # Add an offset to avoid reserved server-id=0 value. 39 | echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf 40 | # Copy appropriate conf.d files from config-map to emptyDir. 41 | if [[ $ordinal -eq 0 ]]; then 42 | cp /mnt/config-map/source.cnf /mnt/conf.d/ 43 | else 44 | cp /mnt/config-map/replica.cnf /mnt/conf.d/ 45 | fi 46 | volumeMounts: 47 | - name: conf 48 | mountPath: /mnt/conf.d 49 | - name: config-map 50 | mountPath: /mnt/config-map 51 | - name: clone-mysql 52 | image: gcr.io/google-samples/xtrabackup:1.0 53 | command: 54 | - bash 55 | - "-c" 56 | - | 57 | set -ex 58 | # Skip the clone if data already exists. 59 | [[ -d /var/lib/mysql/mysql ]] && exit 0 60 | # Skip the clone on source (ordinal index 0). 61 | [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 62 | ordinal=${BASH_REMATCH[1]} 63 | [[ $ordinal -eq 0 ]] && exit 0 64 | # Clone data from previous peer. 65 | ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql 66 | # Prepare the backup. 67 | xtrabackup --prepare --target-dir=/var/lib/mysql 68 | volumeMounts: 69 | - name: data 70 | mountPath: /var/lib/mysql 71 | subPath: mysql 72 | - name: conf 73 | mountPath: /etc/mysql/conf.d 74 | containers: 75 | - name: mysql 76 | image: mysql:5.7.37 77 | env: 78 | - name: MYSQL_ROOT_PASSWORD 79 | valueFrom: 80 | secretKeyRef: 81 | name: mysql-secret 82 | key: password 83 | ports: 84 | - name: mysql 85 | containerPort: 3306 86 | volumeMounts: 87 | - name: data 88 | mountPath: /var/lib/mysql 89 | subPath: mysql 90 | - name: conf 91 | mountPath: /etc/mysql/conf.d 92 | resources: 93 | requests: 94 | cpu: 500m 95 | memory: 1Gi 96 | livenessProbe: 97 | exec: 98 | command: 99 | - bash 100 | - "-c" 101 | - | 102 | set -ex 103 | mysqladmin -uroot -p$MYSQL_ROOT_PASSWORD ping &> /dev/null 104 | initialDelaySeconds: 30 105 | periodSeconds: 10 106 | timeoutSeconds: 5 107 | readinessProbe: 108 | exec: 109 | # Check we can execute queries over TCP (skip-networking is off). 110 | command: 111 | - bash 112 | - "-c" 113 | - | 114 | set -ex 115 | mysql -h 127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD -e "SELECT 1" &> /dev/null 116 | initialDelaySeconds: 5 117 | periodSeconds: 2 118 | timeoutSeconds: 1 119 | - name: xtrabackup 120 | image: gcr.io/google-samples/xtrabackup:1.0 121 | env: 122 | - name: MYSQL_ROOT_PASSWORD 123 | valueFrom: 124 | secretKeyRef: 125 | name: mysql-secret 126 | key: password 127 | ports: 128 | - name: xtrabackup 129 | containerPort: 3307 130 | command: 131 | - bash 132 | - "-c" 133 | - | 134 | set -ex 135 | cd /var/lib/mysql 136 | 137 | # Determine binlog position of cloned data, if any. 138 | if [[ -f xtrabackup_slave_info ]]; then 139 | # XtraBackup already generated a partial "CHANGE MASTER TO" query 140 | # because we're cloning from an existing slave. 141 | mv xtrabackup_slave_info change_master_to.sql.in 142 | # Ignore xtrabackup_binlog_info in this case (it's useless). 143 | rm -f xtrabackup_binlog_info 144 | elif [[ -f xtrabackup_binlog_info ]]; then 145 | # We're cloning directly from master. Parse binlog position. 146 | [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1 147 | rm xtrabackup_binlog_info 148 | echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\ 149 | MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in 150 | fi 151 | 152 | # Check if we need to complete a clone by starting replication. 153 | if [[ -f change_master_to.sql.in ]]; then 154 | echo "Waiting for mysqld to be ready (accepting connections)" 155 | until mysql -h 127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD -e "SELECT 1"; do sleep 1; done 156 | 157 | echo "Initializing replication from clone position" 158 | # In case of container restart, attempt this at-most-once. 159 | mv change_master_to.sql.in change_master_to.sql.orig 160 | mysql -h 127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD <") 129 | @name_space.param('id', 'Specify the ProductId') 130 | class MainClass(Resource): 131 | 132 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 133 | def get(self, id=None): 134 | try: 135 | name = list_of_names[id] 136 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 137 | response = requests.get(str(AGG_APP_URL)) 138 | content = response.json() 139 | flask_app.logger.info('Get Request succeeded ' + list_of_names[id]) 140 | return { 141 | "status": "Product Details retrieved", 142 | "name" : list_of_names[id], 143 | "details" : content['details'] 144 | } 145 | except KeyError as e: 146 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 147 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 148 | except Exception as e: 149 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 150 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 151 | 152 | 153 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 154 | @app.expect(model) 155 | def post(self, id): 156 | try: 157 | connection = create_connection() 158 | 159 | cursor = connection.cursor() 160 | sql = ("INSERT INTO product (prodId, prodName) VALUES (%s, %s)") 161 | data = (id, request.json['name']) 162 | cursor.execute(sql, data) 163 | connection.commit() 164 | cursor.close() 165 | connection.close() 166 | flask_app.logger.info('Post Request succeeded ' + request.json['name']) 167 | return { 168 | "status": "New Product added to Product Catalog", 169 | "name": request.json['name'] 170 | } 171 | except DatabaseError as e: 172 | err_code = e.args[0] 173 | if err_code == 2003: 174 | print('bad connection string') 175 | else: 176 | raise 177 | except KeyError as e: 178 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 179 | name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500") 180 | except Exception as e: 181 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 182 | name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400") 183 | 184 | 185 | if __name__ == '__main__': 186 | app.run(host="0.0.0.0", debug=True) -------------------------------------------------------------------------------- /workshop/apps/product_catalog/app_ebs.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import werkzeug 4 | werkzeug.cached_property = werkzeug.utils.cached_property 5 | from flask import Flask, request, url_for 6 | from flask_restplus import Api, Resource, fields 7 | from flask_cors import CORS 8 | import requests 9 | import os 10 | import logging 11 | # from aws_xray_sdk.core import xray_recorder 12 | # from aws_xray_sdk.ext.flask.middleware import XRayMiddleware 13 | # xray_recorder.configure(context_missing='LOG_ERROR') 14 | # from aws_xray_sdk.core import patch_all 15 | import pymysql 16 | from pymysql.err import DatabaseError 17 | import json 18 | 19 | #patch_all() 20 | 21 | flask_app = Flask(__name__) 22 | 23 | log_level = logging.INFO 24 | flask_app.logger.setLevel(log_level) 25 | # enable CORS 26 | CORS(flask_app, resources={r'/*': {'origins': '*'}}) 27 | 28 | #configure SDK code 29 | # xray_recorder.configure(service='Product-Catalog') 30 | # XRayMiddleware(flask_app, xray_recorder) 31 | 32 | AGG_APP_URL = os.environ.get("AGG_APP_URL") 33 | DB_APP_URL = os.environ.get("DATABASE_SERVICE_URL") 34 | 35 | list_of_names = "" 36 | 37 | if AGG_APP_URL is None: 38 | AGG_APP_URL="http://localhost:3000/catalogDetail" 39 | 40 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 41 | flask_app.logger.info('DB_APP_URL is ' + str(DB_APP_URL)) 42 | 43 | # Connect to the database 44 | def create_connection(): 45 | return pymysql.connect(host=DB_APP_URL, 46 | user='root', 47 | password='', 48 | db='dev', 49 | charset='utf8mb4', 50 | cursorclass=pymysql.cursors.DictCursor 51 | ) 52 | 53 | # Fix of returning swagger.json on HTTP 54 | @property 55 | def specs_url(self): 56 | """ 57 | The Swagger specifications absolute url (ie. `swagger.json`) 58 | :rtype: str 59 | """ 60 | return url_for(self.endpoint('specs'), _external=False) 61 | 62 | Api.specs_url = specs_url 63 | app = Api(app = flask_app, 64 | version = "1.0", 65 | title = "Product Catalog", 66 | description = "Complete dictionary of Products available in the Product Catalog") 67 | 68 | name_space = app.namespace('products', description='Products from Product Catalog') 69 | 70 | model = app.model('Name Model', 71 | {'name': fields.String(required = True, 72 | description="Name of the Product", 73 | help="Product Name cannot be blank.")}) 74 | 75 | class create_dict(dict): 76 | 77 | # __init__ function 78 | def __init__(self): 79 | self = dict() 80 | 81 | # Function to add key:value 82 | def add(self, key, value): 83 | self[key] = value 84 | 85 | @name_space.route('/') 86 | class Products(Resource): 87 | """ 88 | Manipulations with products. 89 | """ 90 | def get(self): 91 | """ 92 | List of products. 93 | Returns a list of products 94 | """ 95 | try: 96 | flask_app.logger.info('Inside Get request') 97 | response = requests.get(str(AGG_APP_URL)) 98 | detailsContent = response.json() 99 | connection = create_connection() 100 | cursor = connection.cursor() 101 | cursor.execute("SELECT `prodId`, `prodName` FROM `product`") 102 | 103 | payload = [] 104 | content = {} 105 | #mydict = create_dict() 106 | list_of_names = {} 107 | for row in cursor.fetchall(): 108 | prodId = str(row["prodId"]) 109 | prodName = str(row["prodName"]) 110 | list_of_names[prodId] = prodName 111 | #content = {row['prodId']:row['prodName']} 112 | #payload.append(content) 113 | flask_app.logger.info(list_of_names) 114 | #prod_json = json.dumps(mydict, indent=2, sort_keys=True) 115 | #flask_app.logger.info(mydict) 116 | return { 117 | "products": list_of_names, 118 | "details" : detailsContent 119 | } 120 | cursor.close() 121 | connection.close() 122 | except KeyError as e: 123 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 124 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 125 | except Exception as e: 126 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 127 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 128 | 129 | @name_space.route('/ping') 130 | class Ping(Resource): 131 | def get(self): 132 | return "healthy" 133 | 134 | @name_space.route("/") 135 | @name_space.param('id', 'Specify the ProductId') 136 | class MainClass(Resource): 137 | 138 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 139 | def get(self, id=None): 140 | try: 141 | name = list_of_names[id] 142 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 143 | response = requests.get(str(AGG_APP_URL)) 144 | content = response.json() 145 | flask_app.logger.info('Get Request succeeded ' + list_of_names[id]) 146 | return { 147 | "status": "Product Details retrieved", 148 | "name" : list_of_names[id], 149 | "details" : content['details'] 150 | } 151 | except KeyError as e: 152 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 153 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 154 | except Exception as e: 155 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 156 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 157 | 158 | 159 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 160 | @app.expect(model) 161 | def post(self, id): 162 | try: 163 | connection = create_connection() 164 | 165 | cursor = connection.cursor() 166 | sql = ("INSERT INTO product (prodId, prodName) VALUES (%s, %s)") 167 | data = (id, request.json['name']) 168 | cursor.execute(sql, data) 169 | connection.commit() 170 | cursor.close() 171 | connection.close() 172 | flask_app.logger.info('Post Request succeeded ' + request.json['name']) 173 | return { 174 | "status": "New Product added to Product Catalog", 175 | "name": request.json['name'] 176 | } 177 | except DatabaseError as e: 178 | err_code = e.args[0] 179 | if err_code == 2003: 180 | print('bad connection string') 181 | else: 182 | raise 183 | except KeyError as e: 184 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 185 | name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500") 186 | except Exception as e: 187 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 188 | name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400") 189 | 190 | 191 | if __name__ == '__main__': 192 | app.run(host="0.0.0.0", debug=True) -------------------------------------------------------------------------------- /workshop/apps/product_catalog/app_aurora.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | import werkzeug 4 | werkzeug.cached_property = werkzeug.utils.cached_property 5 | from flask import Flask, request, url_for 6 | from flask_restplus import Api, Resource, fields 7 | from flask_cors import CORS 8 | import requests 9 | import os 10 | import logging 11 | import pymysql 12 | from pymysql.err import DatabaseError 13 | import json 14 | import sys 15 | import boto3 16 | 17 | flask_app = Flask(__name__) 18 | 19 | log_level = logging.INFO 20 | flask_app.logger.setLevel(log_level) 21 | # enable CORS 22 | CORS(flask_app, resources={r'/*': {'origins': '*'}}) 23 | 24 | 25 | AGG_APP_URL = os.environ.get("AGG_APP_URL") 26 | DB_APP_URL = os.environ.get("DATABASE_SERVICE_URL") 27 | DB_USER_NAME = os.environ.get("DATABASE_USER_NAME") 28 | #DB_TOKEN = os.environ.get("DATABASE_TOKEN") 29 | DB_NAME = os.environ.get("DB_NAME") 30 | DB_PORT = os.environ.get("DB_PORT") 31 | DB_REGION = os.environ.get("DB_REGION") 32 | 33 | list_of_names = "" 34 | 35 | if AGG_APP_URL is None: 36 | AGG_APP_URL="http://localhost:3000/catalogDetail" 37 | 38 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 39 | flask_app.logger.info('DB_APP_URL is ' + str(DB_APP_URL)) 40 | flask_app.logger.info('DB_USER_NAME is ' + str(DB_USER_NAME)) 41 | #flask_app.logger.info('DB_TOKEN is ' + str(DB_TOKEN)) 42 | flask_app.logger.info('DB_NAME is ' + str(DB_NAME)) 43 | flask_app.logger.info('DB_PORT is ' + str(DB_PORT)) 44 | flask_app.logger.info('DB_REGION is ' + str(DB_REGION)) 45 | 46 | os.environ['LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN'] = '1' 47 | 48 | SSL_CA='rds-combined-ca-bundle.pem' 49 | 50 | flask_app.logger.info(SSL_CA) 51 | 52 | #gets the credentials from .aws/credentials 53 | client = boto3.client('rds') 54 | 55 | # Connect to the database 56 | def create_connection(): 57 | # Construct SSL 58 | ssl = {'ca': 'rds-combined-ca-bundle.pem'} 59 | token = client.generate_db_auth_token(DBHostname=DB_APP_URL, Port=3306, DBUsername=DB_USER_NAME, Region=DB_REGION) 60 | flask_app.logger.info('token ' + token) 61 | return pymysql.connect(host=DB_APP_URL, 62 | user=DB_USER_NAME, 63 | password=token, 64 | port=3306, 65 | db=DB_NAME, 66 | ssl=ssl, 67 | charset='utf8mb4', 68 | cursorclass=pymysql.cursors.DictCursor 69 | ) 70 | 71 | # Fix of returning swagger.json on HTTP 72 | @property 73 | def specs_url(self): 74 | """ 75 | The Swagger specifications absolute url (ie. `swagger.json`) 76 | :rtype: str 77 | """ 78 | return url_for(self.endpoint('specs'), _external=False) 79 | 80 | Api.specs_url = specs_url 81 | app = Api(app = flask_app, 82 | version = "1.0", 83 | title = "Product Catalog", 84 | description = "Complete dictionary of Products available in the Product Catalog") 85 | 86 | name_space = app.namespace('products', description='Products from Product Catalog') 87 | 88 | model = app.model('Name Model', 89 | {'name': fields.String(required = True, 90 | description="Name of the Product", 91 | help="Product Name cannot be blank.")}) 92 | 93 | class create_dict(dict): 94 | 95 | # __init__ function 96 | def __init__(self): 97 | self = dict() 98 | 99 | # Function to add key:value 100 | def add(self, key, value): 101 | self[key] = value 102 | 103 | @name_space.route('/') 104 | class Products(Resource): 105 | """ 106 | Manipulations with products. 107 | """ 108 | def get(self): 109 | """ 110 | List of products. 111 | Returns a list of products 112 | """ 113 | # try: 114 | flask_app.logger.info('Inside Get request') 115 | response = requests.get(str(AGG_APP_URL)) 116 | detailsContent = response.json() 117 | connection = create_connection() 118 | cursor = connection.cursor() 119 | cursor.execute("SELECT `prodId`, `prodName` FROM `product`") 120 | 121 | payload = [] 122 | content = {} 123 | #mydict = create_dict() 124 | list_of_names = {} 125 | for row in cursor.fetchall(): 126 | prodId = str(row["prodId"]) 127 | prodName = str(row["prodName"]) 128 | list_of_names[prodId] = prodName 129 | #content = {row['prodId']:row['prodName']} 130 | #payload.append(content) 131 | flask_app.logger.info(list_of_names) 132 | cursor.close() 133 | connection.close() 134 | #prod_json = json.dumps(mydict, indent=2, sort_keys=True) 135 | #flask_app.logger.info(mydict) 136 | return { 137 | "products": list_of_names, 138 | "details" : detailsContent 139 | } 140 | # except DatabaseError as e: 141 | # err_code = e.args[0] 142 | # if err_code == 2003: 143 | # print('bad connection string') 144 | # else: 145 | # raise 146 | # except KeyError as e: 147 | # flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 148 | # name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 149 | # except Exception as e: 150 | # flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 151 | # name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 152 | 153 | @name_space.route('/ping') 154 | class Ping(Resource): 155 | def get(self): 156 | return "healthy" 157 | 158 | @name_space.route("/") 159 | @name_space.param('id', 'Specify the ProductId') 160 | class MainClass(Resource): 161 | 162 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 163 | def get(self, id=None): 164 | try: 165 | name = list_of_names[id] 166 | flask_app.logger.info('AGG_APP_URL is ' + str(AGG_APP_URL)) 167 | response = requests.get(str(AGG_APP_URL)) 168 | content = response.json() 169 | flask_app.logger.info('Get Request succeeded ' + list_of_names[id]) 170 | return { 171 | "status": "Product Details retrieved", 172 | "name" : list_of_names[id], 173 | "details" : content['details'] 174 | } 175 | except KeyError as e: 176 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 177 | name_space.abort(500, e.__doc__, status = "Could not retrieve information", statusCode = "500") 178 | except Exception as e: 179 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 180 | name_space.abort(400, e.__doc__, status = "Could not retrieve information", statusCode = "400") 181 | 182 | 183 | @app.doc(responses={ 200: 'OK', 400: 'Invalid Argument', 500: 'Mapping Key Error' }) 184 | @app.expect(model) 185 | def post(self, id): 186 | try: 187 | connection = create_connection() 188 | 189 | cursor = connection.cursor() 190 | sql = ("INSERT INTO product (prodId, prodName) VALUES (%s, %s)") 191 | data = (id, request.json['name']) 192 | cursor.execute(sql, data) 193 | connection.commit() 194 | cursor.close() 195 | connection.close() 196 | flask_app.logger.info('Post Request succeeded ' + request.json['name']) 197 | return { 198 | "status": "New Product added to Product Catalog", 199 | "name": request.json['name'] 200 | } 201 | except DatabaseError as e: 202 | err_code = e.args[0] 203 | if err_code == 2003: 204 | print('bad connection string') 205 | else: 206 | raise 207 | except KeyError as e: 208 | flask_app.logger.error('Error 500 Could not retrieve information ' + e.__doc__ ) 209 | name_space.abort(500, e.__doc__, status = "Could not save information", statusCode = "500") 210 | except Exception as e: 211 | flask_app.logger.error('Error 400 Could not retrieve information ' + e.__doc__ ) 212 | name_space.abort(400, e.__doc__, status = "Could not save information", statusCode = "400") 213 | 214 | 215 | if __name__ == '__main__': 216 | app.run(host="0.0.0.0", debug=True) -------------------------------------------------------------------------------- /workshop/cluster-autoscaler.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: cluster-autoscaler/templates/pdb.yaml 3 | apiVersion: policy/v1 4 | kind: PodDisruptionBudget 5 | metadata: 6 | labels: 7 | app.kubernetes.io/instance: "cluster-autoscaler" 8 | app.kubernetes.io/name: "aws-cluster-autoscaler" 9 | app.kubernetes.io/managed-by: "Helm" 10 | helm.sh/chart: "cluster-autoscaler-9.46.0" 11 | name: cluster-autoscaler 12 | namespace: kube-system 13 | spec: 14 | selector: 15 | matchLabels: 16 | app.kubernetes.io/instance: "cluster-autoscaler" 17 | app.kubernetes.io/name: "aws-cluster-autoscaler" 18 | 19 | maxUnavailable: 1 20 | --- 21 | # Source: cluster-autoscaler/templates/serviceaccount.yaml 22 | #apiVersion: v1 23 | #kind: ServiceAccount 24 | #metadata: 25 | # labels: 26 | # app.kubernetes.io/instance: "cluster-autoscaler" 27 | # app.kubernetes.io/name: "aws-cluster-autoscaler" 28 | # app.kubernetes.io/managed-by: "Helm" 29 | # helm.sh/chart: "cluster-autoscaler-9.46.0" 30 | # name:cluster-autoscaler 31 | # namespace: kube-system 32 | #automountServiceAccountToken: true 33 | #--- 34 | # Source: cluster-autoscaler/templates/clusterrole.yaml 35 | apiVersion: rbac.authorization.k8s.io/v1 36 | kind: ClusterRole 37 | metadata: 38 | labels: 39 | app.kubernetes.io/instance: "cluster-autoscaler" 40 | app.kubernetes.io/name: "aws-cluster-autoscaler" 41 | app.kubernetes.io/managed-by: "Helm" 42 | helm.sh/chart: "cluster-autoscaler-9.46.0" 43 | name: cluster-autoscaler 44 | rules: 45 | - apiGroups: 46 | - "" 47 | resources: 48 | - events 49 | - endpoints 50 | verbs: 51 | - create 52 | - patch 53 | - apiGroups: 54 | - "" 55 | resources: 56 | - pods/eviction 57 | verbs: 58 | - create 59 | - apiGroups: 60 | - "" 61 | resources: 62 | - pods/status 63 | verbs: 64 | - update 65 | - apiGroups: 66 | - "" 67 | resources: 68 | - endpoints 69 | resourceNames: 70 | - cluster-autoscaler 71 | verbs: 72 | - get 73 | - update 74 | - apiGroups: 75 | - "" 76 | resources: 77 | - nodes 78 | verbs: 79 | - watch 80 | - list 81 | - create 82 | - delete 83 | - get 84 | - update 85 | - apiGroups: 86 | - "" 87 | resources: 88 | - namespaces 89 | - pods 90 | - services 91 | - replicationcontrollers 92 | - persistentvolumeclaims 93 | - persistentvolumes 94 | verbs: 95 | - watch 96 | - list 97 | - get 98 | - apiGroups: 99 | - batch 100 | resources: 101 | - jobs 102 | - cronjobs 103 | verbs: 104 | - watch 105 | - list 106 | - get 107 | - apiGroups: 108 | - batch 109 | - extensions 110 | resources: 111 | - jobs 112 | verbs: 113 | - get 114 | - list 115 | - patch 116 | - watch 117 | - apiGroups: 118 | - extensions 119 | resources: 120 | - replicasets 121 | - daemonsets 122 | verbs: 123 | - watch 124 | - list 125 | - get 126 | - apiGroups: 127 | - policy 128 | resources: 129 | - poddisruptionbudgets 130 | verbs: 131 | - watch 132 | - list 133 | - apiGroups: 134 | - apps 135 | resources: 136 | - daemonsets 137 | - replicasets 138 | - statefulsets 139 | verbs: 140 | - watch 141 | - list 142 | - get 143 | - apiGroups: 144 | - storage.k8s.io 145 | resources: 146 | - storageclasses 147 | - csinodes 148 | - csidrivers 149 | - csistoragecapacities 150 | verbs: 151 | - watch 152 | - list 153 | - get 154 | - apiGroups: 155 | - "" 156 | resources: 157 | - configmaps 158 | verbs: 159 | - list 160 | - watch 161 | - get 162 | - apiGroups: 163 | - coordination.k8s.io 164 | resources: 165 | - leases 166 | verbs: 167 | - create 168 | - apiGroups: 169 | - coordination.k8s.io 170 | resourceNames: 171 | - cluster-autoscaler 172 | resources: 173 | - leases 174 | verbs: 175 | - get 176 | - update 177 | --- 178 | # Source: cluster-autoscaler/templates/clusterrolebinding.yaml 179 | apiVersion: rbac.authorization.k8s.io/v1 180 | kind: ClusterRoleBinding 181 | metadata: 182 | labels: 183 | app.kubernetes.io/instance: "cluster-autoscaler" 184 | app.kubernetes.io/name: "aws-cluster-autoscaler" 185 | app.kubernetes.io/managed-by: "Helm" 186 | helm.sh/chart: "cluster-autoscaler-9.46.0" 187 | name: cluster-autoscaler 188 | roleRef: 189 | apiGroup: rbac.authorization.k8s.io 190 | kind: ClusterRole 191 | name: cluster-autoscaler 192 | subjects: 193 | - kind: ServiceAccount 194 | name: cluster-autoscaler 195 | namespace: kube-system 196 | --- 197 | # Source: cluster-autoscaler/templates/role.yaml 198 | apiVersion: rbac.authorization.k8s.io/v1 199 | kind: Role 200 | metadata: 201 | labels: 202 | app.kubernetes.io/instance: "cluster-autoscaler" 203 | app.kubernetes.io/name: "aws-cluster-autoscaler" 204 | app.kubernetes.io/managed-by: "Helm" 205 | helm.sh/chart: "cluster-autoscaler-9.46.0" 206 | name: cluster-autoscaler 207 | namespace: kube-system 208 | rules: 209 | - apiGroups: 210 | - "" 211 | resources: 212 | - configmaps 213 | verbs: 214 | - create 215 | - apiGroups: 216 | - "" 217 | resources: 218 | - configmaps 219 | resourceNames: 220 | - cluster-autoscaler-status 221 | verbs: 222 | - delete 223 | - get 224 | - update 225 | --- 226 | # Source: cluster-autoscaler/templates/rolebinding.yaml 227 | apiVersion: rbac.authorization.k8s.io/v1 228 | kind: RoleBinding 229 | metadata: 230 | labels: 231 | app.kubernetes.io/instance: "cluster-autoscaler" 232 | app.kubernetes.io/name: "aws-cluster-autoscaler" 233 | app.kubernetes.io/managed-by: "Helm" 234 | helm.sh/chart: "cluster-autoscaler-9.46.0" 235 | name: cluster-autoscaler 236 | namespace: kube-system 237 | roleRef: 238 | apiGroup: rbac.authorization.k8s.io 239 | kind: Role 240 | name: cluster-autoscaler 241 | subjects: 242 | - kind: ServiceAccount 243 | name: cluster-autoscaler 244 | namespace: kube-system 245 | --- 246 | # Source: cluster-autoscaler/templates/service.yaml 247 | apiVersion: v1 248 | kind: Service 249 | metadata: 250 | labels: 251 | app.kubernetes.io/instance: "cluster-autoscaler" 252 | app.kubernetes.io/name: "aws-cluster-autoscaler" 253 | app.kubernetes.io/managed-by: "Helm" 254 | helm.sh/chart: "cluster-autoscaler-9.46.0" 255 | name: cluster-autoscaler 256 | namespace: kube-system 257 | spec: 258 | ports: 259 | - port: 8085 260 | protocol: TCP 261 | targetPort: 8085 262 | name: http 263 | selector: 264 | app.kubernetes.io/instance: "cluster-autoscaler" 265 | app.kubernetes.io/name: "aws-cluster-autoscaler" 266 | type: "ClusterIP" 267 | --- 268 | # Source: cluster-autoscaler/templates/deployment.yaml 269 | apiVersion: apps/v1 270 | kind: Deployment 271 | metadata: 272 | annotations: 273 | {} 274 | labels: 275 | app.kubernetes.io/instance: "cluster-autoscaler" 276 | app.kubernetes.io/name: "aws-cluster-autoscaler" 277 | app.kubernetes.io/managed-by: "Helm" 278 | helm.sh/chart: "cluster-autoscaler-9.46.0" 279 | name: cluster-autoscaler 280 | namespace: kube-system 281 | spec: 282 | replicas: 1 283 | revisionHistoryLimit: 10 284 | selector: 285 | matchLabels: 286 | app.kubernetes.io/instance: "cluster-autoscaler" 287 | app.kubernetes.io/name: "aws-cluster-autoscaler" 288 | template: 289 | metadata: 290 | labels: 291 | app.kubernetes.io/instance: "cluster-autoscaler" 292 | app.kubernetes.io/name: "aws-cluster-autoscaler" 293 | spec: 294 | priorityClassName: "system-cluster-critical" 295 | dnsPolicy: "ClusterFirst" 296 | containers: 297 | - name: aws-cluster-autoscaler 298 | image: "registry.k8s.io/autoscaling/cluster-autoscaler:v1.32.0" 299 | imagePullPolicy: "IfNotPresent" 300 | command: 301 | - ./cluster-autoscaler 302 | - --cloud-provider=aws 303 | - --namespace=kube-system 304 | - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/eksworkshop-eksctl 305 | - --logtostderr=true 306 | - --stderrthreshold=info 307 | - --v=4 308 | env: 309 | - name: POD_NAMESPACE 310 | valueFrom: 311 | fieldRef: 312 | fieldPath: metadata.namespace 313 | - name: SERVICE_ACCOUNT 314 | valueFrom: 315 | fieldRef: 316 | fieldPath: spec.serviceAccountName 317 | - name: AWS_REGION 318 | value: "us-east-1" 319 | livenessProbe: 320 | httpGet: 321 | path: /health-check 322 | port: 8085 323 | ports: 324 | - containerPort: 8085 325 | resources: 326 | {} 327 | serviceAccountName: cluster-autoscaler 328 | tolerations: 329 | [] 330 | -------------------------------------------------------------------------------- /workshop/otel-collector-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: opentelemetry.io/v1alpha1 2 | kind: OpenTelemetryCollector 3 | metadata: 4 | name: observability 5 | namespace: prometheus 6 | spec: 7 | mode: deployment 8 | serviceAccount: amp-irsa-role 9 | 10 | config: | 11 | extensions: 12 | sigv4auth: 13 | region: "AWS_REGION" 14 | service: "aps" 15 | receivers: 16 | prometheus: 17 | config: 18 | global: 19 | scrape_interval: 15s 20 | scrape_timeout: 10s 21 | 22 | scrape_configs: 23 | - job_name: kubernetes-apiservers 24 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 25 | kubernetes_sd_configs: 26 | - role: endpoints 27 | relabel_configs: 28 | - action: keep 29 | regex: default;kubernetes;https 30 | source_labels: 31 | - __meta_kubernetes_namespace 32 | - __meta_kubernetes_service_name 33 | - __meta_kubernetes_endpoint_port_name 34 | scheme: https 35 | tls_config: 36 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 37 | insecure_skip_verify: true 38 | 39 | - job_name: kubernetes-nodes 40 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 41 | kubernetes_sd_configs: 42 | - role: node 43 | relabel_configs: 44 | - action: labelmap 45 | regex: __meta_kubernetes_node_label_(.+) 46 | - replacement: kubernetes.default.svc:443 47 | target_label: __address__ 48 | - regex: (.+) 49 | replacement: /api/v1/nodes/$$1/proxy/metrics 50 | source_labels: 51 | - __meta_kubernetes_node_name 52 | target_label: __metrics_path__ 53 | scheme: https 54 | tls_config: 55 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 56 | insecure_skip_verify: true 57 | 58 | - job_name: kubernetes-nodes-cadvisor 59 | bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token 60 | kubernetes_sd_configs: 61 | - role: node 62 | relabel_configs: 63 | - action: labelmap 64 | regex: __meta_kubernetes_node_label_(.+) 65 | - replacement: kubernetes.default.svc:443 66 | target_label: __address__ 67 | - regex: (.+) 68 | replacement: /api/v1/nodes/$$1/proxy/metrics/cadvisor 69 | source_labels: 70 | - __meta_kubernetes_node_name 71 | target_label: __metrics_path__ 72 | scheme: https 73 | tls_config: 74 | ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 75 | insecure_skip_verify: true 76 | 77 | - job_name: kubernetes-service-endpoints 78 | kubernetes_sd_configs: 79 | - role: endpoints 80 | relabel_configs: 81 | - action: keep 82 | regex: true 83 | source_labels: 84 | - __meta_kubernetes_service_annotation_prometheus_io_scrape 85 | - action: replace 86 | regex: (https?) 87 | source_labels: 88 | - __meta_kubernetes_service_annotation_prometheus_io_scheme 89 | target_label: __scheme__ 90 | - action: replace 91 | regex: (.+) 92 | source_labels: 93 | - __meta_kubernetes_service_annotation_prometheus_io_path 94 | target_label: __metrics_path__ 95 | - action: replace 96 | regex: ([^:]+)(?::\d+)?;(\d+) 97 | replacement: $$1:$$2 98 | source_labels: 99 | - __address__ 100 | - __meta_kubernetes_service_annotation_prometheus_io_port 101 | target_label: __address__ 102 | - action: labelmap 103 | regex: __meta_kubernetes_service_annotation_prometheus_io_param_(.+) 104 | replacement: __param_$$1 105 | - action: labelmap 106 | regex: __meta_kubernetes_service_label_(.+) 107 | - action: replace 108 | source_labels: 109 | - __meta_kubernetes_namespace 110 | target_label: kubernetes_namespace 111 | - action: replace 112 | source_labels: 113 | - __meta_kubernetes_service_name 114 | target_label: kubernetes_name 115 | - action: replace 116 | source_labels: 117 | - __meta_kubernetes_pod_node_name 118 | target_label: kubernetes_node 119 | 120 | - job_name: kubernetes-service-endpoints-slow 121 | kubernetes_sd_configs: 122 | - role: endpoints 123 | relabel_configs: 124 | - action: keep 125 | regex: true 126 | source_labels: 127 | - __meta_kubernetes_service_annotation_prometheus_io_scrape_slow 128 | - action: replace 129 | regex: (https?) 130 | source_labels: 131 | - __meta_kubernetes_service_annotation_prometheus_io_scheme 132 | target_label: __scheme__ 133 | - action: replace 134 | regex: (.+) 135 | source_labels: 136 | - __meta_kubernetes_service_annotation_prometheus_io_path 137 | target_label: __metrics_path__ 138 | - action: replace 139 | regex: ([^:]+)(?::\d+)?;(\d+) 140 | replacement: $$1:$$2 141 | source_labels: 142 | - __address__ 143 | - __meta_kubernetes_service_annotation_prometheus_io_port 144 | target_label: __address__ 145 | - action: labelmap 146 | regex: __meta_kubernetes_service_annotation_prometheus_io_param_(.+) 147 | replacement: __param_$$1 148 | - action: labelmap 149 | regex: __meta_kubernetes_service_label_(.+) 150 | - action: replace 151 | source_labels: 152 | - __meta_kubernetes_namespace 153 | target_label: kubernetes_namespace 154 | - action: replace 155 | source_labels: 156 | - __meta_kubernetes_service_name 157 | target_label: kubernetes_name 158 | - action: replace 159 | source_labels: 160 | - __meta_kubernetes_pod_node_name 161 | target_label: kubernetes_node 162 | scrape_interval: 5m 163 | scrape_timeout: 30s 164 | 165 | - job_name: prometheus-pushgateway 166 | honor_labels: true 167 | kubernetes_sd_configs: 168 | - role: service 169 | relabel_configs: 170 | - action: keep 171 | regex: pushgateway 172 | source_labels: 173 | - __meta_kubernetes_service_annotation_prometheus_io_probe 174 | 175 | - job_name: kubernetes-services 176 | kubernetes_sd_configs: 177 | - role: service 178 | metrics_path: /probe 179 | params: 180 | module: 181 | - http_2xx 182 | relabel_configs: 183 | - action: keep 184 | regex: true 185 | source_labels: 186 | - __meta_kubernetes_service_annotation_prometheus_io_probe 187 | - source_labels: 188 | - __address__ 189 | target_label: __param_target 190 | - replacement: blackbox 191 | target_label: __address__ 192 | - source_labels: 193 | - __param_target 194 | target_label: instance 195 | - action: labelmap 196 | regex: __meta_kubernetes_service_label_(.+) 197 | - source_labels: 198 | - __meta_kubernetes_namespace 199 | target_label: kubernetes_namespace 200 | - source_labels: 201 | - __meta_kubernetes_service_name 202 | target_label: kubernetes_name 203 | 204 | - job_name: kubernetes-pods 205 | kubernetes_sd_configs: 206 | - role: pod 207 | relabel_configs: 208 | - action: keep 209 | regex: true 210 | source_labels: 211 | - __meta_kubernetes_pod_annotation_prometheus_io_scrape 212 | - action: replace 213 | regex: (https?) 214 | source_labels: 215 | - __meta_kubernetes_pod_annotation_prometheus_io_scheme 216 | target_label: __scheme__ 217 | - action: replace 218 | regex: (.+) 219 | source_labels: 220 | - __meta_kubernetes_pod_annotation_prometheus_io_path 221 | target_label: __metrics_path__ 222 | - action: replace 223 | regex: ([^:]+)(?::\d+)?;(\d+) 224 | replacement: $$1:$$2 225 | source_labels: 226 | - __address__ 227 | - __meta_kubernetes_pod_annotation_prometheus_io_port 228 | target_label: __address__ 229 | - action: labelmap 230 | regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+) 231 | replacement: __param_$$1 232 | - action: labelmap 233 | regex: __meta_kubernetes_pod_label_(.+) 234 | - action: replace 235 | source_labels: 236 | - __meta_kubernetes_namespace 237 | target_label: kubernetes_namespace 238 | - action: replace 239 | source_labels: 240 | - __meta_kubernetes_pod_name 241 | target_label: kubernetes_pod_name 242 | - action: drop 243 | regex: Pending|Succeeded|Failed|Completed 244 | source_labels: 245 | - __meta_kubernetes_pod_phase 246 | 247 | - job_name: kubernetes-pods-slow 248 | scrape_interval: 5m 249 | scrape_timeout: 30s 250 | kubernetes_sd_configs: 251 | - role: pod 252 | relabel_configs: 253 | - action: keep 254 | regex: true 255 | source_labels: 256 | - __meta_kubernetes_pod_annotation_prometheus_io_scrape_slow 257 | - action: replace 258 | regex: (https?) 259 | source_labels: 260 | - __meta_kubernetes_pod_annotation_prometheus_io_scheme 261 | target_label: __scheme__ 262 | - action: replace 263 | regex: (.+) 264 | source_labels: 265 | - __meta_kubernetes_pod_annotation_prometheus_io_path 266 | target_label: __metrics_path__ 267 | - action: replace 268 | regex: ([^:]+)(?::\d+)?;(\d+) 269 | replacement: $$1:$$2 270 | source_labels: 271 | - __address__ 272 | - __meta_kubernetes_pod_annotation_prometheus_io_port 273 | target_label: __address__ 274 | - action: labelmap 275 | regex: __meta_kubernetes_pod_annotation_prometheus_io_param_(.+) 276 | replacement: __param_$1 277 | - action: labelmap 278 | regex: __meta_kubernetes_pod_label_(.+) 279 | - action: replace 280 | source_labels: 281 | - __meta_kubernetes_namespace 282 | target_label: namespace 283 | - action: replace 284 | source_labels: 285 | - __meta_kubernetes_pod_name 286 | target_label: pod 287 | - action: drop 288 | regex: Pending|Succeeded|Failed|Completed 289 | source_labels: 290 | - __meta_kubernetes_pod_phase 291 | 292 | processors: 293 | batch/metrics: 294 | timeout: 60s 295 | 296 | exporters: 297 | prometheusremotewrite: 298 | endpoint: "AMP_WORKSPACE_URL" 299 | auth: 300 | authenticator: sigv4auth 301 | 302 | service: 303 | extensions: [sigv4auth] 304 | pipelines: 305 | # 306 | # Metrics pipeline for collecting Prometheus metrics and sending them to Amazon Managed Prometheus 307 | # 308 | metrics: 309 | receivers: [prometheus] 310 | processors: [batch/metrics] 311 | exporters: [prometheusremotewrite] 312 | 313 | --- 314 | apiVersion: rbac.authorization.k8s.io/v1 315 | kind: ClusterRole 316 | metadata: 317 | name: otel-prometheus-role 318 | rules: 319 | - apiGroups: 320 | - "" 321 | resources: 322 | - nodes 323 | - nodes/proxy 324 | - services 325 | - endpoints 326 | - pods 327 | verbs: 328 | - get 329 | - list 330 | - watch 331 | - apiGroups: 332 | - extensions 333 | resources: 334 | - ingresses 335 | verbs: 336 | - get 337 | - list 338 | - watch 339 | - nonResourceURLs: 340 | - /metrics 341 | verbs: 342 | - get 343 | 344 | --- 345 | apiVersion: rbac.authorization.k8s.io/v1 346 | kind: ClusterRoleBinding 347 | metadata: 348 | name: otel-prometheus-role-binding 349 | roleRef: 350 | apiGroup: rbac.authorization.k8s.io 351 | kind: ClusterRole 352 | name: otel-prometheus-role 353 | subjects: 354 | - kind: ServiceAccount 355 | name: amp-irsa-role 356 | namespace: prometheus 357 | -------------------------------------------------------------------------------- /workshop/spinnaker/installspinnaker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OPTIONS=${1:-INSTALL} 4 | 5 | IGREEN='\033[0;92m' 6 | COLOR_OFF='\033[0m' 7 | BRED='\033[1;31m' 8 | ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account) 9 | AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region') 10 | S3_SERVICE_ACCOUNT=s3-access-sa 11 | ECR_REPOSITORY=eks-workshop-demo/test-detail 12 | APP_VERSION=1.0 13 | ADDRESS=https://$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 14 | 15 | exit_trap () { 16 | local lc="$BASH_COMMAND" rc=$? 17 | echo "Command [$lc] exited with code [$rc]" 18 | } 19 | 20 | trap exit_trap EXIT 21 | 22 | usage() { 23 | 24 | filename=$(basename $BASH_SOURCE) 25 | printf "${BRED} ./${filename} # to install spinnaker ${COLOR_OFF}\n" 26 | printf "${BRED} ./${filename} DELETE # to clean up this chapter ${COLOR_OFF}\n " 27 | } 28 | 29 | installing_yq() { # INSTALLING yq Locally 30 | 31 | echo "+++++++++++++++++++++++++++++++++++++++++++++++" 32 | printf "${IGREEN}installing yq locally${COLOR_OFF}\n" 33 | echo "+++++++++++++++++++++++++++++++++++++++++++++++" 34 | 35 | VERSION="v4.25.1" 36 | BINARY="yq_linux_amd64" 37 | 38 | 39 | wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\ 40 | tar xz && sudo mv ${BINARY} /usr/bin/yq 41 | 42 | if [ $? != 0 ]; then 43 | 44 | printf "${BRED}Yq is not installed, Make sure Version of Yq is correct" 45 | exit 46 | fi 47 | 48 | } 49 | 50 | 51 | config() { # GET User Input 52 | 53 | printf "${BRED}Spinnaker Operator Version and Spinnaker Release Version should be like 1.2.5 1.26.6 NOT LIKE v1.2.5${COLOR_OFF}\n" 54 | 55 | read -p "Spinaker operator Version from https://github.com/armory/spinnaker-operator/releases for versions : " SPINNAKER_OPERATOR_VERSION 56 | 57 | read -p "Spinnaker release from https://spinnaker.io/community/releases/versions/: " SPINNAKER_VERSION 58 | 59 | read -p "Git hub account user name: " GITHUB_USER 60 | 61 | read -p "Git hub token: " -s GITHUB_TOKEN 62 | 63 | if [[ ${SPINNAKER_OPERATOR_VERSION} =~ ^[vV] || ${SPINNAKER_VERSION} =~ ^[vV] ]]; then 64 | 65 | printf "\n${BRED}Version Number should only have Numbers ${COLOR_OFF} \n" 66 | exit 67 | fi 68 | 69 | echo " 70 | 71 | UserInput 72 | ********************************************************** 73 | ACCOUNT_ID: ${ACCOUNT_ID} 74 | AWS_REGION: ${AWS_REGION} 75 | SPINNAKER_OPERATOR_VERSION: ${SPINNAKER_OPERATOR_VERSION} 76 | SPINNAKER_VERSION: ${SPINNAKER_VERSION} 77 | GITHUB_USER: ${GITHUB_USER} 78 | *********************************************************** 79 | " 80 | } 81 | 82 | install_spinnaker_creds() { # ISTALLING Spinnaker Creds 83 | echo "+++++++++++++++++++++++++++++++++++++++++++" 84 | printf "${IGREEN}INSTALLING Spinnaker CRDs${COLOR_OFF}\n" 85 | echo "+++++++++++++++++++++++++++++++++++++++++++" 86 | sleep 5 87 | eksctl utils associate-iam-oidc-provider --region=${AWS_REGION} --cluster=eksworkshop-eksctl --approve 88 | cd ~/environment 89 | mkdir -p spinnaker-operator && cd spinnaker-operator 90 | bash -c "curl -L https://github.com/armory/spinnaker-operator/releases/download/v${SPINNAKER_OPERATOR_VERSION}/manifests.tgz | tar -xz" 91 | kubectl apply -f deploy/crds/ 92 | } 93 | 94 | install_spinnaker_operator(){ # INSTALLING Spinaker Operator 95 | 96 | echo "+++++++++++++++++++++++++++++++++++++++++++" 97 | printf "${IGREEN}INSTALLING Spinnaker Operator${COLOR_OFF}\n" 98 | echo "+++++++++++++++++++++++++++++++++++++++++++" 99 | sleep 5 100 | cd ~/environment/spinnaker-operator 101 | kubectl create ns spinnaker-operator 102 | kubectl -n spinnaker-operator apply -f deploy/operator/cluster 103 | 104 | echo "Waiting for pods to come up, It takes 2-3 Mins" 105 | sleep 30 106 | PODS_RUNNING="false" 107 | total_time=30 108 | while [ ${PODS_RUNNING} == "false" ]; do 109 | first_field=$(kubectl get pod -n spinnaker-operator|grep -i spinnaker-op|awk '{print $2}'|cut -d "/" -f1) 110 | second_field=$(kubectl get pod -n spinnaker-operator|grep -i spinnaker-op|awk '{print $2}'|cut -d "/" -f2) 111 | status=$(kubectl get pod -n spinnaker-operator|grep -i spinnaker-op|awk '{print $3}') 112 | sleep 10 113 | echo "Checking if pods are running" 114 | if [ ${first_field} == ${second_field} ] && [ ${status} == "Running" ]; then 115 | echo "Pods are running" 116 | PODS_RUNNING="true" 117 | else 118 | echo "Waiting for pods to come up" 119 | fi 120 | total_time=`expr ${total_time} + 10` 121 | if [ ${total_time} -gt 200 ]; then 122 | printf "${BRED}Something is wrong, Pods don't take this much time${COLOR_OFF}\n" 123 | printf "${BRED}Check if the Version number is correct${COLOR_OFF}\n" 124 | exit 125 | fi 126 | done 127 | 128 | 129 | kubectl get pod -n spinnaker-operator 130 | } 131 | 132 | create_s3() { # CREATING S3 Bucket 133 | 134 | echo "+++++++++++++++++++++++++++++++++++++++++++" 135 | printf "${IGREEN}CREATING S3 Bucket${COLOR_OFF}\n" 136 | echo "+++++++++++++++++++++++++++++++++++++++++++" 137 | sleep 5 138 | 139 | export S3_BUCKET=spinnaker-workshop-$(cat /dev/urandom | LC_ALL=C tr -dc "[:alpha:]" | tr '[:upper:]' '[:lower:]' | head -c 10) 140 | aws s3api head-bucket --bucket ${S3_BUCKET} >/dev/null 2>&1 141 | if [[ $? != 0 ]]; then 142 | 143 | printf "Bucket Doesnot Exists Creating one \n" 144 | aws s3 mb s3://${S3_BUCKET} 145 | 146 | aws s3api put-public-access-block \ 147 | --bucket ${S3_BUCKET} \ 148 | --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" 149 | 150 | echo "S3 Bucket Name: ${S3_BUCKET}" 151 | echo "S3_BUCKET_NAME=${S3_BUCKET}" > /tmp/tempconfig.log 152 | else 153 | printf "Bucket ${S3_BUCKET} already exits choose another name" 154 | exit 155 | fi 156 | 157 | } 158 | 159 | 160 | create_iam_service_account() { # CREATING Service Account 161 | 162 | echo "+++++++++++++++++++++++++++++++++++++++++++" 163 | printf "${IGREEN}CREATING Service Account${COLOR_OFF}\n" 164 | echo "+++++++++++++++++++++++++++++++++++++++++++" 165 | sleep 5 166 | 167 | kubectl create ns spinnaker 168 | cluster_name=$(eksctl get cluster|grep -i eksworkshop|awk '{print $1}') 169 | if [ -z "${cluster_name}" ] 170 | then 171 | printf "${BRED}Cluster Doesnot Exists${COLOR_OFF}\n" 172 | printf "${IGREEN}Open New Terminal to Run Script${COLOR_OFF}\n" 173 | exit; 174 | else 175 | printf "${IGREEN}ClusterName: ${cluster_name}${COLOR_OFF}\n" 176 | fi 177 | 178 | sleep 5 179 | 180 | eksctl create iamserviceaccount \ 181 | --name s3-access-sa \ 182 | --namespace spinnaker \ 183 | --cluster ${cluster_name} \ 184 | --attach-policy-arn arn\:aws\:iam::aws\:policy/AmazonS3FullAccess \ 185 | --approve \ 186 | --override-existing-serviceaccounts 187 | 188 | echo "S3 service account is ${S3_SERVICE_ACCOUNT}" 189 | 190 | echo "Describing Service Account" 191 | 192 | kubectl describe sa s3-access-sa -n spinnaker 193 | 194 | sleep 3 195 | 196 | } 197 | 198 | 199 | create_ecr_repository() { # CREATING ECR Repository 200 | 201 | echo "+++++++++++++++++++++++++++++++++++++++++++" 202 | printf "${IGREEN}CREATING ECR Repository${COLOR_OFF}\n" 203 | echo "+++++++++++++++++++++++++++++++++++++++++++" 204 | sleep 5 205 | 206 | 207 | cd ~/environment/eks-app-mesh-polyglot-demo/workshop 208 | export ECR_REPOSITORY=eks-workshop-demo/test-detail 209 | export APP_VERSION=1.0 210 | aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 211 | aws ecr describe-repositories --repository-name $ECR_REPOSITORY >/dev/null 2>&1 212 | if [ $? != 0 ]; then 213 | aws ecr create-repository --repository-name $ECR_REPOSITORY >/dev/null 214 | fi 215 | echo "ECR_REPOSITORY_NAME=${ECR_REPOSITORY}" > /tmp/tempconfig.log 216 | TARGET=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$APP_VERSION 217 | docker build -t $TARGET apps/catalog_detail 218 | docker push $TARGET 219 | } 220 | 221 | 222 | create_config_map() { # CREATING Config Map 223 | 224 | echo "+++++++++++++++++++++++++++++++++++++++++++" 225 | printf "${IGREEN}CREATING Config Map${COLOR_OFF}\n" 226 | echo "+++++++++++++++++++++++++++++++++++++++++++" 227 | sleep 5 228 | 229 | 230 | cd ~/environment 231 | 232 | cat << EOF > config.yaml 233 | interval: 30m # defines refresh interval 234 | registries: # list of registries to refresh 235 | - registryId: "$ACCOUNT_ID" 236 | region: "$AWS_REGION" 237 | passwordFile: "/etc/passwords/my-ecr-registry.pass" 238 | EOF 239 | 240 | kubectl -n spinnaker create configmap token-refresh-config --from-file config.yaml 241 | 242 | echo "Checking Config Map" 243 | kubectl describe configmap token-refresh-config -n spinnaker 244 | sleep 5 245 | 246 | } 247 | 248 | 249 | download_spinnaker_tool() { # DOWNLOAD and BUILD Spinnaker Tool 250 | 251 | echo "+++++++++++++++++++++++++++++++++++++++++++" 252 | printf "${IGREEN}DOWNLOADING Spinnaker Tool${COLOR_OFF}\n" 253 | echo "+++++++++++++++++++++++++++++++++++++++++++" 254 | sleep 5 255 | 256 | cd ~/environment 257 | git clone https://github.com/armory/spinnaker-tools.git 258 | cd spinnaker-tools 259 | go mod download all 260 | go build 261 | 262 | } 263 | 264 | 265 | create_service_account() { # CREATING Service Account 266 | 267 | echo "+++++++++++++++++++++++++++++++++++++++++++" 268 | printf "${IGREEN}CREATING Service Account${COLOR_OFF}\n" 269 | echo "+++++++++++++++++++++++++++++++++++++++++++" 270 | sleep 5 271 | 272 | export CONTEXT=$(kubectl config current-context) 273 | export SOURCE_KUBECONFIG=${HOME}/.kube/config 274 | export SPINNAKER_NAMESPACE="spinnaker" 275 | export SPINNAKER_SERVICE_ACCOUNT_NAME="spinnaker-ws-sa" 276 | export DEST_KUBECONFIG=${HOME}/Kubeconfig-ws-sa 277 | 278 | echo $CONTEXT 279 | echo $SOURCE_KUBECONFIG 280 | echo $SPINNAKER_NAMESPACE 281 | echo $SPINNAKER_SERVICE_ACCOUNT_NAME 282 | echo $DEST_KUBECONFIG 283 | sleep 10 284 | cd ~/environment/spinnaker-tools 285 | ./spinnaker-tools create-service-account --kubeconfig ${SOURCE_KUBECONFIG} --context ${CONTEXT} --output ${DEST_KUBECONFIG} --namespace ${SPINNAKER_NAMESPACE} --service-account-name ${SPINNAKER_SERVICE_ACCOUNT_NAME} 286 | 287 | 288 | } 289 | 290 | modify_spinnakerservice_file() { # CREATING Manifest File For Spinnaker Service 291 | 292 | echo "+++++++++++++++++++++++++++++++++++++++++++++" 293 | printf "${IGREEN}CREATING Minifest File for Spinnaker Service${COLOR_OFF}\n" 294 | echo "+++++++++++++++++++++++++++++++++++++++++++++" 295 | sleep 5 296 | 297 | cd ~/environment/spinnaker-operator 298 | cp ~/environment/eks-app-mesh-polyglot-demo/workshop/spinnaker/spinnakerservice.yml deploy/spinnaker/basic/spinnakerservice.yml 299 | SPINNAKER_VERSION=${SPINNAKER_VERSION} yq -i '.spec.spinnakerConfig.config.version = env(SPINNAKER_VERSION)' deploy/spinnaker/basic/spinnakerservice.yml 300 | S3_BUCKET_NAME=${S3_BUCKET} yq -i '.spec.spinnakerConfig.config.persistentStorage.s3.bucket = env(S3_BUCKET_NAME)' deploy/spinnaker/basic/spinnakerservice.yml 301 | AWS_REGION=${AWS_REGION} yq -i '.spec.spinnakerConfig.config.persistentStorage.s3.region = env(AWS_REGION)' deploy/spinnaker/basic/spinnakerservice.yml 302 | GITHUB_USER=${GITHUB_USER} yq -i '.spec.spinnakerConfig.config.artifacts.github.accounts[0].name = env(GITHUB_USER)' deploy/spinnaker/basic/spinnakerservice.yml 303 | GITHUB_TOKEN=${GITHUB_TOKEN} yq -i '.spec.spinnakerConfig.config.artifacts.github.accounts[0].token = env(GITHUB_TOKEN)' deploy/spinnaker/basic/spinnakerservice.yml 304 | ECR_REPOSITORY=${ECR_REPOSITORY} yq -i '.spec.spinnakerConfig.profiles.clouddriver.dockerRegistry.accounts[0].repositories[0] = env(ECR_REPOSITORY)' deploy/spinnaker/basic/spinnakerservice.yml 305 | ADDRESS=${ADDRESS} yq -i '.spec.spinnakerConfig.profiles.clouddriver.dockerRegistry.accounts[0].address = env(ADDRESS)' deploy/spinnaker/basic/spinnakerservice.yml 306 | S3_SERVICE_ACCOUNT=${S3_SERVICE_ACCOUNT} yq -i '.spec.spinnakerConfig.service-settings.front50.kubernetes.serviceAccountName = env(S3_SERVICE_ACCOUNT)' deploy/spinnaker/basic/spinnakerservice.yml 307 | cp ${HOME}/Kubeconfig-ws-sa ~/environment/eks-app-mesh-polyglot-demo/workshop/spinnaker/test.yml 308 | yq eval-all -I 4 'select(fileIndex==0).spec.spinnakerConfig.files.kubeconfig-sp = select(fileIndex==1) | select(fileIndex==0)' -i deploy/spinnaker/basic/spinnakerservice.yml ~/environment/eks-app-mesh-polyglot-demo/workshop/spinnaker/test.yml 309 | sed -i "s/kubeconfig-sp:/kubeconfig-sp: |/g" deploy/spinnaker/basic/spinnakerservice.yml 310 | rm ~/environment/eks-app-mesh-polyglot-demo/workshop/spinnaker/test.yml 311 | } 312 | 313 | 314 | install_spinnaker_service() { # INSTALLING Spinnaker Service 315 | 316 | echo "+++++++++++++++++++++++++++++++++++++++++++" 317 | printf "${IGREEN}INSTALLING Spinnaker service using below values${COLOR_OFF}\n" 318 | echo "+++++++++++++++++++++++++++++++++++++++++++" 319 | 320 | echo "Account_ID: ${ACCOUNT_ID}" 321 | echo "AWS_REGION: ${AWS_REGION}" 322 | echo "SPINNAKER_VERSION: ${SPINNAKER_VERSION}" 323 | echo "GITHUB_USER: ${GITHUB_USER}" 324 | echo "S3_BUCKET: ${S3_BUCKET}" 325 | echo "S3_SERVICE_ACCOUNT: ${S3_SERVICE_ACCOUNT}" 326 | echo "ECR_REPOSITORY: ${ECR_REPOSITORY}" 327 | echo "++++++++++++++++++++++++++++++++++++++++++++" 328 | sleep 10 329 | 330 | cd ~/environment/spinnaker-operator/ 331 | kubectl -n spinnaker apply -f deploy/spinnaker/basic/spinnakerservice.yml 332 | echo "===================================================================================" 333 | printf "${IGREEN}Run Commands to Check Status of Pods and Services, It could take 5 Mins${COLOR_OFF}\n" 334 | printf "${IGREEN}Get all the resources created${COLOR_OFF}\n" 335 | printf "kubectl get svc,pod -n spinnaker\n" 336 | printf "${IGREEN}Watch the install progress${COLOR_OFF}\n" 337 | printf "kubectl -n spinnaker get spinsvc spinnaker -w\n" 338 | } 339 | 340 | 341 | install_spinnaker() { # CALLING ALL THE FUNCTIONS 342 | START_TIME=$(date +%s) 343 | installing_yq 344 | install_spinnaker_creds 345 | install_spinnaker_operator 346 | create_s3 347 | create_iam_service_account 348 | create_ecr_repository 349 | create_config_map 350 | download_spinnaker_tool 351 | create_service_account 352 | modify_spinnakerservice_file 353 | install_spinnaker_service 354 | END_TIME=$(date +%s) 355 | ELAPSED=$(( END_TIME - START_TIME )) 356 | ((hour=${ELAPSED}/3600)) 357 | ((mins=(${ELAPSED}-hour*3600)/60)) 358 | ((sec=${ELAPSED}-((hour*3600) + (mins*60)))) 359 | printf "TIME TAKEN: ${IGreen} %02d:%02d:%02d\n" "$hour" "$mins" "$sec" 360 | } 361 | 362 | 363 | clean_up() { #DELETE THE RESOURCES 364 | 365 | for i in $(kubectl get crd | grep spinnaker | cut -d" " -f1) ; do 366 | kubectl delete crd $i 367 | done 368 | 369 | 370 | cluster_name=$(eksctl get cluster|grep -i eksworkshop|awk '{print $1}') 371 | 372 | 373 | eksctl delete iamserviceaccount \ 374 | --name s3-access-sa \ 375 | --namespace spinnaker \ 376 | --cluster ${cluster_name} 377 | 378 | for namespace in $(kubectl get ns |grep -i spinnaker|awk '{print $1}'); do 379 | printf "${IGREEN}Deleting Namespace ${namespace} ${COLOR_OFF}\n" 380 | kubectl delete ns ${namespace} 381 | done 382 | 383 | 384 | cd ~/environment 385 | if [ -f config.yaml ]; then 386 | printf "${IGREEN}Deleting file config.yaml${COLOR_OFF}\n" 387 | rm config.yaml 388 | fi 389 | 390 | if [ -d spinnaker-tools ]; then 391 | printf "${IGREEN}Deleting Spinnaker-tools Folder ${COLOR_OFF}\n" 392 | rm -rf spinnaker-tools 393 | fi 394 | 395 | if [ -d spinnaker-operator ]; then 396 | printf "${IGREEN}Deleting Spinnaker-operator Folder ${COLOR_OFF}\n" 397 | rm -rf spinnaker-operator 398 | fi 399 | 400 | if [ -f /tmp/tempconfig.log ]; then 401 | 402 | BUCKET_NAME=$(cat /tmp/tempconfig.log|grep -i S3_BUCKET_NAME| cut -d "=" -f2) 403 | aws s3api head-bucket --bucket ${S3_BUCKET} >/dev/null 2>&1 404 | if [ $? = 0 ]; then 405 | printf "${IGREEN}Deleting S3 Bucket: ${BUCKET_NAME}${COLOR_OFF}\n" 406 | aws s3 rb s3://${BUCKET_NAME} --force 407 | else 408 | printf "${IGREEN}S3 Bucket ${BUCKET_NAME} already deleted${COLOR_OFF}\n" 409 | fi 410 | 411 | printf "Delete ECR Repository \n" 412 | 413 | ECR_REPOSITORY_NAME=$(cat /tmp/tempconfig.log|grep -i ECR_REPOSITORY_NAME| cut -d "=" -f2) 414 | aws ecr describe-repositories --repository-name $ECR_REPOSITORY >/dev/null 2>&1 415 | 416 | if [ $? = 0 ]; then 417 | 418 | printf "${IGREEN}Deleting ECR Repository: ${ECR_REPOSITORY_NAME}${COLOR_OFF}\n" 419 | aws ecr delete-repository --repository-name ${ECR_REPOSITORY_NAME} --force 420 | 421 | else 422 | printf "${IGREEN}ECR Repository already deleted${COLOR_OFF}\n" 423 | fi 424 | rm /tmp/tempconfig.log 425 | fi 426 | 427 | trap - EXIT 428 | echo "++++++++++++++++++++++++++++++++++++++++++++" 429 | printf "${IGREEN}Clean UP Completed${COLOR_OFF}\n" 430 | 431 | 432 | 433 | } 434 | 435 | 436 | if [[ ${OPTIONS} == "INSTALL" ]]; then 437 | while true; do 438 | config 439 | read -p "All information correct y/n:" yn 440 | case $yn in 441 | [Yy]* ) install_spinnaker; break;; 442 | [Nn]* ) exit;; 443 | * ) echo "Please answer yes or no.";; 444 | esac 445 | done 446 | 447 | elif [[ ${OPTIONS} == "DELETE" ]]; then 448 | #statements 449 | echo "+++++++++++++++++++++++++++++++++++++++++++" 450 | printf "${BRED}DELETING RESOURCES ${COLOR_OFF}\n" 451 | echo "+++++++++++++++++++++++++++++++++++++++++++" 452 | clean_up 453 | else 454 | usage 455 | 456 | fi --------------------------------------------------------------------------------