AWS Gateway API Controller をインストールして EKS で VPC Lattice を扱ってみた
本日は AWS Gateway API Controller をインストールして EKS で VPC Lattice を扱ってみます。
AWS Gateway API Controller とは?
AWS で Kubernetes の Gateway API を利用するためのコンポーネントです。
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 のサービスが作成されます。
※ 画像を引用した元の資料はとてもわかりやすいので、こちらもご参照下さい。
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
としてプレフィックスリストからの通信を許可しています。
合わせて、IRSA 経由で利用可能な IAM ロールを作成しています。
権限は下記を参考にしています。
Pod Identity エージェントを利用する際の注意点
Pod Identity Agent を利用する場合、下記のような値を AWS Gateway API Controller インストール時に手動で設定する必要があります。
- CLUSTER_NAME
- CLUSTER_VPC_ID
- AWS_ACCOUNT_ID
- REGION
IMDSv2 を利用する場合は、そちらから情報を取得してくれるので設定不要となります。
手動で設定すれば良いだけではありますが、Pod Identity を推奨と書いてるのだから、良い感じに設定して欲しいなという気持ちはちょっとあります。
また、EKS Auto Mode の場合は IMDSv2 のホップリミットを設定できない都合上、Pod Identity を利用しつつ各種値は手動で設定するしかないことにも注意が必要です。
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
として指定しています。
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 ベースなので、サービスネットワークを別アカウントに共有してリクエストを送ってみます。
作成されたサービスネットワークを RAM で共有します。
マネージド型アクセス許可を利用します。
共有先のアカウントを指定します。
共有されたアカウントでリソース共有を承認します。
共有されたアカウント側の VPC にサービスネットワークを関連付けます。
関連付けた VPC 内に CloudShell 環境を作成してリクエストを送信すると無事リプライが返ってきました。
$ curl http://fast-api-default-0baa012cf6c1cffe6.7d67968.vpc-lattice-svcs.ap-northeast-1.on.aws
{"Hello":"World"}~