AWS Gateway API Controller をインストールして EKS で VPC Lattice を扱ってみた

AWS Gateway API Controller をインストールして EKS で VPC Lattice を扱ってみた

本日は AWS Gateway API Controller をインストールして EKS で VPC Lattice を扱ってみます。

AWS Gateway API Controller とは?

AWS で Kubernetes の Gateway API を利用するためのコンポーネントです。

https://www.gateway-api-controller.eks.aws.dev/latest/

Gateway API は Ingress API の後継となる Kubernetes リソースであり、HTTP/HTTPS 以外のプロトコルへのサポートやより高度なルーティング、高い拡張性などのメリットがあります。
本来 Gateway API と VPC Lattice は直接結びつく概念ではありませんが、EKS 上で Gateway API を利用する際、実装としては VPC Lattice が利用されます。

As a Kubernetes user, you can have a very Kubernetes-native experience using the VPC Lattice APIs. The following figure illustrates how VPC Lattice objects connect to Kubernetes Gateway API objects:
https://www.gateway-api-controller.eks.aws.dev/latest/concepts/overview/

Gateway API のリソースを作成すると裏で対応する VPC Lattice のサービスが作成されます。
※ 画像を引用した元の資料はとてもわかりやすいので、こちらもご参照下さい。

eks-lattice.png

Amazon VPC Lattice × EKS で実現するアプリケーションネットワーキング

リソース構築

Terraform を利用してリソースを作成します。
まず、VPC を作成します。

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 6.0.1"

  name = "eks-vpc"

  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  public_subnets  = ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24"]
  private_subnets = ["10.0.100.0/24", "10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = true
  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
  }

}

EKS クラスターを作成します。
今回は非 Auto Mode かつ、IRSA を利用する構成とします。
Auto Mode や Pod Identity などを使っても良いのですが、その場合は各種設定を手動で行う必要があったので、このようにしています(詳細は後述)。

locals {
  cluster_name                  = "test-cluster"
  k8s_service_account_namespace = "aws-application-networking-system"
  k8s_service_account_name      = "gateway-api-controller"
}

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0.4"

  name               = local.cluster_name
  kubernetes_version = "1.33"

  addons = {
    coredns                = {}
    kube-proxy             = {}
    vpc-cni                = {
      before_compute = true
    }
  }

  endpoint_public_access = true

  enable_irsa = true

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  enable_cluster_creator_admin_permissions = true

  eks_managed_node_groups = {
    default = {
      name = "default"

      ami_type       = "AL2023_x86_64_STANDARD"
      instance_types = ["t3.medium"]

      min_size     = 1
      max_size     = 3
      desired_size = 1

      metadata_options = {
        http_tokens                 = "required"
        http_put_response_hop_limit = 2
      }
    }
  }

  create_node_security_group = false
  security_group_additional_rules = {
    vpc_lattice_ingress = {
      description = "Allow VPC Lattice traffic"
      protocol    = "tcp"
      from_port   = 443
      to_port     = 443
      type        = "ingress"
      prefix_list_ids = ["pl-0596057d86614af83"]
    }
    vpc_lattice_ingress_ipv6 = {
      description = "Allow VPC Lattice IPv6 traffic"
      protocol    = "tcp"
      from_port   = 443
      to_port     = 443
      type        = "ingress"
      prefix_list_ids = ["pl-0c06554ceeafd8c94"]
    }
  }
}

module "iam_eks_role" {
  source    = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  role_name = "VPCLatticeController"

  role_policy_arns = {
    policy = aws_iam_policy.vpc_lattice_controller_policy.arn
  }

  oidc_providers = {
    one = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["${local.k8s_service_account_namespace}:${local.k8s_service_account_name}"]
    }
  }
}

