🎃

【CFn】RDS〜SnapShot / VPCSecurityGroupsとDBSecurityGroupsプロパティの違いと条件句を学ぶ。

2022/10/30に公開

公式サンプルテンプレート「リードレプリカを持つAmazon RDS DBインスタンス」を今回の勉強の材料にします。

元テンプレ

今回の構成に登場するリソースタイプ

AWS::EC2::SecurityGroup
AWS::RDS::DBSecurityGroup
AWS::RDS::DBInstance ×2(Master,Read Replica)

Yamlコードブロック

コメントにドキュメントに記載の情報等を添えています。

sample
sample.yml
AWSTemplateFormatVersion: 2010-09-09
Description: >-
  AWS CloudFormation Sample Template RDS_MySQL_With_Read_Replica: Sample
  template showing how to create a highly-available, RDS DBInstance with a read
  replica. **WARNING** This template creates an Amazon Relational Database
  Service database instance and Amazon CloudWatch alarms. You will be billed for
  the AWS resources used if you create a stack from this template.
Parameters:

  DBName:
    Default: MyDatabase # テキストボックス内にデフォルトで入力されている値。
    Description: The database name # 説明文。
    Type: String # パラメータのタイプ。
    MinLength: '1' # 最小文字数。
    MaxLength: '64' # 最大文字数。
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*' # 正規表現。
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters. # 正規表現条件外の入力があった場合の表示エラー文。

  DBUser:
    NoEcho: 'true' # パラメータ値をアスタリスク (*****)としてマスクして、コンソール、コマンドラインツール、またはAPIに表示されないようにするかどうか。NoEcho属性を使用してもOutputやMetadataに定義する場合はマスクされない。
    Description: The database admin account username
    Type: String
    MinLength: '1' 
    MaxLength: '16' 
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*' 
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters.

  DBPassword:
    NoEcho: 'true'
    Description: The database admin account password
    Type: String
    MinLength: '1' 
    MaxLength: '41' 
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: must contain only alphanumeric characters.

  DBAllocatedStorage:
    Default: '5'
    Description: The size of the database (Gb)
    Type: Number
    MinValue: '5' # Number型に使用できる数値の最小値。
    MaxValue: '1024' # Number型に使用できる数値の最大値。
    ConstraintDescription: must be between 5 and 1024Gb.

  DBInstanceClass:
    Description: The database instance type
    Type: String
    Default: db.t2.small
    AllowedValues: # パラメーターに許容される一連の値を含む配列。
      - db.t1.micro
      - db.m1.small
      - db.m1.medium
      - db.m1.large
      - db.m1.xlarge
      - db.m2.xlarge
      - db.m2.2xlarge
      - db.m2.4xlarge
      - db.m3.medium
      - db.m3.large
      - db.m3.xlarge
      - db.m3.2xlarge
      - db.m4.large
      - db.m4.xlarge
      - db.m4.2xlarge
      - db.m4.4xlarge
      - db.m4.10xlarge
      - db.r3.large
      - db.r3.xlarge
      - db.r3.2xlarge
      - db.r3.4xlarge
      - db.r3.8xlarge
      - db.m2.xlarge
      - db.m2.2xlarge
      - db.m2.4xlarge
      - db.cr1.8xlarge
      - db.t2.micro
      - db.t2.small
      - db.t2.medium
      - db.t2.large
    ConstraintDescription: must select a valid database instance type.

  EC2SecurityGroup:
    Description: >-
      The EC2 security group that contains instances that need access to the
      database
    Default: default
    Type: String
    AllowedPattern: '[a-zA-Z0-9\-]+'
    ConstraintDescription: must be a valid security group name.

  MultiAZ:
    Description: Multi-AZ master database
    Type: String
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
    ConstraintDescription: must be true or false.

# ============== 条件 ==============

Conditions: # リソースを作成したりOutputに出力させたり出来る条件を設定。
  # 条件①
  Is-EC2-VPC: !Or # Fn::Orは指定された条件のいずれかがtrueに評価された場合はtrueを返す。条件のすべてがfalseに評価された場合はfalseを返す。
    - !Equals # Fn::Equalsは2つの値が等しいかどうかを比較。値が同じ場合はtrueを返し、同じでない場合はfalseを返す。
      - !Ref 'AWS::Region' 
      - eu-central-1 # フランクフルト
    - !Equals 
      - !Ref 'AWS::Region'
      - cn-north-1 # 北京
  # 条件②
  Is-EC2-Classic: !Not # Fn::Notは NOT演算子として機能。
    - !Condition Is-EC2-VPC # 上の条件句(スタック作成リージョンがフランクフルトか北京である事)が否定された場合条件がtrueになる。

