[Kubernetes]etcdのバックアップリストア手順を確認する

#はじめに
etcdはKubernetsの全ての構成情報が保存されるKey-Value Storeです。これが障害などで壊れてしまうと、PodやServiceなどの全ての情報が壊れてしまいます。よって、通常は複数台（3,5,7台などの奇数）で冗長構成を組むことが推奨されています。また、Masterノードとは別のサーバで稼働させることも可能です。
冗長構成を取っていたとしても、何らかの影響で壊れてしまうことがないとは言えませんので、バックアップは取っておいた方がよいでしょう。
ということで、今回はetcdのバックアップとリストアの手順を以下に沿って確認したいと思います。

[etcd-backup-and-restore.md](https://github.com/mmumshad/kubernetes-the-hard-way/blob/master/practice-questions-answers/cluster-maintenance/backup-etcd/etcd-backup-and-restore.md)

今回の構成は、Masterノード1台にetcdが同居しています。

#既存環境の確認
既存環境を確認します。Masterノード1台とWorkerノード2台のクラスタ構成にPodとServiceが以下のようにデプロイされています。

```
$ kubectl get node
NAME           STATUS   ROLES    AGE    VERSION
k8s-master     Ready    master   138d   v1.18.3
k8s-worker01   Ready    <none>   138d   v1.18.3
k8s-worker02   Ready    <none>   138d   v1.18.3
$ kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
nginx-dep-5589d85476-nhc7q   1/1     Running   1          46h
nginx-dep-5589d85476-xdrhg   1/1     Running   1          46h
$ kubectl get svc
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
httpd           ClusterIP      10.97.210.85    <none>         80/TCP           8d
kubernetes      ClusterIP      10.96.0.1       <none>         443/TCP          140d
load-balancer   LoadBalancer   10.111.156.47   10.20.30.150   8080:30002/TCP   42d
nginx-svc       ClusterIP      10.110.96.251   <none>         8080/TCP         8d
```
#etcdctl ユーティリティの入手
etcdのバックアップリストアを行うには、etcdctlが必要になります。
以下の手順で入手します。

##環境変数の設定
以降の手順を簡素化するために、以下の環境変数を設定します。

```
$ ETCD_VER = v3.4.9
$ GOOGLE_URL=https://storage.googleapis.com/etcd
$ DOWNLOAD_URL=${GOOGLE_URL}
```
##アーカイブのダウンロードと展開

```
$ curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 16.5M  100 16.5M    0     0  3200k      0  0:00:05  0:00:05 --:--:-- 4616k

$ mkdir -p /tmp/etcd-download-test
$ tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
$ rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
```
##バージョンの確認

```
$ /tmp/etcd-download-test/etcd --version
etcd Version: 3.4.9
Git SHA: 54ba95891
Go Version: go1.12.17
Go OS/Arch: linux/amd64
$ ETCDCTL_API=3 /tmp/etcd-download-test/etcdctl version
etcdctl version: 3.4.9
API version: 3.4
```
##/usr/binへの移動
etcdctlを/usr/binに移動します。

```
$ sudo mv /tmp/etcd-download-test/etcdctl /usr/bin
```

#バックアップ
etcdctlを使用してバックアップします。

##etcdの設定確認
デプロイされているetcdの設定情報を確認します。バックアップする際には、以下4つのオプションを指定する必要がありますので、確認しておきます。

* --cacert
* --cert
* --endpoints
* --key

以下の設定情報の中のそれぞれ該当する値にコメントで記載しています。見つけにくいですが。。。
デフォルトだったら同じかなと思います。

```
$ kubectl -n kube-system describe pod etcd-k8s-master
Name:                 etcd-k8s-master
Namespace:            kube-system
Priority:             2000000000
Priority Class Name:  system-cluster-critical
Node:                 k8s-master/10.20.30.10
Start Time:           Wed, 15 Jul 2020 21:25:29 +0900
Labels:               component=etcd
                      tier=control-plane
Annotations:          kubernetes.io/config.hash: be47c66ed7fcacf39bb0343a2aa5e685
                      kubernetes.io/config.mirror: be47c66ed7fcacf39bb0343a2aa5e685
                      kubernetes.io/config.seen: 2020-07-13T22:12:51.453317107+09:00
                      kubernetes.io/config.source: file
Status:               Running
IP:                   10.20.30.10
IPs:
  IP:           10.20.30.10
Controlled By:  Node/k8s-master
Containers:
  etcd:
    Container ID:  docker://f75768f70af8a1a936227090f43e341cac5292cce2826f9b15df4fc9432b1c27
    Image:         k8s.gcr.io/etcd:3.4.3-0
    Image ID:      docker-pullable://k8s.gcr.io/etcd@sha256:4afb99b4690b418ffc2ceb67e1a17376457e441c1f09ab55447f0aaf992fa646
    Port:          <none>
    Host Port:     <none>
    Command:
      etcd
      --advertise-client-urls=https://10.20.30.10:2379
      --cert-file=/etc/kubernetes/pki/etcd/server.crt #--cert
      --client-cert-auth=true
      --data-dir=/var/lib/etcd
      --initial-advertise-peer-urls=https://10.20.30.10:2380
      --initial-cluster=k8s-master=https://10.20.30.10:2380
      --key-file=/etc/kubernetes/pki/etcd/server.key #--key
      --listen-client-urls=https://127.0.0.1:2379,https://10.20.30.10:2379 #--endpoints
      --listen-metrics-urls=http://127.0.0.1:2381
      --listen-peer-urls=https://10.20.30.10:2380
      --name=k8s-master
      --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
      --peer-client-cert-auth=true
      --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
      --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
      --snapshot-count=10000
      --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt #--cacert
    State:          Running
      Started:      Wed, 15 Jul 2020 21:25:57 +0900
    Last State:     Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Mon, 13 Jul 2020 22:12:55 +0900
      Finished:     Mon, 13 Jul 2020 23:28:30 +0900
    Ready:          True
    Restart Count:  1
    Liveness:       http-get http://127.0.0.1:2381/health delay=15s timeout=15s period=10s #success=1 #failure=8
    Environment:    <none>
    Mounts:
      /etc/kubernetes/pki/etcd from etcd-certs (rw)
      /var/lib/etcd from etcd-data (rw)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  etcd-certs:
    Type:          HostPath (bare host directory volume)
    Path:          /etc/kubernetes/pki/etcd
    HostPathType:  DirectoryOrCreate
  etcd-data:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/etcd
    HostPathType:  DirectoryOrCreate
QoS Class:         BestEffort
Node-Selectors:    <none>
Tolerations:       :NoExecute
Events:
  Type     Reason          Age                From                 Message
  ----     ------          ----               ----                 -------
  Normal   SandboxChanged  15m                kubelet, k8s-master  Pod sandbox changed, it will be killed and re-created.
  Normal   Pulled          15m                kubelet, k8s-master  Container image "k8s.gcr.io/etcd:3.4.3-0" already present on machine
  Normal   Created         15m                kubelet, k8s-master  Created container etcd
  Normal   Started         14m                kubelet, k8s-master  Started container etcd
  Warning  Unhealthy       14m (x2 over 14m)  kubelet, k8s-master  Liveness probe failed: Get http://127.0.0.1:2381/health: dial tcp 127.0.0.1:2381: connect: connection refused
```

##バックアップ
etcdctlを使用してバックアップします。

```
$ sudo ETCDCTL_API=3 etcdctl \
> --endpoints=https://127.0.0.1:2379 \
> --cacert=/etc/kubernetes/pki/etcd/ca.crt \
> --cert=/etc/kubernetes/pki/etcd/server.crt \
> --key=/etc/kubernetes/pki/etcd/server.key \
> snapshot save /tmp/snapshot-pre-boot.db
{"level":"info","ts":1594817675.7622454,"caller":"snapshot/v3_snapshot.go:119","msg":"created temporary db file","path":"/tmp/snapshot-pre-boot.db.part"}
{"level":"info","ts":"2020-07-15T21:54:35.773+0900","caller":"clientv3/maintenance.go:200","msg":"opened snapshot stream; downloading"}
{"level":"info","ts":1594817675.773231,"caller":"snapshot/v3_snapshot.go:127","msg":"fetching snapshot","endpoint":"https://127.0.0.1:2379"}
{"level":"info","ts":"2020-07-15T21:54:35.856+0900","caller":"clientv3/maintenance.go:208","msg":"completed snapshot read; closing"}
{"level":"info","ts":1594817675.8860555,"caller":"snapshot/v3_snapshot.go:142","msg":"fetched snapshot","endpoint":"https://127.0.0.1:2379","size":"6.0 MB","took":0.123743001}
{"level":"info","ts":1594817675.886203,"caller":"snapshot/v3_snapshot.go:152","msg":"saved","path":"/tmp/snapshot-pre-boot.db"}
Snapshot saved at /tmp/snapshot-pre-boot.db

$ ls -l /tmp/snapshot-pre-boot.db
-rw-------. 1 root root 6017056  7月 15 21:54 /tmp/snapshot-pre-boot.d
```

#疑似障害
etcdに障害が発生したと想定して、保存されているデータストアを削除します。

```
$ sudo ls /var/lib/etcd/member
snap  wal
$ sudo rm -r /var/lib/etcd/member
```
データストアを削除すると、DefaultのClusterIPを除いて全てのPod、Serviceなどがなくなっています。

```
$ kubectl get pod
No resources found in default namespace.
$ kubectl get pod -n kube-system
No resources found in kube-system namespace.
$ kubectl get all -A
NAMESPACE   NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
default     service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   100s
```
#リストア
##データストアの復元
バックアップから削除したデータストアを新しいディレクトリに復元します。

```
$ sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
> --cacert=/etc/kubernetes/pki/etcd/ca.crt \
> --name=master \
> --cert=/etc/kubernetes/pki/etcd/server.crt \
> --key=/etc/kubernetes/pki/etcd/server.key \
> --data-dir /var/lib/etcd-from-backup \
> --initial-cluster=master=https://127.0.0.1:2380 \
> --initial-cluster-token=etcd-cluster-1 \
> --initial-advertise-peer-urls=https://127.0.0.1:2380 \
> snapshot restore /tmp/snapshot-pre-boot.db
{"level":"info","ts":1594818586.6703568,"caller":"snapshot/v3_snapshot.go:296","msg":"restoring snapshot","path":"/tmp/snapshot-pre-boot.db","wal-dir":"/var/lib/etcd-from-backup/member/wal","data-dir":"/var/lib/etcd-from-backup","snap-dir":"/var/lib/etcd-from-backup/member/snap"}
{"level":"info","ts":1594818586.7758226,"caller":"mvcc/kvstore.go:380","msg":"restored last compact revision","meta-bucket-name":"meta","meta-bucket-name-key":"finishedCompactRev","restored-compact-revision":2195612}
{"level":"info","ts":1594818586.790849,"caller":"membership/cluster.go:392","msg":"added member","cluster-id":"7581d6eb2d25405b","local-member-id":"0","added-peer-id":"e92d66acd89ecf29","added-peer-peer-urls":["https://127.0.0.1:2380"]}
{"level":"info","ts":1594818586.8051481,"caller":"snapshot/v3_snapshot.go:309","msg":"restored snapshot","path":"/tmp/snapshot-pre-boot.db","wal-dir":"/var/lib/etcd-from-backup/member/wal","data-dir":"/var/lib/etcd-from-backup","snap-dir":"/var/lib/etcd-from-backup/member/snap"}
```
##etcd Podの再構成
etcd Podのマニフェスト（/etc/kubernetes/manifests/etcd.yaml）を復元した環境に合わせて編集して、Podを再構成します。
編集するのは以下の2点です。

* データストアディレクトリの編集
* 初期クラスタトークンの追加

以下のようにマニフェストを編集します。

```/etc/kubernetes/manifests/etcd.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    component: etcd
    tier: control-plane
  name: etcd
  namespace: kube-system
spec:
  containers:
  - command:
    - etcd
    - --advertise-client-urls=https://10.20.30.10:2379
    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
    - --client-cert-auth=true
    - --data-dir=/var/lib/etcd-from-backup #変更
    - --initial-advertise-peer-urls=https://10.20.30.10:2380
    - --initial-cluster=k8s-master=https://10.20.30.10:2380
    - --key-file=/etc/kubernetes/pki/etcd/server.key
    - --listen-client-urls=https://127.0.0.1:2379,https://10.20.30.10:2379
    - --listen-metrics-urls=http://127.0.0.1:2381
    - --listen-peer-urls=https://10.20.30.10:2380
    - --name=k8s-master
    - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
    - --peer-client-cert-auth=true
    - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
    - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --snapshot-count=10000
    - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --initial-cluster-token=etcd-cluster-1 #追記
    image: k8s.gcr.io/etcd:3.4.3-0
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /health
        port: 2381
        scheme: HTTP
      initialDelaySeconds: 15
      timeoutSeconds: 15
    name: etcd
    resources: {}
    volumeMounts:
    - mountPath: /var/lib/etcd-from-backup #変更
      name: etcd-data
    - mountPath: /etc/kubernetes/pki/etcd
      name: etcd-certs
  hostNetwork: true
  priorityClassName: system-cluster-critical
  volumes:
  - hostPath:
      path: /etc/kubernetes/pki/etcd
      type: DirectoryOrCreate
    name: etcd-certs
  - hostPath:
      path: /var/lib/etcd-from-backup #変更
      type: DirectoryOrCreate
    name: etcd-data
status: {}
```

マニフェストを編集したらPodが自動的に再構成されて起動します。

```
$ docker ps | grep etcd
077d822d6e04        303ce5db0e90           "etcd --advertise-cl…"   19 seconds ago      Up 19 seconds                           k8s_etcd_etcd-k8s-master_kube-system_9eedf34b89e004fe9144efbb95696ed8_0
65490828eb3d        k8s.gcr.io/pause:3.1   "/pause"                 39 seconds ago      Up 38 seconds                           k8s_POD_etcd-k8s-master_kube-system_9eedf34b89e004fe9144efbb95696ed8_0
```

##確認
etcdが復元されましたので、PodやServiceを確認します。

```
$ kubectl -n kube-system get pod
NAME                                      READY   STATUS    RESTARTS   AGE
calico-kube-controllers-77c4b7448-j5qk8   0/1     Running   2          47h
calico-node-2hc9b                         0/1     Running   132        140d
calico-node-cgdgk                         1/1     Running   129        140d
calico-node-tkcz5                         0/1     Running   122        140d
coredns-66bff467f8-csxsv                  1/1     Running   1          2d
coredns-66bff467f8-sq7vh                  1/1     Running   1          47h
etcd-k8s-master                           1/1     Running   0          2d
kube-apiserver-k8s-master                 1/1     Running   7          2d
kube-controller-manager-k8s-master        1/1     Running   3          2d
kube-proxy-6x2wd                          1/1     Running   2          2d
kube-proxy-cksqs                          1/1     Running   2          2d
kube-proxy-m76p2                          1/1     Running   2          2d
kube-scheduler-k8s-master                 1/1     Running   3          2d
metrics-server-fbc46dc5f-z4rmv            1/1     Running   1          47h
$ kubectl get pod
NAME                         READY   STATUS    RESTARTS   AGE
nginx-dep-5589d85476-nhc7q   1/1     Running   1          47h
nginx-dep-5589d85476-xdrhg   1/1     Running   1          47h
$ kubectl get svc
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
httpd           ClusterIP      10.97.210.85    <none>         80/TCP           9d
kubernetes      ClusterIP      10.96.0.1       <none>         443/TCP          140d
load-balancer   LoadBalancer   10.111.156.47   10.20.30.150   8080:30002/TCP   42d
nginx-svc       ClusterIP      10.110.96.251   <none>         8080/TCP         9d
```
各Pod、Serviceの「AGE」の値を見ると、再作成されたのではなく、元の環境が復元されたことがわかりますね。

#まとめ
クラスタやリソースの構成が変わった時に、バックアップは定期的に取っておきたいですね。
また、リストアは定期的にやるものではありませんが、いざという時に備えてリストアのテストもやっておきたいですね。