GremlinをGKEで暴れさせてみる（Chaos Engineering）

モダナイズされた環境でも、正しい設定・チューニングができていなければ意図した挙動は取ってくれません。テストは必要です。
カオスエンジニアリングは良い選択肢です。

今回はChaos EngineeringツールのGremlinをGKE上のリソースで動かす設定を紹介し、挙動を確認したいと思います。

# 設定手順
## （下準備）対象コンテナアプリケーションの作成
不要な方はSkipください〜

### クラスタの作成

まずは、クラスタを作成します。
アプリケーションもGremlinもこのクラスタにデプロイします。
```
% gcloud container clusters create test-cluster --region=us-east1 --num-nodes=2 --node-locations=us-east1-b
```

※ Auto PilotモードではGremlinをインストールできないようです。まぁ、Auto Pilotの目的から考えたら不要でしょうが、自分自身がよく考えないままエラーを出してしまったので共有。

```
Error: INSTALLATION FAILED: admission webhook "gkepolicy.common-webhooks.networking.gke.io" denied the request: GKE Policy Controller rejected the request because it violates one or more policies: {"[denied by autogke-default-linux-capabilities]":["linux capability 'NET_ADMIN,SYS_BOOT,SYS_TIME,SYS_ADMIN' on container 'gremlin' not allowed; Autopilot only allows the capabilities: 'AUDIT_WRITE,CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,MKNOD,NET_BIND_SERVICE,NET_RAW,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT,SYS_PTRACE'. Requested by user: '<myaccount>', groups: 'system:authenticated'."],"[denied by autogke-no-write-mode-hostpath]":["hostPath volume gremlin-state in container gremlin is accessed in write mode; disallowed in Autopilot. Requested by user: '<myaccount>', groups: 'system:authenticated'.","hostPath volume gremlin-logs in container gremlin is accessed in write mode; disallowed in Autopilot. Requested by user: '<myaccount>', groups: 'system:authenticated'.","hostPath volume shutdown-trigger in container gremlin is accessed in write mode; disallowed in Autopilot. Requested by user: '<myaccount>', groups: 'system:authenticated'.","hostPath volume cgroup-root used in container gremlin uses path /sys/fs/cgroup which is not allowed in Autopilot. Allowed path prefixes for hostPath volumes are: [/var/log/]. Requested by user: '<myaccount>', groups: 'system:authenticated'.","hostPath volume docker-sock used in container gremlin uses path /var/run/docker.sock which is not allowed in Autopilot. Allowed path prefixes for hostPath volumes are: [/var/log/]. Requested by user: '<myaccount>', groups: 'system:authenticated'."]}
```

### アプリケーションのデプロイ

適当なアプリをデプロイします。
Gremlinリソースと明示的に分離するため、ネームスペースを切っておきます。

```
% kubectl create ns app
% kubectl apply -f deploy/web/deployment.yaml
```

```deploy/web/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-nginx
  namespace: app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-nginx
  template:
    metadata:
      labels:
        app: web-nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        resources:
          # CPUメトリクスをもとに水平スケールする
          requests:
            cpu: "200m"
```


### ロードバランサーのデプロイ

アプリケーションをロードバランサーにぶら下げて公開します。
オートスケールの設定も入れておきます。
```
% kubectl expose deployment web-nginx --type=LoadBalancer --port=80 -n app
% kubectl apply -f deploy/web/hpa.yaml
```

```deploy/web/hpa.yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: web-nginx
  namespace: app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-nginx
  minReplicas: 2
  maxReplicas: 4
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50

```
### 確認
意図したリソースが作られて、サイトにアクセスできることを確認しておきます。

```
% kubectl get all -n app                                                          
NAME                            READY   STATUS    RESTARTS   AGE
pod/web-nginx-f67cbddc9-hlgf6   1/1     Running   0          19m
pod/web-nginx-f67cbddc9-v92kp   1/1     Running   0          19m

NAME                TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
service/web-nginx   LoadBalancer   10.103.129.132   34.148.117.109   80:31700/TCP   4m6s

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web-nginx   2/2     2            2           19m

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/web-nginx-f67cbddc9   2         2         2       19m

NAME                                            REFERENCE              TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
horizontalpodautoscaler.autoscaling/web-nginx   Deployment/web-nginx   0%/50%    2         4         2          46s
```

## Gremlinのインストール

