LoginSignup
2
1

More than 3 years have passed since last update.

AWS節約術 : 自動で任意の時間にFargateを起動・停止する

Posted at

はじめに

マイクロサービスではお馴染みのAWSのコンテナ向けサーバーレスコンピューティングエンジン「Fargate」。基本料金が高額な上、一般的に1つのサービスでもいくつも動かすため、他のAWS製品と比較しても、毎月かなり高額な請求がきます。

しかし、実際のところ常に動かす必要があるわけではないはずです。本番環境は常時稼働させる必要があるかもしれませんが、dev環境や検証環境などは、使っていない時間はそれなりにあるはずです(夜間など)。

なので弊社サービスのdev環境のFargateは、平日は10時に起動、夜の22時に停止。土日は常に停止という風に自動化しています。

今回はこの自動化の方法について共有することで、「現在Fargateを使っているが、いかんせん節約したい」といった同業者の手助けになれば思い投稿しました。

構成と流れ

・Fargateのサービスを操作するプログラムを書いたLambda関数を「起動用」「停止用」の2つを作る。

・CloudWatch Eventsで「起動用」「停止用」の時間を任意に設定する。

・必要な実行ロールを付与する。

起動・停止をさせるLambda関数

今回はGo言語で実装します。

仕様は、「1つのクラスターにある複数のサービスを起動・停止する」ものとします。

まず、起動用のLambda関数が以下になります。

package main

import (
    "fmt"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/ecs"
    "github.com/aws/aws-sdk-go/aws/awserr"
)

const desiredCount int64  = 1

var cluster = "sample-cluster"

func main() {
    lambda.Start(startServices)
}

func startServices() {
    services := []string{
        "sample-service1",
        "sample-service2",
        "sample-service3",
        "sample-service4",
        "sample-service5",
    }

    for _, service := range services {
        svc := ecs.New(session.New())
        input := &ecs.UpdateServiceInput{
            Cluster:        aws.String(cluster),
            Service:        aws.String(service),
            DesiredCount:   aws.Int64(desiredCount),
        }
        result, err := svc.UpdateService(input)
        if err != nil {
            if aerr, ok := err.(awserr.Error); ok {
                fmt.Println(aerr.Code(), aerr.Error())
            }
            return
        }
        fmt.Println(result)
    }
}

やっていることはとてもシンプルで、aws-sdk-goというパッケージを用いて、ECSの指定したクラスターとサービスを起動させています。

もう少し細かく順番にみていきましょう。

まず、起動したいサービスたち(同クラスター上のものであることが条件)を列挙します。

    services := []string{
        "sample-service1",
        "sample-service2",
        "sample-service3",
        "sample-service4",
        "sample-service5",
    }

その後、指定した各サービスごとにfor文を回し、aws-sdk-goのUpdateServiceという機能で起動させていきます。

ここで事前に、引数のUpdateServiceInputという型にサービス情報を入力しますが、今回は実行するインスタンス数を示すdesiredCountは定数で全て1に設定しています。

    for _, service := range services {
        svc := ecs.New(session.New())
        input := &ecs.UpdateServiceInput{
            Cluster:        aws.String("sample-cluster"),
            Service:        aws.String(service),
            DesiredCount:   aws.Int64(desiredCount),
        }
        result, err := svc.UpdateService(input)

その後はパッケージのエラー処理。

        if err != nil {
            if aerr, ok := err.(awserr.Error); ok {
                fmt.Println(aerr.Code(), aerr.Error())
            }
            return
        }

続いて停止用のLambda関数になります。

といっても違いはdesiredCountと関数名くらいでやっていることは同じです。desiredCountを0にすることでタスクを完全に停止できます。

package main

import (
    "fmt"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/ecs"
    "github.com/aws/aws-sdk-go/aws/awserr"
)

const desiredCount int64  = 0

var cluster = "sample-cluster"

func main() {
    lambda.Start(stopServices)
}

func stopServices() {
    services := []string{
        "sample-service1",
        "sample-service2",
        "sample-service3",
        "sample-service4",
        "sample-service5",
    }

    for _, service := range services {
        svc := ecs.New(session.New())
        input := &ecs.UpdateServiceInput{
            Cluster:        aws.String(cluster),
            Service:        aws.String(service),
            DesiredCount:   aws.Int64(desiredCount),
        }
        result, err := svc.UpdateService(input)
        if err != nil {
            if aerr, ok := err.(awserr.Error); ok {
                fmt.Println(aerr.Code(), aerr.Error())
            }
            return
        }
        fmt.Println(result)
    }
}

最後にこれらをzip化して、それぞれ「起動用」と「停止用」のLambdaにアップロードします。

起動時間と停止時間を設定

それぞれのLambdaのトリガーに、CloudWatch Eventsで起動時間と停止時間をcronで設定します。

今回は、起動時間は平日の10時、停止時間は夜の22時に設定します。

停止時間を毎日に設定しているのは、休日に緊急対応で使う人がもし停止し忘れても、夜の10時には停止するようにします。

picture_pc_d839f2a948a4ab1993d602d89bcf7b81.png

picture_pc_9b820fdbab9705c3ae6b7d9adfa40dfa.png

実行ロール設定

トリガーとプログラムが実行できるような実行ロールを付与してあげます。

今回はECSのServiceに関するものと、CloudWatch Eventsに関するものを付与します。

以下がポリシーのJSONです。

{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Effect": "Allow",
           "Action": [
               "ecs:DescribeServices",
           "ecs:UpdateService",
               "events:DescribeRule",
               "events:ListRuleNamesByTarget",
               "events:ListRules",
               "events:ListTargetsByRule",
               "events:TestEventPattern",
               "events:DescribeEventBus"
           ],
           "Resource": [
               "*"
           ]
       }
   ]
}

あとはこれをそれぞれのLambda関数に実行ロールに付与すれば、自動化は完成です。

picture_pc_6b9ef66a8718f6b7ce702f58673287f4.png

結果

こうした自動化の結果、平日に12時間稼働させるだけになったdev環境のFargateは、単純計算で約65%もの費用を削減できました。

一見、EC2よりも高額に見えるFargateですが、このように効率的に運用することにより、かえってコストパフォーマンスはよくなることもあります。

この機会にどなたかの倹約に役立てれば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1