AWS SDK for Go V2 を使って Fargate for ECS でタスクを実行してコンテナが起動するまでの時間を計測してみた
2021-02-20ECS と Fargate を使ってバッチ処理とか実行するのは楽だなという気持ちになっているのですが、気になるのはタスクを作成してから実際にアプリケーションが実行されるまでの時間です。今回はその時間を計測してみます。最近リリースされた Go 1.16 と AWS SDK for Go V2 を使って計測用のコードを書きました。
目次
はじめに
Fargate for ECS でタスクを実行し、実際に Fargate でコンテナが起動するまでの時間を計測します。
タスクの実行時には、タスクの情報として CreatedAt
が記録され、コンテナ起動時には StartedAt
が記録されます。なので、この差分を計測するのが今回の目的です。
AWS CLI を使う場合、タスクの実行には ecs run-task
コマンドを、タスクの情報を取得するには ecs-describe-tasks
コマンドをそれぞれ実行します。ただ、コンテナ起動までの時間をコマンドで計測するのはちょっとめんどくさそうなので、今回はタスクの実行から時間の計測までを行うスクリプトを AWS SDK for Go V2 を使って実装してみます。
また、最近リリースされた Go 1.16 の機能の中から go:embed
も使ってみます。
なお、今回の計測にあたっては下記の記事を参考にさせていただきました🙇🏻♂️
タスクで実行するコンテナイメージ
今回は起動すればいいだけなので、下記のような Dockerfile
でイメージを作成します。
FROM busybox:latest
ARG PAD=0
RUN dd if=/dev/urandom of=/padding bs=1M count=$PAD
ビルド時に PAD
に値を指定して、任意のサイズのイメージを作成します。今回は 256MB のイメージを作成します。
$ docker build -t fargate-speed-test . --build-arg PAD=256
# イメージサイズの確認 ("だいたい" 256 MB)
$ docker images fargate-speed-test
REPOSITORY TAG IMAGE ID CREATED SIZE
fargate-speed-test latest fc479f1d67e2 4 hours ago 270MB
これを ECR に push しておきます。このあたりの詳しいコマンド操作については前回の記事を参照してください。
ECS タスクを作って実行して CloudWatch でログを確認するまでを AWS CLI だけでやってみた - michimani.net
起動時間を計測するコード
タスクを実行してコンテナ起動までの時間を計測するコードを書きます。
package main
import (
"context"
_ "embed"
"encoding/json"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
)
type AppConfig struct {
Cluster string `json:"cluster"`
TaskDefinitionArn string `json:"taskDefinitionArn"`
SubnetID string `json:"subnetId"`
Region string `json:"region"`
}
//go:embed config.json
var configJson []byte
// runECSTask runs ECS task
func runECSTask() error {
var appConfig AppConfig
if jerr := json.Unmarshal(configJson, &appConfig); jerr != nil {
return jerr
}
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(appConfig.Region))
if err != nil {
return err
}
client := ecs.NewFromConfig(cfg)
runTaskIn := &ecs.RunTaskInput{
TaskDefinition: aws.String(appConfig.TaskDefinitionArn),
Cluster: aws.String(appConfig.Cluster),
NetworkConfiguration: &types.NetworkConfiguration{
AwsvpcConfiguration: &types.AwsVpcConfiguration{
Subnets: []string{
appConfig.SubnetID,
},
AssignPublicIp: types.AssignPublicIpEnabled,
},
},
LaunchType: types.LaunchTypeFargate,
}
runOut, rerr := client.RunTask(context.TODO(), runTaskIn)
if rerr != nil {
return rerr
}
taskArn := aws.ToString(runOut.Tasks[0].TaskArn)
fmt.Println("Task Created.")
fmt.Println("\tTaskARN: ", taskArn)
fmt.Printf("\taws ecs describe-tasks --cluster %s --tasks %s\n\n", appConfig.Cluster, taskArn)
waiter := ecs.NewTasksStoppedWaiter(client)
waitParams := &ecs.DescribeTasksInput{
Tasks: []string{taskArn},
Cluster: aws.String(appConfig.Cluster),
}
maxWaitTime := 5 * time.Minute
if werr := waiter.Wait(context.TODO(), waitParams, maxWaitTime); werr != nil {
return werr
}
describeIn := &ecs.DescribeTasksInput{
Tasks: []string{taskArn},
Cluster: aws.String(appConfig.Cluster),
}
stopOut, serr := client.DescribeTasks(context.TODO(), describeIn)
if serr != nil {
return serr
}
createdAt := *stopOut.Tasks[0].CreatedAt
startedAt := *stopOut.Tasks[0].StartedAt
stoppedAt := *stopOut.Tasks[0].StoppedAt
takenTime := startedAt.Sub(createdAt)
fmt.Println("Task Stopped.")
fmt.Println("\tTaskARN: ", aws.ToString(stopOut.Tasks[0].TaskArn))
fmt.Println("\tCreatedAt: ", createdAt)
fmt.Println("\tStartedAt: ", startedAt)
fmt.Println("\tStoppedAt: ", stoppedAt)
fmt.Printf("\tTakenTimeToStart: %s", takenTime)
return nil
}
func main() {
if err := runECSTask(); err != nil {
fmt.Println(err.Error())
}
}
中身を簡単に解説しておきます。
go:embed
import (
_ "embed"
)
//go:embed config.json
var configJson []byte
この部分で、 Go 1.16 で実装された go:embed
を使っています。
何をしているかというと、上の記述ではこの main.go
と同じディレクトリにある config.json
を byte のスライスとして configJson
に代入しています。そしてその configJson
を AppConfig
の構造体に変換しています。
go:embed
を使って埋め込まれるファイルはバイナリにも含まれるため、ウェブアプリケーションであれば画像や CSS などを go:embed
で埋め込むことで諸々を一つのバイナリに含めることができます。 go:embed
および Go 1.16 のその他のリリース内容については下記の記事で詳しく解説されています。
config.json
では、 ECS タスクの実行に最低限必要な情報を保持しています。
{
"cluster": "fargate-speed-test-cluster",
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:000000000000:task-definition/fargate-speed-test:5",
"subnetId": "subnet-06ee4b00000000000",
"region": "ap-northeast-1"
}
ECS タスクの実行
下記の部分で ECS タスクを実行しています。
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(appConfig.Region))
if err != nil {
return err
}
client := ecs.NewFromConfig(cfg)
runTaskIn := &ecs.RunTaskInput{
TaskDefinition: aws.String(appConfig.TaskDefinitionArn),
Cluster: aws.String(appConfig.Cluster),
NetworkConfiguration: &types.NetworkConfiguration{
AwsvpcConfiguration: &types.AwsVpcConfiguration{
Subnets: []string{
appConfig.SubnetID,
},
AssignPublicIp: types.AssignPublicIpEnabled,
},
},
LaunchType: types.LaunchTypeFargate,
}
runOut, rerr := client.RunTask(context.TODO(), runTaskIn)
if rerr != nil {
return rerr
}
ecs - Client.RunTask · pkg.go.dev
TasksStoppedWaiter でタスクが停止するまで待機
下記の部分で、実行したタスクが停止するまで待機しています。
waiter := ecs.NewTasksStoppedWaiter(client)
waitParams := &ecs.DescribeTasksInput{
Tasks: []string{taskArn},
Cluster: aws.String(appConfig.Cluster),
}
maxWaitTime := 5 * time.Minute
if werr := waiter.Wait(context.TODO(), waitParams, maxWaitTime); werr != nil {
return werr
}
ecs - NewTasksStoppedWaiter · pkg.go.dev
これにより、タスクが終了した状態のタスクの状態を取得でき、タスク実行からコンテナが起動するまでの時間を計測することができます。
実行してみる
$ go run main.go
Task Created.
TaskARN: arn:aws:ecs:ap-northeast-1:000000000000:task/fargate-speed-test-cluster/fc2a03112a0000000000xxxxxxxxxxxx
aws ecs describe-tasks --cluster fargate-speed-test-cluster --tasks arn:aws:ecs:ap-northeast-1:000000000000:task/fargate-speed-test-cluster/fc2a03112a0000000000xxxxxxxxxxxx
Task Stopped.
TaskARN: arn:aws:ecs:ap-northeast-1:000000000000:task/fargate-speed-test-cluster/fc2a03112a0000000000xxxxxxxxxxxx
CreatedAt: 2021-02-19 15:33:20.308 +0000 UTC
StartedAt: 2021-02-19 15:33:58.187 +0000 UTC
StoppedAt: 2021-02-19 15:34:41.937 +0000 UTC
TakenTimeToStart: 37.879s
37.879s
かかったことがわかりました。
まとめ
AWS SDK for Go V2 を使って Farget for ECS でタスクを実行して、実際にコンテナが起動するまでの時間を計測してみた話でした。ついでに、 Go 1.16 で盛り込まれた go:embed
も使ってみました。
今後やりたいこととしては、 ECS_IMAGE_PULL_BEHAVIOR
を有効にしたときに起動までの時間がどれくらい短くなるのかを計測したいと思っています。
ECS (EC2) だと、コンテナイメージをキャッシュするための設定として ECS_IMAGE_PULL_BEHAVIOR
が使えるのですが、この記事を書いている時点では Fargate に対応していません。
Amazon ECS on EC2でキャッシュされたコンテナイメージを使用するには? | DevelopersIO
なので、次はこの設定が使えるようになったら改めて計測して、どれだけ早くなるのか試してみたいと思います。
comments powered by Disqus