From ba6dd0e628e5cd2d9e79cc8dd4bff92dcd21b06b Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Sat, 9 Aug 2025 07:41:30 -0700 Subject: [PATCH 1/7] Log temp directory --- hack/validate-terraform.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hack/validate-terraform.sh b/hack/validate-terraform.sh index a085a0ff6e..e95811f1b4 100755 --- a/hack/validate-terraform.sh +++ b/hack/validate-terraform.sh @@ -36,4 +36,8 @@ done terraform -chdir="${conf_dir}" init -backend=false +echo "" +echo "Validating ${conf_dir}" +echo "" + terraform -chdir="${conf_dir}" validate \ No newline at end of file From d941f448b1d0843beee49f898e7ea4ae6d32fde6 Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Sat, 9 Aug 2025 13:39:58 -0700 Subject: [PATCH 2/7] Minor lint fixes --- website/src/components/ConsoleButton/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/website/src/components/ConsoleButton/index.tsx b/website/src/components/ConsoleButton/index.tsx index ad7bd4c082..1d5743773a 100644 --- a/website/src/components/ConsoleButton/index.tsx +++ b/website/src/components/ConsoleButton/index.tsx @@ -1,10 +1,7 @@ import React, { type ReactNode } from "react"; -import clsx from "clsx"; import styles from "./styles.module.css"; import useBaseUrl from "@docusaurus/useBaseUrl"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; interface Props { service: string; @@ -17,7 +14,7 @@ export default function ConsoleButton({ url = "http://localhost:3000", label = "Launch", }: Props): JSX.Element { - let serviceIcon = service || "console"; + const serviceIcon = service || "console"; return ( Date: Sat, 9 Aug 2025 13:40:23 -0700 Subject: [PATCH 3/7] Minor language fix --- website/docs/fundamentals/exposing/ingress/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/fundamentals/exposing/ingress/index.md b/website/docs/fundamentals/exposing/ingress/index.md index a02634c263..c7cca3df0c 100644 --- a/website/docs/fundamentals/exposing/ingress/index.md +++ b/website/docs/fundamentals/exposing/ingress/index.md @@ -17,8 +17,8 @@ $ prepare-environment exposing/ingress This will make the following changes to your lab environment: -- Creates an IAM role required by the AWS Load Balancer Controller -- Creates an IAM role required by ExternalDNS +- Create an IAM role required by the AWS Load Balancer Controller +- Create an IAM role required by ExternalDNS - Create an AWS Route 53 private hosted zone You can view the Terraform that applies these changes [here](https://github.com/VAR::MANIFESTS_OWNER/VAR::MANIFESTS_REPOSITORY/tree/VAR::MANIFESTS_REF/manifests/modules/exposing/ingress/.workshop/terraform). From b4e0c2ea50e14c806b75eed466d5a805ba49b61b Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Sat, 9 Aug 2025 13:40:47 -0700 Subject: [PATCH 4/7] Minor language fix --- website/docs/introduction/setup/your-account/using-terraform.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/introduction/setup/your-account/using-terraform.md b/website/docs/introduction/setup/your-account/using-terraform.md index 360bddc03e..cef70877e3 100644 --- a/website/docs/introduction/setup/your-account/using-terraform.md +++ b/website/docs/introduction/setup/your-account/using-terraform.md @@ -7,7 +7,7 @@ sidebar_position: 30 Creating the workshop cluster with Terraform is currently in preview. Please raise any issues encountered in the [GitHub repository](https://github.com/aws-samples/eks-workshop-v2/issues). ::: -This section outlines how to build a cluster for the lab exercises using [Hashicorp Terraform](https://developer.hashicorp.com/terraform). This is intended for learners who are familiar with using Terraform infrastructure-as-code. +This section outlines how to build a cluster for the lab exercises using [HashiCorp Terraform](https://developer.hashicorp.com/terraform). This is intended for learners who are familiar with using Terraform infrastructure-as-code. The `terraform` CLI has been pre-installed in your IDE environment, so we can immediately create the cluster. Let's examine the main Terraform configuration files that will be used to build the cluster and its supporting infrastructure. From 31f122f806a4d489305f6d937b1369989b2e2ba8 Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Sat, 9 Aug 2025 13:41:43 -0700 Subject: [PATCH 5/7] WIP commit --- .../developers/ingress/.workshop/cleanup.sh | 10 ++ .../.workshop/terraform/autoscaling.tf | 11 ++ .../ingress/.workshop/terraform/exposing.tf | 42 +++++ .../ingress/.workshop/terraform/main.tf | 10 ++ .../ingress/.workshop/terraform/outputs.tf | 9 + .../.workshop/terraform/pod_identity.tf | 166 ++++++++++++++++++ .../.workshop/terraform/preprovision/main.tf | 153 ++++++++++++++++ .../.workshop/terraform/preprovision/vars.tf | 11 ++ .../ingress/.workshop/terraform/storage.tf | 26 +++ .../ingress/.workshop/terraform/vars.tf | 56 ++++++ .../ingress/creating-ingress/ingress.yaml | 21 +++ .../creating-ingress/kustomization.yaml | 4 + .../ingress/external-dns/ingress.yaml | 23 +++ .../ingress/external-dns/kustomization.yaml | 4 + .../multiple-ingress/ingress-catalog.yaml | 25 +++ .../ingress/multiple-ingress/ingress-ui.yaml | 25 +++ .../multiple-ingress/kustomization.yaml | 5 + .../amazon-eks-pod-identity/assets/error.webp | Bin 0 -> 7008 bytes .../amazon-eks-pod-identity/index.md | 18 ++ .../amazon-eks-pod-identity/introduction.md | 24 +++ .../tests/hook-enable-dynamo.sh | 18 ++ .../tests/hook-enable-pod-identity.sh | 13 ++ .../tests/hook-pod-logs.sh | 18 ++ .../tests/hook-suite.sh | 11 ++ .../amazon-eks-pod-identity/understanding.md | 29 +++ .../use-pod-identity.md | 127 ++++++++++++++ .../amazon-eks-pod-identity/using-dynamo.md | 76 ++++++++ .../verifying-dynamo.md | 35 ++++ .../developer/efs/assets/efs-storage.webp | Bin 0 -> 11390 bytes .../efs/assets/efsfilesystemscreenshort.webp | Bin 0 -> 6358 bytes .../developer/efs/assets/placeholder.jpg | Bin 0 -> 7575 bytes .../developer/efs/deployment-with-efs.md | 160 +++++++++++++++++ .../fastpaths/developer/efs/efs-csi-driver.md | 81 +++++++++ .../developer/efs/existing-architecture.md | 40 +++++ website/docs/fastpaths/developer/efs/index.md | 23 +++ .../efs/tests/hook-efs-deployment.sh | 23 +++ .../developer/efs/tests/hook-placeholder.sh | 21 +++ .../developer/efs/tests/hook-sample-images.sh | 15 ++ .../developer/efs/tests/hook-suite.sh | 11 ++ .../developer/getting-started/about.md | 26 +++ .../assets/catalog-microservice.webp | Bin 0 -> 15136 bytes .../getting-started/assets/ide-base.webp | Bin 0 -> 7370 bytes .../getting-started/assets/ide-initial.webp | Bin 0 -> 3864 bytes .../getting-started/assets/ide-modules.webp | Bin 0 -> 11372 bytes .../getting-started/assets/microservices.webp | Bin 0 -> 26046 bytes .../developer/getting-started/finish.md | 59 +++++++ .../developer/getting-started/first.md | 144 +++++++++++++++ .../developer/getting-started/index.md | 9 + .../getting-started/microservices.md | 22 +++ .../getting-started/packaging-application.md | 16 ++ website/docs/fastpaths/developer/index.md | 23 +++ .../developer/ingress/adding-ingress.md | 124 +++++++++++++ .../ingress/assets/multiple-ingress-lb.webp | Bin 0 -> 6372 bytes .../assets/multiple-ingress-listener.webp | Bin 0 -> 14298 bytes .../assets/multiple-ingress-rules.webp | Bin 0 -> 13180 bytes .../developer/ingress/assets/web-ui.webp | Bin 0 -> 18966 bytes .../developer/ingress/external-dns.md | 90 ++++++++++ .../docs/fastpaths/developer/ingress/index.md | 21 +++ .../developer/ingress/introduction.md | 41 +++++ .../developer/ingress/multiple-ingress.md | 82 +++++++++ .../ingress/tests/hook-add-ingress.sh | 32 ++++ .../developer/ingress/tests/hook-dns-curl.sh | 15 ++ .../developer/ingress/tests/hook-dns-logs.sh | 15 ++ .../ingress/tests/hook-multiple-ingress.sh | 37 ++++ .../developer/ingress/tests/hook-suite.sh | 11 ++ .../developer/keda/configure-keda.md | 30 ++++ .../docs/fastpaths/developer/keda/index.md | 22 +++ .../fastpaths/developer/keda/install-keda.md | 44 +++++ .../fastpaths/developer/keda/test-keda.md | 50 ++++++ .../keda/tests/hook-keda-pod-scaleout.sh | 21 +++ .../developer/keda/tests/hook-suite.sh | 11 ++ .../keda/tests/hook-validate-ingress.sh | 32 ++++ website/sidebars.js | 1 + 73 files changed, 2322 insertions(+) create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/autoscaling.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/exposing.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/main.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/outputs.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/pod_identity.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/preprovision/main.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/preprovision/vars.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/storage.tf create mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/terraform/vars.tf create mode 100644 manifests/modules/fastpath/developers/ingress/creating-ingress/ingress.yaml create mode 100644 manifests/modules/fastpath/developers/ingress/creating-ingress/kustomization.yaml create mode 100644 manifests/modules/fastpath/developers/ingress/external-dns/ingress.yaml create mode 100644 manifests/modules/fastpath/developers/ingress/external-dns/kustomization.yaml create mode 100644 manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-catalog.yaml create mode 100644 manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-ui.yaml create mode 100644 manifests/modules/fastpath/developers/ingress/multiple-ingress/kustomization.yaml create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/assets/error.webp create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/index.md create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/introduction.md create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/tests/hook-enable-dynamo.sh create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/tests/hook-enable-pod-identity.sh create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/tests/hook-pod-logs.sh create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/tests/hook-suite.sh create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/understanding.md create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/use-pod-identity.md create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/using-dynamo.md create mode 100644 website/docs/fastpaths/developer/amazon-eks-pod-identity/verifying-dynamo.md create mode 100644 website/docs/fastpaths/developer/efs/assets/efs-storage.webp create mode 100644 website/docs/fastpaths/developer/efs/assets/efsfilesystemscreenshort.webp create mode 100644 website/docs/fastpaths/developer/efs/assets/placeholder.jpg create mode 100644 website/docs/fastpaths/developer/efs/deployment-with-efs.md create mode 100644 website/docs/fastpaths/developer/efs/efs-csi-driver.md create mode 100644 website/docs/fastpaths/developer/efs/existing-architecture.md create mode 100644 website/docs/fastpaths/developer/efs/index.md create mode 100644 website/docs/fastpaths/developer/efs/tests/hook-efs-deployment.sh create mode 100644 website/docs/fastpaths/developer/efs/tests/hook-placeholder.sh create mode 100644 website/docs/fastpaths/developer/efs/tests/hook-sample-images.sh create mode 100644 website/docs/fastpaths/developer/efs/tests/hook-suite.sh create mode 100644 website/docs/fastpaths/developer/getting-started/about.md create mode 100644 website/docs/fastpaths/developer/getting-started/assets/catalog-microservice.webp create mode 100644 website/docs/fastpaths/developer/getting-started/assets/ide-base.webp create mode 100644 website/docs/fastpaths/developer/getting-started/assets/ide-initial.webp create mode 100644 website/docs/fastpaths/developer/getting-started/assets/ide-modules.webp create mode 100644 website/docs/fastpaths/developer/getting-started/assets/microservices.webp create mode 100644 website/docs/fastpaths/developer/getting-started/finish.md create mode 100644 website/docs/fastpaths/developer/getting-started/first.md create mode 100644 website/docs/fastpaths/developer/getting-started/index.md create mode 100644 website/docs/fastpaths/developer/getting-started/microservices.md create mode 100644 website/docs/fastpaths/developer/getting-started/packaging-application.md create mode 100644 website/docs/fastpaths/developer/index.md create mode 100644 website/docs/fastpaths/developer/ingress/adding-ingress.md create mode 100644 website/docs/fastpaths/developer/ingress/assets/multiple-ingress-lb.webp create mode 100644 website/docs/fastpaths/developer/ingress/assets/multiple-ingress-listener.webp create mode 100644 website/docs/fastpaths/developer/ingress/assets/multiple-ingress-rules.webp create mode 100644 website/docs/fastpaths/developer/ingress/assets/web-ui.webp create mode 100644 website/docs/fastpaths/developer/ingress/external-dns.md create mode 100644 website/docs/fastpaths/developer/ingress/index.md create mode 100644 website/docs/fastpaths/developer/ingress/introduction.md create mode 100644 website/docs/fastpaths/developer/ingress/multiple-ingress.md create mode 100644 website/docs/fastpaths/developer/ingress/tests/hook-add-ingress.sh create mode 100644 website/docs/fastpaths/developer/ingress/tests/hook-dns-curl.sh create mode 100644 website/docs/fastpaths/developer/ingress/tests/hook-dns-logs.sh create mode 100644 website/docs/fastpaths/developer/ingress/tests/hook-multiple-ingress.sh create mode 100644 website/docs/fastpaths/developer/ingress/tests/hook-suite.sh create mode 100644 website/docs/fastpaths/developer/keda/configure-keda.md create mode 100644 website/docs/fastpaths/developer/keda/index.md create mode 100644 website/docs/fastpaths/developer/keda/install-keda.md create mode 100644 website/docs/fastpaths/developer/keda/test-keda.md create mode 100644 website/docs/fastpaths/developer/keda/tests/hook-keda-pod-scaleout.sh create mode 100644 website/docs/fastpaths/developer/keda/tests/hook-suite.sh create mode 100644 website/docs/fastpaths/developer/keda/tests/hook-validate-ingress.sh diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh b/manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh new file mode 100644 index 0000000000..e9466d2513 --- /dev/null +++ b/manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -e + +kubectl delete ingress -n catalog --all --ignore-not-found +kubectl delete ingress -n ui --all --ignore-not-found + +uninstall-helm-chart external-dns external-dns + +uninstall-helm-chart aws-load-balancer-controller kube-system \ No newline at end of file diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/autoscaling.tf b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/autoscaling.tf new file mode 100644 index 0000000000..0c888caaf3 --- /dev/null +++ b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/autoscaling.tf @@ -0,0 +1,11 @@ +module "iam_assumable_role_keda" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" + version = "5.59.0" + create_role = true + role_name = "${var.addon_context.eks_cluster_id}-keda" + provider_url = var.addon_context.eks_oidc_issuer_url + role_policy_arns = ["arn:${data.aws_partition.current.partition}:iam::aws:policy/CloudWatchReadOnlyAccess"] + oidc_fully_qualified_subjects = ["system:serviceaccount:keda:keda-operator"] + + tags = var.tags +} diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/exposing.tf b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/exposing.tf new file mode 100644 index 0000000000..f9707e45a3 --- /dev/null +++ b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/exposing.tf @@ -0,0 +1,42 @@ +resource "aws_route53_zone" "private_zone" { + name = "retailstore.com" + comment = "Private hosted zone for EKS Workshop use" + vpc { + vpc_id = data.aws_vpc.this.id + } + + force_destroy = true + + tags = { + created-by = "eks-workshop-v2" + env = var.addon_context.eks_cluster_id + } +} + +module "eks_blueprints_addons" { + source = "aws-ia/eks-blueprints-addons/aws" + version = "1.21.1" + + cluster_name = var.addon_context.eks_cluster_id + cluster_endpoint = var.addon_context.aws_eks_cluster_endpoint + cluster_version = var.eks_cluster_version + oidc_provider_arn = var.addon_context.eks_oidc_provider_arn + + enable_external_dns = true + external_dns_route53_zone_arns = [aws_route53_zone.private_zone.arn] + external_dns = { + create_role = true + role_name = "${var.addon_context.eks_cluster_id}-external-dns" + policy_name = "${var.addon_context.eks_cluster_id}-external-dns" + } + + enable_aws_load_balancer_controller = true + aws_load_balancer_controller = { + role_name = "${var.addon_context.eks_cluster_id}-alb-controller" + policy_name = "${var.addon_context.eks_cluster_id}-alb-controller" + } + + create_kubernetes_resources = false + + observability_tag = null +} diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/main.tf b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/main.tf new file mode 100644 index 0000000000..87ab7b7c8d --- /dev/null +++ b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/main.tf @@ -0,0 +1,10 @@ +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} +data "aws_partition" "current" {} + +data "aws_vpc" "this" { + tags = { + created-by = "eks-workshop-v2" + env = var.addon_context.eks_cluster_id + } +} diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/outputs.tf b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/outputs.tf new file mode 100644 index 0000000000..c4a62667a3 --- /dev/null +++ b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/outputs.tf @@ -0,0 +1,9 @@ +output "environment_variables" { + description = "Environment variables to be added to the IDE shell" + value = { + LBC_CHART_VERSION = var.load_balancer_controller_chart_version + LBC_ROLE_ARN = module.eks_blueprints_addons.aws_load_balancer_controller.iam_role_arn + DNS_CHART_VERSION = var.external_dns_chart_version + DNS_ROLE_ARN = module.eks_blueprints_addons.external_dns.iam_role_arn + } +} diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/pod_identity.tf b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/pod_identity.tf new file mode 100644 index 0000000000..faf61e2ab0 --- /dev/null +++ b/manifests/modules/fastpath/developers/ingress/.workshop/terraform/pod_identity.tf @@ -0,0 +1,166 @@ +resource "aws_dynamodb_table" "carts" { + #checkov:skip=CKV2_AWS_28:Point in time backup not required for workshop + name = "${var.addon_context.eks_cluster_id}-carts" + hash_key = "id" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + server_side_encryption { + enabled = true + kms_key_arn = aws_kms_key.cmk_dynamodb.arn + } + + attribute { + name = "id" + type = "S" + } + + attribute { + name = "customerId" + type = "S" + } + + global_secondary_index { + name = "idx_global_customerId" + hash_key = "customerId" + projection_type = "ALL" + } + + tags = var.tags +} + +module "iam_assumable_role_carts" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role" + version = "5.59.0" + create_role = true + role_requires_mfa = false + role_name = "${var.addon_context.eks_cluster_id}-carts-dynamo" + trusted_role_services = ["pods.eks.amazonaws.com"] + custom_role_policy_arns = [aws_iam_policy.carts_dynamo.arn] + trusted_role_actions = ["sts:AssumeRole", "sts:TagSession"] + + tags = var.tags +} + +resource "aws_iam_policy" "carts_dynamo" { + name = "${var.addon_context.eks_cluster_id}-carts-dynamo" + path = "/" + description = "Dynamo policy for carts application" + + policy = <4e`I2Jl!u$n` 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 jS)%#DA**ab%x@HzT_i{ou}(;C>OV1sf`SG#{ 3d21h +$ kubectl -n kube-system get pods -l app.kubernetes.io/name=eks-pod-identity-agent +NAME READY STATUS RESTARTS AGE +eks-pod-identity-agent-4tn28 1/1 Running 0 3d21h +eks-pod-identity-agent-hslc5 1/1 Running 0 3d21h +eks-pod-identity-agent-thvf5 1/1 Running 0 3d21h +``` + +An IAM role, which provides the required permissions for the `carts` service to read and write to the DynamoDB table, was created when you ran the `prepare-environment` script in the first step of this module. You can view the policy as shown below: + +```bash +$ aws iam get-policy-version \ + --version-id v1 --policy-arn \ + --query 'PolicyVersion.Document' \ + arn:aws:iam::${AWS_ACCOUNT_ID}:policy/${EKS_CLUSTER_NAME}-carts-dynamo | jq . +{ + "Statement": [ + { + "Action": "dynamodb:*", + "Effect": "Allow", + "Resource": [ + "arn:aws:dynamodb:us-west-2:1234567890:table/eks-workshop-carts", + "arn:aws:dynamodb:us-west-2:1234567890:table/eks-workshop-carts/index/*" + ], + "Sid": "AllAPIActionsOnCart" + } + ], + "Version": "2012-10-17" +} +``` + +The role has also been configured with the appropriate trust relationship, which allows the EKS Service Principal to assume this role for Pod Identity. You can view it with the command below: + +```bash +$ aws iam get-role \ + --query 'Role.AssumeRolePolicyDocument' \ + --role-name ${EKS_CLUSTER_NAME}-carts-dynamo | jq . +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "pods.eks.amazonaws.com" + }, + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ] + } + ] +} +``` + +Next, we will use Amazon EKS Pod Identity feature to associate an AWS IAM role with the Kubernetes Service Account that will be used by our deployment. To create the association, run the following command: + +```bash wait=30 +$ aws eks create-pod-identity-association --cluster-name ${EKS_CLUSTER_NAME} \ + --role-arn arn:aws:iam::${AWS_ACCOUNT_ID}:role/${EKS_CLUSTER_NAME}-carts-dynamo \ + --namespace carts --service-account carts +{ + "association": { + "clusterName": "eks-workshop", + "namespace": "carts", + "serviceAccount": "carts", + "roleArn": "arn:aws:iam::1234567890:role/eks-workshop-carts-dynamo", + "associationArn": "arn:aws::1234567890:podidentityassociation/eks-workshop/a-abcdefghijklmnop1", + "associationId": "a-abcdefghijklmnop1", + "tags": {}, + "createdAt": "2024-01-09T16:16:38.163000+00:00", + "modifiedAt": "2024-01-09T16:16:38.163000+00:00" + } +} +``` + +All that's left is to verify that the `carts` Deployment is using the `carts` Service Account: + +```bash +$ kubectl -n carts describe deployment carts | grep 'Service Account' + Service Account: carts +``` + +With the Service Account verified, let's recycle the `carts` Pods: + +```bash hook=enable-pod-identity hookTimeout=430 +$ kubectl -n carts rollout restart deployment/carts +deployment.apps/carts restarted +$ kubectl -n carts rollout status deployment/carts +Waiting for deployment "carts" rollout to finish: 1 old replicas are pending termination... +deployment "carts" successfully rolled out +``` + +Now, let's verify if the DynamoDB permission issue that we had encountered has been resolved for the carts application in the next section. diff --git a/website/docs/fastpaths/developer/amazon-eks-pod-identity/using-dynamo.md b/website/docs/fastpaths/developer/amazon-eks-pod-identity/using-dynamo.md new file mode 100644 index 0000000000..e8f3bfd699 --- /dev/null +++ b/website/docs/fastpaths/developer/amazon-eks-pod-identity/using-dynamo.md @@ -0,0 +1,76 @@ +--- +title: "Using Amazon DynamoDB" +sidebar_position: 32 +--- + +The first step in this process is to re-configure the carts service to use a DynamoDB table that has already been created for us. The application loads most of its configurations from a ConfigMap. Let's take look at it: + +```bash +$ kubectl -n carts get -o yaml cm carts +apiVersion: v1 +data: + AWS_ACCESS_KEY_ID: key + AWS_SECRET_ACCESS_KEY: secret + RETAIL_CART_PERSISTENCE_DYNAMODB_CREATE_TABLE: "true" + RETAIL_CART_PERSISTENCE_DYNAMODB_ENDPOINT: http://carts-dynamodb:8000 + RETAIL_CART_PERSISTENCE_DYNAMODB_TABLE_NAME: Items + RETAIL_CART_PERSISTENCE_PROVIDER: dynamodb +kind: ConfigMap +metadata: + name: carts + namespace: carts +``` + +The following kustomization overwrites the ConfigMap removing the DynamoDB endpoint configuration. It tells the SDK to use the real DynamoDB service instead of our test Pod. We've also configured the DynamoDB table name that's already been created for us. The table name is being pulled from the environment variable `RETAIL_CART_PERSISTENCE_DYNAMODB_TABLE_NAME`. + +```kustomization +modules/security/eks-pod-identity/dynamo/kustomization.yaml +ConfigMap/carts +``` + +Let's check the value of `CARTS_DYNAMODB_TABLENAME` then run Kustomize to use the real DynamoDB service: + +```bash +$ echo $CARTS_DYNAMODB_TABLENAME +eks-workshop-carts +$ kubectl kustomize ~/environment/eks-workshop/modules/security/eks-pod-identity/dynamo \ + | envsubst | kubectl apply -f- +``` + +This will overwrite our ConfigMap with new values: + +```bash +$ kubectl -n carts get cm carts -o yaml +apiVersion: v1 +data: + RETAIL_CART_PERSISTENCE_DYNAMODB_TABLE_NAME: eks-workshop-carts + RETAIL_CART_PERSISTENCE_PROVIDER: dynamodb +kind: ConfigMap +metadata: + labels: + app: carts + name: carts + namespace: carts +``` + +Now, we need to recycle all the carts pods to pick up our new ConfigMap contents: + +```bash expectError=true hook=enable-dynamo +$ kubectl rollout restart -n carts deployment/carts +deployment.apps/carts restarted +$ kubectl rollout status -n carts deployment/carts --timeout=20s +Waiting for deployment "carts" rollout to finish: 1 old replicas are pending termination... +error: timed out waiting for the condition +``` + +It looks like our change failed to deploy properly. We can confirm this by looking at the Pods: + +```bash +$ kubectl -n carts get pod +NAME READY STATUS RESTARTS AGE +carts-5d486d7cf7-8qxf9 1/1 Running 0 5m49s +carts-df76875ff-7jkhr 0/1 CrashLoopBackOff 3 (36s ago) 2m2s +carts-dynamodb-698674dcc6-hw2bg 1/1 Running 0 20m +``` + +What's gone wrong? diff --git a/website/docs/fastpaths/developer/amazon-eks-pod-identity/verifying-dynamo.md b/website/docs/fastpaths/developer/amazon-eks-pod-identity/verifying-dynamo.md new file mode 100644 index 0000000000..6f496091ad --- /dev/null +++ b/website/docs/fastpaths/developer/amazon-eks-pod-identity/verifying-dynamo.md @@ -0,0 +1,35 @@ +--- +title: "Verifying DynamoDB access" +sidebar_position: 35 +--- + +Now, with the `carts` Service Account associated with the authorized IAM role, the `carts` Pod has permission to access the DynamoDB table. Access the web store again and navigate to the shopping cart. + +```bash +$ ALB_HOSTNAME=$(kubectl get ingress ui -n ui -o yaml | yq .status.loadBalancer.ingress[0].hostname) +$ echo "http://$ALB_HOSTNAME" +http://k8s-ui-ui-a9797f0f61.elb.us-west-2.amazonaws.com +``` + +The `carts` Pod is able to reach the DynamoDB service and the shopping cart is now accessible! + +![Cart](/img/sample-app-screens/shopping-cart.webp) + +After the AWS IAM role is associated with the Service Account, any newly created Pods using that Service Account will be intercepted by the [EKS Pod Identity webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). This webhook runs on the Amazon EKS cluster's control plane and is fully managed by AWS. Take a closer look at the new `carts` Pod to see the new environment variables: + +```bash +$ kubectl -n carts exec deployment/carts -- env | grep AWS +AWS_STS_REGIONAL_ENDPOINTS=regional +AWS_DEFAULT_REGION=us-west-2 +AWS_REGION=us-west-2 +AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials +AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token +``` + +Notable points about these environment variables: + +- `AWS_DEFAULT_REGION` - The region is set automatically to the same as our EKS cluster +- `AWS_STS_REGIONAL_ENDPOINTS` - Regional STS endpoints are configured to avoid putting too much pressure on the global endpoint in `us-east-1` +- `AWS_CONTAINER_CREDENTIALS_FULL_URI` - This variable tells AWS SDKs how to obtain credentials using the [HTTP credential provider](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html). This means that EKS Pod Identity does not need to inject credentials via something like an `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` pair, and instead the SDKs can have temporary credentials vended to them via the EKS Pod Identity mechanism. You can read more about how this functions in the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + +You have successfully configured Pod Identity in your application. diff --git a/website/docs/fastpaths/developer/efs/assets/efs-storage.webp b/website/docs/fastpaths/developer/efs/assets/efs-storage.webp new file mode 100644 index 0000000000000000000000000000000000000000..a7612185e47b4ee660d39bfce6ac905a51b52ba4 GIT binary patch literal 11390 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 literal 0 HcmV?d00001 diff --git a/website/docs/fastpaths/developer/efs/assets/placeholder.jpg b/website/docs/fastpaths/developer/efs/assets/placeholder.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3bc3cbfec2a5a20896c412ea58dcd7e48e8a4c2f GIT binary patch literal 7575 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 literal 0 HcmV?d00001 diff --git a/website/docs/fastpaths/developer/efs/deployment-with-efs.md b/website/docs/fastpaths/developer/efs/deployment-with-efs.md new file mode 100644 index 0000000000..6c0dbbc870 --- /dev/null +++ b/website/docs/fastpaths/developer/efs/deployment-with-efs.md @@ -0,0 +1,160 @@ +--- +title: Dynamic provisioning using EFS +sidebar_position: 30 +--- + +Now that we understand the EFS storage class for Kubernetes, let's create a [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) and modify the UI component to mount this volume. + +First, let's examine the `efspvclaim.yaml` file: + +::yaml{file="manifests/modules/fundamentals/storage/efs/deployment/efspvclaim.yaml" paths="kind,spec.storageClassName,spec.resources.requests.storage"} + +1. The resource being defined is a PersistentVolumeClaim +2. This refers to the `efs-sc` storage class we created earlier +3. We are requesting 5GB of storage + +Now we'll update the UI component to reference the EFS PVC: + +```kustomization +modules/fundamentals/storage/efs/deployment/deployment.yaml +Deployment/ui +``` + +Apply these changes with the following command: + +```bash hook=efs-deployment +$ kubectl apply -k ~/environment/eks-workshop/modules/fundamentals/storage/efs/deployment +namespace/ui unchanged +serviceaccount/ui unchanged +configmap/ui unchanged +service/ui unchanged +persistentvolumeclaim/efs-claim created +deployment.apps/ui configured +$ kubectl rollout status --timeout=130s deployment/ui -n ui +``` + +Let's examine the `volumeMounts` in the deployment. Notice that our new volume named `efsvolume` is mounted at `/efs`: + +```bash +$ kubectl get deployment -n ui \ + -o yaml | yq '.items[].spec.template.spec.containers[].volumeMounts' +- mountPath: /efs + name: efsvolume +- mountPath: /tmp + name: tmp-volume +``` + +A PersistentVolume (PV) has been automatically created to fulfill our PersistentVolumeClaim (PVC): + +```bash +$ kubectl get pv +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +pvc-342a674d-b426-4214-b8b6-7847975ae121 5Gi RWX Delete Bound ui/efs-claim efs-sc 2m33s +``` + +Let's examine the details of our PersistentVolumeClaim (PVC): + +```bash +$ kubectl describe pvc -n ui +Name: efs-claim +Namespace: ui +StorageClass: efs-sc +Status: Bound +Volume: pvc-342a674d-b426-4214-b8b6-7847975ae121 +Labels: +Annotations: pv.kubernetes.io/bind-completed: yes + pv.kubernetes.io/bound-by-controller: yes + volume.beta.kubernetes.io/storage-provisioner: efs.csi.aws.com + volume.kubernetes.io/storage-provisioner: efs.csi.aws.com +Finalizers: [kubernetes.io/pvc-protection] +Capacity: 5Gi +Access Modes: RWX +VolumeMode: Filesystem +Used By: +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal ExternalProvisioning 34s persistentvolume-controller waiting for a volume to be created, either by external provisioner "efs.csi.aws.com" or manually created by system administrator + Normal Provisioning 34s efs.csi.aws.com_efs-csi-controller-6b4ff45b65-fzqjb_7efe91cc-099a-45c7-8419-6f4b0a4f9e01 External provisioner is provisioning volume for claim "ui/efs-claim" + Normal ProvisioningSucceeded 33s efs.csi.aws.com_efs-csi-controller-6b4ff45b65-fzqjb_7efe91cc-099a-45c7-8419-6f4b0a4f9e01 Successfully provisioned volume pvc-342a674d-b426-4214-b8b6-7847975ae121 +``` + +At this point, the EFS file system is successfully mounted but currently empty: + +```bash +$ POD_1=$(kubectl -n ui get pods -l app.kubernetes.io/instance=ui -o jsonpath='{.items[0].metadata.name}') +$ kubectl exec --stdin $POD_1 -n ui -- bash -c 'ls /efs/' +``` + +Let's use a [Kubernetes Job](https://kubernetes.io/docs/concepts/workloads/controllers/job/) to populate the EFS volume with images: + +```bash +$ export PVC_NAME="efs-claim" +$ cat ~/environment/eks-workshop/modules/fundamentals/storage/populate-images-job.yaml | envsubst | kubectl apply -f - +$ kubectl wait --for=condition=complete -n ui \ + job/populate-images --timeout=300s +``` + +Now let's demonstrate the shared storage functionality by listing the current files in `/efs` through one of the UI component Pods: + +```bash +$ POD_1=$(kubectl -n ui get pods -l app.kubernetes.io/instance=ui -o jsonpath='{.items[0].metadata.name}') +$ kubectl exec --stdin $POD_1 -n ui -- bash -c 'ls /efs/' +1ca35e86-4b4c-4124-b6b5-076ba4134d0d.jpg +4f18544b-70a5-4352-8e19-0d070f46745d.jpg +631a3db5-ac07-492c-a994-8cd56923c112.jpg +79bce3f3-935f-4912-8c62-0d2f3e059405.jpg +8757729a-c518-4356-8694-9e795a9b3237.jpg +87e89b11-d319-446d-b9be-50adcca5224a.jpg +a1258cd2-176c-4507-ade6-746dab5ad625.jpg +cc789f85-1476-452a-8100-9e74502198e0.jpg +d27cf49f-b689-4a75-a249-d373e0330bb5.jpg +d3104128-1d14-4465-99d3-8ab9267c687b.jpg +d4edfedb-dbe9-4dd9-aae8-009489394955.jpg +d77f9ae6-e9a8-4a3e-86bd-b72af75cbc49.jpg +``` + +To further demonstrate the shared storage capabilities, let's create a new image called `placeholder.jpg` and add it to the EFS volume through the first Pod: + +```bash +$ POD_1=$(kubectl -n ui get pods -l app.kubernetes.io/instance=ui -o jsonpath='{.items[0].metadata.name}') +$ kubectl exec --stdin $POD_1 -n ui -- bash -c 'curl -sS -o /efs/placeholder.jpg https://placehold.co/600x400/jpg?text=EKS+Workshop\\nPlaceholder' +``` + +Now we'll verify that the second UI Pod can access this newly created file, demonstrating the shared nature of our EFS storage: + +```bash hook=sample-images +$ POD_2=$(kubectl -n ui get pods -o jsonpath='{.items[1].metadata.name}') +$ kubectl exec --stdin $POD_2 -n ui -- bash -c 'ls /efs/' +1ca35e86-4b4c-4124-b6b5-076ba4134d0d.jpg +4f18544b-70a5-4352-8e19-0d070f46745d.jpg +631a3db5-ac07-492c-a994-8cd56923c112.jpg +79bce3f3-935f-4912-8c62-0d2f3e059405.jpg +8757729a-c518-4356-8694-9e795a9b3237.jpg +87e89b11-d319-446d-b9be-50adcca5224a.jpg +a1258cd2-176c-4507-ade6-746dab5ad625.jpg +cc789f85-1476-452a-8100-9e74502198e0.jpg +d27cf49f-b689-4a75-a249-d373e0330bb5.jpg +d3104128-1d14-4465-99d3-8ab9267c687b.jpg +d4edfedb-dbe9-4dd9-aae8-009489394955.jpg +d77f9ae6-e9a8-4a3e-86bd-b72af75cbc49.jpg +placeholder.jpg <---------------- +``` + +As you can see, even though we created the file through the first Pod, the second Pod has immediate access to it because they're both accessing the same shared EFS file system. + +Finally, let's confirm that the image is accessible through the UI service: + +```bash hook=placeholder +$ ALB_HOSTNAME=$(kubectl get ingress ui -n ui -o yaml | yq .status.loadBalancer.ingress[0].hostname) +$ echo "http://$ALB_HOSTNAME/assets/img/products/placeholder.jpg" +http://k8s-ui-ui-a9797f0f61.elb.us-west-2.amazonaws.com/assets/img/products/placeholder.jpg +``` + +Visit the URL in your browser: + + + + + +We've successfully demonstrated how Amazon EFS provides persistent shared storage for workloads running on Amazon EKS. This solution allows multiple pods to read from and write to the same storage volume simultaneously, making it ideal for shared content hosting and other use cases requiring distributed file system access. diff --git a/website/docs/fastpaths/developer/efs/efs-csi-driver.md b/website/docs/fastpaths/developer/efs/efs-csi-driver.md new file mode 100644 index 0000000000..7251e11b69 --- /dev/null +++ b/website/docs/fastpaths/developer/efs/efs-csi-driver.md @@ -0,0 +1,81 @@ +--- +title: EFS CSI Driver +sidebar_position: 20 +--- + +Before diving into this section, you should be familiar with the Kubernetes storage objects (volumes, persistent volumes (PV), persistent volume claims (PVC), dynamic provisioning and ephemeral storage) that were introduced in the main [Storage](../index.md) section. + +The [Amazon Elastic File System Container Storage Interface (CSI) Driver](https://github.com/kubernetes-sigs/aws-efs-csi-driver) enables you to run stateful containerized applications by providing a CSI interface that allows Kubernetes clusters running on AWS to manage the lifecycle of Amazon EFS file systems. + +The following architecture diagram illustrates how we will use EFS as persistent storage for our EKS pods: + +![Assets with EFS](./assets/efs-storage.webp) + +To utilize Amazon EFS with dynamic provisioning on our EKS cluster, we first need to confirm that we have the EFS CSI Driver installed. The driver implements the CSI specification which allows container orchestrators to manage Amazon EFS file systems throughout their lifecycle. + +For improved security and simplified management, you can run the Amazon EFS CSI driver as an Amazon EKS add-on. Since the required IAM role has already been created for us, we can proceed with installing the add-on: + +```bash timeout=300 wait=60 +$ aws eks create-addon --cluster-name $EKS_CLUSTER_NAME --addon-name aws-efs-csi-driver \ + --service-account-role-arn $EFS_CSI_ADDON_ROLE +$ aws eks wait addon-active --cluster-name $EKS_CLUSTER_NAME --addon-name aws-efs-csi-driver +``` + +Let's examine what the add-on has created in our EKS cluster. For example, a DaemonSet that runs a Pod on each node in our cluster: + +```bash +$ kubectl get daemonset efs-csi-node -n kube-system +NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE +efs-csi-node 3 3 3 3 3 kubernetes.io/os=linux 47s +``` + +The EFS CSI driver supports both dynamic and static provisioning: + +- **Dynamic provisioning**: The driver creates an access point for each PersistentVolume. This requires an existing AWS EFS file system that must be specified in the StorageClass parameters. +- **Static provisioning**: This also requires a pre-created AWS EFS file system, which can then be mounted as a volume inside a container using the driver. + +An EFS file system has been provisioned for us, along with mount targets and the required security group that includes an inbound rule allowing NFS traffic to the EFS mount points. Let's get its ID which we'll need later: + +```bash +$ export EFS_ID=$(aws efs describe-file-systems --query "FileSystems[?Name=='$EKS_CLUSTER_NAME-efs-assets'] | [0].FileSystemId" --output text) +$ echo $EFS_ID +fs-061cb5c5ed841a6b0 +``` + +Next, we'll create a [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) object configured to use our pre-provisioned EFS file system and [EFS Access points](https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html) in provisioning mode using the `efsstorageclass.yaml` file. + +::yaml{file="manifests/modules/fundamentals/storage/efs/storageclass/efsstorageclass.yaml" paths="provisioner,parameters.fileSystemId"} + +1. Set the `provisioner` parameter to `efs.csi.aws.com` for the EFS CSI provisioner +2. Inject `EFS_ID` environment variable into the `filesystemid` parameter + + +Apply the kustomization: + +```bash +$ kubectl kustomize ~/environment/eks-workshop/modules/fundamentals/storage/efs/storageclass \ + | envsubst | kubectl apply -f- +storageclass.storage.k8s.io/efs-sc created +``` + +Let's examine the StorageClass. Note that it uses the EFS CSI driver as the provisioner and is configured for EFS access point provisioning mode with the file system ID we exported earlier: + +```bash +$ kubectl get storageclass +NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE +efs-sc efs.csi.aws.com Delete Immediate false 8m29s +$ kubectl describe sc efs-sc +Name: efs-sc +IsDefaultClass: No +Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"efs-sc"},"parameters":{"directoryPerms":"700","fileSystemId":"fs-061cb5c5ed841a6b0","provisioningMode":"efs-ap"},"provisioner":"efs.csi.aws.com"} + +Provisioner: efs.csi.aws.com +Parameters: directoryPerms=700,fileSystemId=fs-061cb5c5ed841a6b0,provisioningMode=efs-ap +AllowVolumeExpansion: +MountOptions: +ReclaimPolicy: Delete +VolumeBindingMode: Immediate +Events: +``` + +Now that we understand the EFS StorageClass and how the EFS CSI driver works, we're ready to proceed to the next step where we'll modify the UI component to use the EFS `StorageClass` with Kubernetes dynamic volume provisioning and a PersistentVolume for storing product images. diff --git a/website/docs/fastpaths/developer/efs/existing-architecture.md b/website/docs/fastpaths/developer/efs/existing-architecture.md new file mode 100644 index 0000000000..be05ebdfcf --- /dev/null +++ b/website/docs/fastpaths/developer/efs/existing-architecture.md @@ -0,0 +1,40 @@ +--- +title: Existing architecture +sidebar_position: 10 +--- + +In this section, we'll explore how to handle storage in Kubernetes deployments using a simple image hosting example. We'll start with an existing deployment from our sample store application and modify it to serve as an image host. The UI component is a stateless microservice, which is an excellent example for demonstrating deployments since they enable **horizontal scaling** and **declarative state management** of Pods. + +One of the roles of the UI component is to serve static product images. Currently, these images are bundled into the container during the build process. However, this approach has a significant limitation - we're unable to add new images once the container is deployed. To address this limitation, we'll implement a solution using [Amazon Elastic File System](https://docs.aws.amazon.com/efs/latest/ug/whatisefs.html) and Kubernetes [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) to create a shared storage environment. This will allow multiple web server containers to serve assets while scaling dynamically to meet demand. + +Let's examine the current Deployment's volume configuration: + +```bash +$ kubectl describe deployment -n ui +Name: ui +Namespace: ui +[...] + Containers: + ui: + Image: public.ecr.aws/aws-containers/retail-store-sample-ui:1.2.1 + Port: 8080/TCP + Host Port: 0/TCP + Limits: + memory: 1536Mi + Requests: + cpu: 250 + memory: 1536Mi + [...] + Mounts: + /tmp from tmp-volume (rw) + Volumes: + tmp-volume: + Type: EmptyDir (a temporary directory that shares a pod's lifetime) + Medium: Memory + SizeLimit: +[...] +``` + +Looking at the [`Volumes`](https://kubernetes.io/docs/concepts/storage/volumes/#emptydir-configuration-example) section, we can see that the Deployment currently uses an [EmptyDir volume type](https://kubernetes.io/docs/concepts/storage/volumes/#emptydir) that exists only for the Pod's lifetime. This means that when the Pod is terminated, the data stored in this volume is permanently lost. + +However, in the case of the UI component, the product images are currently being served as [static web content](https://spring.io/blog/2013/12/19/serving-static-web-content-with-spring-boot) via Spring Boot, so the images are not even present on the filesystem. diff --git a/website/docs/fastpaths/developer/efs/index.md b/website/docs/fastpaths/developer/efs/index.md new file mode 100644 index 0000000000..530841f739 --- /dev/null +++ b/website/docs/fastpaths/developer/efs/index.md @@ -0,0 +1,23 @@ +--- +title: Workload storage with Amazon EFS +sidebar_position: 40 +description: "Serverless, fully elastic file storage for workloads on Amazon Elastic Kubernetes Service with Amazon Elastic File System." +--- + +:::tip What's been set up for you +The environment preparation stage made the following changes to your lab environment: + +- Create an IAM role for the Amazon EFS CSI driver +- Create an Amazon EFS file system + +::: + +[Amazon Elastic File System](https://docs.aws.amazon.com/efs/latest/ug/whatisefs.html) (Amazon EFS) provides a serverless, fully elastic file system that automatically scales on demand to petabytes without disrupting applications. It eliminates the need to provision and manage capacity as you add and remove files, making it ideal for use with AWS Cloud services and on-premises resources. + +In this lab, you will: + +- Learn about persistent network storage +- Configure and deploy the EFS CSI Driver for Kubernetes +- Implement dynamic provisioning using EFS in a Kubernetes deployment + +This hands-on experience will demonstrate how to effectively use Amazon EFS with Amazon EKS for scalable, persistent storage solutions. diff --git a/website/docs/fastpaths/developer/efs/tests/hook-efs-deployment.sh b/website/docs/fastpaths/developer/efs/tests/hook-efs-deployment.sh new file mode 100644 index 0000000000..74b7cac66d --- /dev/null +++ b/website/docs/fastpaths/developer/efs/tests/hook-efs-deployment.sh @@ -0,0 +1,23 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + sleep 60 + + EXIT_CODE=0 + + timeout -s TERM 60 bash -c \ + 'while [[ $(kubectl get pod -l app.kubernetes.io/name=ui -n ui -o json | jq -r ".items | length") -lt 2 ]];\ + do sleep 30;\ + done' || EXIT_CODE=$? + + if [ $EXIT_CODE -ne 0 ]; then + >&2 echo "UI service did not deploy in 60 seconds" + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/efs/tests/hook-placeholder.sh b/website/docs/fastpaths/developer/efs/tests/hook-placeholder.sh new file mode 100644 index 0000000000..cd58bf2e1c --- /dev/null +++ b/website/docs/fastpaths/developer/efs/tests/hook-placeholder.sh @@ -0,0 +1,21 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + export ui_endpoint=$(kubectl get ingress ui -n ui -o yaml | yq .status.loadBalancer.ingress[0].hostname) + + if [ -z "$ui_endpoint" ]; then + >&2 echo "Failed to retrieve LB hostname" + exit 1 + fi + + if [[ "$(curl -s -o /dev/null -L -w ''%{http_code}'' ${ui_endpoint}/assets/img/products/placeholder.jpg)" != "200" ]]; then + >&2 echo "Expected placeholder image not available" + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/efs/tests/hook-sample-images.sh b/website/docs/fastpaths/developer/efs/tests/hook-sample-images.sh new file mode 100644 index 0000000000..323e30452c --- /dev/null +++ b/website/docs/fastpaths/developer/efs/tests/hook-sample-images.sh @@ -0,0 +1,15 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + if [[ $TEST_OUTPUT != *"1ca35e86-4b4c-4124-b6b5-076ba4134d0d.jpg"* ]]; then + >&2 echo "Failed to match expected output" + echo $TEST_OUTPUT + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/efs/tests/hook-suite.sh b/website/docs/fastpaths/developer/efs/tests/hook-suite.sh new file mode 100644 index 0000000000..8b5a4baea5 --- /dev/null +++ b/website/docs/fastpaths/developer/efs/tests/hook-suite.sh @@ -0,0 +1,11 @@ +set -e + +before() { + echo "noop" +} + +after() { + prepare-environment +} + +"$@" diff --git a/website/docs/fastpaths/developer/getting-started/about.md b/website/docs/fastpaths/developer/getting-started/about.md new file mode 100644 index 0000000000..4bb4819b4b --- /dev/null +++ b/website/docs/fastpaths/developer/getting-started/about.md @@ -0,0 +1,26 @@ +--- +title: Sample application +sidebar_position: 10 +--- + +Most of the labs in this workshop use a common sample application to provide actual container components that we can work on during the exercises. The sample application models a simple web store application, where customers can browse a catalog, add items to their cart and complete an order through the checkout process. + + + + + +The application has several components and dependencies: + + + +| Component | Description | +| --------- | --------------------------------------------------------------------------------------------- | +| UI | Provides the front end user interface and aggregates API calls to the various other services. | +| Catalog | API for product listings and details | +| Cart | API for customer shopping carts | +| Checkout | API to orchestrate the checkout process | +| Orders | API to receive and process customer orders | + +Initially we'll deploy the application in a manner that is self-contained in the Amazon EKS cluster, without using any AWS services like load balancers or a managed database. Over the course of the labs we'll leverage different features of EKS to take advantage of broader AWS services and features for our retail store. + +You can find the full source code for the sample application on [GitHub](https://github.com/aws-containers/retail-store-sample-app). diff --git a/website/docs/fastpaths/developer/getting-started/assets/catalog-microservice.webp b/website/docs/fastpaths/developer/getting-started/assets/catalog-microservice.webp new file mode 100644 index 0000000000000000000000000000000000000000..3e213f94819daffd6a41940319b3f6ff9bd6926b GIT binary patch literal 15136 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 literal 0 HcmV?d00001 diff --git a/website/docs/fastpaths/developer/getting-started/assets/ide-initial.webp b/website/docs/fastpaths/developer/getting-started/assets/ide-initial.webp new file mode 100644 index 0000000000000000000000000000000000000000..f1861e65295a0e7a6a9128c09c01ddf7ed4616f3 GIT binary patch literal 3864 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 aLv^AQVIcSVO1w1hG}mdVPvKQS)_*Z&FrW8U}w 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
  • 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 uu$FIRyEfm52G_MF%N0001KF({M( literal 0 HcmV?d00001 diff --git a/website/docs/fastpaths/developer/getting-started/assets/microservices.webp b/website/docs/fastpaths/developer/getting-started/assets/microservices.webp new file mode 100644 index 0000000000000000000000000000000000000000..e00e007ee1261aea106e4dbddde71ee08f002527 GIT binary patch literal 26046 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 sJh5SlL^PBbOwo4za=$u@4oGLRAA#39(st$ 80/TCP 2m48s +catalog-mysql ClusterIP 172.20.181.252 3306/TCP 2m48s +``` + +These Services are internal to the cluster, so we cannot access them from the Internet or even the VPC. However, we can use [exec](https://kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/) to access an existing Pod in the EKS cluster to check the catalog API is working: + +```bash +$ kubectl -n catalog exec -i \ + deployment/catalog -- curl catalog.catalog.svc/catalog/products | jq . +``` + +You should receive back a JSON payload with product information. Congratulations, you've just deployed your first microservice to Kubernetes with EKS! diff --git a/website/docs/fastpaths/developer/getting-started/index.md b/website/docs/fastpaths/developer/getting-started/index.md new file mode 100644 index 0000000000..56e5c40716 --- /dev/null +++ b/website/docs/fastpaths/developer/getting-started/index.md @@ -0,0 +1,9 @@ +--- +title: Getting started +sidebar_position: 10 +description: "Learn the basics of running workloads on Amazon Elastic Kubernetes Service." +--- + +Welcome to the first hands-on lab in the EKS workshop. The goal of this exercise is to familiarize ourselves with the sample application we'll use for many of the coming lab exercises and in doing so touch on some basic concepts related to deploying workloads to EKS. We'll explore the architecture of the application and deploy out the components to our EKS cluster. + +Let's deploy your first workload to the EKS cluster in your lab environment and explore! diff --git a/website/docs/fastpaths/developer/getting-started/microservices.md b/website/docs/fastpaths/developer/getting-started/microservices.md new file mode 100644 index 0000000000..b806c8d12a --- /dev/null +++ b/website/docs/fastpaths/developer/getting-started/microservices.md @@ -0,0 +1,22 @@ +--- +title: Microservices on Kubernetes +sidebar_position: 30 +--- + +Now that we're familiar with the overall architecture of the sample application, how will we initially deploy this in to EKS? Let's explore some of the Kubernetes building blocks by looking at the **catalog** component: + +![Catalog microservice in Kubernetes](./assets/catalog-microservice.webp) + +There are a number of things to consider in this diagram: + +- The application that provides the catalog API runs as a [Pod](https://kubernetes.io/docs/concepts/workloads/pods/), which is the smallest deployable unit in Kubernetes. Application Pods will run the container images we outlined in the previous section. +- The Pods that run for the catalog component are created by a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) which may manage one or more "replicas" of the catalog Pod, allowing it to scale horizontally. +- A [Service](https://kubernetes.io/docs/concepts/services-networking/service/) is an abstract way to expose an application running as a set of Pods, and this allows our catalog API to be called by other components inside the Kubernetes cluster. Each Service is given its own DNS entry. +- We're starting this workshop with a MySQL database that runs inside our Kubernetes cluster as a [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/), which is designed to manage stateful workloads. +- All of these Kubernetes constructs are grouped in their own dedicated catalog Namespace. Each of the application components has its own Namespace. + +Each of the components in the microservices architecture is conceptually similar to the catalog, using Deployments to manage application workload Pods and Services to route traffic to those Pods. If we expand out our view of the architecture we can consider how traffic is routed throughout the broader system: + +![Microservices in Kubernetes](./assets/microservices.webp) + +The **ui** component receives HTTP requests from, for example, a user's browser. It then makes HTTP requests to other API components in the architecture to fulfill that request and returns a response to the user. Each of the downstream components may have their own data stores or other infrastructure. The Namespaces are a logical grouping of the resources for each microservice and also act as a soft isolation boundary, which can be used to effectively implement controls using Kubernetes RBAC and Network Policies. diff --git a/website/docs/fastpaths/developer/getting-started/packaging-application.md b/website/docs/fastpaths/developer/getting-started/packaging-application.md new file mode 100644 index 0000000000..dfb8d8adcd --- /dev/null +++ b/website/docs/fastpaths/developer/getting-started/packaging-application.md @@ -0,0 +1,16 @@ +--- +title: Packaging the components +sidebar_position: 20 +--- + +Before a workload can be deployed to a Kubernetes distribution like EKS it first must be packaged as a container image and published to a container registry. Basic container topics like this are not covered as part of this workshop, and the sample application has container images already available in Amazon Elastic Container Registry for the labs we'll complete today. + +The table below provides links to the ECR Public repository for each component, as well as the `Dockerfile` that was used to build each component. + +| Component | ECR Public repository | Dockerfile | +| ------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| UI | [Repository](https://gallery.ecr.aws/aws-containers/retail-store-sample-ui) | [Dockerfile](https://github.com/aws-containers/retail-store-sample-app/blob/v1.2.1/src/ui/Dockerfile) | +| Catalog | [Repository](https://gallery.ecr.aws/aws-containers/retail-store-sample-catalog) | [Dockerfile](https://github.com/aws-containers/retail-store-sample-app/blob/v1.2.1/src/catalog/Dockerfile) | +| Shopping cart | [Repository](https://gallery.ecr.aws/aws-containers/retail-store-sample-cart) | [Dockerfile](https://github.com/aws-containers/retail-store-sample-app/blob/v1.2.1/src/cart/Dockerfile) | +| Checkout | [Repository](https://gallery.ecr.aws/aws-containers/retail-store-sample-checkout) | [Dockerfile](https://github.com/aws-containers/retail-store-sample-app/blob/v1.2.1/src/checkout/Dockerfile) | +| Orders | [Repository](https://gallery.ecr.aws/aws-containers/retail-store-sample-orders) | [Dockerfile](https://github.com/aws-containers/retail-store-sample-app/blob/v1.2.1/src/orders/Dockerfile) | diff --git a/website/docs/fastpaths/developer/index.md b/website/docs/fastpaths/developer/index.md new file mode 100644 index 0000000000..c246daf7eb --- /dev/null +++ b/website/docs/fastpaths/developer/index.md @@ -0,0 +1,23 @@ +--- +title: "Fast path - Developers" +chapter: true +--- + +::required-time + +:::tip Before you start +Prepare your environment for this section: + +```bash timeout=300 wait=30 +$ prepare-environment fast-path/developers +``` + +Each section of this lab will outline what resources have been setup for your convenience. + +You can view the Terraform that applies these changes [here](https://github.com/VAR::MANIFESTS_OWNER/VAR::MANIFESTS_REPOSITORY/tree/VAR::MANIFESTS_REF/manifests/modules/fast-path/developers/.workshop/terraform). + +::: + +Welcome to the EKS Workshop fast path for developers! This is a collection of labs from EKS Workshop optimized for developers to learn the feature of Amazon EKS we see most often required as they deploy workloads to this AWS service. + +Thoughout this series of exercises you'll learn diff --git a/website/docs/fastpaths/developer/ingress/adding-ingress.md b/website/docs/fastpaths/developer/ingress/adding-ingress.md new file mode 100644 index 0000000000..206d78d18a --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/adding-ingress.md @@ -0,0 +1,124 @@ +--- +title: "Creating the Ingress" +sidebar_position: 20 +--- + +Let's create an Ingress resource with the following configuration: + +::yaml{file="manifests/modules/exposing/ingress/creating-ingress/ingress.yaml" paths="kind,metadata.annotations,spec.rules.0"} + +1. Use an `Ingress` kind +2. We can use annotations to configure various behavior of the ALB thats created such as the health checks it performs on the target pods +3. The rules section is used to express how the ALB should route traffic. In this example we route all HTTP requests where the path starts with `/` to the Kubernetes service called `ui` on port 80 + +Apply this configuration: + +```bash timeout=180 hook=add-ingress hookTimeout=430 +$ kubectl apply -k ~/environment/eks-workshop/modules/exposing/ingress/creating-ingress +``` + +Let's inspect the Ingress object created: + +```bash +$ kubectl get ingress ui -n ui +NAME CLASS HOSTS ADDRESS PORTS AGE +ui alb * k8s-ui-ui-1268651632.us-west-2.elb.amazonaws.com 80 15s +``` + +The ALB will take several minutes to provision and register its targets so take some time to take a closer look at the ALB provisioned for this Ingress to see how its configured: + +```bash +$ aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-ui-ui`) == `true`]' +[ + { + "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-west-2:1234567890:loadbalancer/app/k8s-ui-ui-cb8129ddff/f62a7bc03db28e7c", + "DNSName": "k8s-ui-ui-cb8129ddff-1888909706.us-west-2.elb.amazonaws.com", + "CanonicalHostedZoneId": "Z1H1FL5HABSF5", + "CreatedTime": "2022-09-30T03:40:00.950000+00:00", + "LoadBalancerName": "k8s-ui-ui-cb8129ddff", + "Scheme": "internet-facing", + "VpcId": "vpc-0851f873025a2ece5", + "State": { + "Code": "active" + }, + "Type": "application", + "AvailabilityZones": [ + { + "ZoneName": "us-west-2b", + "SubnetId": "subnet-00415f527bbbd999b", + "LoadBalancerAddresses": [] + }, + { + "ZoneName": "us-west-2a", + "SubnetId": "subnet-0264d4b9985bd8691", + "LoadBalancerAddresses": [] + }, + { + "ZoneName": "us-west-2c", + "SubnetId": "subnet-05cda6deed7f3da65", + "LoadBalancerAddresses": [] + } + ], + "SecurityGroups": [ + "sg-0f8e704ee37512eb2", + "sg-02af06ec605ef8777" + ], + "IpAddressType": "ipv4" + } +] +``` + +What does this tell us? + +- The ALB is accessible over the public internet +- It uses the public subnets in our VPC + +Inspect the targets in the target group that was created by the controller: + +```bash +$ ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-ui-ui`) == `true`].LoadBalancerArn' | jq -r '.[0]') +$ TARGET_GROUP_ARN=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn') +$ aws elbv2 describe-target-health --target-group-arn $TARGET_GROUP_ARN +{ + "TargetHealthDescriptions": [ + { + "Target": { + "Id": "10.42.180.183", + "Port": 8080, + "AvailabilityZone": "us-west-2c" + }, + "HealthCheckPort": "8080", + "TargetHealth": { + "State": "healthy" + } + } + ] +} +``` + +Since we specified using IP mode in our Ingress object, the target is registered using the IP address of the `ui` pod and the port on which it serves traffic. + +You can also inspect the ALB and its target groups in the console by clicking this link: + + + +Get the URL from the Ingress resource: + +```bash +$ ADDRESS=$(kubectl get ingress -n ui ui -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") +$ echo "http://${ADDRESS}" +http://k8s-ui-ui-a9797f0f61.elb.us-west-2.amazonaws.com +``` + +To wait until the load balancer has finished provisioning you can run this command: + +```bash +$ curl --head -X GET --retry 30 --retry-all-errors --retry-delay 15 --connect-timeout 30 --max-time 60 \ + -k $(kubectl get ingress -n ui ui -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") +``` + +And access it in your web browser. You will see the UI from the web store displayed and will be able to navigate around the site as a user. + + + + diff --git a/website/docs/fastpaths/developer/ingress/assets/multiple-ingress-lb.webp b/website/docs/fastpaths/developer/ingress/assets/multiple-ingress-lb.webp new file mode 100644 index 0000000000000000000000000000000000000000..c7f21b056bccc2be7bf75661ba90903f515d0dc0 GIT binary patch literal 6372 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 by;$}=h+5=6w`=`NmG@9}FQftBKl48T3;~bo literal 0 HcmV?d00001 diff --git a/website/docs/fastpaths/developer/ingress/assets/multiple-ingress-rules.webp b/website/docs/fastpaths/developer/ingress/assets/multiple-ingress-rules.webp new file mode 100644 index 0000000000000000000000000000000000000000..f809553601cd84467052bf1fd85e2fe1d7351ecb GIT binary patch literal 13180 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 literal 0 HcmV?d00001 diff --git a/website/docs/fastpaths/developer/ingress/assets/web-ui.webp b/website/docs/fastpaths/developer/ingress/assets/web-ui.webp new file mode 100644 index 0000000000000000000000000000000000000000..2221ddcbde23805a59ac1328d4a2d1adfef93eee GIT binary patch literal 18966 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 + +Route 53 private hosted zones are only accessible from associated VPCs, in this case the EKS cluster VPC. To test the DNS entry we'll use `curl` from inside a pod: + +```bash hook=dns-curl +$ kubectl -n ui exec -it \ + deployment/ui -- bash -c "curl -i http://ui.retailstore.com/actuator/health/liveness; echo" + +HTTP/1.1 200 OK +Date: Thu, 24 Apr 2025 07:45:12 GMT +Content-Type: application/vnd.spring-boot.actuator.v3+json +Content-Length: 15 +Connection: keep-alive +Set-Cookie: SESSIONID=c3f13e02-4ff3-40ba-866e-c777f7450997 + +{"status":"UP"} +``` diff --git a/website/docs/fastpaths/developer/ingress/index.md b/website/docs/fastpaths/developer/ingress/index.md new file mode 100644 index 0000000000..dafb3eff62 --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/index.md @@ -0,0 +1,21 @@ +--- +title: "Exposing workloads with Ingress" +chapter: true +sidebar_position: 20 +description: "Expose HTTP and HTTPS routes to the outside world using Ingress API on Amazon Elastic Kubernetes Service." +--- + +:::tip What's been set up for you +The environment preparation stage made the following changes to your lab environment: + +- Create an IAM role required by the AWS Load Balancer Controller +- Create an IAM role required by ExternalDNS +- Create an AWS Route 53 private hosted zone + +::: + +Right now our web store application is not exposed to the outside world, so there's no way for users to access it. Although there are many microservices in our web store workload, only the `ui` application needs to be available to end users. This is because the `ui` application will perform all communication to the other backend services using internal Kubernetes networking. + +Kubernetes Ingress is an API resource that allows you to manage external or internal HTTP(S) access to Kubernetes services running in a cluster. Amazon Elastic Load Balancing Application Load Balancer (ALB) is a popular AWS service that load balances incoming traffic at the application layer (layer 7) across multiple targets, such as Amazon EC2 instances, in a region. ALB supports multiple features including host or path based routing, TLS (Transport Layer Security) termination, WebSockets, HTTP/2, AWS WAF (Web Application Firewall) integration, integrated access logs, and health checks. + +In this lab exercise, we'll expose our sample application using an ALB with the Kubernetes ingress model. diff --git a/website/docs/fastpaths/developer/ingress/introduction.md b/website/docs/fastpaths/developer/ingress/introduction.md new file mode 100644 index 0000000000..7c49d32e7d --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/introduction.md @@ -0,0 +1,41 @@ +--- +title: "Introduction" +sidebar_position: 10 +--- + +First lets install the AWS Load Balancer controller using helm: + +```bash wait=10 +$ helm repo add eks-charts https://aws.github.io/eks-charts +$ helm upgrade --install aws-load-balancer-controller eks-charts/aws-load-balancer-controller \ + --version "${LBC_CHART_VERSION}" \ + --namespace "kube-system" \ + --set "clusterName=${EKS_CLUSTER_NAME}" \ + --set "serviceAccount.name=aws-load-balancer-controller-sa" \ + --set "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"="$LBC_ROLE_ARN" \ + --wait +Release "aws-load-balancer-controller" does not exist. Installing it now. +NAME: aws-load-balancer-controller +LAST DEPLOYED: [...] +NAMESPACE: kube-system +STATUS: deployed +REVISION: 1 +TEST SUITE: None +NOTES: +AWS Load Balancer controller installed! +``` + +Currently there are no Ingress resources in our cluster, which you can check with the following command: + +```bash expectError=true +$ kubectl get ingress -n ui +No resources found in ui namespace. +``` + +There are also no Service resources of type `LoadBalancer`, which you can confirm with the following command: + +```bash +$ kubectl get svc -n ui +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +ui ClusterIP 10.100.221.103 80/TCP 29m +``` diff --git a/website/docs/fastpaths/developer/ingress/multiple-ingress.md b/website/docs/fastpaths/developer/ingress/multiple-ingress.md new file mode 100644 index 0000000000..e086be6d4f --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/multiple-ingress.md @@ -0,0 +1,82 @@ +--- +title: "Multiple Ingress pattern" +sidebar_position: 30 +--- + +It's common to leverage multiple Ingress objects in the same EKS cluster, for example to expose multiple different workloads. By default each Ingress will result in the creation of a separate ALB, but we can leverage the IngressGroup feature which enables you to group multiple Ingress resources together. The controller will automatically merge Ingress rules for all Ingresses within IngressGroup and support them with a single ALB. In addition, most annotations defined on an Ingress only apply to the paths defined by that Ingress. + +In this example, we'll expose the `catalog` API out through the same ALB as the `ui` component, leveraging path-based routing to dispatch requests to the appropriate Kubernetes service. + +The first thing we'll do is create a new Ingress for the `ui` component adding the annotation `alb.ingress.kubernetes.io/group.name`: + +```file +manifests/modules/exposing/ingress/multiple-ingress/ingress-ui.yaml +``` + +Then we'll create a separate Ingress for the `catalog` component that also leverages the same `group.name`: + +```file +manifests/modules/exposing/ingress/multiple-ingress/ingress-catalog.yaml +``` + +This Ingress is also configuring rules to route requests prefixed with `/catalog` to the `catalog` component. + +Apply these manifests to the cluster: + +```bash wait=60 +$ kubectl apply -k ~/environment/eks-workshop/modules/exposing/ingress/multiple-ingress +``` + +We'll now have two additional Ingress objects in our cluster that end with `-multi`: + +```bash +$ kubectl get ingress -l app.kubernetes.io/created-by=eks-workshop -A +NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE +catalog-multi catalog alb * k8s-retailappgroup-2c24c1c4bc-17962260.us-west-2.elb.amazonaws.com 80 2m21s +ui-multi ui alb * k8s-retailappgroup-2c24c1c4bc-17962260.us-west-2.elb.amazonaws.com 80 2m21s +ui ui alb * k8s-ui-ui-1268651632.us-west-2.elb.amazonaws.com 80 4m3s +``` + +Notice that the `ADDRESS` of both are the same URL, which is because both of these Ingress objects are being grouped together behind the same ALB. + +We can take a look at the ALB listener to see how this works: + +```bash +$ ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-retailappgroup`) == `true`].LoadBalancerArn' | jq -r '.[0]') +$ LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn $ALB_ARN | jq -r '.Listeners[0].ListenerArn') +$ aws elbv2 describe-rules --listener-arn $LISTENER_ARN +``` + +The output of this command will illustrate that: + +- Requests with path prefix `/catalog` will get sent to a target group for the catalog service +- Everything else will get sent to a target group for the ui service +- As a default backup there is a 404 for any requests that happen to fall through the cracks + +You can also check out the new ALB configuration in the AWS console: + + + +To wait until the load balancer has finished provisioning you can run this command: + +```bash timeout=180 +$ curl --head -X GET --retry 30 --retry-all-errors --retry-delay 15 --connect-timeout 30 --max-time 60 \ + -k $(kubectl get ingress -n catalog catalog-multi -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") +``` + +Try accessing the new Ingress URL in the browser as before to check the web UI still works: + +```bash +$ ADDRESS=$(kubectl get ingress -n catalog catalog-multi -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") +$ echo "http://${ADDRESS}" +http://k8s-retailappgroup-2c24c1c4bc-17962260.us-west-2.elb.amazonaws.com +``` + +Now try accessing a path we directed to the catalog service: + +```bash +$ ADDRESS=$(kubectl get ingress -n catalog catalog-multi -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") +$ curl $ADDRESS/catalog/products | jq . +``` + +You'll receive back a JSON payload from the catalog service, demonstrating that we've been able to expose multiple Kubernetes services via the same ALB. diff --git a/website/docs/fastpaths/developer/ingress/tests/hook-add-ingress.sh b/website/docs/fastpaths/developer/ingress/tests/hook-add-ingress.sh new file mode 100644 index 0000000000..8ae8af228b --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/tests/hook-add-ingress.sh @@ -0,0 +1,32 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + sleep 20 + + export ui_endpoint=$(kubectl get ingress -n ui ui -o json | jq -r '.status.loadBalancer.ingress[0].hostname') + + if [ -z "$ui_endpoint" ]; then + >&2 echo "Failed to retrieve hostname from Ingress" + exit 1 + fi + + EXIT_CODE=0 + + timeout -s TERM 400 bash -c \ + 'while [[ "$(curl -s -o /dev/null -L -w ''%{http_code}'' ${ui_endpoint}/home)" != "200" ]];\ + do sleep 20;\ + done' || EXIT_CODE=$? + + echo "Timeout completed" + + if [ $EXIT_CODE -ne 0 ]; then + >&2 echo "Ingress did not become available after 400 seconds" + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/ingress/tests/hook-dns-curl.sh b/website/docs/fastpaths/developer/ingress/tests/hook-dns-curl.sh new file mode 100644 index 0000000000..beed10e525 --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/tests/hook-dns-curl.sh @@ -0,0 +1,15 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + if [[ $TEST_OUTPUT != *"HTTP/1.1 200 OK"* ]]; then + >&2 echo "Failed to match expected output" + echo $TEST_OUTPUT + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/ingress/tests/hook-dns-logs.sh b/website/docs/fastpaths/developer/ingress/tests/hook-dns-logs.sh new file mode 100644 index 0000000000..f02fef2cb9 --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/tests/hook-dns-logs.sh @@ -0,0 +1,15 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + if [[ $TEST_OUTPUT != *"Desired change: CREATE ui.retailstore.com"* ]]; then + >&2 echo "Failed to match expected output" + echo $TEST_OUTPUT + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/ingress/tests/hook-multiple-ingress.sh b/website/docs/fastpaths/developer/ingress/tests/hook-multiple-ingress.sh new file mode 100644 index 0000000000..bfc5010e9d --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/tests/hook-multiple-ingress.sh @@ -0,0 +1,37 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + sleep 60 + + kubectl get ingress -A + + export catalog_endpoint=$(kubectl get ingress -n catalog catalog-multi -o json | jq -r '.status.loadBalancer.ingress[0].hostname') + + if [ -z "$catalog_endpoint" ]; then + >&2 echo "Failed to retrieve hostname from Ingress" + exit 1 + fi + + EXIT_CODE=0 + + timeout -s TERM 400 bash -c \ + 'while [[ "$(curl -s -o /dev/null -L -w ''%{http_code}'' ${catalog_endpoint}/catalog/products)" != "200" ]];\ + do sleep 20;\ + done' || EXIT_CODE=$? + + echo "Timeout completed" + + if [ $EXIT_CODE -ne 0 ]; then + >&2 echo "Ingress did not become available after 400 seconds" + echo "Was checking $catalog_endpoint" + echo "" + kubectl get ingress -A + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/ingress/tests/hook-suite.sh b/website/docs/fastpaths/developer/ingress/tests/hook-suite.sh new file mode 100644 index 0000000000..8b5a4baea5 --- /dev/null +++ b/website/docs/fastpaths/developer/ingress/tests/hook-suite.sh @@ -0,0 +1,11 @@ +set -e + +before() { + echo "noop" +} + +after() { + prepare-environment +} + +"$@" diff --git a/website/docs/fastpaths/developer/keda/configure-keda.md b/website/docs/fastpaths/developer/keda/configure-keda.md new file mode 100644 index 0000000000..56e9d168e0 --- /dev/null +++ b/website/docs/fastpaths/developer/keda/configure-keda.md @@ -0,0 +1,30 @@ +--- +title: "Configure KEDA" +sidebar_position: 10 +--- + +When installed, KEDA creates several custom resources. One of those resources, a `ScaledObject`, enables you to map an external event source to a Deployment or StatefulSet for scaling. In this lab, we'll create a `ScaledObject` that targets the `ui` Deployment and scales this workload based on the `RequestCountPerTarget` metric in CloudWatch. + +::yaml{file="manifests/modules/autoscaling/workloads/keda/scaledobject/scaledobject.yaml" paths="spec.scaleTargetRef,spec.minReplicaCount,spec.maxReplicaCount,spec.triggers"} + +1. This is the resource KEDA will scale. The `name` is the name of the deployment you are targeting and your `ScaledObject` must be in the same namespace as the Deployment +2. The minimum number of replicas that KEDA will scale the deployment to +3. The maximum number of replicas that KEDA will scale the deployment to +4. The `expression` uses [CloudWatch Metrics Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-metrics-insights-querylanguage.html) syntax to select your target metric. When the `targetMetricValue` is exceeded, KEDA will scale out the workload to support the increased load. In our case, if the `RequestCountPerTarget` is greater than 100, KEDA will scale the deployment. + +More details on the AWS CloudWatch scaler can be found [here](https://keda.sh/docs/scalers/aws-cloudwatch/). + +First we need to gather some information about the Application Load Balancer (ALB) and Target Group that were created as part of the lab pre-requisites. + +```bash +$ export ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-ui-ui`) == `true`]' | jq -r .[0].LoadBalancerArn) +$ export ALB_ID=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-ui-ui`) == `true`]' | jq -r .[0].LoadBalancerArn | awk -F "loadbalancer/" '{print $2}') +$ export TARGETGROUP_ID=$(aws elbv2 describe-target-groups --load-balancer-arn $ALB_ARN | jq -r '.TargetGroups[0].TargetGroupArn' | awk -F ":" '{print $6}') +``` + +Now we can use those values to update the configuration of our `ScaledObject` and create the resource in the cluster. + +```bash +$ kubectl kustomize ~/environment/eks-workshop/modules/autoscaling/workloads/keda/scaledobject \ + | envsubst | kubectl apply -f- +``` diff --git a/website/docs/fastpaths/developer/keda/index.md b/website/docs/fastpaths/developer/keda/index.md new file mode 100644 index 0000000000..75d7aaf520 --- /dev/null +++ b/website/docs/fastpaths/developer/keda/index.md @@ -0,0 +1,22 @@ +--- +title: "Autoscaling applications" +chapter: true +sidebar_position: 80 +description: "Automatically scale workloads on Amazon Elastic Kubernetes Service with KEDA" +--- + +:::tip What's been set up for you +The environment preparation stage made the following changes to your lab environment: + +- Create an IAM role required by the KEDA Operator + +::: + +Autoscaling monitors your workloads and automatically adjusts capacity to maintain steady, predictable performance while also optimizing for cost. When using Kubernetes there are two main relevant mechanisms which can be used to scale automatically: + +- **Compute:** As pods are scaled the underlying compute in a Kubernetes cluster must also adapt by adjusting the number or size of worker nodes used to run the Pods. +- **Pods:** Since pods are used to run workloads in a Kubernetes cluster, scaling a workload is primarily done by scaling Pods either horizontally or vertically in response to scenarios such as changes in load on a given application. + +In this lab, we'll look at using the [Kubernetes Event-Driven Autoscaler (KEDA)](https://keda.sh/) to scale pods in a deployment. In the previous lab on the Horizontal Pod Autoscaler (HPA), we saw how the HPA resource can be used to horizontally scale pods in a deployment based on average CPU utilization. But sometimes workloads need to scale based on external events or metrics. KEDA provides the capability to scale your workload based on events from various event sources, such as the queue length in Amazon SQS or other metrics in CloudWatch. KEDA supports 60+ [scalers](https://keda.sh/docs/scalers/) for various metrics systems, databases, messaging systems, and more. + +KEDA is a lightweight workload that can be deployed into a Kubernetes cluster using a Helm chart. KEDA works with standard Kubernetes components like the Horizontal Pod Autoscaler to scale a Deployment or StatefulSet. With KEDA, you selectively choose the workloads you want to scale with these various event sources. diff --git a/website/docs/fastpaths/developer/keda/install-keda.md b/website/docs/fastpaths/developer/keda/install-keda.md new file mode 100644 index 0000000000..2fc92e51c6 --- /dev/null +++ b/website/docs/fastpaths/developer/keda/install-keda.md @@ -0,0 +1,44 @@ +--- +title: "Installing KEDA" +sidebar_position: 5 +--- + +First lets install KEDA using Helm. One pre-requisite was created during the lab preparation stage. An IAM role was created with permissions to access the metric data within CloudWatch. + +```bash +$ helm repo add kedacore https://kedacore.github.io/charts +$ helm upgrade --install keda kedacore/keda \ + --version "${KEDA_CHART_VERSION}" \ + --namespace keda \ + --create-namespace \ + --set "podIdentity.aws.irsa.enabled=true" \ + --set "podIdentity.aws.irsa.roleArn=${KEDA_ROLE_ARN}" \ + --wait +Release "keda" does not exist. Installing it now. +NAME: keda +LAST DEPLOYED: [...] +NAMESPACE: kube-system +STATUS: deployed +REVISION: 1 +TEST SUITE: None +NOTES: +[...] +``` + +After the Helm install, KEDA will be running as several deployments in the keda namespace: + +```bash +$ kubectl get deployment -n keda +NAME READY UP-TO-DATE AVAILABLE AGE +keda-admission-webhooks 1/1 1 1 105s +keda-operator 1/1 1 1 105s +keda-operator-metrics-apiserver 1/1 1 1 105s +``` + +Each KEDA deployment performs a different key role: + +1. Agent (keda-operator) - controls the scaling of the workload +2. Metrics (keda-operator-metrics-server) - acts as a Kubernetes metrics server, providing access to external metrics +3. Admission Webhooks (keda-admission-webhooks) - validates resource configuration to prevent misconfiguration (ex. multiple ScaledObjects targeting the same workload) + +Now we can move on to configuring KEDA to scale our workload. diff --git a/website/docs/fastpaths/developer/keda/test-keda.md b/website/docs/fastpaths/developer/keda/test-keda.md new file mode 100644 index 0000000000..a98d7002d2 --- /dev/null +++ b/website/docs/fastpaths/developer/keda/test-keda.md @@ -0,0 +1,50 @@ +--- +title: "Generate load" +sidebar_position: 20 +--- + +To observe KEDA scale the deployment in response to the KEDA `ScaledObject` we have configured, we need to generate some load on our application. We'll do that by calling the home page of the workload with [hey](https://github.com/rakyll/hey). + +The command below will run the load generator with: + +- 3 workers running concurrently +- Sending 5 queries per second each +- Running for a maximum of 10 minutes + +```bash hook=keda-pod-scaleout hookTimeout=330 +$ export ALB_HOSTNAME=$(kubectl get ingress ui -n ui -o yaml | yq .status.loadBalancer.ingress[0].hostname) +$ kubectl run load-generator \ + --image=williamyeh/hey:latest \ + --restart=Never -- -c 3 -q 5 -z 10m http://$ALB_HOSTNAME/home +``` + +Based on the `ScaledObject`, KEDA creates an HPA resource and provides the required metrics to allow the HPA to scale the workload. Now that we have requests hitting our application we can watch the HPA resource to follow its progress: + +```bash test=false +$ kubectl get hpa keda-hpa-ui-hpa -n ui --watch +NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE +keda-hpa-ui-hpa Deployment/ui 7/100 (avg) 1 10 1 7m58s +keda-hpa-ui-hpa Deployment/ui 778/100 (avg) 1 10 1 8m33s +keda-hpa-ui-hpa Deployment/ui 194500m/100 (avg) 1 10 4 8m48s +keda-hpa-ui-hpa Deployment/ui 97250m/100 (avg) 1 10 8 9m3s +keda-hpa-ui-hpa Deployment/ui 625m/100 (avg) 1 10 8 9m18s +keda-hpa-ui-hpa Deployment/ui 91500m/100 (avg) 1 10 8 9m33s +keda-hpa-ui-hpa Deployment/ui 92125m/100 (avg) 1 10 8 9m48s +keda-hpa-ui-hpa Deployment/ui 750m/100 (avg) 1 10 8 10m +keda-hpa-ui-hpa Deployment/ui 102625m/100 (avg) 1 10 8 10m +keda-hpa-ui-hpa Deployment/ui 113625m/100 (avg) 1 10 8 11m +keda-hpa-ui-hpa Deployment/ui 90900m/100 (avg) 1 10 10 11m +keda-hpa-ui-hpa Deployment/ui 91500m/100 (avg) 1 10 10 12m +``` + +Once you're satisfied with the autoscaling behavior, you can end the watch with `Ctrl+C` and stop the load generator like so: + +```bash +$ kubectl delete pod load-generator +``` + +As the load generator terminates, notice that the HPA will slowly bring the replica count to min number based on its configuration. + +You can also view the load test results in the CloudWatch console. Navigate to the metrics section and find the `RequestCount` and `RequestCountPerTarget` metrics for the load balancer and target group that was created. From the results you can see that initially all of the load was handled by a single pod, but as KEDA begins to scale the workload the requests are distributed across the additional pods added to the workload. If you let the load-generator pod run for the full 10 minutes, you'll see results similar to this. + +![Insights](/img/keda/keda-cloudwatch.png) diff --git a/website/docs/fastpaths/developer/keda/tests/hook-keda-pod-scaleout.sh b/website/docs/fastpaths/developer/keda/tests/hook-keda-pod-scaleout.sh new file mode 100644 index 0000000000..1b16393bd8 --- /dev/null +++ b/website/docs/fastpaths/developer/keda/tests/hook-keda-pod-scaleout.sh @@ -0,0 +1,21 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + EXIT_CODE=0 + + timeout -s TERM 300 bash -c \ + 'while [[ $(kubectl get pod -l app.kubernetes.io/instance=ui -n ui -o json | jq -r ".items | length") -lt 2 ]];\ + do sleep 30;\ + done' || EXIT_CODE=$? + + if [ $EXIT_CODE -ne 0 ]; then + >&2 echo "Pods did not scale within 300 seconds" + exit 1 + fi +} + +"$@" diff --git a/website/docs/fastpaths/developer/keda/tests/hook-suite.sh b/website/docs/fastpaths/developer/keda/tests/hook-suite.sh new file mode 100644 index 0000000000..8b5a4baea5 --- /dev/null +++ b/website/docs/fastpaths/developer/keda/tests/hook-suite.sh @@ -0,0 +1,11 @@ +set -e + +before() { + echo "noop" +} + +after() { + prepare-environment +} + +"$@" diff --git a/website/docs/fastpaths/developer/keda/tests/hook-validate-ingress.sh b/website/docs/fastpaths/developer/keda/tests/hook-validate-ingress.sh new file mode 100644 index 0000000000..e573766a3e --- /dev/null +++ b/website/docs/fastpaths/developer/keda/tests/hook-validate-ingress.sh @@ -0,0 +1,32 @@ +set -Eeuo pipefail + +before() { + echo "noop" +} + +after() { + sleep 20 + + export ui_endpoint=$(kubectl -n kube-system get ingress -n ui ui -o json | jq -r '.status.loadBalancer.ingress[0].hostname') + + if [ -z "$ui_endpoint" ]; then + >&2 echo "Failed to retrieve hostname from Ingress" + exit 1 + fi + + EXIT_CODE=0 + + timeout -s TERM 400 bash -c \ + 'while [[ "$(curl -s -o /dev/null -L -w ''%{http_code}'' ${ui_endpoint}/home)" != "200" ]];\ + do sleep 20;\ + done' || EXIT_CODE=$? + + echo "Timeout completed" + + if [ $EXIT_CODE -ne 0 ]; then + >&2 echo "Ingress did not become available after 400 seconds" + exit 1 + fi +} + +"$@" diff --git a/website/sidebars.js b/website/sidebars.js index 4375d03407..f7fc45a567 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -23,6 +23,7 @@ const sidebars = { automation: [{ type: "autogenerated", dirName: "automation" }], aiml: [{ type: "autogenerated", dirName: "aiml" }], troubleshooting: [{ type: "autogenerated", dirName: "troubleshooting" }], + fastpaths: [{ type: "autogenerated", dirName: "fastpaths" }], }; module.exports = sidebars; From 6c4921baab513d8ef89b6edeb1fa950b3b5780c0 Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Thu, 2 Oct 2025 11:43:35 -0600 Subject: [PATCH 6/7] Fix paths --- .../fastpath/developers/.workshop/cleanup.sh | 55 +++++++++++++++++++ .../.workshop/terraform/autoscaling.tf | 0 .../.workshop/terraform/exposing.tf | 0 .../{ingress => }/.workshop/terraform/main.tf | 0 .../.workshop/terraform/outputs.tf | 8 +++ .../.workshop/terraform/pod_identity.tf | 0 .../.workshop/terraform/preprovision/main.tf | 0 .../.workshop/terraform/preprovision/vars.tf | 0 .../.workshop/terraform/storage.tf | 0 .../{ingress => }/.workshop/terraform/vars.tf | 0 .../developers/ingress/.workshop/cleanup.sh | 10 ---- .../ingress/creating-ingress/ingress.yaml | 21 ------- .../creating-ingress/kustomization.yaml | 4 -- .../ingress/external-dns/ingress.yaml | 23 -------- .../ingress/external-dns/kustomization.yaml | 4 -- .../multiple-ingress/ingress-catalog.yaml | 25 --------- .../ingress/multiple-ingress/ingress-ui.yaml | 25 --------- .../multiple-ingress/kustomization.yaml | 5 -- website/docs/fastpaths/developer/index.md | 4 +- 19 files changed, 65 insertions(+), 119 deletions(-) create mode 100644 manifests/modules/fastpath/developers/.workshop/cleanup.sh rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/autoscaling.tf (100%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/exposing.tf (100%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/main.tf (100%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/outputs.tf (56%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/pod_identity.tf (100%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/preprovision/main.tf (100%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/preprovision/vars.tf (100%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/storage.tf (100%) rename manifests/modules/fastpath/developers/{ingress => }/.workshop/terraform/vars.tf (100%) delete mode 100644 manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh delete mode 100644 manifests/modules/fastpath/developers/ingress/creating-ingress/ingress.yaml delete mode 100644 manifests/modules/fastpath/developers/ingress/creating-ingress/kustomization.yaml delete mode 100644 manifests/modules/fastpath/developers/ingress/external-dns/ingress.yaml delete mode 100644 manifests/modules/fastpath/developers/ingress/external-dns/kustomization.yaml delete mode 100644 manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-catalog.yaml delete mode 100644 manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-ui.yaml delete mode 100644 manifests/modules/fastpath/developers/ingress/multiple-ingress/kustomization.yaml diff --git a/manifests/modules/fastpath/developers/.workshop/cleanup.sh b/manifests/modules/fastpath/developers/.workshop/cleanup.sh new file mode 100644 index 0000000000..6907e8b20e --- /dev/null +++ b/manifests/modules/fastpath/developers/.workshop/cleanup.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +set -e + +# Common +kubectl delete namespace ui --ignore-not-found +kubectl delete namespace catalog --ignore-not-found +kubectl delete namespace carts --ignore-not-found + +# Autoscaling +kubectl delete pod load-generator --ignore-not-found +kubectl delete scaledobject ui-hpa -n ui --ignore-not-found + +uninstall-helm-chart keda keda +kubectl delete ns keda --ignore-not-found + +# Identity +POD_ASSOCIATION_ID=$(aws eks list-pod-identity-associations --region $AWS_REGION --cluster-name $EKS_CLUSTER_NAME --service-account carts --namespace carts --output text --query 'associations[0].associationId') + +if [ "$POD_ASSOCIATION_ID" != "None" ]; then + logmessage "Deleting EKS Pod Identity Association..." + + aws eks delete-pod-identity-association --region $AWS_REGION --association-id $POD_ASSOCIATION_ID --cluster-name $EKS_CLUSTER_NAME + +fi + +pod_identity_check=$(aws eks list-addons --cluster-name $EKS_CLUSTER_NAME --region $AWS_REGION --query "addons[? @ == 'eks-pod-identity-agent']" --output text) + +if [ ! -z "$pod_identity_check" ]; then + logmessage "Deleting EKS Pod Identity Agent addon..." + + aws eks delete-addon --cluster-name $EKS_CLUSTER_NAME --addon-name eks-pod-identity-agent --region $AWS_REGION + + aws eks wait addon-deleted --cluster-name $EKS_CLUSTER_NAME --addon-name eks-pod-identity-agent --region $AWS_REGION +fi + +# Storage +csi_check=$(aws eks list-addons --cluster-name $EKS_CLUSTER_NAME --query "addons[? @ == 'aws-efs-csi-driver']" --output text) + +logmessage "Deleting EFS storage class..." + +kubectl delete storageclass efs-sc --ignore-not-found + +if [ ! -z "$csi_check" ]; then + logmessage "Deleting EFS CSI driver addon..." + + aws eks delete-addon --cluster-name $EKS_CLUSTER_NAME --addon-name aws-efs-csi-driver + + aws eks wait addon-deleted --cluster-name $EKS_CLUSTER_NAME --addon-name aws-efs-csi-driver +fi + +# Ingress +uninstall-helm-chart external-dns external-dns + +uninstall-helm-chart aws-load-balancer-controller kube-system \ No newline at end of file diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/autoscaling.tf b/manifests/modules/fastpath/developers/.workshop/terraform/autoscaling.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/autoscaling.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/autoscaling.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/exposing.tf b/manifests/modules/fastpath/developers/.workshop/terraform/exposing.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/exposing.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/exposing.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/main.tf b/manifests/modules/fastpath/developers/.workshop/terraform/main.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/main.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/main.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/outputs.tf b/manifests/modules/fastpath/developers/.workshop/terraform/outputs.tf similarity index 56% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/outputs.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/outputs.tf index c4a62667a3..02533c655e 100644 --- a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/outputs.tf +++ b/manifests/modules/fastpath/developers/.workshop/terraform/outputs.tf @@ -5,5 +5,13 @@ output "environment_variables" { LBC_ROLE_ARN = module.eks_blueprints_addons.aws_load_balancer_controller.iam_role_arn DNS_CHART_VERSION = var.external_dns_chart_version DNS_ROLE_ARN = module.eks_blueprints_addons.external_dns.iam_role_arn + + EFS_CSI_ADDON_ROLE = module.efs_csi_driver_irsa.iam_role_arn + + CARTS_DYNAMODB_TABLENAME = aws_dynamodb_table.carts.name + CARTS_IAM_ROLE = module.iam_assumable_role_carts.iam_role_arn + + KEDA_ROLE_ARN = module.iam_assumable_role_keda.iam_role_arn + KEDA_CHART_VERSION = var.keda_chart_version } } diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/pod_identity.tf b/manifests/modules/fastpath/developers/.workshop/terraform/pod_identity.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/pod_identity.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/pod_identity.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/preprovision/main.tf b/manifests/modules/fastpath/developers/.workshop/terraform/preprovision/main.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/preprovision/main.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/preprovision/main.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/preprovision/vars.tf b/manifests/modules/fastpath/developers/.workshop/terraform/preprovision/vars.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/preprovision/vars.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/preprovision/vars.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/storage.tf b/manifests/modules/fastpath/developers/.workshop/terraform/storage.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/storage.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/storage.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/terraform/vars.tf b/manifests/modules/fastpath/developers/.workshop/terraform/vars.tf similarity index 100% rename from manifests/modules/fastpath/developers/ingress/.workshop/terraform/vars.tf rename to manifests/modules/fastpath/developers/.workshop/terraform/vars.tf diff --git a/manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh b/manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh deleted file mode 100644 index e9466d2513..0000000000 --- a/manifests/modules/fastpath/developers/ingress/.workshop/cleanup.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -e - -kubectl delete ingress -n catalog --all --ignore-not-found -kubectl delete ingress -n ui --all --ignore-not-found - -uninstall-helm-chart external-dns external-dns - -uninstall-helm-chart aws-load-balancer-controller kube-system \ No newline at end of file diff --git a/manifests/modules/fastpath/developers/ingress/creating-ingress/ingress.yaml b/manifests/modules/fastpath/developers/ingress/creating-ingress/ingress.yaml deleted file mode 100644 index 521dbf7c1b..0000000000 --- a/manifests/modules/fastpath/developers/ingress/creating-ingress/ingress.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ui - namespace: ui - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/healthcheck-path: /actuator/health/liveness -spec: - ingressClassName: alb - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: ui - port: - number: 80 diff --git a/manifests/modules/fastpath/developers/ingress/creating-ingress/kustomization.yaml b/manifests/modules/fastpath/developers/ingress/creating-ingress/kustomization.yaml deleted file mode 100644 index 972f3ed061..0000000000 --- a/manifests/modules/fastpath/developers/ingress/creating-ingress/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ingress.yaml diff --git a/manifests/modules/fastpath/developers/ingress/external-dns/ingress.yaml b/manifests/modules/fastpath/developers/ingress/external-dns/ingress.yaml deleted file mode 100644 index 5361ba4911..0000000000 --- a/manifests/modules/fastpath/developers/ingress/external-dns/ingress.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ui - namespace: ui - annotations: - external-dns.alpha.kubernetes.io/hostname: ui.retailstore.com - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/healthcheck-path: /actuator/health/liveness -spec: - ingressClassName: alb - rules: - - host: ui.retailstore.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: ui - port: - number: 80 diff --git a/manifests/modules/fastpath/developers/ingress/external-dns/kustomization.yaml b/manifests/modules/fastpath/developers/ingress/external-dns/kustomization.yaml deleted file mode 100644 index 972f3ed061..0000000000 --- a/manifests/modules/fastpath/developers/ingress/external-dns/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ingress.yaml diff --git a/manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-catalog.yaml b/manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-catalog.yaml deleted file mode 100644 index 03b37be5fe..0000000000 --- a/manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-catalog.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: catalog-multi - namespace: catalog - labels: - app.kubernetes.io/created-by: eks-workshop - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/healthcheck-path: /health - # HIGHLIGHT - alb.ingress.kubernetes.io/group.name: retail-app-group -spec: - ingressClassName: alb - rules: - - http: - paths: - - path: /catalog - pathType: Prefix - backend: - service: - name: catalog - port: - number: 80 diff --git a/manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-ui.yaml b/manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-ui.yaml deleted file mode 100644 index 69ee2f2137..0000000000 --- a/manifests/modules/fastpath/developers/ingress/multiple-ingress/ingress-ui.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: ui-multi - namespace: ui - labels: - app.kubernetes.io/created-by: eks-workshop - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/healthcheck-path: /actuator/health/liveness - # HIGHLIGHT - alb.ingress.kubernetes.io/group.name: retail-app-group -spec: - ingressClassName: alb - rules: - - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: ui - port: - number: 80 diff --git a/manifests/modules/fastpath/developers/ingress/multiple-ingress/kustomization.yaml b/manifests/modules/fastpath/developers/ingress/multiple-ingress/kustomization.yaml deleted file mode 100644 index e999f2ee71..0000000000 --- a/manifests/modules/fastpath/developers/ingress/multiple-ingress/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ingress-catalog.yaml - - ingress-ui.yaml diff --git a/website/docs/fastpaths/developer/index.md b/website/docs/fastpaths/developer/index.md index c246daf7eb..f97e57fdd8 100644 --- a/website/docs/fastpaths/developer/index.md +++ b/website/docs/fastpaths/developer/index.md @@ -1,5 +1,5 @@ --- -title: "Fast path - Developers" +title: "⚡ Fast path - Developers" chapter: true --- @@ -9,7 +9,7 @@ chapter: true Prepare your environment for this section: ```bash timeout=300 wait=30 -$ prepare-environment fast-path/developers +$ prepare-environment fastpath/developers ``` Each section of this lab will outline what resources have been setup for your convenience. From ceb140745fa618b86f8c5e80700df03f6be81f1e Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Thu, 2 Oct 2025 22:09:09 +0000 Subject: [PATCH 7/7] Updates --- .../fastpath/developers/.workshop/cleanup.sh | 1 - .../developer/efs/tests/hook-suite.sh | 11 --- .../developer/ingress/external-dns.md | 90 ------------------- .../developer/ingress/multiple-ingress.md | 82 ----------------- .../developer/ingress/tests/hook-suite.sh | 11 --- .../developer/keda/tests/hook-suite.sh | 11 --- .../tests/hook-suite.sh | 0 website/test-durations.json | 31 +++++-- 8 files changed, 26 insertions(+), 211 deletions(-) delete mode 100644 website/docs/fastpaths/developer/efs/tests/hook-suite.sh delete mode 100644 website/docs/fastpaths/developer/ingress/external-dns.md delete mode 100644 website/docs/fastpaths/developer/ingress/multiple-ingress.md delete mode 100644 website/docs/fastpaths/developer/ingress/tests/hook-suite.sh delete mode 100644 website/docs/fastpaths/developer/keda/tests/hook-suite.sh rename website/docs/fastpaths/developer/{amazon-eks-pod-identity => }/tests/hook-suite.sh (100%) diff --git a/manifests/modules/fastpath/developers/.workshop/cleanup.sh b/manifests/modules/fastpath/developers/.workshop/cleanup.sh index 6907e8b20e..a89b1fb7e3 100644 --- a/manifests/modules/fastpath/developers/.workshop/cleanup.sh +++ b/manifests/modules/fastpath/developers/.workshop/cleanup.sh @@ -9,7 +9,6 @@ kubectl delete namespace carts --ignore-not-found # Autoscaling kubectl delete pod load-generator --ignore-not-found -kubectl delete scaledobject ui-hpa -n ui --ignore-not-found uninstall-helm-chart keda keda kubectl delete ns keda --ignore-not-found diff --git a/website/docs/fastpaths/developer/efs/tests/hook-suite.sh b/website/docs/fastpaths/developer/efs/tests/hook-suite.sh deleted file mode 100644 index 8b5a4baea5..0000000000 --- a/website/docs/fastpaths/developer/efs/tests/hook-suite.sh +++ /dev/null @@ -1,11 +0,0 @@ -set -e - -before() { - echo "noop" -} - -after() { - prepare-environment -} - -"$@" diff --git a/website/docs/fastpaths/developer/ingress/external-dns.md b/website/docs/fastpaths/developer/ingress/external-dns.md deleted file mode 100644 index f14694646b..0000000000 --- a/website/docs/fastpaths/developer/ingress/external-dns.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: "External DNS" -sidebar_position: 30 ---- - -[ExternalDNS](https://github.com/kubernetes-sigs/external-dns) is a Kubernetes controller that automatically manages DNS records for your cluster's services and ingresses. It acts as a bridge between Kubernetes resources and DNS providers like AWS Route 53, ensuring your DNS records stay synchronized with your cluster's state. Using DNS entries for your load balancers provides human-readable, memorable addresses instead of auto-generated host names, making your services easily accessible and recognizable as your corporate resources with domain names that align with your organization's branding - -In this lab we'll automate DNS management for Kubernetes Ingress resources using ExternalDNS with AWS Route 53. - -First let's install ExternalDNS using Helm, with the IAM role ARN and Helm chart version provided as environment variables: - -```bash -$ helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/ -$ helm upgrade --install external-dns external-dns/external-dns --version "${DNS_CHART_VERSION}" \ - --namespace external-dns \ - --create-namespace \ - --set provider.name=aws \ - --set serviceAccount.create=true \ - --set serviceAccount.name=external-dns-sa \ - --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="$DNS_ROLE_ARN" \ - --set txtOwnerId=eks-workshop \ - --set extraArgs[0]=--aws-zone-type=private \ - --set extraArgs[1]=--domain-filter=retailstore.com \ - --wait -``` - -Check that the ExternalDNS pod is running: - -```bash -$ kubectl -n external-dns get pods -NAME READY STATUS RESTARTS AGE -external-dns-5bdb4478b-fl48s 1/1 Running 0 2m -``` - -Now let's update our previous Ingress resource with DNS configuration: - -::yaml{file="manifests/modules/exposing/ingress/external-dns/ingress.yaml" paths="metadata.annotations,spec.rules.0.host"} - -1. The annotation `external-dns.alpha.kubernetes.io/hostname` tells ExternalDNS which DNS name to create and manage for the Ingress, automating the mapping of your app’s hostname to its load balancer. -2. The `spec.rules.host` defines the domain name your Ingress will listen to, which ExternalDNS uses to create a matching DNS record for the associated load balancer. - -Apply this configuration: - -```bash -$ kubectl apply -k ~/environment/eks-workshop/modules/exposing/ingress/external-dns -``` - -Let's inspect the Ingress object created with host name: - -```bash wait=120 -$ kubectl get ingress ui -n ui -NAME CLASS HOSTS ADDRESS PORTS AGE -ui alb ui.retailstore.com k8s-ui-ui-1268651632.us-west-2.elb.amazonaws.com 80 4m15s -``` - -Verifying DNS record creation, ExternalDNS will automatically create the DNS record in the `retailstore.com` Route 53 private hosted zone. - -:::note - -It can take several minutes for the DNS entries to be reconciled. - -::: - -Check ExternalDNS logs to confirm DNS record creation: - -```bash hook=dns-logs -$ kubectl -n external-dns logs deployment/external-dns -Desired change: CREATE ui.retailstore.com A -5 record(s) were successfully updated -``` - -You can also verify the new DNS record in the AWS Route 53 console by clicking the link and navigating to the `retailstore.com` private hosted zone: - - - -Route 53 private hosted zones are only accessible from associated VPCs, in this case the EKS cluster VPC. To test the DNS entry we'll use `curl` from inside a pod: - -```bash hook=dns-curl -$ kubectl -n ui exec -it \ - deployment/ui -- bash -c "curl -i http://ui.retailstore.com/actuator/health/liveness; echo" - -HTTP/1.1 200 OK -Date: Thu, 24 Apr 2025 07:45:12 GMT -Content-Type: application/vnd.spring-boot.actuator.v3+json -Content-Length: 15 -Connection: keep-alive -Set-Cookie: SESSIONID=c3f13e02-4ff3-40ba-866e-c777f7450997 - -{"status":"UP"} -``` diff --git a/website/docs/fastpaths/developer/ingress/multiple-ingress.md b/website/docs/fastpaths/developer/ingress/multiple-ingress.md deleted file mode 100644 index e086be6d4f..0000000000 --- a/website/docs/fastpaths/developer/ingress/multiple-ingress.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: "Multiple Ingress pattern" -sidebar_position: 30 ---- - -It's common to leverage multiple Ingress objects in the same EKS cluster, for example to expose multiple different workloads. By default each Ingress will result in the creation of a separate ALB, but we can leverage the IngressGroup feature which enables you to group multiple Ingress resources together. The controller will automatically merge Ingress rules for all Ingresses within IngressGroup and support them with a single ALB. In addition, most annotations defined on an Ingress only apply to the paths defined by that Ingress. - -In this example, we'll expose the `catalog` API out through the same ALB as the `ui` component, leveraging path-based routing to dispatch requests to the appropriate Kubernetes service. - -The first thing we'll do is create a new Ingress for the `ui` component adding the annotation `alb.ingress.kubernetes.io/group.name`: - -```file -manifests/modules/exposing/ingress/multiple-ingress/ingress-ui.yaml -``` - -Then we'll create a separate Ingress for the `catalog` component that also leverages the same `group.name`: - -```file -manifests/modules/exposing/ingress/multiple-ingress/ingress-catalog.yaml -``` - -This Ingress is also configuring rules to route requests prefixed with `/catalog` to the `catalog` component. - -Apply these manifests to the cluster: - -```bash wait=60 -$ kubectl apply -k ~/environment/eks-workshop/modules/exposing/ingress/multiple-ingress -``` - -We'll now have two additional Ingress objects in our cluster that end with `-multi`: - -```bash -$ kubectl get ingress -l app.kubernetes.io/created-by=eks-workshop -A -NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE -catalog-multi catalog alb * k8s-retailappgroup-2c24c1c4bc-17962260.us-west-2.elb.amazonaws.com 80 2m21s -ui-multi ui alb * k8s-retailappgroup-2c24c1c4bc-17962260.us-west-2.elb.amazonaws.com 80 2m21s -ui ui alb * k8s-ui-ui-1268651632.us-west-2.elb.amazonaws.com 80 4m3s -``` - -Notice that the `ADDRESS` of both are the same URL, which is because both of these Ingress objects are being grouped together behind the same ALB. - -We can take a look at the ALB listener to see how this works: - -```bash -$ ALB_ARN=$(aws elbv2 describe-load-balancers --query 'LoadBalancers[?contains(LoadBalancerName, `k8s-retailappgroup`) == `true`].LoadBalancerArn' | jq -r '.[0]') -$ LISTENER_ARN=$(aws elbv2 describe-listeners --load-balancer-arn $ALB_ARN | jq -r '.Listeners[0].ListenerArn') -$ aws elbv2 describe-rules --listener-arn $LISTENER_ARN -``` - -The output of this command will illustrate that: - -- Requests with path prefix `/catalog` will get sent to a target group for the catalog service -- Everything else will get sent to a target group for the ui service -- As a default backup there is a 404 for any requests that happen to fall through the cracks - -You can also check out the new ALB configuration in the AWS console: - - - -To wait until the load balancer has finished provisioning you can run this command: - -```bash timeout=180 -$ curl --head -X GET --retry 30 --retry-all-errors --retry-delay 15 --connect-timeout 30 --max-time 60 \ - -k $(kubectl get ingress -n catalog catalog-multi -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") -``` - -Try accessing the new Ingress URL in the browser as before to check the web UI still works: - -```bash -$ ADDRESS=$(kubectl get ingress -n catalog catalog-multi -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") -$ echo "http://${ADDRESS}" -http://k8s-retailappgroup-2c24c1c4bc-17962260.us-west-2.elb.amazonaws.com -``` - -Now try accessing a path we directed to the catalog service: - -```bash -$ ADDRESS=$(kubectl get ingress -n catalog catalog-multi -o jsonpath="{.status.loadBalancer.ingress[*].hostname}") -$ curl $ADDRESS/catalog/products | jq . -``` - -You'll receive back a JSON payload from the catalog service, demonstrating that we've been able to expose multiple Kubernetes services via the same ALB. diff --git a/website/docs/fastpaths/developer/ingress/tests/hook-suite.sh b/website/docs/fastpaths/developer/ingress/tests/hook-suite.sh deleted file mode 100644 index 8b5a4baea5..0000000000 --- a/website/docs/fastpaths/developer/ingress/tests/hook-suite.sh +++ /dev/null @@ -1,11 +0,0 @@ -set -e - -before() { - echo "noop" -} - -after() { - prepare-environment -} - -"$@" diff --git a/website/docs/fastpaths/developer/keda/tests/hook-suite.sh b/website/docs/fastpaths/developer/keda/tests/hook-suite.sh deleted file mode 100644 index 8b5a4baea5..0000000000 --- a/website/docs/fastpaths/developer/keda/tests/hook-suite.sh +++ /dev/null @@ -1,11 +0,0 @@ -set -e - -before() { - echo "noop" -} - -after() { - prepare-environment -} - -"$@" diff --git a/website/docs/fastpaths/developer/amazon-eks-pod-identity/tests/hook-suite.sh b/website/docs/fastpaths/developer/tests/hook-suite.sh similarity index 100% rename from website/docs/fastpaths/developer/amazon-eks-pod-identity/tests/hook-suite.sh rename to website/docs/fastpaths/developer/tests/hook-suite.sh diff --git a/website/test-durations.json b/website/test-durations.json index 4cdcd78ae2..e680b6e9e1 100644 --- a/website/test-durations.json +++ b/website/test-durations.json @@ -1,10 +1,10 @@ { - "/aiml/index.md": 1, "/aiml/chatbot/add-mistral.md": 666117, "/aiml/chatbot/expose.md": 21404, "/aiml/chatbot/gradio-mistral.md": 526008, "/aiml/chatbot/index.md": 203818, "/aiml/chatbot/nodepool.md": 4230, + "/aiml/index.md": 1, "/aiml/inferentia/compile.md": 331930, "/aiml/inferentia/index.md": 205489, "/aiml/inferentia/inference.md": 282459, @@ -51,6 +51,27 @@ "/autoscaling/workloads/keda/install-keda.md": 37918, "/autoscaling/workloads/keda/test-keda.md": 94797, "/autoscaling/workloads/keda/validate-ingress.md": 103610, + "/fastpaths/developer/amazon-eks-pod-identity/index.md": 0, + "/fastpaths/developer/amazon-eks-pod-identity/introduction.md": 1254, + "/fastpaths/developer/amazon-eks-pod-identity/understanding.md": 11171, + "/fastpaths/developer/amazon-eks-pod-identity/use-pod-identity.md": 123041, + "/fastpaths/developer/amazon-eks-pod-identity/using-dynamo.md": 34507, + "/fastpaths/developer/amazon-eks-pod-identity/verifying-dynamo.md": 1259, + "/fastpaths/developer/efs/deployment-with-efs.md": 85345, + "/fastpaths/developer/efs/efs-csi-driver.md": 85681, + "/fastpaths/developer/efs/existing-architecture.md": 592, + "/fastpaths/developer/efs/index.md": 0, + "/fastpaths/developer/getting-started/finish.md": 26489, + "/fastpaths/developer/getting-started/first.md": 35625, + "/fastpaths/developer/getting-started/index.md": 1, + "/fastpaths/developer/index.md": 179009, + "/fastpaths/developer/ingress/adding-ingress.md": 146526, + "/fastpaths/developer/ingress/index.md": 0, + "/fastpaths/developer/ingress/introduction.md": 28632, + "/fastpaths/developer/keda/configure-keda.md": 3298, + "/fastpaths/developer/keda/index.md": 0, + "/fastpaths/developer/keda/install-keda.md": 35494, + "/fastpaths/developer/keda/test-keda.md": 5116, "/fundamentals/exposing/index.md": 0, "/fundamentals/exposing/ingress/adding-ingress.md": 186817, "/fundamentals/exposing/ingress/index.md": 155191, @@ -85,12 +106,12 @@ "/fundamentals/storage/efs/deployments.md": 5420, "/fundamentals/storage/efs/efs-csi-driver.md": 85771, "/fundamentals/storage/efs/index.md": 125877, + "/fundamentals/storage/fsx-for-openzfs/index.md": 1200, "/fundamentals/storage/index.md": 0, "/fundamentals/storage/mountpoint-s3/10-ephemeral-container-storage.md": 4992, "/fundamentals/storage/mountpoint-s3/20-introduction-to-mountpoint-for-amazon-s3.md": 45869, "/fundamentals/storage/mountpoint-s3/30-persistent-object-storage-with-mountpoint-for-amazon-s3.md": 23439, "/fundamentals/storage/mountpoint-s3/index.md": 153471, - "/fundamentals/storage/fsx-for-openzfs/index.md": 1200, "/introduction/getting-started/finish.md": 17926, "/introduction/getting-started/first.md": 12371, "/introduction/getting-started/index.md": 832, @@ -202,12 +223,12 @@ "/security/secrets-management/secrets-manager/index.md": 281009, "/security/secrets-management/secrets-manager/mounting-secrets.md": 16049, "/troubleshooting/alb/index.md": 16049, - "/troubleshooting/pod/index.md": 16049, - "/troubleshooting/dns/index.md": 16049, + "/troubleshooting/cni/index.md": 16049, "/troubleshooting/dns/1-config.md": 16049, "/troubleshooting/dns/2-check-coredns.md": 16049, "/troubleshooting/dns/3-check-kube-dns.md": 16049, "/troubleshooting/dns/4-check-vpc.md": 16049, - "/troubleshooting/cni/index.md": 16049, + "/troubleshooting/dns/index.md": 16049, + "/troubleshooting/pod/index.md": 16049, "/troubleshooting/workernodes/index.md": 16049 }