IaCの汎用化について(その2)
Shigeyukiです。
前回に続き、ブームであるIaCに関してこれからはじめる人向けに、重要となるIaCの汎用化について解説してみたいと思います。
前回は以下ブログを参照。
その1)基礎編 では、テンプレートを抽象化することでテンプレートが汎用化され、保守性が向上することについて解説しました。
今回は、中級編としてテンプレートを分割することによる汎用性について解説します。
テンプレートを分割する目的
いくつかのシステムで同じ設定をもつインフラをIaCコードで管理すると、類似したテンプレートコードが量産され、システムの欠陥が発覚した場合や機能改修する場合に、修正の手間やテンプレートの管理に労力が必要となります。
そこで、テンプレートの「再利用しやすい=汎用性が高い」状態でテンプレートを分割して定義することで、IaCの共有化が図りやすくなったり、品質の向上・メンテナンスコストの削減につながります。
特にテンプレートの規模が大きくなったり、部分的に類似したシステムが増えてくるとそうしたメリットは大きくなります。
テンプレートを分割するための検討事項
テンプレートを再利用しやすく分割する場合の検討事項として、以下を挙げます。
検討事項
① ライフサイクル:1つのテンプレートに異なるライフサイクルをもつリソースを定義しないように分割を考える。
② レイヤー別:アプリ層、セキュリティ層、ネットワーク層の各層の単位で分割を考える。
③ ステート別:ステートレスなリソースとステートフルなリソースで分割を考える。
④ 補足(汎用性):インフラ設定を限定的にさせないように、リソースの各プロパティのデフォルト値を明確し、汎用性を持たせたいプロパティ値はパラメタ化する。
①② ライフサイクル・レイヤー別からみたテンプレート分割
ライフサイクルとレイヤー別は共に類似した特性があり、原則、レイヤー別にテンプレートを分けることで、必然的にライフサイクルの特性で分割することとなる。
- ネットワーク層:更新頻度は一番低く、インフラ構築後はほとんど更新されない。
- セキュリティ層:セキュリティ・権限周りの変更に限り更新するため、更新頻度としては低い。
- アプリケーション層:業務仕様の変更など高頻度で更新される。
③ ステート別からみたテンプレート分割
アプリケーション層に該当するリソースをステートフルとステートレスを基準にテンプレートを分割すると再利用性が高くなります。
④ 補足(汎用性)
インフラ設定を限定的にさせないように、リソースの各プロパティのデフォルト値を明確し、汎用性を持たせたいプロパティ値はパラメタ化する。
テンプレートを再利用しやすくするために、拡張させたいプロパティ値はパラメタすることで、分割されたテンプレートがより再利用しやすくなります。
注意
テンプレートを細かく分割すると返って、インフラ管理が煩雑となってしまうデメリットが大きくなるため、検討事項は必要に応じて実施する必要があります。
テンプレート分割したイメージ
検討事項で上げた考慮ポイントを踏まえ、EC2+RDS構成のシステムに対するテンプレート分割したイメージは以下のようになります。
実装イメージは以下となります。
親スタックに対するテンプレート「cfn-abcsystem.yml」がこちらになります。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
VPCRange:
Type: String
Description: "VPC Subnet Range"
PublicSubnetRangeA:
Type: String
Description: "Public SubnetA Range"
PublicSubnetRangeB:
Type: String
Description: "Public SubnetB Range"
PrivateSubnetRangeA:
Type: String
Description: "Private SubnetA Range"
PrivateSubnetRangeB:
Type: String
Description: "Private SubnetB Range"
DBUsername:
Type: String
Description: "Db User Name"
MultiAZFlg:
Type: String
Description: "Multi Az true or false String"
MySQLMajorNo:
Type: String
Description: "Major No of MySQL"
MySQLMijorNo:
Type: String
Description: "Minor No of MySQL"
Ec2InstanceType:
Type: String
Description: "EC2 Instance Type"
DBInstanceType:
Type: String
Description: "EC2 Instance Type"
Resources:
VpcStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./cfn-split-vpc.yml
Parameters:
NestVPCRange: !Ref VPCRange
PublicSubnetRangeA: !Ref PublicSubnetRangeA
PublicSubnetRangeB: !Ref PublicSubnetRangeB
PrivateSubnetRangeA: !Ref PrivateSubnetRangeA
PrivateSubnetRangeB: !Ref PrivateSubnetRangeB
VpcSecurityStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./cfn-split-security.yml
Parameters:
TargetVpc: !GetAtt VpcStack.Outputs.VpcId
Ec2Stack1:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./cfn-split-app.yml
Parameters:
EC2InstanceType: !Ref Ec2InstanceType
DeploySubnetId: !GetAtt VpcStack.Outputs.PublicSubnetA
InstanceName: NestedStackEC2-1
SecurityGroupId: !GetAtt VpcSecurityStack.Outputs.EC2SecurityGroupId
AttachRoleId: !GetAtt VpcSecurityStack.Outputs.EC2RoleId
Ec2Stack2:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./cfn-split-app.yml
Parameters:
EC2InstanceType: !Ref Ec2InstanceType
DeploySubnetId: !GetAtt VpcStack.Outputs.PublicSubnetB
InstanceName: NestedStackEC2-2
SecurityGroupId: !GetAtt VpcSecurityStack.Outputs.EC2SecurityGroupId
AttachRoleId: !GetAtt VpcSecurityStack.Outputs.EC2RoleId
RdsInstance:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: ./cfn-split-db.yml
Parameters:
RDSSecurityGroupId: !GetAtt VpcSecurityStack.Outputs.RDSSecurityGroupId
DbAccessSecurityGroupId: !GetAtt VpcSecurityStack.Outputs.EC2SecurityGroupId
DBName: "MyRDS"
DBClass: !Ref DBInstanceType
DBAllocatedStorage: 7
DBUsername: !Ref DBUsername
MultiAZ: !Ref MultiAZFlg
MySQLMajorVersion: !Ref MySQLMajorNo
MySQLMinorVersion: !Ref MySQLMijorNo
DBSubnet1: !GetAtt VpcStack.Outputs.PrivateSubnetA
DBSubnet2: !GetAtt VpcStack.Outputs.PrivateSubnetB
こちらがパラメータファイルとなり、抽象化されたテンプレートに対して、リソースの設定を付与するものとなります。
[
{
"ParameterKey": "PrivateSubnetRangeA",
"ParameterValue": "10.0.2.0/24"
},
{
"ParameterKey": "PrivateSubnetRangeB",
"ParameterValue": "10.0.4.0/24"
},
{
"ParameterKey": "PublicSubnetRangeA",
"ParameterValue": "10.0.1.0/24"
},
{
"ParameterKey": "PublicSubnetRangeB",
"ParameterValue": "10.0.11.0/24"
},
{
"ParameterKey": "VPCRange",
"ParameterValue": "10.0.0.0/16"
},
{
"ParameterKey": "DBUsername",
"ParameterValue": "admin"
},
{
"ParameterKey": "MultiAZFlg",
"ParameterValue": "true"
},
{
"ParameterKey": "MySQLMajorNo",
"ParameterValue": "8.0"
},
{
"ParameterKey": "MySQLMijorNo",
"ParameterValue": "35"
},
{
"ParameterKey": "Ec2InstanceType",
"ParameterValue": "t2.micro"
},
{
"ParameterKey": "DBInstanceType",
"ParameterValue": "db.m5d.large"
}
]
子スタック(ネットワークリソース)に対するテンプレート「cfn-abcsystem-vpc.yml」がこちらになります。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
NestVPCRange:
Type: String
PublicSubnetRangeA:
Type: String
PublicSubnetRangeB:
Type: String
PrivateSubnetRangeA:
Type: String
PrivateSubnetRangeB:
Type: String
Resources:
EC2VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref NestVPCRange
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
Tags:
- Key: "Name"
Value: "cf_VPC"
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref EC2VPC
CidrBlock: !Ref PublicSubnetRangeA
AvailabilityZone:
!Select
- 0
- Fn::GetAZs: !Ref AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: "Name"
Value: "cf_PublicSubnet"
PublicSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref EC2VPC
CidrBlock: !Ref PublicSubnetRangeB
AvailabilityZone:
!Select
- 2
- Fn::GetAZs: !Ref AWS::Region
MapPublicIpOnLaunch: true
Tags:
- Key: "Name"
Value: "cf_PublicSubnet"
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref EC2VPC
CidrBlock: !Ref PrivateSubnetRangeA
AvailabilityZone:
!Select
- 0
- Fn::GetAZs: !Ref AWS::Region
MapPublicIpOnLaunch: false
Tags:
- Key: "Name"
Value: "cfn_PrivateSubnet"
PrivateSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref EC2VPC
CidrBlock: !Ref PrivateSubnetRangeB
AvailabilityZone:
!Select
- 2
- Fn::GetAZs: !Ref AWS::Region
MapPublicIpOnLaunch: false
Tags:
- Key: "Name"
Value: "cfn_PrivateSubnet"
PublicRouteIngress:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EC2VPC
PublicRouteIngressAssociation1A:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteIngress
SubnetId: !Ref PublicSubnetA
PublicRouteIngressAssociation1B:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteIngress
SubnetId: !Ref PublicSubnetB
Igw:
Type: AWS::EC2::InternetGateway
VpcIgwAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref EC2VPC
InternetGatewayId: !Ref Igw
RouteIngressDefault:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteIngress
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref Igw
DependsOn:
- VpcIgwAttachment
Outputs:
VpcId:
Value: !Ref EC2VPC
PublicSubnetA:
Value: !Ref PublicSubnetA
PublicSubnetB:
Value: !Ref PublicSubnetB
PrivateSubnetA:
Value: !Ref PrivateSubnetA
PrivateSubnetB:
Value: !Ref PrivateSubnetB
子スタック(セキュリティリソース)に対するテンプレート「cfn-abcsystem-security.yml」がこちらになります。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
TargetVpc:
Type: AWS::EC2::VPC::Id
Description: "Target VPC"
Resources:
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "EC2 SG"
GroupName: !Sub ${AWS::StackName}-ec2-sg
VpcId: !Ref TargetVpc
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-ec2-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
RDSSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref TargetVpc
GroupName: !Sub "${AWS::StackName}-rds-sg"
GroupDescription: "-"
Tags:
- Key: "Name"
Value: !Sub "${AWS::StackName}-rds-sg"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
EC2Role:
Type: AWS::IAM::Role
Properties:
Path: "/"
RoleName: !Sub ${AWS::StackName}-ec2-role
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-ec2-role
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- sts:AssumeRole
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
Outputs:
EC2SecurityGroupId:
Value: !Ref EC2SecurityGroup
EC2RoleId:
Value: !Ref EC2Role
RDSSecurityGroupId:
Value: !Ref RDSSecurityGroup
子スタック(アプリリソース)に対するテンプレート「cfn-abcsystem-app.yml」がこちらになります。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
DeploySubnetId:
Type: AWS::EC2::Subnet::Id
LatestLinuxAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
InstanceName:
Type: String
SecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
EC2InstanceType:
Type: String
AllowedValues:
- t2.micro
- m1.small
- m1.large
AttachRoleId:
Type: String
Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestLinuxAmiId
InstanceType: !Ref EC2InstanceType
IamInstanceProfile: !Ref EC2InstanceProfile
NetworkInterfaces:
-
SubnetId: !Ref DeploySubnetId
GroupSet:
- !Ref SecurityGroupId
DeviceIndex: 0
Tags:
- Key: Name
Value: !Ref InstanceName
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: "/"
Roles:
- !Ref AttachRoleId
InstanceProfileName: !Sub ${AWS::StackName}-ec2-profile
子スタック(DBリソース)に対するテンプレート「cfn-abcsystem-db.yml」がこちらになります。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
RDSSecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
DbAccessSecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
DBName:
Type: String
DBClass:
Type: String
Default: db.m5d.large
AllowedValues:
- db.m5d.large
- db.m5d.xlarge
- db.m5d.2xlarge
DBAllocatedStorage:
Type: Number
Description: The size of the database(Gb)
Default: 5
MinValue: 5
MaxValue: 1024
DBUsername:
Type: String
DBSubnet1:
Type: String
DBSubnet2:
Type: String
MySQLMajorVersion:
Type: String
Default: "8.0"
MySQLMinorVersion:
Type: String
Default: "35"
DBEngine:
Type: String
Default: MySQL
DBInstanceStorageSize:
Type: String
Default: "30"
DBInstanceStorageType:
Type: String
Default: "gp2"
DBMasterUserName:
Type: String
Default: "admin"
NoEcho: true
MinLength: 5
MaxLength: 16
AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
ConstraintDescription: "must begin with a letter and contain only alphanumeric characters."
MultiAZ:
Type: String
AllowedValues: [ "true", "false" ]
Resources:
RDSSecret: # ここでRDSのパスワードとなるランダム文字列を生成
Type: "AWS::SecretsManager::Secret"
Properties:
Name: "RDSSecretInfo"
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateStringKey: "password"
PasswordLength: 16
ExcludeCharacters: '"@/\'
DBSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupName: !Sub "${DBName}-subnet"
DBSubnetGroupDescription: "-"
SubnetIds:
- !Ref DBSubnet1
- !Ref DBSubnet2
DBInstance:
Type: "AWS::RDS::DBInstance"
Properties:
DBInstanceIdentifier: !Sub "${DBName}"
Engine: !Ref DBEngine
EngineVersion: !Sub "${MySQLMajorVersion}.${MySQLMinorVersion}"
DBInstanceClass: !Ref DBClass
AllocatedStorage: !Ref DBInstanceStorageSize
StorageType: !Ref DBInstanceStorageType
DBName: !Ref DBName
MasterUsername: !Ref DBMasterUserName
MasterUserPassword: !Sub '{{resolve:secretsmanager:${RDSSecret}:SecretString:password}}'
DBSubnetGroupName: !Ref DBSubnetGroup
PubliclyAccessible: false
MultiAZ: !Ref MultiAZ
AutoMinorVersionUpgrade: false
DBParameterGroupName: !Ref DBParameterGroup
VPCSecurityGroups:
- !Ref RDSSecurityGroupId
CopyTagsToSnapshot: true
BackupRetentionPeriod: 7
Tags:
- Key: "Name"
Value: !Sub "${DBName}"
DeletionPolicy: "Delete"
DBParameterGroup:
Type: "AWS::RDS::DBParameterGroup"
Properties:
Family: !Sub "MySQL${MySQLMajorVersion}"
Description: !Sub "${DBName}-parm"
発展
分割されたテンプレートファイルは、別システムで流用することが可能である。
以下には、システムAで構築したDBスタックを設定値違いで、システムBにもDBスタックを同一テンプレートを利用するイメージとなります。
ここでのポイントは、システムAとシステムBで個別にシステム固有の設定値をパラメータファイルとして管理することで、同一DBテンプレートに対して、動的にリソース設定を切り替えられるようになる。
まとめ
IaC汎用化について、CloudFormationを例にしてテンプレート分割について解説してみました。
再利用しやすい状態でテンプレート分割することで、テンプレートの汎用性が高くなり、IaCの共有化・品質の向上・メンテナンスコストの削減につながります。
巨大なテンプレート1つとするのではなく、保守性が維持させるためにもテンプレートを分割してみてはいかがでしょうか。
その3)上級編では、IaCによるインフラ分割について解説します。
この記事がCloudFormationによるIaCの品質向上につながると幸いです。