copilotを利用したらモダンなECS環境を爆速で構築できた

# この記事について
AWS & Game Advent Calendar 2020 23日目の記事になります。

AWSでコンテナを動かす場合真っ先に候補に上がるサービスはECSかと思います。
とても便利なサービスなのは間違いありませんが、いざ使うとなると考慮する点がたくさんあります。

- ネットワーク(VPC)設計
- ECSのクラスタ/サービス/タスク定義の作成
- デプロイフローの確立
- ロギング

上記はあくまで一例ですが、基本どのプロジェクトでECSを使う場合でも、タスクが起動するVPCの設計から始まり、デプロイされたタスクが出力するログをどのように集約/管理するのかまでは共通で考える必要があるのではないかと思います。
その一方で、ECSが一般的に広く使われるようになっていて、ある種の「利用する上でのベストプラクティス」的なものが生まれつつあるように思います。
都度1から構築しても良いですが、できればベストプラクティスに沿ってもっと簡単に構築できるようになれば便利ですよね。
そんな願いを叶えてくれるツールが2020/11/23にGAしています。「AWS Copilot」と言います。

# AWS Copilot is 何？
- Amazon ECS CLI の後継に当たるものです
- AWSのECSチームのエンジニアやユーザーが作り上げてきたベストプラクティスに基いてデフォルトでモダンかつプロダクションレディなインフラを構築できます

AWS Copilot のご紹介
https://aws.amazon.com/jp/blogs/news/introducing-aws-copilot/

# 実際に使ってみる

## インストール
GitHubから最新のバイナリファイルを落としてきます

```
$ curl -Lo /usr/local/bin/copilot https://github.com/aws/copilot-cli/releases/download/v0.1.0/copilot-darwin-v0.1.0 && chmod +x /usr/local/bin/copilot
$ copilot --help

👩‍✈️ Launch and manage applications on Amazon ECS and AWS Fargate.

Commands 
  Getting Started 🌱
    init        Create a new ECS application.
    docs        Open the copilot docs.
 
  Develop ✨
    app         Commands for applications.
                Applications are a collection of services and environments.

    env         Commands for environments.
                Environments are deployment stages shared between services.

    svc         Commands for services.
                Services are long-running Amazon ECS services.
 
  Release 🚀
    pipeline    Commands for pipelines.
                Continuous delivery pipelines to release services.

    deploy      Deploy your service.
 
  Settings ⚙️
    version     Print the version number.
    completion  Output shell completion code.

Flags
  -h, --help      help for copilot
  -v, --version   version for copilot

Examples
  Displays the help menu for the "init" command.
  `$ copilot init --help`
```

## サンプルアプリケーションのClone
```
$ git clone https://github.com/aws-samples/amazon-ecs-cli-sample-app.git aws-copilot-example
```

## Credentialの設定
`aws configure` でプロファイルを作成して`AWS_PROFILE`として環境変数に設定しておきます。

```
$ aws configure --profile copilot-example
$ export AWS_PROFILE=copilot-example
```

## アプリケーションのセットアップ
GitからCloneしたディレクトリに移動してでセットアップを行っていきます。
`copilot init`を実行して対話式で情報の入力をしていくことでAWS上に必要なリソースを構築することができます。