### アカウント作成
https://app.gremlin.com/signup
アカウントを作成しましょう。作成時にチームを指定します。
アカウントが作成されると、Webコンソールにログインできます。
そこで、チームIDとシークレットを取得できます。

### インストール
[公式サイト](https://app.gremlin.com/onboarding/install)を参考に、クラスタにGremlin環境をデプロイします。
helmチャートを提供してくれているので、簡単！

TEAM_IDとTEAM_SECRETはGremlinのコンソールにて取得、CLUSTER_IDはアプリケーションをデプロイしたGKEクラスタのIDを確認し、環境変数に設定しておきます。
また、

```
% kubectl create namespace gremlin
% GREMLIN_TEAM_ID="changeit"
% GREMLIN_CLUSTER_ID="changeit"
% GREMLIN_TEAM_SECRET="changeit"
% helm repo add gremlin https://helm.gremlin.com
% helm install gremlin gremlin/gremlin --namespace gremlin \
    --set gremlin.hostPID=true \
    --set gremlin.container.driver=containerd-runc \
    --set gremlin.secret.managed=true \
    --set gremlin.secret.type=secret \
    --set gremlin.collect.processes=false \
    --set gremlin.secret.teamID=$GREMLIN_TEAM_ID \
    --set gremlin.secret.clusterID=$GREMLIN_CLUSTER_ID \
    --set gremlin.secret.teamSecret=$GREMLIN_TEAM_SECRET \
    --set gremlin.apparmor=unconfined
```

ここでの注意点はdriverです。
コンテナのランタイムを調べて、正しいものをチョイスしましょう！

```
% kubectl get nodes -o wide
NAME                                          STATUS   ROLES    AGE   VERSION            INTERNAL-IP   EXTERNAL-IP     OS-IMAGE                             KERNEL-VERSION   CONTAINER-RUNTIME
gke-test-cluster-default-pool-dd94f255-4k7m   Ready    <none>   16m   v1.22.10-gke.600   10.142.0.32   34.75.171.145   Container-Optimized OS from Google   5.10.109+        containerd://1.5.11
gke-test-cluster-default-pool-dd94f255-4z19   Ready    <none>   16m   v1.22.10-gke.600   10.142.0.31   34.75.49.214    Container-Optimized OS from Google   5.10.109+        containerd://1.5.11
```

＜2022/8/16現在＞
GKEについては、公式Helmチャートに問題があるようで、のちのAttack実行時に失敗してしまいます。何やら権限が足りてない模様。

- Podのログ
```
2022-08-15 05:13:01 [INFO] gremlind::exec::persist::active::controller::lib - Execution[ee65ec33-1c58-11ed-8e80-aa537820d197]: attack started
2022-08-15 05:13:11 [INFO] gremlind::exec::persist::active::controller::lib - Active[ee65ec33-1c58-11ed-8e80-aa537820d197]: Executing Gremlin rollback (pid=79452)
2022-08-15 05:13:11 [ERROR] gremlind::daemon - Active[ee65ec33-1c58-11ed-8e80-aa537820d197]: Gremlin command returned error : process: Gremlin execution failure:
	Stderr=standard_init_linux.go:211: exec user process caused "permission denied"
time="2022-08-15T05:13:04Z" level=error msg="Failed to remove paths: map[blkio:/sys/fs/cgroup/blkio/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 cpu:/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 cpuacct:/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 cpuset:/sys/fs/cgroup/cpuset/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 devices:/sys/fs/cgroup/devices/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 freezer:/sys/fs/cgroup/freezer/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 hugetlb:/sys/fs/cgroup/hugetlb/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 memory:/sys/fs/cgroup/memory/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 name=systemd:/sys/fs/cgroup/systemd/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 net_cls:/sys/fs/cgroup/net_cls,net_prio/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 net_prio:/sys/fs/cgroup/net_cls,net_prio/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 perf_event:/sys/fs/cgroup/perf_event/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98 pids:/sys/fs/cgroup/pids/kubepods/burstable/pod9a6c0a3f-1e3f-435f-994b-af62a2395dee/b3df5ce1a99da9e013a190c4e9072db2f3ad92e57c5918697ee95f3862b8ca98]"
container failure: docker: non-zero exit (1)

	Stdout=Gremlin did not exit normally: container failure: docker: non-zero exit (1)
Checking to see if Gremlin should clean up impact

	Code=1 (pid=79452)
2022-08-15 05:13:11 [ERROR] gremlind::daemon - failed to load execution logs: /var/log/gremlin/executions/ee65ec33-1c58-11ed-8e80-aa537820d197/attack.log not found
2022-08-15 05:13:11 [INFO] gremlin_common::svc::executions - Updating Service for active executions
2022-08-15 05:13:11 [WARN] gremlind::daemon - Active[ee65ec33-1c58-11ed-8e80-aa537820d197]: Service reported execution complete, halting. (pid=79452)
2022-08-15 05:13:12 [WARN] gremlin_proc::imp - Tried to kill process (79452), but it was not found
```

- チェック結果抜粋
```
% kubectl exec --stdin --tty gremlin-d6hsl -n gremlin -- /bin/ash
/ # gremlin check files
files
====================================================
Data                                 : /var/lib/gremlin: Expected permissions 750, found 755
Config                               : OK                                 /etc/default/gremlind not found
Logs                                 : /var/log/gremlin: Expected permissions 750, found 755
Execution Data                       : 0 attack records found on disk     
Execution Logs                       : OK                                 
Credential Files                     : OK                                 
Docker                               : OK                                 Docker socket not found
```

ログに記載のエラー（権限不足）と、チェック結果（権限過多）で違和感があるものの、ファイルアクセス権限が怪しい。
HelmチャートをローカルにDLして、以下の部分をコメントアウトしたものをlocalリポジトリに登録して使用することで対応しました。★動作保証なし
```gremlin/templates/daemonset.yaml
        volumeMounts:
#          - name: gremlin-state
#            mountPath: /var/lib/gremlin
#            readOnly: false
#          - name: gremlin-logs
#            mountPath: /var/log/gremlin
#            readOnly: false
```

- チェック結果抜粋
```
% kubectl exec --stdin --tty gremlin-d6hsl -n gremlin -- /bin/ash
/ # gremlin check files
files
====================================================
Data                                 : OK                                 
Config                               : OK                                 /etc/default/gremlind not found
Logs                                 : OK                                 
Execution Data                       : 0 attack records found on disk     
Execution Logs                       : OK                                 
Credential Files                     : OK                                 
Docker                               : OK                                 Docker socket not found
```

#### ＜参考＞Helmローカルリポジトリ利用手順

ローカルファイルでのservecmの情報があまりなさそうだったので、参考までに。
パスなどは適当にお好みで。

```
% cd ~/hoge/work/
% helm pull gremlin/gremlin 
% tar -xvzf gremlin-0.4.8.tgz
% vi gremlin/templates/daemonset.yaml　＜前述の修正＞
% helm package gremlin
＜gremlin-0.4.8.tgzが更新される。Chart.yaml内のバージョンをインクリメントすることも考えたが、あくまで暫定対応のため。＞
% cd ~/hoge/local-helm/
% mv ~/hoge/work/gremlin-0.4.8.tgz ~/hoge/local-helm/charts
% helm servecm --port=8879 --storage local --storage-local-rootdir ./charts
```
- 別ターミナル
```
% helm repo add local http://127.0.0.1:8879/
% helm repo update                          
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "local" chart repository
...Successfully got an update from the "gremlin" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
% helm search repo local/                   
NAME         	CHART VERSION	APP VERSION	DESCRIPTION                       
local/gremlin	0.4.8        	2.16.2     	The Gremlin Inc client application
```


### 確認
#### Podの確認
GremlinのPodを確認しておきます。

```
% kubectl get all -n gremlin
NAME                        READY   STATUS    RESTARTS   AGE
pod/chao-54b8c8d5f5-fvzzd   1/1     Running   0          163m
pod/gremlin-sqbf2           1/1     Running   0          60m

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/gremlin   1         1         1       1            1           <none>          163m

NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/chao   1/1     1            1           163m

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/chao-54b8c8d5f5   1         1         1       163m
```


##### Podの確認（エラー例）
こちらは、エラーの出力例です。
このとき、デフォルト設定のままGKEクラスタを作っていたため、各リージョンに３ノードづつ、合計９ノードが存在していました。Podのログを覗くと、「利用できる上限を超えているのでアップグレードしてね」というようなエラーメッセージが出ていました。

```
% kubectl get all -n gremlin
NAME                        READY   STATUS             RESTARTS        AGE
pod/chao-54b8c8d5f5-gffnr   1/1     Running            0               6m1s
pod/gremlin-6ffsq           0/1     Error              6 (2m54s ago)   6m1s
pod/gremlin-f9c2q           0/1     Error              6 (2m52s ago)   6m1s
pod/gremlin-fn99g           1/1     Running            0               6m1s
pod/gremlin-l7pbw           0/1     CrashLoopBackOff   6 (18s ago)     6m1s
pod/gremlin-lfv9x           0/1     CrashLoopBackOff   5 (2m41s ago)   6m1s
pod/gremlin-nvptm           0/1     CrashLoopBackOff   5 (2m45s ago)   6m1s
pod/gremlin-qqzns           1/1     Running            0               6m1s
pod/gremlin-vt5p9           0/1     Error              6 (2m52s ago)   6m1s
pod/gremlin-xb6ct           0/1     CrashLoopBackOff   5 (2m47s ago)   6m1s

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/gremlin   9         9         2       9            2           <none>          6m2s

NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/chao   1/1     1            1           6m2s

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/chao-54b8c8d5f5   1         1         1       6m2s
```

### Gremlinコンソールの確認
Gremlinコンソールからも、クラスターが確認できました。
![Screenshot at Aug 15 10-25-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/8a5800b3-8cc6-757f-f08d-9f7141e36dd4.png)


ここで、Kubernatesクラスタが認識できていなかったり、gremlinやkube-systemのネームスペースを認識できていない場合は、設定が誤っている可能性が高いです。


## Gremlinの攻撃
いよいよ、暴れさせてみます。

### Attackの実行
appのネームスペースを選択して、CPU負荷をかけてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/ac75b714-32fd-f8b5-27f1-f9abc3bef999.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/7b450adb-49af-2f78-b538-9a8473c42802.png)

