EKSでロードバランサーを構築する

皆さん、こんにちは。技術開発グループのn-ozawanです。
西日本側で梅雨入りしましたね。関東は週末ごろに梅雨入りするそうです。

本題です。
EKSで構築したコンテナ環境を外部に公開するにはロードバランサーを使います。ロードバランサーは外部からのトラフィックを複数のサーバに分散する仕組みで、サーバへの負荷軽減、サーバ障害時のフェースセーフなどに活躍します。今回はTerraformでKubernetesクラスタからALB (Application Load Balancer) を作成する仕組みを構築します。

AWS Load Balancer Controller のインストール

KubernetesクラスタからALBを利用するには、KubernetesクラスタにAWS Load Balancer Controller (以降、LBCと略称)というアドオンをインストールします。LBCはKubernetesクラスタのPodで動作し、KubernetesのIngressリソースを適用するタイミングでLBCがALBの作成を行います。

ゴール

LBCはKubernetesクラスタ内で動作します。Kubernetesクラスタの中からAWSリソースへアクセスするための権限付与が必要となります。ロードバランサーの構築に必要なステップは大きく2つです。1つ目はサービスアカウントの作成、2つ目はLBCアドオンのインストールです。今回はEKSのユーザーガイドを参考に、Terraformのコードを紹介します。

サービスアカウントの作成

Kubernetesのサービスアカウントは、PODと紐づけて動作するアカウントです。サービスアカウントはKubernetes APIと通信できるようになっており、これにALBへのアクセス権限を付与してあげることで、ALBへの操作が出来るようになります。

まず初めにKubernetesクラスタ用のIAM OIDCプロバイダを作成します。このOIDCプロバイダはサービスアカウントへ権限を一時的に付与してあげる働きがあります。

data "tls_certificate" "main" {
  url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}

resource "aws_iam_openid_connect_provider" "main" {
  client_id_list = ["sts.amazonaws.com"]
  thumbprint_list = data.tls_certificate.main.certificates[*].sha1_fingerprint
  url = data.tls_certificate.main.url
}

詳細な説明は省きますが、aws_eks_clusterにて環境を構築した後、クラスタ情報から認証用の発行者URLを取得して作成します。詳細はAWSのユーザーガイドもしくはTerraformのドキュメントを参照してください。

権限を発行してくれるOIDCプロバイダは作成できましたので、次はOIDCプロバイダがサービスアカウントに付与したい権限(=ロール)を作成します。まずはポリシーを作成します。

locals {
  service_account_name = "aws-load-balancer-controller"
}

resource "aws_iam_policy" "main" {
  name       = "${local.service_account_name}-policy"
  policy     = file("${path.module}/iam_policy.json")
}

iam_policy.jsonはサービスアカウントに付与したい権限が定義されているjsonファイルです。AWSが公開してくれていますので、それを使います。jsonファイルはローカルにダウンロードして使った方がよいでしょう。ダウンロードせずにURL指定でも出来ると思いますが、構築時に外部との接続を考慮しなくてはいけないのと、仮に公開先のjsonに変更が入った場合、再現性がなくなるので避けた方が良いかと思います。

次はロールを作成して、先ほど作成したポリシーをアタッチします。

data "aws_caller_identity" "current" {}

locals {
  oidc = replace(var.eks.identity[0].oidc[0].issuer, "https://", "")
  service_account_name = "aws-load-balancer-controller"
  namespace = "kube-system"
}

resource "aws_iam_role" "main" {
  name               = "${local.service_account_name}-role"
  assume_role_policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${local.oidc}"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "${local.oidc}:aud": "sts.amazonaws.com",
                    "${local.oidc}:sub": "system:serviceaccount:${local.namespace}:${local.service_account_name}"
                }
            }
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "eks_cluster_AmazonEKSClusterPolicy" {
  policy_arn = aws_iam_policy.main.arn
  role       = aws_iam_role.main.name
}

assume_role_policyには、先ほど作成したOIDCプロバイダから権限を付与するようなことが書かれています。最後にaws_iam_role_policy_attachmentで、ロールに対して、先ほど作成したポリシーをアタッチしています。

最後にサービスアカウントを作成します。

locals {
  service_account_name = "aws-load-balancer-controller"
  namespace = "kube-system"
}

resource "kubernetes_service_account" "lbc" {
  metadata {
    name = local.service_account_name
    namespace = local.namespace
    labels = {
      "app.kubernetes.io/name" = local.service_account_name
    }
    annotations = {
      "eks.amazonaws.com/role-arn" = aws_iam_role.main.arn
    }
  }
}

metadata.namenamespaceはLBCアドオンのインストール時にも利用します。annotations."eks.amazonaws.com/role-arn"に先ほど作成したロールのARNを指定しています。

LBCのインストール

LBCをインストールするにはHELMを利用します。HELMはKubernetes用のパッケージ管理ツールで、よく使う構成やアドオンなどをインストールすることが出来る優れものです。ユーザーガイドにはhelmコマンドでインストールする手順が記載されていますが、これをTerraformでコード化すると以下のようになります。

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

  dynamic "set" {
    for_each = {
      "serviceAccount.create" = false
      "serviceAccount.name" = "aws-load-balancer-controller"
      clusterName = var.eks.name
      region = "ap-northeast-1"
      vpcId = var.vpcid
      "image.repository" = "602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/amazon/aws-load-balancer-controller"
    }
    content {
      name = set.key
      value = set.value
    }
  }
}

Helmでインストールするにはhelm_releaseを利用します。nameはリリース名で、chartはインストールするチャートを指定します。チャートというのはhelmが管理しているパッケージだと思ってください。repositoryはLBCのチャートが管理されているリポジトリです。namespaceはPODが動作する名前空間を指定します。特に理由がなければkube-systemで良いと思います。

dynamic "set"ブロックには、LBCをKubernetesクラスタにインストールする際に必要となるパラメータを与えています。ここで与えるパラメータはLBCの動作に影響するものになります。for_eachでkey-valueを繰り返し、以下のようにsetブロックを生成しています。

set {
  name = "serviceAccount.create"
  value = false
}

set {
  name = "serviceAccount.name"
  value = "aws-load-balancer-controller"
}

# 以下略

パラメータの説明は割愛しますが、今回のお話で重要なのはserviceAccount.nameで、先ほど作成したサービスアカウントの名前を指定します。ここで指定することにより、LBCは先ほど作成したサービスアカウントで動作するようになり、ALBに対して操作する権限が得られるようになります。それ以外の詳細はこちらを参照してください。

おわりに

権限周りの仕組みは難しいですね。ここまででLBCのインストールと動作に必要なサービスアカウントの作成は出来ました。あとは、実際にKubernetesでIngressに必要なアノテーションを指定してあげれば、ALBによって外部に公開されることでしょう。以下に例を記載します。Ingressに指定するアノテーションの詳細はこちらを参照してください。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: http-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: http-service
            port:
              number: 80

ではまた。

Recommendおすすめブログ