CloudFormationでSSMパラメータストアを使用してみた。
2022-07-29
こんにちは、0371です。
CloudFormation
内で、パラメータストアを使用してみました。
これを行うことで、動的な値を有する変数の管理をAWSに委譲することができます。
parameterstoreを活用するメリット
まずはparameterstoreを使用しない
parameterstoreを使用しないVPC、EC2、RDSの構成を作成しましょう。
サブネットは、パブリック1、プライベート2の構成です。
EC2にはセッションマネージャーで接続し、RDSにはEC2を介して接続しにいきます。
テンプレートの作成にあたって、以下のリポジトリを参考にさせていただいております。
>okubo-t / aws-cloudformation
まずは、VPCの作成です。
parameterstore-vpc.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: "Project Name Prefix and Environment"
Parameters:
- ProjectName
- Env
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
ProjectName:
Type: String
Default: project
Description: Enter Project Name. defailt is project.
Env:
Type: String
Default: common
AllowedValues:
- common
- prd
- stg
- dev
Description: Enter common, prd, stg, dev. default is common.
# ------------------------------------------------------------#
# Mappings
# ------------------------------------------------------------#
Mappings:
Environments:
common:
"VPCCIDR": "10.1.0.0/16"
"PublicSubnetACIDR": "10.1.10.0/24"
"PrivateSubnetACIDR": "10.1.50.0/24"
"PrivateSubnetCCIDR": "10.1.60.0/24"
prd:
"VPCCIDR": "10.2.0.0/16"
"PublicSubnetACIDR": "10.2.10.0/24"
"PrivateSubnetACIDR": "10.2.50.0/24"
"PrivateSubnetCCIDR": "10.2.60.0/24"
stg:
"VPCCIDR": "10.3.0.0/16"
"PublicSubnetACIDR": "10.3.10.0/24"
"PrivateSubnetACIDR": "10.3.50.0/24"
"PrivateSubnetCCIDR": "10.3.60.0/24"
dev:
"VPCCIDR": "10.4.0.0/16"
"PublicSubnetACIDR": "10.4.10.0/24"
"PrivateSubnetACIDR": "10.4.50.0/24"
"PrivateSubnetCCIDR": "10.4.60.0/24"
# ------------------------------------------------------------#
# Conditions
# ------------------------------------------------------------#
Conditions:
IsApNorthEast1or3:
!Or [
!Equals [!Ref AWS::Region, "ap-northeast-1"],
!Equals [!Ref AWS::Region, "ap-northeast-3"],
]
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
InternetGateway:
Condition: IsApNorthEast1or3
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-igw"
VPC:
Condition: IsApNorthEast1or3
Type: AWS::EC2::VPC
Properties:
CidrBlock: !FindInMap [Environments, !Ref "Env", VPCCIDR]
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-vpc"
VPCGatewayAttachment:
Condition: IsApNorthEast1or3
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicRouteA:
Condition: IsApNorthEast1or3
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
RouteTableId: !Ref PublicRouteTableA
PublicRouteTableA:
Condition: IsApNorthEast1or3
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-PublicRouteTable-1a"
VpcId: !Ref VPC
PrivateRouteTableA:
Condition: IsApNorthEast1or3
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-PrivateRouteTable-1a"
VpcId: !Ref VPC
PrivateRouteTableC:
Condition: IsApNorthEast1or3
Type: AWS::EC2::RouteTable
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-PrivateRouteTable-1c"
VpcId: !Ref VPC
PublicSubnetA:
Condition: IsApNorthEast1or3
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !FindInMap [Environments, !Ref "Env", PublicSubnetACIDR]
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-Public-Subnet-1a"
VpcId: !Ref VPC
PrivateSubnetA:
Condition: IsApNorthEast1or3
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !FindInMap [Environments, !Ref "Env", PrivateSubnetACIDR]
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-Private-Subnet-1a"
VpcId: !Ref VPC
PrivateSubnetC:
Condition: IsApNorthEast1or3
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 1
- !GetAZs
Ref: AWS::Region
CidrBlock: !FindInMap [Environments, !Ref "Env", PrivateSubnetCCIDR]
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${Env}-Private-Subnet-1c"
VpcId: !Ref VPC
PublicSubnetRouteTableAAssociation:
Condition: IsApNorthEast1or3
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTableA
SubnetId: !Ref PublicSubnetA
PrivateSubnetRouteTableAAssociation:
Condition: IsApNorthEast1or3
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTableA
SubnetId: !Ref PrivateSubnetA
PrivateSubnetRouteTableCAssociation:
Condition: IsApNorthEast1or3
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTableC
SubnetId: !Ref PrivateSubnetC
EC2SecurityGroup:
Condition: IsApNorthEast1or3
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref VPC
GroupName: !Sub "${ProjectName}-ec2-sg"
GroupDescription: "-"
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-ec2-sg"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: 0.0.0.0/0
RDSSecurityGroup:
Condition: IsApNorthEast1or3
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref VPC
GroupName: !Sub "${ProjectName}-rds-sg"
GroupDescription: "-"
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-rds-sg"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: !FindInMap [Environments, !Ref "Env", VPCCIDR]
DBSubnetGroup:
Condition: IsApNorthEast1or3
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupName: !Sub "${ProjectName}-${Env}-rds-subnet"
DBSubnetGroupDescription: "-"
SubnetIds:
- !GetAtt PrivateSubnetA.SubnetId
- !GetAtt PrivateSubnetC.SubnetId
EC2EndpointSG:
Condition: IsApNorthEast1or3
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Bastion Endpoint Security Group"
VpcId: !GetAtt VPC.VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref EC2SecurityGroup
Tags:
- Key: Name
Value: !Sub "${ProjectName}-endpoint-bastion-sg"
SSMVPCEndpoint:
Condition: IsApNorthEast1or3
Type: "AWS::EC2::VPCEndpoint"
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'
VpcId: !GetAtt VPC.VpcId
SubnetIds:
- Fn::ImportValue: !Sub "${ProjectName}-${Env}-PrivateSubnetASubnetId"
SecurityGroupIds:
- !Ref EC2EndpointSG
SSMMessagesVPCEndpoint:
Condition: IsApNorthEast1or3
Type: "AWS::EC2::VPCEndpoint"
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'
VpcId: !GetAtt VPC.VpcId
SubnetIds:
- Fn::ImportValue: !Sub "${ProjectName}-${Env}-PrivateSubnetASubnetId"
SecurityGroupIds:
- !Ref EC2EndpointSG
EC2MessagesVPCEndpoint:
Condition: IsApNorthEast1or3
Type: "AWS::EC2::VPCEndpoint"
Properties:
VpcEndpointType: Interface
PrivateDnsEnabled: true
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages'
VpcId: !GetAtt VPC.VpcId
SubnetIds:
- Fn::ImportValue: !Sub "${ProjectName}-${Env}-PrivateSubnetASubnetId"
SecurityGroupIds:
- !Ref EC2EndpointSG
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
PublicSubnetAAvailabilityZone:
Condition: IsApNorthEast1or3
Value: !GetAtt PublicSubnetA.AvailabilityZone
Export:
Name: !Sub "${ProjectName}-${Env}-PublicSubnetAAvailabilityZone"
PublicSubnetAVpcId:
Condition: IsApNorthEast1or3
Value: !GetAtt PublicSubnetA.VpcId
Export:
Name: !Sub "${ProjectName}-${Env}-PublicSubnetAVpcId"
PublicSubnetASubnetId:
Condition: IsApNorthEast1or3
Value: !GetAtt PublicSubnetA.SubnetId
Export:
Name: !Sub "${ProjectName}-${Env}-PublicSubnetASubnetId"
PrivateSubnetAAvailabilityZone:
Condition: IsApNorthEast1or3
Value: !GetAtt PrivateSubnetA.AvailabilityZone
Export:
Name: !Sub "${ProjectName}-${Env}-PrivateSubnetAAvailabilityZone"
PrivateSubnetAVpcId:
Condition: IsApNorthEast1or3
Value: !GetAtt PrivateSubnetA.VpcId
Export:
Name: !Sub "${ProjectName}-${Env}-PrivateSubnetAVpcId"
PrivateSubnetASubnetId:
Condition: IsApNorthEast1or3
Value: !GetAtt PrivateSubnetA.SubnetId
Export:
Name: !Sub "${ProjectName}-${Env}-PrivateSubnetASubnetId"
VPCId:
Condition: IsApNorthEast1or3
Value: !GetAtt VPC.VpcId
Export:
Name: !Sub "${ProjectName}-${Env}-VpcId"
VPCCidrBlock:
Condition: IsApNorthEast1or3
Value: !GetAtt VPC.CidrBlock
Export:
Name: !Sub "${ProjectName}-${Env}-VPCCidrBlock"
EC2SecurityGroupGroupId:
Condition: IsApNorthEast1or3
Value: !GetAtt EC2SecurityGroup.GroupId
Export:
Name: !Sub "${ProjectName}-${Env}-EC2SecurityGroupGroupId"
RDSSecurityGroupGroupId:
Condition: IsApNorthEast1or3
Value: !GetAtt RDSSecurityGroup.GroupId
Export:
Name: !Sub "${ProjectName}-${Env}-RDSSecurityGroupGroupId"
つぎに、EC2、RDSです。
parameterstore-ec2-rds.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: "Project Name Prefix and Environment"
Parameters:
- ProjectName
- Env
-
Label:
default: "EC2Instance Configuration"
Parameters:
# - KeyPairName
- EC2InstanceAMI
- EC2InstanceInstanceType
- EC2InstanceVolumeType
- EC2InstanceVolumeSize
-
Label:
default: "RDS Configuration"
Parameters:
- DBInstanceName
- MySQLMajorVersion
- MySQLMinorVersion
- DBInstanceClass
- DBInstanceStorageSize
- DBInstanceStorageType
- DBName
- DBMasterUserName
- DBPassword
- MultiAZ
ParameterLabels:
# KeyPairName:
# default: "KeyPairName"
EC2InstanceAMI:
default: "EC2 AMI"
EC2InstanceInstanceType:
default: "EC2 InstanceType"
EC2InstanceVolumeType:
default: "EC2 VolumeType"
EC2InstanceVolumeSize:
default: "EC2 VolumeSize"
DBInstanceName:
default: "DBInstanceName"
MySQLMajorVersion:
default: "MySQLMajorVersion"
MySQLMinorVersion:
default: "MySQLMinorVersion"
DBInstanceClass:
default: "DBInstanceClass"
DBInstanceStorageSize:
default: "DBInstanceStorageSize"
DBInstanceStorageType:
default: "DBInstanceStorageType"
DBName:
default: "DBName"
DBMasterUserName:
default: "DBUserName"
DBPassword:
default: "DBPassword"
MultiAZ:
default: "MultiAZ"
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
ProjectName:
Type: String
Default: project
Description: Enter Project Name. defailt is project.
Env:
Type: String
Default: common
AllowedValues:
- common
- prd
- stg
- dev
Description: Enter common, prd, stg, dev. default is common.
EC2InstanceAMI:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
EC2InstanceInstanceType:
Type: String
Default: "t2.micro"
EC2InstanceVolumeType:
Type: String
Default: "gp2"
EC2InstanceVolumeSize:
Type: String
Default: "30"
DBInstanceName:
Type: String
Default: "rds"
MySQLMajorVersion:
Type: String
Default: "8.0"
AllowedValues: [ "5.7", "8.0" ]
MySQLMinorVersion:
Type: String
Default: "25"
AllowedValues: [ "22", "25" ]
DBInstanceClass:
Type: String
Default: "db.m4.large"
DBInstanceStorageSize:
Type: String
Default: "30"
DBInstanceStorageType:
Type: String
Default: "gp2"
DBName:
Type: String
Default: "db"
DBMasterUserName:
Type: String
Default: "dbuser"
NoEcho: true
MinLength: 1
MaxLength: 16
AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
ConstraintDescription: "must begin with a letter and contain only alphanumeric characters."
DBPassword:
Default: "dbpassword"
NoEcho: true
Type: String
MinLength: 8
MaxLength: 41
AllowedPattern: "[a-zA-Z0-9]*"
ConstraintDescription: "must contain only alphanumeric characters."
MultiAZ:
Default: "false"
Type: String
AllowedValues: [ "true", "false" ]
# ------------------------------------------------------------#
# Mappings
# ------------------------------------------------------------#
# ------------------------------------------------------------#
# Conditions
# ------------------------------------------------------------#
Conditions:
IsApNorthEast1or3:
!Or [
!Equals [!Ref AWS::Region, "ap-northeast-1"],
!Equals [!Ref AWS::Region, "ap-northeast-3"],
]
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
EC2IAMRole:
Condition: IsApNorthEast1or3
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub "${ProjectName}-${Env}-ec2-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
- "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
EC2InstanceProfile:
Condition: IsApNorthEast1or3
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
- Ref: EC2IAMRole
InstanceProfileName: !Sub "${ProjectName}-${Env}-ec2-profile"
EC2Instance:
Condition: IsApNorthEast1or3
Type: "AWS::EC2::Instance"
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-ec2"
ImageId: !Ref EC2InstanceAMI
InstanceType: !Ref EC2InstanceInstanceType
# KeyName: !Ref KeyPairName
IamInstanceProfile: !Ref EC2InstanceProfile
DisableApiTermination: false
EbsOptimized: false
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
DeleteOnTermination: true
VolumeType: !Ref EC2InstanceVolumeType
VolumeSize: !Ref EC2InstanceVolumeSize
SecurityGroupIds:
- Fn::ImportValue: !Sub "${ProjectName}-${Env}-EC2SecurityGroupGroupId"
SubnetId:
Fn::ImportValue: !Sub "${ProjectName}-${Env}-PublicSubnetASubnetId"
UserData: !Base64 |
#! /bin/bash
sudo yum update -y
sudo yum install -y mysql
ElasticIP:
Condition: IsApNorthEast1or3
Type: "AWS::EC2::EIP"
Properties:
Domain: vpc
ElasticIPAssociate:
Condition: IsApNorthEast1or3
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt ElasticIP.AllocationId
InstanceId: !Ref EC2Instance
DBInstance:
Condition: IsApNorthEast1or3
Type: "AWS::RDS::DBInstance"
Properties:
DBInstanceIdentifier: !Sub "${ProjectName}-${Env}-rds"
Engine: MySQL
EngineVersion: !Sub "${MySQLMajorVersion}.${MySQLMinorVersion}"
DBInstanceClass: !Ref DBInstanceClass
AllocatedStorage: !Ref DBInstanceStorageSize
StorageType: !Ref DBInstanceStorageType
DBName: !Ref DBName
MasterUsername: !Ref DBMasterUserName
MasterUserPassword: !Ref DBPassword
DBSubnetGroupName: !Sub "${ProjectName}-${Env}-rds-subnet"
PubliclyAccessible: false
MultiAZ: !Ref MultiAZ
PreferredBackupWindow: "18:00-18:30"
PreferredMaintenanceWindow: "sat:19:00-sat:19:30"
AutoMinorVersionUpgrade: false
DBParameterGroupName: !Ref DBParameterGroup
VPCSecurityGroups:
- Fn::ImportValue: !Sub "${ProjectName}-${Env}-EC2SecurityGroupGroupId"
CopyTagsToSnapshot: true
BackupRetentionPeriod: 7
Tags:
- Key: "Name"
Value: !Ref DBInstanceName
DeletionPolicy: "Delete"
DBParameterGroup:
Condition: IsApNorthEast1or3
Type: "AWS::RDS::DBParameterGroup"
Properties:
Family: !Sub "MySQL${MySQLMajorVersion}"
Description: !Sub "${ProjectName}-${Env}-rds-parm"
テンプレートは以上です。
テンプレートの実行、接続確認
では、テンプレートを実行します。
マネジメントコンソールからGUIで実行しても良いですが、自分はrain
を使用します。
❯ rain deploy parameterstore-vpc.yml -p cli -y
❯ rain deploy parameterstore-ec2-rds.yml -p cli -y
- parameterstore-vpc.yml
- parameterstore-ec2-rds.yml
の順番で実行しましょう。
実行が完了したら、RDSのエンドポイントURLをメモします。
次に、作成したEC2インスタンスにセッションマネージャーで接続しにいきます。
そこで、以下のコマンドを実行し、mysqlに接続できればOKです。
sh-4.2$ mysql -u dbuser -p -h メモしたRDSのエンドポイントURL
Enter password: dbpassword
MySQL [(none)]>
ソースコードの追加、変更
それでは、parameterstoreを使用するために、テンプレートを編集していきます。
今回は、簡単にRDSのログインユーザー名をparameterstoreに格納し、それを使用しようと思います。
新しくテンプレートを作成します。
parameterstore-parm.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: "Project Name Prefix and Environment"
Parameters:
- ProjectName
- Env
- RDSMasterUsername
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
ProjectName:
Type: String
Default: project
Description: Enter Project Name. defailt is project.
Env:
Type: String
Default: common
AllowedValues:
- common
- prd
- stg
- dev
Description: Enter common, prd, stg, dev. default is common.
RDSMasterUsername:
Default: dbuser
Type: String
NoEcho: true
MinLength: 1
MaxLength: 16
AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
ConstraintDescription: "must begin with a letter and contain only alphanumeric characters."
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
SSMParameterForRDSMasterUsername:
Type: "AWS::SSM::Parameter"
Properties:
Name: "RDSMasterUsername"
Type: "String"
Value: !Ref RDSMasterUsername
Description: "MasterUsername for RDS"
Tags:
Name: !Sub "${ProjectName}-${Env}-RDSMasterUsername"
次に、parameterstore-ec2-rds.ymlの編集を行います。
parameterstore-ec2-rds.yml
- MasterUsername: !Ref DBMasterUserName
+ MasterUsername: "{{resolve:ssm:RDSMasterUsername}}"
再度テンプレートの実行、接続確認
では、テンプレートの実行、更新、接続確認をします。
❯ rain deploy parameterstore-parm.yml -p cli -y
❯ rain deploy parameterstore-ec2-rds.yml -p cli -y
EC2インスタンスにセッションマネージャーで接続し、またmysqlへのアクセスを行います。
sh-4.2$ mysql -u dbuser -p -h メモしたRDSのエンドポイントURL
Enter password: dbpassword
MySQL [(none)]>
無事に接続できました。
後片付け
後片付けもします。 VPCはセキュリティグループを手動で削除した後に削除実行してください。
❯ rain ls -p cli
❯ rain rm parameterstore-ec2-rds -p cli -y
❯ rain rm parameterstore-vpc -p cli -y
❯ rain rm parameterstore-parm -p cli -y
❯ rain ls -p cli
参考サイト
今日の一言
SecretManagerも使ってみたい