はじめに

AWS のリソースを構築するための Infrastructure as Code なツールがいくつかあります。また最近は ECS 関連にフォーカスしたデプロイツールなども公式・非公式を問わずいくつか出てきています。
その中でも今回は AWS Copilot を試したのでその雑感を記してみます。

AWS Copilot とは

Copilot CLI は、AWS App Runner、Amazon ECS および AWS Fargate を利用したプロダクションレディなコンテナ化されたアプリケーションのビルド、リリース、そして運用のためのツールです。 開発のスタートからステージング環境へのプッシュ、そして本番環境へのリリースまで、Copilot はアプリケーション開発ライフサイクル全体の管理を容易にします。

Overview - AWS Copilot CLI

とあります。
さきほど Infrastructure as Code という言葉を出しましたが、Copilot は一部界隈で言及されている Architecture as Code という概念がフィットしそうです。
Copilot における CLI コマンド実行時のコマンドや manifest.yml という設定ファイルには、VPC や ECS などという AWS リソースに関する記述は極力遠ざけようという思想が見受けられます。替わりに Application や Service という、より一般的な概念を採用し、ユーザが AWS のリソースに詳しくなくてもアプリケーションの実行環境を容易に作れるようになることを目的としているようです。

良いところ

  • VPC、Subnet、SecurityGroup、ALB、ECSサービスなどなどの細かな要件を知らなくても、とりあえずコンテナベースのサービスやジョブを実装することができます。とっても手軽!
  • できあがった実装を観察することで、AWS 経験者は AWS の考える「ベストプラクティス」を勉強することができます。
  • 日本語の公式ドキュメントがあります。

使いづらいところ

「できないことがある」あるいは「AWS のリソースあるいは CloudFormation に詳しくないとできないことがある」と感じるところがたくさんありました。

Sidecars

現時点ではサイドカーはリモートイメージの利用のみをサポートしており

Sidecars - AWS Copilot CLI

たとえば同じソースコードを配置した nginx のコンテナと php-fpm のコンテナを互いにサイドカーとしてひとつのタスクに盛り込んで運用したい場合は Copilot のパラダイムではうまくいきません。

ドメイン名

将来的には証明書をインポートしたり、任意のエイリアスを使用できるようにして、この機能をより強力なものにする予定です!

Domain - AWS Copilot CLI

柔軟なドメイン設計はできません。

複数の Service あるいは Job での RDS、S3 あるいは SQS などの共用

Copilot においては Service や Job は各々別々に deploy する対象です。これらは同じ環境下に配置できますが基本的には互いに関心が分離されています。
このとき、たとえば各々の Service や Job が共用するリソースを配置しようとすると、Copilot 特有の面倒さが頭をもたげてきます。
このような「共用するリソースの実装」は Addon という概念下で CloudFormation のテンプレートをゴニョゴニョすることで実現する必要が出てきます。
たとえば、Service-A、Service-B および Job-A からなる Application において、

  • RDS: Service-A と Service-B から利用される
  • S3: Service-B と Job-A から利用される

という要件を満たそうとするとき各々後述のワークアラウンドが必要になります。

RDS

すべての Service あるいは Job(Step Function で実行される runtask 時に)は互いに共通の SecurityGroup ${App}-${Env}-EnvironmentSecurityGroup が付与されます。RDS に付与されている SecurityGroup の Ingress にこの「共通 SecurityGroup」からのアクセスを許容するレコードを追加することで Service-A も Service-B も当該 RDS へのアクセスが可能になります。
仮に RDS を Service-A の Addon として設置した場合、Service-A が構築された時点ですでに RDS にアクセス可能となっています。Service-B から当該 RDS へのアクセスを許容するには /copilot/Service-A/addons/rds.yml に以下の記述を足すことで実現できます。

  ClusterDBClusterSecurityGroup:
    Metadata:
      'aws:copilot:description': 'A security group for your DB cluster appcluster'
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: The Security Group for the database cluster.
      SecurityGroupIngress:
        - ToPort: 3306
          FromPort: 3306
          IpProtocol: tcp
          Description: !Sub 'From the Aurora Security Group of the workload ${Name}.'
          SourceSecurityGroupId: !Ref ClusterSecurityGroup
+       - ToPort: 3306
+         FromPort: 3306
+         IpProtocol: tcp
+         Description: From Environment Security Group
+         SourceSecurityGroupId:
+           Fn::ImportValue:
+             !Sub '${App}-${Env}-EnvironmentSecurityGroup'
      VpcId:
        Fn::ImportValue:
          !Sub '${App}-${Env}-VpcId'

参考: How to share RDS resource between services · Issue #2495 · aws/copilot-cli

S3

Service あるいは Job において、S3 へのアクセスを許容する AWS::IAM::ManagedPolicy を記述します。 仮に S3 を Service-B の Addon として設置した場合、Service-B が構築された時点ですでに S3 にアクセス可能となっています。Job-A から当該 S3 バケットへのアクセスを許容するには /copilot/Job-B/addons/s3-access.yml という CloudFormation 用のテンプレートファイルを置くことで実現できます。

  BucketAccessPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: !Sub Grants READ access to the S3 bucket ${BucketName}
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: S3ObjectActions
            Effect: Allow
            Action:
              - s3:GetObject
            Resource: !Sub "arn:aws:s3:::${App}-${Env}-web-${BucketName}/*"
          - Sid: S3ListAction
            Effect: Allow
            Action: s3:ListBucket
            Resource: !Sub "arn:aws:s3:::${App}-${Env}-web-${BucketName}"

参考: Share same S3 storage between services? · Issue #2138 · aws/copilot-cli

おわりに

とても良いツールでした!しかし、私たちが現状で求める要件を満たすように使うにはまだまだ手間がかかるようです。
ツールの有用性はそのユーザと要件に依存します。これらを加味してツールは選択すべきだなと改めて認識させられました。
AWS Copilot のこれからの発展にも期待大です!
また、私の使い方が悪いため手間がかかっているだけかもしれません。「こういう使い方があるよ」というアドバイスがありましたらどんどんお寄せいただけたら嬉しいです。

補足

日本語の公式ドキュメントが存在しているためそちらから引用していますが、疑問に感じたときなどは原典としての英語のドキュメントにあたっていただくと良いかと思います。
なお、ドキュメントサイトのソースは copilot-cli/site/content/docs at mainline · aws/copilot-cli からご覧になれます。