🚀

Terraform で AWS Load Balancer Controller を宣言的にデプロイする

2020/12/07に公開

これは AWS Containers Advent Calendar 2020 7日目の記事です。

先日 AWS Load Balancer Controller (旧 ALB Ingress Controller) のリリースが発表されましたね!
色々な新機能がありますが、その中でもカスタムリソースとして新しく登場した TargetGroupBinding がドンピシャで欲しい機能だったので早速開発環境に導入して検証中です。
例のごとく公式ドキュメントの Installation Guide は eksctl を用いたものなので、 Terraform に読み替えて進めるときの Tips を紹介できればと思います。

なお、使用したツールやライブラリはほとんど 2020/12/7 時点での最新バージョンです。

なぜ TargetGroupBinding が待ち遠しかったか

IaC として Terraform などのツールを使っていると、Kubernetes との責任分界点で悩むことになります。具体的には旧 ALB Ingress Controller や External DNS など、Kubernetes 上で AWS リソースを管理する系のツールを使うかどうか、です。
もちろんユースケースや好みによりますが、AWS リソースと Kubernetes の依存関係が双方向になることでどうしても循環参照のような感覚になってしまうので、業務ではこれらのツールの導入を見送って NodePort による紐付けで対応していました。

この構成の明確なデメリットの1つとして、ALB のターゲットタイプに IP ではなくインスタンスしか設定できないという縛りがありました。しかし新しくなった AWS Load Balancer Controller の TargetGroupBinding を使うことで、Terraform で管理しているターゲットグループと Kubernetes で管理している Service を直接紐付けて、さらにターゲットタイプに IP を指定できるようになりました。

これで下図の ServiceC の用にターゲットグループから Pod に直接ルーティングできるようになり、トラフィックの無駄を減らせるので嬉しいことしかありません。ありがとうございます!!

Image

Terraform で宣言的にデプロイする

素の eksctl や terraform-provider-eksctl は使わずに、terraform-aws-eks ベースの構成で進めていきます。また、これ以外の terraform-aws-modules も積極的に使っています。特殊な構成でない限りプロダクション利用にも耐えうる便利なモジュール群だと思います。

EKS クラスタの作成

ほぼ公式の Example 通りですが enable_irsa = true にして IRSA を有効化することと、Managed Node Group を利用するのに不要なリソースを作らないようにしているところがポイントです。

data "aws_eks_cluster" "cluster" {
  name = module.eks.cluster_id
}

data "aws_eks_cluster_auth" "cluster" {
  name = module.eks.cluster_id
}

provider "kubernetes" {
  host                   = data.aws_eks_cluster.cluster.endpoint
  cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
  token                  = data.aws_eks_cluster_auth.cluster.token
  load_config_file       = false
}

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "13.2.1"

  cluster_name    = local.cluster_name
  cluster_version = "1.18"
  vpc_id          = module.vpc.vpc_id
  subnets         = concat(module.vpc.private_subnets, module.vpc.public_subnets)

  write_kubeconfig = false
  enable_irsa      = true

  # 余計なリソースを作らないようにする
  worker_create_security_group  = false
  cluster_create_security_group = false

  node_groups = {
    main = {
      desired_capacity = 1
      max_capacity     = 2
      min_capacity     = 1
      instance_type    = "t3.medium"
      subnets          = module.vpc.private_subnets
    }
  }
}

余談ですが、Managed Node Group を使う場合このモジュールの Security Group 関連の変数はほぼ無効になってしまうので、EKS クラスタがデフォルトで作る Security Group に無理やりルールを適用することで回避しています。

resource "aws_security_group_rule" "my_cluster" {
  type                     = "ingress"
  from_port                = 0
  to_port                  = 65535
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.lb.id

  security_group_id = data.aws_eks_cluster.my_cluster.vpc_config[0].cluster_security_group_id
}

こんなことをしなくても今は Managed Node Group に対して自前の Security Group を紐付けた Launch Template を適用できるので早く移行したいところです。。

AWS Load Balancer Controller 用の IRSA 作成

AWS Load Balancer Controller に適切な権限を与えるために IRSA を利用します。
それぞれ README における Setup IAM for ServiceAccount の2,3,4に対応していますが、Kubernetes 上の ServiceAccount までは作らないので後述する Helm のパラメータが少し変わります。

