One of the major IT trends in recent years has been the increase in automation and a simultaneous decrease in the need for manual intervention. Another big hit is 'microservices'. Recently, my team came up with the idea of utilizing these key technological developments to build a flawless infrastructure that can be continuously improved and monitored with minimal manual effort. While numerous IT groups are still dependant on manual steps, custom scripts and obsolete tools, the DevOps team at QBurst took to automating infrastructure management. With this, we were able to oversee the infrastructure, reduce errors, and perform seamless deployments and monitoring. In this blog, I would like to introduce the concept of Infrastructure as Code (IaC) using the example of deploying a microservice to Amazon ECS.

Deploying Microservices to Amazon ECS with IaC

Amazon Web Services, pioneer in Cloud Computing, offers a lot of platforms to build and deploy applications. Amazon ECS is one such service and can be used to deploy microservices using Docker. If you are new to microservices and Docker, this could be right time to get started!

Docker Image, ECR Repository, Task Definitions

For deploying a microservice to Amazon ECS, you will have to create a Docker image of your application. Once the image is ready, you can push it to Amazon ECR, which is a container registry that manages docker images. To do this, you need to create a repository in ECR and then follow the ‘push commands’ mentioned there. After pushing the image to ECR repository, you can use AWS CloudFormation to create a task definition and service template (the template is provided later in this post). Task Definition: It is a text file that describes the containers that form your application. Service: A service maintains the desired count of simultaneous tasks in an ECS Cluster. Given below is a simple architecture diagram depicting how to deploy services to Amazon ECS using AWS CloudFormation. architecture diagram for deploying microservices on AWS using IaC As shown in the diagram, the DevOps engineer writes the AWS CloudFormation templates (IaC) for execution. AWS CloudFormation validates the template and creates all the resources mentioned in the template.

Deploying Infrastructure as Code with AWS CloudFormation

AWS CloudFormation is a great tool to manage your infrastructure (as code) in AWS Cloud. CloudFormation allows you to create templates of the infrastructure and applications that you want to run on AWS. You can specify the desired state of your infrastructure through the templates and CloudFormation takes care of building them for you. You do not have to worry about the order of creation of the resources or failure modes if any. The basic workflow will be:
  • Design a solution for the business problem at hand.
  • Write the application code to implement the business logic.
  • Write the infrastructure templates to host the application.
  • Create the stacks, which are a collection of resources.
  • Use hooks to deploy application packages on top of the stacks of infrastructure resources.
With the stack created, your infrastructure and hosted application is ready to serve customers. You can continue to iterate on it as required. Here is a sample architecture diagram of AWS infrastructure created using IaC that can be used to deploy microservices. AWS infrastructure created using IaC All the AWS resources shown in the diagram can be set up and configured using IaC.  Let’s start with creating the templates for infrastructure stack using AWS CloudFormation

Creating Template for Infrastructure

The infrastructure stack will create all the necessary AWS resources required to run an ECS cluster, including VPC, subnets, security groups, and so on. Before creating the template, let us familiarize ourselves with some common terms used with CloudFormation syntax.
  • Resources: The resources block contains all the AWS resources that you need to set up.
  • Type: For each resource, the 'Type' specifies the kind of AWS resource you are creating.
  • Properties: This specifies the attributes of each resource and varies for each.
    • For example, in the ‘VPC’ block from below template, I have used the attribute 'CidrBlock' to specify the CIDR block that will be used for this VPC.
    • Tags attribute can be used to provide some unique tag for this resource.
The complete syntax for each resource is available in the AWS documentation. The stack for creating infrastructure is given below.
AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS Cloudformation Template to create the Infrastructure'
Resources:
 VPC:
	Type: "AWS::EC2::VPC"
	Properties:
 		CidrBlock: 10.0.0.0/16
 		Tags:
   		- Key: Name
     	  Value: 'vpc'
 PublicSubnetAZ1:
	Type: "AWS::EC2::Subnet"
	Properties:
 		AvailabilityZone:
   		Fn::Select:
     		- 0
     		- Fn::GetAZs: ""
 		CidrBlock: 10.0.1.0/24
 		VpcId: !Ref 'VPC'
 		Tags:
   			- Key: Name
     		  Value: 'PublicSubnetAZ1'
 ECSInternetGateway:
	Type: AWS::EC2::InternetGateway
	Properties:
 		Tags:
   			- Key: Name
     		  Value: 'Internet_Gateway'
 ECSIGVPCAssociation:
	Type: AWS::EC2::VPCGatewayAttachment
	Properties:
 		InternetGatewayId: !Ref 'ECSInternetGateway'
 		VpcId: !Ref 'VPC'
 ECSRouteTable:
	Type: AWS::EC2::RouteTable
	Properties:
 		VpcId: !Ref 'VPC'
 ECSRoute:
	Type: AWS::EC2::Route
	Properties:
 		RouteTableId: !Ref 'ECSRouteTable'
 		DestinationCidrBlock: 0.0.0.0/0
 		GatewayId: !Ref 'ECSInternetGateway'
		DependsOn:
			- ECSIGVPCAssociation
 RoutePublicSubnetAZ1Association:
	Type: AWS::EC2::SubnetRouteTableAssociation
	Properties:
 		RouteTableId: !Ref 'ECSRouteTable'
 		SubnetId: !Ref 'PublicSubnetAZ1'
 PublicSecurityGroup:
	Type: AWS::EC2::SecurityGroup
	Properties:
 		GroupDescription: Public Security Group
 		VpcId: !Ref 'VPC'
 			Tags:
   				- Key: Name
     			  Value: 'Blog'
 PublicSecurityGroupHTTPinbound:
	Type: AWS::EC2::SecurityGroupIngress
	Properties:
 		GroupId: !Ref 'PublicSecurityGroup'
 		IpProtocol: tcp
 		FromPort: '80'
 		ToPort: '80'
 		CidrIp: 0.0.0.0/0
 PublicSecurityGroupSSHinbound:
	Type: AWS::EC2::SecurityGroupIngress
	Properties:
 		GroupId: !Ref 'PublicSecurityGroup'
 		IpProtocol: tcp
 		FromPort: '22'
 		ToPort: '22'
 		CidrIp: 0.0.0.0/0

