Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

KubeIP v2でLocal ZonesのEKSワーカーに静的パブリックIPを割り当てる

By Wiriyang (Pup) PipatsakulrojDec 10, 20247 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

本記事では、KubeIP v2を用いてLocal Zone上のEKSノードにElastic IPを付与する方法を紹介します。

はじめに

低レイテンシ接続やデータローカライゼーション要件への対応を目的に、AWS Local ZonesでAmazon EKSを利用するケースが増えています。一方で、規制下のパートナーと通信するワークロードでは、静的なパブリックIPアドレスが求められる場面があります。しかし、Kubernetes (k8s) のワーカーノードは本質的にエフェメラルであり、バージョンアップグレードなどのタイミングでIPアドレスが変わってしまいます。KubeIPは、クラウドプロバイダーの機能を利用して静的IPアドレスを割り当てることで、ノードのライフサイクルに左右されない一貫したIP運用を可能にします。

ソリューション概要

本記事では、リージョンにマネージドノードグループ、Local Zoneにセルフマネージドノードグループを配置したAmazon EKSクラスターの構築手順を紹介します。狙いは、KubeIP v2を用いてLocal Zone上のEKSノードにElastic IPを割り当てる方法を示すことです。

以下のアーキテクチャ図は、Local Zoneで稼働するエッジサービスと、リージョンで稼働する2つのバックエンドサービスを表しています。

前提条件

手順

  1. サンプルリポジトリには、01-vpc、02-eks、03-kubeip、04-appの4つのフォルダが含まれています。01-vpcから04-appの順に適用していきます。

githubにあるサンプルを作業ディレクトリにクローンします。

git clone https://github.com/duoh/kubeip-eks-localzone
cd kubeip-eks-localzone
  1. まず、リージョンとLocal ZoneにVPCとサブネットをプロビジョニングします。main.tfでは、vpcモジュールでリージョン内にパブリック/プライベートサブネットを作成します。Local Zone内のサブネットはaws_subnetリソースで定義します。
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"
  name            = var.name
  cidr            = var.vpc_cidr
  azs             = local.azs
  public_subnets  = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k)]
  private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 10)]

  ...

  public_subnet_tags = {
    "kubernetes.io/cluster/${var.cluster_name}" = "shared"
    "kubernetes.io/role/elb"                    = "1"
  }
  private_subnet_tags = {
    "kubernetes.io/cluster/${var.cluster_name}" = "shared"
    "kubernetes.io/role/internal-elb"           = "1"
  }
}

resource "aws_subnet" "public-subnet-lz" {
  vpc_id                  = module.vpc.vpc_id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, 5)
  availability_zone       = local.lzs[0]
  map_public_ip_on_launch = true
}

resource "aws_subnet" "private-subnet-lz" {
  ...
}

...

入力変数を定義します。本例では、VPC名は`kubeip-lz-eks-vpc`、リージョンは`ap-southeast-1`、Local Zoneは`ap-southeast-1-bkk-1a`です。

cd 01-vpc
vi example.auto.tfvars
region          = "ap-southeast-1"
lzs             = ["ap-southeast-1-bkk-1a"]
name            = "kubeip-eks-lz-vpc"
vpc_cidr        = "10.0.0.0/16"
cluster_name    = "kubeip-eks-lz-cluster"

terraformでVPC、サブネット、ルートテーブルをデプロイします。

terraform init
terraform apply -auto-approve

出力された値を控えておきます。

Outputs:
private_subnets = [\
  "subnet-09139a5ea9dcd335d",\
  "subnet-0d7cc25e5066687be",\
  "subnet-04bb1e787c9b6e28f",\
]
public_subnets_local_zone = "subnet-0edd1b731c48fb7d7"
vpc_id = "vpc-061f434818a666d8b"
  1. 続いて、リージョンにマネージドノードグループを持つEKSクラスターを作成し、あわせてLocal Zoneにセルフマネージドノードグループを構築します。セルフマネージドノードグループ側のノードには、‘eks.amazonaws.com/nodegroup=public-lz-ng’および‘kubeip=use’というラベルを付与します。さらに、KubeIPのDaemonSet向けにIRSA (IAM Role for Service Accounts) を作成するため、kubeip_roleモジュールを利用します。
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  cluster_name    = var.cluster_name
  cluster_version = "1.30"

  vpc_id                         = var.vpc_id
  subnet_ids                     = var.private_subnets
  cluster_endpoint_public_access = true
  enable_irsa = true
  ...

  eks_managed_node_groups = {
    region-ng = {
      ...
      subnet_ids    = var.private_subnets
      labels = {
        region  = "true"
      }
      ...
    }
  }

  self_managed_node_groups = {
    local-ng = {
      ...
      subnet_ids    = [var.public_subnets_local_zone]
      bootstrap_extra_args = "--kubelet-extra-args '--node-labels=eks.amazonaws.com/nodegroup=public-lz-ng,kubeip=use'"
      ...
    }
  }
  enable_cluster_creator_admin_permissions = true
}

