t_hazawaの日記

株式投資とWebエンジニアリングのブログです。株式投資の目次は→です。 https://t-hazawa.hatenablog.com/entry/2021/02/12/220933

業務のEC2をCloudFormationにするぞ3:私物AWSで練習適用編

経緯・あらすじ

  • 前回までで、業務のAWS(踏み台サーバ周りのNW)を cFnのテンプレにした
  • その後、チームメンバーに説明してレビューしてもらった
  • それを適用すべく、まず私物AWSに同じ構成+ nginx EC2 を立てて、インポート練習をした
  • すると、作ったテンプレでは通らなかったので、足りないところを足していく
  • 結果、最終的に、私物AWSを cFn管理にすることができた。

作ったcFnテンプレについて

  • 詳しくは 前回の 「15日め」に書きましたが、 CloudFormationの実践ベストプラクティス - Qiita のベストプラクティス構成を踏襲しています。
  • 思いっきり「CDKを使いましょう」と書かれてますが、一旦 cFnをマスターしたかったので、このSSHサーバ周りのNWだけ、cFnだけでやってみています
    • それをしてよかったか, 悪かったかは 後日 CDKを学んだ時にこのブログに記します。

インポート検証環境つくり

方針

  • 本稼働環境を稼働させたままcFn管理にできると思うけれど、それを実証したい
  • ので、nginxをec2で動かして、ブラウザで1秒毎にアクセスしてやってみた
    • インターネット経由のアクセスだし、(あまり頻繁にしてブロックされると嫌なので)1秒毎で良いと思う(アドオン利用)

やったこと

私物AWSでインポート練習してみたら問題が何個も発生した

  • 練習したら、問題が何個も見つかった

問題1: ネストしたテンプレでは、実際にインポートしたいリソースをインポートできない

  • 一番上の stacks/master.yml を「既存のリソースを使用する」で読み込んでも、その .yml で読み込んでる、 cFnのNetwork Stack の Stack IDを入力する画面にしかならなかった
  • インポート機能自体が 2019/11 にできたものなので、ネスト先で使ってるリソースのインポートにはまだ対応して無くても仕方ないなと諦めた (2021/08/23)
  • 仕方ないので、 templatex/VPN.yml とかで1つずつ インポートして、それらを読み込む stacks/network/master.yml や stacks/master.yml もインポート作成操作をしようと考えた
  • CDKでこのあたり(インポート周り)も楽になるか要チェックポイント

問題2: 個別にインポートしてると Outputs を吐けない

  • templates/network/VPC.yml を使ってインポートしようとしたら↓を言われた
    • As part of the import operation, you cannot modify or add [Outputs]
  • 一旦消して先に進めた
    • でもインポートじゃない時だけ Outputs する、でいい気もする (この後やってみたい)
      • 多分 UPDATEの時( UPDATE_COMPLETE にする時) には必要そう

問題3: インポートできないリソースがある

  • AWS::EC2::Route や AWS::EC2::VPCGatewayAttachment は、インポートに非対応、と実行して初めてわかった
    • もちろん、「どれがインポートに対応してるよ」表はAWSのドキュメントにあるんだけど
    • 「次のリソースタイプは、リソースのインポートではサポートされていません: AWS::EC2::VPCGatewayAttachment 」
    • 「次のリソースタイプは、リソースのインポートではサポートされていません: AWS::EC2::Route」
    • 「次のリソースタイプは、リソースのインポートではサポートされていません: AWS::EC2::SubnetRouteTableAssociation」
  • 一旦、対応してないリソースは消して、(練習用私物AWSで)インポートしてみた
    • だが、後にそういう対処は誤りだと思った (既にある場合はリソースを作成しない、にテンプレをConditionalにするのがよさそう、と思っている 2021/08/23 23:35)

(自分の過ち: インポートするcFn Stackにも DeletionPolicy と UpdateReplacePolicy が必要)

  • The following resources to import [PubSubAPne1d, VPC, IGW, RouteTableToIGW] must have DeletionPolicy attribute specified in the template.
  • cfn-lint でも怒られてたっぽい

