AmazonECS / Fargate でカナリアデプロイを実現する
AmazonECS / Fargate でカナリアデプロイを実現する:
先日、AmazonECS / Fargate 本番運用のための構築とデプロイ方法まとめというタイトルでECS/Fargateを本番運用するための構成とデプロイについての記事を書き、HiromuMasuda/ecs-deployというデプロイ用のスクリプトを公開しました。
すると、ECS/Fargateを用いた時のカナリアデプロイの手法が意外と話題になったため、前回は簡単に紹介してしまったカナリアデプロイを、今回は少し首を突っ込んで紹介したいと思います。
全体像はこちらです。
ECS/Fargateってなんだ?という方や、まだ全記事を読んでない方はまずはこちらを見てください。
service内でtaskが2台動いている状態から、カナリアデプロイをして33%配信を実現するという例を紹介します。
なお、デプロイ用のスクリプトはこちらで公開しております。
バージョンの識別のために、タイムスタンプとgitのコミットハッシュをタグとしてビルドしたイメージにつけて、ECRにpushします。
最新のTaskDefinitionを取得し、ContainerDefinitionの中の参照するimageのバージョンを新しいものに変更し、リビジョンを更新します。
2で作成したTaskDefinitionを元に、taskを新しく作成します。この時、上の図のようにserviceの外に作ります。そうすることで、上の2台のtaskは前のリビジョンのTaskDefinitionから、下の1台は新しいリビジョンのTaskDefinitionを参照していることになります。また、今作成したtaskがカナリアデプロイによるものだと判別がつくように、
まず、先ほど作成したtaskからprivateIPを取ってきます。taskが作成されてから数秒しないとprivateIPが生成されないため、取得するまでループを回しています。
次に、取得したprivateIPをELBのターゲットグループに追加します。これにより、リクエストがカナリアデプロイした1台のtaskにも分散されて流れて来ます。
以上のようにして、カナリアデプロイを実現しました。
カナリアデプロイをスクリプト化できたことにより、デプロイによる障害の影響が小さく済むようになりました。ぜひ試して見てください。
また、Twitterでは常に技術系・筋トレ系のアウトプットをしているのでぜひフォローしてください!�� https://twitter.com/hiromu_bdy
先日、AmazonECS / Fargate 本番運用のための構築とデプロイ方法まとめというタイトルでECS/Fargateを本番運用するための構成とデプロイについての記事を書き、HiromuMasuda/ecs-deployというデプロイ用のスクリプトを公開しました。
すると、ECS/Fargateを用いた時のカナリアデプロイの手法が意外と話題になったため、前回は簡単に紹介してしまったカナリアデプロイを、今回は少し首を突っ込んで紹介したいと思います。
アーキテクチャ全体像
全体像はこちらです。ECS/Fargateってなんだ?という方や、まだ全記事を読んでない方はまずはこちらを見てください。
AmazonECS / Fargate 本番運用のための構築とデプロイ方法まとめ
カナリアデプロイをECSで実現する
service内でtaskが2台動いている状態から、カナリアデプロイをして33%配信を実現するという例を紹介します。なお、デプロイ用のスクリプトはこちらで公開しております。
HiromuMasuda/ecs-deploy
1. 新しいバージョンのイメージをbuildしてpushする
バージョンの識別のために、タイムスタンプとgitのコミットハッシュをタグとしてビルドしたイメージにつけて、ECRにpushします。
def push_latest_image tag_timestamp = Time.now.strftime("%Y%m%d_%H%M") tag_git_commit_hash = `git rev-parse HEAD` tags = [tag_timestamp, tag_git_commit_hash] puts "-----> Push latest image. Tag: #{tags.join(", ")}" cmd_build = `docker build -t #{@ecr_name} #{@dockerfile_path}` tags.each do |tag| cmd = ` docker tag #{@ecr_name}:latest #{get_ecr_image_name(tag)} docker push #{get_ecr_image_name(tag)}` end return get_ecr_image_name(tag_timestamp) end
2. pushしたイメージを参照するTaskDefinitionの新しいリビジョンを作成する
最新のTaskDefinitionを取得し、ContainerDefinitionの中の参照するimageのバージョンを新しいものに変更し、リビジョンを更新します。
def update_task_definition(image_name) puts "-----> Update task definition" task_definition = get_latest_task_definition_description container_definitions = task_definition["containerDefinitions"] new_container_definitions = [] container_definitions.each do |container| container["image"] = image_name if container["name"] == "#{@container_name}" new_container_definitions << container end new_revision = `aws ecs register-task-definition \ --family #{@task} \ --task-role-arn #{task_definition["taskRoleArn"]} \ --execution-role-arn #{task_definition["executionRoleArn"]} \ --network-mode #{task_definition["networkMode"]} \ --volumes '#{task_definition["volumes"].to_json}' \ --cpu #{task_definition["cpu"]} \ --memory #{task_definition["memory"]} \ --requires-compatibilities #{task_definition["requiresCompatibilities"][0]} \ --container-definitions '#{new_container_definitions.to_json}'` new_task_definition_arn = JSON.parse(new_revision)["taskDefinition"]["taskDefinitionArn"] puts "-----> New task definition arn: #{new_task_definition_arn}" return new_task_definition_arn end
3. 作成したTaskDefinitionを元に1台だけTaskを生成する
2で作成したTaskDefinitionを元に、taskを新しく作成します。この時、上の図のようにserviceの外に作ります。そうすることで、上の2台のtaskは前のリビジョンのTaskDefinitionから、下の1台は新しいリビジョンのTaskDefinitionを参照していることになります。また、今作成したtaskがカナリアデプロイによるものだと判別がつくように、
started_by
というスペースにタグを追加します。このスクリプトではcanary
という文字列を指定しています。def run_task(task_definition, started_by_tag) puts "-----> Run new task" service_desc = get_service_description conf = service_desc["networkConfiguration"]["awsvpcConfiguration"] cmd = `aws ecs run-task \ --cluster #{@cluster} \ --task-definition #{task_definition} \ --network-configuration "awsvpcConfiguration={\ subnets=[#{conf["subnets"].join(",")}],\ securityGroups=[#{conf["securityGroups"].join(",")}],\ assignPublicIp="DISABLED"}" \ --launch-type FARGATE \ --started-by #{started_by_tag}` return JSON.parse(cmd) end
4. 生成したTaskのPrivateIPをロードバランサのターゲットグループに追加する
まず、先ほど作成したtaskからprivateIPを取ってきます。taskが作成されてから数秒しないとprivateIPが生成されないため、取得するまでループを回しています。
def canary_deploy ... task_arn = new_task["tasks"][0]["taskArn"] puts "-----> task ARN: #{task_arn}" # take several seconds to get IP while true private_ip = get_task_private_ip(task_arn) if !private_ip.nil? puts "-----> private IP: #{private_ip}" break end sleep(1) end add_task_to_target_group(private_ip) ... end
def add_task_to_target_group(private_ip) puts "-----> Add #{private_ip} to the target group" cmd = `aws elbv2 register-targets \ --target-group-arn #{@target_group_arn} \ --targets Id=#{private_ip},Port=8000` return cmd end
まとめ
カナリアデプロイをスクリプト化できたことにより、デプロイによる障害の影響が小さく済むようになりました。ぜひ試して見てください。また、Twitterでは常に技術系・筋トレ系のアウトプットをしているのでぜひフォローしてください!�� https://twitter.com/hiromu_bdy
コメント
コメントを投稿