RHCE9学习指南 第26章 使用podman管理容器

对于初学者来说不太容易理解什么是容器,这里举个例子。想象一下,我们把系统安装在一个U盘中,此系统中安装好了MySQL。然后我们把这个U盘插入一台正在运行的物理机上,这个物理机上并没有安装MySQL,如图26-1所示。
file
图26-1 了解容器和镜像
然后把U盘中的mysqld进程“拽”到物理机上运行。但是这个mysqld进程只能适应U盘中的系统,不一定能适应物理机的系统。所以呢,我们找一个类似气球的东西把mysqld进程在物理机包裹保护起来,这个mysqld进程依然享受U盘中的生态环境(系统),却可以从物理机上吸收CPU和内存作为维持mysqld进程运行的“养分”。

那么,这个类似气球的东西,就是容器,U盘呢就是镜像。
在Linux下安装软件包时经常会遇到各种包依赖,或者有人不会在Linux系统(如Ubuntu、CentOS)中安装软件包。这样以后我们就不需要安装和配置MySQL了,直接把这个“U盘”插到电脑上,然后运行一个容器出来,这样就有MySQL这个服务了。

所谓镜像,就是安装了系统的硬盘文件,这个系统中安装了想要运行的程序,如MySQL、Nginx,并规定好使用这个镜像所生成的容器里面运行什么进程。这里假设有一个安装了MySQL的镜像,如图26-2所示。
file
图26-2 了解容器和镜像

在服务器上有一个MySQL的镜像(即已经安装好了MySQL),然后使用这个镜像生成一个容器。这个容器中只运行一个mysqld进程。容器中的mysqld进程直接从物理机吸收CPU和内存以维持它的正常运行。
之后需要一个什么应用那么就直接拉取什么镜像下来,然后使用这个镜像生成容器。例如,需要对外提供MySQL服务,那么我就拉取一个MySQL镜像,然后生成一个MySQL容器。如果我需要对外提供web服务,那么就拉取一个Nginx镜像,然后生成一个Nginx容器。
一个镜像是可以生成很多个容器的,如图26-3所示。

图26-3了解容器和镜像

26.1 安装及配置podman

前面已经配置了yum源,所以这里直接使用yum install podman -y安装,命令如下。

[root@server ~]# yum install podman -y
正在更新 Subscription Management 软件仓库。
    ...输出...
  podman-2:4.0.2-6.el9_0.x86_64                  
完毕!
[root@server ~]#

查看现在系统中有多少镜像,命令如下。

[root@server ~]# podman images
REPOSITORY  TAG     IMAGE ID  CREATED  SIZE
[root@server ~]# 

这里没有任何输出,就说明现在还没有镜像。
查看系统中有多少容器,命令如下。

[root@server ~]# podman ps
CONTAINER ID  IMAGE   COMMAND  CREATED  STATUS  PORTS   NAMES
[root@server ~]#

没有输出说明当前没有容器。
如果要拉取镜像,一般是从国外网的镜像仓库上拉取的速度会很慢。默认podman从以下仓库拉取镜像registry.access.redhat.com、registry.redhat.io、docker.io。下面配置加速器,提高从docker.io拉取镜像的速度。
登录阿里云控制台,找到容器镜像服务,点击镜像工具--镜像加速器,找到自己的加速器地址,这里使用的是https://frz7i079.mirror.aliyuncs.com。
修改podman的配置文件/etc/containers/registries.conf,修改内容如下。

[root@server ~]# cat /etc/containers/registries.conf
unqualified-search-registries = ["docker.io"]
[[registry]]
prefix = "docker.io"
location = "frz7i079.mirror.aliyuncs.com"
[root@server ~]#

这里意思是从docker.io拉取镜像时使用加速器frz7i079.mirror.aliyuncs.com,注意这里不需要加https,配置好之后不需要重启什么服务。
下面开始拉取docker.io/nginx镜像,命令如下。

[root@server ~]# podman  pull docker.io/nginx
Trying to pull docker.io/library/nginx:latest...
Getting image source signatures
    ...输出... 
Writing manifest to image destination
Storing signatures
ea335eea17ab984571cd4a3bcf90a0413773b559c75ef4cda07d0ce952b00291
[root@server ~]#

拉取MySQL镜像docker.io/mysql,命令如下。

[root@server ~]# podman pull mysql
Trying to pull docker.io/library/mysql:latest...
    ...输出...
[root@server ~]# 

网易仓库地址是https://c.163yun.com/hub#/home,浏览器中打开此界面需要登录,然后所搜所需要的镜像即可,下面从网易仓库拉取CentOS镜像,命令如下。

[root@server ~]# podman pull hub.c.163.com/library/centos
Trying to pull hub.c.163.com/library/centos:latest...
    ...输出...
Writing manifest to image destination
Storing signatures
328edcd84f1bbf868bc88e4ae37afe421ef19be71890f59b4b2d8ba48414b84d
[root@server ~]#

26.2 镜像管理

前面讲了要是想创建容器必须要有镜像,本节主要讲解镜像的管理。

26.2.1 镜像的命名

一般情况下,镜像的命名格式如下。

服务器IP:端口/分类/镜像名:tag  

如果不指名端口默认为80,如果不指明tag默认为latest。
例如,192.168.26.101:5000/cka/centos:v2。
再如,hub.c.163.com/library/mysql:latest。
分类也是可以不写的,如docker.io/nginx:latest。
在把镜像上传(push)到仓库时,镜像必须要按这种格式命名,因为仓库地址就是由镜像前面的IP决定的。如果只是在本机使用镜像,命名是可以随意的。
查看当前系统有多少镜像,命令如下。

[root@server ~]# podman images
REPOSITORY                TAG     IMAGE ID       CREATED      SIZE
docker.io/library/nginx        latest    ea335eea17ab  3 days ago   146 MB
docker.io/library/mysql           latest    b05128b000dd  3 days ago   521 MB
hub.c.163.com/library/centos  latest    328edcd84f1b   4 years ago  200 MB
[root@server ~]# 

