今でもあなたは私の光丶

docker(二)

Docker核心原理

作为一种容器虚拟化技术,Docker深度应用了操作系统的多项底层支持技术,包括:Linux操作系统的 命名空间(Namespace)、控制组(Control Group)、 联合文件系统(Union File System)和Linux 网络虚拟化支持。

内部结构

Docker Engine是具有以下主要组件的客户端-服务器应用程序:

  • 服务器是一种长期运行的程序,称为守护程序进程( dockerd命令)。
  • REST API,它指定程序可以用来与守护程序进行通信并指示其操作的接口。
  • 命令行界面(CLI)客户端(docker命令)

这么做的好处就是可以通过RestApi将客户端和服务段解耦,这样服务端就可以做到Volume/Network等 做成插件化组装

容器生命周期

状态与命令对应表

状态转化图

需要根据实际情况选择的操作

  • killed by out-of-memory(因内存不足被终止)
    • 宿主机内存被耗尽,也被称为OOM:非计划终止
    • 这时需要杀死最吃内存的容器
    • 然后进行选择操作
  • container process exitde(异常终止)
  • 出现容器被终止后,将进入Should restart?选择操作:
    • yes 需要重启,容器执行start命令,转为运行状态。
    • no 不需要重启,容器转为停止状态。

容器命名空间(NameSpace)

命名空间(namespace)是Linux内核的一个强大特性,为容器虚拟化的实现带来极大便利。
命名空间要对内存、CPU、网络IO、硬盘IO、存储空间、文件系统、网络、PID、UID、IPC等的相互隔 离。

进程命名空间(PID namespace)

PID namespace 对集成PID重新编号,即两个不同的namespace下的进程可以用相同的PID。

内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,被称作root namespace(进程号1)。
它创建的PID namespace被称作为child namespace(树的字节点),
不同的PID namespace会形成一个层级体系,所属的父节点可以看到子节点中的进程,并通过信号等方 式对子节点中的进程产生影响。反过来,子节点却看不到父节点PID namespace的任何内容。

[root@localhost ~]# docker run --rm -d --network host nginx:latest
0f975c076b5d8b2f2072d184c521b3e81b92df41e9db7b8e5831f99531c4dda6
[root@localhost ~]# docker run --rm --network host -it tomcat:8.5.56-jdk8-
openjdk
[root@localhost ~]# ps -ef |grep docker
root 2335 1 0 21:01 ? 00:00:00 /usr/bin/dockerd -H fd:// --
containerd=/run/containerd/containerd.sock
root 2881 2334 0 21:05 ? 00:00:00 containerd-shim -namespace
moby -workdir
/var/lib/containerd/io.containerd.runtime.v1.linux/moby/30df4df442a94d7d8c244fe4
bc1f2420213cc910155a0392a0771dc6c8b62bb4 -address
/run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtimeroot /var/run/docker/runtime-runc
root 2958 2234 0 21:07 pts/0 00:00:00 docker run -it --network host
--rm tomcat:8.5.56-jdk8-openjdk
root 3002 2334 0 21:07 ? 00:00:00 containerd-shim -namespace
moby -workdir
/var/lib/containerd/io.containerd.runtime.v1.linux/moby/98292aa170640508f26e1a35
0e59fb80778487a38d79c099ba7396faf5e91231 -address
/run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtimeroot /var/run/docker/runtime-runc
root 3099 3064 0 21:08 pts/1 00:00:00 grep --color=auto docker
[root@localhost ~]# ps -ef |grep nginx
root 2896 2881 0 21:05 ? 00:00:00 nginx: master process nginx -g
daemon off;
101 2933 2896 0 21:05 ? 00:00:00 nginx: worker process
root 3109 3064 0 21:08 pts/1 00:00:00 grep --color=auto nginx
[root@localhost ~]# ps -ef |grep tomcat
root 2958 2234 0 21:07 pts/0 00:00:00 docker run -it --network host
--rm tomcat:8.5.56-jdk8-openjdk
root 3018 3002 2 21:07 pts/0 00:00:01 /usr/local/openjdk-8/bin/java
-Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -
Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -
Djdk.tls.ephemeralDHKeySize=2048 -
Djava.protocol.handler.pkgs=org.apache.catalina.webresources -
Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -
Dignore.endorsed.dirs= -classpath
/usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -
Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -
Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap
start
root 3111 3064 0 21:08 pts/1 00:00:00 grep --color=auto tomcat

网络命名空间(network namespace)

通过网络命名空间,可以实现网络隔离。

网络命名空间为进程提供了一个完全独立的网络协议栈的视图,包括网络设备接口、IPv4和IPv6 协议 栈、IP路由表、防火墙规则、sockets等,这样每个容器的网络就能 隔离开来。
Docker采用虚拟网络设备(Virtual Network Device)的方式,将不同命名空间的网络设备连接到一 起。
默认情况下,容器中的虚 拟网卡将同本地主机上的docker0网桥连接在一起

