【ChaosMesh,Redis】Kubernetesでカオスエンジニアリングツールに触る

# 初めに
最近、カオスエンジニアリングに触れる機会があり、勉強し始めました。
KubernetesではカオスエンジニアリングのツールとしてChaosMeshがあります。
この記事では、ChaosMeshを触り、障害を再現してみます。カオスエンジニアリング自体には触れません。

# カオスエンジニアリング？ChaosMesh？

この記事を書くにあたり、私にとって聞きなれない言葉があったので、まとめます。自分なりの解釈です。

・定常状態＝システムの動作やサービスの提供が想定通りに行われている状態
・カオス（Chaos、変数）＝障害を再現したもの
・仮説＝再現する障害に対して定常状態を保てることを理論だてること
・実験＝カオスを対象に起こすこと（カオスを注入すること）。

カオスエンジニアリングでは、初めに定常状態を定義し、仮説を立て、実験を行い、結果からシステムの改善を図ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/9c8c26b8-6465-d4e0-4c69-0bdd3ddde582.png)

ChaosMeshではカオスを注入する方法を提供します。

※参考

https://aws.amazon.com/jp/builders-flash/202110/awsgeek-fault-injection-simulator/?awsf.filter-name=*all

https://qiita.com/hikaru_motomiya/items/ee24c0ab5c01b824b347

https://chaos-mesh.org/docs/

# 実験の準備

## 構成

障害が影響を与える構成を作ります。
AWSのEC2上にk8s環境を作り、クライアントのpodからサーバーのpodへ接続します。podのイメージには最近触る機会のあったRedisを使っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1474054/9dfecf6d-7ac2-50ee-474e-5c9e5c5ffb01.png)

以前書いた記事と同じやり方で作ります。

https://qiita.com/kokohei39/items/89a975564a513a5025a4

## Kubernetesのインストール

```bash:setup-docker-and-k8s.sh
#!/bin/bash -ex
echo 'install docker'
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker

echo 'install k8s'
echo "`ip -4 a show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'` `hostname`" | sudo tee -a /etc/hosts
echo "
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=0
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
" | sudo tee -a /etc/yum.repos.d/kubernetes.repo > /dev/null
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
sudo swapoff -a
sudo systemctl start kubelet
sudo systemctl enable kubelet

echo '
{
  "exec-opts": ["native.cgroupdriver=systemd"]
}
' | sudo tee /etc/docker/daemon.json > /dev/null
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl restart kubelet
sudo kubeadm init

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

echo 'check version'
sudo docker version
kubelet --version
kubeadm version
kubectl version

echo 'setup master node'
K8S_VERSION=$(kubectl version | base64 | tr -d '\n')
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$K8S_VERSION"
MASTER_NODE=`kubectl get node | grep master | cut -d' ' -f1`
kubectl taint nodes $MASTER_NODE node-role.kubernetes.io/master:NoSchedule-
```

## ChaosMeshのインストール

Helmを使ってChaosMeshをインストールします。

```bash:setup-helm-and-chaosmesh.sh
#!/bin/bash -ex
echo 'install helm'
HELM_VERSION='v3.8.1'
cd $HOME
curl "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" -o helm.tar.gz
tar -zxvf helm.tar.gz
mv linux-amd64 helm
echo 'export PATH=$PATH:$HOME/helm' >> $HOME/.bashrc
source .bashrc
helm version

