コンテンツにスキップ

AWS EKS@AWSリソース

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. AWS EKS:Elastic Kubernetes Service

コントロールプレーン

▼ コントロールプレーンとは

コンテナオーケストレーションを実行する環境を提供する。

データプレーンのAWS VPC外に存在している。

▼ コントロールプレーンの仕組み

AWS EKSのコントロールプレーンは、開発者や他のAWSリソースからのリクエストを待ち受けるAPI、接続をAPIにルーティングするNLB、データプレーンを管理するコンポーネント、からなる。

eks_control-plane


データプレーン

▼ データプレーンとは

複数のホスト (AWS EC2、Fargate) のOS上でコンテナオーケストレーションを実行する。

on EC2』『on Fargate』という呼び方は、データプレーンがAWS EKSの実行環境 (on environment) の意味合いを持つからである。


01-02. セットアップ

コンソール画面の場合

▼ 設定項目と説明

設定項目 説明 補足
名前 クラスターの名前を設定する。
Kubernetesバージョン AWS EKS上で稼働するKubernetesのバージョンを設定する。 AWS EKSが対応できるKubernetesのバージョンは以下を参考にせよ。
https://docs.aws.amazon.com/eks/latest/userguide/platform-versions.html
クラスターサービスロール AWS EKS Clusterのサービスリンクロールを設定する。 https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html
シークレット Secretに保管するデータをKMSの暗号化キーで暗号化するか否かを設定する。
AWS VPC、サブネット ENIを配置するサブネットを設定する。 複数のAZにまたがっている必要がある。
クラスターセキュリティグループ AWS EKS Clusterのセキュリティグループを設定する。 インバウンドとアウトバウンドの両方のルールで、全てのIPアドレスを許可する必要がある。このセキュリティグループは、追加のセキュリティグループとして設定され、別途、AWSによってeks-cluster-sg-<AWS EKS Cluster名>というセキュリティグループも自動設定される。
https://yuutookun.hatenablog.com/entry/fargate_for_eks
クラスターIPアドレスファミリー PodとServiceに割り当てるClusterIPのIPアドレスタイプ (IPv4、IPv6) を設定する。
CIDRブロック ClusterIP Serviceに割り当てるIPアドレスのCIDRブロックを設定する。
クラスターエンドポイントアクセス kube-apiserverのリクエスト制限を設定する。
ネットワークアドオン ネットワークに関するAWS EKSアドオンを設定する。 執筆時点 (2023/02/05) では、AWS kube-proxy、AWS CoreDNS、AWS VPC CNI、を使用できる。
コントロールプレーンのログ コントロールプレーンコンポーネントのログをAWS CloudWatchログに出力するかどうかを設定する。 執筆時点 (2023/02/05) では、kube-apiserver (処理ログと監査ログの両方) 、aws-iam-authenticator-server (処理ログ) 、kube-controller-manager (処理ログ) 、cloud-controller-manager (処理ログ) 、kube-scheduler (処理ログ) 、のログを出力できる。


Terraformの公式モジュールの場合

ここでは、Terraformの公式モジュールを使用する。

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

  version = "<モジュールのバージョン>"

  cluster_name    = foo-eks-cluster
  cluster_version = "<Kubernetesのバージョン>"

  # kube-apiserverをプライベートアクセスにするか否か
  cluster_endpoint_private_access = true

  # kube-apiserverにパブリックリクエストできるか否か
  cluster_endpoint_public_access = false

  # AWS EKS Clusterのkube-apiserverにリクエストを送信できるCIDR
  cluster_endpoint_public_access_cidrs = ["*.*.*.*/32", "*.*.*.*/32", "*.*.*.*/32"]

  # AWS CloudWatchログに送信するログの種類
  cluster_enabled_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler",]

  # ログの保管期間
  cluster_log_retention_in_days = 365

  # セキュリティグループを作成するか否か
  cluster_create_security_group = true

  # セキュリティグループのID
  cluster_security_group_id = "*****"

  # IRSAを有効化するか否か
  enable_irsa = true

  # ワーカーNodeのセキュリティグループを作成するか否か
  worker_create_security_group = true

  # AWS VPCのID
  vpc_id = "vpc-*****"

  # サブネットのID
  subnets = ["subnet-*****", "subnet-*****", "subnet-*****"]

  # AWS EKSアドオン
  cluster_addons = {

    coredns = {
      resolve_conflicts = "OVERWRITE"
    }

    kube-proxy = {
      resolve_conflicts = "OVERWRITE"
    }

    vpc-cni = {
      resolve_conflicts = "OVERWRITE"
    }
  }

  # AWS EKSマネージドグループ
  eks_managed_node_groups = {
    node_group_name = "foo-group"
    instance_types  = ["m5.large"]
    min_size        = 3
    max_size        = 4
    desired_size    = 5
  }
}


AWS EKS Clusterの認証情報の追加

kubectlコマンドでAWS EKS Clusterを操作するためには、kubeconfigファイルへClusterの認証情報を登録する必要がある。

(1)

AWS CLIに認証情報を設定する。

$ aws configure
(2)

AWS EKS Clusterの名前を指定して、kubeconfigファイルにClusterの認証情報を登録する。

$ aws eks update-kubeconfig --region ap-northeast-1 --name foo-eks-cluster
(3)

kubectlコマンドの向き先を、AWS EKS Clusterのkube-apiserverに変更する。

$ kubectl config use-context <ClusterのARN>
(4)

kubectlコマンドの接続を確認する。

$ kubectl get pod


02. コントロールプレーンのコンポーネント

対応関係

コントロールプレーン上のAWSリソース Kubernetesリソース 補足
AWS EKSコントロールプレーン コントロールプレーンNode https://docs.aws.amazon.com/eks/latest/userguide/platform-versions.html
kube-apiserver kube-apiserver
kube-apiserverのロードバランサー (例:HAProxy) NLB


コントロールプレーンNode

コントロールプレーンNodeは、ユーザーの管理外のAWS VPCに所属している。


kube-apiserver

▼ aws-auth (ConfigMap) を経由したKubernetes RBACとの連携

eks_auth_architecture

ConfigMapを経由して、KubernetesのRBACと連携することにより、kubectlクライアントの認可スコープを制御する。

Kubernetesリソースの認可スコープは、IRSAで制御する。

(1)

あらかじめ、クライアント (kubectlクライアント、Kubernetesリソース) に紐づくIAMユーザーを作成しておく。

(2)

IAMユーザーがkube-apiserverのURLにリクエストを送信する。

kube-apiserverは、aws-iam-authenticator-serverにWebhookを送信する。

admission-controllersアドオンのWebhookではないことに注意する。

(3)

コントロールプレーンNode上のaws-iam-authenticator-serverは、IAM APIを使用してIAMユーザーを認証する。

(4)

もし認証に成功していた場合に、aws-iam-authenticator-serverは、ConfigMap (aws-auth) を確認する。

このConfigMapには、そのIAMユーザーに紐づくUserAccount / ServiceAccount / Group、RoleBinding / ClusterRoleBinding、が定義されている。