data "http" "albc_policy_json" {
  url = "https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.1.0/docs/install/iam_policy.json"
}

resource "aws_iam_policy" "albc" {
  name   = "AWSLoadBalancerControllerIAMPolicy"
  policy = data.http.albc_policy_json.body
}

module "albc_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
  version = "3.5.0"

  create_role                   = true
  role_name                     = "aws-load-balancer-controller"
  role_policy_arns              = [aws_iam_policy.albc.arn]
  provider_url                  = module.eks.cluster_oidc_issuer_url
  oidc_fully_qualified_subjects = ["system:serviceaccount:kube-system:aws-load-balancer-controller"]
}

Helm Chart デプロイ

初めは Argo CD を利用して AWS Load Balancer Controller の Helm Chart を宣言的にデプロイする方法を2通り紹介しようと思っていましたが、Amazon EKS add-ons がリリースされたことや @mumoshu さんのサンプルリポジトリを拝見して、クラスタを立ち上げた段階で Terraform 経由で Helm Chart を入れる方針に変えました。

デプロイを Argo CD から分離することで将来 Amazon EKS add-ons が AWS Load Balancer Controller に対応したときに移行しやすくするのが主な目的ですが、Argo CD の Web UI 自体を TargetGroupBinding を用いて公開したかったので、依存関係的に Argo CD よりも先に AWS Load Balancer Controller をデプロイしておきたいという要件にも合っていました。
(この辺りは構成ごとにベストなデプロイ方法は変わると思います)

provider "helm" {
  # kubernetes provider と同じ形で認証情報を渡す
  kubernetes {
    host                   = data.aws_eks_cluster.cluster.endpoint
    cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority[0].data)
    token                  = data.aws_eks_cluster_auth.cluster.token
    load_config_file       = false
  }
}

resource "helm_release" "albc" {
  name       = "aws-load-balancer-controller"
  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-load-balancer-controller"
  version    = "1.1.0"
  namespace  = "kube-system"

  values = [yamlencode(
    {
      clusterName = module.eks.cluster_id
      serviceAccount = {
        create = true
        name   = "aws-load-balancer-controller"
        annotations = {
          "eks.amazonaws.com/role-arn" = module.albc_irsa.this_iam_role_arn
        }
      }
    }
  )]
}

README 通りに set ブロックを使って1つずつパラメータを指定するのではなく、まとめて定義したのち yaml 形式に変換して渡しています。現実的には他にも色々と設定する項目があるのでこの方が拡張しやすいでしょう。

また、README だと ServiceAccount は eksctl で既に作られているので --set serviceAccount.create=false となっていますが、今回は必要なので true にしています。

CRD について

ここまでで1つだけ未対応のコマンドがあります。

$ kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"

これは TargetGroupBinding 用の CRD をデプロイするためのものですが、実際には Helm Chart をデプロイするときに CRD も一緒にデプロイされるのでこの操作自体が不要でした。

この挙動が気になったので少し調べてみたところ、そもそも Helm v2 と v3 で CRD のデプロイ方法が変わっていることが原因のようでした。
AWS Load Balancer Controller の Helm Chart は両方のバージョンに対応するために CRD のみ本体から分離してデプロイするような手順になっているのですが、CRD の定義ファイルが v3 準拠のディレクトリ構成で配置されている(crds/crds.yaml)ため、利用する側が Helm v3 を使っていると CRD も一緒にインストールしてくれる、と解釈できそうです。
今回は Helm v3 前提の terraform-provider-helm を使っているので、別途 CRD をインストールする必要はありませんでした。

おわりに

これで AWS Load Balancer Controller のデプロイが完了したので、各種リソースを apply できるようになりました。
これまでのリソースを一通りデプロイ出来るサンプルはこちらです。

https://github.com/yukin01/terraform-sandbox/tree/main/eks-albc

公式ドキュメントのインストール手順を別のアプローチで写経しただけですが、OSS に簡単なプルリクを出したり、Helm の挙動を調べたりできたので意外と知見が貯まりました。やっぱり素振りって大事ですね…!
何かの参考になれば幸いです。

参考

Discussion