Kubernetesディストリビューション — 自分で立てる
profile: cis の一行で RKE2 が起動を拒否した
目標
VM内のRKE2 v1.36.4+rke2r1サーバー1台でデフォルトの構成を読み取り、profile: cisに変更するときにホストが備えるべきことを実際の失敗メッセージで確認し、Pod Security Admissionがprivilegedからrestrictedに変わって同じ特権Podが拒否されることと、etcdのスナップショットまで確認します。
なぜ重要なのか
RKE2はk3sと同じ系統の単一バイナリのディストリビューションですが、データストアは最初から内蔵etcdで、セキュリティのハードニングをプロファイル1つで有効にするように設計されています。 そのため、移行するときに詰まるのは機能ではなく、ポリシーとホストの準備です。cisプロファイルは、etcdを専用ユーザーで動かし、kubeletがカーネル値を変更できないようにし、すべてのネームスペースにrestrictedの基準を強制します。プロファイルはこれらの要求を代わりに満たしてくれるわけではなく、確認するだけです。準備ができていないホストでは、起動そのものを拒否します。また、デフォルトのプロファイルですでに動いていたクラスターにあとから有効にすると、サービスだけを止めて残しておいたrootのetcdが新しい問題を起こします。 どのデフォルトが変わるかを知って移行すれば、「昨日まで起動していたPodが今日は拒否される」という事故を未然に防げます。
ステップ
- 次のフィールドを書いてください(書き込み先:
/root/rke2/layout.json)。version(rke2 --versionの1行目のバージョン。例: v0.0.0+rke2r0の形式)、rke2_binary(rke2の実行ファイルの絶対パス)、kubectl(RKE2が一緒に展開したkubectlの絶対パス)、kubeconfig(管理者のkubeconfigのパス)、kubectl_on_default_path(デフォルトのPATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binだけでkubectlを見つけられるかどうか、ブール値)です。 - 次のフィールドを書いてください(書き込み先:
/root/rke2/components.json)。cni(kube-systemのCNI DaemonSetから判断した名前、小文字)、ingress_class(デフォルトのIngressClassの名前)、datastore(コントロールプレーンのデータストア:etcdまたはsqlite)、storage_classes(StorageClassの名前をソートした配列)、cluster_cidr、service_cidr、cluster_dns(CoreDNS ServiceのIP)、overlaps_host(2つのCIDR範囲のどちらかでもホストクラスターの10.244.0.0/16・10.96.0.0/12と重なるかどうか、ブール値)です。 - ネームスペース
legacyを作成し、/root/rke2/priv-legacy.yamlで特権Podpriv(イメージはpublic.ecr.aws/docker/library/busybox:1.37、sleep 86400、privileged: true)を起動して、Readyにしてください。そのあと次のフィールドを書いてください(書き込み先:/root/rke2/pss-default.json)。pss_file(RKE2が書いたPod Security Admissionの設定ファイルのパス)、enforce(そのファイルのdefaults.enforceの値)、etcd_process_user(現在etcdプロセスを動かしているユーザー名)、priv_uid(Pod privのUID)です。 /etc/rancher/rke2/config.yamlにprofile: cisを入れて、systemctl restart rke2-serverを実行してください。失敗したら、rke2-serverのジャーナルからlevel=fatalの行を1つそのまま保存してください(保存先:/root/rke2/cis-fatal.txt)。そして、ホストの準備が終わるまで再試行ループが動かないようにsystemctl stop rke2-serverで停止し、サービスを停止しても動き続ける静的Pod(rootで動くetcdを含む)をrke2-killall.shで片付けてください。- ドキュメントのホスト要件のとおり、システムユーザーとグループ
etcd(ホームなし、シェルはnologin)を作成し、RKE2が一緒に展開したrke2-cis-sysctl.confを/etc/sysctl.d/60-rke2-cis.confにコピーして適用してください(systemd-sysctlの再起動の代わりにsysctl -p <파일>。プレースホルダーはファイル名です)。このクラスターはデフォルトのプロファイルで作られているので、起動する前に/var/lib/rancher/rke2/server/db/etcd/member/snap/dbの所有者を確認し、etcd_uid、etcd_gid、db_owner_before(사용자:그룹の形式。プレースホルダーはユーザーとグループです)を書いてください(書き込み先:/root/rke2/host-prep.json)。そのあと、所有権には手を加えず、systemctl start --no-block rke2-serverでサービスを起動してください。 - APIサーバーが
/readyzにokを返すまで待ってから、次のフィールドを書いてください(書き込み先:/root/rke2/cis-state.json)。etcd_process_user、enforce(現在のrke2-pss.yamlのdefaults.enforce)、exempt_namespaces(そのファイルのexemptions.namespacesをソートした配列)、db_owner_after(現在のmember/snap/dbの사용자:그룹)、netpol_namespaces(NetworkPolicyが1つでもあるネームスペースをソートした配列)、protect_kernel_defaults(kubeletが--config-dirで読み込む設定ディレクトリのファイルにprotectKernelDefaults: trueがあるかどうか、ブール値)です。 - ネームスペース
shopを作成し、ステップ3と同じ特権Podを適用して(ファイルは/root/rke2/priv-shop.yaml、ネームスペースだけをshopにする)、拒否された出力の全体を保存してください(保存先:/root/rke2/denied.txt)。そのあと、restrictedの基準を満たすPodok(イメージは同じ、sleep 86400)をshopに起動して(ファイルは/root/rke2/ok.yaml)、Readyにしてください。legacyのprivは削除しません。 rke2 etcd-snapshot save --name before-shopでスナップショットを作成し、次のフィールドを書いてください(書き込み先:/root/rke2/snapshot.json)。name(RKE2が付けたスナップショット名の全体)、path(ファイルの絶対パス)、size(バイト数、数値)、sha256(ファイルのハッシュ)です。- 次のフィールドを書いてください(書き込み先:
/root/rke2/report.json)。profile(config.yamlの値)、enforce_before、enforce_after、start_blocker(ステップ4のfatalが要求したもの:etcd-user、sysctl、selinuxのいずれか)、legacy_priv_running(legacyのprivが現在Runningかどうか、ブール値)、snapshot_dir(スナップショットが保存されたディレクトリ)、default_sa_automount_disabled(shopネームスペースのdefault ServiceAccountにautomountServiceAccountToken: falseが設定されているかどうか、ブール値)です。
参考
- ログインシェルでは、
KUBECONFIGと/var/lib/rancher/rke2/binがPATHに入っています。採点ツールは、その設定なしでパスを直接使います。 - サービスのログ:
journalctl -u rke2-server --no-pager | tail - ステップ4で片付けてから、ステップ6で再び立ち上がるまで、APIは応答しません。
systemctl stopだけだと静的Podが動き続け、応答が混ざって見えます。 - よくあるミス: ホストの準備の途中で
systemctl restart systemd-sysctlを実行することです。ドキュメントは、動いているクラスターでの副作用を警告しています。 - よくあるミス:
systemctl stopだけして静的Podを残したまま再び起動することです。古いrootのetcdがデフラグを受け取ってdbをroot所有のまま残し、etcdユーザーで起動した新しいetcdがdbを開けずにpanicします(実測)。 - RKE2 CISハードニングガイド・Pod Security Standards・バックアップと復旧
RKE2は何をどこに置いたか
次のフィールドを書いてください(書き込み先: /root/rke2/layout.json)。version(rke2 --versionの1行目のバージョン。例: v0.0.0+rke2r0の形式)、rke2_binary(rke2の実行ファイルの絶対パス)、kubectl(RKE2が一緒に展開したkubectlの絶対パス)、kubeconfig(管理者のkubeconfigのパス)、kubectl_on_default_path(デフォルトのPATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binだけでkubectlを見つけられるかどうか、ブール値)です。
k3sはkubectlを/usr/local/binに一緒にインストールしますが、RKE2はkubectl・crictl・ctrをデータディレクトリの下のbinに置きます。env -i PATH=... bash -c 'command -v kubectl'で、デフォルトのPATHだけで見つかるかどうかを確認してみてください。
デフォルトで何が入ってきたか
次のフィールドを書いてください(書き込み先: /root/rke2/components.json)。cni(kube-systemのCNI DaemonSetから判断した名前、小文字)、ingress_class(デフォルトのIngressClassの名前)、datastore(コントロールプレーンのデータストア: etcdまたはsqlite)、storage_classes(StorageClassの名前をソートした配列)、cluster_cidr、service_cidr、cluster_dns(CoreDNS ServiceのIP)、overlaps_host(2つのCIDR範囲のどちらかでもホストクラスターの10.244.0.0/16・10.96.0.0/12と重なるかどうか、ブール値)です。
静的Podのマニフェストは/var/lib/rancher/rke2/agent/pod-manifestsにあります。kube-apiserverの--service-cluster-ip-rangeと、kube-controller-managerの--cluster-cidrを読み取ってください。CIDR範囲の重なりは、Pythonのipaddressのoverlapsで計算できます。
デフォルトのプロファイルでは特権Podが起動する
ネームスペースlegacyを作成し、/root/rke2/priv-legacy.yamlで特権Podpriv(イメージはpublic.ecr.aws/docker/library/busybox:1.37、sleep 86400、privileged: true)を起動して、Readyにしてください。そのあと次のフィールドを書いてください(書き込み先: /root/rke2/pss-default.json)。pss_file(RKE2が書いたPod Security Admissionの設定ファイルのパス)、enforce(そのファイルのdefaults.enforceの値)、etcd_process_user(現在etcdプロセスを動かしているユーザー名)、priv_uid(Pod privのUID)です。
RKE2はPSAの設定を/etc/rancher/rke2の下のファイルとして作り、APIサーバーに渡します。プロセスのユーザーはps -eo user,commで見ます。このPodは、後でプロファイルを変更した後も削除しません。
profile: cisの1行でRKE2が起動を拒否した
/etc/rancher/rke2/config.yamlにprofile: cisを入れて、systemctl restart rke2-serverを実行してください。失敗したら、rke2-serverのジャーナルからlevel=fatalの行を1つそのまま保存してください(保存先: /root/rke2/cis-fatal.txt)。そして、ホストの準備が終わるまで再試行ループが動かないようにsystemctl stop rke2-serverで停止し、サービスを停止しても動き続ける静的Pod(rootで動くetcdを含む)をrke2-killall.shで片付けてください。
設定ファイルはデフォルトでは存在しないので、自分で作成します。サービスはRestart=alwaysなので、5秒ごとに再試行します。fatalメッセージは、cisプロファイルがホストに要求するもののうち何が足りないかを教えてくれます。systemctl stopはrke2プロセスだけを停止してコンテナは残すので、古いetcdがrootのまま動き続け、ファイルに触れることができます。インストールプレフィックスのbinに、片付け用のスクリプトがあります。
etcdユーザーとカーネル値を準備して再び起動する
ドキュメントのホスト要件のとおり、システムユーザーとグループetcd(ホームなし、シェルはnologin)を作成し、RKE2が一緒に展開したrke2-cis-sysctl.confを/etc/sysctl.d/60-rke2-cis.confにコピーして適用してください(systemd-sysctlの再起動の代わりにsysctl -p <파일>。プレースホルダーはファイル名です)。このクラスターはデフォルトのプロファイルで作られているので、起動する前に/var/lib/rancher/rke2/server/db/etcd/member/snap/dbの所有者を確認し、etcd_uid、etcd_gid、db_owner_before(사용자:그룹の形式。プレースホルダーはユーザーとグループです)を書いてください(書き込み先: /root/rke2/host-prep.json)。そのあと、所有権には手を加えず、systemctl start --no-block rke2-serverでサービスを起動してください。
tarballインストールのsysctlファイルは、インストールプレフィックスのshare/rke2の下にあります。ドキュメントは、動いているクラスターでsystemd-sysctlを再起動すると、CNIが作ったカーネル値とぶつかって副作用が出ることがあると警告しています。ハードニングガイドは、cisプロファイルが起動時にetcdのデータディレクトリをetcd所有にすると説明しています。デフォルトのプロファイルがroot所有で作ったファイルがどうなるかは、次のステップで確認します。ステップ4で静的Podまで片付けていなかった場合、ここで問題が起きます。
cisプロファイルで再び立ち上がったクラスターを確認する
APIサーバーが/readyzにokを返すまで待ってから、次のフィールドを書いてください(書き込み先: /root/rke2/cis-state.json)。etcd_process_user、enforce(現在のrke2-pss.yamlのdefaults.enforce)、exempt_namespaces(そのファイルのexemptions.namespacesをソートした配列)、db_owner_after(現在のmember/snap/dbの사용자:그룹。プレースホルダーはユーザーとグループです)、netpol_namespaces(NetworkPolicyが1つでもあるネームスペースをソートした配列)、protect_kernel_defaults(kubeletが--config-dirで読み込む設定ディレクトリのファイルにprotectKernelDefaults: trueがあるかどうか、ブール値)です。
再起動の直後はAPIサーバーがしばらく接続を拒否するので、kubectl get --raw /readyzを繰り返して待ってください。最後まで戻ってこない場合は、etcdコンテナのログ(/var/log/pods/kube-system_etcd-*)を見てください。このバージョンのkubeletは、protect-kernel-defaultsをコマンドラインのフラグではなく設定ファイルで受け取ります(実測)。ps -eo argsで--config-dirを探して、そのディレクトリを読み取ってください。cisプロファイルでは、PSAの設定ファイルとデフォルトのネームスペースのNetworkPolicyをRKE2が直接書き込みますが、NetworkPolicyはAPIの準備ができた後に少し遅れて作られるので、一覧が空に見えたら、少し待ってからもう一度読み取ってください。
同じマニフェストが今度は拒否された
ネームスペースshopを作成し、ステップ3と同じ特権Podを適用して(ファイルは/root/rke2/priv-shop.yaml、ネームスペースだけをshopにする)、拒否された出力の全体を保存してください(保存先: /root/rke2/denied.txt)。そのあと、restrictedの基準を満たすPodok(イメージは同じ、sleep 86400)をshopに起動して(ファイルは/root/rke2/ok.yaml)、Readyにしてください。legacyのprivは削除しません。
拒否メッセージは、どのフィールドがrestrictedに違反したかをすべて列挙します。その一覧が、そのまま直す一覧です(runAsNonRoot・seccompProfile・allowPrivilegeEscalation・capabilities)。PSAは作成リクエストしか検査しないので、すでに起動していたPodはそのままにします。ネームスペースを作った直後は、default ServiceAccountがまだなくて別の理由で拒否されることがあるので、拒否の理由がPodSecurityかどうかを確認してください。
プロファイルを変更した後のetcdのスナップショット
rke2 etcd-snapshot save --name before-shopでスナップショットを作成し、次のフィールドを書いてください(書き込み先: /root/rke2/snapshot.json)。name(RKE2が付けたスナップショット名の全体)、path(ファイルの絶対パス)、size(バイト数、数値)、sha256(ファイルのハッシュ)です。
RKE2は、渡した名前の後ろにノード名とUnix時刻を付けます。rke2 etcd-snapshot lsとkubectl get etcdsnapshotfileは、どちらも場所を表示します。config.yamlのprofileキーについて警告が1行出ますが、スナップショットとは無関係です。
移行前に確認する一覧にまとめる
次のフィールドを書いてください(書き込み先: /root/rke2/report.json)。profile(config.yamlの値)、enforce_before、enforce_after、start_blocker(ステップ4のfatalが要求したもの: etcd-user、sysctl、selinuxのいずれか)、legacy_priv_running(legacyのprivが現在Runningかどうか、ブール値)、snapshot_dir(スナップショットが保存されたディレクトリ)、default_sa_automount_disabled(shopネームスペースのdefault ServiceAccountにautomountServiceAccountToken: falseが設定されているかどうか、ブール値)です。
前のステップの記録ファイルと現在のクラスターを根拠にして書きます。ハードニングガイドは、RKE2がシステムのネームスペースのdefault ServiceAccountは自分で直すが、運用者が作ったネームスペースは運用者の担当だと説明しています。shopで実際に確認してください。