🚀

EKSからGoogle Cloudのリソースを触る

2024/08/05に公開

おことわり

EKS初心者なのもあって自分の理解を深めながら書いてるつもりので何か理解に誤りがあったら指摘いただけると嬉しいです :pray:

背景

  • EKSで動くアプリの開発をしていて、データ分析基盤としてBigQueryを使いたくなった
  • Auroraにアクセスして、データを抜いて、BigQueryに転送する一連ワークフローをCronjobとして定期的に動かしてデータを転送する

環境

  • EKSのバージョンは 1.30
  • Podは全てFargate上で動作
  • EKSのエンドポイントはprivateなため、作業は同一VPC内のCloud9で実施

やること

ざっくり簡略化して書くとやるのは以下の3つ

  1. (AWS) IRSAの設定
  2. (GCloud) Workload Identityの設定
  3. (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から読み込む。

serviceaccount.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にアクセスできるか?を持って検証する。

for_test_pod.yaml
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))

参考

https://www.softbank.jp/biz/blog/cloud-technology/articles/202206/eks-to-gcp/
https://zenn.dev/ohsawa0515/articles/gcp-workload-identity-federation

Discussion