🤝

EKS Auto modeで構築分担のもやりが解消した気がする

2024/12/09に公開

はじめに

re:Invent 2024でEKS Auto Modeが発表されました!
https://aws.amazon.com/jp/eks/auto-mode/

以前1度だけAPI GatewayのバックエンドとしてEKSを扱う案件に携わったことがあるのですが、その際スキルセットの異なるチーム間で責任分界点をどうやって設けるか悩んだことがあります。
今回のアップデートで個人的な解が見つかった気がするので実際に構築してみました。
(他にも良いアイデアがあればコメントください!)

当時の振り返り

当時、以下のような2チームがありました。

  • AWSリソースを管理する「インフラチーム」
  • k8sクラスタを管理するk8sチーム

それぞれのチームはそれぞれのチームの得意領域には明るいものの、他方のチームの領域には明るくないという状況でした。
そこで、お互いの得意な領域だけを構築するような分担にできればよかったのですが、次に示す通り少し複雑な構成でした。

分担が困難だった理由

API Gateway(REST API)のバックエンドにEKSをデプロイする場合は、VPC Linkを使ってVPCに統合する必要があります。[1]
VPC LinkはAPI Gatewayが、VPC内に作成したNLBを介してEKSにアクセスするために利用するリソースです。

EKS内にトラフィックを届けるために、NLBのターゲットグループにk8sリソースを登録します。
ターゲットの指定にはIP指定やインスタンス指定などの方法がありますが、ここはk8sが持っているエフェメラルな特性(終了・再作成が不定期に発生する可能性がある特性)を考慮して、k8sリソースとして構築します。

k8sのネイティブなリソースタイプにはターゲットグループを管理するリソースタイプはなく、CRDと呼ばれるリソースを扱う必要があります。
利用したCRDはAWS Load Balancer Controller[2]のTargetGroupBinding[3]というリソースです。
ここでは深く解説しませんが、Load Balancer Controllerを利用できるようにするためには、

  • 必要なIAMロールを準備する
  • クラスタにコントローラーリソースを追加する

等の対応が必要になります。
ここがAWSとk8sの分界点として難しいと感じたポイントです。
AWSの知識とk8sの知識それぞれが求められる作業であり、分担が難しかったです。
また、ターゲットグループ関連のタスクを2チームで分け合う必要があることコントローラーリソース自体をユーザで管理することに対して、もやっと感を抱いていました。

当時の分担と構成

結果、当時の構成は以下のようになりました。

# 作業 担当チーム
1 VPC作成 インフラ
2 クラスタ作成 インフラ
3 AWS Load Balancer Controllerインストール インフラ
4 NLB・ターゲットグループ作成 インフラ
5 ターゲットグループの登録(TargetGroupBindingの作成) k8s
6 各種k8sリソース構築 k8s

これを構成図に表すと以下の通りです。
k8sアイコンがついている部分がk8sチームが作成・管理するリソースです。

Auto Modeが利用できるようになった今どういった構成が取れるか

Auto Modeの大きな利点としてAWS Load Balancer Controllerがマネージドになったことが挙げられます。
これによって、NLBの管理・構築をk8s管理に寄せられるようになったと考えています。

Auto Modeを利用した分担と構成

Auto ModeによってAWS Load Balancer ControllerインストールとTargetGroupBindingの作成が不要になり、分担もNLB・ターゲットグループ作成がk8sチームに変更になりました。
そもそも作業が減ったこともそうですが、お互いのチームが見ないといけない領域がなくなってすっきりしました。)

# 作業 担当チーム
1 VPC作成 インフラ
2 クラスタ作成 インフラ
3 AWS Load Balancer Controllerインストール -
4 NLB・ターゲットグループ作成 k8s
5 ターゲットグループの登録(TargetGroupBindingの作成) -
6 各種k8sリソース構築 k8s

構成図は以下の通りです。
クラスタ内のリソースはサブネットを除きすべて、k8sチーム管轄になったことが確認できます。

実際に構築してみた

今回はswagger-api/swagger-petstore[4]をデプロイして動作確認を行っていきます。

VPCの構築

今回は楽をするためにデフォルトVPCを利用しました。
ルートテーブルの変更等の作業を行いPrivateサブネット化した後、ELBを作成できるサブネットであることを示すためのタグ付け(kubernetes.io/role/internal-elb
:1)[5]を行います。

プライベートサブネット — 次の形式でタグ付けする必要があります。これは、サブネットを内部ロードバランサーに使用できることを、Kubernetes と AWS ロードバランサーコントローラーが認識できるようにするためです。eksctl または Amazon EKS AWS AWS CloudFormation テンプレートを使用して、2020 年 3 月 26 日以降に VPC を作成する場合、サブネットは、作成時に適切にタグ付けされます。Amazon EKS AWS AWS CloudFormation VPC テンプレートの詳細については、「Amazon EKS クラスターの Amazon VPC を作成する」を参照してください。
キー – kubernetes.io/role/internal-elb
値 – 1

