Kubernetesディストリビューション — 自分で立てる
k3s のデータストアは SQLite から始まる
一言でいうと
k3sのサーバー1台は、デフォルトでSQLiteをkine経由で使うためetcdのスナップショットが動作せず、--cluster-initで再起動するとデータを保ったまま内蔵etcdへ移行されます。etcdのスナップショットの復元にはサーバートークンが必要で、スナップショット以降の変更はすべて元に戻ります。
なぜ必要なのか
KubernetesのAPIサーバーは、etcdに状態を保存するように作られています。ところがetcdは、クォーラムのために複数台を前提として設計された分散ストアなので、Raspberry Pi 1台や、CIで一時的に使うクラスターには重すぎます。k3sは、このギャップをkineで埋めます。kineはetcd APIを模倣する薄い層で、その裏にSQLite・MySQL・PostgreSQLのような一般的なデータベースを置きます。実測でログを見ると、APIサーバーは--etcd-servers=unix://kine.sockで起動していて、k3sがKine available at unix://kine.sockを先に知らせています。公式ドキュメントは、SQLiteは他のデータストア設定がなく、ディスクに内蔵etcdのデータもないときに使われるデフォルトであり、サーバーが複数台あるクラスターでは使えないと書いています。
問題は、このデフォルトが静かであることです。クラスターは問題なく動いていて、バックアップを導入しようとした日になって初めて明らかになります。
$ k3s etcd-snapshot save
level=fatal msg="Error: see server log for details: etcd datastore disabled"
実測の出力です。サーバーのログには、同じ文言がHTTP 400のレスポンスとして残ります。スナップショットコマンドがk3sサーバーにリクエストを送り、サーバーが「自分はetcdを使っていない」と拒否したのです。
どう動くのか
SQLiteの場合のバックアップには、特別なコマンドがありません。ドキュメントのとおり/var/lib/rancher/k3s/server/db/をコピーし、復元するときにその内容を元に戻します。ここに、サーバートークンのファイル/var/lib/rancher/k3s/server/tokenを必ず一緒に保管します。トークンがデータストア内の機密データの暗号化に使われるため、別のトークンで復元するとバックアップは使えません。
etcdへの移行は、SQLiteで動いていたサーバーを--cluster-initでもう一度起動するだけです。実測した結果は次のとおりです。
config.yaml 에 cluster-init: true → systemctl restart k3s (API 준비 12초)
로그 Migrating content from sqlite to etcd
디스크 db/state.db → db/state.db.migrated, db/etcd/ 와 db/snapshots/ 생성
노드 node-role.kubernetes.io/etcd=true 가 붙음
데이터 재시작 전에 만든 ConfigMap 의 UID 가 그대로
このコードブロックの韓国語の部分は、config.yamlにcluster-init: trueを書いてk3sを再起動すると(APIの準備に12秒)、ログに移行のメッセージが出て、ディスクではstate.dbがstate.db.migratedに変わってetcdとsnapshotsのディレクトリが作られ、ノードにnode-role.kubernetes.io/etcd=trueが付き、再起動前に作ったConfigMapのUIDは変わらなかったことを示しています。
逆方向の移行はありません。ドキュメントは、ディスクでetcdのデータが見つかると、--cluster-init・--server・--datastore-endpointのようなデータストアの引数を無視すると書いています。一度etcdになったら、引数を外してもSQLiteには戻らないという意味です。
スナップショットには、定期と手動の2種類があります。定期のスナップショットは、デフォルトで0時と12時(0 */12 * * *)に取得されて5個が保持され、名前はetcd-snapshot-<노드>-<시각>です(プレースホルダーはノード名と時刻です)。手動のものはk3s etcd-snapshot saveで取得し、保持数の制限がないので自分で削除する必要があります。--nameは名前の前半部分だけを決めます。どちらも--etcd-snapshot-dirのデフォルト値であるデータディレクトリの下のdb/snapshotsに保存され、実測したパスは/var/lib/rancher/k3s/server/db/snapshots/lab-before-<노드>-<유닉스시각>でした(プレースホルダーはノード名とUnix時刻です)。kubectl get etcdsnapshotfileは、クラスター全体のスナップショットをオブジェクトとして表示します。
復元は、サービスを停止して同じバイナリを1回だけ別に実行します。
systemctl stop k3s
k3s server --cluster-reset --cluster-reset-restore-path=/var/lib/rancher/k3s/server/db/snapshots/<이름>
# Managed etcd cluster membership has been reset, restart without --cluster-reset flag now.
systemctl start k3s
上のコードのプレースホルダーはスナップショット名です。
復元は、現在のetcdデータを削除せずにdb/etcd-old-<시각>へ移してからスナップショットを展開し、他のメンバーをすべて外して自分だけのクラスターにします(プレースホルダーは時刻です)。連続した初期化を防ぐためにdb/reset-flagを作り、正常に起動すると削除します。--cluster-reset-restore-pathなしで--cluster-resetだけを指定すると、スナップショットの復元なしにメンバーシップだけが初期化されます。
現場での姿
実測では、スナップショットを取得した後にConfigMapを1つ作成し、別の1つの値を変更してから復元しました。新しく作ったものは消え、値はスナップショットの時点に戻りました。消えたオブジェクトのUIDは、etcd-old-<시각>の中の古いデータファイルにバイト列として残っていました。復元が古いデータを削除しないことが、こうして確認できます。etcdのメンバー名(db/etcd/name)も、復元前とは違う値が新しく付きました。
現場でよくある事故は2つです。1つは「復元したら昨日デプロイしたものがない」というものです。復元は巻き戻しなので、スナップショット以降に誰が何をしたかを先に確認しないと、2つ目の障害になります。もう1つは、新しいサーバーにスナップショットだけを移して復元しようとして失敗するケースです。ドキュメントは、別のホストで復元するときは元のサーバーのトークンを--tokenで渡す必要があると書いています。スナップショットはオブジェクトストレージにあるのに、トークンは消えたノードのディスクにしかなかった場合、そのバックアップは使えません。
実務で本当に大切なこと
- クラスターを引き継いだら、まずデータストアが何かを確認します。
db/にstate.dbがあるか、etcd/があるかだけを見ればわかります。 - サーバーを増やす予定があるなら、最初からcluster-initで始めます。ドキュメントは、内蔵etcdはクォーラムのために奇数台のサーバーで構成する必要があると説明しています。
- バックアップは、スナップショットとトークンで1セットです。トークンは権限600で、スナップショットとは別の場所にも保管します。
- 復元の前に「スナップショット以降に何が変わったか」を記録します。復元後にもう一度適用すべき一覧になります。
- 復元は、古いデータを
etcd-old-<시각>として残します。ディスクを占有するので、復元が確認できてから片付けます(プレースホルダーは時刻です)。 - ドキュメントによると、復元するときにスナップショットを作ったときと同じk3sのバージョンである必要はなく、より高いマイナーバージョンも受け入れられます。
次のラボですること
VM内のk3sで、データストアがSQLiteであることをkineのログとテーブルで確認し、スナップショットが拒否されることを記録します。cluster-initで再起動してetcdへ移行し、目印のConfigMapのUIDが残るかを確認し、名前を付けたスナップショットをトークンと一緒にバックアップしたうえで、スナップショット以降の変更を作って復元し、何が消えてどこに残るかを確認します。
参考ドキュメント: Cluster Datastore・High Availability Embedded etcd・etcd-snapshot・Backup and Restore