DEV Community

Cover image for Use CDK to deploy a complete solution with Kafka, App Runner, EKS and DynamoDB
Abhishek Gupta for AWS

Posted on • Originally published at abhishek1987.Medium

Use CDK to deploy a complete solution with Kafka, App Runner, EKS and DynamoDB

A previous blog post covered how to deploy a Go Lambda function and trigger it in response to events sent to a topic in a MSK Serverless cluster.

This blog will take it a notch further.

  • The solution consists of a MSK Serverless cluster, a producer application on AWS App Runner and a consumer application in Kubernetes (EKS) persisting data to DynamoDB.
  • The core components (MSK cluster, EKS and DynamoDB) and the producer application will be provisioned using Infrastructure-as-code with AWS CDK.
  • Since the consumer application on EKS will interact with both MSK and DynamoDB, you will also need to configure appropriate IAM roles.

All the components in this solution have been written in Go.

Prerequisites

You will need the following:

Use CDK to provision MSK, EKS and DynamoDB

All the code and config is present in this GitHub repo. Clone the GitHub repo and change to the right directory:

git clone https://github.com/abhirockzz/msk-cdk-apprunner-eks-dynamodb
cd msk-cdk-apprunner-eks-dynamodb/cdk
Enter fullscreen mode Exit fullscreen mode

Deploy the first CDK stack:

cdk deploy MSKDynamoDBEKSInfraStack
Enter fullscreen mode Exit fullscreen mode

Wait for the all the components to get provisioned, including MSK Serverless cluster, EKS cluster and DynamoDB.
You can check its progress in the AWS CloudFormation console.

You can take a look at the CDK stack code here.

Deploy MSK producer application to App Runner using CDK

Deploy the second CDK stack.

Note that in addition to deploying the producer application to App Runner, it also builds and uploads the consumer application Docker image to an ECR repository.

Make sure to enter the MSK Serverless broker endpoint URL.

export MSK_BROKER=<enter endpoint>
export MSK_TOPIC=test-topic

cdk deploy AppRunnerServiceStack
Enter fullscreen mode Exit fullscreen mode

Wait for the the producer application to get deployed to App Runner. You can check its progress in the AWS CloudFormation console.

You can take a look at the CDK stack code here and the producer application here.

Once complete, make a note of the App Runner application public endpoint as well as the ECR repository for the consumer application. You should see these in the stack output as such:

Outputs:
AppRunnerServiceStack.AppURL = <app URL>
AppRunnerServiceStack.ConsumerAppDockerImage = <ecr docker image>
....
Enter fullscreen mode Exit fullscreen mode

Now, you can verify if the application is functioning properly. Get the publicly accessible URL for the App Runner application and invoke it using curl. This will create the MSK topic and send data specified in the HTTP POST body.

export APP_RUNNER_URL=<enter app runner URL>

curl -i -X POST -d '{"email":"user1@foo.com","name":"user1"}' $APP_RUNNER_URL
Enter fullscreen mode Exit fullscreen mode

Now you can deploy the consumer application to the EKS cluster. Before that, execute the steps to configure appropriate permissions for the application to interact with MSK and DynamoDB.

Configure IRSA for consumer application

Exit the cdk directory and change to the root of the project:

cd ..
Enter fullscreen mode Exit fullscreen mode

Create an IAM OIDC identity provider for your cluster with eksctl

export EKS_CLUSTER_NAME=<EKS cluster name>

oidc_id=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f 5)

aws iam list-open-id-connect-providers | grep $oidc_id

eksctl utils associate-iam-oidc-provider --cluster $EKS_CLUSTER_NAME --approve
Enter fullscreen mode Exit fullscreen mode

Define IAM roles for the application

Configure IAM Roles for Service Accounts (also known as IRSA).

Refer to the documentation for details

Start by creating a Kubernetes Service Account:

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: eks-app-sa
EOF
Enter fullscreen mode Exit fullscreen mode

To verify - kubectl get serviceaccount/eks-app-sa -o yaml

Set your AWS Account ID and OIDC Identity provider as environment variables:

ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)

export AWS_REGION=<enter region e.g. us-east-1>

OIDC_PROVIDER=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")
Enter fullscreen mode Exit fullscreen mode

Create a JSON file with Trusted Entities for the role:

read -r -d '' TRUST_RELATIONSHIP <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:aud": "sts.amazonaws.com",
          "${OIDC_PROVIDER}:sub": "system:serviceaccount:default:eks-app-sa"
        }
      }
    }
  ]
}
EOF
echo "${TRUST_RELATIONSHIP}" > trust.json
Enter fullscreen mode Exit fullscreen mode

To verify - cat trust.json

Now, create the IAM role:

export ROLE_NAME=msk-consumer-app-irsa

aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document file://trust.json --description "IRSA for MSK consumer app on EKS"
Enter fullscreen mode Exit fullscreen mode

You will need to create and attach policy to role since we only want the consumer application to consume data from MSK cluster and put data to DynamoDB table - this needs to be fine-grained.

In the policy.json file, replace values for MSK cluster and DynamoDB. Create and attach the policy to the role you just created:

aws iam create-policy --policy-name msk-consumer-app-policy --policy-document file://policy.json

aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/msk-consumer-app-policy
Enter fullscreen mode Exit fullscreen mode

Finally, associate the IAM role with the Kubernetes Service Account that you created earlier:

kubectl annotate serviceaccount -n default eks-app-sa eks.amazonaws.com/role-arn=arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME

#confirm
kubectl get serviceaccount/eks-app-sa -o yaml
Enter fullscreen mode Exit fullscreen mode

Deploy MSK consumer application to EKS

You can refer to the consumer application code here.

Make sure to update consumer application manifest (app-iam.yaml) with the MSK cluster endpoint and ECR image (obtained from the stack output).

kubectl apply -f msk-consumer/app-iam.yaml

# verify Pods
kubectl get pods -l=app=msk-iam-consumer-app
Enter fullscreen mode Exit fullscreen mode

Verify end to end solution

Continue to send records using App Runner producer application:

export APP_RUNNER_URL=<enter app runner URL>

curl -i -X POST -d '{"email":"user2@foo.com","name":"user2"}' $APP_RUNNER_URL
curl -i -X POST -d '{"email":"user3@foo.com","name":"user3"}' $APP_RUNNER_URL
curl -i -X POST -d '{"email":"user4@foo.com","name":"user4"}' $APP_RUNNER_URL
Enter fullscreen mode Exit fullscreen mode

Check consumer app logs on EKS to verify:

kubectl logs -f $(kubectl get pods -l=app=msk-iam-consumer-app -o jsonpath='{.items[0].metadata.name}')
Enter fullscreen mode Exit fullscreen mode

Scale out consumer app

The MSK topic created by the producer application has three topic partitions, so we can have maximum of three consumer instances.
Scale out to three replicas:

kubectl scale deployment/msk-iam-consumer-app --replicas=3
Enter fullscreen mode Exit fullscreen mode

Verify the number of Pods and check logs for each of them. Notice how the data consumption is being balanced across the three instances.

kubectl get pods -l=app=msk-iam-consumer-app
Enter fullscreen mode Exit fullscreen mode

Conclusion

You were able to deploy the end to end application using CDK. This comprised of a producer on App Runner sending data to MSK Serverless cluster and a consumer on EKS persisting data to DynamoDB. All the components were written using the Go programming language!

Top comments (0)