From edd1963669e3f57b6f8b5e824f0b293a1167e051 Mon Sep 17 00:00:00 2001 From: xdavidwu Date: Thu, 30 Jul 2020 01:57:41 +0800 Subject: [PATCH] add healthy k8s --- _posts/2020-07-29-healthy-k8s.md | 275 +++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 _posts/2020-07-29-healthy-k8s.md diff --git a/_posts/2020-07-29-healthy-k8s.md b/_posts/2020-07-29-healthy-k8s.md new file mode 100644 index 0000000..ca8cac3 --- /dev/null +++ b/_posts/2020-07-29-healthy-k8s.md @@ -0,0 +1,275 @@ +--- +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 + +``` +default_runtime = "crun" +``` + +因為 hosts 用了 ubuntu, 設 apparmor + +``` +apparmor_profile = "crio-default-1.17.4" +``` + +drop 掉一堆可能不用的預設 capabilities + +``` +default_capabilities = [ + #"CHOWN", + #"DAC_OVERRIDE", + #"FSETID", + #"FOWNER", + #"NET_RAW", + #"SETGID", + #"SETUID", + #"SETPCAP", + "NET_BIND_SERVICE", + #"SYS_CHROOT", + #"KILL", +] +``` + +`NET_BIND_SERVICE` 感覺常用就留了 + +PID limit 先壓小不夠再改就好 + +``` +pids_limit = 128 +``` + +設 UID/GID mappings 啟用 user namespacing + +``` +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, 有些環境可能還是不怎麼需要開放,這裡我選擇留在 privileged user namespacing + +注意此 privilege 並非 docker/k8s 指的 privilege + +定義 crun runtime + +``` + [crio.runtime.runtimes.crun] +``` + +預設會在 `$PATH` 找跟名字相符的 binary + +### configure storage + +cri-o 用的 storage + +/etc/containers/storage.conf + +設定 UID/GID mappings + +``` +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 是 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 + +``` +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, 比較好看 + +``` +sudo useradd -u 10000000 -U k8s +sudo groupmod -g 10000000 k8s +``` + +預設應該就有鎖了但保險起見還是 `sudo passwd -l k8s` 了一下 + +開始用 group 修權限 + +``` +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 + +``` +sudo chown k8s:k8s -R /var/lib/etcd +``` + +修 k8s config + +``` +sudo chown k8s:k8s -R /etc/kubernetes/ +``` + +然後再 bootstrap 一次應該就沒問題了 + +``` +sudo kubeadm init --apiserver-advertise-address=10.0.3.1 --ignore-preflight-errors=all +``` + +這裡變成忽略全部檢查 (error => warning), 應該只會多了一堆檔案已經存在跟 port 正在用之類的, 但還是注意一下 + +kubeadm 如果檔案存在會用現有的 + +#### bootstrap nodes + +``` +sudo kubeadm join --ignore-preflight-errors=swap +``` + +一樣會 fail + +應該只有 `/var/lib/kubelet/pods` 會需要修 + +修了後一樣再來一次 + +``` +sudo kubeadm join --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 -n kube-system` 觀察各式路徑和設定 + +在 `/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 + +``` +[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 + +這樣基本的 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 的需求 -- 2.43.0