SREのクラシマです。今回も失敗した話をば。
新着情報 – パブリック IPv4 アドレスの利用に対する新しい料金体系を発表 / Amazon VPC IP Address Manager が Public IP Insights の提供を開始 | Amazon Web Services ブログ
2024年2月よりPublic IPv4への課金が開始されるという話が出たので、社内でのPublic IPv4アドレスをカウントしたところ、金額がそれなりになりそうなので対策することにしました。
とはいえ、これまですべてのサービスでIPv4のみを利用、NAT GW費用もかけたくないのでPublic Subnetにサービスを構築しています。
また、AWSのパブリックIPv4の料金体系の変更とサイバーエージェントのIPv6活用推進事例 | CyberAgent Developers Blog を拝読すると、CloudFrontのオリジンにはまだIPv6が未対応とのこと。
弊社のプロダクトではAPIサーバの前にCloudFrontを置いているケースや、WordPressの前にCloudFrontを置いているケースがあり、移行対象外にせざるを得ないものも散見されます。
そんな中、先日引っ越しブログを書いたredashの引っ越しについて - TORANA TECH BLOGは独立したVPCで動作しており、CloudFrontなども配置しておらず、最初にIPv6移行を試して見るにはちょうど良さそう、ということで移行してみたのですが...。
ネットワークのIPv6移行
terraform管理しているVPC/Subnet/IGW/RouteTableをdualstackに切り替えます。 ざっくり、以下の対応を実施。
- VPCにIPv6 CIDRを付与。特にこだわりがないのでAWSから自動でアサインされるものをそのまま使います
- 3AZ構成のサブネットそれぞれにIPv6 CIDRを付与、その際にDNS64/リソース作成時IPv6自動付与/起動時DNS AAAAレコード有効化をそれぞれONに
- route tableにIPv6でのIGWへのルート(
::/0
)を付与
module "subnet" { source = "../../" for_each = local.subnets vpc_id = aws_vpc.vpc.id az = each.value.az cidr_block = each.value.cidr_block ipv6_cidr_block = each.value.ipv6_cidr_block gateway_route = { destination_cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.gateway.id } gateway_route_ipv6 = { destination_cidr_block = "::/0" gateway_id = aws_internet_gateway.gateway.id } name = each.key } locals { cidr_block = "192.168.0.0/16" subnets = { subnet-1a_1 = { az = "ap-northeast-1a" cidr_block = cidrsubnet(local.cidr_block, 8, 1) ipv6_cidr_block = cidrsubnet(aws_vpc.vpc.ipv6_cidr_block, 8, 1) } subnet-1c_1 = { az = "ap-northeast-1c" cidr_block = cidrsubnet(local.cidr_block, 8, 2) ipv6_cidr_block = cidrsubnet(aws_vpc.vpc.ipv6_cidr_block, 8, 2) } subnet-1d_1 = { az = "ap-northeast-1d" cidr_block = cidrsubnet(local.cidr_block, 8, 3) ipv6_cidr_block = cidrsubnet(aws_vpc.vpc.ipv6_cidr_block, 8, 3) } } } resource "aws_vpc" "vpc" { cidr_block = local.cidr_block assign_generated_ipv6_cidr_block = true }
locals { ipv6_only = var.cidr_block == null ipv6_enable = var.ipv6_cidr_block != null } resource "aws_subnet" "this" { cidr_block = var.cidr_block vpc_id = var.vpc_id availability_zone = var.az ipv6_cidr_block = var.ipv6_cidr_block ipv6_native = local.ipv6_only enable_dns64 = local.ipv6_enable assign_ipv6_address_on_creation = local.ipv6_enable enable_resource_name_dns_aaaa_record_on_launch = local.ipv6_enable tags = { Name = var.name } } resource "aws_route_table" "this" { vpc_id = var.vpc_id tags = { Name = var.name } } resource "aws_route_table_association" "this" { route_table_id = aws_route_table.this.id subnet_id = aws_subnet.this.id } resource "aws_route" "gateway" { count = var.gateway_route == null ? 0 : 1 route_table_id = aws_route_table.this.id destination_cidr_block = var.gateway_route.destination_cidr_block gateway_id = var.gateway_route.gateway_id } resource "aws_route" "gateway_ipv6" { count = var.gateway_route_ipv6 == null ? 0 : 1 route_table_id = aws_route_table.this.id destination_ipv6_cidr_block = var.gateway_route_ipv6.destination_cidr_block gateway_id = var.gateway_route_ipv6.gateway_id }
切り替え自体はサービスダウンなしにうまくいきましたが、terraform-provider-aws v5.13.0以前だとIPv6 CIDRのアサインとIPv6関連フラグの有効化の順序が逆でapplyで落ちました。
Fix: Enabling IPv6 apply error by watarukura · Pull Request #32896 · hashicorp/terraform-provider-aws · GitHub ちんまりとしたコントリビューションですが、自分が使っているツールに貢献できて嬉しいところ。
ALB/ECS FargateのIPv6移行
ALBをにIPアドレスタイプをdualstackに切り替え、セキュリティグループもIPv6からの接続に対応します。
ターゲットグループもIPアドレスタイプをIPv6に切り替えるのですが、こちらは再作成になってしまうので、BLUE/GREEN DEPLOYMENT構成の場合は片側ずつ切り替えてやるのが良さそうです。
...と、ここまでは想定して手順書を組み立てていたのですが、「ターゲットグループの再作成めんどいなぁ」と日程調整しているある日...。
redashが沈黙
...稀によくあるやつで、重いクエリを投げつけたところredashが沈黙、CPUが100%に張り付いてうんともすんとも言わなくなりました。
EC2の頃はサーバ再起動用のGHAを用意していたのですが、Fargate移行後は手作業で再デプロイが必要です。(自動化しないとなぁ)
また、現行のredashが使用しているRQ Schedulerのバージョンが古く、scheduler更新に失敗する場合があります。
RedashでScheduler更新時に発生するエラー「There’s already an active RQ scheduler」について原因を調べて解消してみた | DevelopersIO
(最近、redashリポジトリの更新が盛んになっていて、RQ Schedulerのバージョンも上がっているので、次期バージョンでは直るはず!)
ということで、デプロイして次のschedulerが起動する前にredashが利用しているredisに接続してFLUSHDBを実行します。
さて、と待っているとworker、schedulerは正常起動したものの、serverが起動しません。WORKER_TIMEOUTが発生します。
Gunicornのタイムアウトは120秒に設定しており、前回のデプロイ時から特に変更してないのにおかしい、と数回デプロイを繰り返しますが起動せず。
redash停止から1時間ほどして、IPv6を無効化してみたところserverが起動してことなきを得ました。
ポストモーテム
根本原因として、cannot bind Gunicorn to IPv6 link local addresses · Issue #1628 · benoitc/gunicorn · GitHub どうやらGunicornがIPv6未対応のようです。
Subnetの設定で、リソース作成時にIPv6を付与するようにしたため、既存のインスタンスにはIPv4のみがアタッチされていましたが、新規のインスタンスにはIPv6がアタッチされたために発生したものと推定しています。
教訓: WebサーバがIPv6対応しているかを事前調査しましょう
ということで、改めて他のプロダクトも調査しましたがIPv6移行できそうな対象がほぼないことがわかりました...。
ステージング環境は3AZ -> 2AZにしてALBのIPアドレスを削減したり、Internal ALBへの切り替えなどで対応することを検討しようと思います。