Gremlinを解き放つ！

＜平常時＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/2f2adae0-6dd1-4b1d-c1cd-d88eaf25c55e.png)

＜CPU上昇＞
![Screenshot at Aug 16 11-20-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/17db8c6e-1ec1-4204-3fec-1dc15878c75b.png)

＜スケールアウト＞
![Screenshot at Aug 16 11-20-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/1d0e4988-3f44-f30a-330c-f7cbd6ac0fdc.png)

Attackの結果が確認できます。
![Screenshot at Aug 16 13-13-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/958b098f-5a2e-6df9-b21b-09b60b55f4f0.png)

GKEのコンソールからも、Gremlinが暴れたタイミングで負荷の上昇が確認できました。
![Screenshot at Aug 16 13-26-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/89de24f7-0028-77ce-1191-d8d4705b2ad3.png)

### シナリオの実行

先程のAttackをもとにシナリオを作ります。

ステータス（影響）を確認するために、エンドポイントを指定しておきます。Datadogなどからメトリクスを指定することも可能です。

シナリオ実行。無事、成功が確認できました。
CPU負荷をかけてもスケールアウトされて影響なくURLアクセスが成功していることが確認できます。


![Screenshot at Aug 16 17-06-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/2a1d2ac4-df73-7026-d7b0-9f14c9344975.png)