resource "aws_iam_policy" "kubeip-policy" {
  name        = "kubeip-policy"
  description = "KubeIP required permissions"
  policy = jsonencode({
    ...
  })
}

module "kubeip_role" {
  source    = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  role_name = var.kubeip_role_name
  role_policy_arns = {
    "kubeip-policy" = aws_iam_policy.kubeip-policy.arn
  }
  ...
}

...

入力変数を定義します。vpc_idprivate_subnetspublic_subnets_local_zoneには、前のステップで出力された値を貼り付けます。

cd ../02-eks
vi example.auto.tfvars
region                     = "ap-southeast-1"
vpc_id                     = "vpc-061f434818a666d8b"
private_subnets            = [\
                              "subnet-09139a5ea9dcd335d",\
                              "subnet-0d7cc25e5066687be",\
                              "subnet-04bb1e787c9b6e28f",\
                             ]
public_subnets_local_zone  = "subnet-0edd1b731c48fb7d7"
cluster_name               = "kubeip-eks-lz-cluster"
kubeip_role_name           = "kubeip-agent-role"
kubeip_sa_name             = "kubeip-agent-sa"

terraformでリソースをデプロイします。

terraform init
terraform apply -auto-approve

出力された値を控えておきます。

Outputs:
eks_cluster_name = "kubeip-eks-lz-cluster"
kubeip_role_arn = "arn:aws:iam::xxxxxxxxxxxx:role/kubeip-agent-role"
  1. 次に、Elastic IPアドレスと、DaemonSetやサービスアカウントといったk8sリソースをプロビジョニングします。aws_eipリソースで定義したElastic IPアドレスは、network_border_group引数の指定によりLocal Zone内に確保されます。あわせて、node_selectorフィールドでKubeIP DaemonSetをLocal Zone内のノードにスケジューリングします。KubeIPの便利な点として、AWSタグでフィルタしたEIPを選択できることが挙げられます。なお、KubeIPのサービスアカウントには必ずIRSAとRBACロールを割り当ててください。
resource "aws_eip" "kubeip" {
  count = 1
  tags = {
    Name        = "kubeip-${count.index}"
    environment = "demo"
    kubeip      = "reserved"
  }
  network_border_group = var.network_border_group
}

resource "kubernetes_daemonset" "kubeip_daemonset" {
  metadata {
    name      = "kubeip-agent"
    ...
  }
  spec {
    ...

    template {
      ...
      spec {
        service_account_name             = var.kubeip_sa_name
        ...
        container {
          name  = "kubeip-agent"
          image = "doitintl/kubeip-agent"
          env {
            name  = "FILTER"
            value = "Name=tag:kubeip,Values=reserved;Name=tag:environment,Values=demo"
          }
          ...
        }
        node_selector = {
          "eks.amazonaws.com/nodegroup" = "public-lz-ng"
          kubeip = "use"
        }
      }
    }
  }
  depends_on = [kubernetes_service_account.kubeip_service_account]
}

resource "kubernetes_service_account" "kubeip_service_account" {
  metadata {
    name        = var.kubeip_sa_name
    namespace   = "kube-system"
    annotations = {
      "eks.amazonaws.com/role-arn" = var.kubeip_role_arn
    }
  }
}

resource "kubernetes_cluster_role" "kubeip_cluster_role" {
  ...
}

resource "kubernetes_cluster_role_binding" "kubeip_cluster_role_binding" {
  ...
}

入力変数を定義します。kubeip_role_arnには、前のステップで出力された値を貼り付けます。

cd ../03-kubeip
vi example.auto.tfvars
region                  = "ap-southeast-1"
network_border_group    = "ap-southeast-1-bkk-1"
cluster_name            = "kubeip-eks-lz-cluster"
kubeip_role_arn         = "arn:aws:iam::xxxxxxxxxxxx:role/kubeip-agent-role"
kubeip_sa_name          = "kubeip-agent-sa"