```
$ cd aws-copilot-example
$ copilot init

Note: It's best to run this command in the root of your Git repository.
Welcome to the Copilot CLI! We're going to walk you through some questions
to help you get set up with an application on ECS. An application is a collection of
containerized services that operate together.


  What would you like to name your application? [? for help] copilot-example-app

Application name: copilot-example-app

  Which service type best represents your service's architecture?  [Use arrows to move, type to filter, ? for more help]
  > Load Balanced Web Service
    Backend Service

Application name: copilot-example-app
Service type: Load Balanced Web Service

  What do you want to name this Load Balanced Web Service? [? for help] copilot-example-service

Application name: copilot-example-app
Service type: Load Balanced Web Service
Service name: copilot-example-service

  Which Dockerfile would you like to use for copilot-example-service?  [Use arrows to move, type to filter, ? for more help]
  > ./Dockerfile

Application name: copilot-example-app
Service type: Load Balanced Web Service
Service name: copilot-example-service
Dockerfile: ./Dockerfile
no EXPOSE statements in Dockerfile ./Dockerfile

  Which port do you want customer traffic sent to? [? for help] (80) 80

Application name: copilot-example-app
Service type: Load Balanced Web Service
Service name: copilot-example-service
Dockerfile: ./Dockerfile
no EXPOSE statements in Dockerfile ./Dockerfile
Port: 80
Ok great, we'll set up a Load Balanced Web Service named copilot-example-service in application copilot-example-app listening on port 80.

✔ Created the infrastructure to manage services under application copilot-example-app.

✔ Manifest file for service copilot-example-service already exists at copilot/copilot-example-service/manifest.yml, skipping writing it.
Your manifest contains configurations like your container size and port (:80).

✔ Created ECR repositories for service copilot-example-service.

All right, you're all set for local development.

  Would you like to deploy a test environment? [? for help] (y/N) y

All right, you're all set for local development.
Deploy: Yes

All right, you're all set for local development.
Deploy: Yes

✔ Created the infrastructure for the test environment.
- Virtual private cloud on 2 availability zones to hold your services     [Complete]
- Virtual private cloud on 2 availability zones to hold your services     [Complete]
  - Internet gateway to connect the network to the internet               [Complete]
  - Public subnets for internet facing services                           [Complete]
  - Private subnets for services that can't be reached from the internet  [Complete]
  - Routing tables for services to talk with each other                   [Complete]
- ECS Cluster to hold your services                                       [Complete]
- Application load balancer to distribute traffic                         [Complete]
✔ Linked account XXXXXXXXXXXX and region ap-northeast-1 to application copilot-example-app.

✔ Created environment test in region ap-northeast-1 under application copilot-example-app.
Sending build context to Docker daemon  88.06kB
Step 1/2 : FROM nginx
 ---> ae2feff98a0c
Step 2/2 : COPY index.html /usr/share/nginx/html
 ---> Using cache
 ---> ad1b08cb2bb0
Successfully built ad1b08cb2bb0
Successfully tagged XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/copilot-example-app/copilot-example-service:cee7709
Login Succeeded
The push refers to repository [XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/copilot-example-app/copilot-example-service]
2e8160029ff7: Pushed 
4eaf0ea085df: Pushed 
2c7498eef94a: Pushed 
7d2b207c2679: Pushed 
5c4e5adc71a8: Pushed 
87c8a1d8f54f: Pushed 
cee7709: digest: sha256:fe7757f53a6d9a90c57cafd32578e5b5290430cf0a06067ba92b0031fb00c873 size: 1570


✔ Deployed copilot-example-service, you can access it at http://{ELB_ENDPOINT}
```

実行が完了するとテスト環境が作成されELBのエンドポイントのURLが返ってきました。アクセスしてみましょう。
![スクリーンショット 2020-12-25 2.48.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/895851/49d6a243-6e4e-477b-89aa-8d56d567d926.png)

表示できましたね。今回はサンプルアプリケーションですが、実際の開発でこの画面を表示させるまでに構築する必要のあるAWSリソースは結構多いです。
コマンドの実行自体は10分程度で完了するので短時間で必要なものを揃えられるというのはとても魅力的です。
ちなみに裏ではCloudFormationのスタックが実行されています。

