With AWS's introduction of fifth generation instance types, EC2 instances' block-devices' device-naming changes. In the first- through fourth-generation instance-types, block-devices' device-naming was based on the Xen Virtual Device (XVD) storage-driver. The resulting device node-names took the form /dev/xvdN
. With fifth-generation instance types, AWS switches to presenting virtual NVMe devices to the instance. These devices' node-naming take the form dev/nvmeXnY
(where X
is representative of the device's number on the virtual NVMe bus and Y
is representative of the partition on that device). In order for CloudFormation (CFn) templates for Linux-based EC2s to function correctly for older instance-families and fifth-generation instance families, the templates need to be have device-naming selection-logic added to them. Typically, this will mean additional code within a CFn template's Conditions{}
and Mappings{}
sections. Additionally, AWS::EC2::Instance
resource-types' Properties{}
section will likely need updates to their BlockDeviceMappings[]
list and UserData{}
sections.
This section can be use to add logic for parsing an instance-type delcaration (in this case, via an InstanceType
parameter declared in the CFn template's Parameters{}
section) to compute whether the CFn is being used to deploy a fifth-generation or an older instance-type. The below-shown logic is a negative-selector; however, rewriting as a positive-selector would be trivial:
"NotGenFive": {
"Fn::Not": [
{
"Fn::Or": [
{
"Fn::Equals": [
{ "Fn::Select": [
"0",
{ "Fn::Split": [ ".", { "Ref": "InstanceType" } ] }
]
},
"c5"
]
},
{
"Fn::Equals": [
{ "Fn::Select": [
"0",
{ "Fn::Split": [ ".", { "Ref": "InstanceType" } ] }
]
},
"m5"
]
},
{
"Fn::Equals": [
{ "Fn::Select": [
"0",
{ "Fn::Split": [ ".", { "Ref": "InstanceType" } ] }
]
},
"t3"
]
}
]
}
The above CFn-block uses CFn's Fn::Or
logic to allow for a "matches any" overall evaluation to be performed. In the above, there are three subordinate evaluations: one that checks if an instance is a "c5" instance-family; one that checks if an instance is an "m5" instance-family; and, finally, one that checks if an instance is a "t3" instance-family. As new fifth-generation (and presumably later) instance-type families are added, further match-blocks can be trivially added.
Each subordinate-evaluation uses CFn's Fn::Equals
logic. This logic compares two elements — in these blocks' cases, strings, and checks to see if they are matched. If the compared strings match, the block returns a true
condition up to the main, Fn::Or
evaluation.
The strings compared in each of the Fn::Equals
are a static value — c5
, m5
and t3
— compared to a derived-value.
In the above, CFn's Fn::Select
and Fn::Split
are used to derive the value:
- The
Fn::Split
is used to take the value of theInstanceType
parameter — typically something likec5.xlarge
,t3.micro
, etc. — and create a list of values. TheFn::Split
uses the.
as its split-delimeter. In the case of aInstanceType
string-value ofm5.2xlarge
, this creates a two-item list ofm5
and2xlarge
. - The
Fn::Select
is used to extract element zero from the list created by theFn::Split
— per the preceding bullet, this would extract the string-valuem5
.
Given that the derived-value of m5
equals that static-value of m5
the second Fn::Equals
block will return a true
condition up to the main, Fn::Or
evaluation.
The mappings section contains value-mappings that can be used elsewhere in the CFn template. In this snippet, only device-name mappings are defined:
"InstanceTypeCapabilities": {
"IsGenFive": {
"ExternDeviceName": "/dev/xvdf",
"InternDeviceName": "/dev/nvme1n1"
},
"PreGenFive": {
"ExternDeviceName": "/dev/xvdf",
"InternDeviceName": "/dev/xvdf"
}
}
At the AWS (cloud) layer, the (external) device-name will remain a /dev/xvdN
name, regardless of instance type. Inside the Linux-based instance, it will be either a /dev/xvdN
or /dev/nvmeXpY
name depending on instance-type. The previous Conditions{}
will be used elsewhere in the template to pull an appropriate value from the previous Mappings
.
Each CFn-managed Resource has a Properties{}
section. This section defines the deployment-behavior for the managed-resource. For EC2 resource-types, adding the compatibility-logic is done in two sub-sections of the Properties{}
section: the BlockDeviceMappings[]
list and the UserData{}
section.
The BlockDeviceMappings[]
list is list where each element in the list is a ditctionary (a data structure type). Each dictionary defines the storage-layout of an EBS-volume attached to the CFn-managed EC2 instance. Each dictionary contains two main elements:
- The
DeviceName
parameter - the value of this parameter defines the EBS volume's AWS-level attachment-point to the EC2 instance - The
Ebs
sub-dictionary that defines the characteristics of the attached EBS
The sub-dictionary defines the attached EBS's characteristics by three parameter-values:
- The
DeleteOnTermination
selects whether the EBS should be deleted when the attached-to EC2 insance is terminated - The
VolumeSize
selects the size of the EBS volume - The
VolumeType
selects the type of EBS volume to create/attach
"BlockDeviceMappings": [
{
"DeviceName": "/dev/sda1",
"Ebs": {
"DeleteOnTermination": true,
"VolumeSize": { "Ref": "RootVolumeSize" },
"VolumeType": "gp2"
}
},
{
"Fn::If": [
"CreateAppVolume",
{
"DeviceName": {
"Fn::If": [
"NotGenFive",
{
"Fn::FindInMap": [
"InstanceTypeCapabilities",
"PreGenFive",
"ExternDeviceName"
]
},
{
"Fn::FindInMap": [
"InstanceTypeCapabilities",
"IsGenFive",
"ExternDeviceName"
]
}
]
},
"Ebs": {
"DeleteOnTermination": "true",
"VolumeSize": { "Ref": "AppVolumeSize" },
"VolumeType": { "Ref": "AppVolumeType" }
}
},
{ "Ref": "AWS::NoValue" }
]
}
],
In the above block:
- The first dictionary in the list defines the attributes of the EBS attached at
/dev/sda1
. This is the EC2's boot disk. The EBS is configured: to delete on termination of the EC2; be provisioned at a size defined by the template-user via theRootVolumeSize
parameter; will be of the generic SSD EBS-type (gp2
). - The second dictionary in the list is conditionally defined by layered conditions:
- If the
CreateAppVolume
condition evaluates totrue
, defines a secondary EBS.- The
DeviceName
parameter value is determined by how theNotGenFive
condition is evaluated:- If
NotGenFive
evaulates totrue
, thePreGenFive
value forExternDeviceName
is extracted from theInstanceTypeCapabilities
mapping (from the previously discussedMappings{}
section). - If
NotGenFive
evaulates tofalse
, theIsGenFive
value forExternDeviceName
is extracted from theInstanceTypeCapabilities
mapping (from the previously discussedMappings{}
section).
- If
- The
VolumeSize
value is set based on the value of theAppVolumeSize
parameter - The
VolumeType
value is set based on the value of theAppVolumeType
parameter
- The
- If the
CreateAppVolume
condition evaluates tofalse
, the second dictionary becomes null/undefined (rendering theBlockDeviceMapping
list a one-element list)
- If the
The UserData section offers several opportunities for executing actions around the attached EBS volume(s):
cloud-config
operations:growpart
Directive: If supplied a list of (possibly) attached devices,cloud-config
will see if any listed-device nodes are available and whether they can be grown. If growable devices exist,growpart
will extend the the device (partition) onto the avialble contiguous space on the disk. If now growable devices exist, thegrowpart
action will simply exit0
, allowing the othercloud-config
actions to occur.bootcmd
directive: This directive allows the execution of configuration commnds early incloud-config
's operation. Anything that only might exist for modification needs to be placed inside a conditional-evaluation block (anFn::If
structure referencing the template'sConditions{}
section) to prevent an error-abort.cloud-init-per
directive attempts to create anext4
filesystem onto a condition-checked device: the subordinateFn::If
blocks evaluate theNotGenFive
conditional to extract anInternDeviceName
value from the appropriatePreGenFive
orIsGenFive
mapping.mounts
directive attempts to add an appropriate entry to the EC2's/etc/fstab
file using a condition-checked device-name (using the same logic used by thecloud-init-per
directive).
- Scripted operations: These can be simple or complex, BASH-scripted actions that execute at first boot. The block:
Looks for LVM PVs to resize. This block is necessary/desirable on instances with LVM'ed boot volumes that have been grown but not directly related to instance-type compatibility. It is simply provided as storage-related example of scripted operations.if [[ -x $( which pvs ) ]] then LVMPVS=($(pvs --noheadings -o pv_name)) for PV in "${LVMPVS[@]}" do pvresize "${PV}" done fi