如何利用容器技术提升你的工作效率?掌握基础理论和常用命令是必不可少的,本文将为你全面介绍容器技术,并教你必知必会的技能,让你工作、学习效率翻倍,对于网络安全工作者也是必不可少的技能!
0. 引言
学习容器技术对大家的日常工作和学习都有很多好处,主要如下:
-
提高开发效率:使用容器技术可以快速搭建开发环境和部署环境,大大提高了开发效率。
-
更好的可移植性:容器技术使得应用程序更易于在不同的操作系统和云平台之间移植,从而实现更好的可移植性。
-
更好的资源利用率:容器可以在相同的硬件资源上运行更多的应用程序,从而更好地利用硬件资源。
-
更好的安全性:容器技术通过隔离应用程序和操作系统,提供了更好的安全性,防止应用程序之间的相互干扰和攻击。
-
更好的可扩展性:容器技术可以轻松地进行水平和垂直扩展,从而更好地应对高并发和大流量的需求。
另,学习容器技术对于网络安全领域的工作者更是有以下好处:
-
更好地理解应用程序:容器技术可以帮助网络安全工作者更好地理解应用程序的运行机制,因为容器技术可以将应用程序及其依赖项打包成一个可移植的整体;
-
更好地管理应用程序:容器技术可以提供更好的应用程序管理和部署机制,可以帮助网络安全工作者更好地控制应用程序的运行环境,减少潜在的安全漏洞;
博主搭建的漏洞环境、漏洞靶场也几乎都是容器化部署的。
-
更好地隔离应用程序:容器技术可以提供更好的应用程序隔离机制,可以将不同的应用程序隔离开来,从而减少由于应用程序之间相互干扰导致的安全问题;
-
更好地应对攻击:容器技术可以提供更好的应对攻击的机制,例如通过容器镜像签名来确保容器镜像来源的可靠性,以及通过容器网络隔离来限制容器之间的通信等。
1. 基础理论
1.1. 什么是容器(Container)
容器是一种统一的软件交付的标准,所构建的软件包(容器镜像)可以跨平台(Linux或Windows)随处运行,可以对以下场景进行隔离:
- 应用配置:如数据源配置,应用端口配置等;
- 业务应用:如购物网站,SaaS服务等
- 系统软件:如openssl、gcc、curl、python、jdk等;
- 系统配置:如防火墙策略配置iptables;
- 操作系统:RedHat、Ubuntu、CentOS等。
1.1.1. 容器与虚拟机的区别
容器技术起源于Linux,是一种内核虚拟化技术,提供轻量级的虚拟化,以便隔离进程和资源。尽管容器技术已经出现很久,却是随着Docker的出现而变得广为人知。Docker是第一个使容器能在不同机器之间移植的系统。 Docker 简化了打包应用的流程、打包应用的库和依赖,甚至整个操作系统的文件系统能被打包成一个简单的可移植的包,这个包可以被用来在任何其他运行Docker的机器上使用。
容器和虚拟机具有相似的资源隔离和分配方式,容器虚拟化了操作系统而不是硬件,更加便携和高效。
对比指标 | 容器 | 虚拟机 | 备注 |
---|---|---|---|
磁盘占用 | 小 | 大 | 镜像层最小可至KB级别,虚拟机大小一般为GB级别 |
启动速度 | 快 | 慢 | 传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间,大大节约了开发、测试、部署的时间。 |
并发 | 高 | 低 | 宿主机可启动成百上千个容器,但虚拟机至多同时启动几十个 |
性能 | 较高 | 较差 | 容器性能接近宿主机本地进程,虚拟机进程逊于宿主机本地进程 |
资源利用率 | 高 | 低 | 由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,容器对系统资源的利用率更高。 |
维护扩展性 | 高 | 低 | Docker使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker团队同各个开源项目团队一起维护了大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。 |
1.2. 镜像(Image)是什么
将应用软件及所有相关的交付件统一打成一个包,这个包称为镜像,镜像可通过容器引擎快速运行。Docker Hub 中 95%+ 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:
FROM debian
RUN apt-get install emacs
RUN apt-get install apache2
CMD ["/bin/bash"]
注解:
- Dockerfile是一个文本文档,其中包含用户可以在命令行上调用以组装镜像的所有命令。Docker可以通过读取Dockerfile中的指令自动构建镜像;
- 如果多个镜像都从相同的base镜像构建而来,那么Docker主机上只需在磁盘上保存一份base镜像。同时内存中也只需加载一份base镜像(即镜像的每一层都可以被共享),就可以为所有容器服务了。
1.3. 什么是镜像仓库(Image Registry)
镜像仓库用于存放镜像,以及促进不同人和不同电脑之间共享这些镜像。当编译镜像时,要么可以在编译它的电脑上运行,要么可以先上传镜像到一个镜像仓库,然后下载到另外一台电脑上并运行它。Docker Hub便是一个用于存放Docker镜像的在线仓库,它可以存放私人和公开的镜像。
1.4. 什么是卷(Volume)
容器中的文件在磁盘上是临时存放的,当容器重建时,容器中的文件将会丢失,另外当在一个 Pod 中同时运行多个容器时,常常需要在这些容器之间共享文件,这也是容器不好解决的问题。K8s 抽象出了 Volume 来解决这两个问题,也就是存储卷,K8s的Volume是Pod的一部分,Volume不是单独的对象,不能独立创建,只能在Pod中定义。
Pod中的所有容器都可以访问Volume,但必须要挂载,且可以挂载到容器中任何目录。
注意:数据卷类似于Linux下对目录或文件进行mount,镜像中的被指定为挂载点的目录中的文件会复制到数据卷中(仅数据卷为空时会复制)。
1.5. 什么是K8S
Kubernetes是一个开源的容器编排部署管理平台,用于管理云平台中多个主机上的容器化应用。Kubernetes的目标是让部署容器化的应用简单并且高效,Kubernetes提供了应用部署、规划、更新、维护的一种机制。K8S架构图如下:
对应用开发者而言,可以把Kubernetes看成一个集群操作系统。Kubernetes提供服务发现、伸缩、负载均衡、自愈甚至选举等功能,让开发者从基础设施相关配置等解脱出来。
Kubernetes可以把大量的服务器看做一台巨大的服务器,在一台大服务器上面运行应用程序。无论Kubernetes的集群有多少台服务器,在Kubernetes上部署应用程序的方法永远一样。
1.5.1. Kubernetes中的基本对象
下图中介绍了Kubernetes中基本对象及它们之间的一些关系。
-
容器组(Pod):Pod是Kubernetes创建或部署的最小单位。一个Pod封装一个或多个容器(container)、存储资源(volume)、一个独立的网络IP以及管理控制容器运行方式的策略选项。
-
无状态工作负载(Deployment):Deployment是对Pod的服务化封装。一个Deployment可以包含一个或多个Pod,每个Pod的角色相同,所以系统会自动为Deployment的多个Pod分发请求。
-
有状态工作负载(StatefulSet):StatefulSet是用来管理有状态应用的对象。和Deployment相同的是,StatefulSet管理了基于相同容器定义的一组Pod。但和Deployment不同的是,StatefulSet为它们的每个Pod维护了一个固定的ID。这些Pod是基于相同的声明来创建的,但是不能相互替换,无论怎么调度,每个Pod都有一个永久不变的ID。
-
任务(Job):Job是用来控制批处理型任务的对象。批处理业务与长期伺服业务(Deployment)的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。Job管理的Pod根据用户的设置把任务成功完成就自动退出(Pod自动删除)。
-
定时任务(CronJob):CronJob是基于时间控制的Job,类似于Linux系统的crontab,在指定的时间周期运行指定的任务。
-
守护进程集(DaemonSet):DaemonSet是这样一种对象(守护进程),它在集群的每个节点上运行一个Pod,且保证只有一个Pod,这非常适合一些系统层面的应用,例如日志收集、资源监控等,这类应用需要每个节点都运行,且不需要太多实例,一个比较好的例子就是Kubernetes的kube-proxy。
-
服务(Service):Service是用来解决Pod访问问题的。Service有一个固定IP地址,Service将访问流量转发给Pod,而且Service可以给这些Pod做负载均衡。
-
路由(Ingress):Service是基于四层TCP和UDP协议转发的,Ingress可以基于七层的HTTP和HTTPS协议转发,可以通过域名和路径做到更细粒度的划分。
-
配置项(ConfigMap):ConfigMap是一种用于存储应用所需配置信息的资源类型,用于保存配置数据的键值对,可在容器工作负载中作为文件或者环境变量使用。通过ConfigMap可以方便的做到配置解耦,使得不同环境有不同的配置。
-
保密字典(Secret):Secret是一种加密存储的资源对象,您可以将认证信息、证书、私钥等保存在Secret中,在容器工作负载中作为文件或者环境变量使用,而不需要把这些敏感数据暴露到镜像或者Pod定义中,从而更加安全和灵活。
Secret与ConfigMap非常像,都是key-value键值对形式,使用方式也相同,不同的是Secret会加密存储,Secret的Value必须使用Base64编码,所以适用于存储敏感信息。
对字符串进行Base64编码,可以直接使用“echo -n 要编码的内容 | base64”命令即 可,示例如下:
# echo -n "3306" | base64
MzMwNg==
-
存储卷(Persistent Volume,PV):PV指持久化数据存储卷,主要定义的是一个持久化存储在宿主机上的目录,比如一个NFS的挂载目录。
-
存储卷声明(Persistent Volume Claim,PVC):Kubernetes提供PVC专门用于持久化存储的申请,PVC可以让您无需关心底层存储资源如何创建、释放等动作,而只需要申明您需要何种类型的存储资源、多大的存储空间。
-
空目录(EmptyDir):EmptyDir是最简单的一种Volume类型,根据名字就能看出,这个Volume挂载后就是一个空目录,应用程序可以在里面读写文件,emptyDir Volume的生命周期与Pod相同,Pod删除后Volume的数据也同时删除掉。emptyDir的一些用途:
- 缓存空间,例如基于磁盘的归并排序。
- 为耗时较长的计算任务提供检查点,以便任务能从崩溃前状态恢复执行。
emptyDir实际是将Volume的内容写在Pod所在节点的磁盘上,另外emptyDir也可以设置存储介质为内存
-
主机路径(HostPath):HostPath是一种持久化存储,emptyDir里面的内容会随着Pod的删除而消失,但HostPath不会,如果对应的Pod删除,HostPath Volume里面的内容依然存在于节点的目录中,如果后续重新创建Pod并调度到同一个节点,挂载后依然可以读取到之前Pod写的内容。
HostPath存储的内容与节点相关,所以它不适合像数据库这类的应用,如果数据库的Pod被调度到别的节点了,那读取的内容就完全不一样了。
1.5.2. 网络访问场景
工作负载网络访问可以分为如下几种场景:
-
从集群内部访问工作负载:创建ClusterIP类型的Service,通过Service访问工作负载。
-
从集群外部访问工作负载:从集群外部访问工作负载推荐使用Service(NodePort类型或LoadBalancer类型)或Ingress访问。
- 通过公网访问工作负载:需要节点或LoadBalancer绑定公网IP。
- 通过内网访问工作负载:通过节点或LoadBalancer的内网IP即可访问工作负载。如果跨VPC需要通过对等连接等手段打通不同VPC网络。
-
工作负载访问外部网络:
-
工作负载访问内网:负载访问内网地址,在不同容器网络模型下有不同的表现,需要注意在对端安全组放通容器网段,具体请参见容器如何访问VPC内部网络。
-
工作负载访问公网:访问公网有几种方法可以实现,一是让容器所在节点绑定公网IP(容器网络模型为VPC网络或容器隧道网络),或给Pod IP绑定公网IP(云原生2.0网络),另一个是通过NAT网关配置SNAT规则,具体请参见从容器访问公网。
-
1.6. Docker容器典型使用流程
-
制作镜像:首先开发者在开发环境机器上开发应用并制作镜像。Docker执行命令,构建镜像并存储在机器上。
-
上传镜像:开发者发送上传镜像命令。Docker收到命令后,将本地镜像上传到镜像仓库。
-
运行镜像:开发者向生产环境机器发送运行镜像命令。生产环境机器收到命令后,Docker会从镜像仓库拉取镜像到机器上,然后基于镜像运行容器。
1.7. 常用操作
接下来本文主要介绍一些常用操作。若想了解更多Docker常用操作命令可以参阅:
- Download Docker Commands Cheat Sheet
- Docker技术入门与实战.pdf (访问密码: 6277)
- 命令参数细节可参阅官方文档Use the Docker command line
1.7.1. 制作DockerFile
常见 Dockerfile 由FROM、RUN、EXPOSE组成,如下
# 使用官方提供的Nginx镜像作为基础镜像
FROM nginx:alpine
# 执行一条命令修改Nginx镜像index.html的内容
RUN echo "hello world" > /usr/share/nginx/html/index.html
# 允许外界访问容器的80端口
EXPOSE 80
1.7.1.1. Dockerfile命令介绍
1.7.1.1.1. FROM
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。Dockerfile中FROM是必备的指令,并且必须是第一条指令。
1.7.1.1.2. RUN
RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。如下为一个正确的写法的Dockerfile。
FROM debian:stretch
RUN set -x; buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
解读:
- 这里没有使用很多个RUN一一对应不同的命令,而是仅仅使用一个RUN指令,并使用&&将各个所需命令串联起来,此操作可将7层镜像简化为1层,减少了镜像体积。
- Dockerfile 支持 Shell 类的行尾添加
\
的命令换行方式,以及行首#
进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个好的习惯。 - 命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了apt缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
1.7.1.1.3. LABEL
LABEL 指令用来给镜像以键值对的形式添加一些元数据(metadata)
LABEL <key>=<value> <key>=<value> <key>=<value> ...
比如声明镜像的作者,
LABEL author="筑梦之月"
1.7.1.1.4. COPY
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
<源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match (opens new window)规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用WORKDIR指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
此外,还需要注意一点,使用COPY
指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。可在使用该指令的时候还可以加上--chown=<user>:<group>
选项来改变文件的所属用户及所属组。
注意: 如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
1.7.1.1.5. ADD
ADD
指令和 COPY
的格式和性质基本一致。但是在COPY
基础上增加了一些功能。比如<源路径>
可以是一个URL,这种情况下,Docker引擎会试图去下载这个链接的文件放到<目标路径>
去。
如果 <源路径>
为一个tar压缩文件,压缩格式为gzip, bzip2以及xz的情况下,ADD指令将会自动解压缩这个压缩文件到<目标路径>
去。
在Docker官方的Dockerfile最佳实践文档中要求,尽可能的使用COPY,因为COPY的语义很明确,就是复制文件而已,而ADD则包含了更复杂的功能,其行为也不一定很清晰。最适合使用ADD的场合,就是所提及的需要自动解压缩的场合。
1.7.1.1.6. CMD
CMD指令用于执行目标镜像中包含的软件,可以包含参数。指令有以下两种格式:
CMD <命令>
CMD ["可执行文件", "参数1", "参数2"...]
比如,
CMD echo $HOME
CMD [ "sh", "-c", "echo $HOME" ]
1.7.1.1.7. ENTRYPOINT
ENTRYPOINT
主要用于设置镜像的主命令,允许将镜像当成命令本身来运行(用 CMD 提供默认选项),例如,下面的示例镜像提供了命令行工具ls
ENTRYPOINT ["ls"]
CMD ["--help"]
现在直接运行该镜像创建的容器会显示命令帮助,或者提供正确的参数来执行某个命令,比如查看当前容器根目录文件,
$ docker run <镜像ID> /
$ docker run 2c5cdd305083 /
bin
dev
etc
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
1.7.1.1.8. WORKDIR
WORKDIR <工作目录路径>
使用WORKDIR指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR会帮你建立目录。
1.7.1.1.9. ENV
这个指令很简单,就是设置环境变量而已,格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
1.7.1.1.10. EXPOSE
EXPOSE <端口1> [<端口2>...]
EXPOSE
指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在Dockerfile 中写入这样的声明有两个好处:
- 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;
- 在运行时使用随机端口映射时,也就是
docker run -P
时,会自动随机映射 EXPOSE 的端口。
1.7.1.1.11. USER
USER <用户名>[:<用户组>]
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。
注意: USER只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
关于Dockerfile的更多命令介绍请参阅:
-
Docker技术入门与实战.pdf (访问密码: 6277)
-
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
1.7.2. 构建镜像
执行docker build命令打包镜像,命令格式如下:
docker build -f <dockerfile> -t <name:version> .
比如,
docker build -t hello .
其中-t表示给镜像加一个标签,也就是给镜像取名,这里镜像名为hello。. 表示在当前目录下执行该打包命令。
1.7.3. 查看镜像
执行docker images
命令查看镜像。
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ghcr.io/scanoss/scanoss-py latest 2c5cdd305083 2 weeks ago 471MB
owasp/benchmark latest 7bbf7f60d339 3 weeks ago 2.28GB
1.7.4. 运行镜像
有了镜像后,您可以在本地执行docker run
命令运行镜像。命令举例如下,
docker run -it -p 宿主机端口:容器端口 --name 新名字 镜像名:新建并启动容器
参数说明:
-d:后台运行容器(启动便退出,再次进入exit退出后会后台运行),
-i:是以交互模式启动
-t:是为它分配一个伪终端(it经常一起使用)
-p 端口:容器默认端口:指定一个本机端口映射到容器内端口,使得可以从宿主机访问容器内
-P:随机分配映射端口。
-v:宿主机目录(文件):容器目录(文件):文件映射,保持容器文件与外部同步
比如,
# docker run -p 8080:80 hello
此命令会启动一个容器,命令中-p是将本地机器的8080端口映射到容器的80端口,即本地机器的8080端口的流量会映射到容器的80端口。
1.7.5. 上传镜像
上传镜像前需要给镜像取一个完整的名称,如下所示:
docker tag 7bbf7f60d339 owasp/benchmark:1.0
这里,
- 7bbf7f60d339 是镜像ID
- 1.0 则是owasp benchmark镜像分配的版本号。
然后执行docker push命令就可以将镜像上传到镜像仓库。
# docker push owasp/benchmark:1.0
1.7.6. 拉取镜像
当需要使用镜像时,使用docker pull命令拉取(下载)该命令即可。
# docker pull owasp/benchmark:1.0
1.7.7. 导入、导出镜像
将镜像保存为镜像库存储文件
docker save -o <镜像库存储文件名> <镜像ID>
将镜像库存储文件导入到镜像库
docker load -i <镜像库存储文件名>
1.7.8. 删除镜像
删除当前镜像
docker rmi <镜像ID>
删除所有不使用的镜像
docker image prune --force --all
或者 docker image prune -f -a
1.7.9. 导出和导入容器
如果要导出本地某个容器生成容器快照,可以使用docker export
命令,格式如下
docker export <容器ID>
比如导出如下容器
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9624b874754 owasp/benchmark "/bin/bash" 10 days ago Exited (255) 6 minutes ago 0.0.0.0:8443->8443/tcp priceless_blackwell
$ docker export d9624b874754 > benchmark.tar
可以使用docker import
从容器快照文件中再导入为镜像,例如
$ cat ubuntu.tar | docker import - owasp/benchmark:1.0
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
owasp/benchmark 1.0 7bbf7f60d339 About a minute ago 171.3 MB
保存容器快照时,容器挂载的文件不会保存进镜像,需要分析挂载的路径并拷贝相关数据目录;
注意: 既可以使用docker load
来导入镜像存储文件到本地镜像库,也可以使用docker import
来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。
1.7.10. 查看容器
列出运行中的容器
docker ps
列出本机所有的容器(包括停止和运行)
docker ps -a
列出所有的容器 ID
docker ps -aq
1.7.10.1. 查看容器详情
docker inspect
1.7.11. 进入、退出容器
进入容器有两种方式:
-
方式一:
docker attach <容器ID>
-
方式二:
docker exec <容器ID>
进入容器通常使用第二种方式,docker exec后面跟的常见参数如下:
-d, --detach 在容器中后台执行命令;
-i, --interactive=true
如果采用方式一,如果从stdin中exit,会导致容器的停止。
1.7.12. 停止容器
停止运行的容器
docker stop <容器ID>
杀死容器进程
docker kill <容器ID>
停止所有的容器
docker stop $(docker ps -aq)
1.7.13. 重启容器
docker restart <容器ID>
1.7.14. 文件复制
从主机复制到容器
docker cp <宿主机路径> <容器ID>:<容器路径>
从容器复制到主机
docker cp <容器ID>:<容器路径> <宿主机路径>
1.7.15. 删除容器
删除单个容器
docker rm <容器ID>
删除所有停止的容器
docker container prune -f
# docker container prune -f
Deleted Containers:
0884f6e6557c2b04e5951f55470dd7975a4975d14c045bf9514ec08eb43877f0
0f78c5a75d5f8f9e2d5b1d003417e47feae8a10cec5e8fe72317d28bdad49db9
665f41f59280753545879e081a1505878008b1056ca988be5f9dfc3b57f8b719
93700e7699dfa48134adec8d36643efab1fb5b15687f432ca8803ad4c21d8077
d9624b87475454cff7753981d6ae9deac47379e41dcab1b720155e2bd92373c3
Total reclaimed space: 9.482GB
删除所有停止的容器可以清理磁盘空间,但删除前请确认好,防止重要环境丢失。
1.7.16. 创建一个数据卷
docker volume create <卷名>
1.7.17. 查看数据卷
docker volume ls
$ docker volume ls
DRIVER VOLUME NAME
local d9624b87475454cff7753981d6ae9deac47379e41dcab1b720155e2bd92373c3
local 665f41f59280753545879e081a1505878008b1056ca988be5f9dfc3b57f8b719
在主机里使用以下命令可以查看指定数据卷的信息
$ docker volume inspect python
[
{
"CreatedAt": "2023-04-11T16:18:47+08:00",
"Driver": "local",
"Labels": {
"dev.container.volume": "true"
},
"Mountpoint": "/var/lib/containers/storage/volumes/python/_data",
"Name": "python",
"Options": {},
"Scope": "local"
}
]
1.7.18. 删除数据卷
docker volume rm <卷名>
数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用docker rm -v
这个命令。
无主的数据卷可能会占据很多空间,要清理请使用以下命令
docker volume prune
$ docker volume prune
WARNING! This will remove all local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
d9624b87475454cff7753981d6ae9deac47379e41dcab1b720155e2bd92373c3
665f41f59280753545879e081a1505878008b1056ca988be5f9dfc3b57f8b719
Total reclaimed space: 171kB
1.7.19. 挂载数据卷
1.7.19.1. 默认目录挂载
命令:
docker run [options] -v <任意别名:容器内的路径[:ro | rw]> <镜像名|镜像ID>
解读:
- 任意别名是一个数据卷名字,名字可以随便写,Docker会在
/var/lib/docker/volumes
目录下生成该数据卷(Docker默认的数据卷目录),Podman会在/var/lib/containers/storage/volumes/
目录下生成该数据卷(Podman默认的数据卷目录),并且在数据卷里生成_data
目录用于与容器目录同步数据; - 容器的挂载目录内容覆盖到宿主机的挂载目录内容。
举例:
docker run -d -p 8023:8080 --name tomcat -v tomcat_volume:/usr/local/tomcat/webapps tomcat:latest
tomcat_volume代表一个数据卷名字,可以是任意,这相当于相对路径,它会在/var/lib/docker/volumes/
下创建tomcat_volume目录作为数据卷。
1.7.19.2. 具体目录挂载
命令:
docker run [options] -v <宿主机绝对路径 | 任意别名:容器内的路径[:ro | rw]> <镜像名|镜像ID>
解读:
- 宿主机路径必须是绝对路径,如果目录不存在Docker会自动为你创建它;
- 宿主机的的挂载目录内容覆盖到容器的挂载目录内容。
1.7.19.3. 匿名目录挂载
命令:
docker run [options] -v <容器内的路径[:ro | rw]> <镜像名|镜像ID>
解读:
没指定名字的挂载都是匿名挂载,-v
只写了容器内路径,并没写宿主机路径,这样会在Docker宿主机下/var/lib/docker/volumes/
目录中生成匿名数据卷目录。
如执行以下命令
docker run -d -p 3306:3306 --name mysql -v /var/lib/mysql mysql:5.7
可以发现在/var/lib/containers/storage/volumes/
目录下自动生成了一串随机字符串组成的匿名数据卷目录d9624b87475454cff7753981d6ae9deac47379e41dcab1b720155e2bd92373c3
。
$ ls -lh /var/lib/containers/storage/volumes/
total 4.0K
brw------- 1 root root 8, 32 Apr 11 16:18 backingFsBlockDev
drwx------ 3 root root 4.0K Mar 16 16:45 `d9624b87475454cff7753981d6ae9deac47379e41dcab1b720155e2bd92373c3`
1.7.20. 挂载主机目录
1.7.20.1. 挂载一个主机目录作为数据卷
使用--mount
标记可以指定挂载一个本地主机的目录到容器中去。
$ docker run -d -P \
--name web \
# -v /src/webapp:/usr/share/nginx/html \
--mount type=bind,source=/src/webapp,target=/usr/share/nginx/html \
nginx:alpine
ro:代表 read-only,容器的路径只允许读,不允许写。不影响宿主机的路径可读可写
rw:默认值,代表可读可写
1.7.20.2. 挂载一个本地主机文件作为数据卷
--mount
标记也可以从主机挂载单个文件到容器中
$ docker run --rm -it -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:20.04 \
2. 最佳实践
2.1. Dockerfile最佳实践
2.1.1. 避免安装不必要的包
为了降低复杂性、减少依赖、减小文件大小、节约构建时间,我们该避免安装任何不必要的包。例如,不要在数据库镜像中包含一个文本编辑器。
2.1.2. 将多行参数排序
将多行参数按字母顺序排序(比如要安装多个包时)。这可以帮助我们避免重复包含同一个包,更新包列表时也更容易。也便于阅读和审查。建议在反斜杠符号\
之前添加一个空格,以增加可读性。
下面是buildpack-deps镜像的例子:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
2.1.3. 使用多阶段构建
多阶段构建可大幅减小最终镜像的大小,而无须尽力地去想办法减少中间层和文件的数量。镜像是在构建过程的最后阶段构建的,因此可以通过利用构建缓存来最小化镜像层。例如,如果构建包含多个层并且希望确保构建缓存可重用,我们可以将它们从更改频率较低的顺序排列到更改频率较高的顺序。以下列表是指令顺序的示例:
- 安装构建应用程序所需的工具;
- 安装或更新库依赖项;
- 生成应用程序
Go 应用程序的 Dockerfile 样例如下所示:
FROM golang:1.16-alpine AS build
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project
# This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]
2.2. 镜像加速
执行命令vi /etc/containers/registries.conf,在文件最后添加如下registry
unqualified-search-registries = ["docker.io"]
[[registry]]
location = "docker.io"
[[registry.mirror]]
location = "mirror.baidubce.com"
在cmd窗口执行命令docker info
看到如下信息则表明镜像加速配置成功。
registries:
docker.io:
Blocked: false
Location: docker.io
MirrorByDigestOnly: false
- Insecure: false
Location: mirror.baidubce.com
PullFromMirror: ""
Prefix: docker.io
PullFromMirror: ""
search:
- docker.io
2.3. root方式进入容器
场景:用于执行需要root用户才能执行的操作,比如临时加装软件。
比如普通用户安装vim软件时会提示无权限
$ apt-get install vim
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?
在Docker容器中,可以使用--privileged
参数让容器拥有宿主机的root权限。
命令如下:
docker run -it --privileged <container id> /bin/bash
Note:如果你没有在启动容器时使用
--privileged
参数,那么容器中的root用户将不会拥有宿主机的root权限。因此,如果你需要在Docker容器中使用宿主机的root权限,请确保在启动容器时使用了--privileged
参数。
3. 参考链接
-
https://support.huaweicloud.com/cce/index.html
-
https://phoenixnap.com/kb/docker-commands-cheat-sheet
-
https://kubernetes.io/zh-cn/docs/home/
-
https://frxcat.fun/pages/90cc29/
-
镜像的分层结构 - 每天5分钟玩转容器技术(11)
-
Docker技术入门与实战.pdf (访问密码: 6277)
-
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/