#はじめに
ECSのタスクスケジュール機能を利用してバッチ処理などを書いている時、ecs-cli
を利用して何度もタスクを更新しているとタスク定義のリビジョン
が増えていきます。
awsのコンソール画面からポチポチしてリビジョンを消す(登録解除)のも面倒ですし、awscli
のderegister-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のタスク定義の一覧を取得する
boto3
のecs.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
を晒します。
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)
コード全体
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 タスク定義に依存しないようにしてください。