data "aws_iam_policy_document" "vpc_lattice_controller_policy" {
  statement {
    effect = "Allow"
    actions = [
      "vpc-lattice:*",
      "ec2:DescribeVpcs",
      "ec2:DescribeSubnets",
      "ec2:DescribeTags",
      "ec2:DescribeSecurityGroups",
      "logs:CreateLogDelivery",
      "logs:GetLogDelivery",
      "logs:DescribeLogGroups",
      "logs:PutResourcePolicy",
      "logs:DescribeResourcePolicies",
      "logs:UpdateLogDelivery",
      "logs:DeleteLogDelivery",
      "logs:ListLogDeliveries",
      "tag:GetResources",
      "firehose:TagDeliveryStream",
      "s3:GetBucketPolicy",
      "s3:PutBucketPolicy"
    ]
    resources = ["*"]
  }

  statement {
    effect = "Allow"
    actions = [
      "iam:CreateServiceLinkedRole"
    ]
    resources = [
      "arn:aws:iam::*:role/aws-service-role/vpc-lattice.amazonaws.com/AWSServiceRoleForVpcLattice"
    ]
    condition {
      test     = "StringLike"
      variable = "iam:AWSServiceName"
      values   = ["vpc-lattice.amazonaws.com"]
    }
  }

  statement {
    effect = "Allow"
    actions = [
      "iam:CreateServiceLinkedRole"
    ]
    resources = [
      "arn:aws:iam::*:role/aws-service-role/delivery.logs.amazonaws.com/AWSServiceRoleForLogDelivery"
    ]
    condition {
      test     = "StringLike"
      variable = "iam:AWSServiceName"
      values   = ["delivery.logs.amazonaws.com"]
    }
  }
}

resource "aws_iam_policy" "vpc_lattice_controller_policy" {
  name   = "VPCLatticeControllerPolicy"
  policy = data.aws_iam_policy_document.vpc_lattice_controller_policy.json
}

VPC Lattice からのトラフィックを許可する必要があるため、security_group_additional_rules としてプレフィックスリストからの通信を許可しています。

https://dev.classmethod.jp/articles/use-prefix-lists-for-security-grouops-of-vpc-lattice/

合わせて、IRSA 経由で利用可能な IAM ロールを作成しています。
権限は下記を参考にしています。

https://github.com/aws/aws-application-networking-k8s/blob/main/files/controller-installation/recommended-inline-policy.json

Pod Identity エージェントを利用する際の注意点

Pod Identity Agent を利用する場合、下記のような値を AWS Gateway API Controller インストール時に手動で設定する必要があります。

  • CLUSTER_NAME
  • CLUSTER_VPC_ID
  • AWS_ACCOUNT_ID
  • REGION

IMDSv2 を利用する場合は、そちらから情報を取得してくれるので設定不要となります。

https://www.gateway-api-controller.eks.aws.dev/latest/guides/environment/

手動で設定すれば良いだけではありますが、Pod Identity を推奨と書いてるのだから、良い感じに設定して欲しいなという気持ちはちょっとあります。
また、EKS Auto Mode の場合は IMDSv2 のホップリミットを設定できない都合上、Pod Identity を利用しつつ各種値は手動で設定するしかないことにも注意が必要です。

https://github.com/aws/containers-roadmap/issues/2498

AWS Gateway API Controller のインストール

AWS Gateway API Controller をインストールしていきます。
まず、Namespace を作成します。

kubectl create ns aws-application-networking-system

Service Account も作成します。
先ほど Terraform で作成した IAM ロールを利用するよう、annotations を付与します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: gateway-api-controller
  namespace: aws-application-networking-system
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::xxxxxxxxxxxx:role/VPCLatticeController

apply します。

kubectl apply -f gateway-api-controller-service-account.yaml

Gateway API CRDs をインストールします。

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml

AWS Gateway API Controller は AWS 上で Gateway API を利用するためのコンポーネントなので、Gateway API を利用するための CRD は別途必要ということですね。

Install Gateway API CRDs
The latest Gateway API CRDs are available here. Please follow this installation process.
https://www.gateway-api-controller.eks.aws.dev/latest/guides/deploy/

ここのインストールを忘れると下記のようなエラーが発生して CrashLoopBackOff 状態になるのでお気をつけ下さい。

required CRDs check failed:missing required CRDs: gateway.networking.k8s.io/v1, Kind=Gateway, gateway.networking.k8s.io/v1, Kind=GatewayClass, gateway.networking.k8s.io/v1, Kind=HTTPRoute, gateway.networking.k8s.io/v1, Kind=GRPCRoute

AWS Gateway API Controller をインストールします。