この時、kubectlクライアントの場合はUserAccount、Kubernetesリソースの場合はServiceAccount、を取得する。

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapAccounts: []
  mapUsers: []
  mapRoles: |
    - rolearn: arn:aws:iam::<AWSアカウントID>:role/foo-role # IAMロール名
      username: hiroki-it # IAMユーザー名
      groups:
        - system:masters # ClusterRoleBindingに定義されたGroup名
    - rolearn: arn:aws:iam::<AWSアカウントID>:role/bar-role # ワーカーNodeに紐付けたロール名
      username: system:node:{{EC2PrivateDNSName}} # ワーカーNodeの識別子
      groups:
        - system:bootstrappers
        - system:nodes
(5)

aws-iam-authenticator-serverは、UserAccount / ServiceAccount / Group、RoleBindingやClusterRoleBinding、の情報を含むレスポンスをkube-apiserverに返信する。

(6)

あとは、Kubernetesの標準の認可の仕組みである。

kube-apiserverは、UserAccount / ServiceAccount / Groupに紐づくRoleやClusterRoleを、RoleBindingやClusterRoleBindingを経由して取得する。

IAMユーザーは、Kubernetesリソースを操作できる。

▼ プリンシパルIAMロールとアクセスエントリーを経由したKubernetes Clusterの操作

プリンシパルIAMロールとアクセスエントリーを使用する場合、従来のaws-auth (ConfigMap) と比較して、AWS EKSへのアクセス制御をKubernetesリソースで管理する必要がない。

プリンシパルIAMロールに紐づくPodがAWS EKSに接続する時に、アクセスエントリーがこれを仲介する。

アクセスエントリーは、アクセスエントリーポリシーをIAMロールに動的にを設定する。

プリンシパルIAMロールとアクセスエントリーを経由して、kubectlクライアントの認可スコープを制御する。

# ArgoCDのPodにスイッチロールの権限を持たせるためのIAMロール
# 対象のAWS EKS ClusterにアクセスするためのIAMロールにスイッチロールできる
module "iam_assumable_role_with_oidc_argocd_access_entry_service" {

  source                        = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"

  version                       = "<バージョン>"

  # ArgoCDのPodに紐付けるIAMロール
  create_role                   = true
  role_name                     = "foo-argocd-access-entry-service"

  # AWS EKS ClusterのOIDCプロバイダーURLからhttpsプロトコルを除いたもの
  # ArgoCDは外部のAWS EKS Clusterで稼働している
  provider_url                  = replace(module.eks_argocd.cluster_oidc_issuer_url, "https://", "")

  # ArgoCDのPodのServiceAccount名
  # ServiceAccountは、Terraformではなく、マニフェストで定義した方が良い
  oidc_fully_qualified_subjects = [
    # argocd applicaton-controller
    "system:serviceaccount:argocd:foo-argocd-application-controller",
    # argocd-server
    "system:serviceaccount:argocd:foo-argocd-server",
    ...
  ]
}

# 対象のAWS EKS ClusterにアクセスするためのIAMロール
# 対象のAWS EKS Clusterでアクセスエントリーを設定する必要がある
module "iam_assumable_role_argocd_access_entry_cluster" {

  source               = "terraform-aws-modules/iam/aws//modules/iam-assumable-role"

  version              = "<バージョン>"

  create_role          = true
  role_name            = "foo-argocd-access-entry-cluster"

  trusted_role_actions = ["sts:AssumeRole"]

  trusted_role_arns    = [
    module.iam_assumable_role_with_oidc_argocd_access_entry_service.iam_role_arn
  ]

  role_requires_mfa    = false
}
# 対象のAWS EKS Clusterでアクセスエントリーを設定する
resource "aws_eks_access_entry" "argocd" {

  cluster_name      = aws_eks_cluster.foo_argocd.name

  principal_arn     = module.iam_assumable_role_with_oidc_argocd_principal.arn

  kubernetes_groups = ["argocd-group"]
}

# アクセスエントリーポリシー
resource "aws_eks_access_policy_association" "argocd" {

  cluster_name  = aws_eks_cluster.foo_argocd.name

  // アクセス元にどのようなIAMポリシーを付与するかを設定する
  policy_arn    = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"

  principal_arn = module.iam_assumable_role_with_oidc_argocd_principal.arn

  access_scope {
    type = "cluster"
  }

}

AWS EKS ClusterのSSL証明書をbase64方式エンコードした値 (-----BEGIN CERTIFICATE-----から-----END CERTIFICATE-----まで) をcaDataとして設定する。

aws eks describe-clusterコマンドやコンソール画面から確認できる。

apiVersion: v1
kind: Secret
metadata:
  name: <クラスター名>
  labels:
    argocd.argoproj.io/secret-type: cluster
type: Opaque
data:
  config: |
    awsAuthConfig:
      clusterName": "<クラスター名>",
      roleARN: "<対象のAWS EKS ClusterにアクセスするためのIAMロールARN>"
    tlsClientConfig:
      insecure: false,
      caData: "<AWS EKS ClusterのSSL証明書をbase64方式エンコードした値>"
  name: "<クラスター名>"
  server: "https://*****.gr7.ap-northeast-1.eks.amazonaws.com"

▼ パブリックアクセス/プライベートアクセス

kube-apiserverのインターネットへの公開範囲を設定できる。

プライベートアクセスの場合、AWS VPC内部からのみリクエストできるように制限でき、送信元IPアドレスを指定してアクセスを許可できる。

▼ AWS EKS Upgrade insights

非推奨apiVersion検出ツール (例:pluto) のようなクライアント側からの検証ではなく、kube-apiserver側で非推奨apiVersionを検出する。

kube-apiserverの監査ログから非推奨apiVersionを検出する。


NLB

記入中...


03. データプレーンのコンポーネント

対応関係

eks

データプレーン上のAWSリソース Kubernetesリソース 補足
FargateワーカーNode、AWS EC2ワーカーNode ワーカーNode https://docs.aws.amazon.com/eks/latest/userguide/eks-compute.html
AWS EKS Cluster Cluster https://docs.aws.amazon.com/eks/latest/userguide/clusters.html
AWS ALB Ingress IngressはAWS ALBに置き換える必要がある。AWS Load Balancerコントローラーを作成すると、AWS ALBは自動的に作成される。
https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html
https://blog.linkode.co.jp/entry/2020/06/26/095917#AWS-ALB-Ingress-Controller-for-Kubernetes
AWS Load Balancerコントローラー Ingressコントローラー AWS ALBを自動的に作成する。
https://aws.amazon.com/jp/blogs/news/using-alb-ingress-controller-with-amazon-eks-on
AWS API Gateway + NLB https://aws.amazon.com/jp/blogs/news/api-gateway-as-an-ingress-controller-for-eks/
EBS、AWS EFS PersistentVolume https://docs.aws.amazon.com/eks/latest/userguide/storage.html
Secrets Manager Secret https://docs.aws.amazon.com/eks/latest/userguide/manage-secrets.html
IAMユーザー ServiceAccount、UserAccount https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html
IAMロール Role、ClusterRole https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html


AWS EKS Cluster

