はじめに
今年のカレンダーはとある会社のとあるチームで活動した1年間を振り返っていきます
今回は業務で初めてAWSを触ったときの自己学習を備忘録として残していたので供養します
少し情報が古い可能性もありますので、ご注意ください。
やりたいこと
- EC2を踏み台にしてRDSに接続
- RDS接続は、SecretsManagerを使ってRDS Proxy経由で行う
インバウンドルールの設定
- 各リソースやネットワーク系の構築に加えてネットワーク間の通信設定が必要
- ec2からproxyを経由してrdsへアクセスしたいので、インバウンドルールの設定をする
- ec2からproxyにアクセスする許可を設定したいので、proxyのインバウンドルールにec2のIPアドレスを指定
- proxyからrdsにアクセスする許可を設定したいので、rdsのインバウンドルールにproxyのIPアドレスを指定
構成図
cloudformationのテンプレート
ファイルに記載する基本的な構成は以下の通り
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
UserName:
Description: Type of this UserName.
Type: String
Resources:
QiitaUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub "${UserName}"
プロパティ | 概要 |
---|---|
AWSTemplateFormatVersion | テンプレート形式を指定 |
Parameters | 一部の項目を変数にできる。同じ構成で複数の環境を立てる際に使える |
Resource | 使用するAWSリソースを指定 |
Logical ID | テンプレート内でユニークなID。他リソースから参照するときに使える |
Type | AWSリソースプロパティタイプのリファレンスに一覧がある |
Properties | リソース作成時に指定する。タイプによって利用できるプロパティが異なるので、公式ドキュメントなどを参考に指定する |
tags | リソースに命名できる |
Outputs | 作成したスタックの出力タブに指定した値を表示させることができる |
- !Sub "${hoge}" :リージョン名やID、スタック名などをパラメータ参照できる
- !Ref hoge :指定したリソースの値を返す事ができる
- !GetAtt: リソースの属性を返せる(リソースによって返せる値は異なる)
export/import
Outputs:
OutputQiitaName:
Value: !Ref QiitaName
Export:
Name: ExportQiitaName
OutputQiitaArn:
Value: !GetAtt Qiita.Arn
Export:
Name: ExportQiitaArn
Resources:
Qiita:
Type:
Properties:
hogeName: !ImportValue ExportQiitaName
hogeArn: !ImportValue ExportQiitaArn
- スタックAでexportしたNameやArnは、スタックBでimportすると参照できる
- !ImportValue :別のスタックでエクスポートされた値を参照できる
- exportはOutputsで出力したい値を指定
- exportされている値の一覧は
aws cloudformation list-exports
で確認できる
IAMスタックを作成
以下のqita_iam.ymlを使用してcloudformationでスタックを作成
AWSTemplateFormatVersion: "2010-09-09"
Description: Create IAM
Parameters:
UserName:
Description: Type of this UserName.
Type: String
Resources:
# ------------------------------------------------------------#
# qiita IAM User
# ------------------------------------------------------------#
QiitaIAMUser:
Type: AWS::IAM::User
Properties:
UserName: !Sub "${UserName}"
Tags:
- Key: "name"
Value: "qiita"
QiitaIAMUserAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref QiitaIAMUser
QiitaIAMUserAccessKeySecret:
Type: AWS::SecretsManager::Secret
Properties:
Name:
Fn::Sub: "${QiitaIAMUser}-credentials"
SecretString:
Fn::Sub: '{"accessKeyId":"${QiitaIAMUserAccessKey}","secretAccessKey":"${QiitaIAMUserAccessKey.SecretAccessKey}"}'
Outputs:
AccessKey:
Value: !Ref QiitaIAMUserAccessKey
SecretAccessKey:
Value: !GetAtt QiitaIAMUserAccessKey.SecretAccessKey
ネットワーク系のスタックを作成(VPC、Subnet、SecurityGroup、RouteTable、InternetGateway)
- 以下のqiita_network.ymlをcloudformationにアップロードしてスタックを作成
Parameters:
ProjectName:
Description: Type of this ProjectName.
Type: String
Resources:
# ------------------------------------------------------------#
# vpc
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/22
Tags:
- Key: "Name"
Value: "qiita"
# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------#
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-igw"
InternetGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# subnet
# ------------------------------------------------------------#
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: ap-northeast-1a
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-public-subnet"
PublicSubnetC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
AvailabilityZone: ap-northeast-1c
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-public-subnetC"
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: ap-northeast-1a
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-private-subnetA"
PrivateSubnetC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.3.0/24
AvailabilityZone: ap-northeast-1c
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-private-subnetC"
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
PublicRouteTableA:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-public-routetableA"
PublicRouteA:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTableA
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
PublicRouteAssociationA:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTableA
SubnetId: !Ref PublicSubnetA
PublicRouteTableC:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-public-routetableC"
PublicRouteC:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTableC
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
PublicRouteAssociationC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTableC
SubnetId: !Ref PublicSubnetC
VPC
プロパティ | 概要 |
---|---|
CidrBlock | IPv4 CIDRブロック。作成するVPCにおけるIPアドレスの範囲 |
EnableDnsHostnames | DNSホスト名取得の有効/無効 |
EnableDnsSupport | VPCに対してDNS解決がサポートされているかどうか |
InstanceTenancy | VPC内に起動されるインスタンスの許可されているテナンシー(設定するとEC2の料金が割増になる) |
- EC2 等のインスタンスはサブネット内で起動する。サブネットはVPC内に配置する必要があるため、VPCから作成
- VPCの中にサブネットを作成(private/public)
- VPCの作成には CIDR を1つだけ設定
- 各サブネットにアベイラビリティゾーンを用意
Subnet
プロパティ | 概要 |
---|---|
VpcId | サブネットを作成するVPCのID |
AvailabilityZone | サブネットを作成するアベイラビリティゾーン |
CidrBlock | 作成するサブネットにおけるIPアドレスの範囲 |
MapPublicIpOnLaunch | サブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか。パブリックサブネットでは true、プライベートサブネットでは false を指定 |
- 今回はEC2を配置するパブリックサブネットと、RDB サーバーを配置するプライベートサブネットの計3つを作成。
- プライベートサブネットに対してはAZを2つ用意し、冗長化
- 今回は 10.0.1.0/24 , 10.0.2.0/24 , 10.0.3.0/24 の3つのサブネットを作成
InternetGateway
プロパティ | 概要 |
---|---|
InternetGatewayId | インターネットゲートウェイのID |
VpcId | VPC ID |
VpnGatewayId | 仮想プライベートゲートウェイのID |
- インターネットとVPCをつなぐ役割
インターネットゲートウェイはVPCに関連付ける
RouteTable
プロパティ | 概要 |
---|---|
DestinationCidrBlock | ルーティング先の照合に使用するIPv4CIDRブロック |
DestinationIpv6CidrBlock | ルーティング先の照合に使用するIPv6CIDRブロック |
EgressOnlyInternetGatewayId | EgressOnlyインターネットゲートウェイID |
GatewayId | ゲートウェイID |
InstanceId | NATインスタンスID |
NatGatewayId | NATゲートウェイID |
NetworkInterfaceId | ネットワークインターフェイスID |
RouteTableId | ルートテーブルID |
TransitGatewayId | トランジットゲートウェイID |
VpcPeeringConnectionId | VPC ピア接続ID |
- ネットワーク経路を示す役割
- 1つのサブネットに1つのルートテーブルを用意
- ルートテーブルへのアタッチはRouteTableId:で指定
- Type: AWS::EC2::SubnetRouteTableAssociationで、インターネットへ抜けられるサブネットを指定
RDS MySQL Aurora
secret&rds
https://techblog.nhn-techorus.com/archives/17674
セキュリティグループも一緒につくる
依存関係はよしなにしてくれる
Parameters:
ProjectName:
Description: Type of this ProjectName.
Type: String
VPC:
Type: "AWS::EC2::VPC::Id"
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
DBName:
Description: Type of this DatabaseName example sample_db.
Type: String
DBPassword:
Description: Type of this DatabasePassword.
Type: String
# ProxyTargetDBClusterIdentifiers:
# Type: CommaDelimitedList
Resources:
DBCluster:
Type: AWS::RDS::DBCluster
Properties:
DBClusterIdentifier: !Sub "${ProjectName}-db-cluster"
DBSubnetGroupName: !Ref "DBSubnetGroup"
DatabaseName: !Sub "${DBName}"
Engine: aurora-mysql
EngineMode: "serverless"
EngineVersion: "5.7.mysql_aurora.2.10.2"
DBClusterParameterGroupName: !Ref DBParameterGroup
# MasterUserPassword: !Sub "${DBPassword}"
# MasterUsername: admin
MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:username}}' ]]
MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:password}}' ]]
StorageEncrypted: true
ScalingConfiguration:
MinCapacity: 1
AutoPause: false
MaxCapacity: 2
VpcSecurityGroupIds:
- !Ref AuroraSecurityGroup
DBSubnetGroupName: !Ref DBSubnetGroup
DeletionPolicy: Delete
# ------------------------------------------------------------#
# parameter group
# ------------------------------------------------------------#
DBParameterGroup:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Family: aurora-mysql5.7
Description: Database Parameter Group
Parameters:
character_set_database: utf8mb4
character_set_client: utf8mb4
character_set_connection: utf8mb4
character_set_results: utf8mb4
character_set_server: utf8mb4
time_zone: Asia/Tokyo
# ------------------------------------------------------------#
# subnet group
# ------------------------------------------------------------#
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupName: !Sub "${ProjectName}-db-subnet-group"
DBSubnetGroupDescription: for db
SubnetIds: !Split [",", !Join [",", !Ref SubnetIds]]
# ------------------------------------------------------------#
# security group
# ------------------------------------------------------------#
AuroraSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SecurityGroup for Aurora
VpcId: !Sub "${VPC}"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: 10.0.1.0/24
Tags:
- Key: "Name"
Value: !Sub "${ProjectName}-db-sg"
# DependsOn: VPC
# ------------------------------------------------------------#
# secret manager
# ------------------------------------------------------------#
SecretManager:
Type: AWS::SecretsManager::Secret
Properties:
Description: "Secrets Manager for RDS"
SecretString:
!Sub '{"username": "admin","password": "${DBPassword}"}'
Name: !Sub "${ProjectName}-Secrets"
Tags:
- Key: Name
Value: !Sub ${ProjectName}-Secrets
SecretManagerAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref SecretManager
TargetId: !Ref DBCluster
TargetType: AWS::RDS::DBCluster
QitaRDSInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 5
DBInstanceClass: db.t3.small
Engine: MySQL
MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:username}}' ]]
MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:password}}' ]]
BackupRetentionPeriod: 0
DBInstanceIdentifier: 'qiita-proxy-instance'
QitaRDSProxy:
Type: AWS::RDS::DBProxy
Properties:
DBProxyName: !Sub "QitaRDBProxyName"
EngineFamily: MYSQL
RoleArn: !GetAtt IAMRole.Arn
Auth:
- AuthScheme: SECRETS
# プロキシが使用するシークレットを選択
SecretArn: !Ref "SecretManager"
# RDS Proxyにアタッチするセキュリティグループ
VpcSecurityGroupIds:
- !ImportValue EC2-SG
# RDS Proxyを構築するVPC Subnet
# 選択したvpcでデータベースが使用可能なIP範囲を指定
VpcSubnetIds:
- !ImportValue private-subnetA
- !ImportValue private-subnetC
QitaRDSProxyTargetGroup:
Type: AWS::RDS::DBProxyTargetGroup
DependsOn:
- QitaRDSInstance
Properties:
DBProxyName: !Ref QitaRDSProxy
TargetGroupName: default
# Proxyが接続するRDS Auroraクラスターの識別子
# DBClusterIdentifier: !Ref DBCluster
DBInstanceIdentifiers:
- !Ref QitaRDSInstance
# DBClusterIdentifiers: !Ref ProxyTargetDBClusterIdentifiers
RDSProxySecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "RDS Proxy security group"
VpcId: !Sub "${VPC}"
SecurityGroupIngress:
# ec2のIPアドレスをインバウンドルールに設定
- SourceSecurityGroupId: !ImportValue EC2-SG
IpProtocol: "tcp"
FromPort: 3306
ToPort: 3306
IAMRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- rds.amazonaws.com
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- !Ref IAMPolicy
IAMPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
PolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GetSecretValue",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:GetResourcePolicy",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Effect": "Allow",
"Resource": [
"*"
]
},
{
"Sid": "DecryptSecretValue",
"Action": [
"kms:Decrypt"
],
"Effect": "Allow",
"Resource": [
"*"
],
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com"
}
}
}
]
}
Outputs:
Endpoint:
Value: !GetAtt DBCluster.Endpoint.Address
ResourceArn:
Value: !Sub "arn:aws:rds:ap-northeast-1:${AWS::AccountId}:cluster:${ProjectName}-db-cluster"
SecretArn:
Value: !Ref SecretManagerAttachment
security group
プロパティ | 概要 |
---|---|
GroupDescription | セキュリティグループの説明 |
GroupName | セキュリティグループの名前 |
SecurityGroupIngress | インバウンドルールを宣言 |
IpProtocol | IPプロトコル |
FromPort | ポート範囲の開始番号 |
FromPort | ポート範囲の終了番号 |
CidrIp | IPv4の範囲 |
VpcId | VPCのID |
Type: AWS::RDS::DBProxy
プロパティ | 概要 |
---|---|
DBProxyName | 作成するRDS Proxy名 |
EngineFamily | |
RequireTLS | TLSが必須かどうか |
RoleArn | RDS Proxyに設定するIAM Role |
Auth | Secrets Managerを利用してユーザ名とパスワードで認証 |
VpcSecurityGroupIds | RDS Proxyにアタッチするセキュリティグループ |
VpcSubnetIds | RDS Proxyを構築するVPC Subnet |
Type: AWS::RDS::DBProxyTargetGroup
プロパティ | 概要 |
---|---|
DBProxyName | AWS::RDS::DBProxyで作成したRDS Proxy名 |
TargetGroupName | ターゲットグループ名はdefaultが必須 |
Type: "AWS::EC2::SecurityGroup"
プロパティ | 概要 |
---|---|
SecurityGroupIngress | ec2のIPアドレスをインバウンドルールに設定 |
ec2
Parameters:
ProjectName:
Description: Type of this ProjectName.
Type: String
KeyName:
Description: The EC2 Key Pair to allow SSH access to the instance
Type: "AWS::EC2::KeyPair::KeyName"
PubSub:
Type: "AWS::EC2::Subnet::Id"
VPC:
Type: "AWS::EC2::VPC::Id"
Resources:
# ------------------------------------------------------------#
# ec2
# ------------------------------------------------------------#
EC2:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-078296f82eb463377
KeyName: !Ref KeyName
InstanceType: t2.micro
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: !Ref PubSub
GroupSet:
- !Ref EC2SG
UserData: !Base64 |
#!/bin/bash
sudo yum install -y mysql
Tags:
- Key: Name
Value: !Sub "${ProjectName}-ec2"
# ------------------------------------------------------------#
# ec2 security group
# ------------------------------------------------------------#
EC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${ProjectName}-ec2-sg"
GroupDescription: Allow SSH access only MyIP
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
# グローバルIP
CidrIp: 0.0.0.0
Outputs:
EC2PublicIP:
Value: !GetAtt EC2.PublicIp
Description: Public IP of EC2 instance
プロパティ | 概要 |
---|---|
ImageId | マシンイメージのIDを使用。インスタンスを起動するのに必要なOSやボリューム情報などのテンプレートのこと |
keyname | キーペア名 |
ssh pemについて
https://qiita.com/miriwo/items/3da2391f37dab6b5452c
https://blog.recruit.co.jp/rmp/infrastructure/retry-aws-bastion-host-vpc/
ec2へのssh接続を確認するコマンドは以下
ssh -i ~/.ssh/hoge-ec2.pem ec2-user@EC2のpublicIP
mysqlへ接続
mysql -u username -p -h RDSのエンドポイント
secret managerで作成したパスワードを入力すると接続が確認できる
参考
CloudFormationテンプレート
https://dev.classmethod.jp/articles/cloudformation-beginner01/
組み込み関数
https://dev.classmethod.jp/articles/cloudformation-tips-focused-on-refs/
cloudformationメモ
https://www.magata.net/memo/index.php?AWS%20CloudFormation%A5%E1%A5%E2
ネットワークの構築
https://qiita.com/kono-hiroki/items/6863aa06754fb25f28fc
VPC とサブネット
https://zenn.dev/tmasuyama1114/articles/aws-cloudformation-basics
ネットワーク系
https://zenn.dev/anaka/articles/627d0a112d57ed
インバウンドルール
https://qiita.com/yz2cm/items/2448f1545fef406ce8e3
lambda/secrets
https://www.seeds-std.co.jp/blog/creators/2021-08-13-002824/
cloudformationに秘密情報を渡す
https://techblog.zozo.com/entry/pass_secrets_to_cloudformation
ダイナミックリファレンスでシークレットを取得
https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/cfn-example_reference-secret.html
proxyはsecretをどのように利用しているのか
https://dev.classmethod.jp/articles/how-rdsproxy-uses-secrets-manager/
proxyの構築
https://qiita.com/homoluctus/items/be6c9565ddd615d1d9ac
IAMroleにsecretへの権限許可
https://dev.classmethod.jp/articles/rds-proxy-useradd/