IPC命名空间(IPC namespace)

进程间通信(Inter-Process Communitication, IPC)涉及的IPC资源包括常见的信号量,消息队列和共享内 存。
申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace中实际上包含了系统IPC标识符以及 实现POSIX消息队列的文件系统

PID Namespace和IPC Namespace可以组合起来一起使用,在同一个IPC namespace下的进程彼此可 见,不同IPC namespace下的进程则互相不可见

挂载命名空间(Mount namespace)

类似于chroot,将一个进程放到一个特定的目录执行。
挂载命名空间允许不同命名空间的进程看到的文件结构不同,这样每个命名空间中的进程所看到的文件 目录彼此被隔离。

Mount namespace通过隔离文件系统挂载点对隔离文件系统通过提供支持,是Linux支持的第一个 namespace。隔离后不同mount namespace中的文件结构发生变化也不互相影响,可以通 过 /proc/[pid]/mounts 查看到所有挂载在当前namespace中的文件系统,还可以通 过 /proc/[pid]/mountstats 查到mount namespace中文件设备的统计信息,包括挂载文件的名字、 文件系统、挂载位置等

UTS命名空间(UTS namespace)

UTS(UNIX Time-sharing System)命名空间允许每个容器拥有独立的主机名和域名,从而可以虚拟出 一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样
这样每个Docker容器就可以拥有独立的主机名和容器了,在网络上可以被视作一个独立的节点,而非宿 主机上的一个进程

Docker中,每个镜像基本上都以自身提供的服务名来命名镜像的hostname,而且不会对宿主机产生任何 影响,其原理就是利用了UTS namespace
默认情况下,Docker容器的主机名就是返回的容器ID

用户命名空间(user namespace)

每个容器可以有不同的用户和组id,也就是说可以在容器内使用特定的内部用户执行程序,而非本地系 统上存在的用户。

每个容器内部都可以有root帐号,但跟宿主主机不在一个命名空间。

通过使用隔离的用户命名空间可以提高安全性,避免容器内进程获取到额外的权限。

资源配额限制(CGroups)

控制组(CGroups)是Linux内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有能控 制分配到容器的资源,才能避免多个容器同时运行时对宿主机系统的资源竞争
控制组可以提供对容器的内存、CPU、磁盘IO等资源进行限制和管理

默认情况下,容器没有资源限制,并且可以使用主机内核调度程序允许的尽可能多的给定资源, 这可以在 我们查看容器状态时看到 docker stats 容器ID 。Docker提供了一些方法来控制容器可以使用多少内 存或CPU,在设置 docker run 命令的运行时配置标志。

CPU

docker run --cpus 1.5 --rm -d --network host nginx:latest

内存

大部分的选项取正整数,跟着一个后缀 b , k , m , g ,,表示字节,千字节,兆字节或千兆字节。

[root@localhost ~]# docker run --rm -d --network host -m 100m nginx:latest
6491a0d526a44241c744af0bf9a43d5ba80705d053cc446f051857193db40632

磁盘读写

Block I0指的是磁盘的读写,docker 可通过设置权重、限制bps和iops 的方式控制容器读写磁盘的带宽

docker run -it --device-write-bps /dev/sda:30MB ubuntu

总结如下表:

联合文件系统(UnionFS)

联合文件系统(UnionFS)是一种轻量级的高性能分层文件系统,它支持将文件系统中的修改信息作为 一次提交,并层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,应用看到的是挂载的最终 结果

UnionFS可以把文件系统上多个目录(也叫分支)内容联合挂载到同一个目录下,而目录的物理位置是分开 的

联合文件系统是实现Docker镜像的技术基础。Docker镜像可以通过分层来进行继承

Docker存储

Docker目前通过插件化方式支持多种文件系统后端。
Debian/Ubuntu 上成熟的AUFS(Another Union File System,或v2版本往后的Advanced Multilayered Unification File System),就是一种联合文件系统实现
AUFS支持为每一个成员目录(类似Git的分支)设定只读(readonly)、读写(readwrite)或写出 (whiteout-able)权限
同时 AUFS里有一个类似分层的概念,对只读权限的分支可以在逻辑上进行增量地修改(不影响只读部 分的)

Docker镜像自身就是由多个文件层组成,每一层有唯一的编号(层 ID)。
当Docker利用镜像启动一个容器时,将在镜像文件系统的最顶端再挂载一个新的可读写的层给容器。容 器中的内容更新将会发生在可读写层

通过docker history查看一个镜像由哪些层组成。

docker history nginx:latest

多种文件系统比较

Docker目前支持的联合文件系统种类包括AUFS、OverlayFS、 btrfs、vfs、zfs和Device Mapper等,如 下表

通过docker info 的Storage Driver可以看到

容器网络