▼ AWS EKS Clusterとは

FargateワーカーNodeやAWS EC2ワーカーNodeの管理グループ単位のこと。

KubernetesのClusterに相当する。


マルチワーカーNode

▼ マルチワーカーNodeとは

マルチワーカーNodeを作成する場合、AZごとにNodeを作成する。

eks_multi-node

▼ ワーカーNode間のファイル共有

AWS EFSを使用して、ワーカーNode間でファイルを共有する。

PodのファイルはワーカーNodeにマウントされるため、異なるワーカーNode上のPod間でファイルを共有したい場合 (例:PrometheusのローカルストレージをPod間で共有したい) に役立つ。

ただしできるだけ、ワーカーNodeをステートフルではなくステートレスにする必要があり、PodのファイルはワーカーNodeの外で管理する必要がある。


IRSA:IAM Roles for Service Accounts

▼ IRSAとは

eks_oidc

特にKubernetesリソースの認可スコープを制御する仕組みのこと。

kubectlクライアントの認可スコープは、RBACで制御する。

AWS EKSをSSOのIDプロバイダーとして使用することにより、IAMの認証フェーズをAWS EKSに委譲する。

▼ セットアップ

ここでは、SSOの種類でOIDCを選ぶとする。

(1)

SSOのIDプロバイダーのタイプは、OIDCとする。

『AWS EKS ClusterのOIDCプロバイダーURL』『OIDCプロバイダーのSSL証明書を署名する中間認証局 (例:CertificateManagerなど) のサムプリント』『IDプロバイダーによるトークンの発行対象 (sts.amazonaws.com) 』を使用して、OIDCプロバイダーを作成する。

data "tls_certificate" "this" {
  url = module.foo_eks.cluster_oidc_issuer_url
}

# OIDCのIDプロバイダーをAWSに登録する。
resource "aws_iam_openid_connect_provider" "this" {
  url             = module.foo_eks.cluster_oidc_issuer_url
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = data.tls_certificate.this[0].certificates[*].sha1_fingerprint
}
apiVersion: v1
kind: Pod
metadata:
  name: foo-pod
spec:
  containers:
    - name: app
      image: app:1.0.0
      volumeMounts:
        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
          name: kube-api-access-*****
          readOnly: "true"
        # OIDCのプロバイダーによるトークンをコンテナにマウントする
        - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
          name: aws-iam-token
          readOnly: "true"
  volumes:
    - name: kube-api-access-*****
      projected:
        defaultMode: 420
        sources:
          - serviceAccountToken:
              expirationSeconds: 3607
              path: token
          - configMap:
              items:
                - key: ca.crt
                  path: ca.crt
              name: kube-root-ca.crt
          - downwardAPI:
              items:
                - fieldRef:
                    apiVersion: v1
                    fieldPath: metadata.namespace
                  path: namespace
    # AWS EKSを使用している場合、AWS-APIへのリクエストに必要なトークンも設定される
    - name: aws-iam-token
      projected:
        defaultMode: 420
        sources:
          - serviceAccountToken:
              # OIDCのIDプロバイダーによるトークンの発行対象
              audience: sts.amazonaws.com
              expirationSeconds: 86400
              path: token
(2)

IRSAで使用するIAMロールの信頼されたエンティティに、AWS EKS ClusterのOIDCプロバイダーURLやユーザー名 (system:serviceaccount:<Namespac名>:<ServiceAccount名>) を設定する。

{"Version": "2012-10-17", "Statement": [
      {
        "Sid": "",
        "Effect": "Allow",
        "Principal":
          {
            "Federated": "arn:aws:iam::<AWSアカウントID>:oidc-provider/<AWS EKS ClusterのOIDCプロバイダーURL>",
          },
        # AssumeRoleWithWebIdentityを使用する
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
            # 完全一致
            "StringEquals":
              {
                "<AWS EKS ClusterのOIDCプロバイダーURL>:sub":
                  ["system:serviceaccount:<Namespac名>:<ServiceAccount名>"],
              },
          },
      },
    ]}
(3)

ServiceAccountの.metadata.annotations.eks.amazonaws.com/role-arnキーでIAMロールのARNを設定する。

これにより、AWS EKSで認証済みのServiceAccountにIAMロールを紐付けることができるようになる。

automountServiceAccountTokenキーが有効化されていることを確認する。

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: <IAMロールのARN>
  name: <信頼されたエンティティで指定したユーザー名にあるServiceAccount名>
  namespace: <信頼されたエンティティで指定したユーザー名にあるNamespace名>
automountServiceAccountToken: "true"
(4)

Podで、ServiceAccount名を設定する。

apiVersion: v1
kind: Pod
metadata:
  name: foo-pod
  namespace: foo-namespace
spec:
  serviceAccountName: foo-sa
  containers: ...

もし.metadata.annotations.eks.amazonaws.com/role-arnキーを使用しない場合、KubernetesリソースからAWSリソースへのアクセスがあった時は、AWS EC2ワーカーNodeやFargateワーカーNodeのIAMロールが使用される。

IRSAが登場するまでは、AWS EKS上でのワーカーNode (例:AWS EC2、Fargate) にしかIAMロールを紐付けることができず、KubernetesリソースにIAMロールを直接的に紐付けることはできなかった。

ServiceAccountのトークンは、コンテナにファイルとしてマウントされている。

$ printenv | sort -f

AWS_DEFAULT_REGION=ap-northeast-1
AWS_REGION=ap-northeast-1
AWS_ROLE_ARN=arn:aws:iam::<アカウントID>:role/argocd-reposerver
AWS_STS_REGIONAL_ENDPOINTS=regional
# ServiceAccountのトークン文字列が記載されたファイル
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token

...


デバッグ

▼ ダッシュボード

(1)

AWS EKS Clusterの名前を指定して、kubeconfigファイルにClusterの認証情報を登録する。

$ aws eks update-kubeconfig --region ap-northeast-1 --name foo-eks-cluster
(2)

kubectlコマンドの向き先を、AWS EKS Clusterのkube-apiserverに変更する。

$ kubectl config use-context <ClusterのARN>
(3)

マニフェストを使用して、ダッシュボードのKubernetesリソースをAWS EKSにデプロイする。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.5/aio/deploy/recommended.yaml
(4)

ダッシュボードに安全に接続するために、ServiceAccountをAWS EKSにデプロイする

$ kubectl apply -f service-account.yml
(5)

トークン文字列を取得する。

$ kubectl -n kube-system describe secret $(kubectl get secret -n kube-system | grep eks-admin | awk '{print $1}')
(6)

ローカルマシンからAWS EKSにポートフォワーディングを実行する。

$ kubectl proxy
(7)

ダッシュボードに接続する。

GET http://127.0.0.1:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#!/login HTTP/1.1

▼ ワーカーNode (例:AWS EC2、Fargate) への接続

セッションマネージャーを使用して、ワーカーNode (例:AWS EC2、Fargate) に接続できる。


03-02. Cluster内のIPアドレス

ServiceのためのIPアドレス

Serviceに割り当てるIPアドレスは、Service IP範囲によって決まる。

