Podman迁移计划

Docker不愧是容器第一雄关啊。

从Incus出发

在过去的一些文章里,我曾经着迷于Incus作为容器实现。

在我看来,LXC/Incus那一套共享内核同时分隔文件系统与命名空间的法则很有魅力,用来创建 低开销的类虚拟机实例再适合不过了。

不过,Incus的局限也有不少,或许配置麻烦就是其中之一,需要用户学习一套全新的配置模式。

另一方面,尽管Incus已经实现OCI镜像支持,但它不直接兼容当下流行的Dockerfile + Docker Compose模式

理论上,这两者可被Canonical生态的cloud-init脚本替代,Incus对此有非常好的支持,但改写这些脚本总归是一个调试繁琐、易出Bug的工作。

说到底,Incus更偏向于系统级容器的理念,本身就和Docker/Podman的应用级容器定位有很大差异。

如果仍然迷恋Docker风格的应用级容器,或许可以试试Podman,其绝大部分操作API或指令均和Docker相似,并默认运行在用户态,不依赖root权限级别的daemon。

安装

Podman的安装非常简单,在Debian下只需要:

1
sudo apt install podman

在Windows下安装也非常简单,可参考Podman for Windows文档。基本步骤是:

  1. 前往Github Release,下载最新版podman-installer-windows-amd64.exe文件(MSI也行;别被附件里几个不同名字的.exe骗了,它们的SHA256可能是相同的);
  2. 开始安装,安装时可选HyperV或WSL后端,推荐WSL;
  3. 安装程序结束后,打开命令提示符,执行podman machine init,自动下载宿主虚拟机/WSL镜像并导入

若是好奇podman machine init命令干了什么,可以翻阅上面的文档,内有非常详细的介绍。

常见问题

找不到podman.sock

/run/user/<uid>/podman/podman.sock No such file or directory

请尝试:

1
systemctl --user enable --now podman.socket

容器莫名其妙自动结束

如果是以下情况:用户在保持有登录会话(如SSH)时,容器从不自动结束。而在用户关闭所有会话后一小段时间内,容器就自动退出了。

那么请尝试:

1
sudo loginctl enable-linger <username>

Windows WSL Podman设置代理

若要让基于Windows WSL的Podman使用宿主机网络代理,通常要先确定宿主机的IP,再设置环境变量。

太麻烦了,让我们用一个脚本解决这个问题吧。

使用方法:

  1. 复制以下脚本
  2. 根据自己的网络配置修改USER CONFIGURATION配置项
  3. 打开Podman WSL Machine,在WSL的/etc/profile.d/文件夹下新建个配置文件,贴进去

WSL代理自动配置脚本

/etc/profile.d/wsl-proxy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/bin/bash
# WSL Proxy Environment Setup Script
# Place this script in /etc/profile.d/ to auto-load proxy settings for all users
# Or source it manually

# =============================================================================
# USER CONFIGURATION SECTION
# Modify these variables according to your proxy server settings
# =============================================================================

# Proxy protocol schema (http, https, socks4, socks5)
# Default: http
PROXY_SCHEMA="${PROXY_SCHEMA:-http}"

# Proxy server hostname or IP address
# Use "{GATEWAY}" to automatically detect Windows host IP via WSL gateway
# PROXY_HOST="${PROXY_HOST:-localhost}"
PROXY_HOST="{GATEWAY}"

# Proxy server port number
# Default: 8080
PROXY_PORT="${PROXY_PORT:-8080}"

# Proxy authentication username (leave empty if not required)
# Default: empty
PROXY_USER="${PROXY_USER:-}"

# Proxy authentication password (leave empty if not required)
# Default: empty
PROXY_PASSWORD="${PROXY_PASSWORD:-}"

# Comma-separated list of addresses to bypass proxy
# These domains/IPs will not use the proxy connection
# Default: local addresses (localhost, 127.0.0.1, etc.)
PROXY_BYPASS="${PROXY_BYPASS:-localhost,127.0.0.1,::1,169.254.169.254}"

# =============================================================================
# INTERNAL FUNCTIONS
# =============================================================================