echo 'install chaos-mesh'
CHAOS_MESH_VERSION='2.1.3'
INSTALL_NAMESPACE='chaos-testing'
ENABLE_FILTER_NAMESPACE='true'
RUNTIME='docker'
SOCKET_PATH='/var/run/docker.sock'
kubectl create ns $INSTALL_NAMESPACE
helm repo add chaos-mesh https://charts.chaos-mesh.org
helm install chaos-mesh chaos-mesh/chaos-mesh -n=$INSTALL_NAMESPACE --version $CHAOS_MESH_VERSION --set controllerManager.enableFilterNamespace=$ENABLE_FILTER_NAMESPACE --set chaosDaemon.runtime=$RUNTIME --set chaosDaemon.socketPath=$SOCKET_PATH
```

ChaosMeshのインストール時にenableFilterNamespaceオプションを使っています。
これでカオスを注入できるNamespaceを制限して、カオスに関係ないリソースを保護できます。

※参考

https://chaos-mesh.org/docs/production-installation-using-helm/

## PODの作成

k8sのリソースを作成します。

|項番|種類|名前|役割|
|---|---|---|---|
|1|Namespace|myns|カオスを注入するNamespace|
|2|Pod|server|サーバーのPod(redis)|
|3|Pod|client|クライアントのPod(redis)|

```yaml:namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: myns
  annotations: 
    chaos-mesh.org/inject: enabled #カオスの注入を有効にする
```
```yaml:server.yaml
apiVersion: v1
kind: Pod
metadata:
  name: server
  namespace: myns
  labels:
    app: server
spec:
  containers:
  - name: redis
    image: docker.io/library/redis:6.2.6 #レジストリを明示して誤ったイメージのpullを防ぐ
    resources: #ノードのCPU/メモリの無制限使用を防ぐ
      limits:
        memory: "200Mi"
        cpu: "200m"
      requests:
        memory: "100Mi"
        cpu: "100m"
```
```yaml:client.yaml
apiVersion: v1
kind: Pod
metadata:
  name: client
  namespace: myns
  labels:
    app: client
#以降、serverと同じ
```

yamlファイルを作成したら、実際にリソースを作成します。

```bash
kubectl apply -f <yamlファイル>
```

ここまでで、クライアントからサーバーへ接続できるはずです。

```bash
[ec2-user@ip-10-0-0-230 prepare]$ kubectl get pod -n myns -o wide
NAME     READY   STATUS    RESTARTS   AGE   IP           NODE                                            NOMINATED NODE   READINESS GATES
client   1/1     Running   0          93s   10.32.0.10   ip-10-0-0-230.ap-northeast-1.compute.internal   <none>           <none>
server   1/1     Running   0          99s   10.32.0.9    ip-10-0-0-230.ap-northeast-1.compute.internal   <none>           <none>
[ec2-user@ip-10-0-0-230 prepare]$ kubectl exec -n myns client -it -- /bin/bash
root@client:/data# redis-cli -h 10.32.0.9 ping
PONG
```

これで、実験準備は完了です。
これからカオスを注入していきますが、サーバーで起こる障害について、なるべくサーバーとクライアントの接続が保てるように、改善案を考えます。

# 実験開始！

## １．Podの強制終了を起こす

カオスを注入するにはyamlファイルを作成します。

```yaml:chaos.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: pod-kill
  namespace: chaos-testing
spec:
  action: pod-kill
  mode: one
  selector:
    namespaces:
      - myns
    labelSelectors:
      app: server
```

これは、Namespace「myns」にあるラベル「app=server」を持ったPodをランダムで1台強制終了させます。
実際にやってみます。

```bash
[ec2-user@ip-10-0-0-230 step1]$ kubectl apply -f chaos.yaml
podchaos.chaos-mesh.org/pod-kill created
[ec2-user@ip-10-0-0-230 step1]$ kubectl get pod -n myns
NAME     READY   STATUS    RESTARTS   AGE
client   1/1     Running   0          12m
```

サーバーが消えてしまいました。再起動していません。
それでは困るので、server.yamlを修正します。

```yaml:server.yaml
apiVersion: v1
kind: Service
metadata:
  name: server
  namespace: myns
spec:
  selector:
    app: server
  ports:
  - name: redis-server
    port: 6379
    targetPort: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: server
  namespace: myns
spec:
  selector:
    matchLabels:
      app: server
  serviceName: "server"
  replicas: 1
  template:
    metadata:
      labels:
        app: server
    spec:
      containers:
      - name: redis
        image: docker.io/library/redis:6.2.6