10.100.0.0/16または172.20.0.0/16のいずれかになる。

$ kubectl get service -A jsonpath='{.spec.clusterIP}'

# IP範囲の中からServiceにIPアドレスを割り当てる
172.20.74.199
172.20.0.1
172.20.123.203
172.20.0.10


PodのためのIPアドレス

PodのIPアドレスは、AWS EC2のENIとセカンダリープライベートIPアドレスに割り当てられるIPアドレスによって決まる。

AWS VPC CNI内のL-IPAMデーモンは、ENIとセカンダリープライベートIPアドレスの情報をCNIにプールする。


03-03. サブネット内外へのリクエスト

AWS EKSデータプレーンはプライベートサブネットで稼働させ、パブリックネットワーク上のALBから通信を受信すると良い。

この時、パブリックネットワークにあるレジストリから、IstioやArgoCDのコンテナイメージをプルできるように、AWS EKS FargateワーカーNodeとInternet Gateway間のネットワークを繋げる必要がある。

そのために、パブリックサブネットにAWS NAT Gatewayを置く。


03-04. データプレーン内外へのリクエスト

パブリックサブネット内のデータプレーンからのリクエスト

Podをパブリックサブネットに配置した場合に、パブリックネットワークやAWS VPC外にあるAWSリソース (AWS ECR、S3、AWS Systems Manager、AWS CloudWatchログ、DynamoDBなど) に対してリクエストを送信するために特に必要なものは無い。

この時、POD_SECURITY_GROUP_ENFORCING_MODE=standardに設定されたAWS VPC CNIはSNAT処理を実行し、クライアント側Podの送信元IPアドレスをAWS EC2ワーカーNodeのプライマリーENI (eth0) のIPアドレスに変換する。


プライベートサブネット内のデータプレーンからのリクエスト

▼ Pod外から内へのリクエスト

Podをプライベートサブネットに配置した場合に、プライベートサブネット外から内のデータプレーンへのリクエストをAWS Load Balancerコントローラーで受信し、AWS ALBを使用してPodにルーティングする。

eks_architecture

▼ 宛先情報の管理方法

リクエストの宛先情報は、Secretで管理し、Pod内のコンテナにマウントする。

apiVersion: v1
kind: Secret
metadata:
  name: foo-secret
data:
  # RDS (Aurora) の宛先情報
  DB_HOST_PRIMARY: <プライマリーインスタンスのエンドポイント>
  DB_HOST_READ: <リードレプリカのエンドポイント>
  DB_USER: bar
  DB_PASSWORD: baz
  # SQSの宛先情報
  SQS_QUEUE_NAME: foo-queue.fifo
  SQS_REGION: ap-northeast-1

▼ AWS VPC外の他のAWSリソースへのリクエスト

Podをプライベートサブネットに配置した場合に、パブリックネットワークやAWS VPC外にあるAWSリソース (AWS ECR、S3、AWS Systems Manager、AWS CloudWatchログ、AWS DynamoDBなど) に対してリクエストを送信するためには、AWS NAT GatewayまたはAWS VPCエンドポイントを配置する必要がある。

この時、クライアント側Podの送信元IPアドレスは、AWS NAT GatewayまたはAWS VPCエンドポイントに紐づくIPアドレスになる。

以下のようなエラーでPodが起動しない場合、Podが何らかの理由でイメージをプルできない可能性がある。

また、Podが作成されない限り、ワーカーNodeも作成されないことに注意する。

Pod provisioning timed out (will retry) for pod

▼ AWS VPC外のコントロールプレーンへのリクエスト

AWS EKS Clusterを作成すると、ENIも作成する。

これにより、データプレーンがAWS VPC外のコントロールプレーンと通信できるようになる。

データプレーンがコントロールプレーンをリクエストを送受信する場合、コントロールプレーンのクラスターエンドポイントの設定 (パブリック、プライベート) によって、マネージドなInterface型AWS VPCエンドポイントまたはAWS NAT Gatewayが必要になる。

AWS VPCエンドポイントの接続先 タイプ プライベートDNS名 説明
AWS EKSコントロールプレーン (たぶん) Interface マネージド プライベートサブネット内のAWS EC2 NodeからコントロールプレーンのあるAWS VPCにリクエストを送信するため。
AWS CloudWatchログ Interface logs.ap-northeast-1.amazonaws.com Pod内のコンテナのログをPOSTリクエストを送信するため。
AWS ECR Interface api.ecr.ap-northeast-1.amazonaws.com
*.dkr.ecr.ap-northeast-1.amazonaws.com
イメージのGETリクエストを送信するため。
S3 Gateway なし イメージのレイヤーをPOSTリクエストを送信するため
AWS Systems Manager Interface ssm.ap-northeast-1.amazonaws.com AWS Systems ManagerのパラメーターストアにGETリクエストを送信するため。
Secrets Manager Interface ssmmessage.ap-northeast-1.amazonaws.com Secrets Managerを使用するため。

▼ AWS VPC内の他のAWSリソースへのリクエスト

AWS VPC内にあるAWSリソース (RDSなど) の場合、そのAWS側のセキュリティグループにて、PodのプライベートサブネットのCIDRブロックを許可すればよい。


03-05. コントロールプレーン内外へのリクエスト

コントロールプレーンとワーカーNodeのネットワーク

コントロールプレーンはAWS VPC外にあり、ワーカーNodeはAWS VPC内にある。

kubectlコマンドやワーカーNodeからのリクエストのエンドポイントとしてNLBが配置されている。

このNLBを経由して、コントロールプレーン内のkube-apiserverにリクエストを送信できる。

AWS VPC外からNLBへの443番ポートに対するネットワークからのリクエストはデフォルトでは許可されているが、拒否するように設定できる。

eks_control-plane_worker_network


クラスターエンドポイントのリクエスト制限

▼ パブリックのみの場合

基本的には、全てのIPアドレスからkube-apiserverにリクエストを送信できる。

プライベートサブネット内にワーカーNodeがある場合、AWS NAT Gatewayを経由して、kube-apiserverにリクエストを送信することになる。

▼ パブリックとプライベートの場合

パブリックとプライベートを許可する場合、指定したCIDRブロックに含まれるIPアドレスからのみ、kube-apiserverにリクエストを送信できる。

プライベートサブネット内にワーカーNodeがある場合、以下のいずれかの経路でkube-apiserverにリクエストを送信することになる。

  • AWS NAT Gatewayを経由して、AWS NAT Gatewayを経由して、パブリック制限を通過する
  • ENI (Interface型のAWS VPCエンドポイント) を経由して、プライベート制限を通過する

eks_control-plane_worker_network_public_private_endpoint

AWS VPC外のAWSリソース (例:AWS EKSコントロールプレーン、AWS ECR、S3、AWS Systems Manager、AWS CloudWatchログ、DynamoDBなど) にリクエストを送信する場合、専用のAWS VPCエンドポイントを設ける必要がある。

