奇技淫巧:不用 API Server 也能运行 Pod?

2024-03-05 08:30:50 浏览数 (2)

遇到一个奇怪的需求:想复用 Pod 的 YAML,但是家境贫寒,不想搞个高可用 API Server;又惜字如金,不想上 Docker Compose。一顿 Google 猛如虎之后,得到了两个方案:静态 Pod 和 podman play kube

静态 Pod

Kubernetes 有个功能,就是 static pod,官网介绍大致如下:

静态 Pod 由特定节点上的 kubelet 守护进程直接管理的,API 服务器并不关注静态 Pod。通常说来,Pod 是由 Deployments 之类的控制器管理的,而静态 Pod 则是在 Kubelet 的看护之下,并负责其重新启动的。

那么 Kubelet 是否可以脱离 API Server 直接运行呢?答案是肯定的,Kelsey Hightower 早在七年前就做了这样的尝试。

https://github.com/kelseyhightower/standalone-kubelet-tutorial

想法很简单,单独运行一个 Kubelet,使用 Kubelet 拉起磁盘上的 Pod 文件。

测试

以目前最新版本的 1.29 为例,在 Ubuntu 中按照默认方式使用 apt 部署 Containerd:

代码语言:javascript复制
$ apt install containerd cri-tools
...

然后按照官网文档安装 kubelet:

代码语言:javascript复制
$ apt-get install -y apt-transport-https ca-certificates curl gpg
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubelet
...

编写如下 kubelet.yaml

代码语言:javascript复制
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
enableServer: false
staticPodPath: /home/kubelet/pods
readOnlyPort: 10250
failSwapOn: false
podCIDR: 10.241.1.0/24
authentication:
  anonymous:
    enabled: true
  webhook:
    enabled: false
authorization:
  mode: AlwaysAllow

最后,我们启动 Kubelet:

代码语言:javascript复制
$ kubelet --config=kubelet.yaml
I0302 11:39:14.006446    9890 server.go:487] "Kubelet version" kubeletVersion="v1.29.2"
I0302 11:39:14.006492    9890 server.go:489] "Golang settings" GOGC="" GOMAXPROCS="" GOTRACEBACK=""
I0302 11:39:14.006622    9890 server.go:650] "Standalone mode, no API client"
I0302 11:39:14.010584    9890 server.go:538] "No api server defined - no events will be sent to API server"
...

注意,如果使用其它配置方法的容器运行时,可能需要指定不同的 Endpoint

这里会看到,日志中直接就表明这是一个独立运行模式的 Kubelet。

最后只要把一个 Pod 定义的文件拷贝到上文配置中的指定目录就能启动 Pod 了:

代码语言:javascript复制
apiVersion: v1
kind: Pod
metadata:
  name: apache
spec:
  containers:
  - name: apache
    image: httpd
    ports:
    - name: http
      containerPort: 80
      hostPort: 45678
    volumeMounts:
    - name: local
      mountPath: /data
  volumes:
  - name: local
    hostPath:
      path: /home/volumes/data
      type: Directory

使用 crictl 查看运行中的 Pod:

代码语言:javascript复制
$  sudo crictl ps
55a65b4642f47       50a1bd9b297f7       18 seconds ago      Running             apache              0                   c141f4e021cdf       apache-ubuntu
$ curl http://127.0.0.1:45678
<html><body><h1>It works!</h1></body></html>

Pod 已经启动。

限制

因为没有 API Server 的支持,所以静态 Pod 里面是无法引用 ConfigmapSecret 之类的外部对象的。更不要提 Deployment 了。

Podman Play Kube

和独立模式的 Kubelet 不同,podman play kube 支持的 Kubernetes 对象除了 Pod 之外,还支持:

  • Deployment
  • PVC
  • Configmap

启动 Pod

Ubuntu 下可以直接使用 apt install podman 安装部署。安装结束后,可以复用刚才的 pod.yaml

代码语言:javascript复制
$ podman play kube pod.yaml
a container exists with the same name ("apache") as the pod in your YAML file; changing pod name to apache_pod
Pod:
...
Container:
...

$ podman pod ls
POD ID        NAME        STATUS      CREATED        INFRA ID      # OF CONTAINERS
99e235dfe7a3  apache_pod  Running     9 seconds ago  b54991e35f58  2

$ podman ps
CONTAINER ID  IMAGE                           COMMAND           CREATED         STATUS             PORTS                  NAMES
b54991e35f58  k8s.gcr.io/pause:3.5                              41 seconds ago  Up 38 seconds ago  0.0.0.0:45678->80/tcp  99e235dfe7a3-infra
aa4a4ba1af39  docker.io/library/httpd:latest  httpd-foreground  38 seconds ago  Up 38 seconds ago  0.0.0.0:45678->80/tcp  apache_pod-apache

看到这里有几个发现:

  1. podman pod lspodman ps 可以查看 Pod 和容器的情况
  2. Podman 取了个巧,使用命名的方式来区分容器和 Pod
  3. Podman 启动的 Pod 用到了 Infra 容器,所以一个 Pod 里面会有两个容器。

为了让后续动作顺利,可以把容器名称修改为 httpd,用于消除这种隐式变更。在应用新版本 YAML 之前,需要因为发生了改名情况,所以无法使用 podman play kube pod.yaml --down 的方式停止 Pod,这里用 podman pod kill apache_pod && podman pod rm apache_pod 删除 Pod,然后重新创建修改后的 Pod:

代码语言:javascript复制
$podman play kube pod.yaml
Pod:
...
Container:
...

甚至可以启动一个 Deployment,例如:

代码语言:javascript复制
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx

play kube 运行一下:

代码语言:javascript复制
$ podman play kube deploy.yaml
...
podman pod ls
POD ID        NAME         STATUS      CREATED         INFRA ID      # OF CONTAINERS
0a6e4dcda93c  nginx-pod-2  Running     15 seconds ago  319f12f3b6f2  2
266df25c4df1  nginx-pod-1  Running     19 seconds ago  a65f6b601160  2
e6966f42c5fd  nginx-pod-0  Running     22 seconds ago  953e3e830528  2
573597e627ec  apache       Running     9 minutes ago   3b4ff4625b46  2

可以看到,这里生成了 3 个 nginx-pod 为前缀的 Pod。

Configmap

修改一下刚才的 pod.yaml,其中加入 Configmap:

代码语言:javascript复制
apiVersion: v1
kind: ConfigMap
metadata:
  name: cm-sample
data:
  key1: value1
---
apiVersion: v1
kind: Pod
metadata:
  name: apache
spec:
  containers:
  - name: httpd
    image: httpd
    ports:
    - name: http
      containerPort: 80
      hostPort: 45678
    envFrom:
    - configMapRef:
        name: cm-sample
        optional: false

这里加入了一个引用 Configmap 作为环境变量的选项,使用 --down 开关停止当前 Pod 并重建后(4.x 版本有了 --replace 开关),验证一下:

代码语言:javascript复制
$ podman exec -it [your container id] env | grep key
key1=value1

可以看到已经成功引用了 Configmap。

结论

除了简单的运行功能之外,Podman Play 还提供了网络、命名空间等功能,甚至还有现场构建的能力,比孤零零的 kubelet 强大不少,但是如果 Kubelet 加入 crictl、nerdctl 之类的东西的话,勉强也算各擅胜场。

0 人点赞