From 65999cab3a7199299aea685b61c6b8308cea4925 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Thu, 14 Mar 2024 14:04:00 -0600 Subject: [PATCH 01/10] Adding Redpanda Blueprint to Streaming Module --- streaming/redpanda/README.md | 301 ++++++++++++++++++ streaming/redpanda/addons.tf | 191 +++++++++++ streaming/redpanda/amp.tf | 137 ++++++++ .../redpanda/lambdaintegration/README.md | 178 +++++++++++ .../architecture-lambda-redpanda.png | Bin 0 -> 55617 bytes .../redpanda/lambdaintegration/template.yaml | 258 +++++++++++++++ streaming/redpanda/main.tf | 147 +++++++++ streaming/redpanda/outputs.tf | 43 +++ streaming/redpanda/providers.tf | 27 ++ .../templates/kube-prometheus-amp-enable.yaml | 52 +++ .../redpanda/templates/kube-prometheus.yaml | 23 ++ .../redpanda/templates/redpanda_values.yaml | 19 ++ streaming/redpanda/variables.tf | 53 +++ streaming/redpanda/versions.tf | 23 ++ streaming/redpanda/vpc.tf | 31 ++ 15 files changed, 1483 insertions(+) create mode 100644 streaming/redpanda/README.md create mode 100644 streaming/redpanda/addons.tf create mode 100644 streaming/redpanda/amp.tf create mode 100644 streaming/redpanda/lambdaintegration/README.md create mode 100644 streaming/redpanda/lambdaintegration/architecture-lambda-redpanda.png create mode 100644 streaming/redpanda/lambdaintegration/template.yaml create mode 100644 streaming/redpanda/main.tf create mode 100644 streaming/redpanda/outputs.tf create mode 100644 streaming/redpanda/providers.tf create mode 100644 streaming/redpanda/templates/kube-prometheus-amp-enable.yaml create mode 100644 streaming/redpanda/templates/kube-prometheus.yaml create mode 100644 streaming/redpanda/templates/redpanda_values.yaml create mode 100644 streaming/redpanda/variables.tf create mode 100644 streaming/redpanda/versions.tf create mode 100644 streaming/redpanda/vpc.tf diff --git a/streaming/redpanda/README.md b/streaming/redpanda/README.md new file mode 100644 index 000000000..a2ee24e8c --- /dev/null +++ b/streaming/redpanda/README.md @@ -0,0 +1,301 @@ +## Prerequisites + +This guide also has the following prerequisites for setting up and deploying Redpanda in EKS. + +* Terraform +* AWS-CLI +* Kubectl +* Helm +* jq + +**** + +## Terraform +**** +Terraform is an infrastructure as code tool that enables you to safely and predictably provision and manage infrastructure in any cloud. Install the latest. + +https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli + +**** + +## AWS-CLI +**** +AWS-CLI is a command line tool for managing AWS resources, install the latest version. + +https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html +**** + +## Kubectl + +Kubectl is a command line tool that is used to communicate with the Kubernetes API server. Install the latest kubectl for your platform from here: + +https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html + +**** + +## Helm + +Helm is used as the Kubernetes package manager for deploying Redpanda. Install the latest version of Helm for your platform from here: + +https://helm.sh/docs/intro/install/ + +**** + +Provider Versions Tested +| Provider | Version | +| ----------- | ----------- | +| Terraform | v1.7.2 | +| Helm | v3.14.0 | +| Redpanda | v5.7.22 | + +## Setting up the Cluster + +The Redpanda deployment documentation was used extensively in the creation of this guide. You can view the Redpanda guide at the following location: + +https://docs.redpanda.com/current/deploy/deployment-option/self-hosted/kubernetes/eks-guide/ + +In setting up and configuring our Redpanda cluster in AWS, we are using an Amazon EKS cluster as described in the guide linked above. + +We will use Terraform to deploy our AWS Resources (VPC, EKS, EKS add-ons) and our Redpanda Cluster, clone the files from Git. + + +To stand up the Amazon EKS Cluster and Infrastrucuture, run Terraform init, plan and apply. +``` +terraform init +terraform plan +terraform apply --autor-approve +``` + +After some time, you should see output that includes the message “Congratulations on installing redpanda!”, along with information on your cluster as well as a number of sample commands that you can use. + +Create Access to your Amazon EKS Cluster +``` +aws eks --region us-west-2 update-kubeconfig --name doeks-redpanda +``` +Verify Cluster +``` +kubectl get nodes +``` +You should see 6 worked nodes, 3 deployed for EKS Add-ons (Cert-Manager, AWS Load Balancer Controller, Monitoring Tools, etc). + +## Configuring Environment for Lambda/Serverless Lab + +In order to allow our Lambda function to communicate with the cluster, we will need to take several steps to set up external access: + +* Export the external certificate that was generated during the prior steps from the Redpanda cluster, and store it in AWS Secrets Manager +* Get our superuser password from AWS Secrets Manager and Setup Environment Variables + + +We will dive deeper on these steps in the following sections. + +## 1/ Export the Certificate and Store into AWS Secrets Manager + +Export ourt Cert from Kubernetes, this will be used to access the Redpanda Nodes. + +``` +kubectl get secret -n redpanda redpanda-external-cert -o go-template='{{ index .data "ca.crt" | base64decode }}' > ca.crt +``` + +Now we will import this certificate into AWS Secrets Manager so we can use it later + +``` +aws secretsmanager create-secret --name redpandarootcert --secret-string file://ca.crt + +Verify + +aws secretsmanager get-secret-value --secret-id redpandarootcert --output json | jq .SecretString +``` + +Alternatively this can be done with ACM, please check out this document on how: + +## 2/ Setup Environment Variables for Redpanda and Future use with Lambda + +Create Environment Variables, we will pull aws secretsmanager secret we created with Terraform for superuser of cluster, then configure our topic username of "redpanda-twitch-account" and password "changethispassword" which will be used later with SAM. This is for lab purposes only and you should secure this password. +``` +SUPUSER="superuser" +SUPPASS=`aws secretsmanager get-secret-value --secret-id redpanda_password-1234 --query "SecretString" --output text` +REGUSER="redpanda-twitch-account" +REGPASS="changethispassword" + +``` + +## Creating a User + +Now that we have set up and deployed the Redpanda cluster, we will need to create a user. To create the user, determine a username and password you would like to use, and use the following command to create the user: + +``` +kubectl --namespace redpanda exec -ti redpanda-0 -c redpanda -- \ +rpk acl user create $REGUSER \ +-p $REGPASS +``` + +You should see the following output confirming successful creation of the user: + +``` +Created user "". +``` + +Save the user name and password in a separate text file for later when SAM is used to deploy the remaining architecture. + +## Creating a Topic + +Next, we will use the superuser to grant the newly created user permission to execute all operations for a topic called twitch-chat. Feel free to use the topic name of your choice: + +``` +kubectl exec --namespace repanda -c redpanda redpanda-0 -- \ + rpk acl create --allow-principal User:$REGUSER \ + --operation all \ + --topic twitch-chat \ + -X user=$SUPUSER -X pass=$SUPPASS -X sasl.mechanism=SCRAM-SHA-512 +``` + +You should then see output similar to the following: + +``` +PRINCIPAL HOST RESOURCE-TYPE RESOURCE-NAME RESOURCE-PATTERN-TYPE OPERATION PERMISSION ERROR +User:redpanda-twitch-account * TOPIC twitch-chat LITERAL ALL ALLOW +``` + +In the following steps, we are going to use the newly created user account to create the topic, produce messages to the topic, and consumer messages to the topic. + +First, we will create an alias to simplify the usage of the rpk commands that will be used to work with the Redpanda deployment. Use the following command to configure the alias: + +``` +alias internal-rpk="kubectl --namespace redpanda exec -i -t redpanda-0 -c redpanda -- rpk -X user=$REGUSER -X pass=$REGPASS -X sasl.mechanism=SCRAM-SHA-256" +``` + +Next, create the topic “twitch-chat” with the following command: + +``` +internal-rpk topic create twitch-chat +``` + +You should see the following output after executing the above command: + +``` +TOPIC STATUS +twitch-chat OK +``` + +View the details of the topic just created by executing the following command: + +``` +internal-rpk topic describe twitch-chat +``` + +## Produce and Consume Messages + +Now use the following command to interactively produce messages to the topic: + +``` +internal-rpk topic produce twitch-chat +``` + +Type in some text and press enter to publish the message. After publishing several messages, use ctrl+C to end the publishing command. + +The output should look something like the following: + +``` +hello world +Produced to partition 0 at offset 0 with timestamp 1702851801374. +hello world 2 +Produced to partition 0 at offset 1 with timestamp 1702851806788. +hello world 3 +Produced to partition 0 at offset 2 with timestamp 1702851810335. +hello world 4 +Produced to partition 0 at offset 3 with timestamp 1702851813904. +^Ccommand terminated with exit code 130 +``` + +Next, use the following command to consume one message from the topic: + +``` +internal-rpk topic consume twitch-chat --num 1 +``` + +The output should look similar to the following: + +``` +{ + "topic": "twitch-chat", + "value": "hello world", + "timestamp": 1702851801374, + "partition": 0, + "offset": 0 +} +``` +## Accessing the Redpanda Console + +Having verified that you can produce and consume messages, next we will access the Redpanda Console by port forwarding to our localhost. This can be done using the following command: + +``` +kubectl --namespace redpanda port-forward svc/redpanda-console 8080:8080 +``` + +**Note:** If you are using Cloud9, you will need to use the following alternate command to do the port forwarding: + +``` +kubectl --namespace redpanda port-forward --address 0.0.0.0 svc/redpanda-console 8080:8080 +``` + +You will also need to allow traffic on port 8080 coming from your IP address to your localhost. If you are using Cloud9 as described in this guide, you will need to edit the security group of your Cloud9 instance to allow port 8080 inbound with a source of “My IP”. + +Do not allow full public access to port 8080, as the Redpanda Community Edition license does not enable authentication on the Redpanda Console. + +Once you are able to access the Redpanda Console, you can view information about your brokers, IP addresses and IDs, as well as information on your topics. You can view the messages produced to your topics, and produce additional messages to topics using the web interface. + +## Expand the Permissions for the Created Redpanda Account + +Now that the cluster is configured, we need to expand the permissions of the account that we created so that this account can be used to configure the AWS Lambda trigger. The account needs permissions such as creating new consumer groups. + +Steps: + +1. Access the Redpanda console as described in previous steps. +2. Click on “Security” on the left margin. +3. Click on the username that you created in previous steps, our example uses “redpanda-twitch-account”. +4. A dialog titled “Edit ACL” will appear. For the sake of this demo, click on “Allow all operations”. Note that for production use, you will want to limit these permissions. +5. Click on “OK”. + + +## Collecting Information Before Using SAM + +We need to collect a few pieces of information before deploying our template. + +* The VPC ID of the VPC created by eksctl. +* The subnet IDs of the public subnets created by eksctl. + +Get our VPC ID: +``` +export VPCID=`aws eks describe-cluster --name devcluster-02 | jq -r ".cluster.resourcesVpcConfig.vpcId"` +``` +Get the public subnets used by our VPC: + +``` +for subnet in $(aws ec2 describe-subnets --filter Name=vpc-id,Values=$VPCID --query 'Subnets[?MapPublicIpOnLaunch==`true`].SubnetId' --output text); do SUBNETS="${SUBNETS}${subnet}", ; done; + +``` +We now have Environment Variables $VPCID and $SUBNETS, as well as $REGUSER, $REPASS for uses with Lambda SAM Template. + +OR gather this data from console + +1. Navigate to the AWS Console and go to the VPC service. +2. Find the VPC created by our EKS cluster - the VPC name should have “eksctl” in the name. Copy the VPC ID to your notes. It should look something like “vpc-02bd59e133e36991d”. +3. Click on “Subnets” on the left margin. Find the two subnets with “Public” and “eksctl” in the name, and copy the subnet IDs to your notes. They should look something like “subnet-0d7fed3e19322e837”. + + +Get the IP Addresses assigned to your Redpanda containers: +``` +kubectl get pod --namespace \ +-o=custom-columns=NODE:.spec.nodeName,NAME:.[metadata.name](http://metadata.name/) -l \ +[app.kubernetes.io/component=redpanda-statefulset](http://app.kubernetes.io/component=redpanda-statefulset) + +``` + +## Conclusion + +With the Redpanda cluster now configured in EKS, we are ready to return to setting up the Lambda integration with Redpanda. + +[Lambda Integration with Redpanda](README.md) + +* * * +* * * diff --git a/streaming/redpanda/addons.tf b/streaming/redpanda/addons.tf new file mode 100644 index 000000000..935999ea1 --- /dev/null +++ b/streaming/redpanda/addons.tf @@ -0,0 +1,191 @@ + +################################################################################ +# EKS Addons +################################################################################ +#--------------------------------------------------------------- +# IRSA +#--------------------------------------------------------------- + +module "ebs_csi_driver_irsa" { + source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" + version = "~> 5.20" + + role_name_prefix = "${module.eks.cluster_name}-ebs-csi-driver-" + + attach_ebs_csi_policy = true + + oidc_providers = { + main = { + provider_arn = module.eks.oidc_provider_arn + namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"] + } + } + + tags = local.tags +} +#--------------------------------------------------------------- +# GP2 to GP3 default storage class and config for Redpanda +#--------------------------------------------------------------- +resource "kubernetes_annotations" "gp2_default" { + annotations = { + "storageclass.kubernetes.io/is-default-class" : "false" + } + api_version = "storage.k8s.io/v1" + kind = "StorageClass" + metadata { + name = "gp2" + } + force = true + + depends_on = [module.eks] +} + +resource "kubernetes_storage_class" "ebs_csi_encrypted_gp3_storage_class" { + metadata { + name = "gp3" + annotations = { + "storageclass.kubernetes.io/is-default-class" : "true" + } + } + + storage_provisioner = "ebs.csi.aws.com" + reclaim_policy = "Retain" + allow_volume_expansion = true + volume_binding_mode = "WaitForFirstConsumer" + parameters = { + fsType = "xfs" + type = "gp3" + } + + depends_on = [kubernetes_annotations.gp2_default] +} + + +#--------------------------------------------------------------- +# EKS Blueprints Kubernetes Addons +#--------------------------------------------------------------- +module "eks_blueprints_addons" { + source = "aws-ia/eks-blueprints-addons/aws" + version = "~> 1.2" + + cluster_name = module.eks.cluster_name + cluster_endpoint = module.eks.cluster_endpoint + cluster_version = module.eks.cluster_version + oidc_provider_arn = module.eks.oidc_provider_arn + + #--------------------------------------- + # Amazon EKS Managed Add-ons + #--------------------------------------- + eks_addons = { + aws-ebs-csi-driver = { + most_recent = true + service_account_role_arn = module.ebs_csi_driver_irsa.iam_role_arn + } + coredns = { + most_recent = true + } + vpc-cni = { + most_recent = true + } + kube-proxy = { + most_recent = true + } + } + enable_aws_load_balancer_controller = true + enable_cluster_autoscaler = true + enable_metrics_server = true + enable_aws_cloudwatch_metrics = true + + + + #--------------------------------------- + # FluentBit Config for EKS Cluster + #--------------------------------------- + enable_aws_for_fluentbit = true + aws_for_fluentbit = { + enable_containerinsights = true + kubelet_monitoring = true + set = [{ + name = "cloudWatchLogs.autoCreateGroup" + value = true + }, + { + name = "hostNetwork" + value = true + }, + { + name = "dnsPolicy" + value = "ClusterFirstWithHostNet" + } + ] + } + + + #--------------------------------------- + # Prommetheus and Grafana stack + #--------------------------------------- + #--------------------------------------------------------------- + # Install Kafka Montoring Stack with Prometheus and Grafana + # 1- Grafana port-forward `kubectl port-forward svc/kube-prometheus-stack-grafana 8080:80 -n kube-prometheus-stack` + # 2- Grafana Admin user: admin + # 3- Get admin user password: `aws secretsmanager get-secret-value --secret-id --region $AWS_REGION --query "SecretString" --output text` + #--------------------------------------------------------------- + enable_kube_prometheus_stack = true + kube_prometheus_stack = { + values = [ + var.enable_amazon_prometheus ? templatefile("${path.module}/templates/kube-prometheus-amp-enable.yaml", { + region = local.region + amp_sa = local.amp_ingest_service_account + amp_irsa = module.amp_ingest_irsa[0].iam_role_arn + amp_remotewrite_url = "https://aps-workspaces.${local.region}.amazonaws.com/workspaces/${aws_prometheus_workspace.amp[0].id}/api/v1/remote_write" + amp_url = "https://aps-workspaces.${local.region}.amazonaws.com/workspaces/${aws_prometheus_workspace.amp[0].id}" + storage_class_type = kubernetes_storage_class.ebs_csi_encrypted_gp3_storage_class.id + }) : templatefile("${path.module}/templates/kube-prometheus.yaml", {}) + ] + chart_version = "48.1.1" + set_sensitive = [ + { + name = "grafana.adminPassword" + value = data.aws_secretsmanager_secret_version.grafana_password_version.secret_string + } + ], + } + + tags = local.tags +} + + ## Cert-Manager Config + resource "helm_release" "cert-manager" { + name = "cert-manager" + repository = "https://charts.jetstack.io" + chart = "cert-manager" + version = "1.14.2" + namespace = "cert-manager" + create_namespace = true + set { + name = "installCRDs" + value = true + } + depends_on = [ module.eks_blueprints_addons ] + } + + ## Redpanda Helm Config +resource "helm_release" "redpanda" { + name = "redpanda" + repository = "https://charts.redpanda.com" + chart = "redpanda" + version = "5.7.22" + namespace = "redpanda" + create_namespace = true + + values = [ + templatefile("${path.module}/templates/redpanda_values.yaml", { + redpanda_username = var.redpanda_username, + redpanda_password = data.aws_secretsmanager_secret_version.redpanada_password_version.secret_string, + redpanda_domain = var.redpanda_domain, + storage_class = "gp3" + }) + ] + #timeout = "3600" + depends_on = [ helm_release.cert-manager ] + } \ No newline at end of file diff --git a/streaming/redpanda/amp.tf b/streaming/redpanda/amp.tf new file mode 100644 index 000000000..96df2a495 --- /dev/null +++ b/streaming/redpanda/amp.tf @@ -0,0 +1,137 @@ +#IAM Policy for Amazon Prometheus & Grafana +resource "aws_iam_policy" "grafana" { + count = var.enable_amazon_prometheus ? 1 : 0 + + description = "IAM policy for Grafana Pod" + name_prefix = format("%s-%s-", local.name, "grafana") + path = "/" + policy = data.aws_iam_policy_document.grafana[0].json +} + +data "aws_iam_policy_document" "grafana" { + count = var.enable_amazon_prometheus ? 1 : 0 + + statement { + sid = "AllowReadingMetricsFromCloudWatch" + effect = "Allow" + resources = ["*"] + + actions = [ + "cloudwatch:DescribeAlarmsForMetric", + "cloudwatch:ListMetrics", + "cloudwatch:GetMetricData", + "cloudwatch:GetMetricStatistics" + ] + } + + statement { + sid = "AllowGetInsightsCloudWatch" + effect = "Allow" + resources = ["arn:${local.partition}:cloudwatch:${local.region}:${local.account_id}:insight-rule/*"] + + actions = [ + "cloudwatch:GetInsightRuleReport", + ] + } + + statement { + sid = "AllowReadingAlarmHistoryFromCloudWatch" + effect = "Allow" + resources = ["arn:${local.partition}:cloudwatch:${local.region}:${local.account_id}:alarm:*"] + + actions = [ + "cloudwatch:DescribeAlarmHistory", + "cloudwatch:DescribeAlarms", + ] + } + + statement { + sid = "AllowReadingLogsFromCloudWatch" + effect = "Allow" + resources = ["arn:${local.partition}:logs:${local.region}:${local.account_id}:log-group:*:log-stream:*"] + + actions = [ + "logs:DescribeLogGroups", + "logs:GetLogGroupFields", + "logs:StartQuery", + "logs:StopQuery", + "logs:GetQueryResults", + "logs:GetLogEvents", + ] + } + + statement { + sid = "AllowReadingTagsInstancesRegionsFromEC2" + effect = "Allow" + resources = ["*"] + + actions = [ + "ec2:DescribeTags", + "ec2:DescribeInstances", + "ec2:DescribeRegions", + ] + } + + statement { + sid = "AllowReadingResourcesForTags" + effect = "Allow" + resources = ["*"] + actions = ["tag:GetResources"] + } + + statement { + sid = "AllowListApsWorkspaces" + effect = "Allow" + resources = [ + "arn:${local.partition}:aps:${local.region}:${local.account_id}:/*", + "arn:${local.partition}:aps:${local.region}:${local.account_id}:workspace/*", + "arn:${local.partition}:aps:${local.region}:${local.account_id}:workspace/*/*", + ] + actions = [ + "aps:ListWorkspaces", + "aps:DescribeWorkspace", + "aps:GetMetricMetadata", + "aps:GetSeries", + "aps:QueryMetrics", + "aps:RemoteWrite", + "aps:GetLabels" + ] + } +} + +#------------------------------------------ +# Amazon Prometheus +#------------------------------------------ +locals { + amp_ingest_service_account = "amp-iamproxy-ingest-service-account" + amp_namespace = "kube-prometheus-stack" +} + +resource "aws_prometheus_workspace" "amp" { + count = var.enable_amazon_prometheus ? 1 : 0 + + alias = format("%s-%s", "amp-ws", local.name) + tags = local.tags +} + +module "amp_ingest_irsa" { + count = var.enable_amazon_prometheus ? 1 : 0 + + source = "aws-ia/eks-blueprints-addon/aws" + version = "~> 1.0" + create_release = false + create_role = true + create_policy = false + role_name = format("%s-%s", local.name, "amp-ingest") + role_policies = { amp_policy = aws_iam_policy.grafana[0].arn } + + oidc_providers = { + this = { + provider_arn = module.eks.oidc_provider_arn + namespace = local.amp_namespace + service_account = local.amp_ingest_service_account + } + } + + tags = local.tags +} diff --git a/streaming/redpanda/lambdaintegration/README.md b/streaming/redpanda/lambdaintegration/README.md new file mode 100644 index 000000000..5d06e2122 --- /dev/null +++ b/streaming/redpanda/lambdaintegration/README.md @@ -0,0 +1,178 @@ +# Redpanda Integration with AWS Lambda + +## Introduction + +Redpanda is a simple, powerful, and cost-efficient streaming data platform that is compatible with Kafka APIs, and is used by many enterprise organizations across industries. + +Customers have looked to us for guidance on using Redpanda as a Kafka alternative in AWS, and integrating it with various AWS services. + +This guide shows how to deploy a basic Redpanda cluster in Amazon EKS using the free Community Edition of Redpanda, and then how to integrate it with AWS Lambda using the existing Apache Kafka API trigger, which enables Lambda functions to easily be configured as consumers of the streaming data. + +## Solution Overview + +The integration consists of an EKS cluster running the Redpanda deployment, as well as a Lambda function using the Apache Kafka trigger configured to receive data streaming events from Redpanda. + +SASL SCRAM 256 is used for secure authentication between Lambda and Redpanda, and TLS is used for encryption in transit. AWS Secrets Manager securely stores the credentials and the self signed certificate used for TLS communications. + +Finally, we use Amazon Route 53 for private DNS in our EKS VPC to resolve our cluster node names. + +We will walk through the steps to set up and deploy the EKS cluster and Redpanda, and then use AWS SAM to deploy the remaining architecture. + +![arch-final.png](architecture-lambda-redpanda.png) + + **** +## Setting up a Working Environment + +We will use an AWS Cloud9 instance as the working environment in the steps provided in this guide, however the included steps can also be used with Linux and MacOS. + +In order to deploy a Cloud9 instance: + +1. Log in to the AWS Console and navigate to the Cloud9 service. +2. Click on “Create environment”. +3. Give the instance a name such as “RPLambdaIntergationWorkspace”. +4. Optionally specify a VPC and subnet for the instance to run in. +5. Accept the other defaults and click on “Create”. +6. The environment will take a few minutes to be created. Once it has succeeded, click on “Open” to access the environment. + +Once you have accessed your Cloud9 environment, we recommend clicking on the “+” next to the “Welcome” tab and choosing “New Terminal”. This terminal gives you a larger window than the terminal automatically launched at the bottom of the screen, and will be used to run the commands in the following steps. + +Depending on your account configuration, you may also need to export your AWS credentials in this terminal, such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION. “us-east-1” is used as the default region for these steps. + +## Prerequisites + +The following prerequisites must be installed and configured in your working environment. + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + +## Setting up the Redpanda Cluster + +If you do not already have a Redpanda deployment in AWS, you can find instructions on setting up and deploying Redpanda in EKS here: + +[Setting up a Redpanda Cluster in EKS](Redpandasetup.md) + +## Collecting Information Before Using SAM + +We need to collect a few pieces of information before deploying our template. This includes the following: + +* The AWS region in which Redpanda is deployed. For example, 'us-east-1' is used in our examples. +* The VPC ID in which Redpanda is deployed. +* The two public subnet IDs for your Redpanda cluster. +* The domain name used by your Redpanda cluster. For example, 'customredpandadomain.local'. +* The internal IP addresses for your three nodes +* The username that Lambda will use to authenticate with Redpanda +* The password that Lambda will use to authenticate with Redpanda +* The ARN of the secrets manager secret that contains the certificate for TLS communications with Redpanda. + +Make sure that you have this information handy before proceeding. + +## AWS SAM + +The AWS SAM CLI is a serverless tool for building and testing Lambda applications. It uses Docker to locally test your functions in an Amazon Linux environment that resembles the Lambda execution environment. It can also emulate your application's build environment and API. +To use the AWS SAM CLI, you need the following tools. + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) + +During the deployment, you will be prompted to enter the information that we collected during the previous steps. We will use the SAM template to create the following: + + +* Private Route 53 Hosted Zone with A name records for the Redpanda nodes. +* A Lambda function to receive the published messages from our Redpanda topic, along with a role granting the Lambda function the necessary permissions. +* VPC Endpoints for Lambda, STS, and Secretsmanager, to enable secure communications to these services from our EKS cluster. +* A Secrets Manager secret with the credentials needed for the Lambda function to receive events from Redpanda. + +Make sure that you are in the working directory with the template.yaml file cloned from the repository, and then run the following command: + +``` +sam deploy -t template.yaml --guided --capabilities CAPABILITY_NAMED_IAM +``` + +During the guided deployment, you will be prompted to enter the parameters we have collected while setting up our Redpanda deployment. You will also need to specify a parameter prefix, which is a name that will be prepended on your Lambda function. Your input should look similar to the following: + +``` +Setting default arguments for 'sam deploy' +========================================= +Stack Name [sam-app]: pandastack +AWS Region [us-east-1]: +Parameter Prefix []: lambdapanda +Parameter VPC []: vpc-02bd59e133e36991d +Parameter PublicSubnetIDs []: subnet-01fa06aac679a7648,subnet-0d7fed3e19322e837 +Parameter DomainName []: customredpandadomain.local +Parameter InternalIPnode0 []: 192.168.49.207 +Parameter InternalIPnode1 []: 192.168.12.190 +Parameter InternalIPnode2 []: 192.168.48.119 +Parameter UserName []: redpanda-twitch-account +Parameter Password []: changethispassword +Parameter TLSCertificateARN []: arn:aws:secretsmanager:us-east-1:808511450715:secret:redpandarootcert-xeFNgp +#Shows you resources changes to be deployed and require a 'Y' to initiate deploy +Confirm changes before deploy [y/N]: y +#SAM needs permission to be able to create roles to connect to the resources in your template +Allow SAM CLI IAM role creation [Y/n]: +#Preserves the state of previously provisioned resources when an operation fails +Disable rollback [y/N]: +Save arguments to configuration file [Y/n]: +SAM configuration file [samconfig.toml]: +SAM configuration environment [default]: +``` + +After specifying the required parameter values, accept the defaults on the remaining questions and proceed to deploy the changeset. + +## Setting up the Trigger + +Now that our template is deployed successfully, we are ready to configure the trigger that will call the Lambda function when messages are published to our Redpanda topic. + +Steps: + +1. Navigate to Lambda in the AWS Console. +2. Click on the function name created by our SAM template, it will have “Redpanda-Event-Processor” in the name. +3. Click on “Add trigger” found under the Function overview. +4. Choose “Apache Kafka” from the list of available triggers. +5. Click on “Add” under “Boostrap servers”. Here we will specify “redpanda-0.customredpandadomain.local:31092”, as this is the hostname and port for external Kafka API access to our Redpanda cluster. The domain in your hostname may vary depending on the domain you used in earlier steps. +6. Repeat the previous step and twice and specify "redpanda-1.customredpandadomain.local:31092" and "redpanda-2.customredpandadomain.local:31092" as the bootstrap servers. This may be an optional step depending on the number of nodes configured in your deployment. +7. Specify the “Topic name” that you wish to receive events for. +8. Under VPC, choose the VPC that contains the Redpanda deployment. +9. Under VPC subnets, choose the public subnets from the VPC that contains the Redpanda deployment. +10. Under VPC security groups, choose the default VPC security group (or a suitable security group that allows incoming traffic from your VPC to your nodes on port 31092). +11. Under Authentication, choose “SASL_SCRAM_256_AUTH”. +12. Under Secrets Manager key, choose the secret name “pandacredentials”. +13. Under Encryption, choose the secret name used to store the certificate exported from the Redpanda cluster. +14. Click on “Add”. + +The trigger will take some time to be created. Click on the refresh arrow to refresh the status. Once you see a status of “Enabled”, you are ready to test receiving an event. + +## Testing + +To test receiving an event, simply publish any data to the topic that the trigger was configured for. If you used the instructions in this guide for setting up the Redpanda deployment, return to your Cloud9 console or local console that was used for creating the cluster. Re-run the following command that was used to produce a message to our topic: + +``` +internal-rpk topic produce twitch-chat +``` + +Enter some text, press enter, and repeat this several times to produce a few messages to your topic. Now return to the browser tab in which you configured your Lambda function, and click on “Monitor”. Then click on “View Cloudwatch logs”, and click on the most recent log stream under “Log streams”. You should see events representing your Lambda function being called, and the debug output containing the contents of the events. + +## Cleaning Up + +To clean up the resources created during this guide, take the following steps: + +1. Navigate to the Lambda service in the AWS console, click on “Functions”. Next, click on the name of our Lambda function and then click on the “Apache Kafka” trigger. Tick the box next to the trigger and then click on “Delete”. +2. Return to the terminal you have used during the deployment and issue the command “sam delete”. +3. If you used eksctl to create an EKS cluster using this guide, navigate to the AWS CloudFormation console. Delete the stacks with the name “eksctl-” that were automatically created when using eksctl to create the cluster. +4. If you used Cloud9 as the working environment, you can also delete the Cloud9 stack that was built when creating the Cloud9 environment. Alternatively, navigate to the Cloud9 service in the AWS console and delete the environment from there. + +## Conclusion + +In this guide, you have learned how to deploy and configure a Redpanda cluster in AWS EKS, and to enable integration with Lambda event triggering via the Kafka API interface. This allows you to easily use Lambda to consume data streams from your topics in Redpanda. + +For more serverless learning resources, visit [Serverless Land](https://serverlessland.com/). + +Find the Community Edition of Redpanda on Github here: +https://github.com/redpanda-data/redpanda + +* * * +* * * + +Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + diff --git a/streaming/redpanda/lambdaintegration/architecture-lambda-redpanda.png b/streaming/redpanda/lambdaintegration/architecture-lambda-redpanda.png new file mode 100644 index 0000000000000000000000000000000000000000..1fa59e3146636d14ae5180dc54b1b249c0d634f0 GIT binary patch literal 55617 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 b80SQ(y++o}dIobi;73_r167K&eDQw(3yvP$ literal 0 HcmV?d00001 diff --git a/streaming/redpanda/lambdaintegration/template.yaml b/streaming/redpanda/lambdaintegration/template.yaml new file mode 100644 index 000000000..8699fce63 --- /dev/null +++ b/streaming/redpanda/lambdaintegration/template.yaml @@ -0,0 +1,258 @@ +Transform: AWS::Serverless-2016-10-31 +AWSTemplateFormatVersion: 2010-09-09 +Description: "This template is for deploying resources to integrate a Lambda function with a Redpanda deployment in EKS" +Parameters: + Prefix: + Description: "Prefix to be added to all the resources name for tracking purposes" + Type: String + MinLength: 2 + MaxLength: 20 + VPC: + Description: "Enter the VPC ID of your EKS VPC" + Type: AWS::EC2::VPC::Id + PublicSubnetIDs: + Description: "Enter the two Public subnets for the EKS VPC. For example: subnet-0123abc,subnet-0456def" + Type: "List" + #SecurityGroupID: + # Description: "Enter the default security group ID for the EKS VPC" + # Type: AWS::EC2::SecurityGroup::Id + DomainName: + Description: "Full Domain Name for the Redpanda Cluster" + Type: String + MinLength: 15 + MaxLength: 90 + InternalIPnode0: + Description: "The internal ip address of node 0 from the cluster" + Type: String + MinLength: 7 + MaxLength: 15 + InternalIPnode1: + Description: "The internal ip address of node 1 from the cluster" + Type: String + MinLength: 7 + MaxLength: 15 + InternalIPnode2: + Description: "The internal ip address of node 2 from the cluster" + Type: String + MinLength: 7 + MaxLength: 15 + UserName: + Description: "Username used to access Redpanda" + Type: String + MinLength: 8 + MaxLength: 30 + Password: + Description: "Password used to access Redpanda" + Type: String + MinLength: 8 + MaxLength: 30 + #TLSCertificateARN: + # Description: "ARN to Secrets Manager stored certificate" + # Type: String + +Resources: +# Lambda VPC Endpoint + VPCEndpointLambda: + Type: AWS::EC2::VPCEndpoint + DependsOn: + - DNS + # - HostedZoneRecordSet + Properties: + PrivateDnsEnabled: true + ServiceName: !Sub com.amazonaws.${AWS::Region}.lambda + SubnetIds: + - !Select [ 0, !Ref PublicSubnetIDs ] + - !Select [ 1, !Ref PublicSubnetIDs ] + VpcId: !Ref VPC + VpcEndpointType: Interface + +# STS VPC Endpoint + VPCEndpointSTS: + Type: AWS::EC2::VPCEndpoint + DependsOn: + - DNS + Properties: + PrivateDnsEnabled: true + ServiceName: !Sub com.amazonaws.${AWS::Region}.sts + SubnetIds: + - !Select [ 0, !Ref PublicSubnetIDs ] + - !Select [ 1, !Ref PublicSubnetIDs ] + VpcId: !Ref VPC + VpcEndpointType: Interface + +# Secrets Manager VPC Endpoint + VPCEndpointSecrets: + Type: AWS::EC2::VPCEndpoint + DependsOn: + - DNS + Properties: + PrivateDnsEnabled: true + ServiceName: !Sub com.amazonaws.${AWS::Region}.secretsmanager + SubnetIds: + - !Select [ 0, !Ref PublicSubnetIDs ] + - !Select [ 1, !Ref PublicSubnetIDs ] + VpcId: !Ref VPC + VpcEndpointType: Interface + +# Define the role for our Lambda function + LambdaRole: + Type: 'AWS::IAM::Role' + Properties: + RoleName: !Select [0, [!Join ['-', [!Ref Prefix, 'custom-lambda-function-role']]]] + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + Policies: + - PolicyName: !Sub + - '${Prefix}-LambdaRedpandaPolicy' + - Prefix: !Ref Prefix + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "ec2:DescribeNetworkInterfaces" + - "ec2:CreateNetworkInterface" + - "ec2:DeleteNetworkInterface" + - "ec2:AssignPrivateIpAddresses" + - "ec2:UnassignPrivateIpAddresses" + - "ec2:DescribeSecurityGroups" + - "ec2:DescribeSubnets" + - "ec2:DescribeVpcs" + - "secretsmanager:GetSecretValue" + - "kms:DescribeKey" + - "kms:ListAliases" + - "kms:ListKeys" + Resource: '*' + - PolicyName: BasicExecutionRole + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: logs:CreateLogGroup + Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*' + - Effect: Allow + Action: + - logs:CreateLogStream + - logs:PutLogEvents + Resource: !Sub + - 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${functionName}:*' + - functionName: !Select [0, [!Join ['-', [!Ref Prefix, Redpanda-Event-Processor]]]] + + RedpandaEventProcessor: + Type: AWS::Serverless::Function + DependsOn: + - DNS + - HostedZoneRecordSet0 + - HostedZoneRecordSet1 + - HostedZoneRecordSet2 + - UserNamePasswordSecret + - VPCEndpointLambda + - VPCEndpointSTS + - VPCEndpointSecrets + Properties: + FunctionName: !Select [0, [!Join ['-', [!Ref Prefix, Redpanda-Event-Processor]]]] + InlineCode: | + import json + def lambda_handler(event, context): + print('## EVENT') + print(event) + return { + 'statusCode': 200, + 'body': json.dumps('Event received from Redpanda!') + } + Timeout: 60 + Handler: index.lambda_handler + Runtime: python3.11 + Role: !GetAtt LambdaRole.Arn + #Events: + # SelfManagedKafkaEvent: + # Type: SelfManagedKafka + # Properties: + # Enabled: true + # KafkaBootstrapServers: + # - !Join [ '', ['redpanda-0.',!Ref DomainName,':31092'] ] + # - !Join [ '', ['redpanda-1.',!Ref DomainName,':31092'] ] + # - !Join [ '', ['redpanda-2.',!Ref DomainName,':31092'] ] + # SourceAccessConfigurations: + # - Type: SASL_SCRAM_256_AUTH + # URI: !Ref UserNamePasswordSecret + # - Type: SERVER_ROOT_CA_CERTIFICATE + # URI: !Ref TLSCertificateARN + # - Type: VPC_SUBNET + # URI: '' + # - Type: VPC_SECURITY_GROUP + # URI: !Join [ '', ['security_group:',!Ref SecurityGroupID] ] + # StartingPosition: LATEST + # Topics: + # - 'twitch-chat' + +# Create the Private Hosted Zone + DNS: + Type: AWS::Route53::HostedZone + Properties: + HostedZoneConfig: + Comment: 'Hosted zone for Redpanda nodes' + Name: !Ref DomainName + VPCs: + - VPCId: !Ref VPC + VPCRegion: !Ref 'AWS::Region' +# Create the A record for the host redpanda-0 + HostedZoneRecordSet0: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: !Ref DNS + Name: !Select [0, [!Join ['', [ 'redpanda-0.',!Ref DomainName]]]] + Type: "A" + ResourceRecords: + - !Ref InternalIPnode0 + TTL: '300' +# Create the A record for the host redpanda-1 + HostedZoneRecordSet1: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: !Ref DNS + Name: !Select [0, [!Join ['', [ 'redpanda-1.',!Ref DomainName]]]] + Type: "A" + ResourceRecords: + - !Ref InternalIPnode1 + TTL: '300' +# Create the A record for the host redpanda-2 + HostedZoneRecordSet2: + Type: AWS::Route53::RecordSet + Properties: + HostedZoneId: !Ref DNS + Name: !Select [0, [!Join ['', [ 'redpanda-2.',!Ref DomainName]]]] + Type: "A" + ResourceRecords: + - !Ref InternalIPnode2 + TTL: '300' + +# Secrets Manager Secrets + UserNamePasswordSecret: + Type: 'AWS::SecretsManager::Secret' + Properties: + Name: pandacredentials + Description: "Username and password key pairs for Redpanda access" + SecretString: !Join [ '', ['{"username":"',!Ref UserName,'","password":"',!Ref Password,'"}'] ] + +Outputs: + VPCEndpointLambda: + Value: !Ref VPCEndpointLambda + VPCEndpointSTS: + Value: !Ref VPCEndpointSTS + VPCEndpointSecrets: + Value: !Ref VPCEndpointSecrets + RedpandaEventProcessor: + Value: !Ref RedpandaEventProcessor + HostedZoneId: + Description: 'The ID of the hosted zone.' + Value: !Ref DNS + Export: + Name: !Sub '${AWS::StackName}-HostedZoneId' diff --git a/streaming/redpanda/main.tf b/streaming/redpanda/main.tf new file mode 100644 index 000000000..2e3851238 --- /dev/null +++ b/streaming/redpanda/main.tf @@ -0,0 +1,147 @@ +################################################################################ +# Data +################################################################################ + +data "aws_availability_zones" "available" {} +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} + +data "aws_secretsmanager_secret_version" "redpanada_password_version" { + secret_id = aws_secretsmanager_secret.redpanada_password.id + depends_on = [aws_secretsmanager_secret_version.redpanada_password] +} +data "aws_secretsmanager_secret_version" "grafana_password_version" { + secret_id = aws_secretsmanager_secret.redpanada_password.id + depends_on = [aws_secretsmanager_secret_version.grafana_password_version] +} + +################################################################################ +# Local Variables +################################################################################ +locals { + name = var.name + region = var.region + account_id = data.aws_caller_identity.current.account_id + partition = data.aws_partition.current.partition + + vpc_cidr = var.vpc_cidr + azs = slice(data.aws_availability_zones.available.names, 0 ,2) + + + tags = { + + } + +} + +#--------------------------------------- +# Redpanda Config +#--------------------------------------- + +resource "random_password" "redpanada_password" { + length = 16 + special = false +} +resource "aws_secretsmanager_secret" "redpanada_password" { + name = "redpanda_password-1234" + recovery_window_in_days = 0 +} +resource "aws_secretsmanager_secret_version" "redpanada_password" { + secret_id = aws_secretsmanager_secret.redpanada_password.id + secret_string = random_password.redpanada_password.result +} + +#--------------------------------------------------------------- +# Grafana Admin credentials resources +#--------------------------------------------------------------- +resource "random_string" "random_suffix" { + length = 10 + special = false + upper = false +} + +resource "random_password" "grafana" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +#tfsec:ignore:aws-ssm-secret-use-customer-key +resource "aws_secretsmanager_secret" "grafana" { + name = "grafana-${random_string.random_suffix.result}" + recovery_window_in_days = 0 # Set to zero for this example to force delete during Terraform destroy +} + +resource "aws_secretsmanager_secret_version" "grafana_password_version" { + secret_id = aws_secretsmanager_secret.grafana.id + secret_string = random_password.grafana.result +} + + + +################################################################################ +# Cluster and Managed Node Group +################################################################################ +module "eks" { + source ="terraform-aws-modules/eks/aws" + version = "~>19.15" + + + cluster_name = local.name + cluster_version = var.eks_cluster_version + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + cluster_endpoint_public_access = true + cluster_endpoint_private_access = true + + node_security_group_additional_rules = { + ingress_self_all = { + description = "Node to node all ports/protocols" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "ingress" + self = true + } + egress_all = { + description = "Node all egress" + protocol = "-1" + from_port = 0 + to_port = 0 + type = "egress" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + } +#--------------------------------------------------------------- +# Managed Node Group - Core Services +#--------------------------------------------------------------- + + eks_managed_node_groups = { + core_node_group = { + name = "core-mng-01" + description = "Core EKS managed node group" + instance_types = ["m5.large"] + min_size = 3 + max_size = 6 + desired_size = 3 + +#--------------------------------------------------------------- +# Managed Node Group - Redpanda +#--------------------------------------------------------------- + + } + redpanda_node_group = { + name = "redpanda-mng-01" + description = "Redpanda EKS Managed Node Group" + instance_types = ["c5d.large"] + min_size = 3 + max_size = 6 + desired_size = 3 + + } + + } + +} diff --git a/streaming/redpanda/outputs.tf b/streaming/redpanda/outputs.tf new file mode 100644 index 000000000..eb153384e --- /dev/null +++ b/streaming/redpanda/outputs.tf @@ -0,0 +1,43 @@ +################################################################################ +# Cluster +################################################################################ +output "cluster_arn" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = module.eks.cluster_arn +} + +output "cluster_name" { + description = "The Amazon Resource Name (ARN) of the cluster" + value = module.eks.cluster_id +} + +output "oidc_provider" { + description = "The OIDC Provider" + value = module.eks.cluster_oidc_issuer_url +} + +output "oidc_provider_arn" { + description = "The ARN of the OIDC Provider" + value = module.eks.oidc_provider_arn +} + +################################################################################ +# VPC Info +################################################################################ +output "vpc_id" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.vpc.vpc_id +} +output "vpc_subnets" { + description = "Map of attribute maps for all EKS managed node groups created" + value = module.vpc.public_subnets +} +################################################################################ +# Kubernetes Config +################################################################################ + +output "configure_kubectl" { + description = "Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig" + value = "aws eks --region ${local.region} update-kubeconfig --name ${module.eks.cluster_name}" +} + diff --git a/streaming/redpanda/providers.tf b/streaming/redpanda/providers.tf new file mode 100644 index 000000000..ec4ecd7a3 --- /dev/null +++ b/streaming/redpanda/providers.tf @@ -0,0 +1,27 @@ +provider "aws" { + region = local.region +} + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + #token = data.aws_eks_cluster_auth.this.token + exec { + api_version = "client.authentication.k8s.io/v1beta1" + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + command = "aws" + } +} + +provider "helm" { + kubernetes { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + #token = data.aws_eks_cluster_auth.this.token + exec { + api_version = "client.authentication.k8s.io/v1beta1" + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + command = "aws" + } + } +} diff --git a/streaming/redpanda/templates/kube-prometheus-amp-enable.yaml b/streaming/redpanda/templates/kube-prometheus-amp-enable.yaml new file mode 100644 index 000000000..20b4a75d7 --- /dev/null +++ b/streaming/redpanda/templates/kube-prometheus-amp-enable.yaml @@ -0,0 +1,52 @@ +prometheus: + serviceAccount: + create: true + name: ${amp_sa} + annotations: + eks.amazonaws.com/role-arn: ${amp_irsa} + prometheusSpec: + remoteWrite: + - url: ${amp_remotewrite_url} + sigv4: + region: ${region} + queueConfig: + maxSamplesPerSend: 1000 + maxShards: 200 + capacity: 2500 + retention: 5h + scrapeInterval: 30s + evaluationInterval: 30s + scrapeTimeout: 10s + storageSpec: + volumeClaimTemplate: + metadata: + name: data + spec: + storageClassName: ${storage_class_type} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi +alertmanager: + enabled: false + +grafana: + enabled: true + defaultDashboardsEnabled: true +# Adding AMP datasource to Grafana config + serviceAccount: + create: false + name: ${amp_sa} + grafana.ini: + auth: + sigv4_auth_enabled: true + additionalDataSources: + - name: AMP + editable: true + jsonData: + sigV4Auth: true + sigV4Region: ${region} + type: prometheus + isDefault: false + url: ${amp_url} diff --git a/streaming/redpanda/templates/kube-prometheus.yaml b/streaming/redpanda/templates/kube-prometheus.yaml new file mode 100644 index 000000000..498fb2824 --- /dev/null +++ b/streaming/redpanda/templates/kube-prometheus.yaml @@ -0,0 +1,23 @@ +prometheus: + prometheusSpec: + retention: 5h + scrapeInterval: 30s + evaluationInterval: 30s + scrapeTimeout: 10s + storageSpec: + volumeClaimTemplate: + metadata: + name: data + spec: + storageClassName: ${storage_class_type} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi +alertmanager: + enabled: false + +grafana: + enabled: true + defaultDashboardsEnabled: true diff --git a/streaming/redpanda/templates/redpanda_values.yaml b/streaming/redpanda/templates/redpanda_values.yaml new file mode 100644 index 000000000..cb3adfa98 --- /dev/null +++ b/streaming/redpanda/templates/redpanda_values.yaml @@ -0,0 +1,19 @@ +auth: + sasl: + enabled: true + secretRef: redpanda-superusers + users: + - name: ${redpanda_username} + password: "${redpanda_password}" + +storage: + persistentVolume: + enabled: true + size: 20Gi + storageClass: ${storage_class} + labels: {} + annotations: {} + + +external: + domain: ${redpanda_domain} \ No newline at end of file diff --git a/streaming/redpanda/variables.tf b/streaming/redpanda/variables.tf new file mode 100644 index 000000000..3e44fd592 --- /dev/null +++ b/streaming/redpanda/variables.tf @@ -0,0 +1,53 @@ +variable name { + description = "Name for Resoucres created - dokeks-redpanda" + default = "doeks-redpanda" + type = string +} +variable "region" { + description = "Default Region" + default = "us-west-2" + type = string +} +variable eks_cluster_version { + description = "EKS Cluster Version" + default = "1.28" + type = string +} +variable "vpc_cidr" { + description = "VPC CIDR" + default = "172.16.0.0/16" + type = string +} +variable "public_subnets" { + description = "Public Subnets with 126 IPs" + default = ["172.16.255.0/25", "172.16.255.128/25"] + type = list(string) +} + +variable "private_subnets" { + description = "Private Subnets with 510 IPs" + default = ["172.16.0.0/23", "172.16.2.0/23"] + type = list(string) +} +#--------------------------------------- +# Prometheus Enable +#--------------------------------------- +variable "enable_amazon_prometheus" { + description = "Enable AWS Managed Prometheus service" + type = bool + default = true +} + +#--------------------------------------- +# Redpanda Config +#--------------------------------------- +variable "redpanda_username" { + default = "superuser" + description = "Default Super Username for Redpanda deployment" + type = string +} +variable "redpanda_domain" { + default = "customredpandadomain.local" + description = "Repanda Custom Domain" + type = string +} \ No newline at end of file diff --git a/streaming/redpanda/versions.tf b/streaming/redpanda/versions.tf new file mode 100644 index 000000000..3de7838a6 --- /dev/null +++ b/streaming/redpanda/versions.tf @@ -0,0 +1,23 @@ +terraform { + required_version = ">= 1.0.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.72" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.10" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.4.1" + } + random = { + source = "hashicorp/random" + version = ">= 3.5.0" + } + } + +} diff --git a/streaming/redpanda/vpc.tf b/streaming/redpanda/vpc.tf new file mode 100644 index 000000000..6f680191a --- /dev/null +++ b/streaming/redpanda/vpc.tf @@ -0,0 +1,31 @@ +################################################################################ +# VPC +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = ">= 4.0.0" + + name = local.name + cidr = local.vpc_cidr + azs = local.azs + public_subnets = var.public_subnets + private_subnets = var.private_subnets + + enable_nat_gateway = true + single_nat_gateway = true + + public_subnet_tags = { + #Tags for External ELB + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + # Tags for Internal ELB + "kubernetes.io/role/internal-elb" = 1 + # Tags subnets for Karpenter auto-discovery + "karpenter.sh/discovery" = local.name + } + + tags = local.tags +} \ No newline at end of file From cbc3735b95d0b61744e8924abc15d2d5fcd3955c Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Wed, 20 Mar 2024 12:41:36 -0600 Subject: [PATCH 02/10] Fix spelling error --- .../redpanda/lambdaintegration/README.md | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/streaming/redpanda/lambdaintegration/README.md b/streaming/redpanda/lambdaintegration/README.md index 5d06e2122..629ee4b51 100644 --- a/streaming/redpanda/lambdaintegration/README.md +++ b/streaming/redpanda/lambdaintegration/README.md @@ -12,7 +12,7 @@ This guide shows how to deploy a basic Redpanda cluster in Amazon EKS using the The integration consists of an EKS cluster running the Redpanda deployment, as well as a Lambda function using the Apache Kafka trigger configured to receive data streaming events from Redpanda. -SASL SCRAM 256 is used for secure authentication between Lambda and Redpanda, and TLS is used for encryption in transit. AWS Secrets Manager securely stores the credentials and the self signed certificate used for TLS communications. +SASL SCRAM 256 is used for secure authentication between Lambda and Redpanda, and TLS is used for encryption in transit. AWS Secrets Manager securely stores the credentials and the self signed certificate used for TLS communications. Finally, we use Amazon Route 53 for private DNS in our EKS VPC to resolve our cluster node names. @@ -20,7 +20,7 @@ We will walk through the steps to set up and deploy the EKS cluster and Redpanda ![arch-final.png](architecture-lambda-redpanda.png) - **** + **** ## Setting up a Working Environment We will use an AWS Cloud9 instance as the working environment in the steps provided in this guide, however the included steps can also be used with Linux and MacOS. @@ -54,12 +54,12 @@ If you do not already have a Redpanda deployment in AWS, you can find instructio [Setting up a Redpanda Cluster in EKS](Redpandasetup.md) ## Collecting Information Before Using SAM - + We need to collect a few pieces of information before deploying our template. This includes the following: * The AWS region in which Redpanda is deployed. For example, 'us-east-1' is used in our examples. * The VPC ID in which Redpanda is deployed. -* The two public subnet IDs for your Redpanda cluster. +* The two public subnet IDs for your Redpanda cluster. * The domain name used by your Redpanda cluster. For example, 'customredpandadomain.local'. * The internal IP addresses for your three nodes * The username that Lambda will use to authenticate with Redpanda @@ -79,7 +79,7 @@ During the deployment, you will be prompted to enter the information that we col * Private Route 53 Hosted Zone with A name records for the Redpanda nodes. -* A Lambda function to receive the published messages from our Redpanda topic, along with a role granting the Lambda function the necessary permissions. +* A Lambda function to receive the published messages from our Redpanda topic, along with a role granting the Lambda function the necessary permissions. * VPC Endpoints for Lambda, STS, and Secretsmanager, to enable secure communications to these services from our EKS cluster. * A Secrets Manager secret with the credentials needed for the Lambda function to receive events from Redpanda. @@ -95,7 +95,7 @@ During the guided deployment, you will be prompted to enter the parameters we ha Setting default arguments for 'sam deploy' ========================================= Stack Name [sam-app]: pandastack -AWS Region [us-east-1]: +AWS Region [us-east-1]: Parameter Prefix []: lambdapanda Parameter VPC []: vpc-02bd59e133e36991d Parameter PublicSubnetIDs []: subnet-01fa06aac679a7648,subnet-0d7fed3e19322e837 @@ -109,19 +109,19 @@ Parameter TLSCertificateARN []: arn:aws:secretsmanager:us-east-1:808511450715:se #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [y/N]: y #SAM needs permission to be able to create roles to connect to the resources in your template -Allow SAM CLI IAM role creation [Y/n]: +Allow SAM CLI IAM role creation [Y/n]: #Preserves the state of previously provisioned resources when an operation fails -Disable rollback [y/N]: -Save arguments to configuration file [Y/n]: -SAM configuration file [samconfig.toml]: -SAM configuration environment [default]: +Disable rollback [y/N]: +Save arguments to configuration file [Y/n]: +SAM configuration file [samconfig.toml]: +SAM configuration environment [default]: ``` After specifying the required parameter values, accept the defaults on the remaining questions and proceed to deploy the changeset. ## Setting up the Trigger -Now that our template is deployed successfully, we are ready to configure the trigger that will call the Lambda function when messages are published to our Redpanda topic. +Now that our template is deployed successfully, we are ready to configure the trigger that will call the Lambda function when messages are published to our Redpanda topic. Steps: @@ -129,7 +129,7 @@ Steps: 2. Click on the function name created by our SAM template, it will have “Redpanda-Event-Processor” in the name. 3. Click on “Add trigger” found under the Function overview. 4. Choose “Apache Kafka” from the list of available triggers. -5. Click on “Add” under “Boostrap servers”. Here we will specify “redpanda-0.customredpandadomain.local:31092”, as this is the hostname and port for external Kafka API access to our Redpanda cluster. The domain in your hostname may vary depending on the domain you used in earlier steps. +5. Click on “Add” under “Bootstrap servers”. Here we will specify “redpanda-0.customredpandadomain.local:31092”, as this is the hostname and port for external Kafka API access to our Redpanda cluster. The domain in your hostname may vary depending on the domain you used in earlier steps. 6. Repeat the previous step and twice and specify "redpanda-1.customredpandadomain.local:31092" and "redpanda-2.customredpandadomain.local:31092" as the bootstrap servers. This may be an optional step depending on the number of nodes configured in your deployment. 7. Specify the “Topic name” that you wish to receive events for. 8. Under VPC, choose the VPC that contains the Redpanda deployment. @@ -174,5 +174,3 @@ https://github.com/redpanda-data/redpanda * * * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. - - From 5c9baf595d00cef2f9e57f18b14f4b00561166d5 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Thu, 28 Mar 2024 12:45:57 -0600 Subject: [PATCH 03/10] Update README.md --- streaming/redpanda/README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/streaming/redpanda/README.md b/streaming/redpanda/README.md index a2ee24e8c..0e4b7819d 100644 --- a/streaming/redpanda/README.md +++ b/streaming/redpanda/README.md @@ -6,7 +6,7 @@ This guide also has the following prerequisites for setting up and deploying Red * AWS-CLI * Kubectl * Helm -* jq +* jq **** @@ -16,14 +16,14 @@ Terraform is an infrastructure as code tool that enables you to safely and predi https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli -**** +**** ## AWS-CLI **** -AWS-CLI is a command line tool for managing AWS resources, install the latest version. +AWS-CLI is a command line tool for managing AWS resources, install the latest version. https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html -**** +**** ## Kubectl @@ -31,7 +31,7 @@ Kubectl is a command line tool that is used to communicate with the Kubernetes A https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html -**** +**** ## Helm @@ -39,7 +39,7 @@ Helm is used as the Kubernetes package manager for deploying Redpanda. Install t https://helm.sh/docs/intro/install/ -**** +**** Provider Versions Tested | Provider | Version | @@ -55,8 +55,8 @@ The Redpanda deployment documentation was used extensively in the creation of th https://docs.redpanda.com/current/deploy/deployment-option/self-hosted/kubernetes/eks-guide/ In setting up and configuring our Redpanda cluster in AWS, we are using an Amazon EKS cluster as described in the guide linked above. - -We will use Terraform to deploy our AWS Resources (VPC, EKS, EKS add-ons) and our Redpanda Cluster, clone the files from Git. + +We will use Terraform to deploy our AWS Resources (VPC, EKS, EKS add-ons) and our Redpanda Cluster, clone the files from Git. To stand up the Amazon EKS Cluster and Infrastrucuture, run Terraform init, plan and apply. @@ -86,7 +86,7 @@ In order to allow our Lambda function to communicate with the cluster, we will n * Get our superuser password from AWS Secrets Manager and Setup Environment Variables -We will dive deeper on these steps in the following sections. +We will dive deeper on these steps in the following sections. ## 1/ Export the Certificate and Store into AWS Secrets Manager @@ -118,7 +118,7 @@ REGUSER="redpanda-twitch-account" REGPASS="changethispassword" ``` - + ## Creating a User Now that we have set up and deployed the Redpanda cluster, we will need to create a user. To create the user, determine a username and password you would like to use, and use the following command to create the user: @@ -142,7 +142,7 @@ Save the user name and password in a separate text file for later when SAM is us Next, we will use the superuser to grant the newly created user permission to execute all operations for a topic called twitch-chat. Feel free to use the topic name of your choice: ``` -kubectl exec --namespace repanda -c redpanda redpanda-0 -- \ +kubectl exec --namespace redpanda -c redpanda redpanda-0 -- \ rpk acl create --allow-principal User:$REGUSER \ --operation all \ --topic twitch-chat \ @@ -157,7 +157,7 @@ User:redpanda-twitch-account * TOPIC twitch-chat LITERAL ``` In the following steps, we are going to use the newly created user account to create the topic, produce messages to the topic, and consumer messages to the topic. - + First, we will create an alias to simplify the usage of the rpk commands that will be used to work with the Redpanda deployment. Use the following command to configure the alias: ``` @@ -192,7 +192,7 @@ internal-rpk topic produce twitch-chat ``` Type in some text and press enter to publish the message. After publishing several messages, use ctrl+C to end the publishing command. - + The output should look something like the following: ``` @@ -246,7 +246,7 @@ Once you are able to access the Redpanda Console, you can view information about ## Expand the Permissions for the Created Redpanda Account -Now that the cluster is configured, we need to expand the permissions of the account that we created so that this account can be used to configure the AWS Lambda trigger. The account needs permissions such as creating new consumer groups. +Now that the cluster is configured, we need to expand the permissions of the account that we created so that this account can be used to configure the AWS Lambda trigger. The account needs permissions such as creating new consumer groups. Steps: @@ -258,7 +258,7 @@ Steps: ## Collecting Information Before Using SAM - + We need to collect a few pieces of information before deploying our template. * The VPC ID of the VPC created by eksctl. From 63374ac9c0a53b278e88cad9dd7323291ae838b3 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Tue, 9 Apr 2024 10:31:13 -0600 Subject: [PATCH 04/10] PR Review Fixes --- streaming/redpanda/README.md | 8 +- streaming/redpanda/addons.tf | 126 +++++---- .../kube-prometheus-amp-enable.yaml | 0 .../kube-prometheus.yaml | 0 .../redpanda-values.yaml} | 2 +- .../redpanda/lambdaintegration/README.md | 176 ------------ .../architecture-lambda-redpanda.png | Bin 55617 -> 0 bytes .../redpanda/lambdaintegration/template.yaml | 258 ------------------ streaming/redpanda/main.tf | 143 ++++------ streaming/redpanda/providers.tf | 8 +- streaming/redpanda/redpanda.png | Bin 0 -> 55548 bytes streaming/redpanda/variables.tf | 50 ++-- streaming/redpanda/vpc.tf | 10 +- 13 files changed, 169 insertions(+), 612 deletions(-) rename streaming/redpanda/{templates => helm-values}/kube-prometheus-amp-enable.yaml (100%) rename streaming/redpanda/{templates => helm-values}/kube-prometheus.yaml (100%) rename streaming/redpanda/{templates/redpanda_values.yaml => helm-values/redpanda-values.yaml} (90%) delete mode 100644 streaming/redpanda/lambdaintegration/README.md delete mode 100644 streaming/redpanda/lambdaintegration/architecture-lambda-redpanda.png delete mode 100644 streaming/redpanda/lambdaintegration/template.yaml create mode 100644 streaming/redpanda/redpanda.png diff --git a/streaming/redpanda/README.md b/streaming/redpanda/README.md index 0e4b7819d..07b1d4678 100644 --- a/streaming/redpanda/README.md +++ b/streaming/redpanda/README.md @@ -1,3 +1,8 @@ +# Redpanda on Amazon EKS +**** +Redpanda is a simple, powerful, and cost-efficient streaming data platform that is compatible with Kafka APIs but much less complex, faster and more affordable. Get started in seconds with Redpanda Serverless! +**** +![redpanda.png](redpanda.png) ## Prerequisites This guide also has the following prerequisites for setting up and deploying Redpanda in EKS. @@ -293,9 +298,8 @@ kubectl get pod --namespace \ ## Conclusion -With the Redpanda cluster now configured in EKS, we are ready to return to setting up the Lambda integration with Redpanda. +With the Redpanda cluster now configured in EKS, you can now dive deeper by integrating with other AWS services, Creating Consumer and Producers. -[Lambda Integration with Redpanda](README.md) * * * * * * diff --git a/streaming/redpanda/addons.tf b/streaming/redpanda/addons.tf index 935999ea1..c76a1dbd4 100644 --- a/streaming/redpanda/addons.tf +++ b/streaming/redpanda/addons.tf @@ -53,13 +53,57 @@ resource "kubernetes_storage_class" "ebs_csi_encrypted_gp3_storage_class" { allow_volume_expansion = true volume_binding_mode = "WaitForFirstConsumer" parameters = { - fsType = "xfs" - type = "gp3" + fsType = "xfs" + type = "gp3" } depends_on = [kubernetes_annotations.gp2_default] } +#--------------------------------------- +# Redpanda Config +#--------------------------------------- + +resource "random_password" "redpanada_password" { + length = 16 + special = false +} +resource "aws_secretsmanager_secret" "redpanada_password" { + name = "redpanda_password-1234" + recovery_window_in_days = 0 +} +resource "aws_secretsmanager_secret_version" "redpanada_password" { + secret_id = aws_secretsmanager_secret.redpanada_password.id + secret_string = random_password.redpanada_password.result +} + +#--------------------------------------------------------------- +# Grafana Admin credentials resources +#--------------------------------------------------------------- +resource "random_string" "random_suffix" { + length = 10 + special = false + upper = false +} + +resource "random_password" "grafana" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +#tfsec:ignore:aws-ssm-secret-use-customer-key +resource "aws_secretsmanager_secret" "grafana" { + name = "grafana-${random_string.random_suffix.result}" + recovery_window_in_days = 0 # Set to zero for this example to force delete during Terraform destroy +} + +resource "aws_secretsmanager_secret_version" "grafana_password_version" { + secret_id = aws_secretsmanager_secret.grafana.id + secret_string = random_password.grafana.result +} + + #--------------------------------------------------------------- # EKS Blueprints Kubernetes Addons @@ -78,7 +122,7 @@ module "eks_blueprints_addons" { #--------------------------------------- eks_addons = { aws-ebs-csi-driver = { - most_recent = true + most_recent = true service_account_role_arn = module.ebs_csi_driver_irsa.iam_role_arn } coredns = { @@ -91,23 +135,23 @@ module "eks_blueprints_addons" { most_recent = true } } - enable_aws_load_balancer_controller = true - enable_cluster_autoscaler = true - enable_metrics_server = true - enable_aws_cloudwatch_metrics = true + enable_aws_load_balancer_controller = true + enable_cluster_autoscaler = true + enable_metrics_server = true + enable_aws_cloudwatch_metrics = true + enable_cert_manager = true - #--------------------------------------- # FluentBit Config for EKS Cluster #--------------------------------------- - enable_aws_for_fluentbit = true + enable_aws_for_fluentbit = true aws_for_fluentbit = { enable_containerinsights = true kubelet_monitoring = true set = [{ - name = "cloudWatchLogs.autoCreateGroup" - value = true + name = "cloudWatchLogs.autoCreateGroup" + value = true }, { name = "hostNetwork" @@ -133,14 +177,14 @@ module "eks_blueprints_addons" { enable_kube_prometheus_stack = true kube_prometheus_stack = { values = [ - var.enable_amazon_prometheus ? templatefile("${path.module}/templates/kube-prometheus-amp-enable.yaml", { + var.enable_amazon_prometheus ? templatefile("${path.module}/helm-values/kube-prometheus-amp-enable.yaml", { region = local.region amp_sa = local.amp_ingest_service_account amp_irsa = module.amp_ingest_irsa[0].iam_role_arn amp_remotewrite_url = "https://aps-workspaces.${local.region}.amazonaws.com/workspaces/${aws_prometheus_workspace.amp[0].id}/api/v1/remote_write" amp_url = "https://aps-workspaces.${local.region}.amazonaws.com/workspaces/${aws_prometheus_workspace.amp[0].id}" storage_class_type = kubernetes_storage_class.ebs_csi_encrypted_gp3_storage_class.id - }) : templatefile("${path.module}/templates/kube-prometheus.yaml", {}) + }) : templatefile("${path.module}/helm-values/kube-prometheus.yaml", {}) ] chart_version = "48.1.1" set_sensitive = [ @@ -153,39 +197,25 @@ module "eks_blueprints_addons" { tags = local.tags } - - ## Cert-Manager Config - resource "helm_release" "cert-manager" { - name = "cert-manager" - repository = "https://charts.jetstack.io" - chart = "cert-manager" - version = "1.14.2" - namespace = "cert-manager" - create_namespace = true - set { - name = "installCRDs" - value = true - } - depends_on = [ module.eks_blueprints_addons ] - } - - ## Redpanda Helm Config +#--------------------------------------- +## Redpanda Helm Config +#--------------------------------------- resource "helm_release" "redpanda" { - name = "redpanda" - repository = "https://charts.redpanda.com" - chart = "redpanda" - version = "5.7.22" - namespace = "redpanda" - create_namespace = true - - values = [ - templatefile("${path.module}/templates/redpanda_values.yaml", { - redpanda_username = var.redpanda_username, - redpanda_password = data.aws_secretsmanager_secret_version.redpanada_password_version.secret_string, - redpanda_domain = var.redpanda_domain, - storage_class = "gp3" - }) - ] - #timeout = "3600" - depends_on = [ helm_release.cert-manager ] - } \ No newline at end of file + name = "redpanda" + repository = "https://charts.redpanda.com" + chart = "redpanda" + version = "5.7.22" + namespace = "redpanda" + create_namespace = true + + values = [ + templatefile("${path.module}/helm-values/redpanda-values.yaml", { + redpanda_username = var.redpanda_username, + redpanda_password = data.aws_secretsmanager_secret_version.redpanada_password_version.secret_string, + redpanda_domain = var.redpanda_domain, + storage_class = "gp3" + }) + ] + #timeout = "3600" + depends_on = [module.eks_blueprints_addons] +} diff --git a/streaming/redpanda/templates/kube-prometheus-amp-enable.yaml b/streaming/redpanda/helm-values/kube-prometheus-amp-enable.yaml similarity index 100% rename from streaming/redpanda/templates/kube-prometheus-amp-enable.yaml rename to streaming/redpanda/helm-values/kube-prometheus-amp-enable.yaml diff --git a/streaming/redpanda/templates/kube-prometheus.yaml b/streaming/redpanda/helm-values/kube-prometheus.yaml similarity index 100% rename from streaming/redpanda/templates/kube-prometheus.yaml rename to streaming/redpanda/helm-values/kube-prometheus.yaml diff --git a/streaming/redpanda/templates/redpanda_values.yaml b/streaming/redpanda/helm-values/redpanda-values.yaml similarity index 90% rename from streaming/redpanda/templates/redpanda_values.yaml rename to streaming/redpanda/helm-values/redpanda-values.yaml index cb3adfa98..00be421c5 100644 --- a/streaming/redpanda/templates/redpanda_values.yaml +++ b/streaming/redpanda/helm-values/redpanda-values.yaml @@ -16,4 +16,4 @@ storage: external: - domain: ${redpanda_domain} \ No newline at end of file + domain: ${redpanda_domain} diff --git a/streaming/redpanda/lambdaintegration/README.md b/streaming/redpanda/lambdaintegration/README.md deleted file mode 100644 index 629ee4b51..000000000 --- a/streaming/redpanda/lambdaintegration/README.md +++ /dev/null @@ -1,176 +0,0 @@ -# Redpanda Integration with AWS Lambda - -## Introduction - -Redpanda is a simple, powerful, and cost-efficient streaming data platform that is compatible with Kafka APIs, and is used by many enterprise organizations across industries. - -Customers have looked to us for guidance on using Redpanda as a Kafka alternative in AWS, and integrating it with various AWS services. - -This guide shows how to deploy a basic Redpanda cluster in Amazon EKS using the free Community Edition of Redpanda, and then how to integrate it with AWS Lambda using the existing Apache Kafka API trigger, which enables Lambda functions to easily be configured as consumers of the streaming data. - -## Solution Overview - -The integration consists of an EKS cluster running the Redpanda deployment, as well as a Lambda function using the Apache Kafka trigger configured to receive data streaming events from Redpanda. - -SASL SCRAM 256 is used for secure authentication between Lambda and Redpanda, and TLS is used for encryption in transit. AWS Secrets Manager securely stores the credentials and the self signed certificate used for TLS communications. - -Finally, we use Amazon Route 53 for private DNS in our EKS VPC to resolve our cluster node names. - -We will walk through the steps to set up and deploy the EKS cluster and Redpanda, and then use AWS SAM to deploy the remaining architecture. - -![arch-final.png](architecture-lambda-redpanda.png) - - **** -## Setting up a Working Environment - -We will use an AWS Cloud9 instance as the working environment in the steps provided in this guide, however the included steps can also be used with Linux and MacOS. - -In order to deploy a Cloud9 instance: - -1. Log in to the AWS Console and navigate to the Cloud9 service. -2. Click on “Create environment”. -3. Give the instance a name such as “RPLambdaIntergationWorkspace”. -4. Optionally specify a VPC and subnet for the instance to run in. -5. Accept the other defaults and click on “Create”. -6. The environment will take a few minutes to be created. Once it has succeeded, click on “Open” to access the environment. - -Once you have accessed your Cloud9 environment, we recommend clicking on the “+” next to the “Welcome” tab and choosing “New Terminal”. This terminal gives you a larger window than the terminal automatically launched at the bottom of the screen, and will be used to run the commands in the following steps. - -Depending on your account configuration, you may also need to export your AWS credentials in this terminal, such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION. “us-east-1” is used as the default region for these steps. - -## Prerequisites - -The following prerequisites must be installed and configured in your working environment. - -* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. -* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed -* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured -* [Git installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - -## Setting up the Redpanda Cluster - -If you do not already have a Redpanda deployment in AWS, you can find instructions on setting up and deploying Redpanda in EKS here: - -[Setting up a Redpanda Cluster in EKS](Redpandasetup.md) - -## Collecting Information Before Using SAM - -We need to collect a few pieces of information before deploying our template. This includes the following: - -* The AWS region in which Redpanda is deployed. For example, 'us-east-1' is used in our examples. -* The VPC ID in which Redpanda is deployed. -* The two public subnet IDs for your Redpanda cluster. -* The domain name used by your Redpanda cluster. For example, 'customredpandadomain.local'. -* The internal IP addresses for your three nodes -* The username that Lambda will use to authenticate with Redpanda -* The password that Lambda will use to authenticate with Redpanda -* The ARN of the secrets manager secret that contains the certificate for TLS communications with Redpanda. - -Make sure that you have this information handy before proceeding. - -## AWS SAM - -The AWS SAM CLI is a serverless tool for building and testing Lambda applications. It uses Docker to locally test your functions in an Amazon Linux environment that resembles the Lambda execution environment. It can also emulate your application's build environment and API. -To use the AWS SAM CLI, you need the following tools. - -* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) - -During the deployment, you will be prompted to enter the information that we collected during the previous steps. We will use the SAM template to create the following: - - -* Private Route 53 Hosted Zone with A name records for the Redpanda nodes. -* A Lambda function to receive the published messages from our Redpanda topic, along with a role granting the Lambda function the necessary permissions. -* VPC Endpoints for Lambda, STS, and Secretsmanager, to enable secure communications to these services from our EKS cluster. -* A Secrets Manager secret with the credentials needed for the Lambda function to receive events from Redpanda. - -Make sure that you are in the working directory with the template.yaml file cloned from the repository, and then run the following command: - -``` -sam deploy -t template.yaml --guided --capabilities CAPABILITY_NAMED_IAM -``` - -During the guided deployment, you will be prompted to enter the parameters we have collected while setting up our Redpanda deployment. You will also need to specify a parameter prefix, which is a name that will be prepended on your Lambda function. Your input should look similar to the following: - -``` -Setting default arguments for 'sam deploy' -========================================= -Stack Name [sam-app]: pandastack -AWS Region [us-east-1]: -Parameter Prefix []: lambdapanda -Parameter VPC []: vpc-02bd59e133e36991d -Parameter PublicSubnetIDs []: subnet-01fa06aac679a7648,subnet-0d7fed3e19322e837 -Parameter DomainName []: customredpandadomain.local -Parameter InternalIPnode0 []: 192.168.49.207 -Parameter InternalIPnode1 []: 192.168.12.190 -Parameter InternalIPnode2 []: 192.168.48.119 -Parameter UserName []: redpanda-twitch-account -Parameter Password []: changethispassword -Parameter TLSCertificateARN []: arn:aws:secretsmanager:us-east-1:808511450715:secret:redpandarootcert-xeFNgp -#Shows you resources changes to be deployed and require a 'Y' to initiate deploy -Confirm changes before deploy [y/N]: y -#SAM needs permission to be able to create roles to connect to the resources in your template -Allow SAM CLI IAM role creation [Y/n]: -#Preserves the state of previously provisioned resources when an operation fails -Disable rollback [y/N]: -Save arguments to configuration file [Y/n]: -SAM configuration file [samconfig.toml]: -SAM configuration environment [default]: -``` - -After specifying the required parameter values, accept the defaults on the remaining questions and proceed to deploy the changeset. - -## Setting up the Trigger - -Now that our template is deployed successfully, we are ready to configure the trigger that will call the Lambda function when messages are published to our Redpanda topic. - -Steps: - -1. Navigate to Lambda in the AWS Console. -2. Click on the function name created by our SAM template, it will have “Redpanda-Event-Processor” in the name. -3. Click on “Add trigger” found under the Function overview. -4. Choose “Apache Kafka” from the list of available triggers. -5. Click on “Add” under “Bootstrap servers”. Here we will specify “redpanda-0.customredpandadomain.local:31092”, as this is the hostname and port for external Kafka API access to our Redpanda cluster. The domain in your hostname may vary depending on the domain you used in earlier steps. -6. Repeat the previous step and twice and specify "redpanda-1.customredpandadomain.local:31092" and "redpanda-2.customredpandadomain.local:31092" as the bootstrap servers. This may be an optional step depending on the number of nodes configured in your deployment. -7. Specify the “Topic name” that you wish to receive events for. -8. Under VPC, choose the VPC that contains the Redpanda deployment. -9. Under VPC subnets, choose the public subnets from the VPC that contains the Redpanda deployment. -10. Under VPC security groups, choose the default VPC security group (or a suitable security group that allows incoming traffic from your VPC to your nodes on port 31092). -11. Under Authentication, choose “SASL_SCRAM_256_AUTH”. -12. Under Secrets Manager key, choose the secret name “pandacredentials”. -13. Under Encryption, choose the secret name used to store the certificate exported from the Redpanda cluster. -14. Click on “Add”. - -The trigger will take some time to be created. Click on the refresh arrow to refresh the status. Once you see a status of “Enabled”, you are ready to test receiving an event. - -## Testing - -To test receiving an event, simply publish any data to the topic that the trigger was configured for. If you used the instructions in this guide for setting up the Redpanda deployment, return to your Cloud9 console or local console that was used for creating the cluster. Re-run the following command that was used to produce a message to our topic: - -``` -internal-rpk topic produce twitch-chat -``` - -Enter some text, press enter, and repeat this several times to produce a few messages to your topic. Now return to the browser tab in which you configured your Lambda function, and click on “Monitor”. Then click on “View Cloudwatch logs”, and click on the most recent log stream under “Log streams”. You should see events representing your Lambda function being called, and the debug output containing the contents of the events. - -## Cleaning Up - -To clean up the resources created during this guide, take the following steps: - -1. Navigate to the Lambda service in the AWS console, click on “Functions”. Next, click on the name of our Lambda function and then click on the “Apache Kafka” trigger. Tick the box next to the trigger and then click on “Delete”. -2. Return to the terminal you have used during the deployment and issue the command “sam delete”. -3. If you used eksctl to create an EKS cluster using this guide, navigate to the AWS CloudFormation console. Delete the stacks with the name “eksctl-” that were automatically created when using eksctl to create the cluster. -4. If you used Cloud9 as the working environment, you can also delete the Cloud9 stack that was built when creating the Cloud9 environment. Alternatively, navigate to the Cloud9 service in the AWS console and delete the environment from there. - -## Conclusion - -In this guide, you have learned how to deploy and configure a Redpanda cluster in AWS EKS, and to enable integration with Lambda event triggering via the Kafka API interface. This allows you to easily use Lambda to consume data streams from your topics in Redpanda. - -For more serverless learning resources, visit [Serverless Land](https://serverlessland.com/). - -Find the Community Edition of Redpanda on Github here: -https://github.com/redpanda-data/redpanda - -* * * -* * * - -Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/streaming/redpanda/lambdaintegration/architecture-lambda-redpanda.png b/streaming/redpanda/lambdaintegration/architecture-lambda-redpanda.png deleted file mode 100644 index 1fa59e3146636d14ae5180dc54b1b249c0d634f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55617 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 b80SQ(y++o}dIobi;73_r167K&eDQw(3yvP$ diff --git a/streaming/redpanda/lambdaintegration/template.yaml b/streaming/redpanda/lambdaintegration/template.yaml deleted file mode 100644 index 8699fce63..000000000 --- a/streaming/redpanda/lambdaintegration/template.yaml +++ /dev/null @@ -1,258 +0,0 @@ -Transform: AWS::Serverless-2016-10-31 -AWSTemplateFormatVersion: 2010-09-09 -Description: "This template is for deploying resources to integrate a Lambda function with a Redpanda deployment in EKS" -Parameters: - Prefix: - Description: "Prefix to be added to all the resources name for tracking purposes" - Type: String - MinLength: 2 - MaxLength: 20 - VPC: - Description: "Enter the VPC ID of your EKS VPC" - Type: AWS::EC2::VPC::Id - PublicSubnetIDs: - Description: "Enter the two Public subnets for the EKS VPC. For example: subnet-0123abc,subnet-0456def" - Type: "List" - #SecurityGroupID: - # Description: "Enter the default security group ID for the EKS VPC" - # Type: AWS::EC2::SecurityGroup::Id - DomainName: - Description: "Full Domain Name for the Redpanda Cluster" - Type: String - MinLength: 15 - MaxLength: 90 - InternalIPnode0: - Description: "The internal ip address of node 0 from the cluster" - Type: String - MinLength: 7 - MaxLength: 15 - InternalIPnode1: - Description: "The internal ip address of node 1 from the cluster" - Type: String - MinLength: 7 - MaxLength: 15 - InternalIPnode2: - Description: "The internal ip address of node 2 from the cluster" - Type: String - MinLength: 7 - MaxLength: 15 - UserName: - Description: "Username used to access Redpanda" - Type: String - MinLength: 8 - MaxLength: 30 - Password: - Description: "Password used to access Redpanda" - Type: String - MinLength: 8 - MaxLength: 30 - #TLSCertificateARN: - # Description: "ARN to Secrets Manager stored certificate" - # Type: String - -Resources: -# Lambda VPC Endpoint - VPCEndpointLambda: - Type: AWS::EC2::VPCEndpoint - DependsOn: - - DNS - # - HostedZoneRecordSet - Properties: - PrivateDnsEnabled: true - ServiceName: !Sub com.amazonaws.${AWS::Region}.lambda - SubnetIds: - - !Select [ 0, !Ref PublicSubnetIDs ] - - !Select [ 1, !Ref PublicSubnetIDs ] - VpcId: !Ref VPC - VpcEndpointType: Interface - -# STS VPC Endpoint - VPCEndpointSTS: - Type: AWS::EC2::VPCEndpoint - DependsOn: - - DNS - Properties: - PrivateDnsEnabled: true - ServiceName: !Sub com.amazonaws.${AWS::Region}.sts - SubnetIds: - - !Select [ 0, !Ref PublicSubnetIDs ] - - !Select [ 1, !Ref PublicSubnetIDs ] - VpcId: !Ref VPC - VpcEndpointType: Interface - -# Secrets Manager VPC Endpoint - VPCEndpointSecrets: - Type: AWS::EC2::VPCEndpoint - DependsOn: - - DNS - Properties: - PrivateDnsEnabled: true - ServiceName: !Sub com.amazonaws.${AWS::Region}.secretsmanager - SubnetIds: - - !Select [ 0, !Ref PublicSubnetIDs ] - - !Select [ 1, !Ref PublicSubnetIDs ] - VpcId: !Ref VPC - VpcEndpointType: Interface - -# Define the role for our Lambda function - LambdaRole: - Type: 'AWS::IAM::Role' - Properties: - RoleName: !Select [0, [!Join ['-', [!Ref Prefix, 'custom-lambda-function-role']]]] - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - Action: - - 'sts:AssumeRole' - Policies: - - PolicyName: !Sub - - '${Prefix}-LambdaRedpandaPolicy' - - Prefix: !Ref Prefix - PolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Action: - - "ec2:DescribeNetworkInterfaces" - - "ec2:CreateNetworkInterface" - - "ec2:DeleteNetworkInterface" - - "ec2:AssignPrivateIpAddresses" - - "ec2:UnassignPrivateIpAddresses" - - "ec2:DescribeSecurityGroups" - - "ec2:DescribeSubnets" - - "ec2:DescribeVpcs" - - "secretsmanager:GetSecretValue" - - "kms:DescribeKey" - - "kms:ListAliases" - - "kms:ListKeys" - Resource: '*' - - PolicyName: BasicExecutionRole - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: logs:CreateLogGroup - Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*' - - Effect: Allow - Action: - - logs:CreateLogStream - - logs:PutLogEvents - Resource: !Sub - - 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${functionName}:*' - - functionName: !Select [0, [!Join ['-', [!Ref Prefix, Redpanda-Event-Processor]]]] - - RedpandaEventProcessor: - Type: AWS::Serverless::Function - DependsOn: - - DNS - - HostedZoneRecordSet0 - - HostedZoneRecordSet1 - - HostedZoneRecordSet2 - - UserNamePasswordSecret - - VPCEndpointLambda - - VPCEndpointSTS - - VPCEndpointSecrets - Properties: - FunctionName: !Select [0, [!Join ['-', [!Ref Prefix, Redpanda-Event-Processor]]]] - InlineCode: | - import json - def lambda_handler(event, context): - print('## EVENT') - print(event) - return { - 'statusCode': 200, - 'body': json.dumps('Event received from Redpanda!') - } - Timeout: 60 - Handler: index.lambda_handler - Runtime: python3.11 - Role: !GetAtt LambdaRole.Arn - #Events: - # SelfManagedKafkaEvent: - # Type: SelfManagedKafka - # Properties: - # Enabled: true - # KafkaBootstrapServers: - # - !Join [ '', ['redpanda-0.',!Ref DomainName,':31092'] ] - # - !Join [ '', ['redpanda-1.',!Ref DomainName,':31092'] ] - # - !Join [ '', ['redpanda-2.',!Ref DomainName,':31092'] ] - # SourceAccessConfigurations: - # - Type: SASL_SCRAM_256_AUTH - # URI: !Ref UserNamePasswordSecret - # - Type: SERVER_ROOT_CA_CERTIFICATE - # URI: !Ref TLSCertificateARN - # - Type: VPC_SUBNET - # URI: '' - # - Type: VPC_SECURITY_GROUP - # URI: !Join [ '', ['security_group:',!Ref SecurityGroupID] ] - # StartingPosition: LATEST - # Topics: - # - 'twitch-chat' - -# Create the Private Hosted Zone - DNS: - Type: AWS::Route53::HostedZone - Properties: - HostedZoneConfig: - Comment: 'Hosted zone for Redpanda nodes' - Name: !Ref DomainName - VPCs: - - VPCId: !Ref VPC - VPCRegion: !Ref 'AWS::Region' -# Create the A record for the host redpanda-0 - HostedZoneRecordSet0: - Type: AWS::Route53::RecordSet - Properties: - HostedZoneId: !Ref DNS - Name: !Select [0, [!Join ['', [ 'redpanda-0.',!Ref DomainName]]]] - Type: "A" - ResourceRecords: - - !Ref InternalIPnode0 - TTL: '300' -# Create the A record for the host redpanda-1 - HostedZoneRecordSet1: - Type: AWS::Route53::RecordSet - Properties: - HostedZoneId: !Ref DNS - Name: !Select [0, [!Join ['', [ 'redpanda-1.',!Ref DomainName]]]] - Type: "A" - ResourceRecords: - - !Ref InternalIPnode1 - TTL: '300' -# Create the A record for the host redpanda-2 - HostedZoneRecordSet2: - Type: AWS::Route53::RecordSet - Properties: - HostedZoneId: !Ref DNS - Name: !Select [0, [!Join ['', [ 'redpanda-2.',!Ref DomainName]]]] - Type: "A" - ResourceRecords: - - !Ref InternalIPnode2 - TTL: '300' - -# Secrets Manager Secrets - UserNamePasswordSecret: - Type: 'AWS::SecretsManager::Secret' - Properties: - Name: pandacredentials - Description: "Username and password key pairs for Redpanda access" - SecretString: !Join [ '', ['{"username":"',!Ref UserName,'","password":"',!Ref Password,'"}'] ] - -Outputs: - VPCEndpointLambda: - Value: !Ref VPCEndpointLambda - VPCEndpointSTS: - Value: !Ref VPCEndpointSTS - VPCEndpointSecrets: - Value: !Ref VPCEndpointSecrets - RedpandaEventProcessor: - Value: !Ref RedpandaEventProcessor - HostedZoneId: - Description: 'The ID of the hosted zone.' - Value: !Ref DNS - Export: - Name: !Sub '${AWS::StackName}-HostedZoneId' diff --git a/streaming/redpanda/main.tf b/streaming/redpanda/main.tf index 2e3851238..1a9a1d5b5 100644 --- a/streaming/redpanda/main.tf +++ b/streaming/redpanda/main.tf @@ -1,5 +1,5 @@ ################################################################################ -# Data +# Data ################################################################################ data "aws_availability_zones" "available" {} @@ -7,95 +7,52 @@ data "aws_caller_identity" "current" {} data "aws_partition" "current" {} data "aws_secretsmanager_secret_version" "redpanada_password_version" { - secret_id = aws_secretsmanager_secret.redpanada_password.id - depends_on = [aws_secretsmanager_secret_version.redpanada_password] + secret_id = aws_secretsmanager_secret.redpanada_password.id + depends_on = [aws_secretsmanager_secret_version.redpanada_password] } data "aws_secretsmanager_secret_version" "grafana_password_version" { - secret_id = aws_secretsmanager_secret.redpanada_password.id - depends_on = [aws_secretsmanager_secret_version.grafana_password_version] + secret_id = aws_secretsmanager_secret.redpanada_password.id + depends_on = [aws_secretsmanager_secret_version.grafana_password_version] } + ################################################################################ # Local Variables ################################################################################ locals { - name = var.name - region = var.region - account_id = data.aws_caller_identity.current.account_id - partition = data.aws_partition.current.partition - - vpc_cidr = var.vpc_cidr - azs = slice(data.aws_availability_zones.available.names, 0 ,2) - + name = var.name + region = var.region + account_id = data.aws_caller_identity.current.account_id + partition = data.aws_partition.current.partition - tags = { - - } - -} + vpc_cidr = var.vpc_cidr + azs = slice(data.aws_availability_zones.available.names, 0, 2) -#--------------------------------------- -# Redpanda Config -#--------------------------------------- - -resource "random_password" "redpanada_password" { - length = 16 - special = false -} -resource "aws_secretsmanager_secret" "redpanada_password" { - name = "redpanda_password-1234" - recovery_window_in_days = 0 -} -resource "aws_secretsmanager_secret_version" "redpanada_password" { - secret_id = aws_secretsmanager_secret.redpanada_password.id - secret_string = random_password.redpanada_password.result -} -#--------------------------------------------------------------- -# Grafana Admin credentials resources -#--------------------------------------------------------------- -resource "random_string" "random_suffix" { - length = 10 - special = false - upper = false -} - -resource "random_password" "grafana" { - length = 16 - special = true - override_special = "!#$%&*()-_=+[]{}<>:?" -} + tags = { -#tfsec:ignore:aws-ssm-secret-use-customer-key -resource "aws_secretsmanager_secret" "grafana" { - name = "grafana-${random_string.random_suffix.result}" - recovery_window_in_days = 0 # Set to zero for this example to force delete during Terraform destroy -} + } -resource "aws_secretsmanager_secret_version" "grafana_password_version" { - secret_id = aws_secretsmanager_secret.grafana.id - secret_string = random_password.grafana.result } - ################################################################################ # Cluster and Managed Node Group ################################################################################ module "eks" { - source ="terraform-aws-modules/eks/aws" - version = "~>19.15" + source = "terraform-aws-modules/eks/aws" + version = "~>19.15" - cluster_name = local.name - cluster_version = var.eks_cluster_version + cluster_name = local.name + cluster_version = var.eks_cluster_version - vpc_id = module.vpc.vpc_id - subnet_ids = module.vpc.private_subnets - cluster_endpoint_public_access = true - cluster_endpoint_private_access = true + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + cluster_endpoint_public_access = true + cluster_endpoint_private_access = true - node_security_group_additional_rules = { + node_security_group_additional_rules = { ingress_self_all = { description = "Node to node all ports/protocols" protocol = "-1" @@ -114,34 +71,34 @@ module "eks" { ipv6_cidr_blocks = ["::/0"] } } -#--------------------------------------------------------------- -# Managed Node Group - Core Services -#--------------------------------------------------------------- - - eks_managed_node_groups = { - core_node_group = { - name = "core-mng-01" - description = "Core EKS managed node group" - instance_types = ["m5.large"] - min_size = 3 - max_size = 6 - desired_size = 3 - -#--------------------------------------------------------------- -# Managed Node Group - Redpanda -#--------------------------------------------------------------- - - } - redpanda_node_group = { - name = "redpanda-mng-01" - description = "Redpanda EKS Managed Node Group" - instance_types = ["c5d.large"] - min_size = 3 - max_size = 6 - desired_size = 3 - - } + #--------------------------------------------------------------- + # Managed Node Group - Core Services + #--------------------------------------------------------------- + + eks_managed_node_groups = { + core_node_group = { + name = "core-mng-01" + description = "Core EKS managed node group" + instance_types = ["m5.large"] + min_size = 3 + max_size = 6 + desired_size = 3 + + #--------------------------------------------------------------- + # Managed Node Group - Redpanda + #--------------------------------------------------------------- } + redpanda_node_group = { + name = "redpanda-mng-01" + description = "Redpanda EKS Managed Node Group" + instance_types = ["c5d.large"] + min_size = 3 + max_size = 6 + desired_size = 3 + + } + + } } diff --git a/streaming/redpanda/providers.tf b/streaming/redpanda/providers.tf index ec4ecd7a3..67f03a4d6 100644 --- a/streaming/redpanda/providers.tf +++ b/streaming/redpanda/providers.tf @@ -7,10 +7,10 @@ provider "kubernetes" { cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) #token = data.aws_eks_cluster_auth.this.token exec { - api_version = "client.authentication.k8s.io/v1beta1" - args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] - command = "aws" - } + api_version = "client.authentication.k8s.io/v1beta1" + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + command = "aws" + } } provider "helm" { diff --git a/streaming/redpanda/redpanda.png b/streaming/redpanda/redpanda.png new file mode 100644 index 0000000000000000000000000000000000000000..fb9ed501a3b6c7d25e69916c84d7b8705288bc66 GIT binary patch literal 55548 zcmZ5|1z1#F*ETbZ0*Z7CICMx3A>G|AAf*f-jUe5U(lLO5be9N7N=kR5gn)D--SD5` zdEf8ww5^HEJj_Q@l%M<^&LPvqsKG*D1bF;P%JDd-P@E7=Pj zz%M8uCkPygq$ z28i)L-vOhbgjk|L{_`Du;2rrd2KWK){?9vVCg}f#Yq^1T($Gv^N!J8NhpooVP4+35Xhey>uI9fT+GXtRkS_e{ZKzo|QpMK3YWb;x-|x8{9$Sx-%$ z==Z4oG=47EweJ$m>qDP?-w*Y#&jmujP?UeYM9??9SEwFC#8CeAA^zh8dF!2Q?R;-G(DMj?Ob_3-~Z=^D?keUbRdqyKYx zkz;-gOppD?m&Om^)5FEk(dXU;_Se zG>gAl;$it-dXB_m{ZQb_x0iQvUCfiHg&&!LZ5LQyb+R-0&*u4O;yA31tkwp{A05YL za)-!>L3JTZf+v~B#@9g>q+iJdb(Ka{j&WLjc}AVeIW>?gVSERkN;q0%LGYOwKykn| zk)m9iY-_oF~q)1BXk6cf2#3a03+G~51k*8 zvJ4-v5x719oI>}55`yhs=$ZSU3?)5BBv2~UD zh}I}Op!HtT{-hST{WgcgNKI;;g5noM-Pbp*@Jr-`}<+krKvPru%g9T z(HbgYcGm1>zYcnXQ|AfZBWEz%H7KW?8>o6siZ7buXy2@w+x;f|dqETtbs7qJuv|+! z$up#)BA}qznIMGsb>=0Zo9pjf1{t*(>;0nHECR>}sl~q8;aKl}=EV_9s&XuqJ_L2w(u<+2_=)pqw@Sg?LCzF}=}VtibilTvY{a9!V;6NW7R**(Kta zJ<0x7sirOO)h|$G*{fWu0105}1OHJ{+KHdi){Y(Si!SEaacp!l2M%aNYa z_wFR>z*H|I;5C>S5V2NK;Kp-{0KEIn0~tef{pn4Abg#I;FPNL+EjTnTOZMZ2NSqk3 zWua;c5X_=g#*_LNJOL?Yi336wLU0|$&E`eeA9)lkNw@hI$7D$C=!+0L#Z6QKf^om2Ay+a=~S z@}C-~5IX1UDtMjaU`>~+l_R-~=^8!Hd0_6V{haFYK^=giWmwTO5tHOe8&)nv zfY<~sVD-+mgq6ZMqG z8*Lui%WTx>-S6h86tL&qzilH3k$VoNg2IY5n`vte4NHtH;G~npzG$SAVk9{#AMrVQ zqLzoR4PKciSd6lirbr2uAhj+Az;IL%jDOU%7=rli@RLQWI`+peoixV1+xdHqsiclM zUO|WHl$5!<_c!*^R$0jb->(NSbus=Q@Lf#u6HGPJYPpJDa8&glBIt${`yPK@dVwBD z3o?xOIHqf^ZQec+H;|uP5JlD3tIj0}i0cU;E_J68PBo;s7)3x=`GhPy^{<+;dkRH( z5pTA5b!sJ<&;wEY&xgDjYff%bgnJEyGsyMvex#JOqCltv0CDxyMUyQqcCx@5C><;SsN&=e$cu6>WLKq99fJD560V7G+2 zr?5+HMv*0_&p%fViem@CTxC}8t17qdEA3-RH3PwQ+IMfWTiO7>L{%p=N_MX!!l3RV z8bo~qI;_}sj{yTJ4fc1Si<$5x=w(UPsT@d?f^7rB9aR8yue*pJH9UiiTDdt6=2e zA}1a=uZwvfhlEK3(n(UHStn#A)kZ}NYZec7Bn+PviV%k)Fg}rS7j%dOgu0z7vGSkV zGF(kfTG2D2`iIg2`$cX4{NV#+c+*J}<8%WUqfdceudXCqOZML20SVcN=+Y)ne$0rm zu?ht?>|e&@h1=6h)qh-mK_zEsZDbLVH9_q2m{i*sGnV)i0-s`8-F#VXrsGaDROv>B zJKRU*FZK4k1jWIFbjXiv%Uf_Ti#i$*bc3)Y<>rLngOTGYM2npQ=6116KKM9A|FtyIW*=3>pq(_ z_cdK*_csajY7lT6L*@YeS50UXc$lQ$e(b{ScwMMd<}+G_`c7jK%S9pK^=frJc?5F-tSxHy z%|c==pe6ON5}{e5a@H#5D%ZW8(zmT2ID-lNXRAQLVC<`AbNsM5dpk|NO2qd-3@pjm zwl*B(_Wt!& zo`$DX4HG;n3w2(x*EO&Hyw+;(Jq#;JCc5{nCXmBn`jzIJp9cdU61krKs@|LUf!R($ z#0P|=DE>XPt+3)mJ=1Xd9D%s%b^Z~2WW%Ij9_ni{veWs3^@qk;IRC6xVqi>HE`itsnrY^f2Dxyf0dz@t&A|Orf%2Q z%?ABD?+{Km3eDu?AAnVK$j4+fqL{05GG9zXjD>^R7sDSN-mgvqP@m$9kYT;O(INrr zv>w^!W6z$#VhxGc-|%CmYrn;|=n%jsbn8mZ(g?~2Oiq1^ zvJ-)bgPG~qJus8tIqdwFIi=X<_85W{3#VuP>MG;e0k#W(^_zD{SjYI$@P^GSgTELl zW;OKlPs)smY4sDU5^y+AXXPdP4HTRpK{WitCU%!7>fpsmlP+~no9 zhJCtWC1e#MLM0aI*ZlPqF?yZrUC%(naWv87RyKQGmS#e!izT%VBwp&`l=nVP5QJ4X zaQ%*rU)9ovHt(nAVdf~j)PQDAHsThrd)%1mx8iB{($=41=sf@uoVlNki}qB~ObDClnZa=6kJ**&Ey z*O;)9`Hk!&7W>}V0s57A$%JbTIR}y-i}YFHNWE8M0e>NPA?k{Mz8q}Dlg1gWA$v^* zD=tQ;p@W+-NSz0X_{73L_Qrk!CdQ8xke}E&4xl&+r)AdMf}dNt9#;&` zTF*SNT5NQTWqCEFX0s3S=k@o1;t+sO>+zYq7msqo!?6`SA3T-cxS-WrSIsT`o%)!_ zJ?&NEV1AvQYD|$GO}W?SKz>lPWb2lmfclF^;z2nkIKk~Wm|<`4{fmU3*q4YcixgpX zxK_xW#Q2^aCVA43`dXaj%Fgx1Z|;BwsKZal&Uj1qgltGmRv`JqWKKEnKdcRhryP#6 zTsLPg;W`CA(yKlv#Bs?Oxg7M1@9^Nf;@ygo*(OuyM{izVyG#E>A~ZA_fTS;aVngF( zPo)o@@nTFM{FG?{ELg+|UCom@z&;+eVX^MkcifoZUg2xK#y7y!&ajhhxMdv6WvFV!E^ zH)q5q1TWF{M6>W8_GBrbR9PfaXj%5kO2BG;p12c)!)ett}?+Mf2wz+#2tP_9mMQEtMb8umnvdaEkK$COY*B+HpM+E z(SWBdRAZc{$p=fqBdjE%8UC?a91{Or|3+&8K`i}Vm?wGt7vm|Iq`M`4k7z0mm1AL> z1bPUSF74=j#)1HWTx$Xb#=Bw!73qG(UFC(V=1!=$1m&8N&e0gaJkWb~41sX(@1=_~ z|Ld1s%CKQlRm8G!7>`F`!in7e06n8)VV@1y8Z2Enqn`Qg4+lg6>*OBjAn1IjyU?q( zgcRXeKP8L-BxEw`fiZ+LGCy^dakIzOzrixg&|YAduEHgOsI+$5fP*g9to++YzF>ri zr{c2Qk3x%4S4z}(o&^NUn}m#j1CVgU+#o=gA_KWArgZ`4R#K3Nm~LY-j+<>uZJM$0i8b%kdw#PREINNor|BSe2ic&p(ZVv)IjD5eqtl& z9W~7vW;)NNY=)a{pYXfb1Aev55KoQ#fzJwI9t|C@e^P|J7}tR#Z7wyCF{;rjaUeZ| z{|NPB!@D3Br!GsEKHVl>x+moKnG2w6cybgM)?)>U<2J`rE5lS(#qvxjs6coji=}Oj ze;lfe?w70a0827+MGYJ2NBhE1`U_KtmGVDZZ!td)i(p1G*}{kB=)w27s)uJ*rV>>`ldw6t^&K5X^fopFwecE#xc(2WbV1>+;M*T6;p8V~jP0Ly8uzzCIR1Xr~xf2Cz|a4EbGe)CjX-)%5d z5{k?4WGNbl*-jQsToHdHzJ7gaqhIf6I6s0#DIj6rbR<%?(3oH~G2q=g^!(WAt9w6| zz`HF>VJetOLFA%YrTZbR75Oy`yLO!z=?{EOF8W6;bF`oGK8MkrMs z^r7+2o{8c)0%54g${vSZ;k|PO=AFOlZfHz@u&Rgh4Cz-`g07JqF58Tu?N>wD31 z7n5v(Ie&9gUid0$$o+|7!~38p3Q_rxXmXm0_+)hX^&#Wj>g^Xy-8>~Pb4ak72U@OW znHGJ&9KooI*iX|}EHw{Te~srCG%ng(E_Wwb7rkr0gge1UwBy9?3F@w;ouig=O-@}};aqs7%v9izKdb%qk+ zz3zHaZw}(~$9!APUh02ytJiScyMB;2*m8ZdKr1@d%!g%VIdm-@mh*RLwk6j}n|XZ4 zJ-p{nk4{N8AG_iAx$2O%pZB&sQzlvRAp+W-%^GJSf6VzuH__ z75Dz!9%+7ZviUop#%`e#OXbh+z||Dl=rlDSED8~Y=k5jvnY7o7;lVn?;{>%tx8!yCMwX7;)^V%2dL@8;z%q7s(zN>Mo4d^gTg3W zi@#L^%&G{h-$_Pro;;satfOXeIO1!W7WMoCW=~xs{k%eJR_5#FWk4Aab?s-AWgwtk zVw9jnkKZeK9>M<@;fNu``-dJR_* z`>{e$US0vSZJ$QYlhkYbL+4Oaybf`a^NyS_8mKcJ>k4UjzA_n8ce=02JE6{u+iUDq zEw~YTrK83^B{osz(XWsz`|a#!S|QNyu!5Gug<@XUS0tKMY;-42RY>tgGz@hDus;T^{=hS%|+OlnQocW9JlbS>jGGwUDAb zw69qH!#?!YCJ72%lZPF(s<5?oznr?dB zbQ*rA0nV_VELCAmd7<4!?LPvjY;LX3MT+C?Xn|>%P?yS?bEfjE+VPN5tK;&P7FBKj z)5WdH?c;9Io3xo~>%4E4fAmn8&el0}b-y^1vRfA$7w}eFO|$IJto9d-f9QOU$lPxB z_E=l}*<3(>bAa74C>VW=s+pq$H{tZFRJo*0)bn0*ZWw}0ZPO=rU zgG6SX3~EIZ4_0?uSaxv>#Fif1UPfbY{s>Z3Fs*Eni0_IdK+BdNm$}X?XU*B%?7Z^5 zX1|;+eEt6Kh&rBZ`;RL0pI;WB$&+t;vG0};?>9z*`$}wZYF%z*8oXA?ntFCaX%;Ws z*IRs~dxfevLIN_X2@-{F+Vt>i zuBb_#*b#0Ko0B}E5`TD~&piaa0K$`kqg1PxUxW%;j7Z0xt3B%HrB1*Exi3aVM-N*! z?zcC+J)1}6_MQ^Dbh)OzWEI50KG{%6<+Y`$omJM;!0{KLq!B=&Ny>-$M*>f`N_givY?x?)6nhCtpZE`!_d3pUWD2M zE>UmLBrD+f#cc;HyTWHzqM;Q(q?Hq+PYG;}FLCKoI&fUzx{!Z>Uoe*N z{G}c)Q=~q(wM2nRMsLa3Cs&t57v_~zI=97J&m$KdqV`&!o8YmvtoH6g*;=;ab{8V4+({_Kyc$G7>AxUE7(Gu+x;`XU);(!!%pY>UFbFK; zenLr#RlUq5(EdKg_U-mtvYpS#3s{rrmv8Km z+x~vUpsS59TDCi3F`=(}7AkBK>6@=*e8=LTQI;d0`|fTf#~A3>%PBp&DH%YZfh<@1 z4iV>7dsN`|2r+hK8L*v<7 z<72s@fg>ya23OZXyn8`DYza|$%=rF$L^@#x0Fp~wl27h~M0N1ckws4fJY|hXDe{%3 z#Bf}a`s5BeCh#yYb&QY$H>q2dbQwJPqHIlsMc)=uZSWZ~{ahUU$;^r89YKesnmOqQ z4?1olYJdOb6ft;ln`buA+k0cwGn~q{bAg#h`nkk(h9uP!Bg)lZW`_=o@5|?;V zw9tS$^dmju!!;)|FnV$1oO2c*#)Ucl`FFG`4A&<-He^5nnYZ>4#RCwsoE9bdy|?Cd z@|v0%O>vzI_C0eU2K|OXdrT=;sT!zL$(AZVRK@UZ;Dd3xFR!>co=_D=pZTM<7IJ+| zWq?nUIBL#(2~-Hw~Yr#@1M^x;K;xlpG4qy*i=`+CEF8p33Wbmf|OJhwgGls@Pqk|j>_OR^WQ$Wcf`HV0J z2r1BMz(bD<61<7OHITQ%ia$0%bBa4LR-rgwAaM^H#k#D{-nxjIPU+&U=DuNgM*xow zyspCV7@#LeH6_piWEu2=LK2__(J8=o7QA}qku5y6u1>vE{zd*8Qgmp%-Xquq=3z zU3ZF|sQn_`+fY)2xs%sQo|E&OOVh@!5XS(_DrhC*hj#YC^{4)u3~{)jP{IS8ni5{P zp-4DhgOHMpQQr!edm_aFjK-#TEglCJE-tp-=rv@B>G|m&4jS^&Z#Gf>=iR~6zM1dJ z)zYZ_g*w+~+;KcZuM_xqCG~D`bkbxjXrjbOe6V&?eRE6pn@Z)1ihoC+UwLZIl=!YB zch5d;`4|u7M6XSv+=O4?zQLQo$u13re5EZ4%`|z4tX{Rzn=DkQjO)dSUgM#Yx(t12 z)u7b*p!{UlAE=K6{#oa!F?Gc&(k_F~9@Fq+?e#g~G@9aL8c=`+^fA?qxB)E>>%!0h z27=;ff!Pw(c~sdyL|8@6(E7(9S;|pKv2ON98UO*Ap(E}+!ct|Ge!x>-wNoEg*w{<9 zqBa?u#mxZ>0$vW*zaNATRGV7bZyt9vCqKz6g)eN@Q$nj47XzqQcDP@W5x&BXpwu|X zf&7=W%lycbDvZ&S3&!2iAFmm0m+FARrPu_1Pl9?{b}v=Fy0yZFu-HGU94ilX{~Hc3 z)mL!99l-}N zI8fQ`T%lr*?a3;pW3az`Ysi-@2X2fN%Y-1=V^HB+JY>RtjoTwq6K!KneD!>&pZFyH z7d1q9O}iE7yAhMZlk9@6)bSSdX)Qi1xr&oUTaOo z;Nvg^EW(1A;G-0eBGR=1tf!%YP+rtI-KN`-Of_w)Xjse#!PzYHU>FX1D1fnkU$X#H zLqR9V@bjM4@0)3ozGrg}xjQF{%aQeb6#qghoSzkFoz6S?{6@3Ed9>mq0PAT3%;AGF z!7g$e{IJ1c5nB=VUI;e}(vpgZEwP?L56w1!Fn$4&Hg`T}@5!XA>f_WP)f8b!nk4_- z%5j;AuNIw+ak)AIMLt}L3Y4fmXuui%d0n*#%gBQJo!@eQELBe5R=TAFp_>YB`Agc8OwM}R$Yx@k6N>ae75wgI+AiE_-%s;@b{U=7;=79W_u7vvpKb< zItX_@<`uuZpDAr_X%z#b=@to4izmluZzBO1lbh`p^*8JD@KC>LHq(nf#Px*Y`{j7o zk8T&SEwli!5$=<@Vqv_VOjs&%){Su?&ETi1KC0JtuOwm0(5qiQKx;;}9{ZjVmetOP z@;_QSstm0dHi7ZH&BAB{NKO9fcd}rA6C|nm-wLc3j>9Zhuh8-_O$mMDRZw`edmH`jGnf5VD=ke1T{=u4#24a^+}ws`?{)n-+7B) z0=#6iWRF&DMFWKbvg*3X>tA=}pt6Ra=qtik^K=P)Pauo3Eq7PxHg3+ za&lX3%ugL@Maaw#8fl_5lKVj{eO{U zvhQcbA+Bz(g!x`BGJd}Ui&D78g$iZVy=c)PN>5&CNMwkoI95b8z3Odtm&Vl(#6Ul1 z02;$YpCSMS^Ai&T^Qi2-vKn$8f90WOe}{W?s}qMSuanm{jWvg= z6)&nBFu|SFC?Q$8btV9-f&wOkIDUpFzxe2AXQau4@avpw?F1A^n}X#Lunh` zKuXkOqGr3`zNfXPh?#WHQ7%RUp3WB@gN+;m1oTER!-~VXt-}JX47&IJyAeEK=>)xM zwv->RlJ3bA3DSRcw22*{*HziT(|KxuVkjY#^Lc7x243@ra9NTmV8{m;jHx0-^5#4N z16&tZA78{vf&G{8T8&GV@B)O9mUT{LvQYhTNASftQ&ZX?tQZ4ZRGRZ7^}Ztt*t{?~ z=u7kJlKg7h-)|hr=kCjxjBq4sI2ND4UD||J6&+_wi(MEY{vUu?i-7in6H7&hG74b$d zOO48yop(kO_gczPJ39~q8NEM!F9bc76Nop~%PW370ocEfVuzaSv9|3nJWGpec)CdN zo7CEMVUxdy5|Q|QdpNECpI8J+^`w&z{$YXB^!P`TeQAQQ{0CEOfn2h`x6?5`J)`<5 z@96A)qpG0GEr9@qw5nJpIs`MJSbZ#Wl%=Y8Z=)|iE?F_(k}Zqn2i>~mx;hxt zeqXK@!+<{%6swth%u~|sywL|xdqBsK8ZSiTa*^Gg*r>@|IMV0jC-*@J>3jQb7ev}_ zu4cjT$jSB#g4DhQ<}u;339Tru0doPjzv$XaLR`;gZCqf7rBdPM%1!1BDro|eNo>aI zTW(uxhB4T=yYXI67>pISURX}VEG;h|rmj%fPd7+1aUH7sF|U&5Pvq5%iziHVT4x`r zcOowII4SPiUs<8G8mr6FNNjm_(fy~0W>>B`7ZnZr`EIS{@3_;Q-{lbn(nt9U$=Az8 zH14;TDHag{8c2RDx!)pg+0VnFD(FR0#Qq@%srA(J>gGQZe}G;l$@6)^V{!mXBQWZA zfd-o>DHUy202MU*s_dnrBCJ#Z!a%Ty7w-6*@q!O!5^GNSbvM>7Vulj!^;x<%x6hJR zUk^@5t4y`^&un5D5ydB4Kk3|2eD{a%GKmIL)gFpAE-a$m*%ZVlJ&nw_GZcKjyCdrG zX1)7o>hO8*AuO{E*kZOPTXym4k1H+q$HaSFkjya2D=u1K;G&$}C<-L;Tg`5nkd zE~+7!)jq-tvgom<#_KKG=;dYAZnf@H+s!9N^mL|oi|y=;Jal@bvvvCn<2=n%>(6e4 zhdGmB-n?ZCUxT{5ZpC{9F)O9I6W)ynd>ygJAmi03H)Iqg=Q{q_i#Qy3`hM5fnBgMo zFv)=Q1p(>CG0?8MlXuA_DT2<3Z1xHOOauTPysvlP)L+RY%&P1N`vG7HaZ4B}S#yEM z+75yzqgD%r3JY~uawJqy9O(riXcvPGxie=I8b0iI@2X*fBPCY>eF4V^QYS_>ULAMz zq>vjL@xui}+BX{0^KQ3{|U7$VlQSm&b$OMWiea?<&%LPq-(GH)opHsTKs=ZTNS2<&@f=~($+t;Tzj_#jrNbY=_&@>0%-i#6} z%*V8;j#XQ~K!|j9bf{TGwHF%Y6pyvE+->7`W^K+EFm!fzjwcIDELUY~uECyk1k*WBY|YV}Zpq zNcY>Z?CSYS(fBuy-!dK^BlySj&*n7_JH$6mM<{rn>IF|ESkREYC>>_^SXC}hwRl>? ztmp&Ri!Im6(I@m+-!*2&J{p^EEs%|EJRlBfi#O)qVn_x z-x;NER_V)hMUL_NZiRDTC{m>X_BXHBP!Z;&|6cdNLTfm$2L4(#d&agQ3Ka9bp_7FxR%k+vB$_~OF1z=7pMuJz9^>t>!&C0auXQ;l8k9?$BM7Gtq@3V7}bm96+- z?%cTOc1lav+{3~$agLAozl-ruJb;wxVSMMxhGk`lSlQyw`ptpwqKOcPQN+$SbnX*GxA6XzgTJ1j3nt$t_UJSTS{u zhkf*tF$#}Df}c?9OGe|NeIB~t%x%=Pw{3A|?=J^P`@YR4&`CM#GReo;?9iX!yP3Df zuK+X|%MmG$7o+kzozUIou!UCS?04s*#FuFy9?m(OrVL}X%M9x7Mpm?-F%4teaXv0V zQq$&(^td>@&5^8}iIjjuX+8Nk17mXk>yx}^?HQy>RqO2e?EFOHV-@%XZ#Nci`y6u& zMR)Tyxz#;)kKp1Qr&ugvtNjUF3`g%FRiU;Dn9!3OhLhEMY-uJLCe zcvo1x33|eX&E=uaxc#Lrdy-vbu8Czqr#&Oz$7oI5&T3nH%DLG?t?#qpIy?u9cYcd? z*gfu-g1CpC=5?ztQ8AM+6CRqVkiY8)tc4H1f{=ZExg58A}v; zFM8l+oNinA`K2seHE%BSh1#1pAL7`tH+N@h`rl$}`8o&yUx?|wP@%C`l{E*vOasDyjQ;ANIRD1f<>|@Q-gcy-} zcQ5Bp(H7BYA8TX$_SUB{;&h`O7><}+F|Rcv^9YaaJ&PQY*?sQ<$=Pp-`w|cxd5nGqy6a8tGmLL zm3b_X{!xMA)M>0BzXVGzRxeE?c#gNEnBq}RG|C&2?EQ4p4rH)_^R>)SX1q9~0*W!6l0Pdom_s-1w3%J!*$YH* zyyQ_C)p{60m9od%^$5sAY`~SNPHTM^DJPC*TAuewFpwej#fdGotHu_II}Nhw5-U-! z*W&}z4aGt(XIaD)alf1x6tSb7cPlj(#ZyxSiR;rrKjkbsm{qma7=fgK(FuIHqNPB3 z$-Mbye}`hJM3etT`O(>e+tlU>d*@aO0ZLp;IeI4~?HQp*y1k zP+OcXaI?P94sz|`(JGXPk6KsbBylfLNL zb+LAlv{-V!U`u>8`IOuC^?l+)rRw*>Vw{eEYA9ft+(3uxPb&M${@W2jpcQUIZZs1| zEzxCwToLc@PC^7HvcEK}zQF&bOJRDQ?)2uG#lB~BKn4S=Q?o^9ZcVPLJoe<59Am(a ztfym@o}|PsXI{!kCzlYbjV%wqsj5LC$w5*h8@ONXUr8Url%981?@Mqxr@l?j5&|QS zRkWs(J(BF3sZGn9d9=;j&ATtw3mhphG>~9?uUUnCSi^6tG13I$ONSPz1_sa6`TaVYndmCw~;klVT?9Nj5e6OI*g%(v;UQp2crZIsbDw&rhrn z&mmnWk&Y=}% zK^k|7b#h_eS!0gd%Qht!D=jvkP50hg%1I3AS;$L?cmSMhlw90V z;}Qi~@Uly%h_OvooGCms!WKD~h9SyV5-6e=4!h~iHntTUK0#bO<4Vnvp!S;WM7gRe zNAP7=2u`SPCie6c>lgy4Yo@7SG+o9Qsh6OWI6DL{@_EMDsZdB(DDOQ{33&Se0;V;& z+bX9`WhGnWSivP|d-(Cigu**=SCXG=KEPo;SrCXtKI}uP0h#3czo}9{XK4@{;cO`d z-LY4tnophiC*X9)f#IWaTUYY=cI-sOAI`cPwa>tp0HJH1tnJ>Pp9oY&oED}IUJ2$J zfcqZ0l_Kb`7XP4O_8oJ@?6&$QUZ4X#>)t$@_(lq;Y3#LsvqZ{-hS?g$;H}?Rf4t9T zN5hgl{(Y1g{VTze>c}Qh-Pui)W%CU_qx*(+ZZ!Lky;$*m?OyIe)0<5s*OR4jB0-Fk zJP82qqg5K3MvM+-SV6fHPa$_M;Z>(Kkkmfls4XNXieCvY~- zkIDx9{=qpcw3g~C#L6zkJ*g8&4Z?lPp?afGWlf!va=yWZ3_d84!NSzH7!^r!@bPqB_lhMcgMV~Xvycs22T51MuO{Zgp7}XFhe+HoKiMAvL8d3%#aVnmkQn>-sX<)3Q@XIq zR36tO!yi3@Gz@3H&1ybyvke6M#IM94LK6g?{?#M)(Xi43*@d^5ge8XqW8*1fMSVj> zRo$j&e>bi|O*)`Bm5{k87Fbn8@l>2VuAAH+F$-KTF|hwN5G$<|_%CPU_w=f}ym$HC zxMnE(;e%e49_1Hm;mudjnjioDTL4RVZqt+2xzGQ0GN;zbi)t?4#4gUay1W8j3`<;G3B6YF~IXaNI86wZ%s4n zPf%Z7xX|3|GH1z2`4lHAm>unW-I;QD{2X4W*6I0~MZhlkMXaM+?W^1|DiibM)^M&C znpx6Fzvk~dDlGPI2!7^QnLn>NQS51UN<*@iNV1<61ODaHphEB~9`@LgUCywga^ZmD zX&g-IIN-^ljb(I#j8~`50KzF|g?BjbsJx@_B?k$;!hlU*&g7nmUsBH%guKc(YdEuqz_mJv(`=ws1Zp(SahBnHbNLg1MM&x z9Fri9Z0S@-5#>CuICV;t<)eiGcNVaLJgDih?=41?^yxwW=L7PQ{Nj`D4Bp7LHM*kFP1s5ukiRz` z>(y$(pnE^5gNxfO;57LmZVzi_Saxx#v;UjfHkN+lvFuE>=QBN)DX+Y(kXhGjDPy0( z1$%XbN(R4TyzeIrSth*w`hoXVC;{l4h4fLvh?dvE3X@uc$b!-FsIXT=a~-7g`TC&< zYRDsPMX$Fw$z-)`h-a*%!H0zQ{iS40!KHkx$$obp6GbU(l>0iI#^un$*JlB9@9v=r=aD>+T8N94tEdOf4-23g15>G8L=>1lcAF*ED7n zzw{WNk+xAS_EpAH@kcVBc?OT|ZBu@h2_Ff|0{54mQe*>D=2Ge_B^Sh&qmylIQ*0Hv zevetE*YZ)n#zp~KbRq!g?Uq?k{!HeqBKD5)Ww}m5FxIAO0<$)YMz!zlI@iosbx&ch zSh4{1kGME(K@m(D@!=LGlX1ZLXa6C9&od3IGyu<+ONu`Lc=<&N>axf5@!MU5_Kzu4 z%}$?5clGf!9_9R1B6tsk8IfNo&p!fxH$ez&_dSxOsw);~H5lt;SX*6&@1NTd9?ZUG)OxNK`MItpikY}x zcwb4&c0rQ@MK5Dgy39}ex!lW>ZH)jAXU53k*>6_jgndbxJDmqJ1BwkoiuJ{?jH$VT z?Vt9H7X|0~v!9$gAFU>ABc3OzjImq36bnx?FutU*J=wW6F%lKJK{ytLXgNZ5SaPh4 zjR{C6(HLuXQR#l-ek12Zrl$jCEYtoy6Zt|Ugi85cWsDVPejldQJ=u&KsjKuke({ID zaICU7lsv#bjnwC*<8d(6c%nkQs+({2bQXLL;W5_j zD@SmAUlZ9@OqT!vs^Yt2#`&i6<#O}84GKpr@+d`jhFnx%vU{5}GhPqE+ou@h?8K2OWJYXZQy;N|(Tr%vCr9v0}Y#djn5J_*{X{^D93U!aaZoESVftvyha<6e{A!?JA$v&~DH%)fQ&HeE6#jx5{^Yalwl`-gkIJJcS8$9zu(K=8T>cy9_ri<@ zEb1YzM*^j_8p9Tj`~3{~I_-KB3I@G;J(?aD)*uB3WagVQR+>tsZoVlasKci*)p3sr z%qgqqxNlFM>lm-!F5*|*fg1RV@b5~I4>b*Qz_2&3u!n43k6^B;wbyoc(BggHw9Gd7 zr>^S{vxs=TD~BINw7i5w4i}-5`J?N;9_fNUFVQBSD`ZH@o;sQHlBbpAig^w7^~rZL zD*GcDYc00&VccJV<;N~V zkWrW3VnoxDCYKV``X*hDUF_#rW4JqW4*4m#{1x(4#(Z1u=F2LReZN$R_iwl@hXx3*wS5`{=PAsBlFM#Xa5E2M~) z`&-wq0D^k3!-bKy+7n#S`DY^ijV2?rX@ADtF<}HRGL!}Lygs8s-|05%VqU?nlSjwH zYSZtUiLtLuO7IVV??hz%Q_PD;t~mBID~N6i=e3{jmD^(Vw@U+D1iVGM#i`||7|-g= zCFxW)pWC69M!-QdWbx5oVs`v+_PbL42%fd1q*O^n55I@qP`XHhFKIdUr!V}B`}YH( zi$3s|xt)M_ca1{aDN85`3rO;%9c+L_(7F2_5Vd`@z4PsNK0t}gWi%YWe_cHto$xSt z*S@TZ;0vBPXnD9l+Y+_5E;i$%u%5Q?pP_ zvD6P}ULhS~n*i^lrpibUWnmAvw{ zP?uAN0D}b_CQrm=sPRz5tP}NZPT2;o(7o`-3J5!90#xS!H>S(t~U;4{A1 z6^r@lJP0(+!m9Hr!$Fy&RRYY>fKDxe3&ta&r>9!h5(tRdlm;70h*aG99eeNWJB}SG zUE6CT#qi{I$`kbANuNb9BzVoFja5#U2*8qZ&FpYaK_&0(n267BILl7?Z94O9i$q$B zXJ1q(k!HBxcKCmNn*I&noVFX9p*fSf9sZ#Ez*+fpIJ9kVWSH>elO|k&qjbO|A7jaW zJH1ZOqbPXq6fpE9A6tLW@?DwCw6Gek|r!Or%`vbVbSkS<^@0XSkLQ` zpQ-z4%iWWbP{75+&HYuU%`?Y@R6!x@H$yUZSX7#m>9o$xWGEHJMG)2Y-D^sk#BKB# zY~~m%88aoBdbLgp>fSzkP@$@-N~imn$IPYvKZlu2_He)WL^&U0#@W^%(3ujM`L~x{YYA<9nq)t+1MvX)S5e+zRzl0n;H+N4y}~+w970 zPPBQM~f4vfA3({CE>B<;KGP7ttQRSiYL4 z{>}+dl{oKj_w4i2x!2wNNgfd1N9D_X*<0`QU)vcQz9L2bmBS-*Oyw)Q+Sy24a_{pt zBG~xwCbg$ckrCl!!1c$E?M)kKS65t-fK;YS(Q?22S%$fxA-eWIHvIo&;VhgVWfrK5 zED_-80^bPv?foP@%QW3==IBHGGG}!(@n*QN4(zQ=g=4epl7q z>i!|pZs_q?S2*iK>L26tqWR}YlDTfw;rBDoySpXg=QXVTe2_mh_7Y%RU2W?)!9$Yq zZ;1Hhm6De}%JAQKqSp4M09~HS8^`$CR1)c}*zgSSqT zYtXG15@U$3w7uzJy#%?HqmxQPK|V;= zK4GXHGP8rl(Ba5(D;-h(9c!Km-G>gCige?VanS zXKCyflnsT|L7)U;t00mr$eV^x@c|u_@^p#QR?(iIy|iL1xdU!LL)&w8y6wKqc$=%Z z$Aa2CD?+^jzxl~G=Rem1soGs=Okexnw%BH+ zvfZ|kjITRpl(bGpiaFfWEuOgIi}k&b+S1 zAGgcvA#$WQ&u!1GPxIqQ6!>(cfA5|4r`P0hK1e4`qOF6ie>;iMG|b0oI9g~&(B-Nj z){dCCUQF4}6_DK?G%(>gnlzNt$W2HPSSnM_Ny+j&Qa@d2z?-SSFQi$pSV(#&EI8te zO3G8tB_WdXihKAY@ArB>6spKYc4odBMT|OE0~KWht)G_p(%>u56}%*g`(j zX8D{%4Lc?$^wB+9H)2qpd_{B`^|OXbclbdz)n#*VZ9kul|q_`Z`o#vba{7bX4LffnPH-p zoPRhr=vow305olA(r-aaVyOVoEjvh#(YJ%*EbVLuSy?Fq8vPn)vW3C`o7T95pB%M* zPhVGC(6#&~?Xqa!KFGG_p5{~tjDJtdJD+lr!Rjw)6>*gCu~#T{OX14oH=Yl(*Z)}& z;!cC(MPgylecs+qDtDOZzYoeL%ktEJuH6kOjWj*uD80S%hSy#rzWaK9WMARTLeY%HQ*8k}(3Iw_(yQIk;yr2%)-&OV`oW46y%nRTyQ4%A)gNMbM9Ottjw0Gf4 z?eUqpn)R>DxKmvu=9Rzh6wO%o7oW0OzC5Y4 zJ?4Sua_#m3KXH57+WkT1Tr$A*l20@;XHEywlPLhs12!MFKmiRdcwtH0ErfRW8Sshh ztFoOVEg)yZoZpwLx^2dVI(M)4&*_`JZ&`1QL(+bkXRkp3Y(SpO#t0hOXtOU2w<6J< zjiedZ{+jbqdc2Oo^PF$uRD6m7;RHZGy>eJT9LoqQ(ds9zWoJeA1tX_PP1o_#G(TWMj zOb%FhBACDd;i`;~{xsRFhBD)K7;0Dn0Wc0U&tH2;c3c=gr19~)h}a3zD2f(CO{>%x z+*I?yV0pMZ$70o!%eY4IY0~^BKlHSDqNe5FvTLd*H;p;*SIHj;M22bG&RgCe^3)wB z7e?YcDF8unA}O(rc>TAgw^0Y{($}kVq5JbCnkiX{sV4l9ha?w(l)Z_Jw&CmP1pv|x zAQ5Z#$BDR>zK2x;KN##Kvb9myli8N4&`+eExF|mFBdgKLlyTbw?a*LC#a|2R?pLt? z;UX&TwNWakn$QLZN2yKDfKK1>+855#^{Jm7XGL1>;x!qF;A50;m|XrSBC5fkwF7vY z9W;C19JAb8$jT3W6+1r4R6~sz*w@X&C8&t`d17Q=MgJ0E3Yl7CeHI{?K+z-4KwE(| zI-335<2bA@+1Y@c5F93%ci)3y-+#NbJ3av2toa-DE{(g+jS&BRr#p zIb#zz3uLp*gpfrZ3pmJze+pFw|n@74F!sSK%d964dFZ}9=taU_oj{-3fz?^=E z9;Hkm-K|O?e_%>Y>mm*4-9#0FAJ{~Vj(@4oP&*oL3z0Cj83pa|$pe5pSRC%RFKg|S zx;2Xnj2YUa0>i>X-_q(Av#<1}_a}sMC~HJHBRguLRyDjBo5t4K5-vJ8l=Z@Da!JGv z?J74|efM+cj|3`soN*9l6$hc=w85UCM}z|79<5adsM9_0=4nu{NHjpYBM z`6YUDtd$|qHt4hSQ~%Pmn5$`rgTg>urUEc*2L(3CJ%dvZmF6(Q)f~3`Kc7P9var>c zsNg6Qk4Rge+MYesiL`Xs4D(SF3fkqVN{{6PVka1mxI+rS zS9j3)X2mOIvyx5A&k;O+ll7eLSn%9NcgY`NyAT1s&RaKmM!Qteyc9LM<%K+CtV*G- z0Eow}sN&mPNj?!)%OXe$co~CiXZ8O19hCls+D3}dzb7y4?b~nr`4RN>Rt(-4Y2QEpF{)h7aAe| zto%aoqILG-H4Y*(RB(`%w02z{qS$+3D^k+KnJ(y-B;|Iie>8I1JOvIn==okzw#zwB zi`-rN?r@^WE&Et0Ro^YhZ{T&Le^Xf2EEuF!Eg_Eo<291}p~*)H1u7x{m)>nI9BC&2 zob>%;PqfGfI&+?;krJe8G2}AnEL4Ag5I{BzTkj|SpB_Lx>@ayzpq`-^JcK>f_1aUw|@#_f;*1_&|Ep&$+5 zqlDl+qpfCwe6L^XQ7gl|>H6v)z@-}heXw!I)N+b224 z-UckqJRezoM`Ph=K=vTg-C^JqW2hZ4r)4e29PC?W1aLhvWY4ZSN7Z^FW^Pr+oI5a{ zCjnFdR{!fwfpDkCI)BJ58MkP1{MUL?)Y_Rs8^saY0As(}x3-OrBxyQ*oe9uq`PJ;nt zV>k?l}(rzT8mqus~cH*(Y}8jl-&k@J|4J&0)cpx1FkaHvrW zqD@83&&Op0pU3RhYRp8a^m@72qF2YqHtN|3fPlj7B}Kcs z6TTcJthIFh$E(Jq;hTTE=rGRU6xQxXSM5mnX|wVkYYav6Joj@9o2c8%I+(!Od4&pe zVSzE}>(@R@?N&3Hcqhs$*xHmmt#>4r3Y}ynLTI2b3{Z-i<&m+s=rKN6Y1yRXQZ>P0 zq4ZNI)yNr*Pb6e1N+ZbLzoj9-nV(RFpWzD6wka@Up)qAa7R*i+cC@s-lS8npSG6yX zm>aFn8jvCq(UZkTnrYP27fAccm@CZVi>G1U3msXh+%d?A2a6Ry^SvI3liU_l2;2Tj z6A-g7tn>ot(0D}MedX~I-EoXi9`di@Kp}~d$-$VX)*;JF>aB-Utfy0y&5a?NioJXO z`sMa$k`z@8!Q$vo$`*~FEf zJ*XYTDir`Hc+B{<#FTT@t%hvSG7fZrx|5ieM6Z^UI2Z; zixN3f%j}BD`o>IRcMj|!*5v3x7f-#b9Zh-+;57-LCL?Z0bj1{#m4VqL1Y(;o$&;bv z01Be_gtW2n;^J*o)vQWoz{|h>!}qtAV=nJ$4v~r_kBoahXD!CDHbky+_|+3}$xUJj z8=^C2xy<=Er1N=4+?c~(q4I~dp^1XYBtZ(d)T#6KmnSh9Uq^_mH+-Es5`!H205k%> z;c>wg$>;Ll0=$)C?z^J#J@o3e6pD6FZU5NKsIXM4h`TzytO2!8=SrbqEKkHk8~@saLSDHISsdGn3am@d`gs{fNWA#(%n_f%y-9r>TA zF!?P;fe;7=HLnCKTa!kYfB<$kiUXdk1+s%JPm)wMrd%@v-B`Dadf!DnkER8(qx;lP z)~p_>B%0G*&}5!l1Q`OBOiRM-Wad-2_WSprZ+AGAir!<05)!koc|IGDl)QVb= zf6qfKf-cwy*5r?={DhaaaA3x=ghZAleWnjQbW#3!R1II<&>IJUBsd{6fNt)}5PK4$ z1GG6p38KsOKoI5z1Mp>TvP_BRcUe)zRNA+!%wt~3d>!$94e(#@7_Qz>d6M#tVEVs4 znNl0hU13n9lB9s=qIvNtl+W}?m(O%bl!i`?b=u?3@Og{iG*HIhO~NGhs=zU7*4t_C zw^R$kZgeJ-n~tZ2Y$M{pcmDp%o%kUxI^OOcn-ju#di1-=?9hiLz7ysoap9?l8PR(= zUU;(2IX11?XXr+hdkbL)@SG9vL5I%`?~<*=ir9Bv8mA$8-xZt;OI;PH;R^QTu#dn$6Wwq&G|lEvTcGWv zIJBA+>!s?Q#=lv{HyRUrg72)g?{scgLZ_s#h$iKIHj)>UL?eIR}J%swP*$=<5~N zRe3hz|I)?*Z9Gu(nr+t!H_X8369oa`aunDQQNX%2N{C)`b@tSd^VE61z15LDbxrU% zYC(6iLrQE~p~wIf)$TBgMo3x1nN2C~a0zTF=8|80i)3$|c!!$)40g~zmaJg@;n#M1 zUpL=x5v311ZPxBu1H?VY-SUWc1d?4}N*o=(z4Ej-+n@~nGSzhHy-UV!&T?-%9S$Xu zUd&O^aZ$r@@E2!EdW5h`Z_VSBo4lIDv{JAbVDWuQre*svrqX!>DIBSZi+lkplyS;>3Bq0W*dQ)a$BW%tNQWBtOqh9He*Msjbx?00?Wqmu%H|_-mBg5K z<(+J$r3%b+`<7;w1TOAc{IU96$=;c$)KTyGzKfX+0@QivWp?p`iQ=AW;Ct3An$X=fj})m=c%0_D;4)IoCz zU@MifwH<8b^pBEs#9Youif^%(SfPYvtMCa6n;OBv4v-@4g%u^AliRBghGF~?+VGPuG7q*tblZJ|cGu{hkqg^%a(lgbQZIw_3U@A(n?+reu5^PW#a zSsKkl#PWHJn`-L^x@51{KFe@_+iM4fBv<$>vEB4>0T%qPU9gz56)K@qOC7qA5Bsb{ zBv1=!nQrD7ow&a_UI8^ti@&$KT@ZM_OQf1p+vJ3s^ohjhP_R<4mwjUwvRqK`F!#^CMA7-H}R5ZL+^547`hI~g&t(?W{WqHBMXG<6BA`3HdyuB|}wWyFGxok)N zQ*wNy&J1DIFQTwtF}^f-I_4Uhw32n6mS6qJW?%5flO?>(+^dXxSGz2BvHvW3CH<2+ zo^p=wmU*KZuIu?6NQ(^O@rh-t?DWiopTT`3X+p{0RyvFNHt$V7&-K>)eWp zOe>^~L8}o-Vr+U5qeLp9Cdx*bu5&2LN$T==A=A2F(ecw5g>V&|*zt zuNxWA6}%Zdp|6v$_`fGLn-x+aSR`HbN06;*{7l|CYxh}jELPs6J#DUWLi+oBLMp9T z3Xy4~YTw*x`kLU_2&qQI%6&w4i!p#^O8;Fa26l$Z-epei*E{;oFN3=N2_H9FA+(IcX*k*eax_@k@G_zG6s>{Z_94_E&!&diJq#^I@Nc;yIyh?NPga3htPvvYlssDOL*N#v zVR~+itvY-f?8*xG-L&lRxC{znv_vOJU>IIzewi9Nm3Egv_gj)w4An_82>z?^NTG%V z$8^1OcqND>S5Etg_l12#kb*8mQon&P{R=-jrs{D7xRLIS=!$oC0E!XBK@bQE=7DdV zMQ7Sf*mrakVy$7mX|@Uy<-MpnAz;yJPN*UZUM4 z!13}vO~gwe^ORUz&mZlVU#^%SrbgK8*m@9(zjz=|Ada=#(vnR?V$vD;BGz8!{>|f(hm1(~J`kes21yir0DV(VRO-9gbiQ+jvu=Hu-3w3KAhR3FmCGqp;$_S-grb4h`= zJ}XhSWV(g=@1TenvXtph)vX7MW3AndHn{kpZF*pt0PmzwuMqfRZLN4gTV+VF*L~rP z(4Bx4MMe4{*B+|Sb%YXE=wn{m%F}`eE;|SNhT-6envJnnti2)(qeVcft4eZ`%FPes ziBk;M>gnUs$7yf=0pHqv0d>uTN>$mK%TvA2OKhb{XmMi-o5 zx8Z;K6!k0>M81}q^F3D>+5{5q#nt=V!NS)!XwRb%tYw_7xg-@+iGw}xdad)cw!2fd zTTlxdRuX>1QUu0*Y4YkqR+?_N!^1x345%0&RA?ocW3xsG%>^g+JKTu3Wn5_j1x!pJ z&jPEy&n@EFup_<9?aSOr4H9%F1XkUdlvZ`aTN6gbgnu zsad;8>@7Ik6Zgh5%|I*B+k6XUp9?)hZbcZ$Rw3ox+Ydbo3A$w*KOsf#$a_69!)63u z_H8NcZ-4Xm)MRm~L^=o5$iNHam`BrWWCwD&}a zDOW@=gYo2UZA}g#LRm87BA#Jheg3ebBCwEh_<^8km5cDJ-fQoLOR|iyXi>oHJ`*!x zT~LU^6RA^1*ZCj)KYwiUFnL{)J~?Kl3-z^_BDa1MI;PX84box%*1Mo67dTC@4#Nu< zm0AVy0&$c^1Y5pNN=k{0`5U&2L^z*|epp~Y^k_FKTBz#ZhWi~(W$iPFW!K-!5-=&0 ztGgU&rO}l;c*Xspbj+9sIh}_X^%@uz5}F$%v{079#UC-v*UV>6wk#;$UKy7e?D`2x zX~l9;iAP3an86AD{imXkMy;@tQ$MSb^`>+v1A~5&Rnr__D>_>xwiu(|QuaK?c-vlJ zZ$9X|(RguK$PBBZz!!i*LUD!njv1St4iJ~s1p7linbCnfolVD5(&2fZ8Si@T%5iYK zZZx$P^OS>RXxd#gq5w{*!8YUN_J3oji{pmWcTfXf&m zonZA2w`5=%WWlvN-1{B7DlV{*B9J=SX0&$&`3Un*!T64YQP~19N+v;l%ojHH`U2_f z^ToNuyWKPMt*Zy`Al;T9`DN5(KVe~n1 zV`c%H{ciSWTM)?b7ue^?J%u(Aj4m1!6x5YvdT3t!b_KDbHL~vl)4kAxxB(~cE`#k< zdWIV)O1KP0INUft-v8pFw_oxYVD6nYVie8U=(G4^mxI&AY4-}TeOrCNhf(;s})(>t{9;5Up@r0J*?

