AWS Copilot を使ってスケジュールされた ECS タスクをデプロイする
2021-01-23ECS でスケジュールされたタスクを AWS CLI で手動実行したい、でもそもそもそのタスクを用意するのが大変そう。ということで、昨年夏頃に発表されて秋には GA となった AWS Copilot を使ってスケジュールされたタスクを作成してみます。CLI での実行は次回。
目次
AWS Copilot とは
GitHub の README には次のように説明されています。
The AWS Copilot CLI is a tool for developers to build, release and operate production ready containerized applications on Amazon ECS and AWS Fargate.
コンテナ化されたアプリケーションを Amazon ECS で構築・リリース・運用するための CLI です、と。ECS を使ったアプリケーションの構築には、 VPC 作ったりタスク定義作ったりと、正直簡単ではないなという印象です。 AWS Copilot を使うとその辺の設定をよしなにやってくれます。
既に公式ドキュメントやいろんな方のブログで触れられているので、詳細な使い方については下記の参考記事を参照してください。
- Services - AWS Copilot CLI
- AWS Copilot のご紹介 | Amazon Web Services ブログ
- Amazon ECSの新たなデプロイツールとなるAWS CopilotがGAに!ECS環境の構築が便利になるぞ!! | Developers.IO
- AWS Copilot JobsでECSタスクの定期実行を行う
やること
今回やることは次のとおりです。
- スケジュール実行されるタスク (アプリケーション) の実装
- AWS Copilot のインストール
- AWS Copilot を使ってデプロイ
やってみる
では順番にやっていきます。
1. スケジュール実行されるタスク (アプリケーション) の実装
まずはスケジュールされたタスクとして実行するアプリケーションの実装です。今回は、Slack のとあるチャンネルに 「Hello ECS!!!」 とポストするだけのアプリを Go で実装します。
package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"net/http"
"net/url"
"os"
)
const (
iconEmoji string = ":mega:"
userName string = "Hello ECS Job"
channel string = "dev"
)
type payload struct {
Text string `json:"text"`
IconEmoji string `json:"icon_emoji"`
UserName string `json:"username"`
Channel string `json:"channel"`
}
func getWebhookURL() (string, error) {
webhookURL := os.Getenv("SLACK_WEBHOOK_URL")
if webhookURL == "" {
return "", errors.New("The environment value SLACK_WEBHOOK_URL is required.")
}
return webhookURL, nil
}
func postToSlack(message, webhookURL string) error {
p, err := json.Marshal(payload{
Text: message,
IconEmoji: iconEmoji,
UserName: userName,
Channel: channel,
})
if err != nil {
return err
}
resp, err := http.PostForm(webhookURL, url.Values{"payload": {string(p)}})
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func main() {
m := flag.String("m", "Hello ECS!!!", "Message posted to Slack")
flag.Parse()
webhookUrl, err := getWebhookURL()
if err != nil {
fmt.Println("Failed to get Slack Webhook URL.", err.Error())
return
}
if err := postToSlack(*m, webhookUrl); err != nil {
fmt.Println("Failed to post message to Slack.", err.Error())
}
}
Slack の Webhook URL は環境変数 SLACK_WEBHOOK_URL
から取得します。。
ポストするメッセージはデフォルトで 「Hello ECS!!!」 ですが、実行時に -m
オプションで任意のメッセージを指定できるようにしています。
Copilot でデプロイする際には Dockerfile が必要になるので、下記の内容で作成しておきます。
FROM golang:1.15.5-alpine3.12 as build
ADD . .
RUN go build -o /main
FROM golang:1.15.5-alpine3.12
COPY --from=build /main /main
ENTRYPOINT [ "/main" ]
2. AWS Copilot のインストール
続いて AWS Copilot のインストールです。 macOS であれば Homebrew を使ってインストールできます。
$ brew install aws/tap/copilot-cli
$ copilot version
version: v1.1.0, built for darwin
3. AWS Copilot を使ってデプロイ
デプロイするには manifest.yml
が必要になるので、まずはそれを作成し、その後デプロイします。
manifest.yml を作成
copilot init
コマンドで manifest.yml
を作成します。
$ copilot init
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.
Application name: hello-ecs
Workload type: Scheduled Job
Job name: hello-ecs-job
Dockerfile: ./Dockerfile
Custom Schedule: */5 * * * *
Your job will run at the following times: Every 5 minutes
Would you like to use this schedule? Yes
Ok great, we'll set up a Scheduled Job named hello-ecs-job in application hello-ecs running on the schedule */5 * * * *.
✔ Created the infrastructure to manage services and jobs under application hello-ecs.
✔ Wrote the manifest for job hello-ecs-job at copilot/hello-ecs-job/manifest.yml
Your manifest contains configurations like your container size and job schedule (*/5 * * * *).
✔ Created ECR repositories for job hello-ecs-job.
All right, you're all set for local development.
Deploy: No
No problem, you can deploy your service later:
- Run `copilot env init --name test --profile default --app hello-ecs` to create your staging environment.
- Update your manifest copilot/hello-ecs-job/manifest.yml to change the defaults.
- Run `copilot job deploy --name hello-ecs-job --env test` to deploy your job to a test environment.
この時点で下記のようなディレクトリ構成になっています。
$ .
├── Dockerfile
├── copilot
│ └── hello-ecs-job
│ └── manifest.yml
└── main.go
今回、アプリケーション内では Slack の Webhook URL を環境変数から取得するようにしています。タスク実行時に環境変数を設定するため、 Webhook URL の値を SSM パラメータストアに登録しておきます。
$ aws ssm put-parameter \
--name /test/slack-webhook \
--value "https://hooks.slack.com/services/XXXXXXXXXXX" \
--type String \
--tags Key=copilot-environment,Value=test Key=copilot-application,Value=hello-ecs
ここでタグをしているのは、後のデプロイによって生成されるタスク実行ロールが下記のようなポリシーを持つからです。
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"StringEquals": {
"ssm:ResourceTag/copilot-environment": "test",
"ssm:ResourceTag/copilot-application": "hello-ecs"
}
},
"Action": [
"ssm:GetParameters"
],
"Resource": [
"arn:aws:ssm:ap-northeast-1:000000000000:parameter/*"
],
"Effect": "Allow"
},
{
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/copilot-application": "hello-ecs",
"secretsmanager:ResourceTag/copilot-environment": "test"
}
},
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": [
"arn:aws:secretsmanager:ap-northeast-1:000000000000:secret:*"
],
"Effect": "Allow"
},
{
"Action": [
"kms:Decrypt"
],
"Resource": [
"arn:aws:kms:ap-northeast-1:000000000000:key/*"
],
"Effect": "Allow"
}
]
}
SSM パラメータストア、及び SecretManager から値を取得して環境変数に設定する場合、それらのリソースには copilot-application
と copilot-environment
をタグ名として、それぞれ値を設定しておく必要があります。
そして、 copilot/hello-ecs-job/manifest.yml
を下記のように修正します。
- #secrets: # Pass secrets from AWS Systems Manager (SSM) Parameter Store.
- # GITHUB_TOKEN: GITHUB_TOKEN # The key is the name of the environment variable, the value is the name of the SSM parameter.
+ secrets: # Pass secrets from AWS Systems Manager (SSM) Parameter Store.
+ SLACK_WEBHOOK_URL: "/test/slack-webhook" # The key is the name of the environment variable, the value is the name of the SSM parameter.
これについては Copilot のドキュメントにも書かれています。
デプロイ
準備が整ったので、 test
環境にデプロイしてみます。
$ copilot job deploy --name hello-ecs-job --env test
✘ get environment test configuration: couldn't find environment test in the application hello-ecs
環境が無いらしい。ということで作ります。
$ copilot env init --name test --profile default --default-config
✔ Proposing infrastructure changes for the hello-ecs-test environment.
- Creating the infrastructure for the hello-ecs-test environment. [rollback complete] [37.1s]0s]
The following resource(s) failed to create: [InternetGateway, Cluster,
VPC]. Rollback requested by user.
- An IAM Role for AWS CloudFormation to manage resources [not started]
- An ECS cluster to group your services. [delete complete] [2.1s]
Resource creation cancelled
- An IAM Role to describe resources in your environment [not started]
- A security group to allow your containers to talk to each other [not started]
- An Internet Gateway to connect to the public internet [delete complete] [14.4s]
Resource creation cancelled
- Private subnet 1 for resources with no internet access [not started]
- Private subnet 2 for resources with no internet access [not started]
- Public subnet 1 for resources that can access the internet [not started]
- Public subnet 2 for resources that can access the internet [not started]
- A Virtual Private Cloud to control networking of your AWS resources [delete complete] [2.1s]
The maximum number of VPCs has been reached. (Service: AmazonEC2; Stat
us Code: 400; Error Code: VpcLimitExceeded; Request ID: 9571d86e-a7e6-
496b-ae4f-5a4797c1c7a0; Proxy: null)
✘ stack hello-ecs-test did not complete successfully and exited with status ROLLBACK_COMPLETE
The maximum number of VPCs has been reached.
VPC の数が上限に達したらしいです…なので、不要な VPC を削除して再度実行します。
$ copilot env init --name test --profile default --default-config
...
...
✔ Created environment test in region ap-northeast-1 under application hello-ecs.
test
環境が作成されたので、あらためてデプロイします。
$ copilot job deploy --name hello-ecs-job --env test
...
...
✔ Deployed hello-ecs-job.
このコマンドでは
Dockerfile
を元にイメージをビルド
-> ビルドしたイメージにタグ付け
-> ECR に Push
-> ECS のタスク定義を作成
が実行されます。
暫く待つと、スケジュールされたタイミングでタスクが実行されて Slack に通知が来ます。
まとめ
AWS Copilot を使ってスケジュールされた ECS タスクをデプロイしてみた話でした。 VPC 関連リソースの作成、タスク定義の作成、 ECR リポジトリの作成など、 ECS + Fargate でタスクを実行する際に必要なリソースをほぼ意識せずに簡単に作成できるのは良いなと思いました。一方で、カスタマイズした設定、例えばタスクロールにカスタマイズした Role を使いたいとかは実現が難しそうです。タスクロールのカスタマイズについては、調べている中で下記のツイートを見つけたので、参考にしたいと思います。
「スタンドアロンのタスクを起動できる Role」を例のタグ付きで別に作っておいて、そのロールを「Copilot が ECS タスク用に作ったロールから Assume できるようにしてあげる」と、Copilot タスクから別の ECS タスクを呼び出すのいけました!(ちょっとめんどくさい) pic.twitter.com/2TI1Z2JL6g
— ポジティブな Tori (@toricls) October 23, 2020
今回のコードは GitHub に置いてます。
次回は、今回作成したスケジュールされたタスクを、 AWS CLI を使って任意のタイミングで実行してみます。
※追記
このブログが AWS でコンテナ関連の開発をしている方に 見つかって 見ていただいて、下記のリプを頂きました。
Thank you 🙏 for the awesome blog post! You can also modify the task role using an Addons template https://t.co/Gvnq8YPw23
— Efe Karakus (@efekarakus) January 23, 2021
どうやら copilot/{service-name}/addons
ディレクトリに CFn テンプレートを置くことで対応できそうです。
Additional AWS Resources - AWS Copilot CLI
まさか中の人からリプが飛んでくるとは思ってなかったのですが、有益な情報をいただけて嬉しいです。ありがとございます!
comments powered by Disqus