AWS VPCエンドポイントの接続先 タイプ プライベートDNS名 説明
AWS EKSコントロールプレーン (たぶん) Interface マネージド プライベートサブネット内のAWS EC2 NodeからコントロールプレーンのあるAWS VPCにリクエストを送信するため。
AWS CloudWatchログ Interface logs.ap-northeast-1.amazonaws.com Pod内のコンテナのログをPOSTリクエストを送信するため。
AWS ECR Interface api.ecr.ap-northeast-1.amazonaws.com
*.dkr.ecr.ap-northeast-1.amazonaws.com
イメージのGETリクエストを送信するため。
S3 Gateway なし イメージのレイヤーをPOSTリクエストを送信するため
AWS Systems Manager Interface ssm.ap-northeast-1.amazonaws.com AWS Systems ManagerのパラメーターストアにGETリクエストを送信するため。
Secrets Manager Interface ssmmessage.ap-northeast-1.amazonaws.com Secrets Managerを使用するため。

▼ プライベートのみの場合

プライベートのみを許可する場合、このNLBは閉じられ、AWS VPC内からしかkube-apiserverにリクエストを送信できなくなる。

プライベートサブネット内にワーカーNodeがある場合、AWS VPCエンドポイントを経由して、kube-apiserverにリクエストを送信することになる。

この状態で、kubectlコマンドでkube-apiserverにリクエストを送信できるようにする方法としては、以下のパターンがある。

接続元パターン 接続方法パターン
ローカルマシン セッションマネージャー
AWS VPC内の踏み台AWS EC2 セッションマネージャー、SSH
AWS VPC内のCloud9 セッションマネージャー、SSH


04. on AWS EC2 (AWS EC2ワーカーNode)

AWS EC2ワーカーNode

▼ AWS EC2ワーカーNodeとは

AWS EC2で稼働するKubernetesのホストのこと。

Fargateと比べてカスタマイズ性が高く、ワーカーNode当たりで稼働するPod数に重み付けを設定できる。

一方で、各AWS EC2のハードウェアリソースの消費量をユーザーが管理しなければならないため、Kubernetesのホストの管理が大変である。

eks_on_ec2


セットアップ

▼ IAMポリシー

AWS EC2ワーカーNodeが、自身の所属するClusterにリクエストを送信できるように、AWS EC2ワーカーNodeにAmazonAWS EKSWorkerNodePolicyを付与する必要がある。

AWS EC2ワーカーNode内のPodがAWS ECRからコンテナイメージをプルできるように、AWS EC2ワーカーNodeにAmazonEC2ContainerRegistryReadOnlyを付与する必要がある。

これにより、PodのコンテナごとにAWSの認証情報をマウントする必要がなくなる。

aws-nodeのPodがAWSのネットワーク系のAPIにリクエストを送信できるように、IRSA用のServiceAccountにAmazonAWS EKS_CNI_Policy (IPv4の場合) または AmazonAWS EKS_CNI_IPv6_Policy (IPv6の場合) を付与する必要がある。


監視

▼ ログ収集

Node上のログの場所 説明
/var/log/containers このディレクトリに、そのAWS EC2ワーカーNode上のPod内コンテナのログファイルのシンボリックリンクを作成する。
var/log/aws-routed-eni/ipamd.log このディレクトリに、AWS VPC CNIのL-IPAMデーモンのログを出力する。
/var/log/aws-routed-eni/plugin.log 同上


04-02. Nodeグループ (on AWS EC2)

マネージド

▼ マネージドNodeグループ

  • Nodeグループ内の各AWS EC2ワーカーNodeの作成
  • Nodeグループに紐づくAutoScalingグループの作成
  • AWS EC2ワーカーNodeのOSやミドルウェアの各種アップグレード

を自動化する。

Nodeグループは、AWS EC2ワーカーNodeが配置されるプライベートサブネットのAZにこれをスケジューリングさせるように、AutoScalingグループに各AZを自動的に設定する。

AutoScalingグループの機能を使用すれば、AWS EC2ワーカーNodeの自動的な起動/停止を設定できる。

▼ Nodeグループの定期アクション

同じNodeグループのAWS EC2ワーカーNodeの定期アクションを設定する。

AWS EKSのテスト環境の請求料金を節約するために、昼間に通常の個数にスケールアウトし、夜間に0個にスケールインするようにすれば、ワーカーNodeを夜間だけ停止させられる。

▼ 起動テンプレートとAutoScalingグループとの紐付け

マネージドNodeグループは、あくまでAWS EC2 Nodeのライフサイクルを管理するだけである。

どのようなAWS EC2 Nodeを管理するのかは起動テンプレートとAutoScalingグループを使用して定義する必要がある。


セルフマネージド

▼ セルフマネージドNodeグループ

  • Nodeグループ内の各AWS EC2ワーカーNodeの作成
  • Nodeグループに紐づくAutoScalingグループの作成
  • AWS EC2ワーカーNodeのOSやミドルウェアの各種アップグレード

をユーザーが管理する。

AutoScalingグループの機能を使用すれば、AWS EC2ワーカーNodeの自動的な起動/停止を設定できる。


Node数の変更

Nodeグループ (マネージドNodeグループ、セルフマネージドNodeグループ) では、希望数を変更することで現在のNode数を変更できる。

設定後、AutoScalingグループは希望数で設定したNode数を維持する (Karpenterのドキュメントでは、これを『静的』と表現している)。

希望数の他に最大数と最小数を設定できるが、これらは実際は機能しない。

もし負荷の状況に応じてスケーリングしたい場合、Nodeのスケーリングツール (例:ClusterAutoscaler、Karpenterなど) を使用しないと、最大数と最小数の設定に応じたスケーリングを実施してくれない。


AWS EC2へのタグ付けの例

▼ マネージドNodeグループ

タグ 説明
Name AWS EC2ワーカーNodeの名前 Nodeグループで指定する起動テンプレートのタグに、Nameタグを設定しておく。起動するAWS EC2ワーカーNodeにAWS EC2の名前はNameタグで決まる仕組みのため、起動テンプレートによってワーカーNode名を設定させることができる。

▼ セルフマネージドNodeグループ

タグ 説明
Name AWS EC2ワーカーNodeの名前 AWS EC2の名前はNameタグで決まる仕組みのため、Nodeグループに参加させるAWS EC2ワーカーNodeのNameタグに、ワーカーNode名を設定しておく。
kubernetes.io/cluster/<AWS EKS Cluster名> owned セルフマネージド型のAWS EC2ワーカーNodeを使用する場合、ユーザーが作成したAWS EC2をNodeグループに参加させるために、必要である。


04-03. AWS EC2 Node AMI

AWS EC2ワーカーNodeの最適化AMI

▼ AWS EC2ワーカーNodeの最適化AMIとは

任意のAWS EC2ワーカーNodeを使用できるが、AWSが用意している最適化AMIを選んだ方が良い。

このAMIには、AWS EC2がAWS EKSと連携するために必要なソフトウェアがプリインストールされており、AWS EC2ワーカーNodeをセットアップする手間が省ける。

必ずしも、全てのAWS EC2ワーカーNodeを同じAMIで構築する必要はない。