26.2.2 对镜像重新做标签

如果想给本地已经存在的镜像起一个新的名称,可以用tag来做,语法如下。

podman tag 旧的镜像名  新的镜像名

tag之后,新的镜像名和旧的镜像名是同时存在的。
步骤1:给镜像做新标签,命令如下。

[root@server ~]# podman tag docker.io/library/mysql 192.168.26.101/rhce/mysql:v2
[root@server ~]#

这里是为docker.io/library/mysql重新做个tag,名称为192.168.26.101/rhce/mysql标签为v2,这样命名的目的是让大家看到命名的随意性,建议tag可以设置为版本号、日期等有意义的字符。
步骤2:再次查看镜像,命令如下。

[root@server ~]# podman images
REPOSITORY               TAG     IMAGE ID      CREATED      SIZE
docker.io/library/nginx       latest   ea335eea17ab   3 days ago   146 MB
docker.io/library/mysql       latest   b05128b000dd  3 days ago   521 MB
192.168.26.101/rhce/mysql    v2     b05128b000dd  3 days ago   521 MB
hub.c.163.com/library/centos  latest   328edcd84f1b   4 years ago  200 MB
[root@server ~]#

可以看到,对某镜像做了标签之后,看似是两个镜像,其实对应的是同一个(这类似于linux中硬链接的概念,一个文件两个名称而已),镜像ID都是一样的。当删除掉其中一个镜像是不会删除存储在硬盘上的文件的,只有把image id所对应的所有名称全部删除,才会从硬盘上删除。

26.2.3 删除镜像

如果要删除镜像,需要按如下语法来删除。

语法:podman rmi 镜像名:tag

例如,下面要把docker.io/library/mysql:latest删除。
步骤1:删除镜像,命令如下。

[root@server ~]# podman rmi docker.io/library/mysql:latest 
Untagged: docker.io/library/mysql:latest
[root@server ~]#

这里可以看到,只是简单的一个untagged操作,并没有任何的deleted操作。
步骤2:查看镜像,命令如下。

[root@server ~]# podman images
REPOSITORY                TAG     IMAGE ID        CREATED      SIZE
docker.io/library/nginx       latest      ea335eea17ab  3 days ago   146 MB
192.168.26.101/rhce/mysql     v2      b05128b000dd  3 days ago   521 MB
hub.c.163.com/library/centos  latest     328edcd84f1b   4 years ago  200 MB
[root@server ~]#

可以看到,b05128b000dd 对应的本地文件依然是存在的,因为它(id为b05128b000dd)有两个名称,现在只是删除了一个名称而已,所以在硬盘上仍然是存在的。
只有删除最后一个名称,本地文件才会被删除。
步骤3:删除镜像,命令如下。

[root@server ~]# podman rmi 192.168.26.101/rhce/mysql:v2 
Untagged: 192.168.26.101/rhce/mysql:v2
Deleted: b05128b000ddbafb0a0d2713086c6a1cc23280dee3529d37f03c98c97c8cf1ed
[root@server ~]#

26.2.4 查看镜像的层结构

虽然我们所用的镜像都是从网上下载下来的。不过这些镜像在制作过程中,都是一点点的修改的,一步步地做出来的。如果我们要看某镜像这些步骤,可以用podman history命令。语法如下。

podman history 镜像名

查看镜像的结构,命令如下。

[root@server ~]# podman history hub.c.163.com/library/centos
ID            CREATED    CREATED BY                  SIZE      COMMENT
328edcd84f1b  4 years ago  /bin/sh -c #(nop)  CMD ["/bin/bash"]           0 B     
<missing>     4 years ago  /bin/sh -c #(nop)  LABEL name=CentOS Base ...  0 B     
<missing>     4 years ago  /bin/sh -c #(nop)  ADD file:63492ba809361c5...  200 MB  
[root@server ~]# 

最上层的CMD,定义的是当使用这个镜像生成的容器中运行的进程为/bin/bash。

26.2.5 导出和导入镜像

对于一些服务器是没法连接到互联网的,所以没法从互联网上下载镜像。在还没有私有仓库的情况下,如何把现有的镜像传输到其他机器上呢?这里就需要把本地已经pull下来的镜像导出为一个本地文件,这样就可以很容器的传输到其他机器。导出镜像的语法如下。

podman save 镜像名 > file.tar

步骤1:把docker.io/nginx:latest导出为nginx.tar,命令如下。

[root@server ~]# podman save docker.io/library/nginx > nginx.tar
    ...输出...
Storing signatures
[root@server ~]#

删除Nginx这个镜像,命令如下。

[root@server ~]# podman rmi docker.io/library/nginx
    ...输出...
[root@server ~]# podman images
REPOSITORY                    TAG     IMAGE ID      CREATED      SIZE
hub.c.163.com/library/centos  latest  328edcd84f1b  4 years ago  200 MB
[root@server ~]#

既然上面已经把镜像导出为一个文件了,那么需要把这个文件导入,语法如下。

 podman load -i file.tar

步骤2:把nginx.tar导入为镜像,命令如下。

[root@server ~]# podman load -i nginx.tar 
Getting image source signatures
    ...输出...
Loaded image(s): docker.io/library/nginx:latest
[root@server ~]# 

查看现有镜像,命令如下。

[root@server ~]# podman images
REPOSITORY                    TAG     IMAGE ID      CREATED      SIZE
docker.io/library/nginx       latest  ea335eea17ab  3 days ago   146 MB
hub.c.163.com/library/centos  latest  328edcd84f1b  4 years ago  200 MB
[root@server ~]# 

26.3 创建容器

容器就是镜像在宿主机运行的一个实例,大家脑补一下把容器理解为一个气球,气球中运行了一个进程。这个进程透过气球吸收物理机的内存和CPU资源。
查看当前有多少正在运行的容器,命令如下。

[root@server ~]# podman ps
CONTAINER ID  IMAGE   COMMAND  CREATED  STATUS  PORTS   NAMES
[root@server ~]#

