diff --git a/manifests/modules/automation/controlplanes/ack/.workshop/cleanup.sh b/manifests/modules/automation/controlplanes/ack/.workshop/cleanup.sh index e8c75783d..5d6be25dc 100755 --- a/manifests/modules/automation/controlplanes/ack/.workshop/cleanup.sh +++ b/manifests/modules/automation/controlplanes/ack/.workshop/cleanup.sh @@ -1,5 +1,6 @@ #!/bin/bash -echo "Deleting RDS resources created by ACK..." +echo "Deleting resources created by ACK..." -kubectl delete namespace catalog > /dev/null \ No newline at end of file +eksctl delete iamserviceaccount --name carts-ack --namespace carts --cluster $EKS_CLUSTER_NAME -v 0 > /dev/null +kubectl delete table items -n carts --ignore-not-found=true > /dev/null \ No newline at end of file diff --git a/manifests/modules/automation/controlplanes/ack/.workshop/terraform/addon.tf b/manifests/modules/automation/controlplanes/ack/.workshop/terraform/addon.tf index ed5faf11f..cb02d290f 100644 --- a/manifests/modules/automation/controlplanes/ack/.workshop/terraform/addon.tf +++ b/manifests/modules/automation/controlplanes/ack/.workshop/terraform/addon.tf @@ -1,108 +1,58 @@ locals { - ec2_name = "ack-ec2" - rds_name = "ack-rds" + ddb_name = "ack-ddb" } -module "rds" { - source = "github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons/helm-addon?ref=v4.12.2" +#This module installs the ACK controller for DynamoDB through the AWS EKS Addons for ACK +module "dynamodb_ack_addon" { - helm_config = { - name = local.rds_name - chart = "rds-chart" - repository = "oci://public.ecr.aws/aws-controllers-k8s" - version = "v0.1.1" - namespace = local.rds_name - create_namespace = true - description = "ACK RDS Controller Helm chart deployment configuration" - values = [ - # shortens pod name from `ack-rds-rds-chart-xxxxxxxxxxxxx` to `ack-rds-xxxxxxxxxxxxx` - <<-EOT - nameOverride: ack-rds - EOT - ] - } - - set_values = [ - { - name = "serviceAccount.name" - value = local.rds_name - }, - { - name = "serviceAccount.create" - value = false - }, - { - name = "aws.region" - value = data.aws_region.current.id - } - ] - - irsa_config = { - create_kubernetes_namespace = true - kubernetes_namespace = local.rds_name - - create_kubernetes_service_account = true - kubernetes_service_account = local.rds_name + source = "aws-ia/eks-ack-addons/aws" + version = "2.1.0" + + # Cluster Info + cluster_name = local.addon_context.eks_cluster_id + cluster_endpoint = local.addon_context.aws_eks_cluster_endpoint + oidc_provider_arn = local.addon_context.eks_oidc_provider_arn - irsa_iam_policies = [data.aws_iam_policy.rds.arn] - } + # Controllers to enable + enable_dynamodb = true - addon_context = local.addon_context + + tags = local.tags } -data "aws_iam_policy" "rds" { - name = "AmazonRDSFullAccess" -} - -module "ec2" { - source = "github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons/helm-addon?ref=v4.12.2" - - helm_config = { - name = local.ec2_name - chart = "ec2-chart" - repository = "oci://public.ecr.aws/aws-controllers-k8s" - version = "v0.1.1" - namespace = local.ec2_name - create_namespace = true - description = "ACK EC2 Controller Helm chart deployment configuration" - values = [ - # shortens pod name from `ack-ec2-ec2-chart-xxxxxxxxxxxxx` to `ack-ec2-xxxxxxxxxxxxx` - <<-EOT - nameOverride: ack-ec2 - EOT - ] - } - - set_values = [ +#module "iam_assumable_role_carts" { +# source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" +# version = "~> v5.5.5" +# create_role = true +# role_name = "${local.addon_context.eks_cluster_id}-carts-dynamo" +# provider_url = local.addon_context.eks_oidc_issuer_url +# role_policy_arns = [aws_iam_policy.carts_dynamo.arn] +# oidc_subjects_with_wildcards = ["system:serviceaccount:carts:*"] +# +# tags = local.tags +#} + +resource "aws_iam_policy" "carts_dynamo" { + name = "${local.addon_context.eks_cluster_id}-carts-dynamo" + path = "/" + description = "DynamoDB policy for AWS Sample Carts Application" + + policy = < + +To verify that the **Carts** module is in fact using the DynamoDB table we just provisioned, try adding a few items to the cart. + +![Cart screenshot](./assets/cart-items-present.png) + +And to check if items are in the cart as well, run + +```bash +$ aws dynamodb scan --table-name "${EKS_CLUSTER_NAME}-carts-ack" +``` + + +Congratulations! You've successfully created AWS Resources without leaving the confines of the Kubernetes API! + diff --git a/website/docs/automation/controlplanes/ack/how-it-works.md b/website/docs/automation/controlplanes/ack/how-it-works.md index 7d04b92c4..3864b57c8 100644 --- a/website/docs/automation/controlplanes/ack/how-it-works.md +++ b/website/docs/automation/controlplanes/ack/how-it-works.md @@ -1,14 +1,24 @@ --- -title: "How it works" +title: "How does ACK work?" sidebar_position: 5 --- -Each ACK service controller is packaged into a separate container image that is published in a public repository corresponding to an individual ACK service controller. For each AWS service that we wish to provision, resources for the corresponding controller must be installed in the Amazon EKS cluster. +Each ACK service controller is packaged into a separate container image that is published in a public repository corresponding to an individual ACK service controller. For each AWS service that we wish to provision, resources for the corresponding controller must be installed in the Amazon EKS cluster. We've already done this in the ```prepare-environment``` step. Helm charts and official container images for ACK are available [here](https://gallery.ecr.aws/aws-controllers-k8s). -The controllers for both Amazon RDS and Amazon EC2 have been pre-installed in the cluster, each running as a deployment in their respective namespaces. For example, let's take a look at the running RDS controller: +In this section of the workshop, as we will be working with Amazon DynamoDB, the ACK controllers for DynamoDB has been pre-installed in the cluster, running as a deployment in its own Kubernetes namespace. To see what's under the hood, lets run the below. ```bash -$ kubectl describe deployment -n ack-rds ack-rds +$ kubectl describe deployment ack-dynamodb -n ack-dynamodb ``` -This controller will watch for Kubernetes custom resources for RDS such as `rds.services.k8s.aws.DBInstance` and will make API calls to RDS based on the configuration in those resources created. As resources are created, the controller will feed back status updates to the custom resources in the `Status` fields. +:::info +kubectl also contains useful `-oyaml` and `-ojson` flags which extract either the full YAML or JSON manifests of the deployment definition instead of the formatted output. +::: + +This controller will watch for Kubernetes custom resources for DynamoDB such as `dynamodb.services.k8s.aws.Table` and will make API calls to the DynamoDB endpoint based on the configuration in these resources created. As resources are created, the controller will feed back status updates to the custom resources in the `Status` fields. For more information about the spec of the manifest, click [here](https://aws-controllers-k8s.github.io/community/reference/). + +If you'd like to dive deeper into the mechanics of what objects and API calls the controller listens for, run: + +```bash +$ kubectl get crd +``` diff --git a/website/docs/automation/controlplanes/ack/index.md b/website/docs/automation/controlplanes/ack/index.md index a321757a8..623a5cea9 100644 --- a/website/docs/automation/controlplanes/ack/index.md +++ b/website/docs/automation/controlplanes/ack/index.md @@ -12,19 +12,21 @@ $ prepare-environment automation/controlplanes/ack ``` This will make the following changes to your lab environment: -- Install the AWS Controllers for Kubernetes EC2 and RDS controllers in the Amazon EKS cluster +- Install the AWS Controllers for DynamoDB in the Amazon EKS cluster - Install the AWS Load Balancer controller in the Amazon EKS cluster -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/automation/controlplanes/ack/.workshop/terraform). +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/automation/controlplanes/ack/.workshop/terraform). ::: -[AWS Controllers for Kubernetes (ACK)](https://aws-controllers-k8s.github.io/community/) lets you define and use AWS service resources directly from Kubernetes. +The [AWS Controllers for Kubernetes (ACK)](https://aws-controllers-k8s.github.io/community/) project lets you define and use AWS service resources directly from Kubernetes using familiar YAML constructs. -With ACK, you can take advantage of AWS managed services for your Kubernetes applications without having to define resources outside of the cluster. This reduces the overall complexity for managing the dependencies of your application. +With ACK, you can take advantage of using AWS services such as databases ([RDS](https://aws-controllers-k8s.github.io/community/docs/tutorials/rds-example/) or others) and/or queues ([SQS](https://aws-controllers-k8s.github.io/community/docs/tutorials/sqs-example/) etc) for your Kubernetes applications without having to define resources manually outside of the cluster. This reduces the overall complexity for managing the dependencies of your application. -The sample application could be run completely within your cluster, including stateful workloads like database and message queues. This is a good approach when you're developing the application. When the team wants to make the application available in other stages like testing and production, they will use AWS managed services such as Amazon RDS database and Amazon MQ broker. This allows the team to focus on its customers and business projects and not to worry about managing a database or message broker. +The sample application could be run completely within your cluster, including stateful workloads like database and message queues. This is a good approach when you're developing the application. However, when the team wants to make the application available in other stages like testing and production, they will use AWS managed services such as Amazon DynamoDB databases and Amazon MQ brokers. This allows the team to focus on its customers and business projects and not have to administer and manage databases or message brokers. In this lab, we'll leverage ACK to provision these services and create secrets and configmaps containing the binding information connecting the application to these AWS managed services. -![EKS with RDS and MQ](./assets/eks-workshop-ack.jpg) +An important point to note here is that during the provisioning process above, we're using the new ACK Terraform module which allows you to rapidly deploy AWS Service Controllers to your cluster. See [here](https://registry.terraform.io/modules/aws-ia/eks-ack-addons/aws/latest#module_dynamodb) for more information. + +![EKS with DynamoDB](./assets/eks-workshop-ddb.png) diff --git a/website/docs/automation/controlplanes/ack/provision-resources.md b/website/docs/automation/controlplanes/ack/provision-resources.md index c29529c3e..2de7f7597 100644 --- a/website/docs/automation/controlplanes/ack/provision-resources.md +++ b/website/docs/automation/controlplanes/ack/provision-resources.md @@ -1,70 +1,101 @@ --- -title: "Provision ACK Resources" +title: "Provisioning ACK Resources" sidebar_position: 5 --- -By default the catalog component in the sample application uses a MySQL database running as a pod in the EKS cluster. In this lab, we'll provision an Amazon RDS database for our application using Kubernetes custom resources to specify the desired configuration required by the workload. +By default the **Carts** component in the sample application uses a DynamoDB local instance running as a pod in the EKS cluster called ```carts-dynamodb```. In this section of the lab, we'll provision an Amazon DynamoDB cloud based table for our application using Kubernetes custom resources and point the **Carts** deployment to use the newly provisioned DynamoDB table instead of the local copy. -![ACK reconciler concept](./assets/ack-desired-current.jpg) +![ACK reconciler concept](./assets/ack-desired-current-ddb.png) -The first thing we need to do is create a Kubernetes secret that'll be used to provide the master password for the RDS database. We'll configure ACK to read this secret as a source for the password: +The AWS Java SDK in the **Carts** component is able to use IAM Roles to interact with AWS services which means that we do not need to pass credentials, thus reducing the attack surface. In the EKS context, IRSA allows us to define per pod IAM Roles for applications to consume. To leverage IRSA, we first need to: -```bash hook=create-secret -$ kubectl create secret generic catalog-rds-pw \ - --from-literal=password="$(date +%s | sha256sum | base64 | head -c 32)" -n catalog -secret/catalog-rds-pw created +- Create a Kubernetes Service Account in the Carts namespace +- Create an IAM Policy with necessary DynamoDB permissions +- Create an IAM Role in AWS with the above permissions +- Map the Service Account to use the IAM role using Annotations in the Service Account definition. + +Fortunately, we have a handy one-liner to help with this process. Run the below: + +```bash +$ eksctl create iamserviceaccount --name carts-ack \ + --namespace carts --cluster $EKS_CLUSTER_NAME \ + --role-name ${EKS_CLUSTER_NAME}-carts-ack \ + --attach-policy-arn $DYNAMODB_POLICY_ARN --approve +2023-10-31 16:20:46 [i] 1 iamserviceaccount (carts/carts-ack) was included (based on the include/exclude rules) +2023-10-31 16:20:46 [i] 1 task: { + 2 sequential sub-tasks: { + create IAM role for serviceaccount "carts/carts-ack", + create serviceaccount "carts/carts-ack", + } }2023-10-31 16:20:46 [ℹ] building iamserviceaccount stack "eksctl-eks-workshop-addon-iamserviceaccount-carts-carts-ack" +2023-10-31 16:20:46 [i] deploying stack "eksctl-eks-workshop-addon-iamserviceaccount-carts-carts-ack" +2023-10-31 16:20:47 [i] waiting for CloudFormation stack "eksctl-eks-workshop-addon-iamserviceaccount-carts-carts-ack" +2023-10-31 16:21:17 [i] waiting for CloudFormation stack "eksctl-eks-workshop-addon-iamserviceaccount-carts-carts-ack" +2023-10-31 16:21:17 [i] created serviceaccount "carts/carts-ack" ``` +```eksctl``` provisions a CloudFormation stack to help manage these resources which can be seen in the output above. + +To learn more about how IRSA works, go [here](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). -Now let's explore the various ACK resources that we'll create. The first is an EC2 security group that will be applied to control access to the RDS database, which is done with a `SecurityGroup` resource: +--- + +Now, let's explore how we'll create the DynamoDB Table via a Kubernetes manifest ```file -manifests/modules/automation/controlplanes/ack/rds/k8s/rds-security-group.yaml +manifests/modules/automation/controlplanes/ack/dynamodb/dynamodb-create.yaml ``` :::info -The EC2 security group above allows any traffic from the CIDR range of the VPC used by the EKS cluster. This has been done to keep the example clear and understandable. A more secure approach would be to use [Security Groups for Pods](../../../networking/security-groups-for-pods/index.md) to allow traffic from specific pods. +Astute readers will notice the YAML Spec to be similar to the API endpoints and calls for DynamoDB such as ```tableName``` and ```attributeDefinitions```. ::: -Next we want the RDS database to use the private subnets in our VPC. To accomplish this, we'll create a `DBSubnetGroup` which selects the appropriate subnet IDs: +Next, we will need to update the regional endpoint for DynamoDB within the Configmap used by the Kustomization to update the **Carts** deployment. ```file -manifests/modules/automation/controlplanes/ack/rds/k8s/rds-dbgroup.yaml +manifests/modules/automation/controlplanes/ack/dynamodb/dynamodb-ack-configmap.yaml ``` -Finally, we can create the configuration for the RDS database itself with a `DBInstance` resource: - -```file -manifests/modules/automation/controlplanes/ack/rds/k8s/rds-instance.yaml +Using the ```envsubst``` utility, we will rewrite the environment variable AWS_REGION into the manifest and apply all the updates to the cluster. Run the below + +```bash wait=10 +$ kubectl kustomize ~/environment/eks-workshop/modules/automation/controlplanes/ack/dynamodb \ + | envsubst | kubectl apply -f- +namespace/carts unchanged +serviceaccount/carts unchanged +configmap/carts unchanged +configmap/carts-ack created +service/carts unchanged +service/carts-dynamodb unchanged +deployment.apps/carts configured +deployment.apps/carts-dynamodb unchanged +table.dynamodb.services.k8s.aws/items created +$ kubectl rollout status -n carts deployment/carts --timeout=120s ``` -Apply this configuration to the Amazon EKS cluster: - -```bash wait=30 -$ kubectl apply -k ~/environment/eks-workshop/modules/automation/controlplanes/ack/rds/k8s -securitygroup.ec2.services.k8s.aws/rds-eks-workshop created -dbinstance.rds.services.k8s.aws/rds-eks-workshop created -dbsubnetgroup.rds.services.k8s.aws/rds-eks-workshop created -``` +:::info +This command 'builds' the manifests using the kubectl kustomize command, pipes it to ```envsubst``` and then to kubectl apply. This makes it easy to template manifests and populate them at run-time. +::: -The ACK controllers in the cluster will react to these new resources and provision the AWS infrastructure it has expressed. For example, we can use the AWS CLI to query the RDS database: +The ACK controllers in the cluster will react to these new resources and provision the AWS infrastructure we have expressed with the manifests earlier. Lets check if ACK created the table by running ```bash -$ aws rds describe-db-instances \ - --db-instance-identifier ${EKS_CLUSTER_NAME}-catalog-ack +$ kubectl wait table.dynamodb.services.k8s.aws items -n carts --for=condition=ACK.ResourceSynced --timeout=15m +table.dynamodb.services.k8s.aws/items condition met +$ kubectl get table.dynamodb.services.k8s.aws items -n carts -ojson | yq '.status."tableStatus"' +ACTIVE ``` -It takes some time to provision the AWS managed services, in the case of RDS up to 10 minutes. The AWS provider controller will report the status of the reconciliation in the status field of the Kubernetes custom resources. +And now to check if the Table has been created using the AWS CLI, run ```bash -$ kubectl get dbinstances.rds.services.k8s.aws ${EKS_CLUSTER_NAME}-catalog-ack -n catalog -o yaml | yq '.status' -``` - -We can use this status field to instruct `kubectl` to wait until the RDS database has been successfully created: +$ aws dynamodb list-tables -```bash timeout=1080 -$ kubectl wait dbinstances.rds.services.k8s.aws ${EKS_CLUSTER_NAME}-catalog-ack \ - -n catalog --for=condition=ACK.ResourceSynced --timeout=15m -dbinstances.rds.services.k8s.aws/rds-eks-workshop condition met +{ + "TableNames": [ + "eks-workshop-carts-ack" + ] +} ``` + +This output tells us that the new table has been created! \ No newline at end of file