AWS EC2ワーカーNodeを種類ごとに異なるAMIで作成し、特定のアプリを含むPodは特定のAWS EC2ワーカーNodeにスケジューリングさせる (例:計算処理系アプリはAWS EKS最適化高速AMIのAWS EC2ワーカーNode上で動かす) といった方法でもよい。

▼ AWS EKS 最適化 Amazon Linux

AWS EKSのための標準的なAWS EC2を作成できる。最も推奨である。

aws ssm get-parameterコマンドを使用すると、公式が提供するマシンイメージのIDを確認できる。

注意点として、AMIのマイナーバージョンは固定できるが、パッチバージョンは固定できない。

そのため、パッチバージョンがアップグレードされる度に、AMIのIDは変わる。

AMIのIDを固定するためには、AMIをダウンロードして自前で管理する必要がある。

$ aws ssm get-parameter \
    --name /aws/service/eks/optimized-ami/<バージョン>/amazon-linux-2/recommended/image_id \
    --region ap-northeast-1 \
    --query "Parameter.Value" \
    --output text

▼ AWS EKS 最適化高速 Amazon Linux

GPUが搭載されたAWS EC2やAWS EC2 Inf1インスタンスを作成できる。

GPUが必要なアプリケーションの含むPod (計算処理系、機械学習系のアプリケーション) と相性が良い。

▼ AWS EKS 最適化 ARM Amazon Linux

ARMベースのプロセッサーが搭載されたAWS EC2を作成できる。

▼ AWS EKS 最適化 Bottlerocket AMI

コンテナに特化したAWS EC2を作成できる。

$ aws ssm get-parameter \
    --name /aws/service/bottlerocket/aws-k8s-<バージョン>/x86_64/latest/image_id \
    --region ap-northeast-1 \
    --query "Parameter.Value" \
    --output text


AWS EC2ワーカーNodeのカスタムAMI

▼ AWS EC2ワーカーNodeのカスタムAMIとは

AWS EC2ワーカーNodeの最適化AMIではないAMIのこと。


kubelet-config.jsonファイル (KubeletConfiguration)

AWS EC2ワーカーNodeのkubeletを設定する。

{
  "kind": "KubeletConfiguration",
  "apiVersion": "kubelet.config.k8s.io/v1beta1",
  "address": "0.0.0.0",
  "authentication":
    {
      "anonymous": {"enabled": "false"},
      "webhook": {"cacheTTL": "2m0s", "enabled": "true"},
      "x509": {"clientCAFile": "/etc/kubernetes/pki/ca.crt"},
    },
  "authorization":
    {
      "mode": "Webhook",
      "webhook": {"cacheAuthorizedTTL": "5m0s", "cacheUnauthorizedTTL": "30s"},
    },
  "clusterDomain": "cluster.local",
  "hairpinMode": "hairpin-veth",
  "readOnlyPort": 0,
  "cgroupDriver": "cgroupfs",
  "cgroupRoot": "/",
  "featureGates": {"RotateKubeletServerCertificate": "true"},
  "protectKernelDefaults": "true",
  "serializeImagePulls": "false",
  "serverTLSBootstrap": "true",
  "tlsCipherSuites":
    [
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
      "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
      "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
      "TLS_RSA_WITH_AES_256_GCM_SHA384",
      "TLS_RSA_WITH_AES_128_GCM_SHA256",
    ],
}


ユーザーデータファイル

▼ ユーザーデータファイルとは

AWS EC2 Nodeの起動時に任意のコマンドを実行できるようにする。

また、セルフマネージドNodeグループやマネージドNodeグループにて、AWS EC2ワーカーNodeのAMIにカスタムAMIを使用したり、任意のAMIで起動テンプレートを使用する場合、AWS側で決められたコマンド (bootstrap.shファイル) を実行する必要がある。

一方で、マネージドNodeグループにて、起動テンプレートを使用せずにAWS EC2ワーカーNodeを作成する場合、ユーザーデータファイルを自動で作成してくれるため、これは不要である。

bootstrap.shファイル

AWS EC2ワーカーNodeのカスタムAMIに必要なファイルである。

AWS EC2ワーカーNode起動時のユーザーデータファイル内で、bootstrap.shファイルに決められたパラメーターを渡す必要がある。

ユーザーデータファイル内で、bootstrap.shファイルにパラメーターを渡す必要がある。

#!/bin/bash

# ユーザーデータファイル

set -o xtrace

# 主要なパラメーターは以下の通り。
# その他のパラメーター:https://github.com/awslabs/amazon-eks-ami/blob/584f9a56c76fc9e7e8632f6ea45e29d45f2eab63/files/bootstrap.sh#L14-L35
#
# --b64-cluster-ca:kube-apiserverのSSL証明書の値を設定する。
# --apiserver-endpoint:kube-apiserverのエンドポイントを設定する。
# --container-runtime:コンテナランタイムとしてcontainerdを使用する。代わりに、dockerも使用できる。

/etc/eks/bootstrap.sh foo-eks-cluster \
  --b64-cluster-ca ***** \
  --apiserver-endpoint https://*****.gr7.ap-northeast-1.eks.amazonaws.com \
  --container-runtime containerd

なお、設定可能な全てのパラメーターは、以下から確認できる。

よく使用するパラメーター配下の通りである。

パラメーター 説明
--apiserver-endpoint AWS EKS Clusterのkube-apiserverのエンドポイントを設定する。
--b64-cluster-ca kube-apiserverのエンドポイントを設定した場合に、HTTPSでリクエストするために、SSL証明書を設定する。
--container-runtime containerd コンテナランタイムの種類を設定する。
--kubelet-extra-args --node-labels=nodetype=foo --max-pods=110 KubeletConfigurationのデフォルト値を上書きする。
--use-max-pods false kubeletの--max-podsオプションを有効化するかどうかを設定する。Kubeletが実行可能なPod数を設定する。Kubeletではこのオプションは非推奨になっており、代わりにKubeletConfigurationに渡すようにする。

ユーザーデータファイル内で必要なパラメーターの注意点として、各パラメーターはハードコーディングしないようにする。

パラメーターストアにパラメーターを永続化し、ユーザーデータファイル内に出力する。

#!/bin/bash

# ユーザーデータファイル

set -o xtrace

PARAMETERS=$(aws ssm get-parameters-by-path --with-decryption --path "/eks/foo-eks-cluster")

# ClusterのSSL証明書、kube-apiserverのエンドポイントの値をパラメーターストアから取得する。
for parameter in $(echo ${PARAMETERS} | jq -r '.Parameters[] | .Name + "=" + .Value'); do
  echo "export ${parameter##*/}"
done >> "${EXPORT_ENVS}"

# 出力する。
source "${EXPORT_ENVS}"

/etc/eks/bootstrap.sh foo-eks-cluster \
  --b64-cluster-ca $B64_CLUSTER_CA \
  --apiserver-endpoint $APISERVER_ENDPOINT \
  --container-runtime containerd

▼ AWS EC2ワーカーNodeのイメージキャッシュ削除

kubeletは、Nodeのイメージのキャッシュを作成する。

イメージのキャッシュは、kubeletによるガベージコレクションまたはNodeの再作成で削除される。