|論理ID|タイプ|
|:-:|:-:|
|AdministrationRole|AWS::IAM::Role|
|ExecutionRole|AWS::IAM::Role|
|CloudformationExecutionRole|AWS::IAM::Role|
|Cluster|AWS::ECS::Cluster|
|DefaultHTTPTargetGroup|AWS::ElasticLoadBalancingV2::TargetGroup|
|DefaultPublicRoute|AWS::EC2::Route|
|EnableLongARNFormatAction|Custom::EnableLongARNFormatFunction|
|EnableLongARNFormatFunction|AWS::Lambda::Function|
|EnvironmentManagerRole|AWS::IAM::Role|
|EnvironmentSecurityGroup|AWS::EC2::SecurityGroup|
|EnvironmentSecurityGroupIngressFromPublicALB|AWS::EC2::SecurityGroupIngress|
|EnvironmentSecurityGroupIngressFromSelf|AWS::EC2::SecurityGroupIngress|
|HTTPListener|AWS::ElasticLoadBalancingV2::Listener|
|InternetGateway|AWS::EC2::InternetGateway|
|InternetGatewayAttachment|AWS::EC2::VPCGatewayAttachment|
|PrivateSubnet1|AWS::EC2::Subnet|
|PrivateSubnet2|AWS::EC2::Subnet|
|PublicLoadBalancer|AWS::ElasticLoadBalancingV2::LoadBalancer|
|PublicLoadBalancerSecurityGroup|AWS::EC2::SecurityGroup|
|PublicRouteTable|AWS::EC2::RouteTable|
|PublicSubnet1|AWS::EC2::Subnet|
|PublicSubnet1RouteTableAssociation|AWS::EC2::SubnetRouteTableAssociation|
|PublicSubnet2|AWS::EC2::Subnet|
|PublicSubnet2RouteTableAssociation|AWS::EC2::SubnetRouteTableAssociation|
|ServiceDiscoveryNamespace|AWS::ServiceDiscovery::PrivateDnsNamespace|
|VPC|AWS::EC2::VPC|
|ECRRepocopilotDASHexampleDASHservice|AWS::ECR::Repository|
|KMSKey|AWS::KMS::Key|
|PipelineBuiltArtifactBucket|AWS::S3::Bucket|
|PipelineBuiltArtifactBucketPolicy|AWS::S3::BucketPolicy|
|CustomResourceRole|AWS::IAM::Role|
|DiscoveryService|AWS::ServiceDiscovery::Service|
|ExecutionRole|AWS::IAM::Role|
|HTTPListenerRule|AWS::ElasticLoadBalancingV2::ListenerRule|
|HTTPRulePriorityAction|Custom::RulePriorityFunction|
|HTTPWaitHandle|AWS::CloudFormation::WaitConditionHandle|
|LogGroup|AWS::Logs::LogGroup|
|RulePriorityFunction|AWS::Lambda::Function|
|Service|AWS::ECS::Service|
|TargetGroup|AWS::ElasticLoadBalancingV2::TargetGroup|
|TaskDefinition|AWS::ECS::TaskDefinition|
|TaskRole|AWS::IAM::Role|
|WaitUntilListenerRuleIsCreated|AWS::CloudFormation::WaitCondition|

これだけのリソースが構築されていました。
今のところは起動タイプがFargateのみになっていて、EC2で起動したい場合は手動で作成する必要がありそうなので
copilotをECS環境構築のスターターキットとして利用し、細かな要件や設定は自分達で追加していくといった使い方が1番しっくり来ますね。

## アプリケーションに対してのcopilotからの操作
`copilot app show`でcopilotで構築したアプリケーション/環境/サービスの情報を参照できます。

```
$ copilot app show
About

  Name              copilot-example-app
  URI               

Environments

  Name              AccountID           Region
  test              XXXXXXXXXXXX        ap-northeast-1

Services

  Name                     Type
  copilot-example-service  Load Balanced Web Service
```

起動したタスクから出力されるログはCloudWatchlogsに保存されるようになっています。
マネジメントコンソールから見てもいいですが`copilot svc logs`でログの閲覧が可能です。

