Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@neruneruo

CloudFormationテンプレートを1からしっかり理解しながらECS on Fargateなアプリを自動構築する(前編)

前提条件

「CFn?IaC?何それ美味しいの?」な超初心者向け。

とは言え、AWSの機能を使おうって言うんだから、AWS BlackBeltオンラインセミナーのCloudFormation編くらいは見ていると読みやすいと思う。

あと、CFnは知らなくても同じことをマネジメントコンソールでポチポチできるくらいの理解度は前提。

文中後半で動かしているアプリは、8080ポートで待ち受けるHTTPサーバ。
SpringBootなWebアプリと考えてもらえれば。

記事中には、備忘のためにリファレンスに書かれていないデフォルト値を整理しておくが、2020年3月時点の情報であり、後でAWSが仕様を変えたとしても追従する予定はないので、挙動が違ったらリファレンスを見直してほしい。あと、今回の構成(ECS on FargateのBlue/Greenデプロイメント)以外の構成以外のデフォルト値まで調査はしていないのであしからず。

環境を整える

1から理解しながら書くので、力技でいく。
基本の流れは、「リファレンス見ながら書く→構文チェック→動かしてみる→修正する→…」の繰り返し。

というわけで、以下の記事を参考に構文チェッカをインストールしておく。

【Qiita】cfn-python-lint

pipのインストールは以下の記事を見ながら。

【AWS公式】Linux で Python、pip、EB CLI をインストールする

cfn-lintは、↓こんな感じで間違っている箇所を教えてくれる。便利!

$ cfn-lint createALB.yml
E0000 Null value at line 13 column 22
createALB.yml:13:22

とりあえず空っぽのALBを作ってみる

スタックの作成

まずは、ALBを作ってみる。最初はEC2じゃないのかよ!というツッコミがあるのは重々承知しているが、とりあえずALBで。

こういうことをやっているサイトは探してみれば色々とあるものの、結局最後に頼るのはAWS公式のユーザーガイドのリファレンスなのである。これを見ながらしっかり理解していく。
リファレンスで AWS::ElasticLoadBalancingV2::LoadBalancer を選択して、プロパティを設定していく。「必須: いいえ」になっているプロパティがたくさんあるので、そういったものはとりあえずザクザク削っていってみよう。削った場合のデフォルト値が書いてないから不安になるけど、気にしない。

createALB.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  ALB Create

Resources:
  # ------------------------------------------------------------#
  #  ALB
  # ------------------------------------------------------------#
  ALB: 
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      Name: CFn-test-ALB
      Subnets: 
        - [自分のサブネットID①]
        - [自分のサブネットID②]
        - [自分のサブネットID③]

えっ、こんなにシンプルでいいの?と思いつつ、マネコンのCloudFormationの「スタック」で「スタックの作成」ボタンを押す。選択肢が出るので「新しいリソースを使用(標準)」を選択する。

キャプチャ.PNG

こんな感じでYAMLのファイルをアップロードをする。
あとは、「次へ」で進みまくる。細かい設定は今は気にしなくてOK。スタックの名前は「CFn-test」とか適当につけておく。

流し終わると↓こんな感じになる。

キャプチャ2.PNG

ちゃんとCREATE_COMPLETEされているので、ALBを見に行くと、

キャプチャ3.PNG

ちゃんとできてる!すごい!

ちなみに、省略したプロパティのデフォルト値でリファレンスに書いていない値は、ALBの場合は↓こんな感じになるようである。「ALBの場合は」と書いてあるのは、NLBの場合はデフォルト値が違う可能性があるから。試していないので分からない。
リファレンスに書いておいてほしい…。

プロパティ デフォルト値
IpAddressType ipv4
LoadBalancerAttributes なし?
SecurityGroups default VPC security group
SubnetMappings/Subnets 省略不可。省略すると cfn-lint で E2523 Only one of [SubnetMappings, Subnets] should be specified for Resources/ALB/Properties のエラーになる
Tags なし

作成したスタックを修正する

さて、作られたALBを見てみると、セキュリティグループがデフォルトになっている。
さすがにこれはちょっとセキュリティ的にアレなので、テンプレートのYAMLのALBのプロパティに以下を追加する。

      SecurityGroups: 
        - [セキュリティグループID]

セキュリティグループは、新規で作るか、既存のを持ってくるか。
今回は、インバウンドの80, 8080ポートを開放している既存のセキュリティグループをアタッチする。

テンプレートを修正したら、スタックの詳細画面で「更新」を押して、テンプレートを差し替えていく。
ステータスが UPDATE_COMPLETE になったら、もう一度ALBの設定を見てみると、

キャプチャ4.PNG

