什么?!你还没有学习Docker?
可以说只要是后端开发基本都知道点Docker,但是仅仅知道那一点点是不够的,在交付逐渐以jar、war包的形式转为容器形式的趋势中,不管是开发还是运维人员都需要好好学习掌握Docker。
一、为什么会有Docker?
官方文档: https://docs.docker.com/engine/
Docker来自一家名叫“dotCloud”的公司,这家公司主要提供基于PaaS的云计算技术服务(具体来说,是和LXC有关的容器技术,LXC,就是Linux容器虚拟技术)。后来,dotCloud公司将自己的容器技术进行了简化和标准化,并命名为——Docker。
当然Docker诞生之初并没有人注意它,原因是dotCloud公司一直“捂着”它,最后因为经营不善,养不活又舍不得放弃,只能开源,让大家贡献意见和代码。
这一开源不要紧,大家都注意到了Docker的优点,随后Google、微软、Amazon、VMware等都参与进来不断改进和传播它,到现在已是最火的开源项目之一。
那Docker到底能干啥值得大家都这么喜欢它?Docker主要能实现以下功能:
-
快速交付和部署(镜像与容器)
-
资源的高效利用和隔离(高密度部署)
-
轻松的迁移和扩展(一次封装,到处运行)
这里对以上三点稍作解释,等后面介绍了镜像与容器等概念后会更加深刻理解Docker的功能与优点。
首先第一点,Docker有两个经典口号:Build, Ship and Run 和 Build once,Run anywhere。docker未出现及流行之前服务交付的时候都是jar包或者war包,这个大家都知道,jar包或者war包一般是不包含其运行环境的,比如你将你的服务打了一个war包,但是这个war包只能在JDK1.8及以上版本,那你把这个war交付出去,别人在JDK1.7环境基础上运行就会报错。还有我们开发和测试的经典对白 【开发:“不对啊,在我本地是好的啊” 测试:“在我这不行啊,不信你过来看!”】,本地环境运行的好好的,跑到测试环境就不行了这是我们经常遇到的。通过Docker就可以避免这种运行环境的差异,Docker把相关资源及环境一起打包成镜像,这个镜像运行起来的容器是无差别的,不会因为在不同的服务器上而不同,保证交付的一致性。
而且有了这个镜像后,下次再部署相同的服务时,不要再重头开始打包、配置等,只需要把镜像拿过来直接运行即可,保证交付的方便快速。
第二点,在Docker之前,应该说是以Docker为代表的容器技术之前,最火的是以VMWare和OpenStack为代表的虚拟机技术。本该继续光芒万丈的虚拟机技术因为容器技术的诞生变得没那么火了,主要是因为对比Docker和虚拟机发现了很多Docker的优点。
我们知道虚拟机就是在你的操作系统里面,装一个软件,然后通过这个软件,再模拟一台甚至多台“子电脑”出来。在“子电脑”里,你可以和正常电脑一样运行程序,例如开QQ。如果资源充裕,你可以变出好几个“子电脑”,里面都开上QQ。“子电脑”和“子电脑”之间,是相互隔离的,互不影响。
Docker这样的容器技术也是这种虚拟化技术的一种,只是更加“轻量化”。
轻量级给我们带来哪些好处?我们知道,虚拟机虽然可以隔离出很多“子电脑”,但占用空间更大,太过厚重,启动更慢。而容器技术恰好没有这些缺点。它不需要虚拟出整个操作系统,只需要虚拟一个小规模的环境(类似“沙箱”或者生物学上的“微型生态系统”)。它启动时间很快,几秒钟就能完成。而且,它对资源的利用率很高(一台主机可以同时运行几千个Docker容器)。此外,它占的空间很小,虚拟机一般要几GB到几十GB的空间,而容器只需要MB级甚至KB级。关于虚拟机和Docker容器之间的区别后面会详细说明。正因为如此,容器技术受到了热烈的欢迎和追捧,发展迅速。
第三点,Build once,Run anywhere,不是一句空口号。以盖房子为例子:你想建个房子,于是搬石头、砍木头、画图纸,一顿操作,终于把这个房子盖好了。结果,住了一段时间,你想搬到另一片空地去。这时候,按以往的办法,只能再次搬石头、砍木头、画图纸、盖房子。如果可以把盖好的房子复制一份,做成“镜像”,放在背包里。等到了另一片空地,就用这个“镜像”,复制一套房子,摆在那边,拎包入住,那岂不是太爽了。
上面的例子和面向对象、面向过程编程是有相似之处的。刚才例子里面,那个放在包里的“镜像”,就是Docker镜像。背包,就是Docker仓库。在空地上,用魔法造好的房子,就是一个Docker容器。
Docker镜像,是一个特殊的文件系统。它除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(例如环境变量)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。也就是说,每次变出房子,房子是一样的。每一个镜像可以变出一种房子。
二、Docker三个核心概念
镜像:
Docker镜像类似于虚拟机镜像,可以将他理解为一个只读的模板,一个独立的文件系统,包括运行容器所需的数据,可以用来创建新的容器。例如:一个镜像可以完全包含了Ubuntu操作系统环境,可以把它称作一个Ubuntu镜像。
Docker镜像是一种轻量级的、可执行的独立软件包,包含该软件运行的所有内容,比如:代码、运行时库、环境变量、配置等。
容器:
镜像跑起来就是容器,容器是从镜像创建的应用运行实例,可以将其启动、开始、停止、删除,而这些容器都是相互隔离、互不可见的。镜像和容器的关系就好比类和对象的关系,镜像就是类,容器就是对象(是这个类的一个实例)。
仓库:
Docker仓库(Repository)类似与代码仓库,是Docker集中存放镜像文件的场所。根据存储的镜像公开分享与否,Docker仓库分为公开仓库(Public)和私有仓库(Private)两种形式。目前,最大的公开仓库是Docker Hub,存放了数量庞大的镜像供用户下载。国内的公开仓库包括Docker Pool等。
如果你使用过git和github就很容易理解Docker的仓库概念。Docker 仓库的概念跟Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。仓库支持的操作类似git,当用户创建了自己的镜像之后就可以使用 push 命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 pull 下来就可以了。
三、Docker的C/S模式
在Docker Client中来运行Docker的各种命令,而这些命令会传送给在Docker的宿主机上运行的Docker的守护进程。而Docker的守护进程是负责来实现Docker的各种功能。Docker的守护进程运行在宿主机上,也就是我们常说的“C/S架构”的Server(服务)端,守护进程会在启动后,一直在后台运行,负责实现Docker的各种功能。而Docker的使用者并不会直接与守护进程进行交互,而是要通过Docker的客户端,也就是命令行接口,也就是我们在Shell中执行Docker命令时运行的二进制程序,它是Docker最主要的用户接口,用来从用户处接收Docker的命令,并且传递给守护进程;而守护进程将命令执行的结果返回给客户端,返显示在命令行接口中。
四、Docker常见命令
Docker的安装及配置镜像加速就不说明了,基本是傻瓜式操作,按步骤来即可。主要看常见的Docker命令,基本的命令都在下面的图里了:
4.1 镜像命令
1、docker images 查看本机所有的镜像
其中REPOSITORY是镜像名称或者仓库源,TAG为镜像版本标签,IMAGE ID为镜像ID。
2、docker search 搜索镜像
其中STARS是镜像的关注量, --filter是可选参数项,表示只列出STARS>=132的镜像。
3、docker rmi 删除镜像
4、docker pull 拉去远程仓库的镜像
这里不写版本则默认下载latest最新版。如果想下载指定版本镜像,去 https://hub.docker.com 搜索一下,在命令后面增加版本号即可:
4.2 容器命令
1、docker run 【参数】镜像名 启动镜像
这个命令有很多参数选项,可以使用docker run --help来查看一下:
-a, --attach=[] 登录容器(以docker run -d启动的容器)
-c, --cpu-shares=0 设置容器CPU权重,在CPU共享场景使用
--cap-add=[] 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cap-drop=[] 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cidfile="" 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset="" 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
-d, --detach=false 指定容器运行于前台还是后台
--device=[] 添加主机设备给容器,相当于设备直通
--dns=[] 指定容器的dns服务器
--dns-search=[] 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
-e, --env=[] 指定环境变量,容器中可以使用该环境变量
--entrypoint="" 覆盖image的入口点
--env-file=[] 指定环境变量文件,文件格式为每行一个环境变量
--expose=[] 指定容器暴露的端口,即修改镜像的暴露端口
-h, --hostname="" 指定容器的主机名
-i, --interactive=false 打开STDIN,用于控制台交互
--link=[] 指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[] 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
-m, --memory="" 指定容器的内存上限
--name="" 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
--net="bridge" 容器网络设置,待详述
-P, --publish-all=false 指定容器暴露的端口,待详述
-p, --publish=[] 指定容器暴露的端口,待详述
--privileged=false 指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="" 指定容器停止后的重启策略,待详述
--rm=false 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
--sig-proxy=true 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
-t, --tty=false 分配tty设备,该可以支持终端登录
-u, --user="" 指定容器的用户
-v, --volume=[] 给容器挂载存储卷,挂载到容器的某个目录
--volumes-from=[] 给容器挂载其他容器上的卷,挂载到容器的某个目录
-w, --workdir="" 指定容器的工作目录
一般容器启动并进入就用docker run -it 镜像 /bin/bash 即可。
2、docker ps 列出所有正在运行的容器
(1)docker ps
显示当前正在运行的容器。在 PORTS 一列,如果暴露的端口是连续的,还会被合并在一起,例如一个容器暴露了3个 TCP 端口:100,101,102,则会显示为 100-102/tcp。
(2) docker ps -a
显示所有状态的容器。容器的状态共有 7 种:created|restarting|running|removing|paused|exited|dead。
(3)docker ps -n 3
显示最后被创建的 n 个容器。注意,这里不限状态。
(4)docker ps -l
显示最后被创建的容器。相当于 docker ps -n 1。
(5)docker ps -q
只显示容器 ID 。清理容器时非常好用,filter 过滤显示一节有具体实例。
(6)docker ps -s
显示容器文件大小。该命令很实用,可以获得 2 个数值:一个是容器真实增加的大小,一个是整个容器的虚拟大小。
如果容器数量过多,或者想排除干扰容器,可以通过 --filter 或 -f 选项,过滤需要显示的容器。
1. 选项后跟的都是键值对 key=value (可不带引号),如果有多个过滤条件,就多次使用 filter 选项。例如:
docker ps --filter id=a1b2c3 --filter name=bingohuang
2. 相同条件之间的关系是或,不同条件之间的关系是与。例如:
docker ps --filter name=bingo --filter name=huang --filter status=running
以上过滤条件会找出 name 包含 bingo 或 huang 并且 status 为 running 的容器。
3. id 和 name,支持正则表达式,使用起来非常灵活。例如:
docker ps --filter name=^/bingohuang$
精确匹配 name 为 bingohuang 的容器。注意,容器实际名称,开头是有一个正斜线 / ,可用 docker inspect 一看便知。
docker ps --filter name=.*bingohuang.*
匹配 name 包含 bingohuang 的容器,和 --filter name=bingohuang 一个效果。
最后, 举一个复杂点的例子,用于清理名称包含 bingohuang,且状态为 exited 或 dead 的容器:
docker rm $(docker ps -q --filter name=.*bingohuang.* --filter status=exited --filter status=dead2>/dev/null)
3、退出容器
exit # 停止容器并退出
ctrl + P + Q # 不停止容器退出
4、删除容器
docker rm 容器id
docker rm -f $(docker ps -aq) #删除所有容器
docker ps -a -q|xargs docker rm #删除所有容器
5、容器启动、停止、重启
docker start 1372e080595e #启动容器
docker stop 1372e080595e #停止容器
docker restart 1372e080595e #重启容器
docker kill 1372e080595e #强制停止
4.3 其他常用命令
1、查看容器日志
2、docker top 查看容器中的进程信息
3、docker inspect 查看容器的元数据
4、进入正在运行的容器
有两种方式:
(1)docker exec -it 3f821cdd3dbb /bin/bash
(2) docker attach 3f821cdd3dbb
两种进入方式稍微有些区别:第一种是进入并开启新的终端,较常用;第二种是进入容器在原来的终端进行。
5、docker cp 容器id:容器内路径 宿主机路径 将容器的文件拷贝到宿主机
通过上面命令的介绍,可以部署个nginx镜像玩玩:
五、Docker镜像是怎么生成的?我又怎么发布自己的镜像?
前面各种Docker命令敲的很热闹,但是Docker镜像怎么生成的呢?我们怎么生成并提交自己的镜像呢?总不能一直使用别人的镜像吧?
前面介绍Docker三个核心概念时,已经说到了 Docker镜像是一种轻量级的、可执行的独立软件包,包含该软件运行的所有内容,比如:代码、运行时库、环境变量、配置等。要知道镜像的生成原理要先了解UnionFS(联合文件系统)。
5.1 UnionFS
UnionFS(联合文件系统)是一种分层、轻量级、高性能的文件系统,它支持对文件系统的修改,但是每修改一次就在原来文件系统上叠加一层提交。UnionFS是Docker镜像的基础,Docker镜像通过分层来继承,只要在之前的镜像上叠加即可。所以Docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFs
Docker镜像的最底层是Bootfs,Bootfs(boot-file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统,这一层与我们典型的Linux/unix系统是一样的,包含boot加载器和内核,当boot加载完成之后整个内核就能在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。Rootfs(root-file system),在bootfs之上,包含的就是典型Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件,rootfs就是各种不同操作系统的发行版,比如Ubuntu,Centos等等。对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序就可以了,因为底层直接用宿主机的内核,自己只需要提供rootfs就可以了,因此可见,对于不用的Linux发行版,bootfs基本是一致的,而rootfs会有差别,因此不同的发行版可以公用bootfs。
所有的Docker镜像都始于一个基础的镜像层,当对其进行修改或增加时,就会在当前镜像层上创建新的镜像层。后面介绍Dockerfile时,因为原生的centos镜像没有vim、ll等指令,我们可以在Dockerfile中对其进行修改,给原生的centos镜像增加一层镜像形成新的镜像,后面将演示。
Docker分层结构带来的好处是啥?那就是资源共享,比如多个镜像从相同的父镜像构建而来,那么宿主机只需在磁盘上保存一份父镜像,同时内存中也只需加载一份父镜像就可以为所有容器服务了,并且镜像的每一层都可以被共享。
另外,Docker镜像还有两个特点:①Docker镜像只读 ②当镜像实例为容器后,只有最外层是可写的。
5.2 生成自己的镜像
docker commit -m="关于这个镜像的描述" -a="作者信息" 容器id 生成的新的镜像名称:版本号
有了这个指令,如果你想要保存当前容器的状态,就可以把当前容器生成对应的镜像。
5.3 提交镜像到仓库
有了自己的镜像,怎么提交到远程镜像仓库呢?
docker镜像常用的仓库源:
网易 | https://c.163yun.com/hub#/m/home/ (需登录) |
阿里云 | https://cr.console.aliyun.com/cn-beijing/instances/images (需登录) |
DaoCloud | https://hub.daocloud.io/ |
Docker Hub | https://hub.docker.com/ |
Quay | https://quay.io/search |
腾讯 | https://cloud.tencent.com/document/product/1141/41811 |
这里以DockerHub演示。
5.3.1 DockerHub 推送
1、在DockerHub官网注册账号 https://hub.docker.com/,并新建镜像仓库
2、在自己服务器上登录
3、设置镜像的tag,与镜像仓库一致
4、执行push命令
在 DockerHub 已经有刚刚push的镜像。
六、容器数据卷
上面已经演示了容器的启动、进入以及如何从容器中拷贝文件到主机,假如我们遇到这样的场景:有一个Nginx容器,如果我们要去修改它的nginx.conf配置文件,每次都是进入容器中才能修改就太麻烦了,假如把容器内的某个路径映射到容器外的主机上的某个路径,只需要修改外部的文件内容,容器内部的文件就同步修改那该多好,这样就不用每次都进入容器了,省事省力。另外,假如数据只在容器中,一旦容器挂掉或者停掉,数据就会丢失,那有没有一种方式实现容器中的数据持久化呢?
当然有!那就是数据卷。
通过数据卷可以实现容器数据的持久化,以及容器间的数据共享。
6.1、怎么实现容器数据卷
其实很简单,就是挂载!
挂载,就是容器内的地址和外部地址联通,实现数据同步,即使容器不运行或者被删除,数据依然会被外部保留。
挂载有两种:具名和匿名。Docker中在容器启动时,通过-v (volume)参数来设置挂载路径。
举个例子:
这里在启动centos镜像时,将它生成的容器内部路径/home/映射到了外部主机的/home/okokok路径上,通过docker inspect命令的Mounts单元可以查看到这种映射关系。此外,外部主机的/home/路径下也自动生成了okokok目录。
在容器内/home新建666.txt文件,在主机的/home/okokok上也生成了666.txt。且即使停止或删除容器文件依然保留。
同理在外部修改的内容也会同步到容器内,这里在外部修改666.txt文件,在容器中查看也能查看到:
那可以把mysql容器的路径挂载到容器外面实现数据持久化。
6.2、具名挂载、匿名挂载、指定路径挂载
-v 容器内路径 #匿名挂载
-v 卷名: 容器内路径 #具名挂载
-v /宿主机路径:容器内路径 #指定路径挂载
没有指定路径时,默认都在/var/lib/docker/volumes/xxx/_data下,一般都是使用具名挂载。
七、Dockerfile
前面已经介绍了使用commit命令将当前状态的容器保存成镜像并提交,那是在已经有容器的基础上构建的,除了commit外,Dockerfile可以实现“从无到有”的构建镜像。
镜像是一层一层的,Dockerfile文件中的一行命令就会产生一层镜像层。我们可以先看一个简单的Dockerfile文件的编写:
这里build命令的-f参数是设置Dockerfile文件的位置的,若这里不命名成Dockerfile01而是直接命名为Dockerfile则不用设置其路径,会自动找这个文件。
7.1 如何编写Dockerfile
Dockerfile的编写一般遵循下面的规则:
(1)每个命令的关键字必须是大写
(2)从上到下顺序执行
(3)每条指令对应一个新的镜像层
每个指令关键字的含义如下:
7.2 Dockerfile例子
上面介绍了Dockerfile的编写规则,且知道了关键字的含义,可以动手丰富下原始centos镜像的功能,产生一个新的centos镜像。
我们知道直接下载的centos镜像是不包括vim等命令:
我们可以通过Dockerfile在原始centos的基础上增加vim命令,生成新的镜像,首先编写Dockerfile文件:
这里可使用docker history命令查看镜像的构建链路:
后面可以通过这个命令研究下其他镜像是怎么来的,有助于我们自己构建合适的镜像。
八、Docker0网络
Docker如何处理容器的网络访问?比如有两个容器,容器A要去访问容器B,该如何访问?使用127.0.0.1吗?还是写docker0地址?
我们先看下网路情况,linux下ip addr和windows的ipconfig类似,查看系统的网卡:
这里最后有个docker0网卡,Docker 服务启动后默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),使用的技术是evth-pair技术,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络,Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。我们在进入容器内部看下:
可以看到里面除了lo之外还有个74:eth0@if75,可以发现我们每个容器启动的时候会得到一个eth0,这个容器就是eth0@if75,这是Docker给它分配的。
再起一个centos镜像:
启动一个新的容器后可以看到又多了一个网卡:
现在进入刚刚启动的新容器,再ping一下之前的容器:
可以看到容器间是可以ping通的。
容器的网卡都是一对一对的,这就是前面说的evth-pair技术。evth-pair就是一对虚拟设备接口,它们都是成对出现的,一端连着协议,一端彼此相连,所以可以通信。evth-pair可以充当一个桥梁。
九、Springboot服务打包Docker镜像
以eureka服务为例,通过Dockerfile将其打包成Docker镜像,Dockerfile镜像内容为:
将jar包和Dockerfile文件上传服务器,最后执行build命令:
启动镜像试试:
正常访问,说明自己的镜像是管用的。
补充一:Docker容器和虚拟机对比
containers:
容器是在应用层的抽象化,多个容器能够运行在同一台机器上,和其他容器共享操作系统的核,每个容器运行都独立的运行在用户的空间内。容器需要的空间比虚拟机要小(容器镜像的大小一般为MBs级别的),容器能够处理更多的应用程序,并且需要更少的资源。
virtual machines:
虚拟机是物理硬件层的抽象化,让一个服务器转变成多个服务器。管理程序允许多个虚拟机运行在同一台机器上。每个虚拟机都包括操作系统,应用程序,必要的二字节文件和库(虚拟机的大小一般为GBs级别),虚拟机的启动也比较慢。
总结一下:
虚拟机是在硬件上实现虚拟化,需额外的虚拟机管理应用和虚拟机操作系统。Docker容器是在操作系统层面上实现虚拟化,直接复用本地主机操作系统,更加轻量级。
补充二:Docker可视化页面
Docker可视化工具Portainer 。
拉取,启动后即可。
访问web端: