Build Cloud-Native Apps with AWS App Runner, Redis, and AWS CDK

[ad_1]

AWS App Runner allows you to deploy and run cloud-native applications in a fast, simple, and cost-effective manner. You can choose the programming language of your choice since App Runner can deploy directly from source code (in GitHub for example) or a Docker container image (from private or public repo in ECR) – all this without worrying about provisioning and managing the underlying infrastructure.

High-level architecture

High-level architecture

This blog post showcases how to run a Go application on AWS App Runner which will further integrate with Amazon MemoryDB for Redis (a Redis compatible, durable, in-memory database service). You will deploy the application and its infrastructure using AWS CDK. This includes App Runner VPC Connector config to connect with MemoryDB as well as using CDK to package your Go application as a Docker image, upload to ECR, and seamlessly deployment to App Runner (no manual steps needed). I will close the blog with a brief walk-through of the CDK code which is written in Go, thanks to the CDK Go support (which is in Developer Preview atthe time of writing).

The code is available on GitHub

Before you proceed, make sure you have the following ready:

Pre-requisites

  • Create an AWS account (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.
  • Install and configure AWS CLI
  • Install and bootstrap AWS CDK
  • Setup Docker
  • Install Go

Use a Single Command to Deploy Infra and App

Clone the GitHub repo and change it to the correct directory:

git clone https://github.com/abhirockzz/go-redis-apprunner
cd cdk

Set environment variables:

  • App Runner service name and port
  • A password of your choice for MemoryDB (this is just for demonstration purposes – for production, you will have specific processes in place to handle sensitive info)

Be mindful of the password requirements. From the documentation:

“In particular, be aware of these user password constraints when using ACLs for MemoryDB:

  • Passwords must be 16–128 printable characters.
  • The following non-alphanumeric characters are not allowed: , “” / @.”
export APPRUNNER_SERVICE_NAME=apprunner-memorydb-go-app
export APPRUNNER_SERVICE_PORT=8080
export MEMORYDB_PASSWORD=<password of your choice e.g. P@ssw0rd12345678>

cdk deploy --all

This will kick off the stack creation. All you need to do now is …. wait.

Why?? Well, that’s because CDK is doing everything for us behind the scenes. Starting with VPC (and subnets, NAT gateway, etc.), MemoryDB cluster, security groups, packaging and uploading our app as a Docker image (to a private ECR repo) and finally deploying it as an App Runner service – that’s quite a lot!

Feel free to navigate to the AWS Console > CloudFormation > Stacks to see what’s going on…

CloudFormation Stack

CloudFormation Stack

Once both the Stack run to completion, you can explore all the components:

MemoryDB Subnet Group – VPC and two subnets, each in one availability zone.

MemoryDB Subnet Group

MemoryDB Subnet Group

MemoryDB cluster – CDK code was hard-coded to create two-node clusters (single shard) i.e. with one primary and one replica node. Note that the primary and replica nodes are spread across different AZs (as per Subnet Group config above)

MemoryDB Cluster

MemoryDB Cluster

Also, the ACL and user setting for MemoryDB – will be used for authentication (username/password) and access control (authorization).

MemoryDB ACL

MemoryDB ACL

Remember that MemoryDB runs in a different VPC and it’s not possible to connect your App Runner service to it by default. You need to associate your service to the MemoryDB VPC – that’s where the App Runner VPC connector comes in and allows it to communicate with MemoryDB.

The VPC and subnets are the same as that of MemoryDB. Notice the security group as well – more on this in a minute

App Runner VPC config

App Runner VPC config

Check the MemoryDB security group. There is an Inbound rule that says: the source security group (that is associated with App Runner VPC Connector config in this case) can access the TCP port 6379 of instance associated with the target security group (MemoryDB in this case)

MemoryDB Security Group

MemoryDB Security Group

You can also confirm the IAM access role that was created for App Runner as well as the service environment variables:

Environment variables have been used for demonstration purposes. For production apps, you should use AWS Secrets Manager for storing and retrieving sensitive information such as passwords, auth tokens etc.

App Runner - general config

App Runner – general config

Test the Application

The application itself is fairly simple and exposes a couple of HTTP endpoints to create sample data.
Locate the App Runner service URL from the details page.

App Runner application URL

App Runner application URL

You can test the application using any HTTP client (I have used curl in this example):

# create a couple of user entries
curl -i -X POST -d '"email":"[email protected]", "name":"user1"' <enter APPRUNNER_APP_URL>
curl -i -X POST -d '"email":"[email protected]", "name":"user2"' <enter APPRUNNER_APP_URL>

HTTP/1.1 200 OK
Date: Fri, 20 May 2022 08:05:06 GMT
Content-Length: 0

# search for user via email
curl -i <enter APPRUNNER_APP_URL>/[email protected]

HTTP/1.1 200 OK
Date: Fri, 20 May 2022 08:05:11 GMT
Content-Length: 41
Content-Type: text/plain; charset=utf-8

"email":"[email protected]","name":"user2"

# is a user does not exist
curl -i <enter APPRUNNER_APP_URL>/[email protected]

HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Fri, 20 May 2022 08:05:36 GMT
Content-Length: 38

user does not exist [email protected]

Clean Up

Once you’ve completed this tutorial, delete the stack(s):

A Quick Walkthrough of the CDK Code

The Infrastructure part (IaaC to be specific) is comprised of two (CDK) Stacks (in the context of a single CDK App).

I will provide a walk-through of the CDK code written in Go, thanks to the CDK Go support (Developer Preview at the time of writing).

Please note that some of the code has been redacted/omitted for brevity – you can refer to the entire code on GitHub repo.

Here is the first stack:

To summarise:

  • A single line of code to create VPC and related components!
  • We create ACL, User, and Subnet groups for the MemoryDB cluster and refer to them when during cluster creation with awsmemorydb.NewCfnCluster
  • We also create required security groups for MemoryDB as well as AppRunner (VPC config)
...
vpc = awsec2.NewVpc(stack, jsii.String("demo-vpc"), nil)

authInfo := map[string]interface"Type": "password", "Passwords": []stringgetMemoryDBPassword()
user = awsmemorydb.NewCfnUser(stack, jsii.String("demo-memorydb-user"), &awsmemorydb.CfnUserPropsUserName: jsii.String("demo-user"), AccessString: jsii.String(accessString), AuthenticationMode: authInfo)
acl := awsmemorydb.NewCfnACL(stack, jsii.String("demo-memorydb-acl"), &awsmemorydb.CfnACLPropsAclName: jsii.String("demo-memorydb-acl"), UserNames: &[]*stringuser.UserName())

//snip...

subnetGroup := awsmemorydb.NewCfnSubnetGroup(stack, jsii.String("demo-memorydb-subnetgroup"), &awsmemorydb.CfnSubnetGroupPropsSubnetGroupName: jsii.String("demo-memorydb-subnetgroup"), SubnetIds: &subnetIDsForSubnetGroup)

memorydbSecurityGroup := awsec2.NewSecurityGroup(stack, jsii.String("memorydb-demo-sg"), &awsec2.SecurityGroupPropsVpc: vpc, SecurityGroupName: jsii.String("memorydb-demo-sg"), AllowAllOutbound: jsii.Bool(true))

memorydbCluster = awsmemorydb.NewCfnCluster(stack, jsii.String("demo-memorydb-cluster"), &awsmemorydb.CfnClusterPropsClusterName: jsii.String("demo-memorydb-cluster"), NodeType: jsii.String(memoryDBNodeType), AclName: acl.AclName(), NumShards: jsii.Number(numMemoryDBShards), EngineVersion: jsii.String(memoryDBRedisEngineVersion), Port: jsii.Number(memoryDBRedisPort), SubnetGroupName: subnetGroup.SubnetGroupName(), NumReplicasPerShard: jsii.Number(numMemoryDBReplicaPerShard), TlsEnabled: jsii.Bool(true), SecurityGroupIds: &[]*stringmemorydbSecurityGroup.SecurityGroupId(), ParameterGroupName: jsii.String(memoryDBDefaultParameterGroupName))

//snip...

appRunnerVPCConnSecurityGroup = awsec2.NewSecurityGroup(stack, jsii.String("apprunner-demo-sg"), &awsec2.SecurityGroupPropsVpc: vpc, SecurityGroupName: jsii.String("apprunner-demo-sg"), AllowAllOutbound: jsii.Bool(true))

memorydbSecurityGroup.AddIngressRule(awsec2.Peer_SecurityGroupId(appRunnerVPCConnSecurityGroup.SecurityGroupId(), nil), awsec2.Port_Tcp(jsii.Number(memoryDBRedisPort)), jsii.String("for apprunner to access memorydb"), jsii.Bool(false))
...

For the App Runner service:

  • Docker image build and upload to private ECR repo process is done by CDK
  • We specify the environment variables that the service will need
  • App Runner source config refers to the IAM role, ECR image, and environment variables
  • Then there is the networking config that encapsulates the VPC connector config,
  • and finally, the App Runner service is created
... ecrAccessPolicy := awsiam.ManagedPolicy_FromManagedPolicyArn(stack, jsii.String("ecr-access-policy"), jsii.String(appRunnerServicePolicyForECRAccessARN)) apprunnerECRIAMrole := awsiam.NewRole(stack, jsii.String("role-apprunner-ecr"), &awsiam.RolePropsAssumedBy: awsiam.NewServicePrincipal(jsii.String(appRunnerServicePrincipal), nil), RoleName: jsii.String("role-apprunner-ecr"), ManagedPolicies: &[]awsiam.IManagedPolicyecrAccessPolicy) ecrAccessRoleConfig := awsapprunner.CfnService_AuthenticationConfigurationPropertyAccessRoleArn: apprunnerECRIAMrole.RoleArn() memoryDBEndpointURL := fmt.Sprintf( appRunnerServiceEnvVarConfig := []awsapprunner.CfnService_KeyValuePairPropertyName: jsii.String("MEMORYDB_CLUSTER_ENDPOINT"), Value: jsii.String(memoryDBEndpointURL), Name: jsii.String("MEMORYDB_USERNAME"), Value: user.UserName(), Name: jsii.String("MEMORYDB_PASSWORD"), Value: jsii.String(getMemoryDBPassword()) imageConfig := awsapprunner.CfnService_ImageConfigurationPropertyRuntimeEnvironmentVariables: appRunnerServiceEnvVarConfig, Port: jsii.String(getAppRunnerServicePort()) appDockerImage := awsecrassets.NewDockerImageAsset(stack, jsii.String("app-image"), &awsecrassets.DockerImageAssetPropsDirectory: jsii.String("../app/")) sourceConfig := awsapprunner.CfnService_SourceConfigurationPropertyAuthenticationConfiguration: ecrAccessRoleConfig, ImageRepository: awsapprunner.CfnService_ImageRepositoryPropertyImageIdentifier: jsii.String(*appDockerImage.ImageUri()), ImageRepositoryType: jsii.String(ecrImageRepositoryType), ImageConfiguration: imageConfig //snip... vpcConnector := awsapprunner.NewCfnVpcConnector(stack, jsii.String("apprunner-vpc-connector"), &awsapprunner.CfnVpcConnectorPropsSubnets: &subnetIDsForSubnetGroup, SecurityGroups: &[]*stringappRunnerVPCConnSecurityGroup.SecurityGroupId(), VpcConnectorName: jsii.String("demo-apprunner-vpc-connector")) networkConfig := awsapprunner.CfnService_NetworkConfigurationPropertyEgressConfiguration: awsapprunner.CfnService_EgressConfigurationPropertyEgressType: jsii.String(appRunnerEgressType), VpcConnectorArn: vpcConnector.AttrVpcConnectorArn() app := awsapprunner.NewCfnService(stack, jsii.String("apprunner-app"), &awsapprunner.CfnServicePropsSourceConfiguration: sourceConfig, ServiceName: jsii.String(getAppRunnerServiceName()), NetworkConfiguration: networkConfig) ...

Time to Wrap Up!

You deployed a Go application to App Runner using AWS CDK (along with the infra!). In the process, you also learned how to configure App Runner to integrate with MemoryDB for Redis using the VPC connector as well as a high-level overview of the CDK code for the entire solution.

That’s all for this blog. Stay tuned for more and Happy coding! 

[ad_2]