モダナイズされた環境でも、正しい設定・チューニングができていなければ意図した挙動は取ってくれません。テストは必要です。
カオスエンジニアリングは良い選択肢です。
今回は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
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
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とシークレットを取得できます。
インストール
公式サイトを参考に、クラスタに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リポジトリに登録して使用することで対応しました。★動作保証なし
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クラスタを作っていたため、各リージョンに3ノードづつ、合計9ノードが存在していました。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コンソールからも、クラスターが確認できました。
ここで、Kubernatesクラスタが認識できていなかったり、gremlinやkube-systemのネームスペースを認識できていない場合は、設定が誤っている可能性が高いです。
Gremlinの攻撃
いよいよ、暴れさせてみます。
Attackの実行
appのネームスペースを選択して、CPU負荷をかけてみます。
Gremlinを解き放つ!
GKEのコンソールからも、Gremlinが暴れたタイミングで負荷の上昇が確認できました。
シナリオの実行
先程のAttackをもとにシナリオを作ります。
ステータス(影響)を確認するために、エンドポイントを指定しておきます。Datadogなどからメトリクスを指定することも可能です。
シナリオ実行。無事、成功が確認できました。
CPU負荷をかけてもスケールアウトされて影響なくURLアクセスが成功していることが確認できます。
一方、失敗するシナリオの例。
レイテンシを上げるAttackをかけているため、URLチェックの応答が規定時間内に得られずにNGになっています。
このように、サービスの指標となるメトリクスをステータスチェックにしておくことで、どういう障害がサービス影響につながるのかを確認することが可能です。
こちらは、シナリオ自体のNG例です。
CPU負荷を上げたあと、別のAttackをかけたところで失敗しています。
エラーメッセージを読むと、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