# Detect WSL gateway IP address (Windows host IP)
# This is useful when proxy server runs on Windows host
_get_wsl_gateway() {
local gateway_ip
# Method 1: Use ip route to find default gateway
gateway_ip=$(ip route show default 2>/dev/null | awk '/default/ {print $3}' | head -n1)

# Method 2: Fallback to /etc/resolv.conf nameserver (WSL2)
if [[ -z "$gateway_ip" ]] && [[ -r /etc/resolv.conf ]]; then
gateway_ip=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf)
fi

# Method 3: Fallback to Windows host IP pattern (WSL1)
if [[ -z "$gateway_ip" ]]; then
gateway_ip=$(ip route show | grep -oP 'via \K[0-9.]+' | head -n1)
fi

echo "$gateway_ip"
}

# Build proxy URL with optional authentication
_build_proxy_url() {
local schema="$1"
local host="$2"
local port="$3"
local user="$4"
local password="$5"

local url="${schema}://"

# Add authentication if credentials provided
if [[ -n "$user" ]]; then
url+="${user}"
if [[ -n "$password" ]]; then
url+=":${password}"
fi
url+="@"
fi

# Add host and port
url+="${host}:${port}"

echo "$url"
}

# =============================================================================
# MAIN EXECUTION
# =============================================================================

# Resolve special {GATEWAY} keyword to actual WSL gateway IP
if [[ "$PROXY_HOST" == "{GATEWAY}" ]]; then
PROXY_HOST=$(_get_wsl_gateway)

# Validate gateway detection
if [[ -z "$PROXY_HOST" ]]; then
echo "Warning: Failed to detect WSL gateway IP. Using 'localhost' as fallback." >&2
PROXY_HOST="localhost"
fi
fi

# Construct full proxy URL
PROXY_URL=$(_build_proxy_url "$PROXY_SCHEMA" "$PROXY_HOST" "$PROXY_PORT" "$PROXY_USER" "$PROXY_PASSWORD")

# Export standard proxy environment variables
export HTTP_PROXY="$PROXY_URL"
export http_proxy="$PROXY_URL"
export HTTPS_PROXY="$PROXY_URL"
export https_proxy="$PROXY_URL"

# Export no-proxy settings
export NO_PROXY="$PROXY_BYPASS"
export no_proxy="$PROXY_BYPASS"

# =============================================================================
# VERBOSE OUTPUT (optional)
# Uncomment the following lines to enable debug output
# =============================================================================
# echo "WSL Proxy Configuration Loaded:"
# echo " HTTP_PROXY: $HTTP_PROXY"
# echo " HTTPS_PROXY: $HTTPS_PROXY"
# echo " NO_PROXY: $NO_PROXY"

为容器启用N卡

通常来说,只需要首先安装NVIDIA Container Toolkit,然后使用以下命令启动容器:

1
podman run --rm --gpus all docker.io/debian:trixie-slim nvidia-smi

找不到GPU

Podman主要通过Container Device Interface (CDI)寻找调用系统中的显卡。

在安装NVIDIA Container Toolkit时,会附带一个Systemd服务nvidia-cdi-refresh.service,该服务理论上会在后台运行,自动查找系统中的N卡并刷新CDI信息。

可以通过以下命令查看N卡是否成功注册到CDI中,正常应该是这样的:

1
2
3
4
5
$ nvidia-ctk cdi list
INFO[0000] Found 3 CDI devices
nvidia.com/gpu=0
nvidia.com/gpu=GPU-12345678-1234-5678-90ab-1234567890ab
nvidia.com/gpu=all

然而,有时这一服务无法正常运行(可通过sudo systemctl status nvidia-cdi-refresh.service查看),上述服务不能正确更新CDI中的N卡注册信息,最终会导致Podman无法找到我们想要启用的显卡资源。

解决方法是,可以手动运行CDI注册命令:

1
sudo nvidia-ctk cdi generate --output=/var/run/cdi/nvidia.yaml

Podman Compose设置GPU

docker-compose.yml文件中,可以这样定义使用GPU:

1
2
3
4
5
6
services:
llama-server:
image: nvidia/cuda:12.2.0-runtime-ubuntu22.04
command: ["nvidia-smi"]
devices:
- nvidia.com/gpu=all

如果使用Docker Compose运行,可能会遇到错误:Error response from daemon: container create: stat nvidia.com/gpu=all: no such file or directory

解决方法非常简单:请使用Podman-Compose而不是Docker-Compose:

1
2
3
sudo apt install podman-compose

podman-compose -f docker-compose.yml up