# ============== リソース ==============
Resources:
# -------------- セキュリティグループ --------------
  DBEC2SecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Condition: Is-EC2-VPC # 条件①を満たす場合。
    Properties:
      GroupDescription: Open database for access #[必須: はい] セキュリティグループの説明。これは情報提供のみを目的とする。
      SecurityGroupIngress: #[必須: いいえ] 受信規則。
        - IpProtocol: tcp #[必須: はい] -1はすべてのプロトコルを指定するために使用。それ以外はそのプロトコルに準拠。
          FromPort: '3306' #[必須: いいえ] ポート範囲の開始、またば番号。-1の場合は全てを示す。
          ToPort: '3306' #[必須: いいえ] ポート範囲の終了、またば番号。-1の場合は全てを示す。
          SourceSecurityGroupName: !Ref EC2SecurityGroup #[必須: いいえ] ソース セキュリティ グループの名前。IP アドレス範囲と組み合わせて使用する事は不可。どちらかを指定。
# -------------- RDS DBセキュリティグループ --------------
  DBSecurityGroup:
    Type: 'AWS::RDS::DBSecurityGroup'
    Condition: Is-EC2-Classic
    Properties:
      DBSecurityGroupIngress:  # [必須: はい] 受信規則。
        EC2SecurityGroupName: !Ref EC2SecurityGroup #[必須: いいえ] 承認する EC2 セキュリティ グループの ID
      GroupDescription: database access # [必須: はい] DBセキュリティグループの説明を提供。
# -------------- RDSインスタンス --------------
  # マスター
  MasterDB:
    Type: 'AWS::RDS::DBInstance' # RDS DBインスタンスにすることも、Aurora DBクラスター内の DBインスタンスにすることも可能。
    Properties:
      DBName: !Ref DBName #[必須: いいえ] このパラメーターの意味は、使用するデータベースエンジンによって異なる。詳しくは https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbinstance.html
      AllocatedStorage: !Ref DBAllocatedStorage #[必須: 条件付き] DBインスタンスに最初に割り当てられるギビバイト (GiB) 単位のストレージの量
      DBInstanceClass: !Ref DBInstanceClass #[必須: いいえ] DB インスタンスのコンピューティングおよびメモリ容量。
      Engine: MySQL #[必須: 条件付き] DBインスタンスに使用するDBエンジンの名前。DBインスタンスを作成する場合、Engineプロパティは必須。
      MasterUsername: !Ref DBUser #[必須: 条件付き] DB インスタンスのマスターユーザー名。
      MasterUserPassword: !Ref DBPassword #[必須: 条件付き] マスターユーザーのパスワード。
      MultiAZ: !Ref MultiAZ #[必須: いいえ] マルチAZ DBインスタンス配置であるかどうかを指定。
      Tags: #[必須: いいえ] タグ。
        - Key: Name
          Value: Master Database
      # 以下 VPCSecurityGroupsとDBSecurityGroupsは同時設定不可。このケースでは!If条件を使いどちらか片方になるようにしている。
      VPCSecurityGroups: !If #[必須: いいえ] DBインスタンスに割り当てるVPCセキュリティグループIDのリスト。既存のVPCセキュリティグループの物理IDとテンプレートで作成されたAWS::EC2::SecurityGroupリソースへの参照の両方を含めることが可能。
        - Is-EC2-VPC
        - - !GetAtt 
            - DBEC2SecurityGroup
            - GroupId
        - !Ref 'AWS::NoValue' # AWS::NoValue擬似パラメータはFn::If 組み込み関数の戻り値として指定すると、対応するリソースプロパティを削除
      DBSecurityGroups: !If #[必須: いいえ] DBインスタンスに割り当てるVPCセキュリティグループIDのリスト。既存のDBセキュリティグループの物理IDとテンプレートで作成されたAWS::RDS::DBSecurityGroupリソースへの参照の両方を含めることが可能。
        - Is-EC2-Classic
        - - !Ref DBSecurityGroup
        - !Ref 'AWS::NoValue' # 同上。
    DeletionPolicy: Snapshot # 削除時、このDBインスタンスのスナップショットを残す。

  # リードレプリカ
  ReplicaDB:
    Type: 'AWS::RDS::DBInstance' 
    Properties:
      SourceDBInstanceIdentifier: !Ref MasterDB #[必須: いいえ] 今回のようにリードレプリカDBインスタンスを作成する場合は、ソースDBインスタンスのIDを指定。
      DBInstanceClass: !Ref DBInstanceClass
      Tags:
        - Key: Name
          Value: Read Replica Database
