CATのレガシーマイグレーション舞台裏 ― TargetGroupBinding on Amazon EKS
こちらは、公式アドベントカレンダー2024_A IT技術関連トピック Day.12の記事です。
公式アドベントカレンダー2024_B 仕事術・キャリア・体験記も毎日記事を公開していますので、ぜひあわせてご覧下さい。
★Day11のアドベントカレンダー記事
「トラブル報告のコツ ~報告会をスムーズに終わらせる3つのコツ~」(原田 旨一)
1. はじめに
株式会社SHIFT サービスプラットフォームグループ 村上です。
本記事では、CAT(Computer Aided Test)のレガシーマイグレーションの舞台裏として、TargetGroupBinding をご紹介させてください。
CATは、弊社のソフトウェア品質保証事業を長らく支えています。 初コミット(当時 Subversion)の記録が 2012年10月、製品化が 2015 年という歴史あるプロダクトです。2024年12月現在、約1700の顧客環境が稼働しています。お客様や社内のニーズに応えつつ成長を続けています。
世の中の技術の進化を取り入れ、CATもさらなる進化が必要です。現在のシステムでは、リソースの柔軟な調整やミドルウェアアップデート作業の複雑さなどが課題となっていました。これらの課題を克服し、より効果的な運用を実現するために、CATのレガシーマイグレーションを進めています。
2024年前半、CAT のアプリケーションレイヤーをAmazon EC2上の仮想マシンから Amazon EKS(Elastic Kubernetes Service)上のコンテナに移行するマイグレーションを進めていました。
EKSでは通常、ロードバランサ(ALB)の構築にIngressを使います。Ingress を用いる場合、既存のALBをそのまま使うことができず、ID変更を伴う再構築が必須となります。 今回のマイグレーションではALBのID変更が不都合だったため、別の手段が必要となりました。
Ingressは、各コンポーネントの冗長性を保ちつつ負荷に応じて自動的かつ柔軟なリソース配分を可能にします。
TargetGroupBindingを用いることで、ALBの再構築を起こさずに上記の要件を満たせることがわかりました。
以下、TargetGroupBindingを用いた実装について、簡単な動作例を示しつつ、ご紹介します。 ここでは、動作例としてTerraform HCLを用いています。また、EKSの動作環境をTerraformで構築済であることを前提としています。 なお、TerraformによるEKS環境構築は、AWS builder.flash 記事 などをご参照ください。
2. 準備:ネットワーク設定とALB
本記事における「既存の ALB」をここで構築しておきます。
構築先環境に応じた値
まずは、構築先の環境に応じた値を調べて設定します。 以下の locals セクションの3つの変数(my_vpc_id, my_subnet_id_list, allowed_ip_cidr)に適切な値をセットしてください。
locals {
my_vpc_id = "vpc-xxxxxxxx"
my_subnet_id_list = ["subnet-xxxxxxx", "subnet-yyyyyyy"]
allowed_ip_cidr = "aaa.bbb.ccc.ddd/32"
k8s_namespace = "demo-page-namespace"
}
各変数の説明
my_vpc_id : EKSクラスタを構築したVPCのIDを指定します。
my_subnet_id_list : EKSクラスタを構築したサブネットのIDをリスト形式で指定します。
allowed_ip_cidr : このデモへのアクセスを許可するIPアドレスをCIDR形式で指定します。全IPを許可する場合は "0.0.0.0/0" を使用します。
k8s_namespace : 今回のデモ用のnamespaceです。変更の必要はありません。
ロードバランサー本体(ALB)と、ターゲットグループ、リスナー、アクセス制限
移行前を想定したALBと、ALBが機能するためのコンポーネント群を示します。EKSにおいてよく用いられるIngressでは、このALBを参照することができません。
# [ALB] ロードバランサー本体
resource "aws_lb" "main" {
name = "example-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = local.my_subnet_id_list
}
# [LSNR] リスナー
resource "aws_lb_listener" "main" {
load_balancer_arn = aws_lb.main.arn ## [ALB] を参照
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main.arn ## [TGRP] を参照
}
}
# [TGRP] ターゲットグループ
resource "aws_lb_target_group" "main" {
name = "example-tg"
port = 80
protocol = "HTTP"
vpc_id = local.my_vpc_id
target_type = "ip"
}
# アクセス制限
resource "aws_security_group" "alb" {
vpc_id = local.my_vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = [local.allowed_ip_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
各コンポーネント
[ALB] ロードバランサー本体
[LSNR] リスナー
[TGRP] ターゲットグループ
には、下記の参照があることがわかります。
[ALB] - [LSNR] - [TGRP]
なお、スクリプト内では ## [ALB] を参照 などとして参照関係を示しています。
3. kubernetes のコンポーネント 最小例
ここでは、移行先kubernetesのコンポーネント(Pod, Deployment, Service)の最小例を示します。 本例のPodでは、簡単なウェブページを返す "nginxdemos/hello"を用いています。 お好きなimageに差し替えてください。
# [DEP] k8s Deployment
resource "kubernetes_deployment" "demo_page" {
metadata {
name = "demo-page"
namespace = local.k8s_namespace
labels = {
app = "demo-page"
}
}
# [POD] k8s Pod ## [DEP] 内で定義
spec {
replicas = 2
selector {
match_labels = {
app = "demo-page"
}
}
template {
metadata {
labels = {
app = "demo-page"
}
}
spec {
container {
name = "demo-page"
image = "nginxdemos/hello"
port {
container_port = 80
}
}
}
}
}
}
# [SVC] k8s service
resource "kubernetes_service" "demo_page" {
metadata {
name = "demo-page"
namespace = local.k8s_namespace
}
spec {
selector = {
app = "demo-page" ## [DEP] を参照
}
port {
port = 80
target_port = 80
}
type = "ClusterIP"
}
}
resource "kubernetes_namespace" "demo_page" {
metadata {
name = local.k8s_namespace
}
}
4. TargetGroupBinding
ここでは、TargetGroupBindingの最小構成を示します。
# [TGB] TargetGroupBinding
resource "kubernetes_manifest" "target_group_binding" {
manifest = {
apiVersion = "elbv2.k8s.aws/v1beta1"
kind = "TargetGroupBinding"
metadata = {
name = "example-tgb"
namespace = local.k8s_namespace
}
spec = {
serviceRef = {
name = "demo-page" ## [SVC] を参照
port = 80
}
targetGroupARN = aws_lb_target_group.main.arn ## [TGRP] を参照
targetType = "ip"
networking = {
ingress = [
for subnet in data.aws_subnet.selected : {
from = [{
ipBlock = {
cidr = subnet.cidr_block
}
}]
ports = [{
protocol = "TCP"
port = 80
}]
}
]
}
}
}
}
# 各サブネットのCIDRブロック
data "aws_subnet" "selected" {
for_each = toset(local.my_subnet_id_list)
id = each.value
}
# demo_page の URL
output "demo_page_url" {
value = "http://${aws_lb.main.dns_name}"
}
2節で示した各コンポーネント
[ALB] ロードバランサー本体
[LSNR] リスナー
[TGRP] ターゲットグループ
3節、4節のコンポーネント
[TGB] TargetGroupBinding
[SVC] k8s service
[DEP] k8s Deployment
[POD] k8s Pod
に対して、本節の [TGB] は [TGRP] を参照しています。そして、3節、4節の参照を追いかけると
[TGB] - [SVC] - [DEP] - [POD]
となっていることがわかります。これにより、各コンポーネントは下記のようにつながりました。
[ALB] - [LSNR] - [TGRP] - [TGB] - [SVC] - [DEP] - [POD]
すなわち、構築済のaws_lb_target_group.mainとk8s service "demo-page"を接続でき、構築済みALBとPodを接続することができました。
5. 動作確認
構築後、alb_dns_nameがコンソールに表示されます。
Outputs:
http://${alb_dns_name} # ${alb_dns_name} は、表示されたID
このURLにアクセスすると、下記のようなページが表示されるはずです。 (なおHTTPSではないため、アクセス前に警告が表示されます)
むすび
CAT では、TargetGroupBindingを用いることでAmazon EC2 → EKSへのレガシーマイグレーションを実現できました。 実際には、ALBの再構築が起きると不都合であることを当初は想定できておらず、追加開発が必要となりました。 様々な試行錯誤の末のTargetGroupBindingだったのですが、全体としては複雑さを増やさずにマイグレーションを実現できました。
また本記事は、Terraform HCLを用いたTargetGroupBindingの最小実装例を示すことができました、お役に立てれば幸いです。
★SHIFTグループ公式アドベントカレンダー2024【A】 IT技術関連トピック Day13は「ChatGPTで実現する!マルチエージェントの考え方を取り入れたプロトタイプ開発」(Haruki Takenouchi)
お問合せはお気軽に
SHIFTについて(コーポレートサイト)
SHIFTのサービスについて(サービスサイト)
SHIFTの導入事例
お役立ち資料はこちら
SHIFTの採用情報はこちら