問題4: IMPORT_COMPLETE のステータスのスタックを親スタックはインポートできない (のでUPDATE_COMPLETEにしておく必要がある)

  • 4つを一旦インポートしてみたので、その4つをインポートして stacks/network/master.yml を作ろうと思った。
  • しかし、 ↓のように言われた
    • Stack arn:aws:cloudformation:ap-northeast-1:1728XXXXXXXX:stack/igw-20210821/ed4a66a0-0253-11ec-b20d-XXXXXXXX5d45 is not in an importable status, current stack status is IMPORT_COMPLETE.
      
  • IMPORT_COMPLETE では importable ではないらしい
  • ダメといわれた stack で、 変更セットを作成して、適用すると、 UPDATE_COMPLETE になった。そうすると、 stacks/network/master.yml でインポートしたところ、そのエラーは言われなくなった。

問題5: 0.0.0.0/0 のRoute が既にあるから追加できない と言われる

  • PubSubを一度、Route 抜きで既存リソースをインポートして作ったあとに、 Routeありにテンプレを戻して更新セット作ろうとしたら、「0.0.0.0/0 のルートはあるから」作れないよ、と言われた
  • ので、 テンプレをConditionalにしてやり直そうと考えているところである 2021/08/23 23:42

リソースが無い時だけRouteとかのリソースを作る編 2021/08/24 0:03 32min

  • あまり役立たなかった AWSドキュメント
  • 少し役立った AWSドキュメント
  • ☆ドンピシャなAWSドキュメント
  • なんか、 渡したParameterでの分岐しかできないようだった
    • 既にリソースがあるかをcFnに調べてもらって分岐はできなさそうだった
    • まだcFnは発展途上だから仕方ないかな
    • CDKでできるかは要チェックポイント
  • 途中で出てきた noecho のマスク助かる (パスワードとかを非表示にできる)
  • 「テンプレートに認証情報を埋め込まない」
  • aws::novalue擬似パラメーターは、戻り値として使用された場合、対応するリソースプロパティを削除します。

  • cFnでソフトウェアインストールとかする時は、cFnのヘルパースクリプトというのを使うらしい

その方針でやった結果

  • 概ねうまく行った
    • 最終的に、nginxを止めないまま、CloudFormation管理下に置くことに成功した

問題6: 子を持つCloudFormation stackはインポートできない

  • 一番上のmaster.yml は無くして、networkスタックとかを5つ管理しようと思った

結局インポートする時にやったこと

  • ↓のテンプレを使ってインポートするには…

    1. ↓のテンプレの templates/ の各ファイルを使って、4つのスタック(VPC, IGW, PublicSubnetRouteTable, Subnet) をインポート作成していく
    2. その際、 Outputs と importに対応してないリソース(Conditionalなリソース)の部分は手元で消して、その手元のテンプレをcFnのマネジネントコンソールでアップロードしてインポートしていく (IMPORT_COMPLETEになる)
    3. インポートしたら、Outputsと 消したリソースを戻した元の状態のテンプレをcFnマネジネントコンソールでアップロードして変更セットをつくり、反映し、 IMPORT_COMPLETE を UPDATE_COMPLETEにしておく
    1. 4つの templates のリソースをインポートできたら、network スタックを作る
    2. S3に下のテンプレを全部アップロードして、 cFnマネジネントコンソールで S3上の stacks/network/master.yml を選んでインポートする
    3. インポートできたら、 変更セットを現在のテンプレートを使用する で作って、反映し、 IMPORT_COMPLETE状態からUPDATE_COMPLETE にしておく

できたcFnのテンプレート (197行)

  • f:id:t_hazawa:20210825222213p:plain

stacks/network/master.yml (48行)

AWSTemplateFormatVersion: "2010-09-09"
Description: MyService Network Stack

Resources:
  VPC:
    Type: AWS::CloudFormation::Stack
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      Parameters:
        VpcCidrBlock: 172.31.0.0/16
      TemplateURL: 'https://myservice-cfn.s3.ap-northeast-1.amazonaws.com/templates/network/VPC.yml'

  IGW:
    Type: AWS::CloudFormation::Stack
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      Parameters:
        VpcId: !GetAtt VPC.Outputs.VpcId
        CreateOrImport: "import"
      TemplateURL: 'https://myservice-cfn.s3.ap-northeast-1.amazonaws.com/templates/network/IGW.yml'

  RouteTableToIGW:
    Type: AWS::CloudFormation::Stack
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      Parameters:
        VpcId: !GetAtt VPC.Outputs.VpcId
        IgwId: !GetAtt IGW.Outputs.IgwId
        CreateOrImport: "import"
      TemplateURL: 'https://myservice-cfn.s3.ap-northeast-1.amazonaws.com/templates/network/PublicSubnetRouteTable.yml'

  # Subnets
  PubSubAPne1d:
    Type: AWS::CloudFormation::Stack
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      Parameters:
        VpcId: !GetAtt VPC.Outputs.VpcId
        VpcCidrBlock: 172.31.32.0/20
        AvailabilityZone: "ap-northeast-1d"
        RouteTableId: !GetAtt RouteTableToIGW.Outputs.RouteTableId
        InternetAccessibility: "public"
        CreateOrImport: "import"
      TemplateURL: 'https://myservice-cfn.s3.ap-northeast-1.amazonaws.com/templates/network/Subnet.yml'