EKSクラスタの構築

いきなり感想で恐縮ですが…楽でした…!
久しぶりに触ったということもあるのかもしれないですが、クラスタ作成だけでもかなり便利になったことを感じました。
↓画面だけでクラスタの作成が完結するのは正直驚きでした。

IAMロールに関しても「推奨ロールを作成」からサクッと作れてしまいますし、かなり難易度が下がった印象です。
アドオンの導入やノードグループの作成等も不要なのでこれでクラスタの構築は完了です。

NLB・ターゲットグループ作成・ k8sリソースの構築

まずはDeploymentの作成です。特筆する事項はないのでマニフェストだけ貼って詳細は割愛します。

deployment.yaml
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: petstore
  namespace: petstore
  labels:
    app: petstore
spec:
  replicas: 2
  selector:
    matchLabels:
      app: petstore
  template:
    metadata:
      labels:
        app: petstore
    spec:
      containers:
      - name: petstore
        image: swaggerapi/petstore3:unstable
        ports:
        - containerPort: 8080
          name: http
        resources:
          requests:
            cpu: "100m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: petstore
  namespace: petstore
spec:
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: petstore

次にNLBの構築です。
以下のドキュメントを参考に実装しました。
https://docs.aws.amazon.com/eks/latest/userguide/auto-configure-nlb.html

デフォルト値で定義しているということもありますが、Auto Modeがなかったころに定義していたマニフェストと比べるとかなりスリムになった印象です。
そして何よりもLoad Balancer Contollerの導入が不要なのが本当に快適でした。

service.yaml
service.yaml
apiVersion: v1
kind: Service
metadata:
  name: vpclink
  namespace: petstore
  annotations:
    aws-load-balancer-scheme: internet-facing ## 名前とギャップがありますが、先のタグ付けによりInternalなNLBが作成されます
spec:
  selector:
    app: petstore
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: LoadBalancer
  loadBalancerClass: eks.amazonaws.com/nlb

VPC Link・API Gatewayの構築

最後にVPC LinkとAPI Gatewayの構築です。

まずVPC Linkの構築。
プルダウンで先に構築したNLBが選択できました。

API GatewayはサンプルAPIで出てくるPetstoreを使って構築し、/の統合部分だけVPC Linkに変更しています。