这个命令显示的仅仅是正在运行的容器,如果要查看不管运行还是不运行的容器需要加-a选项 podman ps -a。

26.3.1 创建一个简单的容器

运行一个最简单的容器,命令如下。

[root@server ~]# podman run hub.c.163.com/library/centos
[root@server ~]# podman ps 
CONTAINER ID  IMAGE   COMMAND  CREATED  STATUS  PORTS   NAMES
[root@server ~]# podman ps -a
CONTAINER ID  IMAGE     COMMAND    CREATED STATUS     PORTS   NAMES
455391d81738  hub.c.163.com/library/centos  /bin/bash  18 seconds ago  Exited (0) 18 seconds ago          kind_elgamal
[root@server ~]# 

这里可以看到,创建出来了一个容器,容器的ID为455391d81738,容器名是随机产生的名称为kind_elgamal ,所使用的镜像是hub.c.163.com/library/centos,容器中运行的进程为/bin/bash(也就是镜像中CMD指定的)。
podman ps看不到,podman ps -a能看到,且状态为Exited,说明容器是关闭状态。容器运行一瞬间就关闭了,为什么?

26.3.2 容器的生命期

把容器理解为人的肉体,里面运行的进程理解为人的灵魂。如果人的灵魂宕机了,则肉体也就宕机了,只有灵魂正常运行,肉体才能正常运行,如图26-4所示。
file
图26-4 容器和进程之间的关系
同理,只有容器中的进程正常运行,容器才能正常运行,容器中的进程宕机了,则容器也就宕机了。因为没有终端的存在,/bin/bash就像执行ls命令一样一下就执行完了,所以容器生命期也就到期了。
如果把这个bash附着到一个终端上,如果这个终端一直存在,则bash就一直存在,那么是不是容器就能一直存活了呢?
删除容器的语法如下。

podman rm 容器ID/容器名  

如果删除正在运行的容器可以使用-f选项。

podman rm -f 容器ID/容器名 

步骤1:删除刚才的容器,命令如下。

[root@server ~]# podman rm 455391d81738
455391d8173855b53b3bbc40135671e2af696201e9c77b3a31cf5d02d041a6b1
[root@server ~]#

重新创建新的容器,加上-i -t 选项,可以写作-it或-i -t。

(1)-t:模拟一个终端,即tty。
(2)-i:可以让用户进行交互,否则用户看到一个提示符之后就卡主不动了。

步骤2:创建一个容器,命令如下。

[root@server ~]# podman run -it hub.c.163.com/library/centos
[root@0ea7e0a9dbee /]# 
[root@0ea7e0a9dbee /]# exit
exit
[root@server ~]#

创建出来容器之后自动进入容器中了,可以通过exit退出容器,命令如下。

[root@server ~]# podman ps -q  #-q选项可以只显示容器id,不会显示太多信息
[root@server ~]# podman ps -a -q
0ea7e0a9dbee
[root@server ~]# 

但是,一旦退出容器,容器就不再运行了。
步骤3:删除此容器,命令如下。

[root@server ~]# podman rm -f 0ea7e0a9dbee
0ea7e0a9dbeed17603722ce480340ce8621e64735f3f2e10090d5b7bf2d7aaa9
[root@server ~]#
[root@server ~]# podman ps -q
[root@server ~]#

如果希望创建好容器之后不自动进入容器,可以加上-d选项。
步骤4:再次创建一个容器,命令如下。

[root@server ~]# podman run  -dit  hub.c.163.com/library/centos
24d1e502b1efebeb8992a074da984c1123d86c5912f086ce431c23cd35460422
[root@server ~]# podman ps -q
24d1e502b1ef
[root@server ~]# 

因为加了-d选项,所以创建好容器之后并没有自动进入容器中。进入此容器中:

[root@server ~]# podman attach 24d1e502b1ef
[root@24d1e502b1ef /]# exit
exit

[root@server ~]# podman ps -q
[root@server ~]# podman ps -a -q 
24d1e502b1ef
[root@server ~]#

可以看到,只要退出来容器就会自动关闭。
步骤5:删除此容器,命令如下。

[root@server ~]# podman  rm 24d1e502b1ef
24d1e502b1efebeb8992a074da984c1123d86c5912f086ce431c23cd35460422
[root@server ~]# 

在运行容器时加上--restart=always选项可以解决退出容器自动关闭的问题。
步骤6:创建容器,增加--restart=always选项,命令如下。

[root@server ~]# podman run  -dit --restart=always  hub.c.163.com/library/centos
03250f9a5a99372a7078d4d0ca15efc3a82eec91317d9e2e9d471d0e1403a397
[root@server ~]# 

进入容器并退出,命令如下。

[root@server ~]# podman ps -q
03250f9a5a99
[root@server ~]# podman attach 03250f9a5a99
[root@03250f9a5a99 /]# exit
exit
[root@server ~]# podman ps -q
03250f9a5a99
[root@server ~]#

可以看到,容器依然是存活的。
步骤7:删除此容器,因为容器是运行的所以需要加上-f选项,命令如下。

[root@server ~]# podman rm -f 03250f9a5a99
03250f9a5a99372a7078d4d0ca15efc3a82eec91317d9e2e9d471d0e1403a397
[root@server ~]#

每次删除容器时,都要使用容器ID的方式比较麻烦,在创建容器时可以使用--name指定容器名。
步骤8:创建容器,使用--name指定容器的名称。

[root@server ~]# podman run -dit --restart=always --name=c1 hub.c.163.com/library/centos
8a88d9f952e4467098e2fdefbde0f0a5a63b2ad5115bad15461bc41f1fb79e4a
[root@server ~]#

这样容器的名称为c1,以后管理起来比较方便,如切换到容器,然后退出,命令如下。

[root@server ~]# podman attach c1
[root@8a88d9f952e4 /]#   
[root@8a88d9f952e4 /]# exit
exit

[root@server ~]#

步骤9:删除此容器,命令如下。

[root@server ~]# podman rm -f c1
8a88d9f952e4467098e2fdefbde0f0a5a63b2ad5115bad15461bc41f1fb79e4a
[root@server ~]# podman ps -a -q
[root@server ~]#

