Docker 核心技术与基本原理
1. Docker 简介
1.1 背景
首先,我们需要了解一下Docker
产生的背景。在我们的服务器中,可能需要部署多个Web
应用,这些Web
应用采用不同的JDK
版本、Tomcat
版本等。这样可能就会导致一个问题,当我们启动项目时,并没有按照我们的意愿来选择JDK
的版本。因为其他Web
应用的依赖配置对该应用产生了影响,每个应用之间的环境没有彼此隔离。
当然,我们如果细心一点也可以做到每个应用只使用其所依赖的环境,可参考如何在一台计算机上安装多个 JDK ,看了之后你可能觉得好麻烦啊,为什么不一台计算机部署一个项目,这样就不会由于彼此依赖的环境没有被隔离而产生影响。
很多时候,如果一台计算机上只部署一个项目,并不能够充分的利用CPU
和内存,极大的浪费了硬件资源。为此,产生了虚拟机,虚拟机便是利用虚拟化技术以及宿主机的硬件资源,从而实现一台虚拟化的计算机,其效果可以说是等同于一台真实的计算机。
从上图中可以看到,我们可以在一台计算机上开启 3
台虚拟机,这个时候便可以将项目部署到各自的虚拟机内。
笔者在使用虚拟机的时候,由于原本是4GB
的内存,开了三台虚拟机后真实的物理机变得极为卡顿,不得不加了内存条。同时,我们启动虚拟机的过程也极为缓慢,得好几分钟。随着分布式技术的发展,集群环境极为常见,我们对项目的一点改变,都需要使该项目所有的集群进行改变,非常繁琐,且效率低下。
正是由于以上的种种不足,产生了Docker
技术,总结为以下几点。
- 开发和运维之间因为环境不同而导致的矛盾
- 集群环境下每台机器可轻松部署相同的应用
DevOps
(流水线)
1.2 简介
Docker
是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux
机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。
Docker
是世界领先的软件容器平台。开发人员利用Docker
可以消除协作编码时"在我的机器上可正常工作"的问题。运维人员利用Docker
可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker
可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为 Linux
和 Windows Server
应用发布新功能。
1.3 Docker 优点
- 简化程序
Docker
让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux
机器上,便可以实现虚拟化。
Docker
改变了虚拟化的方式,使开发者可以直接将自己的成果放入Docker
中进行管理。方便快捷已经是Docker
的最大优势,过去需要用数天乃至数周的任务,在Docker
容器的处理下,只需要数秒就能完成。
- 避免选择恐惧症
如果你有选择恐惧症,还是资深患者。Docker
帮你打包你的纠结!比如Docker
镜像;Docker
镜像中包含了运行环境和配置,所以 Docker
可以简化部署多种应用实例工作。比如Web
应用、后台应用、数据库应用、大数据应用比如 Hadoop
集群、消息队列等等都可以打包成一个镜像部署。
- 节省开支
一方面, 云计算时代到来,使开发者不必为了追求效果而配置高额的硬件,Docker
改变了高性能必然高价格的思维定势。Docker
与云的结合,让云空间得到更充分的利用。不仅解决了硬件管理的问题,也改变了虚拟化的方式。
1.4 Docker 和虚拟机
特性 | Docker | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为MB | 一般为GB |
性能 | 接近原本 | 弱于原生 |
系统支持量 | 单机支持上千个 | 一般是几十个 |
2. Docker 架构
Docker
采用 C/S
架构,Client
通过接口与 Server
进程通信,从而实现容器的构建、运行和发布。
2.1 Host(宿主机)
安装了Docker
程序,并运行了Docker daemon
的主机。
2.1.1 Docker Daemon(Docker 守护进程)
运行在宿主机上,Docker
守护进程,用户通过Docker client
(Docker
命令)与Docker daemon
交互。
2.1.2 Images(镜像)
将软件环境打包好的模板,用来创建容器的,一个镜像可以创建多个容器。
镜像分层结构
位于下层的镜像称为父镜像(Parent Image
),最底层的称为基础镜像(Base lmage
)。
最上层为"可读写"层,其下的均为“只读”层。
2.1.3 Containers(容器)
Docker
的运行组件,启动一个镜像就是一个容器, 容器与容器之间相互隔离,并且互不影响。
2.2 Docker Client(Docker客户端)
Docker
命令行工具,用户是用Docker Client
与Docker daemon
进行通信并返回结果给用户,也可以使用其他工具通过Docker Api
与Decker doemon
通信。
2.3 Registry(仓库服务注册)
经常会和仓库(Repository
)混为一谈,实际上Registry
上可以有多个仓库每个仓库可以看成是一个用户, 一个用户的仓库放了多个镜像。仓库分为公开仓库和私有仓库。
最大的公开仓库是官方的Docker Hub
,国内也有如阿里云、时速云等,可以给国内用户提供稳定快速的服务。用户也可以在本地网络内创建一个私有仓库。 当用户创建了自己的镜像之后就可以使用 push
命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上pull
下来就可以了。
3. Docker 安装
Docker
提供了两个版本:社区版(CE
)和企业版(EE
)。
操作系统要求:
以Centos7
为例,且Docker
要求操作系统必须为64位,且Centos
内核版本为3.1
及以上。
查看系统内核版本信息:uname -r
3.1 准备
卸载旧版本:
yum list installed | grep docker
yum -y remove ......
卸载后将保留/var/lib/docker
的内容(镜像、容器、存储卷和网络等)。
rm -rf /var/lib/docker
1. 安装依赖软件包
yum insta11 -y yum-utils device-mapper-persistent-data lvm2
#安装前可查看device-mapper-persi stent-data和1vm2是否已经安装
rpm -qa | grep device-mapper-persistent-data
rpm -qa | grep lvm2
2. 设置 yum
源
yum-config-manager --add-repo https://down1oad.docker.com/1inux/centos/docker-ce.repo
3. 更新 yum
软件包索引
yum makecache fast
3.2 安装
安装最新版本 docker-ce
yum insta11 docker-ce -y
#安装指定版本docker-ce可使用以下命令查看
yum list docker-ce.x86_64 --showduplicates | sort -r
#安装完成之后可以使用命令查看
docker version
3.3 配置镜像加速
- 找到
/etc/docker
目录下的daemon.json
文件,没有则直接vi daemon.json
- 加入以下配置
#填写自己的加速器地址
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
- 通知 systemd 重载此配置文件
systemctl daemon-reload
- 重启 docker 服务
systemctl restart docker
4. Docker 常用操作
4.1 镜像常用操作
查找镜像
docker search 关键词
# 搜索 docker hub 网站镜像的详细信息
下载镜像
docker pull 镜像名:TAG
# TAG 表示版本,有些镜像的版本显式 latest,为最新版本
查看镜像
docker images
# 查看本地所有镜像
删除镜像
docker rmi -f 镜像ID或者镜像名:TAG
# 删除指定本地镜像
# -f 表示强制删除
获取元信息
docker inspect 镜像ID或者镜像名:TAG
# 获取镜像的元信息,详细信息
4.2 容器常用操作
运行
docker run --name 容器名 -i -t -p 主机端口:容器端口 -d -v 主机目录:容器目录:ro 镜像ID或镜像名:TAG
# --name 指定容器名,可自定义,不指定自动命名
# -i 以交互模式运行容器
# -t 分配一个伪终端,即命令行,通常 -it 组合来使用
# -p 指定映射端口,将主机端口映射到容器内的端口
# -d 后台运行容器
# -v 指定挂载主机目录到容器陌路,默认为 rw 读写模式,ro表示只读
容器列表
docker ps -a -q
# docker ps 查看正在运行的容器
# -a 查看所有容器(运行中、未运行)
# -q 只查看容器的ID
启动容器
docker start 容器ID或容器名
停止容器
docker stop 容器ID或容器名
删除容器
docker rm -f 容器ID或容器名
# -f 表示强制删除
进入正在运行容器
docker exec -it 容器ID或容器名 /bin/bash
# 进入正在运行的容器并且开启交互模式终端
# /bin/bash 是固有写法,作用是因为 docker 后台必须运行一个进程,否则容器就会退出,在这里表示启动容器后启动bash
# 也可以用 docker exec 在运行中的容器执行命令
查看日志
docker logs 容器ID或容器名
拷贝文件
docker cp 主机文件路径 容器ID或容器名:容器路径 # 主机中文件拷贝到容器中
docker cp 容器ID或容器名:容器路径 主机文件路径 # 容器中文件拷贝到主机中
获取容器元信息
docker inspect 容器ID或容器名
5. Docker 网络
Docker
允许通过外部访问容器或容器互联的方式来提供网络服务。
安装Docker
时,会自动安装一块Docker
网卡称为docker0
, 用于Docker
各容器及宿主机的网络通信,网段为172.0.0.1
。
Docker
网络中有三个核心概念:沙盒(Sandbox
) 、网络(Network
) 、端点(Endpoint
) 。
- 沙盒,提供了容器的虚拟网络栈,也即端口套接字、
IP
路由表、防火墙等内容。隔离容器网络与宿主机网络,形成了完全独立的容器网络环境。 - 网络,可以理解为
Docker
内部的虚拟子网,网络内的参与者相互可见并能够进行通讯。Docker
的虚拟网络和宿主机网络是存在隔离关系的,其目的主要是形成容器间的安全通讯环境。 - 端点,位于容器或网络隔离墙之上的洞,主要目的是形成-一个可以控制的突破封闭的网络环境的出入口。当容器的端点与网络的端点形成配对后,就如同在这两者之间搭建了桥梁,便能够进行数据传输了。
5.1 Docker的四种网络模式
Docker
服务在启动的时候会创建三种网络,bridge
、 host
和none
, 还有一种共享容器的模式container
。
5.1.1 Bridge
桥接模式,主要用来对外通信的,docker
容器默认的网络使用的就是 bridge
。
使用 bridge
模式配置容器指定的网络配置
# 配置容器的主机名
docker run --name t1 --network bridge -h [自定义主机名] -it --rm busybox
# 自定义 DNS
docker run --name t1 --network bridge --dns 114.114 -it --rm busy
# 给 host 文件添加一条
docker run --name t1 --network bridge --add--host [hostname]:[ip] -it --rm busy
5.1.2 Host
host
类型的网络就是主机网络的意思,绑定到这种网络上面的容器,内部使用的端口直接绑定在主机上对应的端口,而如果容器服务没有使用端口,则无影响。(不需要进行端口映射了)
docker run --name tomcat -d --network host tomcat
5.1.3 None
从某种意义上来说,none
应该算不上网络了,因为它不使用任何网络,会形成一个封闭的网络容。
5.1.4 container
共享一个容器的 network namespace
,和 host
模式差不多,只是这里不是使用宿主机网络,而是使用的容器网络。
5.2 开放端口
Docker0
为 NAT
桥,所以容器一般获得的是私有网络地址
给 docker run
命令使用 -p
选项即可实现端口映射,无需手动添加规则
-p
选项的使用-p <containerPort>
- 将指定的容器端口映射到主机所有地址的一个动态端口。
-p <hostPort>:<containerPort>
- 将容器端口
<containerPort>
映射到主机指定<ip>
的动态端口
- 将容器端口
-p <ip>::<containerPort>
- 将指定的容器端口
<containerPort>
映射到主机指定<ip>
的动态端口
- 将指定的容器端口
-p <ip>:<hostPort>:<containerPort>
- 将指定的容器端口
<containerPort>
映射到主机指定<ip>
的端口<hostPort>
- 将指定的容器端口
- 动态端口指随机端口,可以用使用
docker port
命令查看具体映射结果 -P
暴露所有端口(所有端口指构建镜像时EXPOSE
的端口)
自定义docker0
桥的网络属性信息:/etc/docker/daemon.json
文件
{
"bip":"192.168.1.5/24",
"fixed-cidr":"10.20.0.0/16",
"fixed-cidr-v6":"2001:db8::/64",
"mtu":1500,
"default-gateway":"10.20.1.1",
"dns":["10.20.1.2","10.20.1.3"]
}
核心选项为bip
,即bridge ip
,用于指定docker0桥自身的IP地址;其他选项可通过此地址计算出
创建自定义的桥
docker network create -d bridge --subnet "127.26.0.0/16" --gateway "127.26.0.1" mybr0
注意,对网络修改后,需要删除之前的容器,否则 docker 启动失败,因为之前容器的网络配置被修改。
6. 创建镜像
有时候从 Docker
镜像仓库中“下载的镜像不能满足要求,我们可以基于一个基础镜像构建一个自己的镜像
两种方式:
●更新镜像:使用docker commit
命令
●构建镜像:使用docker build
命令,需要创建Dockerfile
文件
6.1 更新镜像
先使用基础镜像创建一个容器, 然后对容器内容进行更改,然后使用docker commit
命令提交为一个新的镜像(以tomcat
为例)。
- 根据基础镜像,创建容器
docker run --name mytomcat -p 80:8080 -d tomcat
- 修改容器内容
docker exec -it mytomcat /bin/bash
cd webapps/R0OT
rm -f index. jsp
echo he11o world > index. htm1
exit
- 提交为新镜像
docker commit -m="描述消息" -a="作者” 容器ID或容器名镜像名:TAG
#例:
# docker commit -m="修改了首页" -a="wangzhao" mytomcat wangzhao/tomcat:v1.0
- 使用新镜像运行容器
docker run --name tom -p 8080:8080 -d wangzhao/tomcat:v1. 0
6.2 Dockerfile 构建镜像
6.2.1 Dockerfile 是什么
Dockerfile
是一个文本文档,其中包含用户可以在命令行上调用来构建镜像的所有命令。使用docker build
用户可以创建自动执行的构建,该构建可以连续执行多个命令行指令。
6.2.2 Dockerfile 格式
- 格式
- # 注释
- 指令
- 指令不区分大小写
- 但是约定为,指令是大写,参数是小写的形式
Dockerfile
中的指令按序执行Dockerfile
第一个指令必须是FROM
,用来指定构建镜像的基础镜像
6.2.3 使用 Dockerfile 构建 SpringBoot 应用镜像
一、准备
- 把你的 SpringBoot 项目打包成可执行 jar 包
- 把 jar 包上传到 linux 服务器
二、构建
- 在
jar
包路径下创建 Dockerfile 文件vi Dockerfile
#指定基础镜像,本地没有会从dockerHub pu11下来
FROM java:8
#从宿主机的当前目录下拷贝可执行jar包复制到基础镜像的根目录下
ADD xxxxx.jar /xxxxx.jar
#镜像要暴露的端口,如要使用端口,在执行docker run命令时使用-p生效
EXPOSE 80
#在镜像运行为容器后执行的命令
ENTRYPOINT ["java","-jar","/xxxxx.jar"]
- 使用
docker build
命令构建镜像,基本语法
docker build -t wangzhao/lemoncinema:v1 .
# -f 指定dockerfile 文件的路径
# -t 指定镜像名字和TAG
# . 指当前目录,这里实际需要一个上下文路径
三、运行
docker run --name lemoncinema -p 80:8080 664118cc3020
6.2.4 Dockerfile 常用指令
FROM
FROM
指令是最重要的一个并且必须为Dockerfile
文件的第一个非注释行,用于为镜像文件构建过程指定基础镜像,后续的指令运行于此镜像提供的运行环境。
这个基础镜像可以是任何可用镜像,默认情况下docker build
会从本地仓库找指定的镜像文件,如果不存在就会从Docker Hub
上拉取。
语法:
FROM <image>
FROM <image>:<tag>
MAINTAINER(将被废弃)
Dockerfile
的制作者提供的本人详细信息
Dockerfile
不限制 MAINTAINER
出现的位置,但是推荐放到FROM
指令之后
语法:
MAINTAINER <name>
name
可以是任何文本信息,一般用作者名称或者邮箱
LABEL(作用同MAINTAINER)
给镜像指定各种元数据
语法:
LABEL <key>=<value> <key>=<value> <key>=<value>...
一个Dockerfile
可以写多个LABEL
,但是不推荐这么做,Dockerfile
每一条指令都会生成一层镜像,如果LABEL
太长可以使用\
符号换行。
构建的镜像会继承继承镜像的LABEL
,并且会去掉重复的,但如果值不同,则后面的值会覆盖前面的值。
COPY
用于从宿主机复制文件到创建的新镜像文件
语法:
COPY <src>...<dest>
COPY ["<src>",..."<dest>""]
# <src>:要复制的源文件或者目录,可以使用通配符
# <dest>: 目标路径,即正在创建的 image 的文件系统路径:建议<dest>使用绝对路径,否则 COPY 指令则以WORKDIR为其起始路径
注意:如果你的路径中有空白符,通常会使用第二种格式
规则:
<src>
必须是build
上下文中的路径,不能是其父目录中的文件- 如果
<src>
是目录,则其内部我呢见或子目录会被递归复制,但<src>
目录自身不会被复制 - 如果指定了多个
<src>
,或在<src>
中使用了通配符,则<dest>
必须是一个目录,则必须以/
符号结尾 - 如果
<dest>
不存在,将会被自动创建,包括其父目录路径
ADD
基本用法和COPY
指令一样,ADD
支持使用TAR
文件和URL
路径
语法:
ADD <src>...<dest>
ADD ["<src>",..."<dest>"]
规则:
- 和
COPY
规则相同 - 如果
<src>
为URL并且<dest>
没有以/
结尾,则<src>
指定文件将被下载到<dest>
- 如果
<src>
为本地系统上的压缩格式的tar
文件,它会展开成一个目录;但是通过URL
获取的tar
文件不会自动解压 - 如果
<src>
有多个,直接或间接使用了通配符指定多个资源,则<dest>
必须是目录并且以\
结尾
WORKDIR
用于为 Dockerfile
中所有的RUN
、CMD
、ENTRYPOINT
、COPY
和ADD
指定工作目录,只会影响当前WORKDIR
之后的指令。
语法:
WORKDIR <dirpath>
在Dockerfile
文件中,WORKDIR
可以出现多次,路径可以是相对路径,但是它是相对于前一个WORKDIR
指令指定的路径。
另外,WORKDIR
可以是ENV
指定的变量。
VOLUME
用来创建挂载点,可以挂载宿主机上的卷或者其他容器上的卷。
语法:
VOLUME <mountpoint>
VOLUME ["<mountpoint>"]
不能指定宿主机中的目录,宿主机挂载的目录是自动生成的。
ENV
用来给镜像定义所需要的环境变量,并且可以被Dockerfile
文件中位于其后的其他指令(如ENV、ADD、COPY等)所调用,调用格式:$varivale_name
或者${variable_name}
语法:
ENV <KEY> <VALUE>
ENV <KEY>=<VALUE>...
第一种格式中,<key>
之后的所有内容都会被视为value
的组成部分,所以一次只能设置一个变量。
第一种格式可以一个设置多个变量,如果<value>
当中有空格可以使用\
进行转义或者对value
加引号进行标识,另外\
也可以用来续行
ARG
用法同ENV
语法:
ARG <name>[=<default value>]
指定一个变量,可以在docker build
创建镜像的使用,使用--build-arg <varname>=<value>
来指定参数
RUN
用来指定docker build
过程中运行指定的命令
语法:
RUN <command>
RUN ["<executable>", "<paraml>", "<param2>"]
第一种格式里面的参数一般是一个shell
命令,以/bin/sh -c
来运行它。
第二种格式中的参数是一个JSON
格式的数组,其中<executable>
是要运行的命令,后面是传递给命令的选项或者参数;但是这种格式不用用/bin/sh -c
发起,所以常见的shell
操作像变量替换和通配符替换不会进行;如果你运行的命令依赖shell
特性,可以替换成类型一下的格式
RUN ["/bin/bash", "-c", "<executable>", "<paraml>"]
CMD
容器启动时运行的命令
语法:
CMD <command>
CMD ["executable", "param1", "param2"]
CMD ["<param1>","<param2>"]
前两种语法和RUN
相同
第三种语法用于为ENTRYPOINT
指令提供默认参数
RUN和CMD区别
- RUN 指令运行于镜像文件构建过程中,CMD 则运行于基于Dockerfile构建的镜像文件启动为一个容器的时候
- CMD指令的主要目的在于给启动的容器指定默认要运行的程序,且在运行结束后,容器也将终止;不过,CMD命令可以被docker run 的命令行选项给覆盖
- Dockerfile中可以存在多个CMD指令,但是只有最后一个会生效
ENTRYPOINT
类似于CMD
指令功能,用于给容器指定默认运行程序
语法:
ENTRYPOINT <command>
ENTRYPOINT ["<executable>","<paraml>","<param2>"]
和CMD
不同的是ENTRYPOINT
启动的程序不会被docker run
命令指定的参数所覆盖,而且,这些命令行参数会被当做参数传递给ENTRYPOINT
指定的程序(但是,docker run
命令的--entrypoint
参数可以覆盖ENTRYPOINT
)
docker run
命令传入的参数会覆盖CMD
指令的内容并且附加到ENTRYPONT
命令作为其参数使用。
同样,Dockerfile
中可以存在多个ENTRYPOINT
指令,但是只有最后一个会生效。
Dockerfile
中如果既有CMD
又有ENTRYPOINT
,并且CMD
是一个完整的可执行命令,那么谁在最后谁生效。
ONBUILD
用来在 Dockerfile
中定义一个触发器
语法:
ONBUILD <instruction>
Dockerfile
用来构建镜像文件,镜像文件也可以当成是基础镜像被另外一个Dockerfile
用作FROM
指令参数。
在后面这个Dockerfile
中的FROM
指令在构建过程中执行的时候,会触发基础镜像里面的ONBUILD
指令。
ONBUILD
不能自我嵌套,ONBUILD
不会触发FROM
和MAINTAINER
指令。
在ONBUILD
指令中使用ADD
和COPY
要小心,因为新构建过程中的上下文在缺少指定的源文件的时候会失效。