本文主要说明如何从0到1制作一个docker镜像
一、Docker镜像
(1)介绍
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
(2)Dockerfile相关命令
FROM
FROM指定一个基础镜像, 一般情况下一个可用的 Dockerfile一定是 FROM 为第一个指令。至于image则可以是任何合理存在的image镜像。 FROM 一定是首个非注释指令 Dockerfile. FROM 可以在一个 Dockerfile 中出现多次,以便于创建混合的images。 如果没有指定 tag ,latest 将会被指定为要使用的基础镜像版本。
MAINTAINER
这里是用于指定镜像制作者的信息 RUN RUN命令将在当前image中执行任意合法命令并提交执行结果。命令执行提交后,就会自动执行Dockerfile中的下一个指令。 层级 RUN 指令和生成提交是符合Docker核心理念的做法。它允许像版本控制那样,在任意一个点,对image 镜像进行定制化构建。 RUN 指令缓存不会在下个命令执行时自动失效。比如 RUN apt-get dist-upgrade -y 的缓存就可能被用于下一个指令. --no-cache 标志可以被用于强制取消缓存使用。
ENV
ENV指令可以用于为docker容器设置环境变量 ENV设置的环境变量,可以使用 docker inspect命令来查看。同时还可以使用docker run --env =来修改环境变量。
USER USER
用来切换运行属主身份的。Docker 默认是使用 root,但若不需要,建议切换使用者身分,毕竟 root 权限太大了,使用上有安全的风险。
WORKDIR
WORKDIR 用来切换工作目录的。Docker 默认的工作目录是/,只有 RUN 能执行 cd 命令切换目录,而且还只作用在当下下的 RUN,也就是说每一个 RUN 都是独立进行的。如果想让其他指令在指定的目录下执行,就得靠 WORKDIR。WORKDIR 动作的目录改变是持久的,不用每个指令前都使用一次 WORKDIR。
COPY
COPY 将文件从路径 复制添加到容器内部路径 。 必须是想对于源文件夹的一个文件或目录,也可以是一个远程的url, 是目标容器中的绝对路径。 所有的新文件和文件夹都会创建UID 和 GID 。事实上如果 是一个远程文件URL,那么目标文件的权限将会是600。
ADD
ADD 将文件从路径 复制添加到容器内部路径 。 必须是想对于源文件夹的一个文件或目录,也可以是一个远程的url。 是目标容器中的绝对路径。 所有的新文件和文件夹都会创建UID 和 GID。事实上如果 是一个远程文件URL,那么目标文件的权限将会是600。
VOLUME
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。
EXPOSE
EXPOSE 指令指定在docker允许时指定的端口进行转发。
CMD
Dockerfile.中只能有一个CMD指令。 如果你指定了多个,那么最后个CMD指令是生效的。 CMD指令的主要作用是提供默认的执行容器。这些默认值可以包括可执行文件,也可以省略可执行文件。 当你使用shell或exec格式时, CMD 会自动执行这个命令。
ONBUILD
ONBUILD 的作用就是让指令延迟執行,延迟到下一个使用 FROM 的 Dockerfile 在建立 image 时执行,只限延迟一次。 ONBUILD 的使用情景是在建立镜像时取得最新的源码 (搭配 RUN) 与限定系统框架。
ARG
ARG是Docker1.9 版本才新加入的指令。 ARG 定义的变量只在建立 image 时有效,建立完成后变量就失效消失 LABEL 定义一个 image 标签 Owner,并赋值,其值为变量 Name 的值。(LABEL Owner=$Name )
ENTRYPOINT
ENTRYPOINT是指定 Docker image 运行成 instance (也就是 Docker container) 时,要执行的命令或者文件。
二、docker端口映射
(1)端口映射的概念
Docker 端口映射即映射容器内应用的服务端口到本机宿主机器。
docker容器在启动的时候,如果不指定端口映射参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的。
亦可使用Dockerfile文件中的EXPOSE指令来配置。
端口映射可使用-p、-P来实现:
-p指定要映射的端口,一个指定端口上只可以绑定一个容器
-P将容器内部开放的网络端口随机映射到宿主机的一个端口上
(2)端口映射的五种方法
1、将容器暴露的所有端口,都随机映射到宿主机上。
例如:(不推荐使用)
docker run -P -it ubuntu /bin/bash
2、将容器指定端口随机映射到宿主机一个端口上。
例如:
docker run -P 80 -it ubuntu /bin/bash
以上指令会将容器的80端口随机映射到宿主机的一个端口上。
3、将容器指定端口指定映射到宿主机的一个端口上。
例如:
docker run -p 8000:80 -it ubuntu /bin/bash
以上指令会将容器的80端口映射到宿主机的8000端口上。
4、将容器ip和端口,随机映射到宿主机上。
docker run -P 192.168.0.100::80 -it ubuntu /bin/bash
以上指令会将容器的ip192.168.0.100和80端口,随机映射到宿主机的一个端口上。
5、将容器ip和端口,指定映射到宿主机上。
docker run -p 192.168.0.100:8000:80 -it ubuntu /bin/bash
以上指令会将容器的ip192.168.0.100和80端口,映射到宿主机的8000端口。
三、docker网络模式
和vmware虚拟机一样,docker网络也是一样包含了几种网络模式来实现和host通信以及容器间的互联,docker启动后默认会生成一个虚拟网桥bridge0,它的ip是172.17.0.1但是它的掩码是255.255.0.0也就是16
1、bridge桥接模式
--network bridge
#没有进行特殊申明的话默认为docker0
为每一个容器分配、设置IP等,并将容器连接到docker0的虚拟网桥。若没有特别申明,则为默认自带一个IP以及网络设置。(一人一个)
docker0在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。
这里衍生一下它的拓扑
网桥docker0创建一对对等虚拟设备接口一个叫veth,另一个叫eth0,成对匹配。
整个宿主机的网桥模式都是docker0,类似一个交换机有一堆接口,每个接口叫veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);
每个容器实例内部也有一块网卡,每个接口叫eth0;
docker0上面的每个veth匹配某个容器实例内部的eth0,两两配对,一一匹配。
通过上述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的ip,此时两个容器的网络是互通的。
2、主机模式
--network host
容器不会虚拟出自己的网卡、IP等,而是使用宿主机的IP和端口,不在需要额外进行NAT转换。(多人一个)
3、独立模式
--network none
容器有自己独立的Network namespace,但是没有进行任何的相关配置。(有,但是空的)
4、容器模式
--network container:[容器名或容器ID]
新创建的容器不会创建自己的网卡,没有自己的IP,也不会进行相应的配置。而是和一个指定的容器共享IP端口范围等。(自己没有,用别人的)
四、容器隔离
Docker容器类似于一个Namespace,隔离了宿主机的部分权限和空间,其主要包含六大空间
(1)命名空间概念
1、pid命名空间(进程ID)
不同用户的进程就是通过 pid 命名空间隔离开的,且不同命名空间中可以有相同 pid。所有的 LXC 进程在 Docker 中的父进程为 Docker 进程,每个 LXC 进程具有不同的命名空间。同时由于允许嵌套,因此可以很方便的实现嵌套的 Docker 容器。
2、net命名空间(网络)
有了 pid 命名空间,每个命名空间中的 pid 能够相互隔离,但是网络端口还是共享 host 的端口。网络隔离是通过 net 命名空间实现的, 每个 net 命名空间有独立的 网络设备,IP 地址,路由表,/proc/net 目录。这样每个容器的网络就能隔离开来。Docker 默认采用 veth 的方式,将容器中的虚拟网卡同 host 上的一 个Docker 网桥 docker0 连接在一起。
3、ipc命名空间(进程间通信)
容器中进程交互还是采用了 Linux 常见的进程间交互方法(interprocess communication - IPC), 包括信号量、消息队列和共享内存等。然而同 VM 不同的是,容器的进程间交互实际上还是 host 上具有相同 pid 命名空间中的进程间交互,因此需要在 IPC 资源申请时加入命名空间信息,每个 IPC 资源有一个唯一的 32 位 id。
4、mnt命名空间(挂载文件系统)
类似 chroot,将一个进程放到一个特定的目录执行。mnt 命名空间允许不同命名空间的进程看到的文件结构不同,这样每个命名空间 中的进程所看到的文件目录就被隔离开了。同 chroot 不同,每个命名空间中的容器在 /proc/mounts 的信息只包含所在命名空间的 mount point。
5、UTS命名空间(主机名/域名)
UTS(“UNIX Time-sharing System”) 命名空间允许每个容器拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 主机上的一个进程。
6、User命名空间(用户)
每个容器可以有不同的用户和组 id, 也就是说可以在容器内用容器内部的用户执行程序而非主机上的用户。
(2)使用场景
1、容器进程内想要执行宿主机的命令如df、free、top等采集宿主机磁盘、内存、CPU信息
2、容器内更改宿主机的网卡、硬盘等信息
3、容器内命令不足,如telnet调试端口
(3)使用方法
1、容器特权运行
# 打开容器和宿主机操作系统之间的PID地址空间共享,使用宿主机命名空间,方便容器获取到宿主机所有进程信息,其中/proc/1作为nsenter的目标
pid: host
# 赋予容器有宿主机root权限
privileged: true
2、nsenter命令前缀
# 添加命令前缀,如获取宿主机网络信息
nsenter -n -t 1 ip addr
五、Docker文件挂载
容器中运行的应用,有可能会产生数据,如果将数据直接存储到容器中,如果删除容器时,容器中的数据会一起被删除,例如:
如果运行的是数据库容器,数据库容器直接保存的数据文件,删除容器时,容易导致数据丢失
为了防止数据丢失,可以把数据保存到宿主机上,这样有便于容器之间的数据共享
六、Multi-stage多阶段编译优化
multi-stage多阶段编译指将Docker镜像的编译分为多个阶段。比如常见的软件打包过程中,编译阶段需要多种编译工具,运行阶段有些工具不需要使用,这就造成了空间的冗余。使用多阶段编译将编译完成的二进制文件直接拷贝到运行空间,然后打包镜像能够达到节省空间的效果。以python为例,使用普通版本的python包编译,使用alpine版本的python运行。
FROM python:3.6.15 as builder
RUN pip install -r requirements.txt
FROM python:3.6.15-alpine3.15
COPY --from=builder source_file dist_file
CMD ["python3","main.py"]
第一个From用python:3.6.15环境作为编译环境,其大小333M。第二个From进入较小的python:3.6.15-alpine3.15环境,其大小只有14M,apline linux是一种轻量级的Linux发行版本,其大小仅有5M,能极大的压缩了最后docker镜像包的大小,使用 COPY --from语法,从前一个阶段的编译结果中将source文件复制到dist的环境中,最后只保留dist的环境目录。