在默认情况下,Kubernetes会以root用户的身份运行容器,这可能会带来安全风险和一些限制。本文将介绍如何在Kubernetes上以非root用户身份运行容器的步骤和注意事项。而是通过使用用户命名空间去运行 Kubernetes 节点组件(例如 kubelet、CRI、OCI、CNI)。这种技术也叫做 rootless 模式(Rootless mode)。
一、准备
Kubernetes 服务器版本必须不低于版本 1.22. 要获知版本信息,请输入 kubectl version.
- 启用 cgroup v2;
- 在 systemd 中启用 user session;
- 根据不同的 Linux 发行版,配置 sysctl 的值;
- 确保非特权用户被列在 /etc/subuid 和 /etc/subgid 文件中;
- 启用 KubeletInUserNamespace 特性门控。
二、在非特权容器内运行
Sysbox 是一个开源容器运行时 (类似于 “runc”),支持在 Linux 用户命名空间隔离的非特权容器内运行系统级工作负载, 比如 Docker 和 Kubernetes。
Sysbox 支持在非特权容器内运行 Kubernetes, 而不需要 cgroup v2 和 “KubeletInUserNamespace” 特性门控。 Sysbox 通过在容器内暴露特定的 /proc 和 /sys 文件系统, 以及其它一些先进的操作系统虚拟化技术来实现。
三、在主机上运行Rootless模式
- Usernetes 是 Kubernetes 的一个参考发行版, 它可以在不使用 root 特权的情况下安装在 $HOME 目录下。
- Usernetes 支持使用 containerd 和 CRI-O 作为 CRI 运行时。 Usernetes 支持配置了 Flannel (VXLAN)的多节点集群。
四、命名空间运行kubelet
注意:下面内容是面向 Kubernetes 发行版的开发者,而不是最终用户。
1、创建用户命名空间
第一步是创建一个 用户命名空间。
如果正在尝试使用用户命名空间的容器(例如 Rootless 模式的 Docker/Podman 或 LXC/LXD) 运行 Kubernetes,否则需要通过传递参数 CLONE_NEWUSER 调用 unshare(2),自己创建一个命名空间。
用户命名空间也可以通过如下所示的命令行工具取消共享:
- unshare(1)
- RootlessKit
- become-root
在取消命名空间的共享之后,也必须对其它的命名空间例如 mount 命名空间取消共享。
在取消 mount 命名空间的共享之后,不需要调用 chroot() 或者 pivot_root(), 但是必须在这个命名空间内挂载可写的文件系统到几个目录上。
请确保这个命名空间内(不是这个命名空间外部)至少以下几个目录是可写的:
- /etc
- /run
- /var/logs
- /var/lib/kubelet
- /var/lib/cni
- /var/lib/containerd
- /var/lib/containers
2、创建委派 cgroup 树
除了用户命名空间,也需要有一个版本为 cgroup v2 的可写 cgroup 树。
Kubernetes 需要 cgroup v2 才支持在用户命名空间运行节点组件。 cgroup v1 是不支持的。
如果在一个采用 systemd 机制的主机上使用用户命名空间的容器(例如 Rootless 模式的 Docker/Podman 或 LXC/LXD)来运行 Kubernetes,那么已经准备就绪。否则必须创建一个具有 Delegate=yes 属性的 systemd 单元,来委派一个具有可写权限的 cgroup 树。在节点上,systemd 必须已经配置为允许委派。
3、配置网络
节点组件的网络命名空间必须有一个非本地回路的网卡。它可以使用 slirp4netns、 lxc-user-nic(1) 等工具进行配置。
Pod 的网络命名空间可以使用常规的 CNI 插件配置。对于多节点的网络,已知 Flannel (VXLAN、8472/UDP) 可以正常工作。诸如 kubelet 端口(10250/TCP)和 NodePort 服务端口之类的端口必须通过外部端口转发器 (例如 RootlessKit、slirp4netns 或 socat(1)) 从节点网络命名空间暴露给主机。
可以使用 K3s 的端口转发器。更多细节请参阅 在 Rootless 模式下运行 K3s。 该实现可以在 k3s 的 pkg/rootlessports 包中找到。
4、配置 CRI
kubelet 依赖于容器运行时。需要部署一个容器运行时(例如 containerd 或 CRI-O), 并确保它在 kubelet 启动之前已经在用户命名空间内运行。
containerd:
containerd 1.4 开始支持在用户命名空间运行 containerd 的 CRI 插件。
在用户命名空间运行 containerd 必须进行如下配置:
version = 2 [plugins."io.containerd.grpc.v1.cri"] # 禁用 AppArmor disable_apparmor = true # 忽略配置 oom_score_adj 时的错误 restrict_oom_score_adj = true # 禁用 hugetlb cgroup v2 控制器(因为 systemd 不支持委派 hugetlb controller) disable_hugetlb_controller = true [plugins."io.containerd.grpc.v1.cri".containerd] # 如果内核 >= 5.11 , 也可以使用 non-fuse overlayfs, 但需要禁用 SELinux snapshotter = "fuse-overlayfs" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] # 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 SystemdCgroup 驱动 # (除非你在命名空间内运行了另一个 systemd) SystemdCgroup = false
CRI-O:
CRI-O 1.22 开始支持在用户命名空间运行 CRI-O。CRI-O 必须配置一个环境变量 _CRIO_ROOTLESS=1。推荐使用以下配置:
[crio] storage_driver = "overlay" # 如果内核 >= 5.11 , 也可以使用 non-fuse overlayfs, 但需要禁用 SELinux storage_option = ["overlay.mount_program=/usr/local/bin/fuse-overlayfs"] [crio.runtime] # 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 "systemd" 驱动 # (除非你在命名空间内运行了另一个 systemd) cgroup_manager = "cgroupfs"
5、配置 kubelet
在用户命名空间运行 kubelet 必须进行如下配置:
apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration featureGates: KubeletInUserNamespace: true # 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 "systemd" 驱动 # (除非在命名空间内运行了另一个 systemd) cgroupDriver: "cgroupfs"
当 KubeletInUserNamespace 特性门控被启用时, kubelet 会忽略节点内由于配置如下几个 sysctl 参数值而可能产生的错误。
- vm.overcommit_memory
- vm.panic_on_oom
- kernel.panic
- kernel.panic_on_oops
- kernel.keys.root_maxkeys
- kernel.keys.root_maxbytes.
在用户命名空间内, kubelet 也会忽略任何由于打开 /dev/kmsg 而产生的错误。 这个特性门控也允许 kube-proxy 忽略由于配置 RLIMIT_NOFILE 而产生的一个错误。
KubeletInUserNamespace 特性门控从 Kubernetes v1.22 被引入, 标记为 “alpha” 状态。
通过挂载特制的 proc 文件系统 (比如 Sysbox), 也可以在不使用这个特性门控的情况下在用户命名空间运行 kubelet,但这不受官方支持。
6、配置 kube-proxy
在用户命名空间运行 kube-proxy 需要进行以下配置:
apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: "iptables" # or "userspace" conntrack: # 跳过配置 sysctl 的值 "net.netfilter.nf_conntrack_max" maxPerCore: 0 # 跳过配置 "net.netfilter.nf_conntrack_tcp_timeout_established" tcpEstablishedTimeout: 0s # 跳过配置 "net.netfilter.nf_conntrack_tcp_timeout_close" tcpCloseWaitTimeout: 0s
五、注意事项
大部分“非本地”的卷驱动(例如 nfs 和 iscsi)不能正常工作。 已知诸如 local、hostPath、emptyDir、configMap、secret 和 downwardAPI 这些本地卷是能正常工作的。
一些 CNI 插件可能不正常工作。已知 Flannel (VXLAN) 是能正常工作的。