26.3.3 创建临时容器

如果要临时创建一个测试容器,又怕用完忘记删除它,可以加上--rm选项。
创建临时容器,命令如下。

[root@server ~]# podman run -it --name=c1 --rm hub.c.163.com/library/centos
[root@6b603cee057a /]# exit
exit
[root@server ~]# 

创建容器时加了--rm,然后退出容器之后容器会会被自动删除。

[root@server ~]# podman ps -a -q
[root@server ~]#

可以看到,此容器自动被删除了,注意--rm和--restart=always不可以同时使用。

26.3.4 指定容器中运行的命令

以上创建容器时,容器中运行的是什么进程,都是由镜像里CMD里指定的,如果想自定义容器中运行的进程,可以在创建容器的命令最后面指定,如下所示。

[root@server ~]# podman run -it --name=c1 --rm hub.c.163.com/library/centos sh
sh-4.2# 
sh-4.2# exit
exit
[root@server ~]#

这里就是以sh的方式运行,而不是以bash运行。

26.3.5 创建容器时使用变量

在利用一些镜像创建容器时需要传递变量,例如,使用MySQL的镜像、wordpress的镜像创建容器时都需要通过变量来指定一些必备的信息。需要变量用-e来指定,可以多次使用-e来指定多个变量。
创建一个容器c1,里面传递两个变量,命令如下。

[root@server ~]# podman run -it --name=c1 --rm -e aa=123 -e bb=456 hub.c.163.com/library/centos
[root@1bda626849e9 /]# echo $aa
123
[root@1bda626849e9 /]# echo $bb
456
[root@1bda626849e9 /]# exit
exit
[root@server ~]#

在创建容器时,通过-e指定了2个变量aa和bb,然后进入容器之后可以看到具有这两个变量。

26.3.6 把容器端口映射到物理机

外部主机(即本机之外的其他主机)是不能和容器进行通信的,如果希望外部主机能访问到容器的内容,就需要使用-p将容器的端口映射到物理机上,以后访问物理机对应的端口就可以访问到容器了,如图26-5所示。
file
图26-5 把容器的端口映射到宿主机上
语法:

-p N:物理机随机生成一个端口映射到容器的端口N上。
-p M:N:把容器的端口N映射到物理机指定的端口M上。

步骤1:创建一个容器名称为web,把容器端口80映射到物理机一个随机端口,命令如下。

[root@server ~]# podman run -d --name=web --restart=always -p 80 docker.io/nginx
2cd7bcf4ed6aa676ff9699fcb079b903ba983c9717c960a32c7337fd1731f6b6
[root@server ~]#

这里把容器web的80端口映射到物理机的随机端口,这个端口号可以通过如下命令查询。

[root@server ~]# podman ps
CONTAINER ID  IMAGE   COMMAND      CREATED     STATUS   PORTS   NAMES
2cd7bcf4ed6a  docker.io/nginx  nginx -g daemon o...  19 seconds ago  Up 20 seconds ago  0.0.0.0:33513->80/tcp  web
[root@server ~]#

可以看到,映射到物理机的33513上了,访问物理机的32770即可访问到web容器,结果如图26-6所示。
file
图26-6 访问物理机端口33513
删除此容器,命令如下。

[root@server ~]# podman rm -f web
2cd7bcf4ed6aa676ff9699fcb079b903ba983c9717c960a32c7337fd1731f6b6
[root@server ~]#

步骤2:如果想映射到物理机指定的端口,命令如下。

[root@server ~]# podman run -d --name=web --restart=always -p 88:80 docker.io/nginx
ddd179f577bca4602b681ed11fd18bdb7973cca94b5839097a1c5e09785f4685
[root@server ~]#

此处把容器的端口80映射到物理机的88端口(你可以自己指定端口,如80),那么访问物理机的端口80即可访问到web容器的端口80,结果如图26-7所示。
file
图26-7 访问物理机端口88

删除此容器,命令如下。

[root@server ~]# podman rm -f web
ddd179f577bca4602b681ed11fd18bdb7973cca94b5839097a1c5e09785f4685
[root@server ~]#

26.4 实战练习——创建MySQL的容器

创建MySQL容器时不要使用从阿里云或docker官方仓库下载的镜像,请拉取镜像hub.c.163.com/library/mysql。

在使用MySQL镜像时至少需要指定一个变量MYSQL_ROOT_PASSWORD来指定root密码,其他变量如MYSQL_USER、MYSQL_PASSWORD、MYSQL_DATABASE这些都是可选的。

[root@server ~]# podman history hub.c.163.com/library/mysql
ID             CREATED      CREATED BY                  SIZE  COMMENT
9e64176cd8a2  4 years ago  /bin/sh -c #(nop)  CMD ["mysqld"]    0 B   
    ...输出...                   
[root@server ~]#

可以看到,使用MySQL镜像创建出来的容器中运行的是mysqld。
步骤1:创建容器,命令如下。

[root@server ~]# podman run -d --name=db --restart=always  -e MYSQL_ROOT_PASSWORD=haha001 -e MYSQL_DATABASE=blog hub.c.163.com/library/mysql
bc663f50355605df6e966a773ef24aed642ad60c7de600809d27ba896e2c2c1e
[root@server ~]# 

这里使用MYSQL_ROOT_PASSWORD指定了MySQL root密码为haha001,通过MYSQL_DATABASE在容器中创建一个数据库名称为blog。

步骤2:做连接测试。
查看db容器的IP,命令如下。

[root@server ~]# podman inspect db | grep -i ipaddr
            "IPAddress": "10.88.0.16",
                    "IPAddress": "10.88.0.16",
[root@server ~]#

在宿主机上用yum安装mariadb客户端 (命令是yum -y install mariadb),然后连接容器,命令如下。

[root@server ~]# mysql -uroot -phaha001 -h10.88.0.16
    ...输出...
MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| blog               |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.002 sec)

MySQL [(none)]> exit
Bye
[root@server ~]#

