CloudWatch Container InsightsをEKSで設定する際にIAM Role for Service Accounts (IRSA)を使用する

先日公開したブログ記事についてTwitterでコメントを頂きましたので、実際に検証してみました。
2020.03.30

みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。

先日、このようなブログ記事を公開しました。
EKSで「CloudWatch Container Insights」を利用できるように設定する | Developers.IO

すると、ブログを読んで頂いた方からTwitterで以下のようなコメントを頂きました。

cloudwatch-agentとfluentdのDaemonSetは、IRSAに対応してないのかな~?

なるほど! その発想はありませんでした。

ということで、対応させることができるかどうか、試しにやってみました。

「IRSA (IAM Role for Service Accounts)」とは

「IRSA (IAM Role for Service Accounts)」とは、Kubernetesにおけるアクセス制御のエンティティである「サービスアカウント (ServiceAccount)」に対してIAMロールを割り当てることにより、AWS側でアクセスを制御できるようにする仕組みです。

詳しくは、IRSAのリリース時に執筆したブログ記事を参照してください。

[アップデート] EKSでIAMロールを使ったPod単位のアクセス制御が可能になりました! | Developers.IO

IRSAを使わない場合、どうなるのか?

IRSAを使わない場合、Kubernetesクラスター上で動作するPodに対してAWSリソースへのアクセス権限を与えるためには、Podが動作するワーカーノード全体に対してアクセス権限を与える必要がありました。

その場合、目的とするPod (「cloudwatch-agent」と「fluentd」のDaemonSet定義から起動されるPod) に対して必要な権限 (CloudWatchAgentServerPolicy) が適用される一方、ワーカーノード上で動作する他のPodに対しても同様の権限が与えられてしまいます。

実際に確認してみましょう。

最初に紹介したブログ記事の手順通りに進めた後に、以下を試してみてください。

動作確認のためのPodを起動して、Podへログインします。

$ kubectl run -it al2-pod --image=amazonlinux:2 -- sh

AWS CLIをインストールします。

# yum install -y less unzip
# curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
# unzip awscliv2.zip
# ./aws/install

CloudWatchに対してメトリクスを送信してみます。

# aws cloudwatch put-metric-data --namespace MyNamespace --metric-name MyMetric --dimensions MyDimention=abc123 --value 99.99 --unit Percent

特にエラーも無くコマンドが通ったことと思います。

マネジメントコンソールでCLoudWatchメトリクスを見てみましょう。

デタラメなメトリクスがCloudWatchに書き込まれてしまったことが分かります。

与えられる権限は限られたものであるとは言え、目的としないPodに対して意図しないアクセス権限が付与されてしまうのは、できれば避けたいところですね。

IRSAを使用してContainer Insightsを設定する

それでは、前回のブログ記事の手順をアレンジして、IRSAを使用するように設定していきます。

ここからは前回のブログ記事と併せて読み進めてください。
EKSで「CloudWatch Container Insights」を利用できるように設定する | Developers.IO

EKSクラスターの準備

前回ブログ記事の手順の通りに実施します。

前提条件: ワーカーノードのIAMロールに必要なポリシーを追加する

この手順は実施しません。

(ワーカーノードに対してアクセス権限を設定しない代わりに、後ほどの手順でIAMロールの設定を行います)

(追加の手順) IAMのIDプロバイダーを作成する

IRSAの仕組みで必要となる「OIDC (OpenID Connector)」に準拠したIDプロバイダーを作成します。

eksctl utils associate-iam-oidc-provider --cluster eks-example --approve

作成したIDプロバイダーは、マネジメントコンソールの「IMA」→「IDプロバイダー」で確認できます。

ステップ1: メトリクス収集を行うCloudWatchエージェントを導入する

(1) 名前空間を作成する

前回ブログ記事の手順の通りに実施します。

(2) サービスアカウントおよび関連リソースを作成する

前回ブログ記事の手順の通りに実施します。

(追加の手順) IAMロールを作成してサービスアカウントと連係させる

アクセス権限を指定するIAMロールを作成して、手順(2)で作成したサービスアカウントと連係するように設定します。

これらの設定は eksctl コマンドを使って一度に行うことができます。

eksctl create iamserviceaccount \
  --cluster eks-example \
  --name cloudwatch-agent \
  --namespace amazon-cloudwatch \
  --attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
  --override-existing-serviceaccounts \
  --approve

