一、先搞懂为啥Podman构建镜像慢
很多用Podman做开发的朋友都遇到过这个糟心事:写好Dockerfile,敲个podman build,结果等了十几分钟甚至半小时才出结果,中间只能干瞪眼刷手机。其实大部分时候慢不是电脑配置不够,而是没摸透Podman的运行逻辑,踩了几个常见的坑。
Podman和Docker的核心逻辑差不多,都是一层一层构建镜像,每一层的改动都会被缓存下来,下次构建如果没改就直接用缓存。但它俩在缓存管理、镜像拉取的默认配置上有不少区别,很多人习惯了Docker的操作,直接套用到Podman上,就容易出问题。比如Docker默认会把构建缓存存在本地,而Podman在某些发行版上会把缓存存在临时目录,重启电脑就清掉了,每次构建都要重新拉基础镜像,这肯定慢。
还有一个常见的问题是网络配置。Podman默认用的是用户命名空间的网络,拉取镜像的时候会通过镜像仓库的默认地址,但很多国内开发者没配置镜像加速,拉取国外的基础镜像(比如Ubuntu、CentOS的官方镜像)就会慢得离谱。另外,Podman的构建上下文默认是当前目录,要是你把项目的大文件(比如编译好的二进制、日志文件、node_modules)都放在当前目录,构建的时候会把这些没用的文件一起打包传给Podman,光传文件就要花好几分钟,自然慢。
1.1 先排查自己的慢是哪种类型
要解决问题,得先知道自己的慢是哪来的。你可以先做个小测试:把构建命令改成带输出的,看看每一步花了多久。比如你写了个简单的Dockerfile,内容是基于Ubuntu镜像装个Python,然后执行构建命令:
# 带详细输出的Podman构建命令,能看到每一步的耗时
podman build --progress=plain -t test-image .
执行完之后,看输出里的每一步:如果第一步拉基础镜像就花了很久,那大概率是网络或者基础镜像没缓存;如果每一层的构建都慢,那可能是缓存配置或者构建上下文的问题;如果只有某一步(比如安装依赖)慢,那可能是依赖源的问题。
举个例子,我之前帮朋友排查,他的构建每次都要等10分钟,看输出发现第一步拉Ubuntu镜像就花了8分钟,这明显是网络问题,后来配了国内的镜像加速,拉取时间降到了30秒以内。
二、最实用的3种加速方法,新手也能直接用
搞懂了慢的原因,接下来就说具体的解决方法,这些方法都是我自己和身边开发者亲测有效的,不需要复杂的配置,跟着做就能提速。
2.1 配置国内镜像加速,解决基础镜像拉取慢
这是最立竿见影的方法,适合所有开发者,尤其是国内的开发者。Podman默认拉取镜像的地址是Docker官方的镜像仓库,国内访问速度很慢,我们只需要把它改成国内的镜像仓库,拉取速度就能提升好几倍。
首先要知道自己的Podman是怎么配置的。Podman的镜像配置文件一般在/etc/containers/registries.conf(系统级,所有用户都能用)或者~/.config/containers/registries.conf(用户级,只有当前用户能用)。我们可以先看看有没有这个文件,如果没有就自己新建一个。
然后在文件里添加国内的镜像地址,比如阿里云、网易云、中科大的镜像源,这些都是免费的。具体的配置示例如下(技术栈:Podman 4.x以上版本,适配主流Linux发行版):
# 先备份原配置文件(如果有的话)
sudo cp /etc/containers/registries.conf /etc/containers/registries.conf.bak
# 新建或编辑配置文件
sudo vim /etc/containers/registries.conf
然后在文件里添加以下内容:
# 国内镜像加速配置,按优先级排序,前面的先拉
unqualified-search-registries = ["docker.io", "quay.io"]
[[registry]]
prefix = "docker.io"
location = "docker.io"
[[registry.mirror]]
location = "mirror.aliyuncs.com" # 阿里云镜像源
[[registry.mirror]]
location = "hub-mirror.c.163.com" # 网易云镜像源
[[registry.mirror]]
location = "ustc.edu.cn" # 中科大镜像源
配置完之后,保存退出,然后重启Podman的服务(如果是用systemd管理的话):
# 重启Podman服务,让配置生效
sudo systemctl restart podman
之后再拉取基础镜像,速度就会快很多。你可以测试一下:
# 拉取Ubuntu镜像,看耗时
time podman pull ubuntu:22.04
正常情况下,配置前可能要10分钟,配置后1分钟以内就能拉完。
这个方法的优点是配置简单,一劳永逸,所有的镜像拉取都会走加速;缺点是如果国内镜像源同步不及时,可能会拉不到最新的镜像,这时候可以把官方源放在优先级最高的位置,或者手动指定拉取官方源。注意事项是配置的时候不要写错地址,否则会拉取失败,另外如果是个人电脑,用用户级的配置文件更安全,不需要改系统级的配置。
2.2 优化构建缓存,避免重复拉取和构建
构建缓存是Podman提升速度的核心,很多人不知道Podman的缓存默认存在哪里,也不知道怎么管理缓存,导致每次构建都要重新开始。
首先,Podman的构建缓存默认存在~/.local/share/containers/storage/overlay目录下,这个目录是用户级的,重启电脑不会清空,除非你手动删了。但有些发行版(比如Fedora)会把这个目录放在临时分区,重启就会清空,这时候就需要把缓存目录改到永久分区。
怎么改缓存目录呢?可以在Podman的配置文件里指定,或者在构建的时候临时指定。比如我们要把缓存目录改到/opt/podman/cache,可以这样配置:
# 新建缓存目录,给当前用户权限
sudo mkdir -p /opt/podman/cache
sudo chown $USER:$USER /opt/podman/cache
# 编辑Podman的配置文件
vim ~/.config/containers/storage.conf
在文件里添加以下内容:
# 指定Podman的存储目录,也就是缓存目录
graphroot = "/opt/podman/cache"
保存退出之后,下次构建就会把缓存存在这个目录,重启也不会丢。
另外,构建缓存的生效规则和Docker差不多:Dockerfile里的每一步,只要命令没改,基础镜像没改,就会用缓存。所以写Dockerfile的时候要注意顺序,把容易变的命令放在后面,不容易变的放在前面。比如安装依赖的命令(比如apt install、npm install)要放在复制代码的命令前面,这样代码改了,依赖没改,就可以用缓存,不用重新装依赖。
举个例子,错误的Dockerfile顺序是:
# 错误的顺序:先复制代码,再装依赖,每次代码改了都要重新装依赖
FROM node:18-alpine
WORKDIR /app
COPY . . # 复制所有代码,只要代码改了,这一层就会变
RUN npm install # 装依赖,每次都要重新跑
CMD ["npm", "start"]
正确的顺序是:
# 正确的顺序:先复制依赖文件,装依赖,再复制代码
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./ # 只复制依赖文件,只要依赖没改,这一层就会缓存
RUN npm install # 装依赖,依赖没改就用缓存
COPY . . # 复制代码,代码改了才会重新跑这一层
CMD ["npm", "start"]
这样改了之后,每次代码改了,只要依赖没改,就不用重新装依赖,构建速度至少提升一半。
还有一个小技巧是用--no-cache参数的时候要注意,这个参数会禁用所有缓存,每次构建都重新开始,只有在排查问题的时候用,平时不要用。
这个方法的优点是能重复利用之前的构建成果,避免重复拉取镜像和构建依赖;缺点是缓存会占用磁盘空间,时间长了可以用podman system prune命令清理没用的缓存;注意事项是写Dockerfile的时候一定要注意顺序,否则缓存不会生效,另外如果基础镜像更新了,要及时拉取最新的基础镜像,否则会用旧的缓存。
2.3 缩小构建上下文,避免传输没用的文件
很多人构建的时候,当前目录下有很多没用的文件,比如编译好的二进制、日志文件、node_modules、.git目录等,这些文件加起来可能有几个G,构建的时候Podman会把整个目录打包传给后台服务,光传文件就要花好几分钟,这也是慢的常见原因。
解决这个问题的方法是用.dockerignore文件,告诉Podman哪些文件不需要打包。.dockerignore的规则和.gitignore差不多,只要在当前目录下新建一个.dockerignore文件,把要忽略的文件和目录写进去就行。
举个例子,一个Node.js项目的.dockerignore文件可以这样写(技术栈:Podman 4.x + Node.js 18):
# 忽略依赖目录,因为构建的时候会自己装
node_modules
# 忽略日志文件
logs
*.log
# 忽略编译后的二进制
dist
build
# 忽略版本控制目录
.git
.gitignore
# 忽略配置文件
.env
.env.*
# 忽略IDE生成的文件
.vscode
.idea
写好之后,构建的时候Podman就不会把这些文件打包进去,构建上下文的大小会从几个G降到几百M甚至几十M,传输时间就会大大缩短。
另外,如果你要构建的文件不在当前目录,也可以指定构建上下文的路径,比如你的Dockerfile在当前目录,要构建的代码在/home/user/project目录,就可以这样构建:
# 指定Dockerfile的路径和构建上下文的路径
podman build -f ./Dockerfile -t test-image /home/user/project
这样就不会把当前目录下的没用文件传进去。
这个方法的优点是简单有效,能快速减少构建上下文的大小;缺点是如果忽略了必要的文件,会导致构建失败,所以写.dockerignore的时候要仔细检查;注意事项是.dockerignore文件要放在构建上下文的根目录,否则不会生效,另外如果用COPY命令复制文件,要确保要复制的文件没有被忽略。
三、进阶加速方法,适合对速度要求高的场景
上面的三种方法适合大部分场景,如果你对速度要求更高,比如每天要构建几十次镜像,或者要构建大项目的镜像,可以试试下面的进阶方法。
3.1 用BuildKit替代默认的构建引擎
Podman默认的构建引擎是Buildah,而BuildKit是Docker推出的一个更高效的构建引擎,支持并行构建、缓存优化等功能,很多人用了之后构建速度提升了30%以上。
Podman从3.0版本开始就支持用BuildKit作为构建引擎,配置起来也很简单。首先要安装BuildKit,不同的发行版安装方法不一样,比如Ubuntu可以用以下命令安装:
# 安装BuildKit
sudo apt install buildkit
然后配置Podman用BuildKit作为构建引擎,编辑Podman的配置文件:
# 新建或编辑Podman的配置文件
vim ~/.config/containers/buildah.conf
在文件里添加以下内容:
# 配置用BuildKit作为构建引擎
[engine]
buildkit = true
配置完之后,构建的时候加上--buildkit参数就能用了:
# 用BuildKit构建镜像
podman build --buildkit -t test-image .
你也可以把BuildKit设为默认的构建引擎,这样每次构建都会自动用,不用加参数。
这个方法的优点是支持并行构建,缓存更高效,适合构建大项目;缺点是配置稍微复杂一点,有些旧版本的Podman不支持;注意事项是安装BuildKit的时候要和Podman的版本兼容,否则会出问题。
3.2 用本地镜像仓库缓存基础镜像
如果你在团队开发,或者经常拉取相同的基础镜像,可以搭建一个本地的镜像仓库,把常用的基础镜像存在本地,这样拉取的时候就不用从国外的仓库拉,直接从本地拉,速度会快很多。
搭建本地镜像仓库可以用Podman官方提供的镜像,非常简单,只需要一个命令就能启动:
# 启动本地镜像仓库,端口是5000
podman run -d -p 5000:5000 --name registry registry:2
启动之后,你可以把常用的基础镜像推到本地仓库,比如把Ubuntu镜像推到本地仓库:
# 先拉取Ubuntu镜像
podman pull ubuntu:22.04
# 给镜像打标签,指向本地仓库
podman tag ubuntu:22.04 localhost:5000/ubuntu:22.04
# 推到本地仓库
podman push localhost:5000/ubuntu:22.04
然后配置Podman优先从本地仓库拉取镜像,编辑/etc/containers/registries.conf文件,把本地仓库放在优先级最高的位置:
unqualified-search-registries = ["localhost:5000", "docker.io", "quay.io"]
这样下次拉取Ubuntu镜像的时候,就会先从本地仓库拉,速度非常快。
这个方法的优点是适合团队开发,所有成员都能共享本地缓存的镜像,减少重复拉取;缺点是需要占用服务器的磁盘空间,还要维护本地仓库;注意事项是本地仓库要定期同步官方的镜像,确保镜像的安全性和时效性,另外如果是跨机器拉取,要把本地仓库的地址改成服务器的IP,不要用localhost。
四、方法的应用场景、优缺点和注意事项总结
上面说的几种方法,各有各的适用场景,大家可以根据自己的情况选择。配置国内镜像加速适合所有开发者,尤其是国内的个人开发者,一劳永逸;优化构建缓存适合经常构建相同项目的开发者,能重复利用缓存;缩小构建上下文适合项目大、文件多的开发者,能快速减少传输时间;用BuildKit适合对速度要求高的开发者,比如CI/CD场景;用本地镜像仓库适合团队开发,能共享缓存。
这些方法的优点都是能有效提升构建速度,缺点也各不相同:配置国内镜像加速可能会遇到镜像源同步不及时的问题;优化构建缓存会占用磁盘空间;缩小构建上下文容易忽略必要的文件;用BuildKit配置稍微复杂;用本地镜像仓库需要维护。
注意事项方面,不管用哪种方法,都要先备份原配置,避免配置错了之后恢复不了;写Dockerfile和.dockerignore的时候要仔细检查,避免遗漏必要的文件;定期清理没用的缓存和镜像,避免占用太多磁盘空间;如果是在生产环境构建镜像,要确保用的镜像源是安全的,避免拉取到恶意镜像。
最后,构建镜像慢的问题,大部分时候都是小问题,只要找对原因,用对方法,就能解决。大家可以先从配置国内镜像加速开始,然后优化构建缓存和缩小构建上下文,这三种方法加起来,一般能把构建速度提升50%以上,甚至几倍。如果还不够快,再试试进阶的方法,总能找到适合自己的解决方案。
Comments