```

ポイントは２点です。
・StatefulSetでPodが削除されても再作成されるようにした。
・Serviceを使って、サーバーのIPアドレスが変わっても接続できるようにした。

Podが再作成されると、そのIPアドレスは変わってしまいます。そのため、Podのラベル指定でDNS機能を提供するServiceを使いました。

もう一度、カオスを注入します。

```bash
[ec2-user@ip-10-0-0-230 step1]$ kubectl apply -f server.yaml
service/server created
statefulset.apps/server created
[ec2-user@ip-10-0-0-230 step1]$ kubectl get pod -n myns
NAME       READY   STATUS    RESTARTS   AGE
client     1/1     Running   0          27m
server-0   1/1     Running   0          15s #←NAMEに通し番号がついている
[ec2-user@ip-10-0-0-230 step1]$ kubectl apply -f chaos.yaml
podchaos.chaos-mesh.org/pod-kill created
[ec2-user@ip-10-0-0-230 step1]$ kubectl get pod -n myns
NAME       READY   STATUS    RESTARTS   AGE
client     1/1     Running   0          27m
server-0   1/1     Running   0          4s #←AGEが小さくなっており、再作成された。
```

今度は、サーバーは消えませんでした。人知れず再作成されていました。
また、クライアントからエンドポイント名でサーバーへ、接続できます。

```bash
[ec2-user@ip-10-0-0-230 step1]$ kubectl get endpoints -n myns
NAME     ENDPOINTS        AGE
server   10.32.0.9:6379   114s #←serverのPodのIPに対してエンドポイントができている
[ec2-user@ip-10-0-0-230 step1]$ kubectl exec -n myns client -it -- /bin/bash
root@client:/data# redis-cli -h server.myns.svc.cluster.local set testkey testvalue
OK
root@client:/data# redis-cli -h server.myns.svc.cluster.local get testkey
"testvalue"
```

※参考

https://kubernetes.io/ja/docs/concepts/services-networking/dns-pod-service/

## ２．POD間の接続を切断する

次に、クライアントとサーバー間の接続を切断します。

```yaml:chaos.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: partition
  namespace: chaos-testing
spec:
  action: partition
  mode: one
  selector:
    namespaces:
      - myns
    labelSelectors:
      app: client
  direction: to
  target:
    mode: one
    selector:
      namespaces:
        - myns
      labelSelectors:
        app: server
```

これは、Namespace「myns」にあるラベル「app=client」を持つPodと、Namespace「myns」にあるラベル「app=server」を持つPodとの接続を切断します。

```bash
root@client:/data# timeout 5 redis-cli -h server.myns.svc.cluster.local ping
root@client:/data# echo $?
124 #←接続できず、タイムアウトしている。
```

対策として、サーバーの台数を増やしてみます。変更点を中心に記載します。

```yaml:server.yaml
#略
  clusterIP: None #ServiceをHeadlessとして動かす
---
#略
  serviceName: "server"
  replicas: 2 #サーバーの複製数を増やす
  template:
#略
```

```yaml:client.yaml
#略
    image: docker.io/library/redis:6.2.6
    command: ['bash', '-c']
    args: ['cp -r -L /myconfig $HOME/src && chmod -R +x $HOME/src && $HOME/src/setup-client.sh'] #Pod起動時に実行するコマンド
    volumeMounts:
    - name: config #ConfigMapのマウント
      mountPath: '/myconfig'
#略
  volumes:
  - name: config
    configMap:
      name: mycm
