2
1

More than 3 years have passed since last update.

CloudFormation備忘録

Last updated at Posted at 2019-11-14

現存する環境から複製するために部分的に書けたテンプレートを起こすというようなタスクがアレで個人的な覚書を。

・なんでこれで?

→ほかのあらゆるInfrastracture as a codeと同様に見える化と省力化とミス防止などいろいろメリットがある。当初の担当者が既にいないがブラックボックスな状況を多少は防げると思われるしわかる人どうしなら引き継ぎがしやすい。
 強いて言うとクラウドプロバイダ謹製だから新サービス対応が早い
 わかる人がみるとエクセル資料などをあさって確認せずとも環境を確認・俯瞰しやすいリアルな資料がわりに。
 乖離してなければ。

・Terraformとなにが違うか

テスト結果の確認しやすさはTerraformが上かもしれないけども、CFでも変更検知は可能。
Terraformもマルチクラウドでできるといってもそれぞれのクラウドのことを知らずに使えるわけでもなく、
リソースの書き方はクラウドによって独特な感じなので、使う人々が使いやすい広めやすい方でよくてケースバイケースかな。

・一度始めたら手動対応するとめんどくさいことになる件

乖離してるときは手で変えた環境を手で戻してドリフトステータス再度確認する感じ。
そうしないとスタックが消せなくなる。スタックが消せないと中途半端に残った同名のリソースを作り直すとかできないことに。
作りかけとか消すのに失敗してるステータスだと問い合わせるかだいぶ待つかしないとロールバックしてくれなかったりも。
まあそれは他のツールでもあまり変わらないけどTerraformだとimportとかできるのはいい。それが良し悪しという意見もありそうだけど。

・動的パラメータが可能な種類が限られる件

ざっくりいうとRDBのパスワードは大体いけたりするがそれ以外は結構無理だったりなど。
パラメータストアとセキュリティマネージャは結構違い、どっちかにすぐできるという感じでもなくて
コードから変えないといけなかったりするため既存をある程度踏襲したいという場合には不向き。

これは書けるが

  DBMasterUserPassword:
    Description: from system manater parameter.
    Default: "{{resolve:ssm-secure:stg-admin-DB_PASSWORD:1}}"
    Type: String
    NoEcho: true

これは無理だった。webhookのトークンとかも。

  DatadogApikey:
    Description: "datadog apikey from system manager parameterstore(manual, its support not yet)"
#    Default: "{{resolve:ssm-secure:datadog_APIKEY:1}}"
    Type: String
    NoEcho: true
・アカウントIDやらリージョンやらはいきなり使える
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kinesisfirehose/${KinesisFirehoseName}:log-stream:*'
・パラメータからリストで取り出して番号指定して使う

何度もテストするのに既存にあるものから選ぶほうが運用しやすくてたぶん便利という動機からこんなかんじに。

Parameters:
  EfsSecurityGroup:
    Description: 'select sg-efs-xxx'
    Type: "List<AWS::EC2::SecurityGroup::Id>"
  Subnets:
    Type: "List<AWS::EC2::Subnet::Id>"
    Description: "The list of SubnetIds in your Virtual Private Cloud (VPC)"
Resources:
  FileSystem:
    Type: AWS::EFS::FileSystem
    Properties:
      PerformanceMode: generalPurpose
      FileSystemTags:
      - Key: Name
        Value: efs-xxxx-stg
      Encrypted: false
  MountTarget1: 
    Type: AWS::EFS::MountTarget
    Properties: 
      FileSystemId: 
        Ref: "FileSystem"
      SubnetId: !Select [0, !Ref Subnets]
      SecurityGroups: !Ref EfsSecurityGroup 
  MountTarget2: 
    Type: AWS::EFS::MountTarget
    Properties: 
      FileSystemId: 
        Ref: "FileSystem"
      SubnetId: !Select [1, !Ref Subnets]
      SecurityGroups: !Ref EfsSecurityGroup 
・文字列を結合して値とするにはSubとJoin
Policies:
        - PolicyName: KinesisFirehosePolice
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kinesisfirehose/${KinesisFirehoseName}:log-stream:*'
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
ResourceId: !Join ['', [service/, !Ref 'ECSCluster', /, !GetAtt [service, Name]]]
・パラメータはデフォルト値を定義したりDescriptionに書くと便利