TerraformでElastic IP (EIP) およびKubernetes (k8s) リソースをデプロイします。

terraform init
terraform apply -auto-approve

後の動作確認で使用するため、出力されたIPアドレスを控えておきます。

elastic_ips = [\
  "15.220.243.225",\
]
  1. 最後に、リージョンノードで2つのアプリケーションを、Local Zoneノードで1つのアプリケーションを稼働させるシナリオを動かしてみます。Local Zone側のアプリケーションは、エンドユーザーからのWebトラフィックを受け、リージョン側のアプリケーションへ振り分けるエッジサービスとして機能します。

app_a.yamlおよびapp_b.yamlは、リージョン内で稼働するバックエンドアプリを定義したファイルです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-a-deployment
  ...
spec:
  ...
    spec:
      containers:
      - name: app-a
        image: hashicorp/http-echo
        ports:
        - containerPort: 5678
        args: ["-text=<h1>I'm APP <em>A</em></h1>"]
      nodeSelector:
        region: "true"
---
apiVersion: v1
kind: Service
metadata:
  name: app-a-service
spec:
  type: ClusterIP
  ...

edge_svc.yamlは、Local Zone内のエッジアプリを定義したファイルです。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: edge-deployment
  ...
spec:
  ...
    spec:
      containers:
      - name: edge-svc
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: indexfile
          mountPath: /usr/share/nginx/html/
          readOnly: true
        - name: nginx-conf
          mountPath: /etc/nginx/conf.d/
      nodeSelector:
        kubeip: use
      volumes:
      ...

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-indexfile-configmap
data:
  index.html: |
    <h1>I am EDGE SERVICE</h1>
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf-configmap
data:
  default.conf: |
    server {
      resolver kube-dns.kube-system.svc.cluster.local valid=1s
      ...
    }
---
apiVersion: v1
kind: Service
metadata:
  name: edge-service
spec:
  type: NodePort
  ...

カレントディレクトリを04-appに移し、お使いのリージョンに合わせてaws cliを実行し、EKSクラスター認証用のkubeconfigを更新します。

cd ../04-app
aws eks update-kubeconfig --name kubeip-lz-cluster --region ap-southeast-1

kubectlを実行し、アクセスできるか確認します。

kubectl get no
NAME                                             STATUS   ROLES    AGE     VERSION
ip-10-0-10-213.ap-southeast-1.compute.internal   Ready    <none>   4m      v1.30.0-eks-036c24b
ip-10-0-5-76.ap-southeast-1.compute.internal     Ready    <none>   3m11s   v1.30.0-eks-036c24b

アプリケーションをデプロイし、稼働状況を確認します。

kubectl apply -f edge_svc.yaml
kubectl apply -f app_a.yaml
kubectl apply -f app_b.yaml

kubectl get po
NAME                                READY   STATUS    RESTARTS   AGE
app-a-deployment-587b484997-k6f8q   1/1     Running   0          12s
app-b-deployment-78bc6675db-2mk2s   1/1     Running   0          12s
edge-deployment-f6b9f4d5f-5j6f7     1/1     Running   0          13s

前のステップで取得したパブリックIPアドレスを使い、アプリケーションが期待どおりに動作するかを確認します。

curlでテストします。

% curl 15.220.243.225:30000
<h1>I am EDGE SERVICE</h1>

% curl 15.220.243.225:30000/app-a
<h1>I'm APP <em>A</em></h1>

% curl 15.220.243.225:30000/app-b
<h1>I'm APP <em>B</em></h1>

あるいはブラウザからアクセスして確認します。

まとめ

AWSのLocal Zonesでは、Elastic Load Balancing (ELB) のうちApplication Load Balancer (ALB) のみが利用可能です。さらに、ALBに割り当てられるパブリックIPアドレスは時間とともに変動する可能性があります。Local Zonesで稼働するEKSクラスターのワーカーノードに静的なパブリックIPを維持するには、独自の自動化が欠かせません。こうした課題に対し、KubeIPは有効な解決策となります。

DoiTは、インテリジェントなテクノロジーとマルチクラウドの専門知見により、Amazon Web Services (AWS) 、Google Cloud (GCP) 、Microsoft Azureといったパブリッククラウドの理解と活用を支援し、お客様のビジネス成長を後押しします。DoiTのサービス内容はdoit.comでご覧いただけます。