OpenShift/Kubernetesクラスタのバックアップと復元

# OpenShift / Kubernetesクラスタのバックアップと復元

今回は、OpenShiftのドキュメントから、クラスタのバックアップと復元について紹介します。

https://docs.openshift.com/container-platform/3.7/admin_guide/backup_restore.html

ディザスタリカバリーを想定したドキュメントですが、OpenShiftやKubernetesのバージョンをアップデートするとき、新しいクラスタを立ててから古いクラスタを削除するというBlueGreen的なアプローチで行うときにもこの手順が参考になります。

## 概要

Kubernetesは(OpenShiftも)、クラスタの状態は全て`etcd`に格納されています。
クラスタのバックアップと復元というのは、`etcd`のバックアップと復元を意味します。
なので、このドキュメントの大部分はetcdの復元方法について記載されています。

しかし、OpenShiftの場合、`oc export all`でProject(namespace)の全部をエクスポートできるので、それでバックアップを取ることもできます。
Kubernetesでも、`kubectl get --export`を駆使すれば同じようにyamlでクラスタをエクスポートできます。
「クラスタのバックアップと復元」の方法として、大きく以下の二つがあるわけです。

* [低レベル方式] 普段etcdをバックアップしておく。Kubernetesクラスタ復元の際は、まずetcdを復元し、クラスタを再インストールする復元したetcdを参照する新規クラスタを立てる
* [高レベル方式] クラスタの状態をyamlにexportしてバックアップしておく。Kubernetes復元の際は、新規Kubernetesクラスタを立てて、そこにバックアップしたyamlをインポートする

Kubernetesだと、`export`するAPIオブジェクトの種別が増えたり減ったりするかもしれないですし、低レベル方式のほうが安心感がありますね。
(ただ、etcdのバックアップと復元は、KubernetesノードのIPが変わると復元したクラスタの動作がうまく行かなかったりしたので、不安が残る)