Swagger Petstore - OpenAPI 3.0-prod-swagger-apigateway.json
{
  "swagger" : "2.0",
  "info" : {
    "description" : "This is a sample Pet Store Server based on the OpenAPI 3.0 specification.",
    "version" : "1.0.17",
    "title" : "Swagger Petstore - OpenAPI 3.0"
  },
  "host" : "oz2vid1f0h.execute-api.us-west-2.amazonaws.com",
  "basePath" : "/prod",
  "schemes" : [ "https" ],
  "paths" : {
    "/" : {
      "get" : {
        "operationId" : "getApiSpec",
        "produces" : [ "application/json" ],
        "responses" : {
          "200" : {
            "description" : "200 response",
            "schema" : {
              "$ref" : "#/definitions/MODELcf3f3f"
            }
          }
        },
        "security" : [ {
          "api_key" : [ ]
        } ],
        "x-amazon-apigateway-integration" : {
          "type" : "http_proxy",
          "connectionId" : "zm3d6l",
          "httpMethod" : "GET",
          "uri" : "http://internal-health-nlb-123456789.ap-northeast-1.elb.amazonaws.com/",
          "responses" : {
            "default" : {
              "statusCode" : "200"
            }
          },
          "requestParameters" : {
            "integration.request.header.Accept" : "'application/json'"
          },
          "connectionType" : "VPC_LINK",
          "passthroughBehavior" : "when_no_match",
          "tlsConfig" : {
            "insecureSkipVerification" : true
          }
        }
      }
    },
    "/pet" : {
      "put" : {
        "operationId" : "updatePet",
        "consumes" : [ "application/json" ],
        "produces" : [ "application/json" ],
        "parameters" : [ {
          "in" : "body",
          "name" : "Pet",
          "required" : true,
          "schema" : {
            "$ref" : "#/definitions/Pet"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "200 response",
            "schema" : {
              "$ref" : "#/definitions/Pet"
            }
          },
          "400" : {
            "description" : "400 response"
          },
          "404" : {
            "description" : "404 response"
          },
          "405" : {
            "description" : "405 response"
          }
        },
        "security" : [ {
          "api_key" : [ ]
        } ],
        "x-amazon-apigateway-integration" : {
          "type" : "http_proxy",
          "connectionId" : "zm3d6l",
          "httpMethod" : "PUT",
          "uri" : "http://internal-pet-nlb-123456789.ap-northeast-1.elb.amazonaws.com/pet",
          "responses" : {
            "default" : {
              "statusCode" : "200"
            }
          },
          "requestParameters" : {
            "integration.request.header.Content-Type" : "'application/json'"
          },
          "connectionType" : "VPC_LINK",
          "passthroughBehavior" : "when_no_match",
          "tlsConfig" : {
            "insecureSkipVerification" : true
          }
        }
      }
    },
    "/user" : {
      "post" : {
        "operationId" : "createUser",
        "consumes" : [ "application/json" ],
        "produces" : [ "application/json" ],
        "parameters" : [ {
          "in" : "body",
          "name" : "User",
          "required" : true,
          "schema" : {
            "$ref" : "#/definitions/User"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "200 response",
            "schema" : {
              "$ref" : "#/definitions/User"
            }
          }
        },
        "x-amazon-apigateway-integration" : {
          "type" : "http",
          "connectionId" : "zm3d6l",
          "httpMethod" : "ANY",
          "uri" : "http://internal-pet-nlb-123456789.ap-northeast-1.elb.amazonaws.com/user",
          "responses" : {
            "default" : {
              "statusCode" : "200"
            }
          },
          "connectionType" : "VPC_LINK",
          "passthroughBehavior" : "when_no_templates"
        }
      }
    },
    "/user/login" : {
      "get" : {
        "operationId" : "loginUser",
        "produces" : [ "application/json" ],
        "parameters" : [ {
          "name" : "password",
          "in" : "query",
          "required" : true,
          "type" : "string"
        }, {
          "name" : "username",
          "in" : "query",
          "required" : true,
          "type" : "string"
        } ],
        "responses" : {
          "200" : {
            "description" : "200 response",
            "schema" : {
              "$ref" : "#/definitions/MODEL79f9c3"
            }
          },
          "400" : {
            "description" : "400 response"
          }
        },
        "x-amazon-apigateway-integration" : {
          "type" : "http_proxy",
          "connectionId" : "zm3d6l",
          "httpMethod" : "GET",
          "uri" : "http://[NLBのDNS名]/user/login",
          "responses" : {
            "default" : {
              "statusCode" : "200"
            }
          },
          "requestParameters" : {
            "integration.request.header.Accept" : "'application/json'"
          },
          "connectionType" : "VPC_LINK",
          "passthroughBehavior" : "when_no_match",
          "tlsConfig" : {
            "insecureSkipVerification" : true
          }
        }
      }
    }
  },
  "securityDefinitions" : {
    "api_key" : {
      "type" : "apiKey",
      "name" : "x-api-key",
      "in" : "header"
    }
  },
  "definitions" : {
    "MODELcf3f3f" : {
      "type" : "object",
      "properties" : {
        "status" : {
          "type" : "string"
        },
        "version" : {
          "type" : "string"
        },
        "openapi" : {
          "type" : "object",
          "description" : "Full OpenAPI specification",
          "properties" : { }
        }
      }
    },
    "User" : {
      "type" : "object",
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "username" : {
          "type" : "string"
        },
        "email" : {
          "type" : "string"
        }
      }
    },
    "MODEL79f9c3" : {
      "type" : "object",
      "properties" : {
        "token" : {
          "type" : "string"
        }
      }
    },
    "Pet" : {
      "type" : "object",
      "required" : [ "name" ],
      "properties" : {
        "id" : {
          "type" : "integer",
          "format" : "int64"
        },
        "name" : {
          "type" : "string"
        },
        "status" : {
          "type" : "string",
          "enum" : [ "available", "pending", "sold" ]
        }
      }
    }
  }
}

最後に

今回はEKS Auto Modeのアップデートによって変化する構築分担についてまとめてみました。
マネージドな領域が増え、構築作業自体が減ったのもそうですが、分担がすっきりしてくるのも1つの大きなメリットだなと改めて感じました。
今回は手動で構築しましたが、商用環境だとIaCで構築することが多いと思います。
そういった場合でも分担がすっきりすることは依存関係がなくなってよいのではないでしょうか。

一方できれいに分担が分かれすぎて、お互いの領域に無関心になってしまうことは懸念されます。
どんなプロジェクトでも壁を作りすぎて狭間に落ちるのはもったいないので、両方を自戒を込めて…

脚注
  1. https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-private-integration.html ↩︎

  2. https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/ ↩︎

  3. https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/targetgroupbinding/targetgroupbinding/ ↩︎

  4. https://github.com/swagger-api/swagger-petstore ↩︎

  5. https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/network-load-balancing.html ↩︎

Discussion