# 不用 API Server 也能运行 Pod？

遇到一个奇怪的需求：想复用 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：

```plaintext
$ apt install containerd cri-tools
...
```

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

```plaintext
$ 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`：

```yaml
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：

```plaintext
$ 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 了：

```yaml
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：

```plaintext
$  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 里面是无法引用 `Configmap`、`Secret` 之类的外部对象的。更不要提 `Deployment` 了。

## Podman Play Kube

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

* Deployment
    
* PVC
    
* Configmap
    

### 启动 Pod

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

```plaintext
$ 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 ls` 和 `podman 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：

```plaintext
$podman play kube pod.yaml
Pod:
...
Container:
...
```

甚至可以启动一个 Deployment，例如：

```yaml
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` 运行一下：

```plaintext
$ 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：

```plaintext
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` 开关），验证一下：

```plaintext
$ podman exec -it [your container id] env | grep key
key1=value1
```

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

## 结论

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