既にサービスアカウントが存在する場合は --override-existing-serviceaccounts オプションを指定する必要があります。

コマンドを実行すると、IAMロール「eksctl-eks-example-addon-iamserviceaccount-a-Role1-XXXXXXXXXXXX」が作成されます。

また、サービスアカウント「cloudwatch-agent」にIAMロールと連係することを示すアノテーションが追加されます。

$ kubectl describe sa cloudwatch-agent -n amazon-cloudwatch
Name:                cloudwatch-agent
Namespace:           amazon-cloudwatch
Labels:              <none>
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eksctl-eks-example-addon-iamserviceaccount-a-Role1-1L8SKZX04MUYT
Image pull secrets:  <none>
Mountable secrets:   cloudwatch-agent-token-lsh9p
Tokens:              cloudwatch-agent-token-lsh9p
Events:              <none>

(3) ConfigMapを作成する

前回ブログ記事の手順の通りに実施します。

(4) DaemonSetとしてCloudWatchエージェントをデプロイする

前回ブログ記事の手順の通りに実施します。

(5) CloudWatchへメトリクスが出力されることを確認する

前回ブログ記事の手順の通りに実施します。

ステップ2: ログ収集を行うFluentdを導入する

(1) 名前空間を作成する

この手順はスキップします。

(追加の手順) IAMロールとサービスアカウントを作成する

「ステップ1」と同様に eksctl コマンドを使って作成します。

eksctl create iamserviceaccount \
  --cluster eks-example \
  --name fluentd \
  --namespace amazon-cloudwatch \
  --attach-policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy \
  --approve

「ステップ1」の時との違いは、--override-existing-serviceaccounts オプションを指定しない点です。

何故なら、この時点ではまだサービスアカウントが存在しないためです。

--override-existing-serviceaccounts オプションを指定せずにコマンドを実行することにより、IAMロールと同時にサービスアカウントが作成されて、IAMロールとの連係も設定されます。

(2) ConfigMapを作成する

前回ブログ記事の手順の通りに実施します。

(3) Fluentdをインストールする

上の手順でサービスアカウントを作成しましたので、ここではサービスアカウントを作成しないようにします。

マニフェストファイルの最初にある ServiceAccount を作成する部分をコメントアウトします。

fluentd.yaml

# apiVersion: v1
# kind: ServiceAccount
# metadata:
#   name: fluentd
#   namespace: amazon-cloudwatch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
・・・

修正したマニフェストを使って各リソースを作成します。

$ kubectl apply -f fluentd.yaml
clusterrole.rbac.authorization.k8s.io/fluentd-role created
clusterrolebinding.rbac.authorization.k8s.io/fluentd-role-binding created
configmap/fluentd-config created
daemonset.apps/fluentd-cloudwatch created

(4) CloudWatchへログが出力されることを確認する

前回ブログ記事の手順の通りに実施します。

Container Insightsのダッシュボードを表示する

前回ブログ記事の手順の通りに実施します。

IRSAを使用した場合でも、使用しない場合と同様にContainer Insightsダッシュボードが表示されればOKです。

IRSAを使用した場合のアクセス権限の違いを確認する

IRSAを使わない場合と同じ手順で確認してみます。

動作確認のためのPodを起動して、Podへログインします。

$ kubectl run -it al2-pod --image=amazonlinux:2 -- sh

AWS CLIをインストールします。

# yum install -y less unzip
# curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
# unzip awscliv2.zip
# ./aws/install

CloudWatchに対してメトリクスを送信してみます。

# aws cloudwatch put-metric-data --namespace MyNamespace --metric-name MyMetric --dimensions MyDimention=abc123 --value 99.99 --unit Percent

An error occurred (AccessDenied) when calling the PutMetricData operation: User: arn:aws:sts::123456789012:assumed-role/eksctl-eks-example-nodegroup-ng-e-NodeInstanceRole-B8HDIVU5561Z/i-0a3810f33e7db6744 is not authorized to perform: cloudwatch:PutMetricData

「AccessDenied」エラーが発生して、メトリクスを送信できませんでした。

必要なPod (ここでは「cloudwatch-agent」と「fluentd」) のみにアクセス権限が与えられ、それ以外のPodにはアクセス権限が与えられないことが確認できました。

おわりに

EKSにContainer Insightsを設定する際に、IRSA (IAM Role for Service Accounts) を使用してアクセス制御を行うことができることが確認できました!