KubeletConfigurationの--image-gc-high-thresholdオプションで、キャッシュ削除の閾値とするディスク使用率を設定する。

--image-gc-low-thresholdオプションで、解放しようとするディスク使用率を設定する。

*実装例*

ディスク使用率が70%を超過した場合に、ディスク使用率50%分を解放する。

#!/bin/bash

# ユーザーデータファイル

set -o xtrace

# --image-gc-high-thresholdオプションに値が既に設定されていなければ、設定を挿入する。
if ! grep -q imageGCHighThresholdPercent /etc/kubernetes/kubelet/kubelet-config.json;
then
    sed -i '/"apiVersion*/a \ \ "imageGCHighThresholdPercent": 70,' /etc/kubernetes/kubelet/kubelet-config.json
fi

# --image-gc-low-thresholdオプションに値が既に設定されていなければ、設定を挿入する。
if ! grep -q imageGCLowThresholdPercent /etc/kubernetes/kubelet/kubelet-config.json;
then
    sed -i '/"imageGCHigh*/a \ \ "imageGCLowThresholdPercent": 50,' /etc/kubernetes/kubelet/kubelet-config.json
fi

/etc/eks/bootstrap.sh foo-eks-cluster \
  --b64-cluster-ca $B64_CLUSTER_CA \
  --apiserver-endpoint $APISERVER_ENDPOINT \
  --container-runtime containerd

▼ AWS EC2ワーカーNodeのGraceful Shutdown

デフォルトでは、AWS EC2ワーカーNodeは新しいPodのスケジューリングを禁止した後、Podの退避を待たずに停止してしまう。

kubeletを使用してAWS EC2ワーカーNodeの停止を待機し、Podが終了する (ワーカーNodeから退避させる) までの時間を稼ぐ。

ワーカーNodeの停止までの待機中に終了できたPodは、Failedステータスとなる。

KubeletConfigurationの--shutdown-grace-periodオプション (shutdownGracePeriod) で、ワーカーNodeの停止を待機する期間を設定する。

また--shutdown-grace-period-critical-podsオプション (shutdownGracePeriodCriticalPods) で、特に重要なPodの終了のために待機する時間を設定する。

InhibitDelayMaxSecには、--shutdown-grace-periodオプションと同じ秒数 (単位は不要) を設定する。

注意点として、この時間が長すぎると、ワーカーNodeの停止の全体時間が長くなるため、結果的にローリングアップグレードでNodeの所要時間も長くなってしまう。

*実装例*

ワーカーNodeの停止を6分だけ待機し、その後に停止を始める。

6分のうち後半2分を重要なPodのために停止に割り当てる。

#!/bin/bash

# ユーザーデータファイル

set -o xtrace

# --shutdown-grace-periodオプションに値が既に設定されていなければ、設定を挿入する。
if ! grep -q shutdownGracePeriod /etc/kubernetes/kubelet/kubelet-config.json;
then
    sed -i '/"apiVersion*/a \ \ "shutdownGracePeriod": "360s",' /etc/kubernetes/kubelet/kubelet-config.json
fi

# --shutdown-grace-period-critical-podsオプションに値が既に設定されていなければ、設定を挿入する。
if ! grep -q shutdownGracePeriodCriticalPods /etc/kubernetes/kubelet/kubelet-config.json;
then
    sed -i '/"shutdownGracePeriod*/a \ \ "shutdownGracePeriodCriticalPods": "120s",' /etc/kubernetes/kubelet/kubelet-config.json
fi

mkdir -p /etc/systemd/logind.conf.d
cat <<EOF > /etc/systemd/logind.conf.d/50-max-delay.conf
[Login]
InhibitDelayMaxSec=360
EOF

sudo systemctl restart systemd-logind

/etc/eks/bootstrap.sh foo-eks-cluster \
  --b64-cluster-ca $B64_CLUSTER_CA \
  --apiserver-endpoint $APISERVER_ENDPOINT \
  --container-runtime containerd

FailedステータスなPodはそのままでは削除できない。

そのため、FailedステータスなPodを自動で削除してくれるツール (例:descheduler) や、以下のような削除コマンドを持つCronJobを作成するとよい。

for ns in $(kubectl get namespace -o name | cut -d / -f 2); do
  echo $ns
  kubectl get pod -n $ns -o json \
    | jq -r '.items[] | select(.status.phase == "Failed") | select(.status.reason == "Shutdown" or .status.reason == "NodeShutdown" or .status.reason == "Terminated") | .metadata.name' \
    | xargs --no-run-if-empty --max-args=100 --verbose kubectl delete pod -n $ns
done


04-04. セットアップ

コンソール画面の場合

▼ マネージドNodeグループの場合

起動テンプレートを使用し、AWS EC2ワーカーNodeを作成する。

起動テンプレートのタグ付け機能を使用してAWS EC2にタグ付けでき、これは任意である。

▼ セルフマネージドNodeグループの場合

任意のAutoScalingにて、起動テンプレートを使用してAWS EC2ワーカーNodeを作成する。

AutoScalingのタグ付け機能を使用して、kubernetes.io/cluster/<AWS EKS Cluster名>タグ (値はowned) をつけ、Nodeグループに明示的に参加させる必要がある。

なお、起動テンプレートも合わせて使用でき、これは任意である。


Terraformの場合

▼ マネージドNodeグループの場合

起動テンプレートを使用し、AWS EC2ワーカーNodeを作成する。

# Nodeグループ
resource "aws_eks_node_group" "foo" {

  ...

  # Nodeグループの種類だけ、起動テンプレートを設定する
  launch_template {
    id      = aws_launch_template.foo1.id
    version = "$Latest"
  }

  launch_template {
    id      = aws_launch_template.foo2.id
    version = "$Latest"
  }

  ...
}

# 起動テンプレート
resource "aws_launch_template" "foo" {

  # タグ付けは任意である
  tag_specifications {
    tags = {
      Env  = var.environment
    }
  }

  ...
}

# Nodeグループのタグ
resource "aws_autoscaling_group_tag" "foo" {
  for_each = local.tags

  autoscaling_group_name = aws_eks_node_group.foo.name

  # Nodeグループに設定する全てのタグに対して適用する
  tag {
    key                 = each.key
    value               = each.value
    # 実装時点 (2023/06/06) で、マネージドNodeグループは自身の作成するAutoScalingグループにタグ付けできない
    # そのままではterraform planのたびに、AutoScalingグループにタグ付けしようとする差分がでてしまうため、Nodeグループ外からAutoScalingグループのタグ付けを有効化する
    # @see
    # https://github.com/aws/containers-roadmap/issues/608
    # https://github.com/terraform-aws-modules/terraform-aws-eks/issues/1558#issuecomment-1030633280
    propagate_at_launch = true
  }
}

▼ セルフマネージドNodeグループの場合

任意のAutoScalingにて、起動テンプレートを使用してAWS EC2ワーカーNodeを作成する。