```

クライアントのソースコードはConfigMapで送ります。ConfigMapはディレクトリから作り、Podにマウントできます。後で増やしたサーバーに接続するために使います。

```bash
SRC_CONFIGMAP_NAME='mycm'
kubectl delete cm -n myns $SRC_CONFIGMAP_NAME
kubectl create cm -n myns $SRC_CONFIGMAP_NAME --from-file configmap
```

もう一度、カオスを注入します。
クライアントから切断されていないサーバーへアクセスできれば、接続できそうです。
しかし、デフォルトではサーバーのServiceはラウンドロビン方式でアクセスが分散されます。
そのため、接続できないときも出てきます。

```bash
root@client:/data# timeout 5 redis-cli -h server.myns.svc.cluster.local ping
PONG
root@client:/data# timeout 5 redis-cli -h server.myns.svc.cluster.local ping #←接続できていない
root@client:/data# timeout 5 redis-cli -h server.myns.svc.cluster.local ping
PONG
```

ここで、Serviceに問い合わせると、サーバーのPodのIPアドレスすべてを取得できるので、使えるかもしれません。これにはServiceをHeadlessで動かす必要があります。

```bash
[ec2-user@ip-10-0-0-66 step2]$ kubectl get pod -n myns -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP           NODE                                           NOMINATED NODE   READINESS GATES
client     1/1     Running   0          19m   10.32.0.10   ip-10-0-0-66.ap-northeast-1.compute.internal   <none>           <none>
server-0   1/1     Running   0          19m   10.32.0.9    ip-10-0-0-66.ap-northeast-1.compute.internal   <none>           <none>
server-1   1/1     Running   0          19m   10.32.0.11   ip-10-0-0-66.ap-northeast-1.compute.internal   <none>           <none>
root@client:/data# dig server.myns.svc.cluster.local +short
10.32.0.9
10.32.0.11
```

また、digコマンドを使うには、必要なパッケージをインストールします。ConfigMapを使って起動時にインストールできるようにしています。

```bash:setup-client.sh
#!/bin/bash -e

apt-get update
apt-get install dnsutils -y

redis-server
```

## ３．メモリ負荷をかける

```yaml:chaos.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
  name: memory-stress
  namespace: chaos-testing
spec:
  mode: one
  selector:
    namespaces:
      - myns
    labelSelectors:
      app: server
  stressors:
    memory:
      workers: 1
      size: '256MB'
```

これは、ランダムなサーバー１台に256MBのメモリ負荷をかけます。サーバーのメモリ制限よりも大きな値です。
カオスを注入したら、どうなるでしょうか。OOM Killerに強制終了させられるでしょうか。やってみます。

```bash
[ec2-user@ip-10-0-0-66 step3]$ kubectl apply -f chaos.yaml
stresschaos.chaos-mesh.org/memory-stress created
[ec2-user@ip-10-0-0-66 step3]$ kubectl get pod -n myns
NAME       READY   STATUS    RESTARTS   AGE
client     1/1     Running   0          39m
server-0   1/1     Running   0          39m
server-1   1/1     Running   0          39m
```

サーバーのAGEを見てもPodが再作成された形跡はありません。
どのくらいPodでメモリを使っているか見てみます。metrics-serverを使います。
インストール方法は後述します。

```bash
[ec2-user@ip-10-0-0-66 step3]$ kubectl top pod -n myns
NAME       CPU(cores)   MEMORY(bytes)
client     2m           23Mi
server-0   193m         199Mi
server-1   1m           2Mi
```

かなり、ギリギリで耐えているようでした。あと少しメモリ使ったら強制終了するかもしれません。
強制終了されては困るので、メモリ制限に近づいたらアクセスされないようにすることを考えます。

```yaml:server.yaml
#略
        env:
          - name: MAX_MEMORY_USED_RATIO
            value: '80'
        readinessProbe:
          exec:
            command:
            - "./src/healthy-server.sh"
          initialDelaySeconds: 0
          periodSeconds: 1
          successThreshold: 1
          failureThreshold: 3
#略
```

```bash:healthy-server.sh
#!/bin/bash -e

MEMORY_USED=`redis-cli info | grep 'used_memory:' | cut -d':' -f2 | sed -e 's/\r//g'`
MEMORY_LIMIT=`redis-cli info | grep 'maxmemory:' | cut -d':' -f2 | sed -e 's/\r//g'`
MEMORY_USED_RATIO=`echo $((100 * $MEMORY_USED / $MEMORY_LIMIT))`
if [ $MEMORY_USED_RATIO -ge $MAX_MEMORY_USED_RATIO ];then
  exit 1