Docker的本地网络实现其实就是利用了Linux上的网络命名空间和虚拟网络设备(veth pair)
要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)与外界相通,并可以收发数据包
如果不同子网之间要进行通信,需要额外的路由机制
Docker中的网络接口默认都是虚拟的接口
Docker容器网络在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)

Docker的网络定义

  • bridge:默认值,在Docker网桥docker0上为容器创建新的网络栈。
  • host:对于独立容器,不要将容器网络放到隔离的命名空间中,然后直接使用宿主机的网络(宿主 机IP)。 host 仅可用于Docker 17.06及更高版本上的集群服务
  • overlay:覆盖网络将多个Docker守护程序连接在一起,并使群集服务能够相互通信。您还可以使 用覆盖网络来促进群集服务和独立容器之间或不同Docker守护程序上的两个独立容器之间的通信。 这种策略消除了在这些容器之间进行操作系统级路由的需要
  • macvlan:Macvlan网络允许您将MAC地址分配给容器,使其在网络上显示为物理设备。Docker守 护程序通过其MAC地址将流量路由到容器。 macvlan 在处理希望直接连接到物理网络而不是通过 Docker主机的网络堆栈进行路由的旧应用程序时,使用驱动程序有时是最佳选择
  • none:对于此容器,禁用所有联网。通常与自定义网络驱动程序(user_defined_network)一起 使用
  • 网络插件:可以在Docker上安装和使用第三方网络插件。

查看已经安装的驱动

docker network ls

使用默认网络

docker run --rm -d --name my_nginx nginx

使用宿主机网络

docker run --rm -d --network host --name my_nginx nginx

使用物理网卡模式

[root@localhost ~]# docker network create -d macvlan --subnet=172.16.206.0/24 --
gateway=172.16.206.1 -o parent=eth0 my-macvlan-net
e34965e7359cd9ac81f534a44b626b64848fc19a165fbcb9e58c0d9d0f31e1dd
[root@localhost ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
0ecdc79fec3d bridge bridge local
5e417b84c226 host host local
e34965e7359c my-macvlan-net macvlan local
0274a191bbcf none null local
[root@localhost ~]# docker run -it --rm -d --network my-macvlan-net nginx
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
eadc1ccec8da nginx "/docker-entrypoint.…" 7 seconds ago
Up 6 seconds peaceful_mclaren
[root@localhost ~]# docker inspect eadc1ccec8da
[
.....
"NetworkSettings": {
"Bridge": "",
"SandboxID":
"fe5becb769eefe6f78c458b3781dccd5b384fda4bba3bf251fabc7488c330020",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/fe5becb769ee",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {
"my-macvlan-net": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"eadc1ccec8da"
],
"NetworkID":
"e34965e7359cd9ac81f534a44b626b64848fc19a165fbcb9e58c0d9d0f31e1dd",
"EndpointID":
"505faeca525c90baed4a1b5c3427bfcde50e3dfa9e5f0e1134eeed26a1d6d026",
"Gateway": "192.168.72.1",
"IPAddress": "192.168.72.2",
"IPPrefixLen": 24,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:c0:a8:48:02",
"DriverOpts": null
}
}
}
}
]
#删除网络
docker network rm e3

这样我们的启动的容器就有了单独的Mac地址,并且可以在服务器端找的到了

存储卷

默认情况下,在容器内创建的所有文件都存储在可写容器层上。这样做问题有:

  • 当该容器不再存在时,数据将不会持久保存,如果另一个进程需要它,则可能很难从容器中取出数 据
  • 容器的可写层与运行容器的主机紧密耦合。不能轻易地将数据移动到其他地方
  • 写入容器的可写层需要存储驱动程序来管理文件系统。性能比直接写入主机要低

Docker为容器提供了两个选项来将文件存储在主机中,以便即使容器停止后文件也可以持久存储:

  • 数据卷(Data Volumes):容器内数据直接映射到本地主机环境
  • 数据卷容器(Data Volume Containers):使用特定容器维护数据卷,也成为bind mounts(绑定 挂载)

如果在Linux上运行Docker,则还可以使用 tmpfs 挂载。如果在Windows上运行Docker,则还可以使用 命名管道

数据卷(Data Volumes)

概念

由Docker创建和管理
是一个可供容器使用的特殊目录,它将主机操作系统目录直接映射进容器,类似于Linux中的mount操 作

特性

  • 可以在容器之间共享和重用
  • 对数据卷内数据的修改会立马生效
  • 对数据卷的更新不会影响镜像
  • 卷会一直存在,直到没有容器使用,可以安全地卸载它

创建方式

显式创建卷:docker volume create
在容器内创建卷:docker run -v

使用场景

在多个运行中的容器之间共享数据
当要将容器的数据存储在远程主机或云提供商上时,而不是在本地
当需要将数据从一个Docker主机备份,还原或迁移到另一个Docker主机时