resource "aws_autoscaling_group" "foo" {

  ...

  tag {
    key   = "Name"
    value = "foo-instance"
  }

  # AutoScalingのタグに kubernetes.io/cluster/<AWS EKS Cluster名> をつける必要がある
  tag {
    key   = "kubernetes.io/cluster/<AWS EKS Cluster名>"
    value = "owned"
  }

  # 起動テンプレートは任意である
  launch_template {
    id      = aws_launch_template.foo.id
    version = "$Latest"
  }

  ...
}


05. on Fargate (FargateワーカーNode)

on Fargate (FargateワーカーNode) とは

記入中...


監視

▼ メトリクス収集

FargateワーカーNode内のメトリクスのデータポイントを収集する上で、FargateワーカーNodeはDaemonSetに非対応である。

そのため、メトリクス収集コンテナをサイドカーコンテナとして配置する必要がある。

収集ツールとして、OpenTelemetryをサポートしている。

▼ ログ収集

FargateワーカーNode内のログを転送する上で、FargateはDaemonSetに非対応のため、ログ転送コンテナをサイドカーコンテナとして配置する必要がある。

ロググーティングツールとして、FluentBitをサポートしている。

(1)

ログ転送コンテナのためのNamespaceを作成する。

名前は、必ずaws-observabilityとする。

apiVersion: v1
kind: Namespace
metadata:
  name: aws-observability
  labels:
    aws-observability: enabled
(2)

aws-observability内でaws-loggingという名前のConfigMapを作成する。

これより、ログ転送コンテナとしてFluentBitコンテナが作成され、PodからAWS CloudWatchログにログを送信できるようになる。

名前は、必ずaws-loggingとする。

$ kubectl apply -f config-map.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-logging
  namespace: aws-observability
data:
  output.conf: |
    [OUTPUT]
        Name cloudwatch
        Match *
        region ap-northeast-1
        log_group_name fluent-bit-cloudwatch
        log_stream_prefix from-fluent-bit-
        auto_create_group true
(3)

FargateワーカーNodeにAWS ECRやCloudWatchへの認可スコープを持つポッド実行ロールを付与しておく。

これにより、KubernetesリソースにAWSへの認可スコープが付与され、ServiceAccountやSecretを作成せずとも、PodがAWS ECRからコンテナイメージをプルできる様になる。

一方で、Pod内のコンテナには認可スコープが付与されない。

そのため、Podが作成された後に必要な認可スコープ (例:コンテナがRDSにリクエストを送信する認可スコープなど) に関しては、ServiceAccountとIAMロールの紐付けが必要である。



05-02. セットアップ

コンソール画面の場合

▼ AWS EC2ワーカーNodeとの比較

AWS EC2ワーカーNodeと比較して、使用できない機能については、以下のリンクを参考にせよ。


FargateワーカーNode

▼ FargateワーカーNodeとは

eks_on_fargate

Fargate上で稼働するKubernetesのホストのこと。

KubernetesのワーカーNodeに相当する。

AWS EC2ワーカーNodeと比べてカスタマイズ性が低く、ワーカーNode当たりで稼働するPod数はAWSが管理する。

一方で、各AWS EC2のハードウェアリソースの消費量をユーザーが管理しなくてもよいため、Kubernetesのホストの管理が楽である。

▼ FargateワーカーNodeを使用できない場合

以下の場合は、AWS EC2ワーカーNodeを使用する。

  • FargateワーカーNodeでは、DaemonSetが使えない。サイドカーを配置する必要がある。
  • Fargateで設定可能な最大スペックを超えたスペックが必要である。
  • EmptyDir Volume以外が必要である。
  • FargateワーカーNodeでは、サービスメッシュにAppMeshしか使えない。もし、AppMeshを使いたくない場合は、AWS EC2ワーカーNodeを使用する。

▼ Fargateプロファイル

Fargateを設定する。

コンポーネント名 説明 補足
Pod実行ロール kubeletがAWSリソースにリクエストを送信できるように、Podにロールを設定する。 ・実行ポリシー (AmazonAWS EKSFargatePodExecutionRolePolicy) には、AWS ECRへの認可スコープのみが付与されている。
・信頼されたエンティティでは、eks-fargate-pods.amazonaws.comを設定する必要がある。
https://docs.aws.amazon.com/eks/latest/userguide/pod-execution-role.html
サブネット AWS EKS FargateワーカーNodeが起動するサブネットIDを設定する。 プライベートサブネットを設定する必要がある。
ポッドセレクタ (Namespace) AWS EKS FargateワーカーNodeにスケジューリングさせるPodを固定できるように、PodのNamespaceの値を設定する。 kube-systemdefaultを指定するKubernetesリソースが稼働できるように、ポッドセレクタにこれを追加する必要がある。
・IstioやArgoCDを、それ専用のNamespaceで稼働させる場合は、そのNamespaceのためのプロファイルを作成しておく必要がある。
ポッドセレクタ (Label) AWS EKS FargateワーカーNodeにスケジューリングさせるPodを固定できるように、Podの任意のlabelキーの値を設定する。


05-02. Nodeグループ (on Fargate)

記入中...


06. アップグレード

アップグレードとは

AWS EKS Clusterにて、コントロールプレーンとデータプレーンをローリング方式でアップグレードする。

AWSはIaaSのため、AMIを指定すれば、NodeのOSのアップグレードも実施してくれる。

執筆時点 (2022/01/28) では、AWSのAPIを経由してupdateConfig値を設定すれば、アップグレード時のサージ数を設定できる。


アップグレードの仕組み

▼ データプレーンの場合

AWS EKS Clusterのアップグレード時、以下の仕組みでデータプレーンのワーカーNodeをローリングアップグレードする。

また、Nodeグループに紐づくAutoScalingグループのAZリバランシングの仕組みによって、既存のワーカーNodeと同じAZでワーカーNodeを再作成する。

(1)

Nodeグループ単位でローリングアップグレードできるように、AWS EKS ClusterのワーカーNode数の設定 (Node希望数、Node最大数) を自動的に増加させる。

(2)

旧ワーカーNodeを残して、新しいAMIを使用したワーカーNodeを作成する。

旧ワーカーNodeが稼働しているAZで新ワーカーNodeを作成する。

旧ワーカーNodeを残せるのは、あらかじめAWS EKS ClusterのワーカーNode数の設定 (Node希望数、Node最大数) を増加させているためである。

(3)

各AZで新ワーカーNodeを正しく作成できることを検証する。

(4)

AZリバランシングが成功すれば、旧ワーカーNodeでDrainが開始され、Podのスケジューリングが無効化される。

(5)

新ワーカーNode上でPodをスケジューリングさせ直し、旧ワーカーNodeを削除する。

(6)

最終的に、アップグレード前のワーカーNode数 (Node希望数) に戻る。

手順

AWS EKS Clusterはおおよそ以下の方法でアップグレードする。

(1)

コントロールプレーンNodeをアップグレードする。

(2)

ワーカーNodeをアップグレードする。

コントロールプレーン上のkube-apiserverのバージョンに応じた新しいAMIを使用して、ワーカーNodeを再作成する。

(3)

ワーカーNode上のAWS EKSアドオン (例:AWS CoreDNS、AWS kube-proxy、AWS VPC CNIなど) をアップグレードする。