一方、失敗するシナリオの例。
レイテンシを上げるAttackをかけているため、URLチェックの応答が規定時間内に得られずにNGになっています。

![Screenshot at Aug 16 18-23-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/4e25968f-998a-09fc-a4d3-242c6f98837c.png)

このように、サービスの指標となるメトリクスをステータスチェックにしておくことで、どういう障害がサービス影響につながるのかを確認することが可能です。

こちらは、シナリオ自体のNG例です。
CPU負荷を上げたあと、別のAttackをかけたところで失敗しています。

![Screenshot at Aug 16 17-12-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51320/3bba9cf3-9284-bbcc-e686-0199bb31d006.png)

エラーメッセージを読むと、Pending状態のPodに攻撃していました。
Delayの値を見直すか、構成を見直して、適切なシナリオを組みましょう。



# まとめ
今回は、GKEに対してGremlinで攻撃する基本的な設定を実施してみました。
少しインストールで手間取るところは出ましたが、インストールできさえすれば、UIは直感的で非常に使いやすいです。secretの設定も仕組み化されており、事故が起こりにくいユーザーフレンドリーなツールと思います。

おすすめシナリオも提供されており、そこまで事前知識はなくともスタートできると思いますので、是非、お試しください！

### 参考
- かなり親切な公式サイト。
https://www.gremlin.com/docs/
- このBlogを書くきっかけに。すごくまとまっている良記事（ただ、この手順だけじゃ動かない）。
https://medium.com/google-cloud/gremlin-chaos-engineering-on-google-cloud-2568f9fc70c9