@@ 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 <token, api server, ca cert...> --ignore-preflight-errors=swap
+```
+
+一樣會 fail
+
+應該只有 `/var/lib/kubelet/pods` 會需要修
+
+修了後一樣再來一次
+
+```
+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` 觀察各式路徑和設定
+
+在 `/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 的需求