無事、設定が反映されている。

作成したスタックを削除する

これも簡単で、スタックの詳細画面から「削除」すれば良い。
CloudFormationで作成したスタックのリソースは、スタックの削除に紐付けてまとめて掃除してくれるらしい。便利!

ちなみに、マネジメントコンソールから先回りしてALBを削除をしておいたらどうなるか実験したが、問題なく DELETE_COMPLETE のステータスになった。最終結果が合えば良いらしい。

ALBのリスナー・ターゲットグループを設定する

このセクションでは、空っぽのALBを完成品にして、80ポートで待ち受けしているEC2インスタンスにフォワードするところまで。待ち受けするEC2インスタンスを立てる手順をここで書くのは本筋とは違うので、分からなければこの記事docker run -p 80:8080 ... しているあたりまでやっておく。テンプレートの中で作ってもいいけど、それも面倒なので今回は有り物を使うということで。

今回の完成品のテンプレートのYAMLは以下。ALBのリソース部分は前回と同じ。

createALB_TG.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  ALB Create

Resources:
  # ------------------------------------------------------------#
  #  ALB
  # ------------------------------------------------------------#
  ALB: 
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      Name: CFn-test-ALB
      SecurityGroups: 
        - [セキュリティグループID]
      Subnets: 
        - [自分のサブネットID①]
        - [自分のサブネットID②]
        - [自分のサブネットID③]
  ALBLISTENER: 
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      DefaultActions: 
        - TargetGroupArn: !Ref ALBTG
          Type: forward
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP
  ALBTG:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      Name: CFn-test-ALB-TG
      Targets: 
        - Id: [EC2インスタンスID]
          Port: 80
      Port: 80
      Protocol: HTTP
      VpcId: [VPCID]

ALBのターゲットグループ

ALBのリソースとほぼ同じような記述方法なので、特に目新しい部分は無いと思う。

ターゲットグループのプロパティで、リファレンスにALBの場合のデフォルト値が明記されていないものは以下。
なお、PortProtocol はリファレンスで「必須: いいえ」と書いてあったし cfn-lint でも引っかからなかったので指定をしなかったら、CREATE の際にエラーになったので指定した。

プロパティ デフォルト値
Matcher HttpCode: "200"
Name AWS払い出しの名前
TargetGroupAttributes それぞれの属性のプロパティのデフォルト値(リファレンスに書いてある)
Targets なし

ALBのリスナー

ここはデフォルト値がリファレンスに明記されているので分かりやすかった。

ポイントは以下の部分。

      DefaultActions: 
        - TargetGroupArn: !Ref ALBTG
          Type: forward
      LoadBalancerArn: !Ref ALB

リスナーの定義には、ALBのARNとターゲットグループのARNが必須パラメータになっているが、どちらもこのテンプレート内でリソースを作成しているので、あらかじめ作っておいたリソースのARNをマネジメントコンソールから転記することができない。なので、同一テンプレート内で作成したリソースのARNを参照するためのCloudFormationの機能を使うのである。
※そういう意味では、ターゲットグループのVPCやインスタンスなんかも、同じテンプレート内でリソース定義してRefすれば全部ひっくるめてコード化できるけど、今回はやらなかった。

各リソースに対して !Ref した場合に何が取得できるかは、ユーザーガイドの各リファレンスの「戻り値」に 記載されている。

ECSでサービスを起動する

ECSで起動するためのALBの設定の見直し

最終的にBlue/GreenデプロイメントできるECSの起動を行いたいので、ALBはBlue用とGreen用の2つの待ち受けをできるようにする。つまり、ターゲットとリスナーをそれぞれ2つ定義すれば良い。
また、Blue/Greenデプロイメント環境にするのであれば、ターゲットグループのタイプ指定を ip にしておく必要がある。

Outputsは、テンプレートファイルを分けるなら必要。
ALB作成のテンプレートのアウトプットをエクスポートして、次のECS作成してインポートすることが可能。

  ALBLISTENER1: 
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      DefaultActions: 
        - TargetGroupArn: !Ref ALBTG1
          Type: forward
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP
  ALBLISTENER2: 
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties: 
      DefaultActions: 
        - TargetGroupArn: !Ref ALBTG2
          Type: forward
      LoadBalancerArn: !Ref ALB
      Port: 8080
      Protocol: HTTP
  ALBTG1:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      Name: CFn-test-ALB-TG1
      Port: 80
      Protocol: HTTP
      TargetType: ip
      VpcId: [VPCID]
  ALBTG2:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties: 
      Name: CFn-test-ALB-TG2
      Port: 8080
      Protocol: HTTP
      TargetType: ip
      VpcId: [VPCID]

