ecs-deployのコードリーディングをしました。バージョンは3.4.0です。
以下のパターンで追ってみます。/ecs-deployのシェルスクリプトが実体になります。
$ ecs-deploy -c {cluster_name} -n {service_name} -i {image_uri}
最初の方は定数とメソッドが定義されていて、最後にメインの処理が書かれています。
if [ "$BASH_SOURCE" == "$0" ]; then
# ...
# Check for AWS, AWS Command Line Interface
require aws
# Check for jq, Command-line JSON processor
require jq
# Loop through arguments, two at a time for key and value
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-k|--aws-access-key)
AWS_ACCESS_KEY_ID="$2"
shift # past argument
;;
# ...
--version)
echo ${VERSION}
exit 0
;;
*)
usage
exit 2
;;
esac
shift # past argument or value
done
シェルからecs-deployのコマンドを実行した場合は$BASH_SOURCE = $0になり、分岐の中が実行されます。while, case文の中でoptionをパースしています。
requireではコマンドがインストールされていてパスが通っているかを確認しています。
function require() {
command -v "$1" > /dev/null 2>&1 || {
echo "Some of the required software is not installed:"
echo " please install $1" >&2;
exit 4;
}
}
ecs-deployはaws-cliとjq(JSONパースして属性値取り出す用)を使っているのでrequireで確認しています。
次にassertRequiredArgumentsSet関数で必須のオプションが指定されているかどうかをチェックしています。
# Check that required arguments are provided
assertRequiredArgumentsSet
if [[ "$AWS_ASSUME_ROLE" != false ]]; then
assumeRole
fi
assumeRoleでは aws sts assume-role
でAssumeRoleした後、レスポンスのAPIクレデンシャルを各環境変数(AWS_ACCESS_KEY_IDなど)にセットします。
次にparseImageName関数で指定したイメージ名をパースしてecs-deploy用にイメージ名を再構築します。
# Determine image name
parseImageName
echo "Using image name: $useImage"
# Get current task definition
getCurrentTaskDefinition
echo "Current task definition: $TASK_DEFINITION_ARN";
# create new task definition json
createNewTaskDefJson
# register new task definition
registerNewTaskDefinition
echo "New task definition: $NEW_TASKDEF";
getCurrentTaskDefinition関数ではaws ecs describe-services
とaws ecs describe-task-definition
でクラスタ・サービスに紐づくタスク定義を抽出します。
function getCurrentTaskDefinition() {
if [ $SERVICE != false ]; then
# Get current task definition arn from service
TASK_DEFINITION_ARN=`$AWS_ECS describe-services --services $SERVICE --cluster $CLUSTER | jq -r .services[0].taskDefinition`
TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN`
# For rollbacks
LAST_USED_TASK_DEFINITION_ARN=$TASK_DEFINITION_ARN
if [ $USE_MOST_RECENT_TASK_DEFINITION != false ]; then
# Use the most recently created TD of the family; rather than the most recently used.
TASK_DEFINITION_FAMILY=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN | jq -r .taskDefinition.family`
TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_FAMILY`
TASK_DEFINITION_ARN=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_FAMILY | jq -r .taskDefinition.taskDefinitionArn`
fi
elif [ $TASK_DEFINITION != false ]; then
# Get current task definition arn from family[:revision] (or arn)
TASK_DEFINITION_ARN=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION | jq -r .taskDefinition.taskDefinitionArn`
fi
TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN`
}
createNewTaskDefJson関数では既存のタスク定義をsedで書き換えて更新用のタスク定義JSONを生成します。
function createNewTaskDefJson() {
# Get a JSON representation of the current task definition
# + Update definition to use new image name
# + Filter the def
if [[ "x$TAGONLY" == "x" ]]; then
DEF=$( echo "$TASK_DEFINITION" \
| sed -e "s|\"image\": *\"${imageWithoutTag}:.*\"|\"image\": \"${useImage}\"|g" \
| sed -e "s|\"image\": *\"${imageWithoutTag}\"|\"image\": \"${useImage}\"|g" \
| jq '.taskDefinition' )
else
DEF=$( echo "$TASK_DEFINITION" \
| sed -e "s|\(\"image\": *\".*:\)\(.*\)\"|\1${useImage}\"|g" \
| jq '.taskDefinition' )
fi
# Default JQ filter for new task definition
NEW_DEF_JQ_FILTER="family: .family, volumes: .volumes, containerDefinitions: .containerDefinitions, placementConstraints: .placementConstraints"
# ...
# Build new DEF with jq filter
NEW_DEF=$(echo "$DEF" | jq "{${NEW_DEF_JQ_FILTER}}")
# If in test mode output $NEW_DEF
if [ "$BASH_SOURCE" != "$0" ]; then
echo "$NEW_DEF"
fi
}
registerNewTaskDefinition関数でcreateNewTaskDefJsonで生成したJSONファイルを元に aws ecs register-task-definition
を使って新しいタスク定義を登録します。
function registerNewTaskDefinition() {
# Register the new task definition, and store its ARN
NEW_TASKDEF=`$AWS_ECS register-task-definition --cli-input-json "$NEW_DEF" | jq -r .taskDefinition.taskDefinitionArn`
}
-nオプションが指定されている場合は、updateServiceが実行されます。
# update service if needed
if [ $SERVICE == false ]; then
if [ $RUN_TASK == true ]; then
runTask
fi
echo "Task definition updated successfully"
else
updateService
if [[ $SKIP_DEPLOYMENTS_CHECK != true ]]; then
waitForGreenDeployment
fi
fi
if [[ "$AWS_ASSUME_ROLE" != false ]]; then
assumeRoleClean
fi
updateService関数はaws ecs update-service
でクラスター・サービス・タスク定義を引数に既存サービスを更新します。
function updateService() {
# ...
# Update the service
UPDATE=`$AWS_ECS update-service --cluster $CLUSTER --service $SERVICE $DESIRED_COUNT --task-definition $NEW_TASKDEF $DEPLOYMENT_CONFIG`
# Only excepts RUNNING state from services whose desired-count > 0
SERVICE_DESIREDCOUNT=`$AWS_ECS describe-services --cluster $CLUSTER --service $SERVICE | jq '.services[]|.desiredCount'`
# ...
}
最後にwaitForGreentDeploy関数を実行します。 aws ecs describe-services
でサービスのデプロイ数を取得し、1であればループを抜け、そうでなければ2秒sleepしたのち再度デプロイ数をチェックします。
function waitForGreenDeployment {
DEPLOYMENT_SUCCESS="false"
every=2
i=0
echo "Waiting for service deployment to complete..."
while [ $i -lt $TIMEOUT ]
do
NUM_DEPLOYMENTS=$($AWS_ECS describe-services --services $SERVICE --cluster $CLUSTER | jq "[.services[].deployments[]] | length")
# Wait to see if more than 1 deployment stays running
# If the wait time has passed, we need to roll back
if [ $NUM_DEPLOYMENTS -eq 1 ]; then
echo "Service deployment successful."
DEPLOYMENT_SUCCESS="true"
# Exit the loop.
i=$TIMEOUT
else
sleep $every
i=$(( $i + $every ))
fi
done
if [[ "${DEPLOYMENT_SUCCESS}" != "true" ]]; then
if [[ "${ENABLE_ROLLBACK}" != "false" ]]; then
rollback
fi
exit 1
fi
}
タイムアウトした場合はrollbackが走ります。
rollbackは aws ecs update-service
でtask-definitionに$LAST_USED_TASK_DEFINITION_ARNを指定します。
function rollback() {
echo "Rolling back to ${LAST_USED_TASK_DEFINITION_ARN}"
$AWS_ECS update-service --cluster $CLUSTER --service $SERVICE --task-definition $LAST_USED_TASK_DEFINITION_ARN > /dev/null
}
LAST_USED_TASK_DEFINITION_ARNはgetCurrentTaskDefinitionで設定されるデプロイ前の最新のタスク定義ARNになります。