見出し画像

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の最小実装例を示すことができました、お役に立てれば幸いです。


執筆者プロフィール:村上 直
研究所にて、インフラの運用や調達、ひとりDevOps、セキュリティ、フレームワーク開発、研究発表といった業務に関わる。SHIFTに入社後も、統合型ソフトウェアテスト管理ツール「CAT」に関することのほか、グループ会社の技術調査や講義、デバッグ、コード解析、コーディング、リファクタリング、製品調査、などなど、手広く技術と関わっている。 現在は、成長速度を増している CAT/TD の負荷と戦いつつ、レガシーマイグレーションにまい進する日々。

SHIFTグループ公式アドベントカレンダー2024【A】 IT技術関連トピック Day13「ChatGPTで実現する!マルチエージェントの考え方を取り入れたプロトタイプ開発」(Haruki Takenouchi)

お問合せはお気軽に

SHIFTについて(コーポレートサイト)

SHIFTのサービスについて(サービスサイト)

SHIFTの導入事例

お役立ち資料はこちら

SHIFTの採用情報はこちら

PHOTO:UnsplashMiriam G