0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CloudWatchのルール名とECSのタスクスケジューラー

Last updated at Posted at 2020-04-09

はじめに

シェルでECSのタスクスケジューラーを登録するスクリプトを書いていた。
複数の環境で同じタスクをECSのスケジュールに登録する際、ハマったことを記事に残しておく

シェルの内容

流れとしては以下のようなスクリプト
1)タスク定義を作成
2)CloudWatchのルールを作成
3)1と2を紐付けたものをECSのタスクスケジューラーに登録

シェル全文
# !/bin/bash
set -eu

# $1 : stg or prod
ENV_ARG=$1
args_count=$#

# 引数チェック
check_args() {
    if [ $args_count -ne 1 ]; then
        echo "指定された引数は $args_count 個です。" 1>&2
        echo "実行するには 1 個の引数が必要です。" 1>&2
        exit 0
    fi
    
    if [ "$ENV_ARG" = prod ]; then
        ENV_NAME_JP="本番環境"
        elif [ "$ENV_ARG" = stg ]; then
        ENV_NAME_JP="ステージング環境"
    else
        echo "第1引数には、stgまたはprodを指定してください" 1>&2
        exit 0
    fi
}

select_branch() {
    DEFAULT_BRANCH_NAME=master
    
    echo "$ENV_NAME_JP へのデプロイを実行します。"
    
    if [ "$ENV_ARG" = stg ]; then
        echo 'デフォルト値は Enter キーを入力してください。'
        while true; do
            read -p "> ブランチ名を指定してください。[$DEFAULT_BRANCH_NAME] : " BRANCH_NAME
            case $BRANCH_NAME in
                "" ) BRANCH_NAME=$DEFAULT_BRANCH_NAME
                    echo ""
                break;;
                * ) echo ""
                break;;
            esac
        done
    else
        BRANCH_NAME=$DEFAULT_BRANCH_NAME
    fi
}

fetch_SHA1() {
    echo "$BRANCH_NAME ブランチの最新のコミットSHA1を検索しています...\n"
    BRANCH_SHA1=$(git ls-remote $(git config --get remote.origin.url) $BRANCH_NAME | awk '{ print $1 }')
    
    # リモートリポジトリ存在チェック
    if [ -z $BRANCH_SHA1 ]; then
        echo "リモートブランチが存在しないため最新のコミットSHA1が取得できません。" 1>&2
        exit 0
    fi
}

check_ecr_build_artifacts () {
    echo "$BRANCH_SHA1 のタグのイメージを検索しています...\n"
    
    APP_IMAGE=$(aws ecr list-images --repository-name sample-api --query "imageIds[*].imageTag")
    
    if echo $APP_IMAGE | jq .[] | xargs echo | grep -q $BRANCH_SHA1; then
        echo "$BRANCH_SHA1 のタグのsample-apiのイメージが見つかりました\n"
    else
        echo "$BRANCH_SHA1 のタグのsample-apiのイメージが見つかりませんでした"
        exit 0
    fi
}

get_commmit_message () {
    if [ ${#BRANCH_SHA1} -gt 0 ]; then
        echo "git fetch しています...\n"
        $(git fetch origin $BRANCH_NAME &> /dev/null)
        COMMIT_MESSAGE=$(git log --format=%B -n 1 $BRANCH_SHA1 2>/dev/null | awk 'NR==1')
    fi
    COMMIT_MESSAGE=${COMMIT_MESSAGE:-"[取得出来ませんでした。]"}
}

select_release_file() {
    while true; do
        read -p "> リリースする対象ファイル名を入力してください。: " file_name
        case $file_name in
            "" ) continue;;
            *) break;;
        esac
    done
    
    file_name_ext="$file_name.json"
    
    ls -l docker/batch/commands/$file_name_ext 2>&1 > /dev/null
    
    release_file_name=$file_name_ext
}

pick_params_from_json() {
    task_name=$(cat docker/batch/commands/$release_file_name | jq -r '.name')
    cron=$(cat docker/batch/commands/$release_file_name | jq -r '.cron')
    description=$(cat docker/batch/commands/$release_file_name | jq -r '.description')
    command=$(cat docker/batch/commands/$release_file_name | jq -r '.command')
}

confirm_release() {
    echo "========================================"
    echo "> 環境               : $ENV_NAME_JP" 1>&2
    echo "> ブランチ名         : $BRANCH_NAME" 1>&2
    echo "> コミットSHA1       : $BRANCH_SHA1" 1>&2
    echo "> コミットメッセージ : $COMMIT_MESSAGE" 1>&2
    echo "> タスク名           : $task_name" 1>&2
    echo "> cron情報           : $cron" 1>&2
    echo "> 説明               : $description" 1>&2
    echo "> 実行コマンド       : $command" 1>&2
    echo "========================================"
    
    while true; do
        read -p "> 以上でスケジュールタスクを作成してもよろしいですか? (y/n) [n] : " yn
        case $yn in
            [Yy] ) break;;
            [Nn] ) echo "終了します。\n"
            exit 0;;
            "" ) echo "終了します。\n"
            exit 0;;
            * ) echo "(y/n) で入力してください。\n";;
        esac
    done
}