# ============== 出力 ==============
Outputs:

  EC2Platform:
    Description: Platform in which this stack is deployed # 直訳:このスタックがデプロイされているプラットフォーム。
    Value: !If 
      - Is-EC2-VPC
      - EC2-VPC
      - EC2-Classic

  MasterJDBCConnectionString: # JDBCとは https://atmarkit.itmedia.co.jp/ait/articles/0106/26/news001.html
    Description: JDBC connection string for the master database # 直訳:マスターDB用JDBC接続文字列。
    Value: !Join 
      - ''
      - - 'jdbc:mysql://'
        - !GetAtt 
          - MasterDB
          - Endpoint.Address
        - ':'
        - !GetAtt 
          - MasterDB
          - Endpoint.Port
        - /
        - !Ref DBName

  ReplicaJDBCConnectionString:
    Description: JDBC connection string for the replica database # 直訳:レプリカDB用JDBC接続文字列。
    Value: !Join 
      - ''
      - - 'jdbc:mysql://'
        - !GetAtt 
          - ReplicaDB
          - Endpoint.Address
        - ':'
        - !GetAtt 
          - ReplicaDB
          - Endpoint.Port
        - /
        - !Ref DBName

作成します。

パラメーターを埋めてスタックの作成をクリックします。

RDSは作成に時間がかかるイメージがあります。
マスター(プライマリ)、レプリカ共に体感10分弱かかっています。

CREATE_COMPLETEしました。

出力タブからマスターとレプリカのDBインスタンスに飛べます。

RDSコンソール上はこのように表示されています。

「ロール」列からプライマリとレプリカがどちらであるか見て取れるようになっています。

なるほどと思った点。

私は今回初めてCondition(条件)句に触れたのですが、

コードブロック内の

#条件①を作成し、

#条件②でそれをFn::Notで正反対(それ以外)の条件とする事でMECE(漏れ・ダブりのない状態)にしているようです。

その意図として「AWS::RDS::DBInstance」内でVPCSecurityGroupsとDBSecurityGroupsはドキュメント上どちらか片方しか存在してはいけない事となっているのですが、

このテンプレのように実際にプロパティを2つ共存させた時点で直ちにスタック作成にエラーが起きる訳ではなく、

リソース作成時に条件分岐で実行されるプロパティがどちらか片方である事が保証されていれば成立するものなのだなということは勉強になりました。

逆にCondition句の意図が理解しきれていない点。

これは私の前提知識が足りない事からくるモヤモヤですが、

Condition句の論理ID名の名付けだけを見ると、

条件①はVPC内のEC2
条件②はEC2クラシック(既に廃止済み/VPCサービスが出来る前から存在するVPC上にないEC2)
を判断しているように見えます。

実際に中を覗いてみると、条件①②をエクセル関数風に表現すると、

IF(OR(スタック作成リージョン="フランクフルト",スタック作成リージョン="北京"),"EC2-VPC","EC2-Classic")

としているように見えます。(※↑これがtrueを返す訳ではないですが)

が、「EC2 Classic eu-central-1 cn-north-1 」と検索してみても、この2つのリージョンとEC2クラシックの提供リージョンの関連についての情報は見つかりませんでした。

北京については、お国柄制約がある事を認定試験で繰り返し刷り込まれてきましたが、フランクフルトはなんだろうと思いました。

※追記:中国リージョンについて

フランクフルト(eu-central-1)でスタック作成をしてみる。

前章で触れた通り、

「フランクフルトと(or)北京かそれ以外か」という条件分けをしていました。

北京リージョンはマネコンのリージョンの選択肢にないので、フランクフルトでの動作を確認してみます。

リージョンを移動しました。

※帯のグラデーションや国旗はChromeの拡張機能です。

出力タブでは当該OutputのIf条件で条件句のどちらがTrueになっているのかが返ってくるようになっています。
今度は先程と違い”EC2-VPC”である事が確認出来ます。

改めてVPCSecurityGroupsとDBSecurityGroupsの違いとは。

【AWS】RDSには二つのセキュリティグループがある」という記事では公式ドキュメントの情報から、


GUI上ではそんな項目なかったはず…
この違いはいったい?
と思って調べてみると、公式ドキュメントに記載がありました。

DBセキュリティグループはEC2-Classic専用、で現在は新規作成するとVPCセキュリティグループになるみたいですね。


とまとめています。

2022年10月現在フランクフルトリージョンとそれ以外で新規にEC2をたててみてもEC2クラシックで動作確認出来ませんので、検証が難しいですが、

このテンプレートを作成した時点では
「フランクフルトと北京の2リージョンではEC2クラッシックが作成される可能性は無く(未対応など)それ以外ではクラシックで(も)動作するようにしたかった」のではないかと思います。

最初はDBSecurityGroupsはEC2クラシックにも現在のVPC上のEC2にも(一時的かもしれませんが)対応する項目だったのではないかと思っていましたが、

それがハズレだとするとこのテンプレートが作成された時点では現在のVPC上で動作するEC2はこの瞬間フランクフルトと北京のみで動いていたのでしょうか?