固定の値ならデフォルト値書いとくと指定しなくていいので楽。TypeとDescriptionが必須。

  InstanceType:
    Description: "(Required)"
    Default: "t3a.medium"
    Type: "String"

パスワード的なものはNoEchoで定義したやつを見られないようにするのが定石っぽい

・OutputsとImportValueでテンプレート間のリレーション

OutPutしてImportすると親子的なリレーション依存関係のあるテンプレを作れる
便利なケースもそれだと取り回しづらいというケースもある

Outputs:
  S3BucketName:
    Description: Bucket used for CodePipeline temp files
    Value: !Ref S3Bucket
    Export:
      Name: !Sub "${AWS::StackName}-S3BucketName"
  CodePipeline:
    Type: "AWS::CodePipeline::Pipeline"
    DependsOn:
      - CodeBuild
      - CodePipelineRole
    Properties:
      Name: !Ref AWS::StackName
      RoleArn: !GetAtt [ CodePipelineRole, Arn ]
      ArtifactStore:
        Location: !ImportValue pipeline-dependencies-stg-S3BucketName
        Type: S3
・既存のリソースを使うときはARNを指定する書き方だと消して作り直されることはない

気を付ける点として、テンプレートとその設定した環境が乖離した状況になると
環境を手で戻して再度確認するなどの手順を踏まないと更新や削除が一切できなくなる。
なので、自由奔放な運用方法はできない。手動対応を規制する必要がある。

Parameterとして指定することで厳密に管理され固定的になるのを防ぐという手もなくはない。

ただパラメータにしてしまうと動的にとか複数環境に対応できるように抽象的にするということは難しいような気がする。たぶん。

・mappingで複数環境対応もできる

が、これも運用しやすいかどうかは対応する人によるので、誰がどうするかとか考えて書いたほうがいいかも

Mappings:
  RDS:
    stg:
      ClusterName: rds-xxxxx-dbcluster
      ClusterNameShort: rds-xxxxx
      Instance1Name: rds-xxxxx-a
      Instance2Name: rds-xxxxx-c
      InstanceType: db.t2.medium
      BackupRetentionPeriod: 5
      PreferredBackupWindow: "15:07-15:37"
      PreferredMaintenanceWindow: "sun:23:00-sun:23:30"
      AvailabilityZone1: "ap-northeast-1a"
      AvailabilityZone2: "ap-northeast-1c"
      MonitoringInterval: 60
~~~
  DBCluster:
    Type: "AWS::RDS::DBCluster"
    DeletionPolicy: Snapshot
    Properties:
      AvailabilityZones:
        - !FindInMap [RDS, !Ref "EnvName", "AvailabilityZone1"]
        - !FindInMap [RDS, !Ref "EnvName", "AvailabilityZone2"]
      BackupRetentionPeriod: !FindInMap [RDS, !Ref "EnvName", "BackupRetentionPeriod"]
・Conditionで条件分岐する

あんまやりすぎると複雑になるので必要な分だけでいいかなあというか。
以下は、パラメータにスナップショットが指定さなかった場合に新規作成アトリビュートができて、
スナップショットは空に、ユーザとパスワードが設定されるがスナップショットがあったらそれが設定されて
ユーザとパスは無視される的な見本。

Conditions:
  isBrandNewDB: !Equals [ !Ref DBSnapshotArn, "" ]
~~~
      SnapshotIdentifier:
        !If [isBrandNewDB, !Ref "AWS::NoValue", !Ref "DBSnapshotArn" ]
~~~
      MasterUsername:
        !If [isBrandNewDB, !Ref "DBMasterUserName", !Ref "AWS::NoValue" ]
      MasterUserPassword:
        !If [isBrandNewDB, !Ref "DBMasterUserPassword", !Ref "AWS::NoValue" ] 
・対応しているリソースやロードマップを確認できる

リソースのTypeで指定できるかどうかという話を↑でみられる
こういうの↓

Type: "AWS::AutoScaling::LaunchConfiguration"

マニュアル検索するときに↑のTypeで検索する

・一回ブラックベルトよむとだいぶわかった気にはなりそう

・Jsonだったらデザイナーでyamlに置換しとくと楽

yamlの可読性がjsonに比べたら大変すばらしいし置換はデザイナーでできる。

・同様のツール

GCPだとCloud Deployment Manager、AzureだとAzure Resource Managerなど。(つかったことはない。使えと強制されたことがないのが大きいと思う。)

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1