7
1

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 3 years have passed since last update.

ハンズラボAdvent Calendar 2019

Day 1

ECSのタスク定義にライフサイクルを設定したい

Last updated at Posted at 2019-11-30

#はじめに
ECSのタスクスケジュール機能を利用してバッチ処理などを書いている時、ecs-cliを利用して何度もタスクを更新しているとタスク定義のリビジョンが増えていきます。

awsのコンソール画面からポチポチしてリビジョンを消す(登録解除)のも面倒ですし、awsclideregister-task-definitionを利用して消す方法もありますがコマンドラインから実行するのも面倒です。

awscliでの削除の仕方
https://docs.aws.amazon.com/cli/latest/reference/ecs/deregister-task-definition.html

なので、Lambdaを実行してリビジョンを消す(登録解除)ことにしました。

使用したもの

  • ServerlessFrameWork lambdaのデプロイ用
  • serverless-python-requirements Pythonの外部パッケージをデプロイする用
  • Pipenv パッケージ管理

#ECSのタスク定義の一覧を取得する
boto3ecs.list_task_definitionsを使用してタスク定義を全て取得します。
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecs.html#ECS.Client.list_task_definitions

引数としてsort='DESC'にしていますが、これは後のitertools.groupbyを使用する際に必要なので'DESC'に設定しています。


import json
import boto3
import logging

logger = logging.Logger(__name__)
logger.setLevel(logging.DEBUG)

def get_task_definitions():
    """ECSのタスク定義を取得する

    Returns:
        list -- タスク定義のarnのlist
    """

    client = boto3.client('ecs')
    try:
        res = client.list_task_definitions(
            status='ACTIVE',
            sort='DESC')
        logger.info(json.dumps(res))
    except Exception as e:
        logger.info(e)
    else:
        logger.info('タスク定義取得成功')

    list_task_definitions = res['taskDefinitionArns']
    return list_task_definitions

こんな感じに取得できます

[
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_a:3',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_a:2',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_a:1',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_b:5',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_b:4',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_b:3',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_b:2',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_b:1',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_c:5',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_c:4',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_c:3',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_c:2',
    'arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXXX:task-definition/task_c:1'
]

#タスク定義のデータを整形する

タスク定義を取得することができたので同じタスク名同士でグループ化して、タスクがいくつのリビジョンがあるかを確認できるようデータを整形します。
タスク定義を取得しましたが正確にはタスク定義のarnなのでタスク名のみに整形します。


# 整形したい形
{
    'task_a': ['3', '2', '1'],
    'task_b': ['5', '4', '3', '2', '1'],
    'task_c': ['5', '4', '3', '2', '1']
}

itertools.groupbyを使用してグループ化をしていきます。itertools.groupbyを使用するには事前にソートする必要があるので、ecs.list_task_definitionsのsortを設定しました。


a = [('a', '3'), ('a', '2'), ('a', '1'), ('b', '4'), ('b', '3'),
     ('b', '2'), ('b', '1'), ('c', '2'), ('c', '1')]

for key, group in groupby(a, lambda x: x[0]): 
    for team in group: 
        print(team)
    print('')

こんな感じでグループ化してくれるのでこれを辞書に整形します。

('a', '3')
('a', '2')
('a', '1')

('b', '4')
('b', '3')
('b', '2')
('b', '1')

('c', '2')
('c', '1')
from itertools import groupby

def groupby_task_definitions(list_task_definitions):
    """タスク定義のarnのlistをまとめる

    Arguments:
        list_task_definitions {list} -- タスク定義のarnのlist

    Returns:
        dict -- タスク名とrevisionのlist

    Example:
        {
            task_a: ['4', '3', '2', '1'],
            task_b: ['2', 1]
        }
    """
    group_list = []
    for task_definition in list_task_definitions:
        # タスク名とrevisionのところのみ切り取る
        task_definition = task_definition.rsplit('/', 1)[1]
        task_name, revision = task_definition.split(':')

        # タプルとしてリストに追加する
        group_list.append((task_name, revision))

    result_dict = {}
    for key, group in groupby(group_list, lambda x: x[0]):
        revision_list = []
        for _, v in group:
            revision_list.append(v)
        result_dict[key] = revision_list

    return result_dict

#ECSタスクを消す(登録解除)

タスク定義の辞書を作成することができたのでECSタスクを消していきます。
辞書をforで回し、取得したリビジョンのリストの長さが指定した数になるまでpop()して消していきます。

ecs.deregister_task_definitionの引数にはタスク定義の名前を与えればいいので楽に実行できます。