> OpenShift Container Platformでは、クラスタレベルでのバックアップ(現在の状態を隔離されたストレージに保存)し、復元(隔離されたストレージからクラスタの状態を再現する)ことができます。
> また、補助的なものとして、[プロジェクトごとのバックアップ](#プロジェクトのバックアップ)も可能です。
> 「クラスタの状態」を構成するものは以下の4つです。

> * etcdのデータ
> * APIオブジェクト
> * イメージレジストリのストレージ
> * Volumeのストレージ

> ここでは、永続ストレージのバックアップと復元については触れません。ストレージの実装に依存するからです。
> しかし、アプリケーションデータのバックアップをどのように行うかの一般的な例を紹介します。

Kubernetes上で動いているアプリケーションはPVを通して永続ストレージを使用している場合があり、
その場合はもちろん、永続ストレージも復元する必要があります。

永続ストレージは、バックエンドの製品ごとに様々なバックアップ方法があるわけですが、
このドキュメントでは「コンテナの永続状態はアタッチされているボリュームにのみ保存されているはず」という観点から、
デプロイ設定のPVのパスに対してrsyncでバックアップと復元を行なっています。

> **! 注意**
> このドキュメントは、アプリケーションとOpenShiftクラスタのバックアップ方法について、一般論的に記載したものです。
> 様々な特殊な要件について考慮されているわけではありません。
> 実際には、そのような要件も考慮して、独自の完全なバックアップ手順を確率しなければなりません。
> データの紛失を防ぐために、必要な予防措置を取ってください。

> etcdのバックアップには、ストレージボリュームの参照情報が含まれていることに注意してください。
> etcdをバックアップから復元すると、OpenShiftは、バックアップ時のpodをノード上で起動し、また同じストレージをアタッチしようとします。
> これは実際、OpenShiftからノードを削除してから新しいノードを追加したときの挙動と変わりません。
> ノードにアタッチされていた全てのストレージは、podがどのノードに再スケジュールされても再度podにアタッチされます。

> **! 注意**
> バックアップと復元は、特に保証のあるプロセスではなく、自分のデータを保全する責任はユーザーにあります。

## 前提条件

> 1. 復元処理は完全な再インストールを伴うので、最初のインストール時に使用したすべてのファイルを保存しておいてください。 これには、以下のものが含まれます。
>     * `~/.config/openshift/installer.cfg.yml` (クイックインストールの場合)
>     * Ansibleのplaybookとインベントリファイル (アドバンスドインストールの場合)
>     * `/etc/yum.repos.d/ose.repo` (ネットワークが繋がらない環境でインストールした場合)
> 1. インストールした後に何か追加処理を実施した場合、その手順を再現できるようにバックアップを取っておいてください。インストーラーに含まれていないインストール処理が実施されているかもしれません。例えば、OpenShiftの制御下にない外部サービスの変更や、監視エージェントのような追加サービスのインストールなどです。さらに、アドバンスドインストールでもサポートされていないOpenShiftの設定を変更した場合も気をつける必要があります。例えば、複数の認証方法のサポートなどです。
> 1. 必要なコマンドラインツールをパッケージからインストールしておきます。
> 
>    ```bash
>    yum install etcd
>    ```
> 
> 4: コンテナベースのインストーラーを使っている場合は、代わりにetcdのイメージをpullしておきます。
> 
>    ```bash
>    docker pull rhel7/etcd
>    ```

> etcdのデータディレクトリ(以下のセクションでの`$ETCD_DATA_DIR`)は、どのようにetcdをデプロイしたかでパスが変わってくるので注意してください。

> | デプロイの種類 | データディレクトリ |
> |:-----------|:-------------|
> | all-in-one cluster | /var/lib/origin/openshift.local.etcd |
> | 外部etcd(masterや他のホストにある場合) | /var/lib/etcd |

> **! 注意**
> 組み込みのetcdは、OpenShift Container Platform 3.7からサポートされなくなりました。
> 詳しくは[組み込みのetcdを外部etcdに移行する](https://docs.openshift.com/container-platform/3.7/install_config/upgrading/migrating_embedded_etcd.html#install-config-upgrading-etcd-data-migration)を参照してください。

OpenShift Container Platformの3.6までは、OpenShiftのmasterに組み込まれたetcdでOpenShiftのクラスタを構築することができ、別途etcdクラスタを用意しなくていいというオプションがありました。
OpenShift 3.7からはそのオプションはサポートされなくなり、OpenShiftと別にetcdクラスタを用意する必要があります。
(OpenShiftのインストーラーは、etcdクラスタもいっしょに構築できるのでインストール時にはあまり意識しないかもしれませんが)

組み込みのetcdの場合、etcdのデータディレクトリが普通のetcdと違う場所にあるので、気をつけましょうという記載です。

## クラスタのバックアップ

### Masterのバックアップ

> 1. 各masterにある証明書を全て保存してください。
> 
>     ```bash
>     cd /etc/origin/master
>     tar cf /tmp/certs-and-keys-$(hostname).tar *.key *.crt
>     ```

### etcdのバックアップ

> 1. etcdが一つ以上のホストで動作している場合は、それぞれのetcdを以下のように停止してください。
> 
>     ```bash
>     sudo systemctl stop etcd
>     ```
> 
>     厳密には、このステップは必須というわけではないですが、こうしておくとetcdのデータが同期された状態になります。
> 
> 2. etcdのバックアップを作成する
> 
>     以下コマンドで、etcdのバックアップを作成します。
> 
>     ```bash
>     etcdctl backup \
>         --data-dir $ETCD_DATA_DIR \
>         --backup-dir $ETCD_DATA_DIR.bak
>     ```
> 
>     **! メモ**
>     etcdクラスタが複数のホストで構成されている場合も、各インスタンスは定期的にデータを同期しているので、いずれか
>     のホストでバックアップを作成すれば大丈夫です。
> 
>     **! メモ:**
>     コンテナを使ってインストールしている場合、コンテナ内のetcdctlを実行するためにdocker execコマンドを使用します。
> 
> 3. dbファイルを作成したバックアップディレクトリにコピーします
> 
>     ```bash
>     cp "$ETCD_DATA_DIR"/member/snap/db "$ETCD_DATA_DIR.bak"/member/snap/db
>     ```

* `etcd`のv3からは、(v3形式のデータは) `snapshot`コマンドでオンラインでバックアップと復元ができる
* OpenShiftのv3.7からは、`etcd`のv3形式のデータが必須

ことから、`snapshot`でバックアップと復元をしても大丈夫そうです。
ただし、`flannel`など、一部アプリケーションはv2形式のデータモデルを使用することがあるので、etcd v2とv3、複数形式のデータモデルが混在している場合、`backup`コマンドであれば両方の形式のバックアップが取れるので安全かもしれません。

### レジストリの証明書をバックアップする

> 1. レジストリの全証明書を全てのmasterおよびノードホストで保存します
> 
>     ```bash
>     cd /etc/docker/certs.d/
>     tar cf /tmp/docker-registry-certs-$(hostname).tar *
>     ```

>     **! メモ**
>     [セキュアな外部レジストリ](https://docs.openshift.com/container-platform/3.7/install_config/registry/securing_and_exposing_registry.html#exposing-the-registry)を使用している場合、podを実行するには、イメージをpullしたりpushしたりする全てのホストでレジストリの証明書を信頼しなければなりません

## クラスタの復元(etcdがシングルノードの場合)

> OpenShiftのクラスタを復元するには、

> 1. OpenShiftを再インストールします。これは、前回インストールした時と[同じ方法](https://docs.openshift.com/container-platform/3.7/install_config/install/planning.html#installation-methods)で実施してください。
> 1. インストール後の独自処理がある場合は、それを実行します。
> 1. masterノード全てで、証明書と鍵を復元します。
> 
>     ```bash
>     cd /etc/origin/master
>     tar xvf /tmp/certs-and-keys-$(hostname).tar
>     ```
> 
> 1. etcdのバックアップを復元します。
> 
>     ```bash
>     mv $ETCD_DATA_DIR $ETCD_DATA_DIR.orig
>     cp -Rp $ETCD_DATA_DIR.bak $ETCD_DATA_DIR
>     chcon -R --reference $ETCD_DATA_DIR.orig $ETCD_DATA_DIR
>     chown -R etcd:etcd $ETCD_DATA_DIR
>     ```
> 
> 1. シングルノードのetcdクラスタを、`--force-new-cluster`オプションをつけて新しく作成します。`/etc/etcd/etcd.conf`を使うか、一時的に`systemd`のユニットファイルを書き換えてetcdを通常通り開始します。つまり、`/usr/lib/systemd/system/etcd.service`で以下のように`--force-new-cluster`を追加します。
> 
>     ```bash
>     sed -i '/ExecStart/s/"$/  --force-new-cluster"/' /usr/lib/systemd/system/etcd.service
>     systemctl show etcd.service --property ExecStart --no-pager
> 
>     # -> ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd --force-new-cluster"
>     ```
> 
>     それから、etcdを再起動します。
> 
>     ```bash
>     systemctl daemon-reload
>     systemctl start etcd
>     ```
> 
> 1. etcdが正しく起動したら、`/usr/lib/systemd/system/etcd.service`を再度書き換え、`--force-new-cluster`オプションを削除しておきます。
> 
>     ```bash
>     sed -i '/ExecStart/s/ --force-new-cluster//' /usr/lib/systemd/system/etcd.service
>     systemctl show etcd.service --property ExecStart --no-pager
> 
>     # -> ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd"
>     ```
> 
> 1. etcdを再起動し、etcdクラスタが正しく実行されていることとOpenShiftの設定が正しく表示されることを確認してください。
> 
>     ```bash
>     systemctl daemon-reload
>     systemctl restart etcd
>     ```

## クラスタの復元(etcdがマルチノードの場合)

> 外部のetcdホストを使用している場合、まず新しいシングルノードのetcdクラスタを作成してバックアップを復元する必要があります。
etcdをマルチノードで構成するのであれば、そのあとで新しいetcdメンバーを一つずつ追加します。

> 最初のetcdメンバーとなるシステムを選び、そこでバックアップと設定を復元します。

> 1. 次のコマンドをetcdホストで実行します
> 
>     ```bash
>     ETCD_DIR=/var/lib/etcd/
>     mv $ETCD_DIR /var/lib/etcd.orig
>     cp -Rp /var/lib/origin/etcd-backup-<timestamp>/ $ETCD_DIR
>     chcon -R --reference /var/lib/etcd.orig/ $ETCD_DIR
>     chown -R etcd:etcd $ETCD_DIR
>     ```
> 
> 1. `/etc/etcd/etcd.conf`をバックアップから、または`.rpmsave`からリストアします。
> 1. 環境に応じて、次セクションの[コンテナ化されたetcdのデプロイ](#コンテナ化されたetcdのデプロイ)または[非コンテナ環境でのetcdのデプロイ](#非コンテナ環境でのetcdのデプロイ)のどちらかを実行します。

OpenShiftのインストールでは、Atomic Hostにインストールする場合など、`etcd`をコンテナで動かす(`docker run registry.access.redhat.com/rhel7/etcd`)オプションがあります。
以下では、コンテナ化されたetcdとコンテナでないetcdの2パターンの記載が続きますが、コマンド名など細部が変わるくらいで手順としてはほぼ同じです。

### コンテナ化されたetcdのデプロイ

> 1. etcdの`--force-new-cluster`オプションを使って、新しいシングルノードクラスタを作成します。`/etc/etcd/etcd.conf`またはユニットファイルの書き換えでさらに複雑なコマンドを使うこともできます。以下のように、`/etc/systemd/system/etcd_container.service`を編集して`--force-new-cluster`オプションを追加します。

>     ```bash
>     sed -i '/ExecStart=/s/$/  --force-new-cluster/' /etc/systemd/system/etcd_container.service
> 
>     # -> ExecStart=/usr/bin/docker run --name etcd --rm -v \
>     # -> /var/lib/etcd:/var/lib/etcd:z -v /etc/etcd:/etc/etcd:ro --env-file=/etc/etcd/etcd.conf \
>     # -> --net=host --entrypoint=/usr/bin/etcd rhel7/etcd:3.1.9  --force-new-cluster
>     ```
> 
>     それから、etcdを再起動します。
> 
>     ```bash
>     systemctl daemon-reload
>     systemctl start etcd_container
>     ```
> 
> 1. etcdサービスが正しく起動したのを確認したら、再度`/etc/systemd/system/etcd_container.service`を編集して、`--force-new-cluster`オプションを削除します。

>     ```bash
>     sed  -i '/ExecStart=/s/ --force-new-cluster//' /etc/systemd/system/etcd_container.service
> 
>     # -> ExecStart=/usr/bin/docker run --name etcd --rm -v /var/lib/etcd:/var/lib/etcd:z -v \
>     # -> /etc/etcd:/etc/etcd:ro --env-file=/etc/etcd/etcd.conf --net=host \
>     # -> --entrypoint=/usr/bin/etcd rhel7/etcd:3.1.9
>     ```
> 
> 1. etcdを再起動し、正しく起動すること、OpenShiftの設定が正しく表示されることを確認します。
> 
>     ```bash
>     systemctl daemon-reload
>     systemctl restart etcd_container
>     etcdctl --cert-file=/etc/etcd/peer.crt \
>         --key-file=/etc/etcd/peer.key \
>         --ca-file=/etc/etcd/ca.crt \
>         --peers="https://172.16.4.18:2379,https://172.16.4.27:2379" \
>         ls /
>     ```

> 1. etcdのクラスタにメンバーを追加するときは、[etcdメンバーを追加する](#etcdメンバーを追加する)を実施してください。そうではなく、シングルノードのetcdでよいのであれば、[OpenShiftをオンラインに戻す](#OpenShiftをオンラインに戻す)に進んでください。

### 非コンテナ環境でのetcdのデプロイ

> 1. etcdの`--force-new-cluster`オプションを使って、新しいシングルノードクラスタを作成します。`/etc/etcd/etcd.conf`またはユニットファイルの書き換えでさらに複雑なコマンドを使うこともできます。
> 
>     そのために、`/usr/lib/systemd/system/etcd.service`を編集し、`--force-new-cluster`を追加します。
> 
>     ```systemd:/usr/lib/systemd/system/etcd.service
>     # sed -i '/ExecStart/s/"$/  --force-new-cluster"/' /usr/lib/systemd/system/etcd.service
>     # systemctl show etcd.service --property ExecStart --no-pager
> 
>     ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd --force-new-cluster"
>     ```
> 
> 1. それから、etcdを再起動します。
> 
>     ```bash
>     systemctl daemon-reload
>     systemctl start etcd
>     ```
> 
> 1. etcdサービスが正しく起動したのを確認したら、再度`/usr/lib/systemd/system/etcd.service`を編集して、`--force-new-cluster`オプションを削除します。
> 
>     ```systemd:/usr/lib/systemd/system/etcd.service
>     # sed -i '/ExecStart/s/ --force-new-cluster//' /usr/lib/systemd/system/etcd.service
>     # systemctl show etcd.service --property ExecStart --no-pager
> 
>     ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd"
>     ```
> 
> 1. etcdを再起動し、正しく起動すること、OpenShiftの設定が正しく表示されることを確認します。
> 
>     ```bash
>     systemctl daemon-reload
>     systemctl restart etcd_container
>     etcdctl --cert-file=/etc/etcd/peer.crt \
>         --key-file=/etc/etcd/peer.key \
>         --ca-file=/etc/etcd/ca.crt \
>         --peers="https://172.16.4.18:2379,https://172.16.4.27:2379" \
>         ls /
>     ```
> 
> 1. もし、クラスタにetcdメンバーを追加するなら、[etcdメンバーを追加する](#etcdメンバーを追加する)を実施してください。そうではなく、シングルノードのetcdでよいのであれば、[OpenShiftをオンラインに戻す](#OpenShiftをオンラインに戻す)に進んでください。

## etcdメンバーを追加する

メンバーの追加は、`etcdctl member add`を実行してから、対応するetcdサービスを立ち上げる、という順序で実行します。
あと、そのときは「一つmember add」-> 「一つetcd起動」を一個ずつやっていくのがベストプラクティスとされています。
クラスタが機能するために過半数のetcdノードが必要で、1つのetcdノードに対して`member add`した状態だと、実際に新メンバーがオンラインになるまで過半数の合意が取れなくなるという理由です。

https://coreos.com/etcd/docs/latest/v2/runtime-configuration.html#add-a-new-member

> etcdメンバーをクラスタに追加するには、まず上記で起動した最初のメンバーが、`peerURLs`に`localhost`で登録されているのを修正します。

> 1. 最初のメンバーのメンバーIDを`member list`で取得します。
> 
>     ```bash
>     etcdctl --cert-file=/etc/etcd/peer.crt \
>         --key-file=/etc/etcd/peer.key \
>         --ca-file=/etc/etcd/ca.crt \
>         --peers="https://172.18.1.18:2379,https://172.18.9.202:2379,https://172.18.0.75:2379" \
>         member list
>     ```
> 
> 1. `etcdctl member update`コマンドに前ステップで取得したメンバーIDを渡して、peerURLsの値を更新します。
> 
>     ```bash
>     etcdctl --cert-file=/etc/etcd/peer.crt \
>         --key-file=/etc/etcd/peer.key \
>         --ca-file=/etc/etcd/ca.crt \
>         --peers="https://172.18.1.18:2379,https://172.18.9.202:2379,https://172.18.0.75:2379" \
>         member update 511b7fb6cc0001 https://172.18.1.18:2380
>     ```
> 
>     または、`curl`を使うこともできます。
> 
>     ```bash
>     curl --cacert /etc/etcd/ca.crt \
>     --cert /etc/etcd/peer.crt \
>     --key /etc/etcd/peer.key \
>     https://172.18.1.18:2379/v2/members/511b7fb6cc0001 \
>     -XPUT -H "Content-Type: application/json" \
>     -d '{"peerURLs":["https://172.18.1.18:2380"]}'
>     ```
> 
> 1. `member list`コマンドを再実行して、peerURLsにlocalhostが含まれていないことを確認してください。
> 1. 終わったら、クラスタに新しいメンバーを一つずつ追加していきます。
> 
>     ! 注意
>     etcdのメンバーは、クラスタに追加してから起動する、という手順を一つずつ、確実に実施していく必要があります。
>     新しいメンバーがetcdのクラスタに追加されたら、peerURLsのリストはその時点で新しいメンバーを含めた正しいものになっていなければなりません。そのため、peerURLsはメンバーが追加されるたびに一つずつ大きくなっていく形になります。
>     以下の手順で記載されている通り、新しいメンバーを起動する際に`etcd.conf`に設定する値は、`etcdctl member add`コマンドを実行したときに出力されます。
> 
> 
>     1. etcdのメンバーを追加するには、システムの`etcd.conf`にある値を参照して、`etcdctl member add`を発行します。
> 
>         ```bash
>         etcdctl --cert-file=/etc/etcd/peer.crt \
>             --key-file=/etc/etcd/peer.key \
>             --ca-file=/etc/etcd/ca.crt \
>             --peers="https://172.16.4.18:2379,https://172.16.4.27:2379" \
>             member add 10.3.9.222 https://172.16.4.27:2380
> 
>         Added member named 10.3.9.222 with ID 4e1db163a21d7651 to cluster
> 
>         ETCD_NAME="10.3.9.222"
>         ETCD_INITIAL_CLUSTER="10.3.9.221=https://172.16.4.18:2380,10.3.9.222=https://172.16.4.27:2380"
>         ETCD_INITIAL_CLUSTER_STATE="existing"
>         ```
> 
>     1. メンバーのシステム自身にある`/etc/etcd/etcd.conf`ファイルを編集し、ファイル内の設定と`etcdctl member add`コマンドで出力された設定が一致するようにします。 [^1]
>     1. 新しいetcdメンバーを起動します。
> 
>         ```bash
>         rm -rf /var/lib/etcd/member
>         systemctl enable etcd
>         systemctl start etcd
>         ```
> 
>     1. サービスが正しく開始され、etcdクラスタがhealtyになっていることを確認します。
> 
>         ```bash
>         etcdctl --cert-file=/etc/etcd/peer.crt \
>             --key-file=/etc/etcd/peer.key \
>             --ca-file=/etc/etcd/ca.crt \
>             --peers="https://172.16.4.18:2379,https://172.16.4.27:2379" \
>             member list
> 
>         51251b34b80001: name=10.3.9.221 peerURLs=https://172.16.4.18:2380 clientURLs=https://172.16.4.18:2379
>         d266df286a41a8a4: name=10.3.9.222 peerURLs=https://172.16.4.27:2380 clientURLs=https://172.16.4.27:2379
> 
>         etcdctl --cert-file=/etc/etcd/peer.crt \
>             --key-file=/etc/etcd/peer.key \
>             --ca-file=/etc/etcd/ca.crt \
>             --peers="https://172.16.4.18:2379,https://172.16.4.27:2379" \
>             cluster-health
> 
>         cluster is healthy
>         member 51251b34b80001 is healthy
>         member d266df286a41a8a4 is healthy
>         ```
> 
>     1. このプロセスを、次にクラスタに追加するメンバーにも繰り返します。
> 
> 1. 全てのetcdメンバーを追加し終わったら、[OpenShiftをオンラインに戻す](#OpenShiftをオンラインに戻す)に進んでください。

[^1]: `etcdctl member add`の結果は、値がダブルクォーテーションで囲まれた形式で出力されますが、`etcd.conf`に結果を貼り付けるときはダブルクォーテーションを外さないとうまくいかなかったです。


## 新しいetcdホストの追加

> etcdメンバーがエラーになっても、まだ過半数のメンバーが実行されているのであれば、その残ったメンバーを使ってダウンタイムなしでetcdメンバーを追加できます。

### 推奨されるクラスタサイズ

> etcdのフォールトトレランスは、奇数個のetcdホストを持つことで実現されています。
> 奇数個にしてもクオラムに必要なホスト数は変わりませんが、エラーで落ちても大丈夫なホスト数が増えます。
> 例えば、クラスタが3台で構成されている場合、クオラムは2で、エラーで落ちても大丈夫な台数は1です。
> これは、2つのメンバーがhealthyであれば、クラスタの動作が保証されるということです。

> プロダクション環境では3台のetcdホストでクラスタを構築することを推奨します。

> ! 注意
> 以下の手順は、`/etc/etcd`の設定のバックアップがあることを前提にしています。

> 1. もし新しいetcdメンバーがOpenShiftのノードとしても実行される場合、[希望する数のホストをクラスターに追加する](https://docs.openshift.com/container-platform/3.7/install_config/adding_hosts_to_existing_cluster.html#install-config-adding-hosts-to-cluster)を参照してください。この手順ではホストを一つ追加しますが、複数のホストを追加する場合はそれぞれのホストで全てのステップを繰り返してください。
> 1. 生き残っているノードでetcdとiptablesをアップグレードします。
> 
>     ```bash
>     yum update etcd iptables-services
>     ```
> 
>     etcd-2.3.7-4.el7.x86_64かそれ以上のバージョンのetcdがインストールされ、また全てのホストでetcdのバージョンが同じになっていることを確認します。
> 1. etcdとiptablesを新しいホストでインストールします。
> 
>     ```bash
>     yum install etcd iptables-services
>     ```

>     etcd-2.3.7-4.el7.x86_64かそれ以上のバージョンのetcdがインストールされ、新しいホストで、既存のetcdと同じバージョンがインストールされていることを確認します。
> 1. クラスタの設定を変更する前に、必ず[etcdデータのバックアップ](#クラスタのバックアップ)を実行してください。
> 1. エラーになっているetcdメンバーを置き換える場合は、新しいetcdメンバーを追加する**前に**そのetcdメンバーを削除してください。
> 
>     ```bash
>     etcdctl -C https://<surviving host IP>:2379 \
>     --ca-file=/etc/etcd/ca.crt     \
>     --cert-file=/etc/etcd/peer.crt     \
>     --key-file=/etc/etcd/peer.key cluster-health
> 
>     etcdctl -C https://<surviving host IP>:2379 \
>     --ca-file=/etc/etcd/ca.crt     \
>     --cert-file=/etc/etcd/peer.crt     \
>     --key-file=/etc/etcd/peer.key member remove <failed member identifier>
>     ```
> 
>     そして、エラーになったetcdを停止します。
> 
>     ```bash
>     systemctl stop etcd
>     ```
> 
> 1. 新しいホストで、適切なiptablesのルールを追加します。
> 
>     ```bash
>     systemctl enable iptables.service --now
>     iptables -N OS_FIREWALL_ALLOW
>     iptables -t filter -I INPUT -j OS_FIREWALL_ALLOW
>     iptables -A OS_FIREWALL_ALLOW -p tcp -m state \
>     --state NEW -m tcp --dport 2379 -j ACCEPT
>     iptables -A OS_FIREWALL_ALLOW -p tcp -m state \
>     --state NEW -m tcp --dport 2380 -j ACCEPT
>     iptables-save > /etc/sysconfig/iptables
>     ```
> 
> 1. 新しいetcdホストに必要な証明書を作成します。既存のetcdホストで以下を実行します。
> 
>     1. `/etc/etcd/ca/`ディレクトリのバックアップを取ってください。
>     1. 証明書を生成するための環境変数と作業ディレクトリを用意し、`PREFIX`ディレクトリが作成されていない場合は作成します。
> 
>         ```bash
>         cd /etc/etcd
>         export NEW_ETCD="<NEW_HOST_NAME>"
> 
>         export CN=$NEW_ETCD
>         export SAN="IP:<NEW_HOST_IP>"
>         export PREFIX="./generated_certs/etcd-$CN/"
>         ```
> 
>     1. `$PREFIX`ディレクトリを作成します。
> 
>         ```bash
>         mkdir -p $PREFIX
>         ```
> 
>     1. server.csrとserver.crt、二つの証明書を作成します。
> 
>         ```bash
>         openssl req -new -keyout ${PREFIX}server.key \
>         -config ca/openssl.cnf \
>         -out ${PREFIX}server.csr \
>         -reqexts etcd_v3_req -batch -nodes \
>         -subj /CN=$CN
> 
>         openssl ca -name etcd_ca -config ca/openssl.cnf \
>         -out ${PREFIX}server.crt \
>         -in ${PREFIX}server.csr \
>         -extensions etcd_v3_ca_server -batch
>         ```
> 
>     1. peer.csrとpeer.crt、二つの証明書を作成します。
> 
>         ```bash
>         openssl req -new -keyout ${PREFIX}peer.key \
>         -config ca/openssl.cnf \
>         -out ${PREFIX}peer.csr \
>         -reqexts etcd_v3_req -batch -nodes \
>         -subj /CN=$CN
> 
>         openssl ca -name etcd_ca -config ca/openssl.cnf \
>         -out ${PREFIX}peer.crt \
>         -in ${PREFIX}peer.csr \
>         -extensions etcd_v3_ca_peer -batch
>         ```
> 
>     1. etcd.confとca.crtをコピーし、ディレクトリをアーカイブします。
> 
>         ```bash
>         cp etcd.conf ${PREFIX}
>         cp ca.crt ${PREFIX}
>         tar -czvf ${PREFIX}${CN}.tgz -C ${PREFIX} .
>         ```
> 
>     1. このファイルを、新しいetcdホストに転送します。
> 
>         ```bash
>         scp ${PREFIX}${CN}.tgz  $CN:/etc/etcd/
>         ```

> 1. 引き続き、既存のホスト上で作業し、新しいホストをクラスタに追加します。
> 
>     1. 新しいホストをクラスタに追加します。
> 
>         ```bash
>         export ETCD_CA_HOST="<SURVIVING_ETCD_HOSTNAME>"
>         export NEW_ETCD="<NEW_ETCD_HOSTNAME>"
>         export NEW_ETCD_IP="<NEW_HOST_IP>"
> 
>         etcdctl -C https://${ETCD_CA_HOST}:2379 \
>         --ca-file=/etc/etcd/ca.crt     \
>         --cert-file=/etc/etcd/peer.crt     \
>         --key-file=/etc/etcd/peer.key member add ${NEW_ETCD} https://${NEW_ETCD_IP}:2380
> 
>         ETCD_NAME="<NEW_ETCD_HOSTNAME>"
>         ETCD_INITIAL_CLUSTER="<NEW_ETCD_HOSTNAME>=https://<NEW_HOST_IP>:2380,<SURVIVING_ETCD_HOST>=https:/<SURVIVING_HOST_IP>:2380
>         ETCD_INITIAL_CLUSTER_STATE="existing"
>         ```
> 
>         `etcdctl member add`の出力にある三つの環境変数をコピーしておいてください。後で使用します。
> 
>     1. 新しいホストの上で、転送された設定データを解凍し、パーミッションを設定してください。
> 
>         ```bash
>         tar -xf /etc/etcd/<NEW_ETCD_HOSTNAME>.tgz -C /etc/etcd/ --overwrite
>         chown -R etcd:etcd /etc/etcd/*
>         ```
> 
>     1. 新しいホストで、全てのetcdデータを削除します。
> 
>         ```bash
>         rm -rf /var/lib/etcd/member
>         chown -R etcd:etcd /var/lib/etcd
>         ```
> 
> 1. 新しいホストのetcd.confは、
> 
>     1. 前の手順で作った値で次のものを置き換えます
> 
>         * ETCD_NAME
>         * ETCD_INITIAL_CLUSTER
>         * ETCD_INITIAL_CLUSTER_STATE
> 
>         以下のものは、IPアドレスを`NEW_ETCD`の値で置き換えます。
> 
>         * ETCD_LISTEN_PEER_URLS
>         * ETCD_LISTEN_CLIENT_URLS
>         * ETCD_INITIAL_ADVERTISE_PEER_URLS
>         * ETCD_ADVERTISE_CLIENT_URLS
> 
>         エラーになったetcdメンバーを置き換える場合、そのメンバーをetcdの設定から削除してください。
> 
> 1. etcdを新しいホストで開始します。
> 
>     ```bash
>     systemctl enable etcd --now
>     ```
> 
> 1. 新しいメンバーがちゃんと追加されていることを確認します。
> 
>     ```bash
>     etcdctl -C https://${ETCD_CA_HOST}:2379 --ca-file=/etc/etcd/ca.crt \
>     --cert-file=/etc/etcd/peer.crt     \
>     --key-file=/etc/etcd/peer.key cluster-health
>     ```
> 
> 1. Masterの設定を全てのmasterホストで更新し、新しいetcdホストをポイントするように修正します。
> 
>     1. クラスタ内の全てのmasterで、`/etc/origin/master/master-config.yml`を編集する
>     1. `etcdClientInfo`セクションを見つける
>     1. 新しいetcdホストを、urlsリストに加える
>     1. 失敗したetcdホストを入れ替える場合は、リストからそれを削除する
> 
>         すべてのmasterで以下を実行してください。
> 
>         ```bash
>         systemctl restart atomic-openshift-master-api atomic-openshift-master-controllers
>         ```
> 
> これで、etcdメンバーを追加する手順は完了です。

## OpenShiftをオンラインに戻す

> 全てのOpenShiftのmasterで、masterとノードの設定をバックアップからリストアし、関連する全てのサービスをenable、retartします。
> 
> ```bash
> cp /etc/sysconfig/atomic-openshift-master-api.rpmsave /etc/sysconfig/atomic-openshift-master-api
> cp /etc/sysconfig/atomic-openshift-master-controllers.rpmsave /etc/sysconfig/atomic-openshift-master-controllers
> cp /etc/origin/master/master-config.yaml.<timestamp> /etc/origin/master/master-config.yaml
> cp /etc/origin/node/node-config.yaml.<timestamp> /etc/origin/node/node-config.yaml
> systemctl enable atomic-openshift-master-api
> systemctl enable atomic-openshift-master-controllers
> systemctl enable atomic-openshift-node
> systemctl start atomic-openshift-master-api
> systemctl start atomic-openshift-master-controllers
> systemctl start atomic-openshift-node
> ```
> 
> ノードのほうは、`node-config.yaml`をバックアップからリストアし、`atomic-openshift-node`サービスをenable、restartしてください。
> 
> ```bash
> cp /etc/origin/node/node-config.yaml.<timestamp> /etc/origin/node/node-config.yaml
> systemctl enable atomic-openshift-node
> systemctl start atomic-openshift-node
> ```
> 
> これで、OpenShiftクラスタはオンラインに戻るはずです。

## プロジェクトのバックアップ

> 将来的には、OpenShiftでプロジェクトのバックアップと復元を行う機能をサポートする予定です。

> 今のところは、プロジェクトレベルのAPIオブジェクトをバックアップするには、保存したいオブジェクトに対して`oc export`をそれぞれ実行してください。
例えば、`frontend`のデプロイ設定をYAML形式で保存するには、以下のようにします。

> ```bash
> oc export dc frontend -o yaml > dc-frontend.yaml
> ```

> プロジェクトの全て(namespacesやprojectsのようなクラスタレベルのオブジェクト以外)をバックアップするには、以下のようにします。

> ```bash
> oc export all -o yaml > project.yaml
> ```

### Role Bindings

> ときどき、プロジェクトで[ロールバインディング](https://docs.openshift.com/container-platform/3.7/admin_guide/manage_rbac.html#managing-role-bindings)のポリシーをカスタマイズしている場合があります。例えば、プロジェクトの管理者が他のユーザーに特定のロールを与え、プロジェクトにアクセスできるようにするときなどです。

> このようなロールバインディングは、以下のようにエクスポートできます。

> ```bash
> oc get rolebindings -o yaml --export=true > rolebindings.yaml
> ```

### Service Accounts

> カスタムのService Accountがある場合は、それをエクスポートする必要があります。

> ```bash
> oc get serviceaccount -o yaml --export=true > serviceaccount.yaml
> ```

### Secrets

> 独自のSecretsを使用している場合、例えば、ソースコード管理システムの秘匿情報(SSHの公開鍵/ユーザー名とパスワード)などをSecretsで提供している場合、それらをエクスポートしなければなりません。

> ```bash
> oc get secret -o yaml --export=true > secret.yaml
> ```

### Persistent Volume Claims

> プロジェクト内のアプリケーションが、`Persisitent Volume Claim(PVC)`を使って`Persistent Volume`使用しているなら、そのPVCもバックアップする必要があります。

> ```bash
> oc get pvc -o yaml --export=true > pvc.yaml
> ```

## プロジェクトの復元

> プロジェクトを復元するには、プロジェクトを再作成し、バックアップ時にエクスポートした全てのオブジェクトを再作成します。

> ```bash
> oc new-project myproject
> oc create -f project.yaml
> oc create -f secret.yaml
> oc create -f serviceaccount.yaml
> oc create -f pvc.yaml
> oc create -f rolebindings.yaml
> ```

> **! メモ**
> いくつかのリソースは作成に失敗する可能性があります(podやデフォルトのService Account)

## アプリケーションデータのバックアップ

クラスタの状態が戻っても、アプリケーションの永続データをPVで保存している場合、そのPVも復旧しなければなりません。

PVのバックエンドとなるネットワークストレージ側でバックアップをとることも必要ですが、ここでは、コンテナに対して`oc rsync`でrsyncを使う方法が紹介されています。
(Kubernetesの場合、[kubectl cp](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#cp)が使えそうです)

rsyncの対象となるパスを、デプロイ設定のmountPathから取得しているのが面白いところです。

> 多くの場合、アプリケーションデータは`oc rsync`コマンドでバックアップできます。コンテナイメージに`rsync`がインストールされている想定ですが、Red Hatのrhel7のベースイメージには`rsync`が含まれています。
> したがって、rhel7をベースにした全てのイメージにも`rsync`は含まれています。[CLI操作のトラブルシューティングとデバッグ - rsync](https://docs.openshift.com/container-platform/3.7/cli_reference/basic_cli_operations.html#cli-operations-rsync)を参照してください。

> ! 注意
> これはアプリケーションデータの一般的なバックアップ方法であり、アプリケーション固有のバックアップ手順（データベースの特殊なエクスポート/インポート手順など）は考慮していません。

> また、PVの種類(例えば、CinderやNFS、Glusterなど)によっては、他のバックアップ手順があるかもしれません。

> バックアップ対象のファイルパスも、アプリケーションごとに異なります。DeploymentConfigの`mountPath`を見れば、どのパスのデータをバックアップすべきか判断できます。

### Jenkinsのアプリケーションデータをバックアップする例

> 1. DeploymentConfigから、アプリケーションデータの`mountPath`を取得します。
> 
>     ```bash
>     oc get dc/jenkins -o jsonpath='{ .spec.template.spec.containers[?(@.name=="jenkins")].volumeMounts[?(@.name=="jenkins-data")].mountPath }'
>     # -> /var/lib/jenkins
>     ```
> 
> 1. 現在実行されているpodの名前を取得します。
> 
>     ```bash
>     oc get pod --selector=deploymentconfig=jenkins -o jsonpath='{ .metadata.name }'
>     # -> jenkins-1-37nux
>     ```
> 
> 1. `oc rsync`コマンドでアプリケーションデータをバックアップします。
> 
>     ```bash
>     oc rsync jenkins-1-37nux:/var/lib/jenkins /tmp/
>     ```
> 
> **! メモ**
> このアプリケーションデータのバックアップ方法は、現在実行されているアプリケーションのpodに対してしか実行できません。

記載されている通り、アプリケーションが稼働しているときにしか実行できない方法です。
アプリケーションが死んでからデータを救出するような用途には使えません。

## アプリケーションデータの復元

> アプリケーションデータのリストア手順は、`oc rsync`を使ったアプリケーションのバックアップ手順と似たような手順になります。
制限事項も同じです。このアプリケーションデータのリストア手順も、PVがアタッチされていることが前提になっています。

### Jenkinsのアプリケーションデータをリストアする例

> 1. バックアップを検証します。
> 
>     ```bash
>     ls -la /tmp/jenkins-backup/
>     total 8
>     drwxrwxr-x.  3 user     user   20 Sep  6 11:14 .
>     drwxrwxrwt. 17 root     root 4096 Sep  6 11:16 ..
>     drwxrwsrwx. 12 user     user 4096 Sep  6 11:14 jenkins
>     ```
> 
> 1. `oc rsync`を使って、データを実行中のpodにコピーします。
> 
>     ```bash
>     oc rsync /tmp/jenkins-backup/jenkins jenkins-1-37nux:/var/lib
>     ```
> 
>     **! メモ**
>     アプリケーションによっては、ここでアプリケーションの再起動が必要になるかもしれません
> 
> 1. 新しいデータでアプリケーションを再起動します(オプショナル)。
> 
>     ```bash
>     oc delete pod jenkins-1-37nux
>     ```
> 
>     または、そのデプロイをレプリカ数0になるまでスケールダウンし、再度スケールアップしてもかまいません。
> 
>     ```bash
>     oc scale --replicas=0 dc/jenkins
>     oc scale --replicas=1 dc/jenkins
>     ```