好文推荐
docker 与 podman 的故事:一个方兴未艾,一个异军突起
Podman简介
什么是容器?
容器是软件的可执行单元,它采用通用方式封装了应用程序代码及其库和依赖项,因此可以随时随地运行容器(无论是在桌面、传统 IT 还是云端)。
为此,容器利用一种操作系统 (OS) 虚拟化的形式,进而可以利用操作系统内核的功能(例如 Linux 命名空间和 cgroups、Windows 孤岛和作业对象)来隔离进程,并控制进程可以访问的 CPU、内存和磁盘的数量。
容器小巧轻便、速度快且可移植;与虚拟机不同,容器不需要在每个实例中都包含访客操作系统,只需利用主机操作系统的功能和资源。
简单的说:就是利用像沙箱技术,把主机资源(例如CPU,内存和硬盘等)进行拆分隔离,使程序或服务能够在每一个单独的沙箱内运行而互不干扰。
podman
Podman是RedHat开发的一个用户友好的容器调度器,是一种开源的Linux原生工具,是RedHat 8和CentOS 8中默认的容器引擎, 旨在根据开放容器倡议(Open Container Initiative, OCI)标准 开发、管理和运行容器和Pod。是一款集合了命令集的工具,设计初衷是为了处理容器化进程的不同任务,可以作为一个模块化框架工作。
Podman旨在使用类似于Kubernetes的方法来构建、管理和运行容器。
自RHEL 8起,Red Hat用CRI-O/Podman取代了Docker Daemon。
RedHat 8已经默认安装了podman,其他版本需要手动安装,
[root@node-137 ~]# yum install -y podman
[root@node-137 ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
如果要拉取镜像,一般是从国外镜像仓库拉取,速度会很慢,默认podman从以下仓库中拉取:registry.access.redhat.com
、registry.redhat.io
、docker.io
。下面配置加速器,提高从docker.io
中拉取镜像的速度。
登录阿里云控制台,找到容器镜像服务,单击镜像工具->镜像加速器,找到自己的加速器地址。
修改配置文件
[root@node-137 ~]# egrep -v '^$|^#' /etc/containers/registries.conf
unqualified-search-registries = ["docker.io"]
[[registry]]
prefix = "docker.io"
location = "frz7i079.mirror.aliyuncs.com"
[root@node-137 ~]# podman pull alpine
Trying to pull docker.io/library/alpine...
Getting image source signatures
Copying blob 59bf1c3509f3 done
Copying config c059bfaa84 done
Writing manifest to image destination
Storing signatures
c059bfaa849c4d8e4aecaeb3a10c2d9b3d85f5165c66ad3a4d937758128c4d18
这里的意思是从docker.io
中拉取镜像时,使用加速器frz7i079.mirror.aliyuncs.com
,注意不要加https
,配置好之后不需要重启服务。
网易仓库地址是https://c.163yun.com/hub#/home
,在浏览器登录,然后搜寻镜像即可,下载nginx
,
[root@node-137 ~]# podman pull hub.c.163.com/public/nginx:1.2.1
查看镜像
[root@node-137 ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/alpine latest c059bfaa849c 24 months ago 5.87 MB
hub.c.163.com/public/nginx 1.2.1 2dc68ff797db 7 years ago 178 MB
一般情况下,镜像的命名格式:
服务器IP[或 域名]:端口/分类/镜像名:tag
如果不指定端口则默认为80
,如果不指定tag则默认为latest
,例如,hub.c.163.com/public/nginx:1.2.1
分类也可以不写,如docker.io/nginx:latest
在把镜像上传到仓库时,镜像必须按这种格式命名,因为仓库地址就是镜像前面的IP决定的。如果只是在本地使用镜像,命名可以随意。
如果想给本地已经存在的镜像起一个新的名称,可以用tag
命令,语法,
podman tag 旧镜像名 新镜像名
[root@node-137 ~]# podman tag hub.c.163.com/public/nginx:1.2.1 192.168.17.137/nginx:latest
[root@node-137 ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/alpine latest c059bfaa849c 24 months ago 5.87 MB
hub.c.163.com/public/nginx 1.2.1 2dc68ff797db 7 years ago 178 MB
192.168.17.137/nginx latest 2dc68ff797db 7 years ago 178 MB
可以看到,对某镜像做了标签之后,看似时两个镜像,其实对应的是同一个(类似linux中的硬链接),镜像ID是一样的。删除其中一个镜像是不会删除存储在硬盘上的文件,只有把IMAGE ID
所对应的所有名称全部删除,才会把镜像从硬盘删除。
删除镜像使用rmi
,语法,
podman rmi 镜像名:tag
[root@node-137 ~]# podman rmi localhost/nginx:latest
Untagged: localhost/nginx:latest
虽然所用镜像都是从网络下载的,但是这些镜像的制作过程中都是一点点修改的做出来的。如果我们要查看某个镜像的制作步骤,可以使用history
命令,语法,
podman history [--no-trunc] 镜像名
[root@node-137 ~]# podman history docker.io/library/nginx:latest --no-trunc
ID CREATED CREATED BY SIZE COMMENT
605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85 23 months ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 23 months ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
<missing> 23 months ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 23 months ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entrypoint.sh"] 0B
<missing> 23 months ago /bin/sh -c #(nop) COPY file:09a214a3e07c919af2fb2d7c749ccbc446b8c10eb217366e5a65640ee9edcc25 in /docker-entrypoint.d 1.395kB
<missing> 23 months ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7de297435e32af634f29f7132ed0550d342cad9fd20158258 in /docker-entrypoint.d 666B
<missing> 23 months ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b03c4e6c8c513ae014f691fb05d530257dfffd07035c1b75da in /docker-entrypoint.d 894B
<missing> 23 months ago /bin/sh -c #(nop) COPY file:65504f71f5855ca017fb64d502ce873a31b2e0decd75297a8fb0a287f97acf92 in / 602B
<missing> 23 months ago /bin/sh -c set -x && addgroup --system --gid 101 nginx && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx && apt-get update && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates && NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; found=''; for server in hkp://keyserver.ubuntu.com:80 pgp.mit.edu ; do echo "Fetching GPG key $NGINX_GPGKEY from $server"; apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; done; test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* && dpkgArch="$(dpkg --print-architecture)" && nginxPackages=" nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} " && case "$dpkgArch" in amd64|arm64) echo "deb https://nginx.org/packages/mainline/debian/ bullseye nginx" >> /etc/apt/sources.list.d/nginx.list && apt-get update ;; *) echo "deb-src https://nginx.org/packages/mainline/debian/ bullseye nginx" >> /etc/apt/sources.list.d/nginx.list && tempDir="$(mktemp -d)" && chmod 777 "$tempDir" && savedAptMark="$(apt-mark showmanual)" && apt-get update && apt-get build-dep -y $nginxPackages && ( cd "$tempDir" && DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" apt-get source --compile $nginxPackages ) && apt-mark showmanual | xargs apt-mark auto > /dev/null && { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } && ls -lAFh "$tempDir" && ( cd "$tempDir" && dpkg-scanpackages . > Packages ) && grep '^Package: ' "$tempDir/Packages" && echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list && apt-get -o Acquire::GzipIndexes=false update ;; esac && apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages gettext-base curl && apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list && if [ -n "$tempDir" ]; then apt-get purge -y --auto-remove && rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; fi && ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log && mkdir /docker-entrypoint.d 25.35MB
<missing> 23 months ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye 25.35MB
<missing> 23 months ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.1 25.35MB
<missing> 23 months ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.5 25.35MB
<missing> 23 months ago /bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers 25.35MB
<missing> 23 months ago /bin/sh -c #(nop) CMD ["bash"] 25.35MB
<missing> 23 months ago /bin/sh -c #(nop) ADD file:09675d11695f65c55efdc393ff0cd32f30194cd7d0fbef4631eebfed4414ac97 in / 31.36MB
无法联网的服务器,需要离线导入和导出镜像
导出,
podman save 镜像名 > file.tar
导入,
podman load -i file.tar
[root@node-137 ~]# podman save docker.io/library/alpine:latest > alpine.tar
[root@node-137 ~]# ll
...
-rw-r--r-- 1 root root 5875712 Nov 23 16:08 alpine.tar
...
[root@node-138 ~]# scp node-137:/root/alpine.tar .
alpine.tar 100% 5738KB 44.4MB/s 00:00
[root@node-138 ~]# ll
...
-rw-r--r--. 1 root root 5875712 Nov 23 22:20 alpine.tar
...
[root@node-138 ~]# podman load -i alpine.tar
Getting image source signatures
Copying blob 8d3ac3489996 done
Copying config c059bfaa84 done
Writing manifest to image destination
Storing signatures
Loaded image(s): docker.io/library/alpine:latest
[root@node-138 ~]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/alpine latest c059bfaa849c 24 months ago 5.87 MB
容器就是镜像运行在宿主机上的一个实例。
查看正在运行的容器,
podman ps [-aq]
-a 查看所有容器,包括未运行的
-q 只显示容器ID
podman run -itd 镜像名:tag
-i 可以让用户进行交互
-t 模拟一个终端
-d 后台运行
--restart=always 重启策略,always一直
--name=abc 以指定名称运行容器
--rm 退出容器后,自动删除容器
删除容器语法,
podman rm [-f] 容器ID/容器名
-f 删除正在运行的容器
[root@node-137 ~]# podman rm dd5
dd5de0dae9777e732544aa67fb5561218fa5ef87c434ac5170923462cf81eb41
[root@node-137 ~]# podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
可以在启动容器时,加--rm
选项,当退出容器后,自动删除容器
创建容器时,容器中运行什么进程,都是由镜像中的CMD指定的。如果想自定义容器中运行的进程,可以在创建容器的命令最后指定,
[root@node-137 ~]# podman run -it docker.io/library/alpine:latest /bin/sh
#或
[root@node-137 ~]# podman run -it docker.io/library/alpine:latest sh
在利用一些镜像创建容器时,如果需要传递变量,需要变量使用-e
来指定,可以多次使用-e
来指定多个变量
[root@node-137 ~]# podman run -it --rm -e aa=123 -e bb=234 docker.io/library/alpine:latest /bin/sh
/ # echo $aa
123
/ # echo $bb
234
/ # exit
[root@node-137 ~]# podman run -it --rm -e aa=123 bb=234 docker.io/library/alpine:latest /bin/sh
Error: unable to pull bb=234: error getting default registries to try: invalid reference format
但不要打算使用一个-e
传递多个变量
一般来说,外部主机是不能和本机容器进行通信的,如果希望外部主机能访问容器,需要使用-p
选项将容器端口映射到本机。
语法,
-p N 物理机随机生成一个端口映射到容器端口
-p M:N 把容器指定的N端口映射到物理机的M端口
[root@node-137 ~]# podman run -d --name=web --restart=always -p 80:80 docker.io/library/nginx:latest
8d220ee835f12e2de5dc3fec5fb2e311577541fece40a69df3a2f1a0a12016e2
[root@node-138 ~]# curl node-137
<!DOCTYPE html>
<html>
...
<h1>Welcome to nginx!</h1>
...
</html>
创建MySQL容器
[root@node-137 ~]# podman pull docker.io/library/mariadb:latest
[root@node-137 ~]# podman run -d --name=db --restart=always -e MYSQL_ROOT_PASSWOR=haha001 -e MYSQL_DATABASE=blog docker.io/library/mariadb:latest
4143419fe67707a2388452417770042985bbab6089625458ebd86f5f066f0be7
[root@node-137 ~]# podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4143419fe677 docker.io/library/mysql:latest mysqld 6 seconds ago Up Less than a second ago db
[root@node-137 ~]# podman inspect db|grep -i ipaddr
"SecondaryIPAddresses": null,
"IPAddress": "10.88.0.17",
[root@node-137 ~]# yum install -y mariadb
[root@node-137 ~]# mysql -uroot -phaha001 -h10.88.0.17
...
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| blog |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
MariaDB [(none)]> exit
Bye
语法,
podman exec 容器名 命令
在容器中执行ip a|grep 'inet '
,命令,
[root@node-137 ~]# podman exec db ip a|grep 'inet '
inet 127.0.0.1/8 scope host lo
inet 10.88.0.17/16 brd 10.88.255.255 scope global eth0
如果在容器中运行未安装的命令,则会报错
[root@node-137 ~]# podman exec db ifconfig
Error: exec failed: unable to start container process: exec: "ifconfig": executable file not found in $PATH: OCI runtime command not found error
获取shell控制台
[root@node-137 ~]# podman exec -it db bash
root@3c2fd1899118:/# ip a|grep 'inet '
inet 127.0.0.1/8 scope host lo
inet 10.88.0.17/16 brd 10.88.255.255 scope global eth0
root@3c2fd1899118:/# exit
exit
语法,
podman cp /path/file 容器:/path2 把本地文件拷贝到容器
podman cp 容器:/path2 /path/file 把容器文件拷贝到本地
[root@node-137 ~]# podman exec db ls /opt
[root@node-137 ~]# podman cp /etc/hosts db:/opt/
[root@node-137 ~]# podman exec db ls /opt
hosts
[root@node-137 ~]# podman cp db:/opt/hosts /opt/
[root@node-137 ~]# ls /opt/
... hosts ...
[root@node-137 ~]# diff /opt/hosts /etc/hosts
[root@node-137 ~]# podman stop db
3c2fd18991183ba566378708ecd7247bbc982031b305c77edd02a78731982a51
[root@node-137 ~]# podman start db
db
[root@node-137 ~]# podman restart db
3c2fd18991183ba566378708ecd7247bbc982031b305c77edd02a78731982a51
[root@node-137 ~]# podman top db
USER PID PPID %CPU ELAPSED TTY TIME COMMAND
mysql 1 0 0.000 6.686039701s ? 0s mariadbd
查看容器输出的日志信息
podman log [-f] 容器名
-f 不间断输出
查看容器属性
podman inspect 容器名
当容器创建出来之后,会映射到物理机的某个目录(这个目录叫容器层)中,在容器中写的数据实际都存储在容器层,所以只要容器不被删除,在容器中写的数据就会一直存在。但是一旦删除容器,对应的容器层也会被删除。
如果希望数据能够永久保存,则需要配置数据卷,把容器中的指定目录挂载到物理机的某个目录上,如上图。
在创建容器时,使用-v
选项指定数据卷,语法
-v /dir1 把物理机的一个随机目录映射到容器的/dir1目录中
-v /dir2:/dir1:Z 把物理机指定的目录/dir2映射到容器的/dir1目录中
物理机/dir2
需要提前创建出来,而容器/dir1
如果不存在则会自动创建。这里Z
的意思是把物理机的目录/dir2
的上下文改成container_file_t
[root@node-137 ~]# podman run -itd --name=alpine -v /data docker.io/library/alpine:latest
8d5cc7a4f87e38725eb458940f7de49aba42e5d2c7c705b69c4162688381213f
[root@node-137 ~]# podman inspect alpine |grep -A5 Mounts
"Mounts": [
{
"Type": "volume",
"Name": "a7357fa51d37a9fa34168fbf89d80af78f5f66bb58f02c54cac0ee1d695a1305",
"Source": "/var/lib/containers/storage/volumes/a7357fa51d37a9fa34168fbf89d80af78f5f66bb58f02c54cac0ee1d695a1305/_data",
"Destination": "/data",
[root@node-137 ~]# ls /var/lib/containers/storage/volumes/a7357fa51d37a9fa34168fbf89d80af78f5f66bb58f02c54cac0ee1d695a1305/_data
[root@node-137 ~]# cp /etc/hostname /var/lib/containers/storage/volumes/a7357fa51d37a9fa34168fbf89d80af78f5f66bb58f02c54cac0ee1d695a1305/_data
[root@node-137 ~]# podman exec alpine ls /data
hostname
[root@node-137 ~]# podman exec alpine cat /data/hostname
node-137
[root@node-137 ~]# podman run -itd --name=alpine -v /opt:/data docker.io/library/alpine:latest
fae37449d94ccbcdc8eeb5e6661b8b70794231e6cb4edfba359cc2b76f2e6cbb
[root@node-137 ~]# podman exec alpine ls /data
cni
containerd
...
hosts
...
[root@node-137 ~]# ll /opt/
total 17768
drwxr-xr-x. 3 root root 17 Jul 14 16:38 cni
drwx--x--x. 4 root root 28 Jul 17 14:20 containerd
...
-rw-r--r-- 1 root root 422 Nov 22 18:43 hosts
...
切换用户登录如果出现:
[yurq@node-137 ~]$ podman images
cannot clone: Invalid argument
user namespaces are not enabled in /proc/sys/user/max_user_namespaces
Error: could not get runtime: cannot re-exec process
解决方法:
# centos 7默认关闭了 user namespace,需要手动打开
echo 10000 > /proc/sys/user/max_user_namespaces
grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
echo "user.max_user_namespaces=10000" >> /etc/sysctl.conf
不同用户对镜像和容器的管理都是独立的,所以root的镜像并不能给普通用户使用
[root@node-137 ~]# su - yurq
Last login: Fri Nov 24 13:16:25 CST 2023 from 192.168.17.1 on pts/1
[yurq@node-137 ~]$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
[yurq@node-137 ~]$ exit
logout
[root@node-137 ~]# podman save docker.io/library/alpine:latest > /opt/alpine.tar
[root@node-137 ~]# su - yurq
Last login: Fri Nov 24 13:19:13 CST 2023 on pts/0
[yurq@node-137 ~]$ podman load -i /opt/alpine.tar
Getting image source signatures
Copying blob 8d3ac3489996 done
Copying config c059bfaa84 done
Writing manifest to image destination
Storing signatures
Loaded image(s): docker.io/library/alpine:latest
[yurq@node-137 ~]$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/alpine latest c059bfaa849c 24 months ago 5.87 MB
所以普通用户可以通过重新拉取镜像或者从root导入镜像
以普通用户启动容器
[yurq@node-137 ~]$ podman run -dit --name=web --restart=always -v /home/yurq/web:/web:Z docker.io/library/nginx
f2c9cfc2e3d6aa2a9c6f363de9c9bc346bbd14df3374403a21235fb12107f7a4
现在容器创建好了,但这个容器在系统重启时不会随着系统一起启动。要让容器随着系统一起启动,需要为这个容器创建一个服务。
[root@node-137 ~]# loginctl enable-linger yurq
可以通过loginctl disable-linger yurq
关闭普通用的服务自启动
[yurq@node-137 ~]$ mkdir -p ~/.config/systemd/user;cd ~/.config/systemd/user
[yurq@node-137 user]$ podman generate systemd --name web --files
[yurq@node-137 user]$ ll
total 8
-rw-r--r-- 1 yurq yurq 538 Nov 24 15:25 container-web.service
这里可以加参数
--new
,该参数表示 即使现在把web容器删除,那么重启系统时也会自动创建这个容器,不过笔者这个版本的podman似乎不识别--new
这里的参数--name
可以简写为-n
,--files
可以简写为-f
,--new
可以省略,所以简写后的命令,
[yurq@node-137 user]$ podman generate systemd -n web -f
/home/yurq/.config/systemd/user/container-web.service
--user
选项,[yurq@node-137 user]$ systemctl --user daemon-reload
这里如果报错Failed to get D-Bus connection: No such file or directory
,一般的解决方法是升级systemd,参考
https://blog.csdn.net/counsellor/article/details/128448999
[yurq@node-137 user]$ systemctl --user enable container-web.service
[yurq@node-137 user]$ systemctl --user status container-web.service
[yurq@node-137 user]$ systemctl --user disable container-web.service
重启操作系统,可以发现web容器随系统启动