可以看到,用密码haha001能正确的连接到容器中,并且页创建了一个名称叫作blog的库。

26.5 管理容器的命令

容器如同一台没有显示器的电脑,如何查看容器中的东西呢,又如何在容器中执行命令呢?这里可以利用podman exec来实现,如图26-8所示。
file
图26-8 在容器中执行命令

26.5.1 在容器中执行指定的命令

语法如下所示。

podman exec 容器名 命令。

步骤1:在容器db中执行 ip | grep 'inet '命令,命令如下。

[root@server ~]# podman exec db ip a | grep 'inet '
    inet 127.0.0.1/8 scope host lo
    inet 10.88.0.16/16 brd 10.88.255.255 scope global eth0
[root@server ~]# 

在容器db中执行 ip a | grep ‘inet ’
如果容器中没有要执行的命令,就会出现报错如下。

[root@server ~]# podman exec db ifconfig
Error: exec failed: container_linux.go:370: starting container process caused: exec: "ifconfig": executable file not found in $PATH: OCI not found
[root@server ~]#

如果想获取shell控制台,需要加上-it选项。
步骤2:获取容器中bash控制台,命令如下。

[root@server ~]# podman exec -it db bash
root@d1c70b97a93d:/# exit
exit
[root@server ~]# 

注意:有的镜像中不存在bash,可以使用sh替代。

26.5.2 物理机和容器互相拷贝文件

有时我们需要让宿主机和容器之间互相拷贝一些文件,拷贝文件的语法如下。

podman cp /path/file   容器:/path2  
将物理机中的/path/file拷贝到容器的/path2中
podman cp  容器:/path2/file /path/ 将容器中的/path2/file拷贝到物理机的/path2中

步骤1:把物理机的/etc/hosts拷贝到容器的/opt中,命令如下。

[root@server ~]# podman exec db ls /opt
[root@server ~]# podman cp /etc/hosts db:/opt
[root@server ~]# podman exec db ls /opt
hosts
[root@server ~]#

步骤2:把容器的/etc/passwd拷贝到物理机的/opt中,命令如下。

[root@server ~]# rm -rf /opt/*
[root@server ~]# podman cp db:/etc/passwd /opt
[root@server ~]# ls /opt/
passwd
[root@server ~]#

26.5.3 停止,启动,重启容器

一般情况下在操作系统中重启某个服务,可以通过systemctl restart 服务名来重启,容器中一般是没法使用systemctl命令的。如果要重启容器中的程序,直接重启容器就可以了。下面演示如何启动、关闭、重启容器。

步骤1:关闭、启动、重启容器,命令如下。

[root@server ~]# podman stop db
d1c70b97a93d7af8a048905b72362067843cf5872b337fa3f7ad0efcf101bd8a
[root@server ~]# 
[root@server ~]# podman start db
db
[root@server ~]# podman restart db
d1c70b97a93d7af8a048905b72362067843cf5872b337fa3f7ad0efcf101bd8a
[root@server ~]# 

步骤2:查看容器中运行的进程。
语法 podman top 容器名,这个类似于任务管理器,可以查看到容器中正在运行的进程,命令如下。

[root@server ~]# podman top db
USER    PID   PPID   %CPU    ELAPSED       TTY   TIME   COMMAND
mysql   1     0      0.000   27.013893711s   ?      0s     mysqld 
[root@server ~]#

26.5.4 查看容器中的输出

当容器没法正常运行时,需要查看容器中的输出来进行排错。如果要看查看容器中的日志信息可以通过如下命令进行查看。

    podman logs 容器名

如果想不间断的查看输出可以使用如下命令。

 docker logs -f 容器名

步骤1:查看容器日志输出,命令如下。

[root@server ~]# podman logs db
Initializing database
    ...输出...
[root@server ~]# 

如果要查看容器的属性可以通过podman inspect 容器名。
步骤2:查看容器db的属性,命令如下。
[root@server ~]# podman inspect db
[
{
"Id": "d1c70b97a93d7af8a048905b7236
...输出...
}
]
[root@server ~]#
在这个输出中,可以查看到容器的各种信息,如数据卷、网络信息等。

26.5.5 数据卷的使用

当创建容器创建出来之后,容器会映射到物理机的某个目录(这个目录叫作容器层),在容器中写的东西实际都存储在容器层,所以只要容器不删除,在容器中写的数据就会一直存在。但是一旦删除容器,对应的容器层也会被删除。

如果希望数据能永久保存,则需要配置数据卷,把容器中指定目录挂载到物理机某目录,如图27-9所示。
file
图26-9 数据卷
这里把容器中目录aa挂载物理机的bb目录,当往容器目录aa中写数据时,实际上是往物理机的目录bb中写的。这样即使删除了容器,则物理机目录bb中的数据仍然是存在的,这样就实现了数据的永久保留(除非手动删除)。

在创建容器时,用-v指定数据卷,用法如下。

    -v /dir1         在物理机随机一个目录映射到容器的/dir1目录中
    -v /dir2:/dir1:Z    在物理机中指定目录/dir2映射到容器的/dir1目录中

记住,冒号左边的/dir2是物理机的目录,此目录需要提前创建出来,右边是容器中的目录,容器中的目录/dir1如果不存在会自动创建。这里大写Z的意思是把宿主机上的目录/dir2的上下文改为container_file_t。

步骤1:创建容器c1,物理机的一个随机目录映射到容器的/data目录,命令如下。

[root@server ~]# podman run -dit --name=c1 --restart=always -v /data  hub.c.163.com/library/centos
f2eefb6253c53d6909a144af4ef0fddc4ba13562a2acba7e7da196b678397e4f
[root@server ~]#

在此命令中-v后面只指定了一个目录/data/,指的是在容器中创建/data,挂载物理机中随机一个目录。
步骤2:查看对应物理机是哪个目录,命令如下。