Creating Template for ECS Cluster

Once the infrastructure is ready, we can set up the ECS cluster. Given below is the template to create the ECS cluster and its agents (EC2 instances).
AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS Cloudformation Template to create the Infrastructure'
Resources:
 ECSCluster:
	Type: AWS::ECS::Cluster
	Properties:
		ClusterName: 'Sample-Cluster'
 EC2InstanceProfile:
	Type: AWS::IAM::InstanceProfile
	Properties:
 		Path: /
 		Roles: [!Ref 'EC2Role']
 ECSAutoScalingGroup:
	Type: AWS::AutoScaling::AutoScalingGroup
	Properties:
 		VPCZoneIdentifier:
   		 - subnet-*****  // Provide the subnet_id created using Infra.yaml
 		LaunchConfigurationName: !Ref 'ECSAutoscalingLC'
 		MinSize: '1'
 		MaxSize: '2'
 		DesiredCapacity: '1'
 ECSAutoscalingLC:
	Type: AWS::AutoScaling::LaunchConfiguration
	Properties:
 		AssociatePublicIpAddress: true
 		ImageId: 'ami-b743bed1'
 		SecurityGroups:
   		 - sg-***** //The security group id created using Infra.yaml
 		InstanceType: 't2.micro'
 		IamInstanceProfile: !Ref 'EC2InstanceProfile'
 		KeyName: 'sample' // Provide a Key-pair name
 		UserData:
   			Fn::Base64: !Sub |
     		 #!/bin/bash -xe
     		 echo ECS_CLUSTER=Blog-iac-test-1 >> /etc/ecs/ecs.config
 EC2Role:
	Type: AWS::IAM::Role
	Properties:
 		AssumeRolePolicyDocument:
   			Statement:
   			- Effect: Allow
     		Principal:
       		 Service: [ec2.amazonaws.com]
     		Action: ['sts:AssumeRole']
 		Path: /
 ECSServicePolicy:
	Type: "AWS::IAM::Policy"
	Properties:
 		PolicyName: "root"
 		PolicyDocument:
   		 Version: "2012-10-17"
   		 Statement:
     		- Effect: Allow
       		  Action: ['ecs:*', 'logs:*', 'ecr:*', 's3:*']
       		  Resource: '*'
 		Roles: [!Ref 'EC2Role']

Creating Template for Service & Task Definition

After setting up the infrastructure and ECS cluster, the microservice can be deployed to the cluster by creating the task definition and service. You can use the template given below to define the task and deploy the service.
AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS Cloudformation Template to create the Infrastructure'
Resources:
 TaskDefinition:
	Type: AWS::ECS::TaskDefinition
	Properties:
 	Family: sample-task
 	ContainerDefinitions:
 	- Name: Sample-Server
   	  Cpu: '512'
   	  Essential: 'true'
   	  Image:
209********.dkr.ecr.ap-northeast-1.amazonaws.com/go-app:latest' //Provide the ECR Repository URI:version you created earlier
   	  Memory: '512'
   	  PortMappings:
   	  - ContainerPort: 9090
         HostPort: 80
 Service:
	Type: AWS::ECS::Service
	Properties:
 		Cluster: 'Sample-Cluster'
 		DesiredCount: '1'
 		TaskDefinition:
   			Ref: TaskDefinition
You can either run the stacks separately or run as a single stack by combining the resources block of stacks together. While running as a separate stack, make sure you edit the values of the resources (I have hardcoded them to reduce template complexity.). Once the stacks are up and running, you can view the services from ECS console. The service can be accessed using the public IP attached to the container. You can also include an ALB (Application Load Balancer) in the template to access the service.

Why Go With Infrastructure as Code?

Our team started to think about IaC when we had to re-create the infrastructure multiple times for testing and deployment. While it usually took hours to set up the entire infrastructure without any errors, using IaC only took us less than 30 minutes! You can imagine how much time we were able to save with this. I am listing out some of the benefits we enjoyed by moving to IaC. You can be sure that it’s  not limited to these few.
  • We could set up an environment for application deployment much faster.
  • The mean time to resolve issues was reduced drastically.
  • The same IaC scripts could be used to create development, testing, and production environments.
  • We could implement an efficient monitoring system and get alerted when something went wrong.
  • Changes to infrastructure could be done seamlessly, without breaking the system.
There are a lot of DevOps practices that could really change the pace of handling infrastructure and operations. Coding your infrastructure could be the first step towards this. Are you seeking guidance for moving your apps to AWS and automating your environment? QBurst can help! Visit our Cloud Services and DevOps pages to learn more.