def deregister_task_definition(groupby_task_definitions_dict, leave):
    """タスク定義をrevisionが最新のもの2個まで残して削除する

    Arguments:
        groupby_task_definitions_dict {dict} -- タスク名とrevisionのlistを格納したdict
    """
    client = boto3.client('ecs')

    for name, revision_list in groupby_task_definitions_dict.items():
        logger.info(name)
        try:
            while len(revision_list) > 2:
                revision = revision_list.pop()
                client.deregister_task_definition(
                    taskDefinition='{}:{}'.format(name, revision)
                )
        except Exception as e:
            logger.error(e)
        else:
            logger.info('{} : OK!'.format(name))

#Lambdaで使えるようにする

現段階ではイベントは設定していませんがlambda invokeしてもいいですし、CloudWatchEventsで定期的に行ってもいいです。

serverless.yml

ServerlessFrameworkを使用してLambdaを作成するのでserverless.ymlを晒します。

serverless.yml
service: ecs-task

provider:
  name: aws
  runtime: python3.7
  region: ap-northeast-1
  logRetentionInDays: 30

  iamRoleStatements:
    - Effect: Allow
      Action:
        - ecs:*
      Resource: "*"

functions:
  definition-lifecycle :
    handler: handler.main

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    usePipenv: true 

handler

handlerは今までに行った処理をする関数を実行しているだけです。

def main(event, context):
    list_task_definitions = get_task_definitions()
    groupby_task_definitions_dict = groupby_task_definitions(
        list_task_definitions)
    logger.info(groupby_task_definitions_dict)
    deregister_task_definition(groupby_task_definitions_dict, leave)

コード全体

handler.py

import boto3
import json
from itertools import groupby
import logging

logger = logging.Logger(__name__)
logger.setLevel(logging.DEBUG)


def get_task_definitions():
    """ECSのタスク定義を取得する

    Returns:
        list -- タスク定義のarnのlist
    """

    client = boto3.client('ecs')
    try:
        res = client.list_task_definitions(
            status='ACTIVE',
            sort='DESC')
        logger.info(json.dumps(res))
    except Exception as e:
        logger.info(e)
    else:
        logger.info('タスク定義取得成功')

    list_task_definitions = res['taskDefinitionArns']
    return list_task_definitions


def groupby_task_definitions(list_task_definitions):
    """タスク定義のarnのlistをまとめる

    Arguments:
        list_task_definitions {list} -- タスク定義のarnのlist

    Returns:
        dict -- タスク名とrevisionのlist

    Example:
        {
            task_a: ['4', '3', '2', '1'],
            task_b: ['2', 1]
        }
    """
    group_list = []
    for task_definition in list_task_definitions:
        # タスク名とrevisionのところのみ切り取る
        task_definition = task_definition.rsplit('/', 1)[1]
        task_name, revision = task_definition.split(':')

        group_list.append((task_name, revision))

    result_dict = {}
    for key, group in groupby(group_list, lambda x: x[0]):
        revision_list = []
        for _, v in list(group):
            revision_list.append(v)
        result_dict[key] = revision_list

    return result_dict


def deregister_task_definition(groupby_task_definitions_dict):
    """タスク定義をrevisionが最新のもの2つまで残して削除する

    Arguments:
        groupby_task_definitions_dict {dict} -- タスク名とrevisionのlistを格納したdict
    """
    client = boto3.client('ecs')

    for name, revision_list in groupby_task_definitions_dict.items():
        logger.info(name)
        try:
            while len(revision_list) > 2:
                revision = revision_list.pop()
                client.deregister_task_definition(
                    taskDefinition='{}:{}'.format(name, revision)
                )
        except Exception as e:
            logger.error(e)
        else:
            logger.info('{} : OK!'.format(name))


def main(event, context):
    list_task_definitions = get_task_definitions()
    groupby_task_definitions_dict = groupby_task_definitions(
        list_task_definitions)
    logger.info(groupby_task_definitions_dict)
    deregister_task_definition(groupby_task_definitions_dict)

AWSなんとかしてくれ

タスク定義の削除(登録解除)はActiveなタスクのみ行えて、その後はInactive状態になります。その状態になると現時点でが完全に消すことができないのでAWSさん対応を待っています。

現時点では、INACTIVE タスク定義はアカウント内で無期限に検出可能な状態で残されます。ただし、この動作は今後変更される場合があるため、関連付けられたタスクとサービスのライフサイクルを超えて保持される INACTIVE タスク定義に依存しないようにしてください。

image.png

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?