Outputs:
  TargetGroupArn1:
    Description: ALB Target Group Arn 1
    Value: !Ref ALBTG1
    Export:
      Name: !Sub ${AWS::StackName}-TargetGroupArn1
  TargetGroupArn2:
    Description: ALB Target Group Arn 2
    Value: !Ref ALBTG2
    Export:
      Name: !Sub ${AWS::StackName}-TargetGroupArn2

ECSクラスターの作成

これは悩むことはないはず。適当に名前を付ければOK。

  ECSCLUSTER:
    Type: AWS::ECS::Cluster
    Properties: 
      ClusterName: CFn-test-Cluster

ロググループの作成

最悪無くても良いのだが、CloudWatch Logsにコンテナのログを出力しておかないと、何かあった時に手も足も出なくなってしまうので、新規のロググループをリソースに追加しておくことをオススメする。

  LOGGROUP:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /ecs/CFn-test-Container

タスク定義

ここはプロパティが大量。
今回は Fargate でコンテナ起動をすることを前提にしているので、Fargate対象外のプロパティのデフォルト値調査は割愛。

プロパティ デフォルト値
IpcMode よく分からない…
NetworkMode host
ProxyConfiguration AppMeshのプロキシ設定無効
Tags 無し

タスク定義の中のコンテナ定義( ContainerDefinitions )も色々と設定項目がある。
ただ、ここはそもそものコンテナ定義の理解が必要なのと、その理解にはかなり時間がかかりそうなので、今回は必要な設定のみを考える。

プロパティ デフォルト値
Family AWS払い出しの名前

最終的に、タスク定義のリソースについては↓こんなイメージ。
ARNの指定については、!Sub ${AWS::AccountId}!Sub ${AWS::Region}などを使ってなるべくベタ書きをしないようにした方が良い。
LogConfiguration は↑で作成したロググループをコンテナに紐付けるプロパティ。ログ出力しないのであれば不要。

  ECSTASKDEFINITION:
    Type: AWS::ECS::TaskDefinition
    Properties: 
      RequiresCompatibilities: 
        - FARGATE
      Cpu: "256"
      ExecutionRoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole
      Family: CFn-test-task
      Memory: "512"
      NetworkMode: awsvpc
      TaskRoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole
      ContainerDefinitions: 
        - Name: CFn-test-Container
          Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-greeting-web:52c324b
          Memory: 512
          PortMappings: 
            - ContainerPort: 8080
              HostPort: 8080
              Protocol: HTTP
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LOGGROUP
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: ecs

ECSサービスの作成

ここもプロパティがたくさんあって大変。

これ、死ぬほど悩んだのだが、日本語ドキュメントで翻訳されていない DeploymentController というプロパティがあって、これがマネジメントコンソール上でサービスを作成するときの「デプロイメントタイプ」に該当する。デフォルトがECSなので、Blue/Greenデプロイメントができないように思えてしまうけど、実際には指定したら作れた。

プロパティ デフォルト値
DeploymentConfiguration MinimumHealthyPercent: 100, MaximumPercent: 200
DeploymentController ECS
EnableECSManagedTags よく分からない…
HealthCheckGracePeriodSeconds 0
LaunchType EC2
LoadBalancers 無し
PlacementConstraints 制約無し
PlacementStrategies 戦略無し
PropagateTags よく分からない…
SchedulingStrategy REPLICA
ServiceName AWS払い出しの名前
ServiceRegistries 無し
Tags 無し

サービスの設定については最終的に以下のような感じになる。
ここで、ALBの設定のときにエクスポートした値を !ImportValue CFn-test-TargetGroupArn1 して取り込んでいる。

  ECSSERVICE:
    Type: AWS::ECS::Service
    Properties: 
      Cluster: !Ref ECSCLUSTER
      ServiceName: CFn-test-ECSService
      LaunchType: FARGATE
      DesiredCount: 1
      DeploymentController:
        Type: CODE_DEPLOY
      LoadBalancers: 
        - ContainerName: CFn-test-Container
          ContainerPort: 8080
          TargetGroupArn: !ImportValue CFn-test-TargetGroupArn1
      NetworkConfiguration: 
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups: 
            - [セキュリティグループID]
          Subnets: 
            - [自分のサブネットID①]
            - [自分のサブネットID②]
            - [自分のサブネットID③]
      TaskDefinition: !Ref ECSTASKDEFINITION

ここまでやると、Blue/GreenデプロイメントするためのECS on Fargateが1アクションないし2アクションで起動する。

さて、ここからCI/CDパイプラインまで自動構築してみよう!というところで中編に続く。

2
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What is going on with this article?