镜像:
Docker 镜像(Image),就相当于是一个 root 文件系统。Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
因为镜像包含操作系统完整的 root
文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层,可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
容器:
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。
前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层。在只读的镜像层外面挂载一层可读写层
修改容器内容:
docker exec -it xxx bash
docker commit 可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。
Dockerfile定制镜像:
是以一个镜像为基础,在其上进行定制。可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,这个脚本就是 Dockerfile。每一个 RUN 的行为:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。镜像构建时应尽量避免冗余的镜像层,如下示例构建一个redis镜像,可在一层内搭建完成。
FROM debian:stretch
RUN 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
可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt
缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
构建镜像:
在 Dockerfile 文件所在目录执行:
docker build -t nginx:v3 .
docker build [选项] <上下文路径/URL/->
先启动容器,再其上进行修改并提交,随即删除这个容器.
docker build 命令构建镜像,是在服务端构建,也就是 Docker 引擎中构建的.当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。
Docker 结构:
Docker 采用的是 client-server 模式, 用户通过 client(客户端,指 docker 或 docker-compose 命令行)与 server(服务器端程序,Docker daemon,dockerd)交互,由 server 完成构建镜像,生成容器,运行容器等任务。docker client 和 server 通过 REST API 交互,既可以在同一个主机上,也可以分布不同主机上。
可以使用systemctl status docker查看dockerd PID
启动容器:
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。
1. 新建并启动:docker run
–detach | -d | 在后台运行容器,并且打印容器id。 |
–interactive | -i | 即使没有连接,也要保持标准输入保持打开状态,一般与 -t 连用。 |
–tty | -t | 分配一个伪tty,一般与 -i 连用。 |
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
-
检查本地是否存在指定的镜像,不存在就从公有仓库下载
-
利用镜像创建并启动一个容器
-
分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
-
从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
-
从地址池配置一个 ip 地址给容器
-
执行用户指定的应用程序
-
执行完毕后容器被终止
2. 启动已终止容器:docker container start
进入容器:
docker exec,如果从这个 stdin 中 exit,不会导致容器的停止
docker exec -it 69d1 bash
Docker网络通信:
Veth设备对是Docker中容器和宿主机连通的主要设备,同一命名空间中的一对Veth设备,可以将其中一个转移到其他空间实现命名空间之间的通信。
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether XX:XX:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
3: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether XX:XX:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
Docker Daemon首次启动时会创建一个虚拟网桥docker0,并且在私有网络空间中给这个网桥分配一个子网。对Docker创造的每个容器,都会创建一个Veth设备对,一端关联到网桥上,另一端使用Linux的网络命名空间技术映射到容器内的eth0设备,然后在网桥的地址段给eth0接口分配一个IP地址。