はじめに
これまでの記事では、kubeadmで構築したKubernetesクラスターでOIDCエンドポイントを公開し、PodのServiceAccountからIAMロールを引き受けるところまでを確認しました。
以下は、前回の記事です。
次の目標は、EBSのようなAWSリソースをKubernetesから利用することです。そのためにはPodの認証だけでなく、ノードとEC2インスタンスの対応付けも必要になります。
本記事では、kubeadmで構築したKubernetesクラスターにAWS Cloud Controller Manager(以下CCM)を導入し、ノードにEC2に関する情報が設定されることを確認します。
本記事でやること
本記事では、CCMによってKubernetesのノードへEC2インスタンスID、インスタンスタイプ、リージョン、ゾーンなどの情報を設定します。これにより、EBS CSI Driverなど、AWSリソースと連携するKubernetesコンポーネントを利用できる前提を整えます。
Cloud Controller Managerとは
CCMは、クラウド固有の制御ロジックをKubernetesのコントロールプレーンから分離して実行するコンポーネントです。AWS CCMは、KubernetesのノードとEC2インスタンスを対応付け、EC2のインスタンスタイプ、リージョン、ゾーンなどの情報をノードのラベルとして設定します。
※ 以後、CCMは "AWS CCM" を表します。
CCMを利用するためには、CCM Podが動作するEC2インスタンスがAWSから必要な情報を取得できるよう、相応の権限を持つ必要があります。一般的にAWSでこのようなことを実現するにはIAMロールを使います。EC2インスタンスへIAMロールを付与するには、インスタンスプロファイルと呼ばれるリソースがさらに必要です。
CCMはインスタンスプロファイル経由で渡されたIAMロールを使ってEC2 APIを参照します。kubeletがノードへ設定するproviderIDを手掛かりに、CCMがノードの初期化を完了させます。
前提条件
第1,2,3回の手順を完了し、EC2上でkubeadmで構築したKubernetesクラスターが稼働しているものとします。
また、以下を前提とします。
- AWS CLI、Helmfile、kubectlを利用できること
- CCM用のEC2タグとインスタンスプロファイルを設定できるAWS権限があること
検証環境
本記事では、control-planeとworkerを兼ねる単一ノード構成で検証します。CCM導入前のノードは次の状態です。
$ kubectl get node
NAME STATUS ROLES AGE VERSION
ip-10-0-0-4 Ready control-plane 35h v1.36.5
CCM導入前に付与されているラベルは次のとおりです。
$ kubectl get node --show-labels | tr ',' '\n'
NAME STATUS ROLES AGE VERSION LABELS
ip-10-0-0-4 Ready control-plane 35h v1.36.5 beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=ip-10-0-0-4
kubernetes.io/os=linux
node-role.kubernetes.io/control-plane=
自動化した構成
本記事の手順は、Terraform、Ansible、Helmfileとして自動化しています。
TerraformはEC2のNameタグからinstance ID、接続先、AZ、providerIDを取得してinventoryを生成します。Ansibleはexternal cloud providerの設定とノード再登録・CCM初期化の確認を担当します。
ただし、本記事ではCCMを動かす仕組みを確認しやすいよう、TerraformやAnsibleが実行する内容をAWS CLI、kubectl、Helmfileを使った手順として説明します。
セットアップ手順
1. EC2にCCM用のIAMロールとインスタンスプロファイルを設定する
CCMはEC2 APIを参照するため、EC2にインスタンスプロファイルを付与します。以下の順に、IAMロール、インスタンスプロファイル、EC2タグを設定します。
1-1. 変数を設定する
以下はAWS CLIで作成する例です。値は環境に合わせて置き換えてください。
AWS_REGION=ap-northeast-1
INSTANCE_ID=i-xxxxxxxxxxxxxxxxx
CLUSTER_ID=<cluster-id>
ROLE_NAME=kubernetes-aws-cloud-controller-manager
INSTANCE_PROFILE_NAME=${ROLE_NAME}
CLUSTER_ID は任意の識別子を指定できますが、同一クラスターの対象EC2には同じ値を使ってください。また、IAMロール名(ROLE_NAME)とインスタンスプロファイル名(INSTANCE_PROFILE_NAME)も任意です。ここでは管理しやすいよう、インスタンスプロファイル名をIAMロール名と同じにしています。
1-2. IAMポリシーファイルを作成する
まず、EC2がIAMロールを引き受けるための信頼ポリシーを、ccm-trust-policy.json として作成します。
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
次に、CCMがEC2 APIを参照するための許可ポリシーを、ccm-policy.json として作成します。今回必要だった最小権限は次の3つです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeRegions",
"ec2:DescribeAvailabilityZones"
],
"Resource": "*"
}
]
}
1-3. IAMロールとインスタンスプロファイルを作成する
aws iam create-role \
--role-name "${ROLE_NAME}" \
--assume-role-policy-document file://ccm-trust-policy.json
aws iam put-role-policy \
--role-name "${ROLE_NAME}" \
--policy-name cloud-controller-manager \
--policy-document file://ccm-policy.json
aws iam create-instance-profile \
--instance-profile-name "${INSTANCE_PROFILE_NAME}"
aws iam add-role-to-instance-profile \
--instance-profile-name "${INSTANCE_PROFILE_NAME}" \
--role-name "${ROLE_NAME}"
1-4. EC2へタグとインスタンスプロファイルを関連付ける
対象EC2には、CCMがクラスターのメンバーとして認識するための kubernetes.io/cluster/<cluster-id>=owned タグを付与します。
aws ec2 create-tags \
--region "${AWS_REGION}" \
--resources "${INSTANCE_ID}" \
--tags "Key=kubernetes.io/cluster/${CLUSTER_ID},Value=owned"
aws ec2 associate-iam-instance-profile \
--region "${AWS_REGION}" \
--instance-id "${INSTANCE_ID}" \
--iam-instance-profile Name="${INSTANCE_PROFILE_NAME}"
EC2にすでに別のインスタンスプロファイルが関連付けられている場合、最後のコマンドは実行しないでください。既存のロールやプロファイルへの影響を確認してから、置換方法を検討してください。
2. kubeletとkube-controller-managerをexternal cloud providerに切り替える
kubeletには、/etc/default/kubelet で次の引数を設定します。
KUBELET_EXTRA_ARGS="--cloud-provider=external --provider-id=aws:///ap-northeast-1a/i-xxxxxxxxxxxxxxxxx"
また、/etc/kubernetes/manifests/kube-controller-manager.yaml のcommandへ次を追加します。
- --cloud-provider=external
設定後はkubeletを再起動し、API ServerがReadyへ戻ることを確認します。
sudo systemctl restart kubelet
kubectl get --raw=/readyz
Kubernetes v1.33以降では、kube-apiserverへ--cloud-providerを渡しません。
3. AWS CCMを導入する
CCMはcontrol-planeノードへ配置します。今回利用した設定の要点は次のとおりです。
hostNetworking: true
image:
repository: registry.k8s.io/provider-aws/cloud-controller-manager
tag: v1.36.1
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- key: node.cloudprovider.kubernetes.io/uninitialized
value: "true"
effect: NoSchedule
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
args:
- --cloud-provider=aws
- --configure-cloud-routes=false
以下のコマンドを実行して、AWS CCMをインストールします。
helmfile -f helmfile-aws-ccm.yaml apply
CCMのPodがデプロイされていることを確認します。
$ kubectl get ds,po -A -l k8s-app=aws-cloud-controller-manager
NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-system daemonset.apps/aws-cloud-controller-manager 1 1 1 1 1 node-role.kubernetes.io/control-plane= 49s
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system pod/aws-cloud-controller-manager-pdd7l 1/1 Running 0 49s
4. 既存ノードを再登録する
すでにproviderIDなしで登録済みのノードは、kubeletへ引数を追加しただけではproviderIDが更新されません。そのため、ノードオブジェクトを削除してkubeletを再起動し、再登録させます。
ノードオブジェクトの削除はEC2インスタンスを削除する操作ではありません。ただし単一ノードクラスターでは、再登録と初期化の間にワークロードPodが一時的にPendingになります。
以下のコマンドで、ノードを削除します。
(本記事の例では、ノード ip-10-0-0-4 を削除します)
kubectl delete node ip-10-0-0-4
一時的に、以下のようにノードが1台も起動していない状態となります。
$ kubectl get node
No resources found
続いて、kubeletを再起動してラベルをつけ直します。
sudo systemctl restart kubelet
kubectl label node ip-10-0-0-4 node-role.kubernetes.io/control-plane= --overwrite
すると、ノードが再登録されます。
$ kubectl get node
NAME STATUS ROLES AGE VERSION
ip-10-0-0-4 Ready control-plane 22s v1.36.5
--node-labels=node-role.kubernetes.io/control-plane= をkubeletへ渡す方法は使いません。Kubernetes v1.36では予約済みのラベル名前空間として拒否されます。
5. ノードが初期化されたことを確認する
CCMがEC2と対応付けられると、ノードにEC2の情報が反映されます。
以下のコマンドを実行すると、EC2に関する情報を持つラベルが追加されているのを確認できます。
$ kubectl get node --show-labels | tr ',' '\n'
NAME STATUS ROLES AGE VERSION LABELS
ip-10-0-0-4 Ready control-plane 17m v1.36.5 beta.kubernetes.io/arch=amd64
beta.kubernetes.io/instance-type=t3a.small # 追加
beta.kubernetes.io/os=linux
failure-domain.beta.kubernetes.io/region=ap-northeast-1 # 追加
failure-domain.beta.kubernetes.io/zone=ap-northeast-1a # 追加
kubernetes.io/arch=amd64
kubernetes.io/hostname=ip-10-0-0-4
kubernetes.io/os=linux
node-role.kubernetes.io/control-plane=
node.kubernetes.io/instance-type=t3a.small # 追加
topology.k8s.aws/zone-id=apne1-az4 # 追加
topology.kubernetes.io/region=ap-northeast-1 # 追加
topology.kubernetes.io/zone=ap-northeast-1a # 追加
また、spec.providerID にEC2インスタンスのIDが付与されていることも確認できます。
$ kubectl get node ip-10-0-0-4 -o custom-columns=NAME:.metadata.name,PROVIDER:.spec.providerID
NAME PROVIDER
ip-10-0-0-4 aws:///ap-northeast-1a/i-xxxxxxxxxxxxxxxx
まとめ
EKSを使わないKubernetesでも、EC2のインスタンスプロファイル、kubeletのproviderID、AWS CCMを組み合わせることで、ノードへAWSのEC2に関する情報を設定できます。これで、EBS CSI DriverなどAWSと連携するKubernetesコンポーネントを導入する前提が整いました。