vLA-m{1%_I=&_ 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%0D;9Yuw;1u-pO3uSh*Z91~ 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 literal 0 HcmV?d00001 diff --git a/streaming/redpanda/variables.tf b/streaming/redpanda/variables.tf index 3e44fd592..538bc902a 100644 --- a/streaming/redpanda/variables.tf +++ b/streaming/redpanda/variables.tf @@ -1,33 +1,33 @@ -variable name { - description = "Name for Resoucres created - dokeks-redpanda" - default = "doeks-redpanda" - type = string +variable "name" { + description = "Name for Resoucres created - dokeks-redpanda" + default = "doeks-redpanda" + type = string } variable "region" { - description = "Default Region" - default = "us-west-2" - type = string + description = "Default Region" + default = "us-west-2" + type = string } -variable eks_cluster_version { - description = "EKS Cluster Version" - default = "1.28" - type = string +variable "eks_cluster_version" { + description = "EKS Cluster Version" + default = "1.28" + type = string } variable "vpc_cidr" { - description = "VPC CIDR" - default = "172.16.0.0/16" - type = string + description = "VPC CIDR" + default = "172.16.0.0/16" + type = string } variable "public_subnets" { description = "Public Subnets with 126 IPs" - default = ["172.16.255.0/25", "172.16.255.128/25"] - type = list(string) + default = ["172.16.255.0/25", "172.16.255.128/25"] + type = list(string) } variable "private_subnets" { description = "Private Subnets with 510 IPs" - default = ["172.16.0.0/23", "172.16.2.0/23"] - type = list(string) + default = ["172.16.0.0/23", "172.16.2.0/23"] + type = list(string) } #--------------------------------------- # Prometheus Enable @@ -42,12 +42,12 @@ variable "enable_amazon_prometheus" { # Redpanda Config #--------------------------------------- variable "redpanda_username" { - default = "superuser" - description = "Default Super Username for Redpanda deployment" - type = string + default = "superuser" + description = "Default Super Username for Redpanda deployment" + type = string } variable "redpanda_domain" { - default = "customredpandadomain.local" - description = "Repanda Custom Domain" - type = string -} \ No newline at end of file + default = "customredpandadomain.local" + description = "Repanda Custom Domain" + type = string +} diff --git a/streaming/redpanda/vpc.tf b/streaming/redpanda/vpc.tf index 6f680191a..3fe64112e 100644 --- a/streaming/redpanda/vpc.tf +++ b/streaming/redpanda/vpc.tf @@ -6,10 +6,10 @@ module "vpc" { source = "terraform-aws-modules/vpc/aws" version = ">= 4.0.0" - name = local.name - cidr = local.vpc_cidr - azs = local.azs - public_subnets = var.public_subnets + name = local.name + cidr = local.vpc_cidr + azs = local.azs + public_subnets = var.public_subnets private_subnets = var.private_subnets enable_nat_gateway = true @@ -28,4 +28,4 @@ module "vpc" { } tags = local.tags -} \ No newline at end of file +} From aaf2f508c974a23cee48426e216c4bf67f70da37 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Mon, 15 Apr 2024 15:39:06 -0600 Subject: [PATCH 05/10] Adding install files per upstream requirements --- streaming/redpanda/cleanup.sh | 32 ++++++++++++++++++++++++++++++++ streaming/redpanda/install.sh | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 streaming/redpanda/cleanup.sh create mode 100644 streaming/redpanda/install.sh diff --git a/streaming/redpanda/cleanup.sh b/streaming/redpanda/cleanup.sh new file mode 100644 index 000000000..416373b7e --- /dev/null +++ b/streaming/redpanda/cleanup.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -o errexit +set -o pipefail + +read -p "Enter the region: " region +export AWS_DEFAULT_REGION=$region + +targets=( + "module.eks_data_addons" + "module.eks_blueprints_addons" +) + +for target in "${targets[@]}" +do + terraform destroy -target="$target" -auto-approve + destroy_output=$(terraform destroy -target="$target" -auto-approve 2>&1) + if [[ $? -eq 0 && $destroy_output == *"Destroy complete!"* ]]; then + echo "SUCCESS: Terraform destroy of $target completed successfully" + else + echo "FAILED: Terraform destroy of $target failed" + exit 1 + fi +done + +terraform destroy -auto-approve +destroy_output=$(terraform destroy -auto-approve 2>&1) +if [[ $? -eq 0 && $destroy_output == *"Destroy complete!"* ]]; then + echo "SUCCESS: Terraform destroy of all targets completed successfully" +else + echo "FAILED: Terraform destroy of all targets failed" + exit 1 +fi diff --git a/streaming/redpanda/install.sh b/streaming/redpanda/install.sh new file mode 100644 index 000000000..83c76e620 --- /dev/null +++ b/streaming/redpanda/install.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +read -p "Enter the region: " region +export AWS_DEFAULT_REGION=$region + +# List of Terraform modules to apply in sequence +targets=( + "module.vpc" + "module.eks" + "module.ebs_csi_driver_irsa" + "module.eks_bluepints_addons" +) + +# Apply modules in sequence +for target in "${targets[@]}" +do + echo "Applying module $target..." + apply_output=$(terraform apply -target="$target" -auto-approve 2>&1 | tee /dev/tty) + if [[ ${PIPESTATUS[0]} -eq 0 && $apply_output == *"Apply complete"* ]]; then + echo "SUCCESS: Terraform apply of $target completed successfully" + else + echo "FAILED: Terraform apply of $target failed" + exit 1 + fi +done + +# Final apply to catch any remaining resources +echo "Applying remaining resources..." +apply_output=$(terraform apply -auto-approve 2>&1 | tee /dev/tty) +if [[ ${PIPESTATUS[0]} -eq 0 && $apply_output == *"Apply complete"* ]]; then + echo "SUCCESS: Terraform apply of all modules completed successfully" +else + echo "FAILED: Terraform apply of all modules failed" + exit 1 +fi From 54ed69dba26260c885191ed70626d254d9f91643 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Mon, 15 Apr 2024 15:50:50 -0600 Subject: [PATCH 06/10] update README --- streaming/redpanda/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/redpanda/README.md b/streaming/redpanda/README.md index 07b1d4678..c3eb8f613 100644 --- a/streaming/redpanda/README.md +++ b/streaming/redpanda/README.md @@ -1,6 +1,6 @@ # Redpanda on Amazon EKS **** -Redpanda is a simple, powerful, and cost-efficient streaming data platform that is compatible with Kafka APIs but much less complex, faster and more affordable. Get started in seconds with Redpanda Serverless! +Redpanda is a simple, powerful, and cost-efficient streaming data platform that is compatible with Kafka APIs but much less complex, faster and more affordable. Lets take a look at how we can deploy this on Amazon EKS! **** ![redpanda.png](redpanda.png) ## Prerequisites From 315f06da5004d662799a19b80e9e5c433869259f Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Mon, 15 Apr 2024 15:55:44 -0600 Subject: [PATCH 07/10] update README --- streaming/redpanda/README.md | 50 ++---------------------------------- 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/streaming/redpanda/README.md b/streaming/redpanda/README.md index c3eb8f613..101245f94 100644 --- a/streaming/redpanda/README.md +++ b/streaming/redpanda/README.md @@ -83,9 +83,9 @@ kubectl get nodes ``` You should see 6 worked nodes, 3 deployed for EKS Add-ons (Cert-Manager, AWS Load Balancer Controller, Monitoring Tools, etc). -## Configuring Environment for Lambda/Serverless Lab +## Configuring Environment -In order to allow our Lambda function to communicate with the cluster, we will need to take several steps to set up external access: +In order to utilize our Redpanda Cluster we will need to create some items: * Export the external certificate that was generated during the prior steps from the Redpanda cluster, and store it in AWS Secrets Manager * Get our superuser password from AWS Secrets Manager and Setup Environment Variables @@ -249,52 +249,6 @@ Do not allow full public access to port 8080, as the Redpanda Community Edition Once you are able to access the Redpanda Console, you can view information about your brokers, IP addresses and IDs, as well as information on your topics. You can view the messages produced to your topics, and produce additional messages to topics using the web interface. -## Expand the Permissions for the Created Redpanda Account - -Now that the cluster is configured, we need to expand the permissions of the account that we created so that this account can be used to configure the AWS Lambda trigger. The account needs permissions such as creating new consumer groups. - -Steps: - -1. Access the Redpanda console as described in previous steps. -2. Click on “Security” on the left margin. -3. Click on the username that you created in previous steps, our example uses “redpanda-twitch-account”. -4. A dialog titled “Edit ACL” will appear. For the sake of this demo, click on “Allow all operations”. Note that for production use, you will want to limit these permissions. -5. Click on “OK”. - - -## Collecting Information Before Using SAM - -We need to collect a few pieces of information before deploying our template. - -* The VPC ID of the VPC created by eksctl. -* The subnet IDs of the public subnets created by eksctl. - -Get our VPC ID: -``` -export VPCID=`aws eks describe-cluster --name devcluster-02 | jq -r ".cluster.resourcesVpcConfig.vpcId"` -``` -Get the public subnets used by our VPC: - -``` -for subnet in $(aws ec2 describe-subnets --filter Name=vpc-id,Values=$VPCID --query 'Subnets[?MapPublicIpOnLaunch==`true`].SubnetId' --output text); do SUBNETS="${SUBNETS}${subnet}", ; done; - -``` -We now have Environment Variables $VPCID and $SUBNETS, as well as $REGUSER, $REPASS for uses with Lambda SAM Template. - -OR gather this data from console - -1. Navigate to the AWS Console and go to the VPC service. -2. Find the VPC created by our EKS cluster - the VPC name should have “eksctl” in the name. Copy the VPC ID to your notes. It should look something like “vpc-02bd59e133e36991d”. -3. Click on “Subnets” on the left margin. Find the two subnets with “Public” and “eksctl” in the name, and copy the subnet IDs to your notes. They should look something like “subnet-0d7fed3e19322e837”. - - -Get the IP Addresses assigned to your Redpanda containers: -``` -kubectl get pod --namespace \ --o=custom-columns=NODE:.spec.nodeName,NAME:.[metadata.name](http://metadata.name/) -l \ -[app.kubernetes.io/component=redpanda-statefulset](http://app.kubernetes.io/component=redpanda-statefulset) - -``` ## Conclusion From 7fce126132d84fd074deb1b0ff2f432b44a2c889 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Mon, 15 Apr 2024 15:56:57 -0600 Subject: [PATCH 08/10] update README --- streaming/redpanda/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/redpanda/README.md b/streaming/redpanda/README.md index 101245f94..6ca416e50 100644 --- a/streaming/redpanda/README.md +++ b/streaming/redpanda/README.md @@ -113,7 +113,7 @@ aws secretsmanager get-secret-value --secret-id redpandarootcert --output json Alternatively this can be done with ACM, please check out this document on how: -## 2/ Setup Environment Variables for Redpanda and Future use with Lambda +## 2/ Setup Environment Variables for Redpanda Cluster Create Environment Variables, we will pull aws secretsmanager secret we created with Terraform for superuser of cluster, then configure our topic username of "redpanda-twitch-account" and password "changethispassword" which will be used later with SAM. This is for lab purposes only and you should secure this password. ``` From 5bb6a28570cf67cbc4b14a842f90760fd828cc69 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Mon, 15 Apr 2024 15:58:38 -0600 Subject: [PATCH 09/10] update README --- streaming/redpanda/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/redpanda/README.md b/streaming/redpanda/README.md index 6ca416e50..543b4faf1 100644 --- a/streaming/redpanda/README.md +++ b/streaming/redpanda/README.md @@ -115,7 +115,7 @@ Alternatively this can be done with ACM, please check out this document on how: ## 2/ Setup Environment Variables for Redpanda Cluster -Create Environment Variables, we will pull aws secretsmanager secret we created with Terraform for superuser of cluster, then configure our topic username of "redpanda-twitch-account" and password "changethispassword" which will be used later with SAM. This is for lab purposes only and you should secure this password. +Create Environment Variables, we will pull aws secretsmanager secret we created with Terraform for superuser of cluster, then configure our topic username of "redpanda-twitch-account" and password "changethispassword" which will be used later for testing topics. This is for lab purposes only and you should secure this password. ``` SUPUSER="superuser" SUPPASS=`aws secretsmanager get-secret-value --secret-id redpanda_password-1234 --query "SecretString" --output text` From cd6c19c17bffe322076282d3a422bb7fac30e7a7 Mon Sep 17 00:00:00 2001 From: Aaron Shapiro Date: Wed, 1 May 2024 13:24:35 -0600 Subject: [PATCH 10/10] Changes to cert-manager and test/add sh scripts --- streaming/redpanda/addons.tf | 26 ++++++++++++++++++++------ streaming/redpanda/cleanup.sh | 4 +++- streaming/redpanda/main.tf | 20 +------------------- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/streaming/redpanda/addons.tf b/streaming/redpanda/addons.tf index c76a1dbd4..8dbd2db3e 100644 --- a/streaming/redpanda/addons.tf +++ b/streaming/redpanda/addons.tf @@ -63,6 +63,10 @@ resource "kubernetes_storage_class" "ebs_csi_encrypted_gp3_storage_class" { #--------------------------------------- # Redpanda Config #--------------------------------------- +data "aws_secretsmanager_secret_version" "redpanada_password_version" { + secret_id = aws_secretsmanager_secret.redpanada_password.id + depends_on = [aws_secretsmanager_secret_version.redpanada_password_version] +} resource "random_password" "redpanada_password" { length = 16 @@ -72,7 +76,7 @@ resource "aws_secretsmanager_secret" "redpanada_password" { name = "redpanda_password-1234" recovery_window_in_days = 0 } -resource "aws_secretsmanager_secret_version" "redpanada_password" { +resource "aws_secretsmanager_secret_version" "redpanada_password_version" { secret_id = aws_secretsmanager_secret.redpanada_password.id secret_string = random_password.redpanada_password.result } @@ -80,6 +84,13 @@ resource "aws_secretsmanager_secret_version" "redpanada_password" { #--------------------------------------------------------------- # Grafana Admin credentials resources #--------------------------------------------------------------- + +data "aws_secretsmanager_secret_version" "grafana_password_version" { + secret_id = aws_secretsmanager_secret.redpanada_password.id + depends_on = [aws_secretsmanager_secret_version.grafana_password_version] +} + + resource "random_string" "random_suffix" { length = 10 special = false @@ -135,11 +146,11 @@ module "eks_blueprints_addons" { most_recent = true } } - enable_aws_load_balancer_controller = true - enable_cluster_autoscaler = true - enable_metrics_server = true - enable_aws_cloudwatch_metrics = true - enable_cert_manager = true + enable_cluster_autoscaler = true + enable_metrics_server = true + enable_aws_cloudwatch_metrics = true + enable_cert_manager = true + #--------------------------------------- @@ -162,6 +173,9 @@ module "eks_blueprints_addons" { value = "ClusterFirstWithHostNet" } ] + + tags = local.tags + } diff --git a/streaming/redpanda/cleanup.sh b/streaming/redpanda/cleanup.sh index 416373b7e..3d6e650df 100644 --- a/streaming/redpanda/cleanup.sh +++ b/streaming/redpanda/cleanup.sh @@ -6,8 +6,10 @@ read -p "Enter the region: " region export AWS_DEFAULT_REGION=$region targets=( - "module.eks_data_addons" "module.eks_blueprints_addons" + "module.ebs_csi_driver_irsa" + "module.eks" + "module.vpc" ) for target in "${targets[@]}" diff --git a/streaming/redpanda/main.tf b/streaming/redpanda/main.tf index 1a9a1d5b5..9cbe8058d 100644 --- a/streaming/redpanda/main.tf +++ b/streaming/redpanda/main.tf @@ -6,15 +6,6 @@ data "aws_availability_zones" "available" {} data "aws_caller_identity" "current" {} data "aws_partition" "current" {} -data "aws_secretsmanager_secret_version" "redpanada_password_version" { - secret_id = aws_secretsmanager_secret.redpanada_password.id - depends_on = [aws_secretsmanager_secret_version.redpanada_password] -} -data "aws_secretsmanager_secret_version" "grafana_password_version" { - secret_id = aws_secretsmanager_secret.redpanada_password.id - depends_on = [aws_secretsmanager_secret_version.grafana_password_version] -} - ################################################################################ # Local Variables @@ -79,7 +70,7 @@ module "eks" { core_node_group = { name = "core-mng-01" description = "Core EKS managed node group" - instance_types = ["m5.large"] + instance_types = ["m5.xlarge"] min_size = 3 max_size = 6 desired_size = 3 @@ -89,15 +80,6 @@ module "eks" { #--------------------------------------------------------------- } - redpanda_node_group = { - name = "redpanda-mng-01" - description = "Redpanda EKS Managed Node Group" - instance_types = ["c5d.large"] - min_size = 3 - max_size = 6 - desired_size = 3 - - } }