--- title: "建一個比較健康的 k8s" categories: - Kubernetes tags: - kubernetes - installation - linux - containers --- 帶有些微潔癖的 k8s 折騰紀錄 目標: * single control-pane * user namespacing * CRI-O + crun 環境: * master 和其餘 nodes 在同一個內網 * master 在 gateway 上 * 所有 nodes 皆有現有 bridges * 使用既有內網 成果: * kube-apiserver, kube-controller-manager, kube-scheduler 和所有 pods 跑在 user namespace enabled containers * kube-proxy 直接跑在 host 安裝: 裝 crun, cri-o, kubelet, kubectl, kubeadm, 注意版本相容, 記得 package manager 鎖版防手殘造成不預期的更新 我因為安裝時 cri-o 提供的套件只有到 1.17 所以全部鎖 1.17 的 branch ### configure cri-o /etc/crio/crio.conf: 修改預設 runtime ```toml default_runtime = "crun" ``` 因為 hosts 用了 ubuntu, 設 apparmor ```toml apparmor_profile = "crio-default-1.17.4" ``` drop 掉一堆可能不用的預設 capabilities ```toml default_capabilities = [ #"CHOWN", #"DAC_OVERRIDE", #"FSETID", #"FOWNER", #"NET_RAW", #"SETGID", #"SETUID", #"SETPCAP", "NET_BIND_SERVICE", #"SYS_CHROOT", #"KILL", ] ``` `NET_BIND_SERVICE` 感覺常用就留了 PID limit 先壓小不夠再改就好 ```toml pids_limit = 128 ``` 設 UID/GID mappings 啟用 user namespacing ```toml uid_mappings = "0:10000000:65536" gid_mappings = "0:10000000:65536" ``` 設 10000000 開始是因為我在一些機器上有做 lxc containers 也有 user namespacing 需求, 是手動每個 containers 都錯開分配的, 用量頗多, 所以我抓了一個我覺得夠高不會撞到又好記的 10000000 我 crio 的 user 還是 root, 但其實 crio 有環境變數可以打開 unprivileged mode `_CRIO_ROOTLESS`, 但如果要配合 user namespacing 就得取消 Debian 加的預防措施 `kernel.unprivileged_userns_clone`, 考量到 user namespacing 早期跟其他東西配合曾出現不少 privilege escalation, 雖然現在都修了並且瀏覽器的 sandboxing 也都開始利用 unprivileged user namespacing, 有些環境可能還是不怎麼需要開放, 且我的 node 同時含有 k8s 以外的服務, 這裡我選擇留在 privileged user namespacing 注意此 privilege 並非 docker/k8s 指的 privilege 定義 crun runtime ```toml [crio.runtime.runtimes.crun] ``` 預設會在 `$PATH` 找跟名字相符的 binary ### configure storage cri-o 用的 storage /etc/containers/storage.conf 設定 UID/GID mappings ```toml remap-uids = "0:10000000:65536" remap-gids = "0:10000000:65536" ``` ### configure network 我的規劃是利用現有內網跟 bridge, 所以修改預設 example 就行 /etc/cni/net.d/100-crio-bridge.conf ```json { "cniVersion": "0.3.1", "name": "crio-bridge", "type": "bridge", "bridge": "lxcbr0", "isGateway": false, "ipMasq": false, "ipam": { "type": "host-local", "routes": [ { "dst": "0.0.0.0/0" } ], "ranges": [ [{ "subnet": "10.0.3.0/24", "rangeStart": "10.0.3.16", "rangeEnd": "10.0.3.60", "gateway": "10.0.3.1" }] ] } } ``` 根據源碼如果 bridge 存在會沿用 這裡我現有的 bridge 是 lxcbr0, 現有內網是 10.0.3.0/24, 因為我本來這個內網就是手動 allocate address 的, 所以很容易的給每個 node 都留了一段可用空間 ### configure kubelet /etc/default/kubelet ``` KUBELET_EXTRA_ARGS=--fail-swap-on=false --cgroup-driver=systemd ``` swap 是個好東西我把他留了, 畢竟通常情況總是會有些 pages 本質上很常 inactive 適合進 swap, 而且 swap 也是個很好的吃緊下的臨時備案 k8s 的 resource 控管都假設沒有 swap, 擔心控管較差的話可以把 swappiness 調低 然後記得 restart kubelet ## kubeadm ### bootstrap master ```shell sudo kubeadm init --apiserver-advertise-address=10.0.3.1 --ignore-preflight-errors=swap ``` 我 master 同時是內網 gateway, 所以設了 advertise address 避免被從外面戳 會 fail, 因為用了 user namespacing 然而 kube-system 的 pods 都有 bind mount 一些 hosts 端的檔案進去, containers 裡的 root 是 host 端的 UID 10000000 這裡我先創 UID 10000000/GID 10000000 的 passwd/groups entry, 比較好看 ```shell sudo useradd -u 10000000 -U k8s sudo groupmod -g 10000000 k8s ``` 預設應該就有鎖了但保險起見還是 `sudo passwd -l k8s` 了一下 開始用 group 修權限 ```shell sudo chown root:k8s /var/lib/kubelet sudo chmod g+rx /var/lib/kubelet sudo chown root:k8s /var/lib/kubelet/pods -R sudo chmod g+s /var/lib/kubelet/pods ``` 這裡在 directory 用了 set-GID bit, 讓以後創的 pods 套用 group 不用手動再修 修 etcd 用的 directory ```shell sudo chown k8s:k8s -R /var/lib/etcd ``` 修 k8s config ```shell sudo chown k8s:k8s -R /etc/kubernetes ``` 然後再 bootstrap 一次應該就沒問題了 ```shell sudo kubeadm init --apiserver-advertise-address=10.0.3.1 --ignore-preflight-errors=all ``` 這裡變成忽略全部檢查 (error => warning), 應該只會多了一堆檔案已經存在跟 port 正在用之類的, 但還是注意一下 kubeadm 如果檔案存在會用現有的 ### bootstrap nodes ```shell sudo kubeadm join <token, api server, ca cert...> --ignore-preflight-errors=swap ``` 一樣會 fail 應該只有 `/var/lib/kubelet/pods` 會需要修 修了後一樣再來一次 ```shell sudo kubeadm join <token, api server, ca cert...> --ignore-preflight-errors=all ``` ### fix kube-proxy 到這裡 kubectl 設定好已經能看到 nodes 了, 也可以開 pods 來用 但 `kubectl get pods -n kube-system` 會發現 kube-proxy 都起不來, coredns 也都進不了 ready 主要問題來源是 kube-proxy, coredns 只是靠 proxy 戳 api 戳不到 分析 kube-proxy 發現 SecurityContext 有設 privileged, 查詢相似錯誤在 runc 上是 privileged 導致 /dev/tty 在 spec 中被定義, 跟內建 device list 重疊, 然後在 user namespacing 下 device 是透過 bind mount, 重複操作第二次就 fail 了, 懷疑在 crun 上也是相似情況 分析 kube-proxy 本身為何需要 privilege, 發現運作原理會需要修改 host 端 iptables, 測試及讀 man pages 後得知這在 user namespacing 下就算用 capabilities 也因 capabilities 侷限於 namespace 內沒辦法修改到 host 端的 iptables, 觀察 CRI/CRI-O 等層發現當下不易實做根據 pods 關閉 user namespacing, 故放棄使用 k8s container 跑 kube-proxy kube-proxy 本身實做上除了 iptables 外也會自動幫你 modprobe 和 sysctl, 本身假設的權限就極大, 做 container 保護效果不大 這裡撈出來直接跑在 host 端, 因為我的網路架構往非 10.0.3.0/24 內網都會過 master, 我在 master 上做一個就能達到效果 用 `kubectl get pods <kube-proxy pod on master> -n kube-system -o yaml` 觀察各式參數和設定 在 `/var/lib/containers/storage` `find` 出 kube-proxy binary, 把他抓出來裝進 `$PATH` 在 `/var/lib/kubelet/pods` 下找到 kube-proxy pod 的 volumes, 內含他用的 config 和 cert, token 等, 抓出來放好設好權限防護, 例如我放在 `/var/lib/kube-proxy`, config 內要改的路徑比較少, 注意 cert 和 token 原本在 container 內掛在 `/var/run`, 這在大多系統上可能是 tmpfs, 改放到一個能保存的地方, 並修改 config 內他們的路徑, 例如我還是一律丟進 `/var/lib/kube-proxy`, 並且 `sudo chmod o= /var/lib/kube-proxy` 防止 others 存取 寫個自動 startup, 例如 systemd service ```systemd [Unit] Description=kube-proxy Wants=network-online.target Before=multi-user.target [Service] Type=simple ExecStart=/usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf [Install] WantedBy=multi-user.target ``` 然後把他 enable 且戳起來 可以開個 pod 戳戳看 dns, 預設是用過 proxy 的 address, 戳的到應該就好了, 可以試試查詢 `kubernetes`, 會得到過 proxy 的 api server address 刪除原本 kube-proxy 的 DaemonSet ```shell kubectl delete daemonset kube-proxy -n kube-system ``` 這樣基本的 k8s 功能應該都能用了 另外我因為 master node 有對外的 interface, 有掃過 port 並修改各 config 確保沒有不必要的 port 在對外 address 給 listen 到, 有些東西預設是 bind 0.0.0.0 的要注意 這個 setup 的缺點是 privileged container 會撞上面 kube-proxy 最初的問題 fail on create, 就算這點修好了有 user namespacing 下 privileged 也沒有原本那麼多 privilege, 但我們目標本來就是盡量減少 containers 的權限並達到更大的隔離, 我的假設就是沒有 privileged containers 的需求