```
$ copilot svc logs
Showing logs of service copilot-example-service deployed in environment test
copilot-example-service/0 10.0.1.45 - - [24/Dec/2020:18:03:01 +0000] "GET / HTTP/1.1" 200 3165 "-" "ELB-HealthChecker/2.0" "-"
copilot-example-service/0 10.0.1.45 - - [24/Dec/2020:18:03:12 +0000] "GET / HTTP/1.1" 200 3165 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" "XXX.XXX.XXX.XXX"
copilot-example-service/0 10.0.1.45 - - [24/Dec/2020:18:03:12 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" "XXX.XXX.XXX.XXX"
```

現在はテスト環境しかないので本番環境を作成してみましょう。
新たに`prod`という環境を作成して先ほど作成した`copilot-example-service`をデプロイします。

```
$ copilot env init --name prod
$ copilot svc deploy --name copilot-example-service --env prod
```

デプロイが完了するとテスト環境を作成したときと同じくELBのエンドポイント経由でECSにアクセスできるようになります。
アプリケーションの情報とログを見てみましょう。

```
$ copilot app show                                            
About

  Name              copilot-example-app
  URI               

Environments

  Name              AccountID           Region
  test              XXXXXXXXXXXX        ap-northeast-1
  prod              XXXXXXXXXXXX        ap-northeast-1

Services

  Name                     Type
  copilot-example-service  Load Balanced Web Service

$ copilot svc logs copilot-example-service --env prod
Showing logs of service copilot-example-service deployed in environment prod
copilot-example-service/7 10.0.0.206 - - [24/Dec/2020:18:20:25 +0000] "GET / HTTP/1.1" 200 3165 "-" "ELB-HealthChecker/2.0" "-"
copilot-example-service/7 10.0.0.206 - - [24/Dec/2020:18:20:30 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" "XXX.XXX.XXX.XXX"
copilot-example-service/7 10.0.0.206 - - [24/Dec/2020:18:20:30 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" "XXX.XXX.XXX.XXX"
```

正常にアクセスが来ていますね、問題なさそうです。
`copilot app show`だと若干情報が少なめですが`copilot svc status`でサービスのステータスを確認できます。

```
$ copilot svc status copilot-example-service --env prod
Showing status of service copilot-example-service deployed in environment prod
Service Status

  ACTIVE 1 / 1 running tasks (0 pending)

Last Deployment

  Updated At        6 minutes ago
  Task Definition   arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/copilot-example-app-prod-copilot-example-service:1

Task Status

  ID                Image Digest        Last Status         Health Status       Started At          Stopped At
  7788f7bc          fe7757f5            RUNNING             UNKNOWN             7 minutes ago       -

Alarms

  Name              Health              Last Updated        Reason
```

一通り動作を確認できました。費用がかかるのでお掃除しましょう。`copilot app delete`を実行します。

```
$ copilot app delete
Are you sure you want to delete application copilot-example-app? Yes
✔ Deleted service copilot-example-service from environment test.
✔ Deleted service copilot-example-service from environment prod.
✔ Deleted service copilot-example-service resources from application copilot-example-app.
✔ Deleted service copilot-example-service from application copilot-example-app.
✔ Deleted environment test from application copilot-example-app.
✔ Deleted environment prod from application copilot-example-app.
✔ Cleaned up deployment resources.
✔ Deleted application resources.
✔ Deleted application configuration.
✔ Deleted local .workspace file.
```

お掃除できました、CloudFormationで管理されているのでそこから削除すれば不要なものは残らず綺麗になりますね。

## まとめ
copilotを利用することでベストプラクティスに沿ったECS環境をお手軽に構築できました。
今回の記事ではご紹介していませんが、デプロイに利用するCodePipelineなどのCode系サービスもcopilotから構築して
GitへのPushをトリガーに自動デプロイするフローを整えることが可能ですのでまたの機会に試してみたいと思います。
まだGAされたばかりなので今後もさらに便利なアップデートが行われていくに違いありません。
私自身も定期的にcopilotのアップデートをウォッチしていきたいと思います。