GKEのWorkloadIdentityを利用してChaosMeshのGCPFaultsを実行したい

こんにちは。
業務でGKEに対する障害試験を実施するためにChaosMeshを利用することを検討している際に発生した課題に対処した件について記載します。

## やりたかったこと
- ChaosMeshを使った障害試験のパターンとしてGKEノード停止~復旧中のサービスの正常性確認をしたかった。

## 環境など前提
- GKEのバージョンはv1.22.12-gke.2300でスタンダード構成（Autopilotではない。）
- ChaosMeshのバージョンは2.4.1
- 障害試験の対象となるGKEとChaosMeshをインストールするk8sクラスタは同一

## 前提知識
- **WorkloadIdentity**：GKE上(k8s)のサービスアカウントに対してGCPのサービスアカウントを紐づけてGCPリソースを操作する権限を与えられることができる機能。
- **ChaosMesh**:カオスエンジニアリングするためのツールでk8sなどにインストールして様々な障害試験を実施することが可能。

## 課題
- ChaosMeshでGCP上のノードに対して障害を発生させる場合、ノードに対する権限を持ったサービスアカウントキーをk8sのシークレットとして用意する必要がある。
⇨**セキュリティ上、サービスアカウントキーを作成して外出し・管理したくなかった**
※ChaosMeshではGCPFaultという機能が提供されている。[ページはコチラ](https://chaos-mesh.org/docs/simulate-gcp-chaos/)

## 対処方法
検証の結果、以下の方法によってサービスアカウントキーを利用せずにWorkloadIdentityによって認証して試験実施することができました。
- ChaosMeshインストール時に作成されるchaos-controller-managerのサービスアカウント（chaos-controller-manager）に対してWorkloadIdentityでGCPのSAを紐づける。
- GCPFaultの試験作成時にサービスアカウントキーをbase64したシークレットを指定する必要があるが、シークレットを指定せずに作成する。

## 検証の過程
### GCPFaultにおけるGCPリソースアクセス時の認証方法について

chaos-meshのソース抜粋
https://github.com/chaos-mesh/chaos-mesh/blob/master/controllers/chaosimpl/gcpchaos/utils/utils.go

GCPFaultsではcomputeAPIをコールしてノード停止を行っていますが、APIコール時の認証処理は以下のようになっており、試験作成時のシークレットがあればキー情報を取得してAPIに対してキーによる認証を行います。
反対にシークレットがない場合はAPIに対して何も渡さないため、APIのデフォルトの方法によって認証されるため、結果的にk8sのサービスアカウントとGCPのサービスアカウントがWorkloadIdentityで紐づけられていれば認証が成功し、APIアクセスすることができるようになります。

```
// GetComputeService is used to get the GCP compute Service.
func GetComputeService(ctx context.Context, cli client.Client, gcpchaos *v1alpha1.GCPChaos) (*compute.Service, error) {
	if gcpchaos.Spec.SecretName != nil {
		secret := &v1.Secret{}
		err := cli.Get(ctx, types.NamespacedName{
			Name:      *gcpchaos.Spec.SecretName,
			Namespace: gcpchaos.Namespace,
		}, secret)
		if err != nil {
			return nil, err
		}

		decodeBytes, err := base64.StdEncoding.DecodeString(string(secret.Data["service_account"]))
		if err != nil {
			return nil, err
		}
		computeService, err := compute.NewService(ctx, option.WithCredentialsJSON(decodeBytes))
		if err != nil {
			return nil, err
		}
		return computeService, nil
	}

	computeService, err := compute.NewService(ctx)
	if err != nil {
		return nil, err
	}
	return computeService, nil
}
```

### 障害試験が実行されるPodについて

ChaosMeshのdocによると、障害試験はChaosDaemonというDaemonSetのPod上で実行されるようですが、GCPFaultsでのノードAPIへのアクセスはChaosControllerManagerから行っているようでした。

以下の図はChaosMeshのdocより抜粋したものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/727512/735218c3-0a29-a20e-1d51-615c12b60122.png)

そのため、WorkloadIdentityで紐付けるk8sのサービスアカウントは本ChaosMeshのバージョンではChaosContllerManagerのサービスアカウントとしてインストール時に作成される「chaos-controller-manager」に当たるため、これに対してWorkloadIdentityの設定を行えばchaos-controller-managerに対してGCPのサービスアカウントの権限を付与することが可能になります。

### WorkloadIdentityの設定方法

WorkloadIdentityの設定方法はGoogle Cloudのdocを見るのが簡単で分かりやすいと思います。
https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity?hl=ja

参考までに対象となるChaosMeshのサービスアカウントに紐づけるコマンドは以下の通りです。
```
$GCP_SERVICEACCOUT_ADDRESS：紐付けしたいGCPのサービスアカウント
$PROJECT_ID：GCPのPROJECTID

gcloud iam service-accounts add-iam-policy-binding $GCP_SERVICEACCOUT_ADDRESS --role roles/iam.workloadIdentityUser --member "serviceAccount:$PROJECT_ID.svc.id.goog[chaos-mesh/chaos-controller-manager]"
```

### 最後に
ChaosMeshのdocにシークレットにキー情報を渡す方法しか書かれておらず、試験作成時のパラメータにサービスアカウントを渡すインタフェースが無くてキー情報を渡す方法でしかできないものと思いましたがキーを持ち出したくなかったので調査しました。  
GCPFaultsでやっていることは単純にAPI叩いているだけなので自前でジョブを作成する、コンソールからノードを直接止める等でも可能だったとは思いますが、ChaosMeshに障害試験を統合できるという点ではやる価値があったかなと思います。同じく困っている方の参考になれば幸いです。