# タスク定義の作成
create_task_definision() {
    export COMMAND=$command
    export SHA1=$BRANCH_SHA1
    export LOG_GROUP="/ecs/sample-batch-${ENV_ARG}"
    
    task_definition_name="sample-batch-$ENV_ARG-${task_name}"
    
    ecs-cli configure \
    --cluster sample-${ENV_ARG} \
    --region ap-northeast-1 \
    --default-launch-type FARGATE
    
    ecs-cli compose \
    --project-name ${task_definition_name} \
    --file docker/docker-compose.${ENV_ARG}.yml \
    --ecs-params docker/ecs-params.${ENV_ARG}.yml \
    create
    
    task_arn=$(aws ecs describe-task-definition --task-definition $task_definition_name --query 'taskDefinition.taskDefinitionArn' --output text)
    echo "\nタスク定義を作成しました。タスク定義ARN: $task_arn\n"
}

# CloudWatchのルール作成
create_cloudwatch_rule() {
    rule_arn=$(aws events put-rule --schedule-expression "$cron" --name $task_name --description "$description" --query 'RuleArn' --output text)
    echo "CloudWatchのルールを作成しました。ルールARN: $rule_arn\n"
}

# タスク定義とルールの紐付け
associate() {
    cluster_arn="arn:aws:ecs:ap-northeast-1:784286759117:cluster/sample-${ENV_ARG}"
    
    json=$(cat "./docker/batch/env/${ENV_ARG}.json" | jq '.[0].Id = "'$task_name'" | .[0].Arn = "'$cluster_arn'" | .[0].EcsParameters.TaskDefinitionArn = "'$task_arn'"')
    
    aws events put-targets --rule $task_name --targets "$json"
    
    echo "\n$task_definition_name をsample-${ENV_ARG} クラスターのスケジュールタスクに登録しました。"

    # 読み込んでいるjsonはこんな感じのやつ
    # [
    #   {
    #     "Id": "タスク名",
    #     "Arn": "クラスターのARN",
    #     "RoleArn": "RoleのARN",
    #     "Input": "",
    #     "EcsParameters": {
    #       "TaskDefinitionArn": "タスク定義のARN",
    #       "TaskCount": 1,
    #       "LaunchType": "FARGATE",
    #       "NetworkConfiguration": {
    #         "awsvpcConfiguration": {
    #           "Subnets": [
    #             "サブネットのid"
    #           ],
    #           "SecurityGroups": [
    #             "セキュリティグループのid"
    #           ],
    #           "AssignPublicIp": "ENABLED"
    #         }
    #       }
    #     }
    #   }
    # ]
}

check_args
select_branch
fetch_SHA1
check_ecr_build_artifacts
get_commmit_message
select_release_file
pick_params_from_json
confirm_release
create_task_definision
create_cloudwatch_rule
associate

リリースするバッチの内容はこんな感じのJSONを事前に用意しておき、それをスクリプトで読み込んで使う。

    {
      "name": "sample",
      "cron": "cron(0/5 * * * ? *)",
      "description": "コマンドのhelpを表示する",
      "command": "node dist/src/command help"
    }

例として上記のJSONを読み込んだ場合、CloudWatchのルール名はsampleで作成される。(jsonのnameは $task_name に入っている)

シェル抜粋
    rule_arn=$(aws events put-rule --schedule-expression "$cron" --name $task_name --description "$description" --query 'RuleArn' --output text)

作成したsampleのルールを、作成したタスク定義に紐付けてECSに登録する。

シェル抜粋
    aws events put-targets --rule $task_name --targets "$json"

起こったこと

上述のシェルを使ってstg用のECSにタスクを登録。
sampleのタスクが登録された。

スクリーンショット_2020-04-10_6_26_56.png

今度は同じsampleのタスクを本番用のECSに登録する。
ちゃんとsampleが登録された。

スクリーンショット_2020-04-10_6_28_33.png

しかし、stgのECSからsampleのタスクが消えた。
スクリーンショット_2020-04-10_6_28_15.png

原因

「CloudWatchのルール sample の実行内容は stg用のタスク定義。それを stg用のecsクラスター に登録」

としていたのを、

「CloudWatchのルール sample の実行内容は prod用のタスク定義。それを prod用のecsクラスター に登録」

に上書きしたことで、stg用のECSからsampleのスケジュールタスクが消えた。

対応

CloudWatchのルール名は一意になるように変更した。

    # タスク定義名に使っているやつをCloudWatchのルール名にも利用
    task_definition_name="sample-batch-$ENV_ARG-${task_name}"

    # CloudWatchのルール作成
    rule_arn=$(aws events put-rule --schedule-expression "$cron" --name $task_definition_name --description "$description" --query 'RuleArn' --output text)

    # CloudWatchのルールをタスク定義、ECSに紐付け
    aws events put-targets --rule ${task_definition_name} --targets "$json"

最後に

CloudWatchのルール名はグローバル変数みたいな物だから、同じ処理だからといって同じルール名で作成すると今回みたいなことになる。複数の環境で登録するのであれば環境名などをルール名に加えておき、一意にする必要がある。

考えてみれば当たり前のことだが、今回のこの挙動をはじめみたとき、「んん??」と思ったので残しておく。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?