fi
```

上記の設定では、Redisのメモリ使用率が制限の80%を超えたら、アクセスされないようにしています。今回は簡単のためにRedisのメモリを使っています。
また、Podのメモリ制限を増やすと対策になるかもしれません。Kubernetesには水平・垂直スケーリングがあります。
今回は垂直スケーリングとしてVertical Pod Autoscaler(VPA)の例を示します。

```yaml:server.yaml
#略
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: server
  namespace: myns
spec:
  targetRef:
    apiVersion: 'apps/v1'
    kind: StatefulSet
    name: server
  updatePolicy:
    updateMode: Initial
  resourcePolicy:
    containerPolicies:
    - containerName: '*'
      minAllowed:
        memory: 100Mi
      maxAllowed:
        memory: 200Mi
```

上記の設定では、サーバーのメモリの最小値と最大値の推奨値を算出し、Podの再作成時に適用されます。
ここで、推奨値はPodの再起動では適用されないので、livenessProbeを使う場合には気を付けたいです。

metrics-serverとVPAのインストールするためのコマンドを示します。

```bash:install.sh
#!/bin/bash -ex
SCRIPT_DIR=`cd $(dirname $0); pwd`

echo 'install metrics-server'
cd $SCRIPT_DIR
mkdir metrics-server
cd metrics-server
curl -LO https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.1/components.yaml
sed -i -e "140i#        - --kubelet-insecure-tls" components.yaml
sed -i -e 's/#//g' components.yaml
kubectl apply -f components.yaml

echo 'install required packages'
cd $SCRIPT_DIR
sudo yum install -y git gcc perl-Test-Simple
curl -OL https://www.openssl.org/source/openssl-1.1.1l.tar.gz
tar xzf openssl-1.1.1l.tar.gz
cd openssl-1.1.1l/
./config
sudo make install
make test

echo 'install vpa'
cd $SCRIPT_DIR
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler/hack/
git checkout vpa-release-0.10
export LD_LIBRARY_PATH=$SCRIPT_DIR'/openssl-1.1.1l'
./vpa-up.sh
```

※参考

https://kubernetes.io/ja/docs/concepts/services-networking/service/

https://kubernetes.io/ja/docs/tasks/configure-pod-container/assign-memory-resource/

https://grugrut.hatenablog.jp/entry/2014/09/docker-container-constrain-ram

https://qiita.com/dingtianhongjie/items/a8ddc2d7f7b57291a13e

https://ccvanishing.hateblo.jp/entry/2018/10/02/203205

## ４．ファイルの書き込みミスを起こす

ファイルの書き込み時に、誤った結果を起こします。
Redisはデータ永続化のためにファイルを作成します。

```conf:redis.conf
save 10 1
dir /server-data
dbfilename <rdbファイル名>
rdbchecksum yes
```

```yaml:chaos.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: IOChaos
metadata:
  name: io-mistake
  namespace: chaos-testing
