8️⃣

Terraform、Argo CDで構成管理しつつ遊ベるEKSクラスタを構築する

に公開

グロービス SREチームの坂口です。

グロービスのデジタル・プラットフォーム部門では複数のWebサービスを展開していて、それらのインフラはSREチームによってEKSを使って管理されています。
私はこれまでECSやCloudRunなどのコンテナホスティングサービスの利用経験はありましたが、Kubernetesを触るのは初めてでした。
そのため入社を機にKubernetes入門したのですが、Kubernetesで登場する概念やそれに付随して出てくるEKSやGitOpsといった要素を個々で把握することはできても、それらを組み合わせて構成管理しつつ運用していく全体像を掴むまでに結構苦戦しました。

そこで本記事では個人学習用に、チームで採用している主要技術(Terraform、EKS、Karpenter、Argo CD)で、インフラの構築からKubernetesマニフェストの継続的なデプロイを実現するリポジトリを作成したので、その構成を紹介します。

AWSの主要なサービスは知っているけどEKSに馴染みない方向けに、EKSを用いたプラットフォームの理解を深める一助となれば幸いです。

全体像

概要図概要図

以下の技術スタックを用いています。

  • Terraform:インフラをコード管理できるIaCの代表的なツール
  • EKS:AWSが提供するKubernetesのマネージドサービス。EBS、EFS、ロードバランサー、Secrets Managerなど、AWSが提供している各種サービスと連携する
  • Karpenter:EKSのクラスターオートスケーラー。従来のNodeGroup(AWSが提供する標準的なNodeを管理する仕組み。実体がEC2 Auto Scalingグループ)と比べるとスケールが速くなり、より柔軟で動的なノードの構成が可能にするもの

Karpenterのスケール確認で用いているアプリケーションのコードとマニフェストは自前では用意せず、AWSのサンプルで登場するgame-2048をお借りしています。

とりわけ一般的に動的なスケールが求められるアプリケーションコンテナをKarpenterを使って管理するNodePoolで稼働させます。ただし動的なスケールを指示するKarpenter自体のコンテナはこの上にデプロイすることはできないので、NodeGroupで管理するインスタンス上に稼働させる必要があります。合わせてスケールが必要ないArgo CDのコンテナもNodeGroupで稼働させます。

コード全体のurlは以下です。

https://github.com/reiichii/EKS-de-asobo

注意

本リポジトリの構成は本番ワークロードでの運用を想定したものでもないため、以下ご注意ください。
また業務

  • 把握目的でミニマム構成にすることを優先したため、TerraformコードとKubernetesマニフェストを一つのリポジトリで管理しています。
  • 環境ごとの設定を想定したディレクトリ構成や、各種CI/CDパイプラインは割愛しています。
  • Argo CDとGitリポジトリの通信に関して、今回はパブリックリポジトリを参照していますが、業務では基本的にプライベートリポジトリを参照するため、追加で認証の設定が必要になります。

構築手順

構築は以下の流れで行います。

  • VPC、EKSクラスターを構築
  • AWS Load Balancer Controller、Argo CDを構築する
  • Karpenterのセットアップ
  • アプリケーションのデプロイ

具体的な手順については リポジトリのREADMEをご参照ください。

構成について

AWS馴染みのサービス(VPC、IAM、ALB等)から EKS特有の概念の橋渡し部分と、Argo CDを活用したGitOpsの観点で以下4点解説します。

ネットワーク基盤とEKSの設定

terraform/main.tf
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
    ...
  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
    "karpenter.sh/discovery"          = local.cluster_name
  }

Terraformのmodule.vpcでは、AWSが提供している[1]モジュールを用いて専用VPCを作成し、3つのアベイラビリティゾーンにパブリック・プライベートサブネットをそれぞれ配置しています。
とりわけprivate_subnetに対して上記のようなタグを付与しています。
AWS Load Balancer ControllerはKubernetesのIngressやServiceといったネットワーク系のリソースに基づいてAWS ALB/NLBを自動作成・管理します。subnetにkubernetes.io/role/internal-elbタグを付与することで、リソースを構築する際に適切なサブネットを自動発見して利用できるようになります。
KarpenterがEC2インスタンスを作成する際も同様で、「karpenter.sh/discovery = {EKSクラスター名}」のタグにより、サブネットを自動発見して利用します。