[root@server ~]# podman inspect c1 | grep -A5 Mounts
        "Mounts": [
            {
                "Type": "volume",
                "Name": "d18f9d35971c7e11dfba9423058812a22e0da5b176ee2f0ce9649eeecf995463",
                "Source": "/var/lib/containers/storage/volumes/d18f9d35971c7e11dfba9423058812a22e0da5b176ee2f0ce9649eeecf995463/_data",
                "Destination": "/data",
[root@server ~]#

上面有两个参数,其中Destination指的是容器中的目录,Source指的是物理机对应的目录。得到的结论就是容器中的目录/data对应宿主机的/var/lib/containers/storage/volumes/d18f9d35971c7e11dfba9423058812a22e0da5b176ee2f0ce9649eeecf995463/_data。
先查看容器c1的目录/data中的数据和宿主机对应目录的数据,命令如下。

[root@server ~]# podman exec c1 ls /data
[root@server ~]# ls /var/lib/containers/storage/volumes/d18f9d35971c7e11dfba9423058812a22e0da5b176ee2f0ce9649eeecf995463/_data
[root@server ~]#

可以看到,目录是空的,如下所示。

[root@server ~]# podman cp /etc/hosts c1:/data
[root@server ~]# podman exec c1 ls /data
hosts
[root@server ~]# ls /var/lib/containers/storage/volumes/d18f9d35971c7e11dfba9423058812a22e0da5b176ee2f0ce9649eeecf995463/_data
hosts
[root@server ~]#

往容器中拷贝数据,宿主机了也会这个数据了。
步骤3:删除此容器,命令如下。

[root@server ~]# podman rm -f c1c1
[root@server ~]#

如果想在物理机中也指定目录而非是随机目录,则用法为 -v /xx:/data,此处冒号前面是物理机的目录,冒号后面是容器中的目录,这里要提前在宿主机上把目录/xx创建出来。
步骤4:创建容器c1,把物理机的目录/xx映射到容器的/data目录中,命令如下。

[root@server ~]# mkdir /xx
[root@server ~]# podman run -dit --name=c1 --restart=always -v /xx:/data:Z  hub.c.163.com/library/centos
d0c815a7750a0d3837d4632b4fdd96876f502437316b5f45c134dc6c350c533b
[root@server ~]#

这里大写Z的意思是把宿主机目录/xx的上下文改成container_file_t。

[root@server ~]# ls -dZ /xx
system_u:object_r:container_file_t:s0:c714,c979 /xx
[root@server ~]#

查看此容器属性,命令如下。

[root@server ~]# podman inspect c1 | grep -A5 Mounts
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/xx",
                "Destination": "/data",
                "Driver": "",
[root@server ~]#

步骤5:拷贝一些测试文件过去观察一下,命令如下。

[root@server ~]# podman exec c1 ls /data
[root@server ~]# ls /xx
[root@server ~]#
[root@server ~]# podman cp /etc/hosts c1:/data #往容器的/data中拷贝一个文件
[root@server ~]# podman exec c1 ls /data
hosts
[root@server ~]# ls /xx/ #对应的,物理机的/xx也有相关的数据
hosts
[root@server ~]#

步骤6:删除此容器,命令如下。

[root@server ~]# podman rm -f c1
d0c815a7750a0d3837d4632b4fdd96876f502437316b5f45c134dc6c350c533b
[root@server ~]# 

在重启系统后,所创建的容器并不会随着系统自动运行,可把容器创建为一个服务,然后设置这个服务开机自动启动,那么这个容器也就可以实现开机自动启动了。
下面用lduan用户创建一个容器,然后实现开机自动启动。

26.6 使用普通用户对容器进行管理

使用lduan用户ssh到server,切记这里不能通过其他用户su到lduan用户
不同用户对镜像和容器的管理都是独立的,所以root拉取的镜像并不能给lduan用户使用。

[lduan@server ~]$ podman images
REPOSITORY  TAG     IMAGE ID  CREATED  SIZE
[lduan@server ~]$

这里可以看到,用lduan用户查询时是没有任何镜像的,所以先拉取Nginx镜像,命令如下。

[lduan@server ~]$ podman pull docker.io/library/nginx
Trying to pull docker.io/library/nginx:latest...
    ...输出...
ea335eea17ab984571cd4a3bcf90a0413773b559c75ef4cda07d0ce952b00291
[lduan@server ~]$

查看现有镜像,命令如下。

[lduan@server ~]$ podman images
REPOSITORY               TAG     IMAGE ID      CREATED     SIZE
docker.io/library/nginx  latest  ea335eea17ab  3 days ago  146 MB
[lduan@server ~]$

为了使用数据卷,先用root用户创建一个目录/yy并把所有者和所属组改为lduan,命令如下。

[root@server ~]# mkdir /yy
[root@server ~]# chown lduan.lduan /yy
[root@server ~]#

然后lduan用户开始创建一个名称叫作web的容器,把宿主机的/yy挂载到容器的/data目录中,命令如下。

[lduan@server ~]$ podman run -dit --name=web --restart=always -v /yy:/data:Z  docker.io/library/nginx:latest
efa9b18046c8d4680f7201bfaa44f2a8ff199f311af3cbfe2ed8be0f3b072a3d
[lduan@server ~]$

现在容器创建好了,但是这个容器在系统重启时并不会随着系统一起启动,所以下面设置允许开机自动运行容器。要让容器跟着系统一起启动,需要为这个容器创建一个服务。

首先设置lduan用户创建的服务在系统启动时能自动启动,命令如下。

[lduan@server ~]$ loginctl enable-linger lduan
[lduan@server ~]$

如果这里没有开启,或者通过 loginctl disable-linger lduan关闭了,那么系统启动之后lduan用户所创建的服务是不会自动启动的,只有lduan用户ssh或通过控制台登录之后,服务才会启动起来。

这里设置了loginctl enable-linger lduan,当系统启动之后lduan用户即使没有通过ssh或控制台登录,lduan用户创建的服务也会自动启动。是开启或还是关闭可以通过loginctl show-user lduan | grep Linger进行查看。

因为要为容器创建出来一个服务,所以先创建存储服务文件的目录,命令如下。

[lduan@server ~]$ mkdir -p ~/.config/systemd/user ; cd  ~/.config/systemd/user
[lduan@server user]$ ls
[lduan@server user]$

为容器web生成一个服务文件,命令如下。

[lduan@server user]$ podman generate systemd --name web --files --new 
/home/lduan/.config/systemd/user/container-web.service
[lduan@server user]$ ls
container-web.service
[lduan@server user]$ 

这里--new的意思是,即使现在把容器web删除掉,那么重启系统时也会自动创建这个容器。
这里--name可简写为-n,--files可简写为-f,--new可以省略,所以整个命令可以简写为如下所示。

podman generate systemd -n web -f 

重新加载这个服务文件,这里要加上--user选项,命令如下。

[lduan@server user]$ systemctl --user daemon-reload

设置这个服务开机自动启动,命令如下。

[lduan@server user]$ systemctl --user enable container-web.service
    ...输出...
[lduan@server user]$ ls
container-web.service  default.target.wants  multi-user.target.wants
[lduan@server user]$ 

然后重启操作系统然后验证,命令如下。

[lduan@server ~]$ podman ps
CONTAINER ID  IMAGE                           COMMAND               CREATED         STATUS             PORTS   NAMES
b918c3e9638c  docker.io/library/nginx:latest  nginx -g daemon o...  17 seconds ago  Up 16 seconds ago          web
[lduan@server ~]$

等启动起来之后发现web容器跟着系统起来了。

2.6.7 自定义镜像

前面所使用的镜像都是我们从网上下载下来的,有的镜像并不能满足我们的需求,比如CentOS的镜像里就没有ifconfig命令,比如下面的例子。
先拉取镜像hub.c.163.com/library/centos

[lduan@server ~]$ podman pull hub.c.163.com/library/centos

使用此镜像创建一个临时容器。

[lduan@server ~]$ podman  run --rm -it hub.c.163.com/library/centos:latest 
[root@43b1327696ab /]# 
[root@43b1327696ab /]# ifconfig eth0
bash: ifconfig: command not found
[root@43b1327696ab /]#
[root@43b1327696ab /]# exit
exit
[lduan@server2 ~]$

这里就能看到镜像里连最基本的ifconfig都没有,所以很多时候我们需要根据自己的需要来自定义镜像。

自定义镜像的过程并非从零到有的过程,而是在已经存在镜像的基础上进行修改,这个已经存在的镜像我们称之为“基镜像”。

要自定义镜像,就需要写Containerfile文件了,如果文件名不是Containerfile,那么编译镜像的时候需要使用-f来指定文件名,如图26-10所示。
file
图26-10 用Containerfile构建镜像的流程
构建镜像的本质就是,先利用基镜像生成一个临时容器,然后在这个临时容器里执行Containerfile里指定的命令,等做完所有的操作之后,会把这个临时容器导出为一个新的镜像。最后把这个临时容器删除。

关键就是如何写Containerfile,Containerfile的格式及常用命令如下。

(1)FROM:指定基镜像。
(2)MAINTAINER:维护者的信息。
(3)RUN:想在临时容器里执行的操作系统命令。
(4)ADD file /path/:把物理机里file拷贝到镜像的指定目录/path。
(5)COPY file /path/:把物理机里file拷贝到镜像的指定目录/path。
(6)ENV:指定变量。
(7)USER:指定容器内部以哪个用户运行进程。
(8)VOLUME:指定数据卷。
(9)EXPOSE:指定镜像容器所使用的端口,这个只是一个标记。
(10)CMD:指定镜像创建出来的容器运行什么进程。

练习1:创建可以执行ifconfig的CentOS镜像。

Containerfile的内容如下。

[lduan@server ~]$ cat Containerfile
FROM hub.c.163.com/library/centos
MAINTAINER duan
RUN yum install net-tools -y

CMD ["/bin/bash"]
[lduan@server ~]$

这个文件里指明了基于hub.c.163.com/library/centos这个镜像自定义新的镜像,在新的镜像里安装net-tools工具包。
在临时容器执行系统命令的时候都要以RUN开头,构建语法如下。

podman  build -t新镜像名:tag 目录 -f Containerfile文件名

这里的“目录”的意思是,后面讲往新镜像里拷贝文件时可以用ADD或COPY,这个"目录"是要拷贝的文件所在目录,如果写".",表示从当前目录里拷贝文件到镜像里。
如果构建镜像的文件名不是Containerfile,需要使用-f指定文件名,具体如下。

podman  build -t新镜像名:tag   .  -f file

开始构建。

[lduan@server ~]$ podman  build -t centos:v1 .
STEP 1/4: FROM hub.c.163.com/library/centos
STEP 2/4: MAINTAINER duan
...输出...
Step 4/4 : CMD /bin/bash
---> Running in 8e0fb6170a3b
---> b3c554b578c4
Removing intermediate container 8e0fb6170a3b
Successfully built b3c554b578c4
[lduan@server ~]$

构建完成,查看现有镜像。

[lduan@server ~]$ podman  images
REPOSITORY        TAG       IMAGE ID         CREATED             SIZE
    localhost/centos     v1        b3c554b578c4     22 seconds ago      301 MB  
...
[lduan@server ~]$ 

使用该镜像创建出一个容器,验证是否可以使用ifconfig命令。

[lduan@server ~]$ podman  run --rm -it centos:v1 
[root@cea78ca52d6f /]# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.88.0.3  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:acff:fe11:4  prefixlen 64  scopeid 0x20<link>
        ether 02:42:ac:11:00:04  txqueuelen 0  (Ethernet)
        RX packets 5  bytes 418 (418.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6  bytes 508 (508.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@cea78ca52d6f /]# exit
exit
[lduan@server ~]$

可以看到,一切是正常的。

练习2:验证ADD和COPY的区别。

ADD和COPY都可以把当前目录里的文件拷贝到临时容器里,但是ADD和COPY在拷贝压缩文件的时候存在一些区别。ADD把压缩文件拷贝到临时容器里时会自动解压,COPY不带解压功能。
在当前目录随意创建一个压缩文件,这里有个aa.tar.gz,内容如下。

[lduan@server ~]$ tar ztf aa.tar.gz 
epel.repo
index.html
[lduan@server ~]$

创建Containerfile2,内容如下。

[lduan@server ~]$ cat Containerfile2
FROM hub.c.163.com/library/centos
MAINTAINER duan
#在临时容器里创建目录/11和/22
RUN mkdir /11 /22
#利用ADD把aa.tar.gz拷贝到/11里,利用COPY把aa.tar.gz拷贝到/22里
ADD aa.tar.gz /11
COPY aa.tar.gz /22

CMD ["/bin/bash"]
[lduan@server ~]$

aa.tar.gz以ADD的方式拷贝到镜像的/11目录。
aa.tar.gz以COPY的方式拷贝到镜像的/22目录。
编译镜像,镜像名为centos:add-copy。

[lduan@server ~]$ podman  build -t centos:add-copy . -f Containerfile2 
STEP 1/4: FROM hub.c.163.com/library/centos
STEP 2/4: MAINTAINER duan
...
Removing intermediate container 7d00f53629f3
Successfully built d21b6fa6234a
[lduan@server ~]$

使用该镜像创建出容器验证结果。

[lduan@server ~]$ podman  run --rm -it centos:add-copy 
[root@7bdd096a6221 /]# ls /11
epel.repo  index.html
[root@7bdd096a6221 /]# ls /22/
aa.tar.gz
[root@7bdd096a6221 /]# exit
exit
[lduan@server ~]$

可以看到,以ADD方式拷贝过去的压缩文件进行了解压操作,而以COPY方式拷贝过去的并没有解压。

练习3:USER命令的使用。

前面做的镜像里,都是以root来运行进程,如果要以指定的用户来运行进程,可以使用USER命令,创建Containerfile3,内容如下。

[lduan@server ~]$ cat Containerfile3
FROM hub.c.163.com/library/centos
MAINTAINER duan
RUN useradd lduan 
USER lduan

CMD ["/bin/bash"]
[lduan@server ~]$

这里首先创建出lduan用户,然后用USER指定后面容器里要以lduan来运行进程。
编译镜像,镜像为centos:user。

[lduan@server ~]$ podman  build -t centos:user . -f Containerfile3
...
Removing intermediate container 97e2d65cb77f
Successfully built 518cea386492
[lduan@server ~]$

使用该镜像创建容器。

[lduan@server ~]$ podman  run --restart=always  --name=c1 -it centos:user
[lduan@a7f0227edfb7 /]$ 
[lduan@a7f0227edfb7 /]$ whoami
lduan
[lduan@a7f0227edfb7 /]$ exit
exit
[lduan@server ~]$

可以看到,容器里的进程是以lduan的身份来运行的。如果要以root身份进入容器里,加上--user=root选项即可。

[lduan@server ~]$ podman  exec -it --user=root  c1  bash
[root@a7f0227edfb7 /]# 
[root@a7f0227edfb7 /]# whoami
root
[root@a7f0227edfb7 /]# exit
exit
[lduan@server ~]$

删除这个容器c1。
[lduan@server ~]$ podman rm -f c1
c1
[lduan@server ~]$

练习4:用ENV来指定变量。

创建Containerfile4。

[lduan@server ~]$ cat Containerfile4
FROM hub.c.163.com/library/centos
MAINTAINER duan
ENV myenv=/aa

CMD ["/bin/bash"]
[lduan@server ~]$

构建镜像名称为centos:env。

[lduan@server ~]$ podman  build -t centos:env  .  -f Containerfile4

使用该镜像创建出来一个容器。

[lduan@server ~]$ podman  run --rm -it centos:env
[root@457c99cfd44b /]# echo $myenv
/aa
[root@457c99cfd44b /]# exit
exit
[lduan@server ~]$

可以看到,容器里存在一个变量myenv=aa。
在创建容器的时候是可以使用-e来指定变量的值的。

[lduan@server ~]$ podman  run --rm -it -e myenv=xxx centos:env
[root@1084136a59d2 /]# echo $myenv
xxx
[root@1084136a59d2 /]# exit
exit
[lduan@server ~]$

练习6:数据卷。

创建Containerfile5。

[lduan@server ~]$ cat Containerfile5
FROM hub.c.163.com/library/centos
MAINTAINER duan
VOLUME ["/data1"]

CMD ["/bin/bash"]
[lduan@server ~]$

此新镜像创建出来的容器里,会创建一个目录/data1绑定物理机的随机目录。
构建镜像,名称为centos:volume。

[lduan@server ~]$ podman  build -t centos:volume . -f Containerfile5

使用此镜像创建一个容器出来。

[lduan@server ~]$ podman  run --rm -it  centos:volume
[root@ff3ee713ea74 /]# ls /data1/
[root@ff3ee713ea74 /]#

在其他终端查看此容器的属性。

[lduan@server ~]$ podman  inspect ff3ee713ea74 | grep -A5 Mounts
        ...
                "Source": "/var/lib/docker/
volumes/44a9c964f8192431aa18c5861e5ac80364133639a5615c29d1201fee3ac3e70a/_data",
                "Destination": "/data1",
[lduan@server ~]$

注意:如果想有多个挂载点,应该写成VOLUME ["/data1","/data"]。

作业

作业题在server2上做。

  1. 在server2上配置镜像加速器,使得从docker.io拉取镜像时使用加速器地址frz7i079.mirror.aliyuncs.com。

  2. 使用tom用户 ssh到server2上,拉取镜像docker.io/library/nginx。

  3. 使用tom用户 ssh到server2上,利用镜像docker.io/library/nginx创建一个名称为web的容器。要求把tom家目录下的yy目录映射到容器web的/data目录中。

  4. 使用tom用户 ssh到server2上,做相关配置,当系统启动之后,web容器能自动启动起来。

相关新闻

发表回复

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

                                                                                                                                    RHCE9学习指南连载,点击阅读