ヴァージニアがそこに該当しないのは考えにくいので、それもなさそうと思いますが、このあたりは私の読解違いであるかもしれません。ご存知の方がいらっしゃればご教示いただけたら幸いです。

もしかしたら、DBSecurityGroups項目は、お役御免になる日も来るのかなと感じました。

スナップショットについて

振り返りですが、テンプレートでは以下の様になっていました。

  MasterDB:
    Type: 'AWS::RDS::DBInstance' 
《中略》
    DeletionPolicy: Snapshot

スタックを削除してみます。

「手動スナップショット」としてスナップショットが残っています。

DeletionPolicy: Snapshotによって最終スナップショットが作成されたものでしょうか。

まず、以下にCFnスタックのイベントタブとRDSコンソールのスナップショットの各タブに表示された時間を時系列で並べるために、再度スタックを作成し直してみました。


※Cfnスタック画面は太字、RDSコンソールは通常です。

15:48:25(MasterDB作成開始=CREATE_IN_PROGRESS/Resource creation Initiated)

15:52 手動・自動すべてのスナップショット上に表示された「DBインスタンスの作成時刻」
15:54 自動スナップショット①
15:54 自動バックアップ→現在のリージョン ※時間は「最も早い復元可能な時間」(「遅い」は一度更新を観測)

15:56:06(MasterDB作成完了=CREATE_COMPLETE)

15:57 自動スナップショット②

16:21:35(MasterDB削除開始=DELETE_IN_PROGRESS)

16:25時点(目視)自動バックアップ→現在のリージョン 現在のリージョン のバックアップが見つかりません
16:28 手動スナップショット(スナップショットの作成時刻)

16:29:08(MasterDB削除完了=DELETE_COMPLETE)

16:35時点(目視)自動スナップショット①②〜スナップショットが見つかりません。


結果

・「バックアップサービス」タブのスナップショットには最初から最後まで何も作成されませんでした。

・「手動スナップショット」はMasterDBが削除開始(DELETE_INPROGRESS)された時のみ一度作成されています。

・「自動スナップショット(システムスナップショット)」はMasterDB作成時に2つのスナップショット作成を確認しました。その後時間経過による更新は見られなった事から、作成などなんらかのイベントをトリガーに自動作成されたものではと考えます。MasterDB削除完了時点では残っていましたが、数分するとスタック全て削除されていたのでDB削除や保持期間がなにやら関係しているように感じます。

・左側のサイドカラムから「自動バックアップ」についてはスタック作成時に作成され、「最も遅い復元可能な時間」が更新されていた事から時間経過に応じて動いているものと理解しました。こちらもMasterDBの削除開始と共に削除されていました。

ここまでの結果から

DeletionPolicy: Snapshot

で作成される最終スナップショットは手動スナップショット扱いであるかと思います。

ドキュメントでは

【バックアップについて】

RDS は、DB インスタンスのバックアップ期間中に DB インスタンスの自動バックアップを作成して保存します。

とあります。

まず自動バックアップと自動(システム)スナップショットについてですが、

こちらの記事のAWSサポートへの問い合わせの結果から、

・自動(システム)スナップショットの削除は自動バックアップと連動している。
・自動(システム)スナップショットを手動で削除する事は出来ない。

と回答があり、

一応2つは別の存在である事がわかります。

「自動(システム)スナップショット」が自動バックアップと連動しているとして、自動バックアップの保持期限の最小値は「1日」ですので、結局

①DB自体の削除
②最終手動スナップショットの作成

のいずれかがトリガーになっていそうです。

公式ドキュメントには

DB インスタンスを削除するとき、自動バックアップを保持するようにできます。

とある通り、デフォルトではDBインスタンス削除がトリガーになって自動バックアップは削除される事が読み取れます。

CFnでは

DeleteAutomatedBackups
DB インスタンスが削除された直後に自動バックアップを削除するかどうかを示す値。

がありますので、明示的にこのプロパティを指定すれば自動バックアップ→自動スナップショットはDBインスタンス削除に連動しなくなるようです。

ちなみに、

DB インスタンスの初期のスナップショットには、フル DB インスタンスのデータが含まれています。同じ DB インスタンスの後続のスナップショットは増分です。

自動スナップショット①と②はこの「初期フル」と「増分」であった事が窺えます。

最後に

触れないで終わりそうになりましたが、2行目のDescription:にはしっかりと「CloudWatch alarmsが作成されます。」と書いてありました。

が、スタック作成完了後のコンソール上で、待てど暮せどアラームが生成される様子はありません。

テンプレ上に「AWS::CloudWatch::Alarm」リソースもなく、
「AWS::RDS::DBInstance」ドキュメントにもアラームの作成を操作するプロパティは見当たらなかったのでこれは作成者の筆の誤りという事かと思います。

以上でした。

お付き合いいただき有難うございました。

Discussion