Cloud Security Automation
上QQ阅读APP看书,第一时间看更新

Infrastructure as Code

Now, as you have already learned about the basic fundamentals of DevOps, let's start to understand it by example. As an example, I will use the CloudFormation template to describe how easy and fast it is to deploy solutions in AWS.

CloudFormation is an AWS service that is used to provision your IT Infrastructure as Code. Once it's coded, you can deploy it in any region, and it also gives you facility to customize it as per your own wishes.

As we can see in the following screenshot, CloudFormation gives you a very rich GUI where you can drag and drop the required components and establish relations between them, and in backend code is automatically generated in the JSON and YAML formats. Once you are done drawing using drag and drop, you save this as a template and start deployment:

In CloudFormation, we generate code in the JSON or YAML formats. JSON stands for JavaScript Object Notation, and YAML stands for Yet Another Markup Language. Both are very easy to understand and work on key-value form.

In the CloudFormation template, we have the following section:

  • Description: It describes the purpose of the template, and that is what it will do.
  • Metadata: It is defined as data about data. It's an optional section where we define some custom values.
  • Parameters: It is also an optional section in the CloudFormation template where we define the runtime parameters for the template. We use it in the Resources and Output section of code.
  • Mapping: Here, we map a key with associated values for conditional parameters.
  • Conditions: It defines how certain resources are created or assigned a value during the creation process or the update process.
  • Resources: The only mandatory component of the CloudFormation template; it defines what resource needs to be created.
  • Output: It also displays output about specified resources whenever you view your stack.

Now we understand the sections of the CloudFormation template and their functionality, it's time to see the example. In the preceding screenshot, we created a CloudFormation template using CloudFormation designer. Now, let's look at the anatomy of the code:

{
"Description": "AWS CloudFormation Template
EC2InstanceWithSecurityGroup",

In the preceding code, we have defined the description of this template, that is, what exactly it will do: 

    "Parameters": {
"KeyName": {
"Description": "Name of an existing EC2 KeyPair",
"Type": "AWS::EC2::KeyPair::KeyName"
},
"InstanceType": {
"Description": "WebServer EC2 instance type",
"Type": "String",
"Default": "t2.medium",
"AllowedValues": [
"t1.micro",
"t2.nano",
"t2.micro",
"t2.small",
"t2.medium",
"t2.large",
"m1.small",
"m1.medium",
" List of all the compute "
],
"ConstraintDescription": "must be a valid EC2 instance type."
},
"SSHLocation": {
"Description": "SSH Open for Public Network",
"Type": "String",
"Default": "0.0.0.0/0"
}
},

In the preceding code block, we have passed the parameter to the template.

In the parameter, we have passed the key-value pair for the instance, which is required for SSH-access (for Linux instances) and RDP access (for Windows instances). 

Then, we have passed the parameter to specify the instance type and default instance type. The allowed values for compute type specify that we can use any of the listed values for the InstanceType, but if we do not choose it will use the default value.

Now, as it's going to be a Linux instance, we have passed the SSHLocation parameter, which says by default SSH access is enabled from 0.0.0.0/0 (public internet).

Once we have completed the Parameters section, we will map the AMI to create the VMs. In the following code snippet, we have mapped the compute with AMI type and AMI types mapped with AMI ID in the AWS regions:

    "Mappings": {
"AWSInstanceType2Arch": {
"t1.micro": {
"Arch": "PV64"
},
"t2.nano": {
"Arch": "HVM64"
},
"t2.micro": {
"Arch": "HVM64"
},
"t2.small": {
"Arch": "HVM64"
},
"t2.medium": {
"Arch": "HVM64"
},
"t2.large": {
"Arch": "HVM64"
},
"m1.small": {
"Arch": "PV64"
},
"m1.medium": {
"Arch": "PV64"
},
"m1.large": {
"Arch": "PV64"
},
"m1.xlarge": {
"Arch": "PV64"
},
"m2.xlarge": {
"Arch": "PV64"
},
"m2.2xlarge": {
"Arch": "PV64"
},
"m2.4xlarge": {
"Arch": "PV64"
},
"m3.medium": {
"Arch": "HVM64"
},
"m3.large": {
"Arch": "HVM64"
},
"m3.xlarge": {
"Arch": "HVM64"
},
"m3.2xlarge": {
"Arch": "HVM64"
},
"m4.large": {
"Arch": "HVM64"
},
"m4.xlarge": {
"Arch": "HVM64"
}
},
"AWSRegionArch2AMI": {
"ap-southeast-1": {
"PV64": "ami-df9e4cbc",
"HVM64": "ami-a59b49c6",
"HVMG2": "ami-3e91ed5d"
},
"ap-southeast-2": {
"PV64": "ami-63351d00",
"HVM64":"ami-dc361ebf",
"HVMG2": "ami-84a142e6"
},
"ap-south-1": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-ffbdd790",
"HVMG2": "ami-25ffbe4a"
}
}
},

In the following code snippet, the deployed resource is defined. In the Resources section, we have specified the InstanceType and referred to it with the InstanceType defined in the Parameters section. Instance is defined with all of its properties InstanceType, SecurityGroups, KeyName, and ImageId. Then, we have specified the security group and referred to it as InstanceSecurityGroup. KeyName is referred to with the SSH KeyName section.

The SSH port and ingress rules are also defined in the InstanceSecurityGroup section:

    "Resources": {
"EC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": {
"Ref": "InstanceType"
},
"SecurityGroups": [
{
"Ref": "InstanceSecurityGroup"
}
],
"KeyName": {
"Ref": "KeyName"
},
"ImageId": {
"Fn::FindInMap": [
"AWSRegionArch2AMI",
{
"Ref": "AWS::Region"
},
{
"Fn::FindInMap": [
"AWSInstanceType2Arch",
{
"Ref": "InstanceType"
},
"Arch"
]
}
]
}
}
},
"InstanceSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Enable SSH access on port 22",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": {
"Ref": "SSHLocation"
}
}
]
}
}
},
}

Finally, we have defined the output values in the Outputs section:

"Outputs": {
"InstanceId": {
"Description": "InstanceId of the EC2 instance",
"Value": {
"Ref": "EC2Instance"
}
},
"AZ": {
"Description": "Availability Zone of EC2 instance",
"Value": {
"Fn::GetAtt": [
"EC2Instance",
"AvailabilityZone"
]
}
},
"PublicDNS": {
"Description": "Public DNSName of EC2 instance",
"Value": {
"Fn::GetAtt": [
"EC2Instance",
"PublicDnsName"
]
}
},
"PublicIP": {
"Description": "Public IP address of EC2 instance",
"Value": {
"Fn::GetAtt": [
"EC2Instance",
"PublicIp"
]
}
}
}

Here, we have specified all the details that we require as output in Fn:GetAtt. Once the CloudFormation template is executed, it will return InstanceID, AZ, PublicDNS, and PublicIP.

You can save the same JSON code and deploy it whenever you want, and it can also be updated whenever required.

This summarizes our examples for Infrastructure as Code.