every Tech Blog

株式会社エブリーのTech Blogです。

ECSとALBでのカナリアリリース

はじめに

エブリーでソフトウェアエンジニアをしている本丸です。
DELISH KITCHENでカナリアリリースの仕組みを作成したので、今回はそのことについて紹介させていただこうかと思います。

カナリアリリースとは

カナリアリリースとは、一度に全体に公開するのではなく、最初は一部のユーザーに限定して公開を行い、問題がなければ全体に公開していくリリースの方法です。

カナリアリリースを行うメリットとしては、本番環境で影響範囲は狭めて動作確認ができることや問題が発生した場合にリリース前のサーバに切り戻ししやすいことなどが挙げられます。

やったこと

概要

ECSのserviceを2つ用意しておき、片方のservice(task)の環境変数にflagを持たせます。このflagがtrueかどうかでカナリアリリースしたい機能が呼び出されるようになります。
カナリアリリース時のルーティングにはALBを使用します。ALBではターゲットグループへの重みづけを使って、どの程度カナリアリリース用のserviceにルーティングするかのルールを作成しておきます。
カナリアリリースを行うためのルーティングについてもう少し掘り下げて説明します。

通常のタスク定義

{
  "family": "sample",
  "cpu": "1024",
  "memory": "8192",
  "containerDefinitions": [
    {
      "name": "sample",
      "portMappings": [
        {
          "hostPort": 80,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "environment": [
        // 通常のタスクではここがfalseになる
        {
          "name": "flag",
          "value": "false"
        }
      ],
      "secrets": [],
      "volumesFrom": []
    }
  ]
}

カナリアリリースのタスク定義

{
  "family": "sample",
  "cpu": "1024",
  "memory": "8192",
  "containerDefinitions": [
    {
      "name": "sample",
      "portMappings": [
        {
          "hostPort": 80,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "environment": [
        // カナリアリリース用のタスクではここがtrueになる
        {
          "name": "flag",
          "value": "true"
        }
      ],
      "secrets": [],
      "volumesFrom": []
    }
  ]
}

ALBのルーティング

カナリアリリースへのルーティングには前述した通りAWSのALBを使用しています。ALBのリスナールールのアクションの項目で、どのターゲットグループにルーティングするかを選択することができるのですが、そのアクションにターゲットグループごとのルーティングされる割合を指定することができます。
Terraformだと下記のようなコードになります。

locals {
  normal_target_weight = 100
  canary_target_weight = 0
}

# 動作確認用のルール
resource "aws_lb_listener_rule" "canary" {
  listener_arn = aws_lb_listener.sample.arn
  priority     = 1

  action {
    type = "forward"
    target_group_arn = aws_alb_target_group.canary.arn

  }
  condition {
    # HTTPヘッダに特定の文字列が入った時にこのルールを適用するようにする
    http_header {
      http_header_name = "canary"
      values           = ["true"]
    }
  }
}
# カナリアリリースに必要なルール
resource "aws_lb_listener_rule" "sample" {
  listener_arn = aws_lb_listener.sample.arn
  priority     = 2

  action {
    type = "forward"
    forward {
      # 通常のサービスに向ける
      target_group {
        arn    = aws_alb_target_group.sample.arn
        # 重みづけを行う
        weight = local.normal_target_weight
      }

      # カナリアリリース用のサービスに向ける
      target_group {
        arn    = aws_alb_target_group.canary.arn
        # 重みづけを行う
        weight = local.canary_target_weight
      }
    }
  }
  condition {
    path_pattern {
      values = ["*"]
    }
  }
}

カナリアリリースに必要なルールはaws_lb_listener_rule.sampleの方です。このリソースのアクションの中にターゲットグループを2つ用意してそれぞれのweightを変更することでルーティングされる割合を制御します。
aws_lb_listener_rule.canaryのルールはカナリアリリースしたいserverへの重みづけが0の時に動作確認を行えるように準備しています。
上記の実装ではaws_lb_listener_rule.canaryのpriorityを1、aws_lb_listener_rule.sampleのpriorityを2にし、aws_lb_listener_rule.canaryにHTTPヘッダに特定の文字列が入るときに適用されるという条件を加えることで制御しています。

問題点

実装や運用をする上で問題になったことがいくつかあるので、共有させていただければと思います。

コスト

ECSのserviceが通常のものとカナリアリリース用のもので2つになるので、無計画に運用するとコストが嵩むという問題があります。そこで弊社では、カナリアリリース時以外はカナリアリリース用のserverの台数を0にすることで対応しています。

AutoScaling

よく考えると当たり前の話なのですが、AutoScalingを使用していて必要数を低めに設定している場合、割合の切り替えに気をつける必要があります。サービスに影響はなかったのですが、カナリアリリースの導入当初に一度に切り替えを行なってしまいタスクの数が少なく負荷がかかってしまうということがありました。

おわりに

本記事では、ECSとALBを使ってカナリアリリースを行う方法を紹介しました。ECSとALBを使ってカナリアリリースを考えている人の参考になれば幸いです。
ここまで読んでいただきありがとうございました。

参考資料