helm install gateway-api-controller \
    oci://public.ecr.aws/aws-application-networking-k8s/aws-gateway-controller-chart \
    --version=v1.1.3 \
    --set=serviceAccount.create=false \
    --namespace aws-application-networking-system \
    --set=log.level=info \
    --set=defaultServiceNetwork=my-network

無事、Running 状態になりました。

% kubectl get pod -n aws-application-networking-system
NAME                                                              READY   STATUS    RESTARTS   AGE
gateway-api-controller-aws-gateway-controller-chart-6444d8gpk9n   1/1     Running   0          15s
gateway-api-controller-aws-gateway-controller-chart-6444d8h4khq   1/1     Running   0          15s

VPC 内からリクエストを送ってみる

まず、GatewayClass リソースを作成します。
VPC Lattice を利用するということをここで指定します。

apiVersion: gateway.networking.k8s.io/v1beta1
kind: GatewayClass
metadata:
  name: amazon-vpc-lattice
spec:
  controllerName: application-networking.k8s.aws/gateway-api-controller

続いて、Gateway リソースを作成します。

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-network
spec:
  gatewayClassName: amazon-vpc-lattice
  listeners:
    - name: http
      protocol: HTTP
      port: 80

ここで VPC Lattice のリスナー設定を行います。
また、利用する VPC Lattice のサービスネットワーク名を metadata.name として指定しています。

https://www.gateway-api-controller.eks.aws.dev/latest/api-types/gateway/

VPC Lattice のサービスネットワークは、Terraform などで作成して参照しても良いのですが、今回は AWS Gateway API Controller インストール時に --set=defaultServiceNetwork=my-network と指定しているため、そのタイミングで作成されています。

When set as a non-empty value, creates a service network with that name. The created service network will be also associated with cluster VPC.
https://www.gateway-api-controller.eks.aws.dev/latest/guides/environment/

今回は簡素化のためにサービスネットワークを自動生成させましたが、複雑なネットワーク構成の場合は Terraform などで作成して管理した方が扱いやすいと思います。
続いて、下記を利用してアプリケーションを作成します。

簡単な Fast API のアプリケーション

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

Dockerfile

FROM python:3.12-slim

WORKDIR /app

RUN pip install --upgrade pip
RUN pip install fastapi uvicorn

COPY . .

CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "80"]

Deployment 用のマニフェストファイル

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fast-api
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: fast-api
  replicas: 1
  template:
    metadata:
      labels:
        app.kubernetes.io/name: fast-api
    spec:
      containers:
        - image: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/fast-api-app:v1
          imagePullPolicy: Always
          name: fast-api
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "0.5"

Service 用のマニフェストファイル

apiVersion: v1
kind: Service
metadata:
  name: fast-api
spec:
  selector:
    app.kubernetes.io/name: fast-api
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP

アプリケーション作成完了後、作成した Gateway と Service を指定して HTTPRoute リソースを作成します。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: fast-api
spec:
  parentRefs:
    - name: my-network
      sectionName: http
  rules:
    - backendRefs:
        - name: fast-api
          kind: Service
          port: 80
      matches:
        - path:
            type: PathPrefix
            value: /

spec.parentRefs[0].name で Gateway の名前 (VPC Lattice のサービスネットワーク名)、spec.parentRefs[0].sectionName でリスナー名を指定します。
ここまで完了すると、AWS Gateway API Controller によって VPC Lattice のターゲットグループ作成やルーティング設定が行われます。
この状態で VPC 内に CloudShell 環境を作成してリクエストを投げてみます。

$ curl http://fast-api-default-0baa012cf6c1cffe6.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
{"Hello":"World"}

上手く返ってきました。

クロスアカウントでリクエストを送ってみる

せっかく VPC Lattice ベースなので、サービスネットワークを別アカウントに共有してリクエストを送ってみます。

lattice.png

作成されたサービスネットワークを RAM で共有します。

lattice1.png

マネージド型アクセス許可を利用します。

lattice2.png

共有先のアカウントを指定します。

lattice3.png

共有されたアカウントでリソース共有を承認します。

lattice4.png

共有されたアカウント側の VPC にサービスネットワークを関連付けます。

lattice5.png

関連付けた VPC 内に CloudShell 環境を作成してリクエストを送信すると無事リプライが返ってきました。

$ curl http://fast-api-default-0baa012cf6c1cffe6.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
{"Hello":"World"}~

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.