spec:
  action: mistake
  mode: one
  selector:
    namespaces:
      - myns
    labelSelectors:
      app: server
  volumePath: /server-data
  path: /server-data/*
  mistake:
    filling: zero
    maxOccurrences: 1
    maxLength: 10
  methods:
    - WRITE
  percent: 100
```

上記の設定では、サーバーは一定の間隔でメモリ内のデータを、バックアップファイル「/server-data/(rdbファイル)」に保存します。カオスを注入すると、ディレクトリ「/server-data/」内のすべてのファイルへの書き込み時、100%の確率で0埋めの書き込みをします。Podのyamlの変更点は後述します。

```bash
root@server-0:/data# redis-cli set testkey testvalue
OK
root@server-0:/data# redis-check-rdb /server-data/server-0.rdb
[offset 0] Checking RDB file /server-data/server-0.rdb
[offset 26] AUX FIELD redis-ver = '6.2.6'
[offset 39] AUX FIELD redis-bits = ''
--- RDB ERROR DETECTED ---
[offset 48] Invalid object type: 194
[additional info] While doing: read-type
[info] 1 keys read
[info] 0 expires
[info] 0 already expired
```

Redisのチェックサムを使うと、バックアップファイルが壊れていることがわかります。
ただ、バックアップファイルはデータ永続化のためのものなので、クライアントから見れば、サーバーはいつも通りに動いているように見えます。
これはPodの強制終了など複数の障害が同時に起こった際に問題になります。

Podのyamlファイルの変更点を以下に示します。

```yaml:server.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass #ボリュームを作成するためのリソース
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume #サーバー１台目のボリューム
metadata:
  name: server-volume-0
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  storageClassName: local-storage
  local:
    path: /home/ec2-user/storage
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: Exists
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: server-volume-1 #サーバー２台目のボリューム
#略
        volumeMounts: #ボリュームのマウント
        - name: config
          mountPath: '/myconfig'
        - name: data
          mountPath: '/server-data'
#略
  volumeClaimTemplates: #ボリュームをマウントするためのPVCの作成
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: local-storage
      resources:
        requests:
          storage: 1G
#略
```

詳細は省きますが、yamlの変更点のポイントは以下の通りです。
・サーバー２台用にボリュームを作成する。
・マウントの設定はStatefulSetにて行う。

※参考

https://redis-documentasion-japanese.readthedocs.io/ja/latest/topics/persistence.html

https://kubernetes.io/ja/docs/concepts/storage/dynamic-provisioning/

https://kubernetes.io/ja/docs/concepts/storage/_print/#local

## ５．複数の障害を同時に起こす

最後に、以下の障害を同時に起こしてみます。
・Podの強制終了
・Pod間の接続の切断
・ファイルの書き込みミス

```yaml:chaos.yaml
apiVersion: chaos-mesh.org/v1alpha1
kind: Workflow
metadata:
  name: try-workflow-parallel
  namespace: chaos-testing
spec:
  entry: parallel
  templates:
    - name: parallel
      templateType: Parallel
      children:
        - pod-kill
        - partition
        - io-mistake
    - name: pod-kill
      templateType: PodChaos
      podChaos:
        action: pod-kill
#略
    - name: partition
      templateType: NetworkChaos
      networkChaos:
        action: partition
#略
    - name: io-mistake
      templateType: IOChaos
      ioChaos:
        action: mistake
#略
```

上記の設定では、すべての障害を同じタイミングで１回だけ起こします。回数や時間差を設定することも可能です。
カオスを注入すると、サーバーのPodの再作成が失敗することがありました。

```bash
[ec2-user@ip-10-0-0-66 step5]$ kubectl get pod -n myns
NAME       READY   STATUS    RESTARTS      AGE
client     1/1     Running   0             37m
server-0   0/1     Error     2 (24s ago)   26s
server-1   1/1     Running   0             37m
```

ログを見てみると壊れたバックアップファイルが悪さをしていそうです。

```bash
[ec2-user@ip-10-0-0-66 step5]$ kubectl logs -n myns server-0
(略)
15:M 27 Mar 2022 07:54:46.392 # Server initialized
15:M 27 Mar 2022 07:54:46.393 * Loading RDB produced by version 6.2.6
15:M 27 Mar 2022 07:54:46.393 * RDB age 406 seconds
15:M 27 Mar 2022 07:54:46.393 * RDB memory usage when created 0.77 Mb
15:M 27 Mar 2022 07:54:46.393 # Wrong RDB checksum expected: (1e2d3e33985355dd) but got (28da15a8fdfd9bf1). Aborting now.
15:M 27 Mar 2022 07:54:46.393 # Internal error in RDB reading offset 0, function at rdb.c:2718 -> RDB CRC error
[offset 0] Checking RDB file server-0.rdb
[offset 26] AUX FIELD redis-ver = '6.2.6'
[offset 40] AUX FIELD  = '64'
[offset 52] AUX FIELD ctime = '1648367280'
[offset 67] AUX FIELD used-mem = '812632'
[offset 83] AUX FIELD aof-preamble = '0'
[offset 85] Selecting DB ID 0
--- RDB ERROR DETECTED ---
[offset 116] RDB CRC error
[additional info] While doing: check-sum
[info] 1 keys read
[info] 0 expires
[info] 0 already expired
```

今回は応急手当として、Redis起動時に壊れたバックアップファイルを削除するようにします。

```bash:setup-server.sh
#!/bin/bash -x

#略

redis-check-rdb /server-data/${HOSTNAME}.rdb
if [ `echo $?` -eq 1 ];then
  rm /server-data/${HOSTNAME}.rdb
fi

redis-server redis.conf
```

もう一度やってみます。

```bash
[ec2-user@ip-10-0-0-66 step5]$ kubectl exec -n myns server-0 -- redis-cli set testkey testvalue
OK
[ec2-user@ip-10-0-0-66 step5]$ kubectl exec -n myns server-1 -- redis-cli set testkey testvalue
OK
[ec2-user@ip-10-0-0-66 step5]$ kubectl exec -n myns server-0 -- ls /server-data
server-0.rdb
server-1.rdb
[ec2-user@ip-10-0-0-66 step5]$ kubectl apply -f chaos.yaml
workflow.chaos-mesh.org/try-workflow-parallel created
[ec2-user@ip-10-0-0-66 step5]$ kubectl get pod -n myns
NAME       READY   STATUS    RESTARTS   AGE
client     1/1     Running   0          2m13s
server-0   1/1     Running   0          14s
server-1   1/1     Running   0          2m21s
[ec2-user@ip-10-0-0-66 step5]$ kubectl exec -n myns client -it -- /bin/bash
root@client:/data# dig server.myns.svc.cluster.local +short
10.32.0.16
10.32.0.11
root@client:/data# redis-cli -h 10.32.0.11 set testkey2 testvalue2
OK
root@client:/data# redis-cli -h 10.32.0.11 get testkey2
"testvalue2"
```

無事、カオス注入後もサーバーに接続できました。
以上で実験は終了とします。
カオス注入後に新しく作ったリソースは以下の通りです。

|項番|種類|名前|役割|
|---|---|---|---|
|4|ConfigMap|mycm|サーバーとクライアント用のソースファイル送信|
|5|StorageClass|local-storage|サーバーのボリューム作成|
|6|PersistentVolume|server-volume-0|サーバー１台目のデータ保存用のボリューム|
|7|PersistentVolume|server-volume-1|サーバー２代目のデータ保存用のボリューム|
|8|Service|server|サーバーのDNS|
|9|StatefulSet|server|サーバーの複製|
|10|VerticalPodAutoscaler|server|サーバーの垂直スケーリング|

## その他苦戦したところ

・立てたEC2にファイルを送る方法

EC2は勉強用に立てる都合上、何度もEC2を立て直すことがありました。そのため、PCからEC2に複数のファイルを簡単に送る必要がありました。普通にSSHで送ればよいですが、ファイルが欠けることがあることに気づきました。

https://it-afi.com/ftp/post-1809/

そのため、EC2へのファイル転送はSFTPを使いました。

```bat:send-src-to-ec2.bat
chcp 65001

set /p EC2_ADDR="input addr of EC2 : "
set KEY_FILE=<keyファイル>

ssh -i %KEY_FILE% ec2-user@%EC2_ADDR% echo "hello"
set BATCH_FILE="put-by-sftp.bat"
sftp -i %KEY_FILE% -b %BATCH_FILE% ec2-user@%EC2_ADDR%
```

```bat:put-by-sftp.bat
put -R <ファイルのあるディレクトリ>
```

・KubernetesインストールではSELinuxは無効(permissive)にした方がよいらしい

　今回使ったEC2ではSELinuxの設定は無効(disabled)になっていました。公式ドキュメントに書いてある通りpermissiveにした方がいいのかなと思います。しかし、disabledから他のモードに変更するには、再起動で結構時間がかかるので、面倒でやめました。。。

https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#k8s-install-1

https://tech.virtualtech.jp/entry/2021/04/06/160926

# 終わりに

カオスエンジニアリングツールを触ってみて、yamlファイルで手軽に障害を再現できて便利だなと感じました。
ただ、あくまでも疑似的なので、現実の障害と違いがないかをよく考える必要があると思います。
カオスを投入する実験は、自ら厳しい環境に身を置く筋トレみたいだなと思いました。