Pod配置戦略

terraform/main.tf
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
    ...
  eks_managed_node_groups = {
    ops = {
      ami_type       = "BOTTLEROCKET_x86_64"
      instance_types = ["m5.large", "t3.medium"]
      capacity_type  = "SPOT"

      min_size     = 1
      max_size     = 3
      desired_size = 2

      labels = {
        "workload-type"           = "ops"
        "karpenter.sh/controller" = "true"
      }
    }
  }

module.eksの中でEKSクラスターの土台の部分の定義を行っています。
とりわけ eks_managed_node_groups に関して、AWS Auto Scalingグループの定義をし設定した条件のインスタンスが起動するようになっています。

podの配置を指定するKubernetesの設定はいくつか種類がありますが、今回はnodeSelector を使用し、上記で指定しているkarpenter.sh/controller というラベルが付与されたNodeを選択するようにしています。

terraform/modules/argocd/main.tf
resource "helm_release" "argocd" {
  name       = "argocd"
    ...
  values = [
    yamlencode({
      global = {
        nodeSelector = var.node_selector
      }

      controller = {
        nodeSelector = var.node_selector
      }

      server = {
        nodeSelector = var.node_selector
terraform/modules/karpenter/main.tf
resource "helm_release" "karpenter" {
  name             = "karpenter"
    ...
  values = [
    <<-EOT
    replicas: 1
    nodeSelector:
      karpenter.sh/controller: 'true'

AWS Load Balancer Controllerの設定

terraform/modules/albc ではAWS Load Balancer Controllerを導入しています。これによりArgo CD、game-2048アプリケーションそれぞれで定義しているingressの設定により、自動的にインターネット向けALBが作成されます。設定がArgo CDにも内包されているため、Argo CDでも同様に自動でLoad Balancerが構築されます。
とりわけ alb.ingress.kubernetes.io/target-type: ipの設定により、EC2インスタンスではなくPodのIPアドレスが直接ALBのターゲットとして登録されるようになります。

terraform/modules/argocd/main.tf
resource "helm_release" "argocd" {
  name       = "argocd"
    ...
  values = [
    yamlencode({
          ...
        ingress = {
          enabled = true
          annotations = {
            "kubernetes.io/ingress.class"            = "alb"
            "alb.ingress.kubernetes.io/scheme"       = "internet-facing"
            "alb.ingress.kubernetes.io/target-type"  = "ip"
            "alb.ingress.kubernetes.io/listen-ports" = "[{\"HTTP\":80}]"
          }
          paths    = ["/"]
          pathType = "Prefix"
        }
argocd_apps/game_manifests/game-2048.yaml
    ...
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: game-2048
  name: game-2048
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: game-2048
                port:
                  name: http

GitOps周りの設定

argocd_apps/game.yamlargocd_apps/ops.yamlでは、ApplicationSetリソースによってGitHub上のマニフェストファイルをクラスターの状態として自動同期されます。

ApplicationSetリソースでは、以下のようにソース設定(spec.template.spec.source)があり、これにより設定配下で管理するマニフェストを一括で自動的にクラスターに適用できるようになっています。(Argo CDのアプリケーション管理手法の一つでApp of Appsパターンと呼ばれているもの)

argocd_apps/ops.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: karpenter-nodepool
  namespace: argocd
spec:
    ...
  template:
    ...
    spec:
      project: default
      source:
        repoURL: 'https://github.com/reiichii/EKS-de-asobo'
        path: 'argocd_apps/ops_manifests'
        targetRevision: 'main'
        directory:
          recurse: false

参考

脚注
  1. 訂正:公式ではなくコミュニティサポートのものでした ↩︎

GLOBIS Tech

Discussion