EKSからGoogle Cloudのリソースを触る
おことわり
EKS初心者なのもあって自分の理解を深めながら書いてるつもりので何か理解に誤りがあったら指摘いただけると嬉しいです :pray:
背景
- EKSで動くアプリの開発をしていて、データ分析基盤としてBigQueryを使いたくなった
- Auroraにアクセスして、データを抜いて、BigQueryに転送する一連ワークフローをCronjobとして定期的に動かしてデータを転送する
環境
- EKSのバージョンは 1.30
- Podは全てFargate上で動作
- EKSのエンドポイントはprivateなため、作業は同一VPC内のCloud9で実施
やること
ざっくり簡略化して書くとやるのは以下の3つ
- (AWS) IRSAの設定
- (GCloud) Workload Identityの設定
- (AWS) Secretsの設定
(AWS) IRSAの設定
基本的には公式の通り実施する
ref. https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/iam-roles-for-service-accounts.html
今回の環境はAWS Load Balancer Controllerを導入済みだったため、OIDCの設定等は済み。
紐づけたいIAM Roleを作成して、KSAのアノテーションにIAMのARNを指定するのがここでのゴール。
IAM Roleの作成
公式の手順
ref. https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/associate-service-account-role.html
今回はAuroraに対してIAM認証をしたいわけでもないので、IAM Policyで指定する必要はない。
とりあえずKSAの動作確認のために、S3の権限だけ振っておく。
eksctlで作成すると楽かもしれないと思いつつ、何が起きてるか理解したかったのでAWS CLIで作成。
POLICY_NAME=irsa-for-google-cloud-policy
cat >/tmp/irsa-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListObject"],
"Resource": "*"
}
]
}
EOF
aws iam create-policy --policy-name $POLICY_NAME --policy-document file:///tmp/irsa-policy.json
これでIAM Policyができる。
次はIAM Roleの作成。
公式の手順的にはKSAを作成してから、とあるが、kustomizeで管理したいのでスキップする。
CLUSTER_NAME=""
ROLE_NAME=irsa-for-google-cloud-role
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
OIDC_PROVIDER=$(aws eks describe-cluster --name ${CLUSTER_NAME} --region ap-northeast-1 --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")
NAMESPACE=default
SERVICE_ACCOUNT=irsa-for-google-cloud
cat >/tmp/irsa-trust-relationship.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::$ACCOUNT_ID:oidc-provider/$OIDC_PROVIDER"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"$OIDC_PROVIDER:aud": "sts.amazonaws.com",
"$OIDC_PROVIDER:sub": "system:serviceaccount:$NAMESPACE:$SERVICE_ACCOUNT"
}
}
}
]
}
EOF
aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document file:///tmp/irsa-trust-relationship.json --description "For IRSA"
aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/$POLICY_NAME
KSAの作成
kustomizeで管理したいので、以下のファイルを作成して、kustomization.yamlから読み込む。
apiVersion: v1
kind: ServiceAccount
metadata:
name: irsa-for-google-cloud
namespace: default
kubectl apply -k ./ -n default
で適用してserviceaccountの作成を確認
$ kubectl -n default get serviceaccount irsa-for-google-cloud
NAME SECRETS AGE
irsa-for-google-cloud 0 2d23h
KSAとIAM Roleの紐付け
作成したKSAに上述のIAM Roleを紐付けてあげる。
kubectl annotate serviceaccount -n $NAMESPACE $SERVICE_ACCOUNT eks.amazonaws.com/role-arn=arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME
KSAにIAM Roleが割り当てられたことを確認
$ kubectl -n default get serviceaccount irsa-for-google-cloud -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::000000000000:role/irsa-for-google-cloud-role
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"XXXX"},"name":"irsa-for-google-cloud","namespace":"default"}}
creationTimestamp: "2024-08-01T22:06:56Z"
labels:
app.kubernetes.io/instance: XXXX
name: irsa-for-google-cloud
namespace: default
resourceVersion: "25271272"
uid: fc8af7eb-aade-4e7c-a85f-a2dcad4a88a3
動作確認
一気に作って動作確認、となると問題の切り分けが難しく時間を浪費してしまいがちなので、細かく動作確認したい。
今回は動作確認用のPodを作成して、そのPodにKSAを紐付け、AWSのリソースにS3にアクセスできるか?を持って検証する。
apiVersion: v1
kind: Pod
metadata:
name: test-app
labels:
app: test-app
namespace: default
spec:
serviceAccountName: irsa-for-google-cloud
containers:
- image: amazon/aws-cli
command:
- "sleep"
- "604800"
imagePullPolicy: IfNotPresent
name: awscli
restartPolicy: Always
AWS CLIのイメージをPod上で動かして動作確認していく。
kubectl apply -f for_test_pod.yaml -n default
でPodを作成。
立ち上がったことを確認したらkubectl exec -it test-app -n default -- /bin/bash
でPodに入る。
aws s3 ls
が成功すれば問題ない。
(GCloud) Workload Identityの設定
BigQueryが存在するプロジェクトで作業。
やることは3つ
- Workload Identity Poolの作成
- Workload Identity Providerの作成
- Workload Identity ProviderとService Accountの紐付け
前提として gcloud
コマンドのターゲットが当該プロジェクトに設定されている状態。
gcloud config set project XXX
で設定。
全体の流れは https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds?hl=ja に記載がある。
Workload Identity Poolの作成
ただの箱を作るイメージなので何も考えずに作成。
公式ドキュメントで言うと https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds?hl=ja#configure ここらへん。
コンソールでやっても良いがCLI経由のほうが何をしてるのか分かりやすいので。
POOL="aws-oidc-pool"
gcloud iam workload-identity-pools create $POOL --location="global"
Workload Identity Providerの作成
GCloudとAWS間の関係性をProviderで表現する。
参考文献に載せたままではあるが、Tokenに含まれるPayloadからどの属性を見るのか、等を設定している。
ここではKSAの名前が一致することを後で検証するので、そのように記述してあると理解。
CLUSTER=""
POOL="aws-oidc-pool"
# EKSのコンソール等から取得しておく
ISSUER_URL="https://oidc.eks.ap-northeast-1.amazonaws.com/id/1234567890"
PROVIDER="aws-oidc-provider"
gcloud iam workload-identity-pools providers create-oidc $PROVIDER \
--location="global" \
--workload-identity-pool=$POOL \
--attribute-mapping="google.subject=assertion.sub,attribute.ksa_name=assertion.sub.extract('system:serviceaccount:{ksa_name}')" \
--issuer-uri=$ISSUER_URL \
--allowed-audiences=sts.amazonaws.com
はまりポイント
最初、Providerの種別をoidcじゃなくてawsとして作ったところ、全然アクセスできんかった。
種別をawsで作成すると、credential_sourceがEC2のメタデータサーバーになるが、Fargateだと存在しないので。
"credential_source": {
"environment_id": "aws1",
"region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
"regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
}
逆に言うとNodeの種類がEC2だとAWSでも良いのかもしれない。
Workload Identity ProviderとService Accountの紐付け
ここでいうService AccountはKSAではなく、GCloudのService Account。
GCloudに到達したリクエストがどのように振る舞うか、誰で振る舞うか、を表現するためにService Accountを使う。
ここではBigQueryに対して諸々EKSから操作をしたいので管理者権限を付与している。
PROJECT_ID=""
PROJECT_NUMBER=""
SERVICE_ACCOUNT="aws-oidc-sa"
gcloud iam service-accounts create $SERVICE_ACCOUNT \
--description="Access from Amazon EKS with workload identity federation"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/bigquery.admin"
POOL="aws-oidc-pool"
NAMESPACE="default"
KSA_NAME="${NAMESPACE}:irsa-for-google-cloud"
gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL}/attribute.ksa_name/${KSA_NAME}"
KSA_NAMEは名前空間から必要になる。
ここらへんの情報がどこかにまとまっていて欲しかったが見つけられなかった…。
(AWS) Secretsの設定
ここまでで、KSAがどのRoleとして振る舞うかを設定し、GCloudが届いたTokenの情報をみてリソースへのアクセスを判断できるようになっている。
最後に、PodがWorkload Identityにアクセスできるように設定していく。
ここでやることは3つ
- Workload Identityの構成ファイルを作成
- 構成ファイルをKubernetes Secretsとして登録
- 動作確認
Workload Identityの構成ファイルを作成
以下のコマンドで取得できる構成ファイルを用いてGCloudへアクセスしていくことになる。
PROJECT_ID=""
PROJECT_NUMBER=""
SERVICE_ACCOUNT="aws-oidc-sa"
POOL="aws-oidc-pool"
PROVIDER="aws-oidc-provider"
gcloud iam workload-identity-pools create-cred-config \
"projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL}/providers/${PROVIDER}" \
--subject-token-type="urn:ietf:params:oauth:token-type:jwt" \
--service-account="${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--credential-source-file="/var/run/secrets/eks.amazonaws.com/serviceaccount/token" \
--credential-source-type="text" \
--output-file="config-aws-eks-provider.json"
IRSAがstsで取得したIdTokenが格納されるパスをcredential-source-fileに設定することで、AWSからGCloudへのアクセス時のトークンの中身が決められる(のだと思う)。
ref. https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/pod-configuration.html
構成ファイルをKubernetes Secretsとして登録
上記ファイルは中身みても分かる通り認証情報を持っているわけでもないので、DockerfileにCOPYしてImageに持たせてしまっても特段問題はないはず。
が、環境間の差異をこのタイミングであまり持ちたくなかったのでSecretsに登録する方法を採用。
Volumeがマウントできればなんでも良いと思うけれど。
kubectl create secret generic gc-conf --from-file=key.json=./config-aws-eks-provider.json -n default
key.jsonというファイルに作成した構成情報のファイルの中身が転記されるようにした。
動作確認
AWS CLIと同じようにすればOK。
apiVersion: v1
kind: Pod
metadata:
name: test-app
labels:
app: test-app
namespace: default
spec:
serviceAccountName: irsa-for-google-cloud
containers:
- image: python:3.12.4-slim
command:
- "sleep"
- "604800"
imagePullPolicy: IfNotPresent
name: test-app
volumeMounts:
- name: google-cloud-key
mountPath: /var/secrets/google
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/secrets/google/key.json
volumes:
- name: google-cloud-key
secret:
secretName: gc-conf
restartPolicy: Always
こんな感じで、KSAの指定とSecretsをMountしたPodを用意してあげる。
Podに入り込んだら、 pip3 install google-cloud-bigquery
でライブラリを入れた後に、以下のスクリプトが動くことを確認。
# python3
from google.cloud import bigquery
project_id = 't'
client = bigquery.Client(project=project_id)
dataset_id=""
tables = client.list_tables(dataset_id)
print("Tables contained in '{}':".format(dataset_id))
for table in tables:
print("{}.{}.{}".format(table.project, table.dataset_id, table.table_id))
参考
Discussion