创建和管理卷

创建一个卷:

$ docker volume create my-vol

清单数量:

$ docker volume ls
local my-vol

检查体积:

$ docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]

删除卷:

$ docker volume rm my-vol

启动一个volume的容器

如果使用尚不存在的卷启动容器,则Docker将创建该卷。以下示例将卷 myvol2 挂在到容器的 /app 目 录
下面的 -v 和 --mount 示例产生相同的结果。不能同时运行它们,除非在运行第一个 devtest 容器和 myvol2 卷之后将其删除。

$ docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest

使用 docker inspect devtest 验证创建卷并安装正确。查找 Mounts 部分:

docker inspect devtest
"Mounts": [
{
"Type": "volume",
"Name": "myvol2",
"Source": "/var/lib/docker/volumes/myvol2/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],

这表明安装是一个卷,它显示了正确的源和目标,并且该安装是可读写的。
停止容器并卸载卷。卷删除是一个单独的步骤。

$ docker container stop devtest
$ docker container rm devtest
$ docker volume rm myvol2

数据卷容器(Data Volume Containers)

概念

数据卷容器也成为绑定挂载,使用绑定安装时,主机上的文件或目录将安装到容器中。该文件或目录不 需要在Docker主机上已经存在。如果尚不存在,则按需创建
可以通过容器中运行的进程来更改主机文件系统 ,包括创建,修改或删除重要的系统文件或目录

使用场景

  • 多个容器之间共享一些持续更新的数据
  • Docker主机上的开发环境和容器之间共享源代码或构建工件

创建和管理数据卷容器

创建容器

docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app \
nginx:latest

使用docker inspect devtest验证绑定安装正确创建。查找Mounts部分:

docker inspect devtest
"Mounts": [
{
"Type": "bind",
"Source": "/root/target",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
$ docker container stop devtest
$ docker container rm devtest

使用只读绑定安装
对于某些开发应用程序,容器需要写入绑定安装,因此更改将传播回Docker主机。在其他时间,容器仅 需要读取访问权限。
此示例修改了上面的示例,但ro通过在容器中的安装点之后添加到(默认为空)选项列表中,将目录作 为只读绑定安装进行安装。如果有多个选项,请用逗号分隔。

启动容器

$ docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app:ro \
nginx:latest

使用docker inspect devtest验证绑定安装正确创建。查找Mounts部分:

docker inspect devtest
"Mounts": [
{
"Type": "bind",
"Source": "/root/target",
"Destination": "/app",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
}
],

tmpfs挂载

概念

tmpfs挂载仅存储在主机系统的内存中,不会写入主机系统的文件系统中
不会持久化在磁盘上,无论是在Docker主机上还是在容器内

使用场景

可以使用它来存储非持久状态或敏感信息

在容器中使用tmpfs挂载

1) 要在容器中使用 tmpfs ,请使用 --tmpfs 标志,或将 --mount 标志与 type=tmpfs 和 destination 选项一起使用。没有 source 了 tmpfs 标志。以下示例在Nginx容器中的上创建一个 tmpfs 安 装 /app

$ docker run -d \
-it \
--name tmptest \
--tmpfs /app \
nginx:latest

2) tmpfs通过运行docker container inspect tmptest并查找以下Mounts部分来验证安装是否为安 装:

docker container inspect tmptest
"Tmpfs": {
"/app": ""
},

3) 卸下容器:

$ docker container stop tmptest
$ docker container rm tmptest

指定tmpfs选项

tmpfs安装允许两个配置选项,都不是必需的。如果需要指定这些选项,则必须使用--mount标志,因为 该--tmpfs标志不支持它们。
tmpfs-size : tmpfs安装的大小(以字节为单位)。默认情况下不受限制。
tmpfs-mode: tmpfs的文件模式(八进制)。例如1770 (不是全局可读)

以下示例将设置tmpfs-mode为1770。

docker run -d \
-it \
--name tmptest \
--mount type=tmpfs,destination=/app,tmpfs-mode=1770 \
nginx:latest

Docker高级实战

DockerFile

DockerFile是一个文本格式的配置文件,用户可以使用DockerFile来快速创建自定义的镜像。

DockerFile基本结构

Dockerfile由一行行命令语句组成,并且支持以#开头的注释行
Dockerfile分为四部分:基础镜像信息、维护者信息、 镜像操作指令和容器启动时执行指令

比如:

# This Dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# Base image to use, this must be set as the first line
FROM ubuntu:18.04
MAINTAINER docker_user docker_user@email.com
COPY . /app
RUN make /app
CMD python /app/app.py

每条指令创建一层:

  • FROM : 指明所基于的镜像名称,从ubuntu:18.04Docker镜像创建一个图层。
  • MAINTAINER:维护者信息,docker_user维护(可以不写)
  • COPY : 从Docker客户端的当前目录添加文件,镜像操作指令
  • RUN : 构建镜像时执行make命令,每运行一条RUN指令,镜像就添加新的一层,并提交(添加可写 层)
  • CMD : 指定在容器中运行什么命令,用来指定运行容器时的操作命令

Docker镜像由只读层组成,每个只读层代表一个Dockerfile指令。这些层是堆叠的,每个层都是上一层 的变化的增量

Docker可以通过读取Dockerfile指令来自动构建镜像

完整的小例子:

#在一个空目录下,新建一个名为 Dockerfile 文件
mkdir /usr/dockerfile -p
vim dockerfile-demo
#编辑 dockerfile
FROM nginx:latest
# 维护者 可以省略
MAINTAINER gavinli gavinli@docker.com
#启动容器
RUN mkdir /usr/share/nginx/html/ -p
RUN echo Hello DockerFile! > /usr/share/nginx/html/demo.html
#构建镜像 . : 根据当前上下文环境构建
docker build -f dockerfile-demo -t lagou/nginx:v1 .
#运行
docker run --rm -d -it --network host lagou/nginx:v1

浏览器访问

DockerFile指令详解

DockerFile命令官方文档

常见命令详解:

FROM

指定所创建镜像的基础镜像,如果本地不存在,则默认会去Docker Hub下载指定镜像。
命令格式如下:

FROM [--platform=<platform>] <image> [AS <name>]

Or

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

Or

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

平时不用这么复杂的,只需如下即可

FROM <image>[:<tag>]
FROM centos:7.6.1810

任何Dockerfile中的第一条指令必须为FROM指令。并且,如果在同 一个Dockerfile中创建多个镜 像,可以使用多个FROM指令(每个镜像一 次)。

MAINTAINER

指定维护者信息,格式为MAINTAINER。可以不写

MAINTAINER gavinli gavinli@docker.com

该信息会写入生成镜像的Author属性域中

RUN

运行指定命令

格式为

1、RUN

默认将在shell终端中运行命令,即/bin/sh-c

2、RUN["executable","param1","param2"]。

指令会被解析 为Json数组,因此必须用双引号。
exec执行,不会启动shell环境
指定使用其他终端类型可以通过此方式实现,例如

RUN["/bin/bash","-c","echo hello"]

每条RUN指令将在当前镜像的基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \来换行
在shell形式中,可以使用\(反斜杠)将一条RUN指令继续到下一行。例如,考虑以下两行:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

它们在一起等效于以下这一行:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

要使用'/bin/sh'以外的其他 shell ,请使用 exec 形式传入所需的 shell 。例如:

RUN ["/bin/bash", "-c", "echo hello"]

注意
在JSON格式中,必须转义反斜杠。在Windows中,反斜杠是路径分隔符,这一点尤其重要。 由于无效的JSON,以下行否则将被视为shell形式,并以意外的方式失败:

RUN ["c:\windows\system32\tasklist.exe"]

此示例的正确语法为:

RUN ["c:\\windows\\system32\\tasklist.exe"]

CMD

CMD指令用来指定启动容器时默认执行的命令。它支持三种格式:
CMD["executable","param1","param2"]使用exec执行,是推荐使用的方式;

CMD [ "sh", "-c", "echo $HOME" ]

CMD command param1 param2在/bin/sh中执行,提供给需要交互的应用;

CMD echo $HOME

CMD["param1","param2"]提供给ENTRYPOINT的默认参数

CMD [ "echo", "$HOME" ]

每个Dockerfile只能有一条CMD命令。如果指定了多条命令,只有最后一条会被执行
如果用户启动容器时手动指定了运行的命令(作为run的参数), 则会覆盖掉CMD指定的命令

docker run --rm centos ping 192.168.1.1

COPY

复制本地主机的下的内容到镜像中的路径下。目标路径不存在时,会自动创建

格式如下:

COPY
src:为Dockerfile所在目录的相对路径、文件或目录
dest:镜像中的目标路径,相对和绝对都可以

支持通配符和正则

COPY hom* /mydir/
COPY hom?.txt mydir/

当使用本地目录为源目录时,推荐使用COPY

ENV

指定环境变量,在镜像生成过程中会被后续RUN指令使用,在镜像启动的容器中也会存在。
格式为ENV或ENV=...
ENV 指令有两种形式

    null
  • 第一种形式 ENV 会将一个变量设置为一个值。第一个空格之后的整个字符串 将被视为 -包括空格字符。该值将为其他环境变量解释,因此如果不对引号字符进行 转义,则将其删除。
    null
  • 第二种形式 ENV = ... 允许一次设置多个变量。请注意,第二种形式在语法中 使用等号 = ,而第一种形式则不使用等号 = 。与命令行解析一样,引号和反斜杠可用于在值 中包含空格。

例如:

ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

是一样的

ENV 从结果镜像运行容器时,使用设置的环境变量将保留。可以使用查看值 docker inspect , 并使用更改它们 docker run --env = 。

docker run --env<key>=<value> built_image

ADD

该命令将复制指定的路径下的内容到容器中的路径下

格式为ADD

ADD hom* /mydir/
ADD hom?.txt /mydir/
ADD test.txt /absoluteDir/
ADD test.txt relativeDir/

其中可以是Dockerfile所在目录的一个相对路径(文件或目录),也可以是一个URL,还可以是一 个tar文件(如果为tar文件,会自 动解压到路径下)。可以是镜像内的绝对路径,或者相对 于工作目录(WORKDIR)的相对路径

与COPY的区别

  1. Dockerfile中的COPY指令和ADD指令都可以将主机上的资源复制或加入到容器镜像中,都是在 构建镜像的过程中完成的。
  2. COPY指令和ADD指令的区别在于是否支持从远程URL获取资源。COPY指令只能从执行docker build所在的主机上读取资源并复制到镜像中。而ADD指令还支持通过URL从远程服务器读取资源并 复制到镜像中。
  3. 满足同等功能的情况下,推荐使用COPY指令。ADD指令更擅长读取本地tar文件并解压缩
  4. 要读取URL远程资源的时候,并不推荐使用ADD指令,而是建议使用RUN指令,在RUN指令 中执行wget 或curl命令

ENTRYPOINT

指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的 参数

ENTRYPOINT有两种形式:

  • 在EXEC的形式,这是优选的形式
    ENTRYPOINT ["executable", "param1", "param2"] , 这种执行的是 executable param1 param2
  • 带壳形式:
    ENTRYPOINT command param1 param2 。这中其实执行的是 /bin/sh -c command param1 param2

多个 ENTRYPOINT 只有最后一条生效

ENTRYPOINT ["echo", "4"]
ENTRYPOINT ["echo", "5"]
ENTRYPOINT ["echo", "6"]

VOLUME

创建一个数据卷挂载点
格式为VOLUME["/data"]

FROM centos:7.6.1810
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
RUN echo "hello" > /myvol/greeting

可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要保存的数据等

WORKDIR

为后续的RUN、CMD和ENTRYPOINT指令配置工作目录
格式为WORKDIR/path/to/workdir
可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则 会基于之前命令指定的路径

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

最终的输出pwd命令这 Dockerfile 将是 /a/b/c

EXPOSE

声明镜像内服务所监听的端口
格式为EXPOSE[...]
EXPOSE 指令通知 Docker 容器在运行时监听指定的网络端口。可以指定端口是侦听 TCP 还是 UDP ,如果未指定协议,则默认值为 TCP 。

EXPOSE 22 80 8443

默认情况下, EXPOSE 假定为 TCP 。还可以指定 UDP :

EXPOSE 80/udp

要同时在TCP和UDP上公开,请包括以下两行:

EXPOSE 80/tcp
EXPOSE 80/udp

无论`EXPOSE`设置如何,都可以在运行时使用该`-p`标志覆盖它们。例如

docker run -p 80:80/tcp

DockerFile创建镜像

编写完成Dockerfile之后,可以通过docker build命令来创建镜像。
基本的格式为docker build[选项]内容路径,该命令将读取指定路径下(包括子目录)的Dockerfile,并 将该路径下的所有内容发送给Docker服务端,由服务端来创建镜像
如果使用非内容路径下的Dockerfile,可以通过-f选项来指定其路径

$ docker build -f /path/to/a/Dockerfile .

要指定生成镜像的标签信息,可以使用-t选项

docker build -t lagou/ubuntu:v1 .

docker build 最后的 . 号,其实是在指定镜像构建过程中的上下文环境的目录

镜像管理

Docker镜像由一系列层组成。每层代表镜像的Dockerfile中的一条指令。除最后一层外的每一层都是只 读的。考虑以下Dockerfile:

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

该Dockerfile包含四个命令,每个命令创建一个层。
该 FROM语句首先从ubuntu:18.04镜像创建只读层。
该COPY命令从Docker客户端的当前目录添加一些文件。
该RUN命令使用命令构建您的应用程序make。

最后一层指定在容器中运行什么命令。
运行镜像并生成容器时,可以在基础层之上添加一个新的可写层("容器层")。对运行中的容器所做的 所有更改(例如写入新文件,修改现有文件和删除文件)都将写入此可写容器层。

容器和镜像之间的主要区别是可写顶层。在容器中添加新数据或修改现有数据的所有写操作都存储在此 可写层中。删除容器后,可写层也会被删除。基础镜像保持不变。

因为每个容器都有其自己的可写容器层,并且所有更改都存储在该容器层中,所以多个容器可以共享对 同一基础镜像的访问,但具有自己的数据状态。下图显示了多个共享相同Ubuntu 18.04镜像的容器

查看镜像的分层信息

docker history 镜像ID

磁盘上容器大小

要查看正在运行的容器的大致大小,可以使用以下 docker ps -s 命令。有两个不同的列与大小有关。

  • size :用于每个容器的可写层的数据量(在磁盘上)。
  • virtual size :容器使用的只读图像数据加上容器的可写层使用的数据量size。多个容器可以共 享部分或全部只读图像数据。从同一图像开始的两个容器共享100%的只读数据,而具有不同图像 的两个容器(具有相同的层)共享这些公共层。因此,您不能只对虚拟大小进行总计。这高估了总 磁盘使用量,可能是一笔不小的数目。

我们可以通过Docker仓库来传输我们的镜像,也可以通过文件模式

docker save 镜像ID -o xxxx.tar 或(docker save xxxx > xxxx.tar)
docker load -i xxxx.tar 或docker (docker load < xxxx.tar)
docker diff 容器ID
docker commit 容器ID svendowideit/testimage:version4 # 直接保存容器
docker commit --change='CMD ["apachectl", "-DFOREGROUND"]' -c "EXPOSE 80" 容器ID
svendowideit/testimage:version4 # 将正在运行的容器添加几个层之后再保存】

DockerFile模版

编写建议:

  • 从适当的基础镜像开始。例如,如果需要JDK镜像基于正式 openjdk 镜像
  • 使用多阶段构建。例如,可以使用该 maven 镜像来构建Java应用程序,然后将其重置为该 tomcat 镜像并将Java构件复制到正确的位置以部署我们的应用程序,所有这些操作都在同一 Dockerfile 中。这意味着的最终镜像不包括构建所引入的所有库和依赖项,而仅包括运行它们所需的工件和环 境。
  • 通过最小化Dockerfile中 RUN 单独命令的数量来减少镜像中的层数。可以通过将多个命令合并为 RUN 一行并使用Shell的机制将它们组合在一起来实现此目的。考虑以下两个片段。第一层在镜像 中创建两层,而第二层仅创建一层
#在镜像中创建两层
RUN apt-get -y update
RUN apt-get install -y python
#在镜像中创建一层
RUN apt-get -y update && apt-get install -y python
  • 如果有多个具有很多共同点的图像,请考虑使用共享的组件创建自己的基础镜像,Docker只需要加 载一次公共层,然后将它们缓存。这意味着我们的派生镜像将更有效地使用Docker主机上的内存, 并更快地加载。
CentOS镜像模版

得益于Docker镜像的分层技术,我们尽量将不变的构建指令放在Dockerfile较靠前的位置,常变的放在 Dockerfile较下层的位置,这样后续重新构建镜像是就可以使用之前构建镜像的只读层版本了
之所以yum分了很多个 RUN 指令,是因为可能后期仍会安装部分功能,而后期安装的 RUN 指令,可以用 之前的只读层,这样可以减少后续构建镜像的时间

注意:如果使用虚拟机,不要用NAT网络 要用网桥

dockerfile-centos

# 依据哪个镜像创建
From centos:7.6.1810
# 指定容器内部使用语言
ENV LANG="en_US.UTF-8"
ENV LC_ALL="en_US.UTF-8"
# 使用亚洲/上海时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 创建工作目录
RUN mkdir -p /data/apps /data/data /data/logs ; ln -s /data/apps /apps
# 安装字体
RUN yum install -y epel-release
RUN yum groupinstall -y "fonts"
RUN yum install -y kde-l10n-Chinese
# 安装openssl等依赖包
RUN yum install -y openssl openssl-devel
RUN yum install -y crontabs cronolog ntp
# 安装数据库依赖
RUN yum install -y mariadb-libs
RUN ln -s /usr/lib64/mysql/libmysqlclient.so.18
/usr/lib64/libmysqlclient_r.so.16
RUN yum install -y gcc cmake
RUN yum install -y lrzsz telnet net-tools file bind-utils less
RUN yum install -y jq xml2; yum clean all
RUN yum install -y expat-devel apr-devel ghostscript ghostscript-devel
# 运行容器时的默认命令
CMD ["/bin/bash"]

构建命令:

docker build -f dockerfile-centos -t lagou/centos/7.6/centos .
docker run --rm -it lagou/centos/7.6/centos
JDK镜像模版

dockerfile-jdk

FROM lagou/centos/7.6/centos
ENV JAVA_HOME="/apps/jdk"
ENV PATH=${JAVA_HOME}/bin:$PATH
ADD ./jdk-8u251-linux-x64.tar.gz /apps/
RUN ln -s /apps/jdk1.8.0_251 /apps/jdk
ADD ./UnlimitedJCEPolicyJDK8/US_export_policy.jar /apps/jdk/jre/lib/security/
ADD ./UnlimitedJCEPolicyJDK8/local_policy.jar /apps/jdk/jre/lib/security/
ADD ./msyhbd.ttf /apps/jdk/jre/lib/fonts/
ADD ./msyh.ttf /apps/jdk/jre/lib/fonts/
CMD ["/bin/bash"]

构建命令:

#下载jce_policy-8.zip并解压到当前目录
wget http://pkgs-linux.cvimer.com/jdk/1.8/jce_policy-8.zip
unzip jce_policy-8.zip
#下载jdk-8u251-linux-x64.tar.gz、msyh.ttf、msyhbd.ttf
wget http://pkgs-linux.cvimer.com/jdk/1.8/jdk-8u251-linux-x64.tar.gz
wget http://pkgs-linux.cvimer.com/fonts/msyh.ttf
wget http://pkgs-linux.cvimer.com/fonts/msyhbd.ttf
#构建
docker build -f dockerfile-jdk -t lagou/centos/7.6/jdk/1.8/jdk .
Tomcat镜像模版

dockerfile-tomcat

FROM lagou/centos/7.6/jdk/1.8/jdk
ENV TOMCAT_HOME="/apps/tomcat"
ENV PATH=${TOMCAT_HOME}/bin:$PATH
RUN yum install -y gcc make expat-devel apr-devel; yum clean all ; rm -rf
/var/cache/yum/*
ADD ./apr-1.6.5.tar.gz /root/
RUN cd /root/apr-1.6.5 && ./configure && make && make install && rm -rf
/root/apr*
ADD ./apr-util-1.6.1.tar.gz /root/
RUN cd /root/apr-util-1.6.1 && ./configure --with-apr=/usr/local/apr && make &&
make install && rm -rf /root/apr*
ADD ./apache-tomcat-7.0.93.tar.gz /data/apps/
RUN ln -s /apps/apache-tomcat-7.0.93 /apps/tomcat
RUN cd /apps/tomcat/bin; tar xf tomcat-native.tar.gz; cd
/apps/tomcat/bin/tomcat-native-1.2.21-src/native && \
./configure --with-apr=/usr/local/apr && make && make install
CMD ["/bin/bash"]

构建命令:

#下载包
wget http://pkgs-linux.cvimer.com/apr/1.6.5/apr-1.6.5.tar.gz
wget http://pkgs-linux.cvimer.com/apr/1.6.1/apr-util-1.6.1.tar.gz
wget https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.93/bin/apache-tomcat7.0.93.tar.gz
docker build -f dockerfile-tomcat -t
lagou/centos/7.6/jdk/1.8/tomcat/7.0.9/tomcat .

运行

docker run --rm -it --network host lagou/centos/7.6/jdk/1.8/tomcat/7.0.9/tomcat
#进入Docker
[root@teacher1 /]# cd apps
[root@teacher1 apps]# ls
apache-tomcat-7.0.93 jdk jdk1.8.0_251 tomcat
[root@teacher1 apps]# cd tomcat/
[root@teacher1 tomcat]# cd bin/
[root@teacher1 bin]# ./startup.sh
Using CATALINA_BASE: /apps/tomcat
Using CATALINA_HOME: /apps/tomcat
Using CATALINA_TMPDIR: /apps/tomcat/temp
Using JRE_HOME: /apps/jdk
Using CLASSPATH: /apps/tomcat/bin/bootstrap.jar:/apps/tomcat/bin/tomcatjuli.jar
Tomcat started.

访问:

多个容器可以在同一时间段内使用相同的卷。如果两个容器需要访问共享数据,这将很有用。例如,如 果一个容器写入而另一个容器读取数据。

Docker数据持久化

创建一个卷,待后边使用

docker volume create test_volume

分别启动2个容器挂在上卷

在2个终端窗口启动2个容器
docker run -it --rm -v test_volume:/test nginx:latest /bin/bash
docker run -it --rm -v test_volume:/test nginx:latest /bin/bash
cd /test;
touch a.txt
ls /test
# 在两个容器中我们均可以看到我们创建的文件,这样我们就可以做到了在多个容器之间实现数据共享

挂载在容器 /test 目录内创建。 Docker 不支持容器内安装点的相对路径。
多个容器可以在同一时间段内使用相同的卷。如果两个容器需要访问共享数据,这将很有用。例如,如 果一个容器写入而另一个容器读取数据。
卷名 在驱动程序test必须唯一。这意味着我们不能将相同的卷名与两个不同的驱动程序一起使用。
如果我们指定了当前test_volume程序上已在使用的卷名,则Docker会假定我们要重用现有卷,并且不 会返回错误。如果开始无 test_volume 则会创建这个卷

当然除了使用卷,也可以使用将宿主机的文件映射到容器的卷,命令类似,只不过不用提前创建卷,而 且数据会映射到宿主机上

docker run -it --rm -v /root/test_volume:/test centos /bin/bash

注意如果宿主机上的目录可以不存在,会在启动容器的时候创建