AmazonECS / Fargate でカナリアデプロイを実現する

AmazonECS / Fargate でカナリアデプロイを実現する:

先日、AmazonECS / Fargate 本番運用のための構築とデプロイ方法まとめというタイトルでECS/Fargateを本番運用するための構成とデプロイについての記事を書き、HiromuMasuda/ecs-deployというデプロイ用のスクリプトを公開しました。



Screen Shot 2018-10-23 at 14.02.21.png


すると、ECS/Fargateを用いた時のカナリアデプロイの手法が意外と話題になったため、前回は簡単に紹介してしまったカナリアデプロイを、今回は少し首を突っ込んで紹介したいと思います。


アーキテクチャ全体像

全体像はこちらです。



Screen Shot 2018-10-20 at 17.50.52.png


ECS/Fargateってなんだ?という方や、まだ全記事を読んでない方はまずはこちらを見てください。

AmazonECS / Fargate 本番運用のための構築とデプロイ方法まとめ


カナリアデプロイをECSで実現する

service内でtaskが2台動いている状態から、カナリアデプロイをして33%配信を実現するという例を紹介します。



Screen Shot 2018-10-23 at 14.11.10.png


なお、デプロイ用のスクリプトはこちらで公開しております。

HiromuMasuda/ecs-deploy


1. 新しいバージョンのイメージをbuildしてpushする



Screen Shot 2018-10-23 at 14.11.18.png


バージョンの識別のために、タイムスタンプと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の新しいリビジョンを作成する



Screen Shot 2018-10-23 at 14.11.26.png


最新の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を生成する



Screen Shot 2018-10-23 at 14.11.33.png


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をロードバランサのターゲットグループに追加する



Screen Shot 2018-10-23 at 14.11.40.png


まず、先ほど作成した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 
次に、取得したprivateIPをELBのターゲットグループに追加します。これにより、リクエストがカナリアデプロイした1台のtaskにも分散されて流れて来ます。

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

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)