templates/network/

VPC.yml (22行)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  VpcCidrBlock:
    Type: String
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    Description: IP address class used for VPC.

Resources:
  VPC:
    Type: AWS::EC2::VPC
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      CidrBlock: !Ref VpcCidrBlock
      Tags:
        - Key: Name
          Value: vpc-cfn

Outputs:
  VpcId:
    Value: !Ref VPC

IGW.yml (34行)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  VpcId:
    Type: String
  CreateOrImport:
    Type: String
    AllowedValues: ["newly create", "import"]

Conditions: 
  CreateNewAttachment: !Equals [!Ref CreateOrImport, "newly create"]

Resources:
  IGW:
    Type: AWS::EC2::InternetGateway
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      Tags:
        - Key: Name
          Value: igw-cfn

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Condition: CreateNewAttachment
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      VpcId: !Ref VpcId
      InternetGatewayId: !Ref IGW

Outputs:
  IgwId:
    Value: !Ref IGW

PublicSubnetRouteTable.yml (41行)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  VpcId:
    Type: String
  IgwId:
    Type: String
  CreateOrImport:
    Type: String
    AllowedValues: ["newly create", "import"]

Conditions: 
  CreateRoute: !Equals [!Ref CreateOrImport, "newly create"]
    
Resources:
  RouteTable:
    Type: AWS::EC2::RouteTable
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      VpcId: !Ref VpcId
      Tags:
        - Key: Name
          Value: public-subnet-routetable-cfn

  # Routing
  # (Route for local will be created automatically as 1st priority routing)
  # PubSub-Internet Routing
  RouteToIGW:
    Type: AWS::EC2::Route
    Condition: CreateRoute
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IgwId

Outputs:
  RouteTableId:
    Value: !Ref RouteTable

Subnet.yml (52行)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  VpcId:
    Type: String
  VpcCidrBlock:
    Type: String
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
  AvailabilityZone:
    Type: String
    AllowedValues:
      - "ap-northeast-1a"
      - "ap-northeast-1c"
      - "ap-northeast-1d"
  RouteTableId:
    Type: String
  InternetAccessibility:
    # This Parameter is used only in Name tag
    Type: String
    AllowedValues:
      - "public"
      - "private"
  CreateOrImport:
    Type: String
    AllowedValues: ["newly create", "import"]

Conditions: 
  CreateRouteTableAssociation: !Equals [!Ref CreateOrImport, "newly create"]

Resources:
  Subnet:
    Type: AWS::EC2::Subnet
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      AvailabilityZone: !Ref AvailabilityZone
      VpcId: !Ref VpcId
      CidrBlock: !Ref VpcCidrBlock
      Tags:
        - Key: Name
          Value: !Join 
            ["-", [!Ref InternetAccessibility, "subnet", !Ref AvailabilityZone, "cfn"]]

  # Associate Route Table To Subnet
  AssoRouteTable:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Condition: CreateRouteTableAssociation
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      SubnetId: !Ref Subnet
      RouteTableId: !Ref RouteTableId

感想

  • 慣れたらスムーズかも
  • でも全然ワンタッチではないので、CDKに期待
  • メキメキとCloudFormation力がついていってるのが分かるのはいいね (AWS NW知識もついた)
    • CloudFormation力は CDKでも要るはずなのでいいんじゃないかなと思う

時間管理

  • 前回まで(0からcFnを学んで、cFnテンプレにして、チームメンバーに共有するまで)で 23.5時間かかっていたらしい
    • ~ 2021/7/28
  • 今回は、そのテンプレを使って私物AWSでcFn管理下にするまでで、9時間かかった