看完此文,妈妈再也不会担心你Docker入不了门了


上周对象突然心血来潮说想养个小宠物,我问想养啥她又说随便,你看着办!这我真的比较难办啊!但是咱们程序员能有个对象就不错了,还不赶紧宠着,我只能照办咯!

我去到了一家宠物店,半天也没有找到合适的目标。正在我犹豫彷徨之时,看到了老板门口鱼缸里面的金鱼游来游去还挺顺眼!于是:

我:老板,金鱼多少钱?
老板:加鱼缸一起100块钱不讲价!
我:这……便宜一点咯!
老板:小伙子看你骨骼惊奇,定是个养鱼的奇才,2块钱卖给你吧!但是鱼缸可不能给你!
我:那,你帮我打包一条吧,帮我拿个袋子装着就好了!

于是我兴高采烈的拎着小金鱼就回家了,找了个大罐子养着!对象看到我买的小金鱼后露出了幸福的笑容~

第二天早上对象把我从睡梦中摇醒:“嘤嘤嘤,人家的小鱼动不了了,你赔~~~ ”。于是我很愤怒的跑去宠物店找老板索赔:

我:你们家卖的鱼有问题,回去就不行了!
老板:不可能,昨天在我们这都活蹦乱跳的!
我:就是你们家的鱼有问题!
老板:肯定是你自己买的鱼缸有问题!
我:手持两把锟斤拷,口中疾呼烫烫烫。
老板:脚踏千朵屯屯屯,笑看万物锘锘锘?

这一幕,似曾相识!像极了我们在开发中:

测试:xx,你的代码在生产环境上运行有问题。
我:不可能,我本地都运行得好好的。
测试:你自己上生产环境上看。
我:我不看,我的代码在本地没问题,肯定是运维的锅,你去找运维!

或许很多开发人员都有过上面的经历,程序在本地运行都很正常,一上到生产环境就崩了。这是因为程序跟小金鱼一样也会“水土不服”!而导致程序水土不服的原因一般就是环境和配置的差异!加上现在互联网高并发、大流量的访问,一个应用往往需要部署到集群的多台机器上,并且集群扩容缩容的需求也比较频繁。如果按照传统的方式部署,那每一台服务器上都需要装各种软件……然后进行各种配置……我仿佛看到了“工作996,生病ICU”在向运维工程师招手!

那有没有一种方案不仅能屏蔽环境的差异,并且还能快速部署呢?既然“水土不服”那我把程序及整个“水土”都打包迁移,就看你服不服。而Docker就是这样的一种让你服技术!

昂,上面的对象是我自己new的(* ̄︶ ̄)……

Hello Docker

Docker是什么呢?百度百科是这样跟我说的:Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。如果你想和更多微服务架技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

这段话已经很概括的描述了Docker是什么,Docker能干嘛,Docker的基本特性!相信刚开始接触Docker的你跟我也一样,看了几遍都很懵逼!没关系,看完全文再回头看这段话,或许就有不一样的体会了!我们先看看Docker官方给出的“定妆照”:


如果非要我用一句话描述这张图片,还在上幼儿园的我会说:“一条可爱的鲸鱼背着多个集装箱,畅游在大海里!”而现在我会说:“Docker是一个运行在操作系统上的软件,这个软件上面可以运行多个相互隔离的容器!”不同的表述,同一个意思!这条可爱的鲸鱼就是咱们的Docker,而大海就是我们的操作系统,多个集装箱就是在Docker上运行的容器!什么是容器咱们后面会说。

假如你想漂洋过海来看我,你可以选择自己造一条船,这样你就得自己备足很多干粮,还得准备很多一些其他的必需品才能出发!但是现在有一条鲸鱼游过来对你说,我这里有很多集装箱,里面有你所需要的一切,你选一个适合你的进来就可以了,我会带你乘风破浪的!

看到这里,你是否对Docker有个初步的印象了呢?至少知道了:1、Docker是什么?2、为什么需要Docker?

与传统虚拟机对比

前面我们说过Docker可以实现虚拟化,那Docker与我们平时用的虚拟机有什么区别和联系呢?在那些年我们还买不起云服务器的时候,如果我们想学Linux那就得先安装一个创建虚拟机的软件,然后在软件上面创建虚拟机,然后分配内存、分配磁盘、安装Linux操作系统等等一系列的操作,然后等个分把钟让虚拟机运行起来。

为什么传统虚拟机启动会那么慢呢?因为传统虚拟机技术是虚拟出一套硬件后,在其上面运行一个完整的操作系统,然后在该系统上面再运行所需要的应用程序,并且虚拟机的资源需要提前分配,一旦分配这些资源将全部被占用。但是Docker容器内的应用程序是直接运行于宿主的内核,容器没有自己的内核,更加不会对硬件进行虚拟。因此docker容器比传统的虚拟机更为轻便!但是Docker容器技术也是参考虚拟机一步一步的迭代优化过来的!我们来看看官方给出的Docker容器和传统虚拟机的对比图:


图中也能看出来,Docker就是一个运行在操作系统上的软件!以后如果想在Windows上面学习Linux,只需要在本地安装一个Windows版本的Docker,然后看完本文的剩下的部分,就能轻轻松松的玩转Linux啦!不过在Windows上安装Docker也需要先安装一个虚拟机。

基本组成要素

前面对Docker的基本概念有了个大致印象,但是到目前为止,可能你对Docker的认识还比较空泛,那下面部分我们就从Docker的基本组成要素来更深入的走进Docker!Docker是一个client-server的结构!先看看官网给出的架构图:


这张图里面概括了Docker的所有的元素!我们就逐一分析Docker客户端、Docker服务、仓库、镜像、容器等概念!

Docker客户端

最左边是Docker的客户端,类似我们操作MySQL的工具Navicat,只不过我们这里的是没有图形化界面的命令终端。Docker客户端是用户与Docker服务交互的窗口!我们能看到图中就是各种操作的命令!

Docker服务

中间的是Docker后台运行的服务,一个称为docker daemon的守护进程。可以理解为我们MySQL的服务,我们的操作命令都是在这部分进行处理!docker deamon监听着客户端的请求,并且管理着Docker的镜像、容器、网络、磁盘(图中只列出了镜像与容器)等对象。同样,Docker的客户端与服务可以运行在同一机器上,也可以用某台机器上的客户端远程连接另一台机器上的Docker服务,这跟我们的MySQL一样的呢。

仓库

右边部分是注册仓库,在远古时代做开发的都知道,我们以前需要一个第三方包的时候需要去网上下载对应的jar包,很麻烦不说,还容易下的包是不稳定的版本。有了Maven之后,我们只要在Maven配置文件中引入对应的依赖,就可以直接从远程仓库中下载对应版本的jar包了。Docker中的仓库与Maven的仓库是一个概念,可以远程下载常用的镜像,也可以push包到远程仓库(如图中的Redis、Nginx等镜像),同一个镜像又可以有多个版本,在Docker中称为tag!

镜像&容器

前面我们有多次提到镜像和容器,这是Docker里面很核心的两个概念。那镜像和容器分别是什么呢?镜像和容器的关系是什么呢?

镜像
官方给出的定义是:Docker镜像是一个只读模板,可以用来创建Docker容器。镜像是一种轻量级的、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件。它包含运行某个软件所需要的所有的内容,包括代码、运行时、库、环境变量、配置文件等。我们开发的Web应用需要JDK环境、需要Tomcat容器、需要Linux操作系统,那我们可以把我们所需要的一切都进行打包成一个整体(包括自己开发的Web应用+JDK+Tomcat+CentOS/Ubuntu+各种配置文件)。打包后的镜像在某台机器上能运行,那它就能够在任何装有Docker的机器上运行。

任何镜像的创建会基于其他的父镜像,也就是说镜像是一层套一层,比如一个Tomcat镜像,需要运行在CentOS/Ubuntu上,那我们的Tomcat镜像就会基于CentOS/Ubuntu镜像创建(在后面的操作部分我们可以通过命令查看),这样的结构就类似于我们吃的洋葱,如果你愿意一层一层一层地剥开我的心~


容器
官方给出的定义是:Docker的容器是用镜像创建的运行实例,Docker可以利用容器独立运行一个或一组应用。我们可以使用客户端或者API控制容器的启动、开始、停止、删除。每个容器之间是相互隔离的。上一步我们构建的镜像只是一个静态的文件,这个文件需要运行就需要变为容器,我们可以把容器看做是一个简易版的Linux系统和运行在其中的应用程序!就是前面看到的鲸鱼背上的一个一个的集装箱,每个集装箱都是独立的!

镜像与容器关系
上面的概念很抽象,可以理解为容器就是镜像的一个实例,相信大家都写过类似下面的代码:
public void Dog extends Animal{  
......
}  
......
Dog dog = new Dog()

我们在代码中定义了一个Dog类,这个类就相当于一个镜像,可以根据这个类new出很多的实例,new出来的实例就相当于一个个的容器。镜像是静态的文件,而容器就是有生命的个体!Dog类可以继承父类Animal,如果不显式的指定继承关系,Dog类就默认继承Object类。同样上面也说到过docker中的镜像也有继承关系,一个镜像可以继承其他的镜像创建,添加新的功能!

看到这里的你是不是对Docker有了更多的了解了呢?我们再回头看看百度百科对Docker的描述,可能你又会有更深的印象:

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。

容器数据卷

上面说到容器是一个简易版的Linux系统和运行在其中的应用程序,那我们的应用程序产生的数据(比如操作日志、异常日志、数据)也是在容器内的系统中存放的,默认不会做持久化,我们可以进入到容器中查看。但是万一有一天,Docker这条鲸鱼不满人类的压迫,反抗了……老子打烂你的集装箱!


随着容器的关闭,容器内的数据也会丢失,重新开启的容器不会加载原来的数据(简单说就是容器重新启动又是另外一个实例了)。那对容器内的数据需要持久化到宿主机上就很有必要了,这就需要了解我们的容器数据卷。

容器数据卷的设计目的就是做数据的持久化和容器间的数据共享,数据卷完全独立于容器的生命周期,也就是说就算容器关闭或者删除,数据也不会丢失。简单点说就将宿主机的目录挂在到容器,应用在容器内的数据可以同步到宿主机磁盘上,这样容器内产生的数据就可以持久化了。关于容器卷的命令我们后面会有操作实例。

命令操作

上面说了那么多,下面就到了咱们的实操环节啦!这一节的内容会通过一些常用的命令让大家更进一步的了解Docker,注意!!这里只是一些常用的命令来加深理解,而不是命令大全!如果没有安装Docker的小伙伴可以自己按照官网的文档进行安装,本文不会讲到这部分的内容!所以我假设你在自己的服务器上已经装好了Docker。

帮助命令

  1. docker version查看Docker客户端和服务的版本。
  2. docker info查看Docker的基本信息,如有多少容器、多少镜像、Docker根目录等等。
  3. docker –help查看Docker的帮助信息,这个命令可以查看所有Docker支持的命令。


这几个命令非常简单,有过一点Linux基础的小伙伴应该很容易理解。

镜像命令

docker images查看本地主机上所有的镜像。注意是本地主机的!这里能看到镜像的名称、版本、ID、大小等基本信息,注意这里的image ID是镜像的唯一标识!还可以通过docker images tomcat指定某个具体的镜像查看对应信息。这里还要注意的是CentOS的镜像才200MB的大小,比我们物理机器上装的CentOS要小得多的多,这是因为CentOS的镜像只保留了Linux核心部分,这也是为什么Docker虚拟化技术比虚拟机运行效率更高的原因!那为什么Tomcat的镜像这么大呢?那是因为我们之前说过我们的镜像就像一个洋葱一样,是一层套一层的!Tomcat的运行需要基于CentOS、JDK等等镜像,Tomcat在上层所以体积比较大啦。


docker rmi删除本地的镜像,如下图所示,可以加上-f参数进行强制删除。这里的rmi命令跟Linux中的删除命令就很像啦,只是这里加了一个i代表image。


docker search根据镜像名称搜索远程仓库中的镜像。


docker pull搜索到某个镜像之后就可以从远程拉取镜像啦,有点类似咱们Git中的pull命令,当然对应的还有个docker push的命令。如图,如果我们没有指定tag,默认就会拉取latest版本,也可以通过docker pull tomcat:1.7的方式拉取指定版本!注意这里在拉取镜像的时候打印出来的信息有很多,这也是前面说到的镜像是一层套一层,拉取一个镜像也是一层一层的拉取。

容器命令

通过镜像命令我们就能获取镜像、删除镜像等操作啦!镜像有了下面自然就需要通过镜像创建对应的实例啦,也就是我们的容器。下面我们以Tomcat为例:

docker run [OPTIONS] IMAGE [COMMAND] [ARG…] 可以基于某个镜像运行一个容器,如果本地有指定的镜像则使用本地镜像,如果没有则从远程拉取对应的镜像然后启动!由于这个命令非常重要,所以下面列出几个比较重要的参数:
  • -d:启动容器,并且后台运行(Docker容器后台运行,就必须要有一个前台进程,容器运行的命令如果不是一直挂起的命令,容器启动后就会自动退出);
  • -i:以交互模式运行容器,通常与-t同时使用;
  • -t:为容器重新分配一个伪输入终端,通常与-i同时使用(容器启动后进入到容器内部的命令窗口);
  • -P:随机端口映射,容器内部端口随机映射到主机的高端口;
  • -p:指定端口映射,格式为:主机(宿主)端口:容器端口;
  • -v:建立宿主机与容器目录的同步;
  • –name=”myTomcat”:为容器指定一个名称(如果不指定,则有个随机的名字);


 

上面我通过命令启动了一个Tomcat的容器,由于使用了 -t 的参数,所以容器启动后就进入到了容器的内部的命令窗口,打印了很多Tomcat启动的日志。并且使用 -p 参数指定了端口映射,也就是容器内Tomcat运行的端口是8080,并且映射到了宿主机上的8888端口,这样我们在外部就可以通过服务器的IP+8888端口访问到我们容器内部Tomcat部署的服务了。

前面我们提到过容器内的数据会随着容器的关闭而丢失。那我们就需要有容器数据卷的技术能将容器内的数据持久化到宿主机。这里需要用到 -v 参数。我们看下面的截图:


这里第一个要注意的是我们用的 -d 参数,启动后没有进入到容器内部,还是在宿主机。(可以对比一下与上面 -it 参数的区别)。第二个要注意的是 -v /宿主机:/容器内目录,实现了宿主机与容器内指定目录的数据同步。容器启动后就可以使用Linux的 ll 命令查看宿主机上已经同步到了容器内的文件。第三个要注意的是这里的同步是双向的,也就是说在宿主机上对文件的修改也会同步到容器内部。多个不同的容器映射到宿主机的同一个目录,就可以实现不同容器间的数据共享啦。


进入到容器后可以通过exit命令退出容器,也可以通过ctrl+P+Q快捷键退出容器,这两种方式的不同之处是exit会退出并且关闭容器,而ctrl+P+Q快捷键只是单纯的退出,容器还在运行,并且还能再次进入。

docker ps:我们可以通过该命令查看正在运行的容器的信息,这里能看到容器的唯一ID,启动时间等等……这里跟Linux的ps命令类似,所以也可以把容器理解为一个运行在Docker上的进程。docker ps -a可以查看运行中与停止的所有容器。


docker attach [OPTIONS] CONTAINER:上面说过通过ctrl+P+Q快捷键退出容器后容器还在后台运行,那如果想再次进入容器怎么办呢?我们就可以通过attach命令+容器的ID再次进入容器。

docker exec [OPTIONS] CONTAINER:这个命令与attach一样都可以再次进入后台运行的容器,但是该命令可以不进入容器而在运行的容器中执行命令!比attach更加强大!

docker stop docker kill docker restart:这三个命令分别用来停止容器、强制停止容器和重启容器,就跟我们在Linux上停止、强制停止和重启某个进程一样的啦,这里就不做演示了!

docker rm:使用这个命令就可以删除某个容器,这里跟删除镜像的区别是这里少了一个 i 啦。需要注意的是通过stop和kill停止的容器还存在于Docker中,而使用 rm 命令操作后的容器将不再存在。


docker inspect:查看容器的详情(也能查看镜像详情)。

Dockerfile

前面我们对Docker以及相关概念、常用命令有了基本的了解,我们也知道了可以从远程pull一个镜像,那远程的镜像是怎么来的呢?如果我们想自己创建一个镜像又该怎么做呢?

对,Dockerfile!Dockerfile是一个包含用户能够构建镜像的所有命令的文本文档,它有自己的语法以及命令,Docker能够从Dockerfile中读取指令自动的构建镜像。

我们要想编写自己的Dockerfiler并构建镜像,那对Dockerfile的语法和命令的了解就是必须的,了解规则才好办事嘛。

相关指令

FROM
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]

指定基础镜像,当前镜像是基于哪个镜像创建的,有点类似Java中的类继承。FROM指令必是Dockerfile文件中的首条命令。

MAINTAINER
MAINTAINER <name>

镜像维护者的信息,该命令已经被标记为不推荐使用了。

LABEL
LABEL <key>=<value> <key>=<value> <key>=<value> ...

给镜像添加元数据,可以用LABEL命令替换MAINTAINER命令。指定一些作者、邮箱等信息。

ENV
ENV <key> <value>
ENV <key>=<value> ...

设置环境变量,设置的变量可供后面指令使用。跟java中定义变量差不多的意思。

WORKDIR
WORKDIR /path/to/workdir

设置工作目录,在该指令后的RUN、CMD、ENTRYPOINT,COPY、ADD指令都会在该目录执行。如果该目录不存在,则会创建。

RUN
RUN <command>
RUN ["executable", "param1", "param2"]

RUN会在当前镜像的最上面创建一个新层,并且能执行任何的命令,然后对执行的结果进行提交。提交后的结果镜像在Dockerfile的后续步骤中可以使用。

ADD
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

从宿主机拷贝文件或者文件夹到镜像,也可以复制一个网络文件!如果拷贝的文件是一个压缩包,会自动解压缩。

COPY
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

从宿主机拷贝文件或者文件夹到镜像,不能复制网络文件也不会自动解压缩。

VOLUME
VOLUME ["/data"]

VOLUME用于创建挂载点,一般配合run命令的-v参数使用。

EXPOSE
EXPOSE <port> [<port>/<protocol>...]

指定容器运行时对外暴露的端口,但是该指定实际上不会发布该端口,它的功能是镜像构建者和容器运行者之间的记录文件。

回到容器命令中的run命令部分,run命令有-p和-P两个参数,如果是-P就是随机端口映射,容器内会随机映射到EXPOSE指定的端口,如果是-p就是指定端口映射,告诉运维人员容器内需要映射的端口号。

CMD
CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2

指定容器启动时默认运行的命令,在一个Dockerfile文件中,如果有多个CMD命令,只有一个最后一个会生效!同样是可以执行命令,可能你会觉得跟上面的RUN指令很相似,RUN指令是在构建镜像时候执行的,而CMD指令是在每次容器运行的时候执行的!docker run命令会覆盖CMD的命令!

ENTRYPOINT
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

这个指令与CMD指令类似,都是指定启动容器时要运行的命令,如果指定了ENTRYPOINT,则CMD指定的命令不会执行!在一个Dockerfile文件中,如果有多个ENTRYPOINT命令,也只有一个最后一个会生效!不同的是通过docker run command命令会覆盖CMD的命令!执行的命令不会覆盖ENTRYPOINT,docker run命令中指定的任何参数都会被当做参数传递给ENTRYPOINT!

RUN、CMD、ENTRYPOINT区别
  1. RUN指令是在镜像构建时运行,而后两个是在容器启动时执行。
  2. CMD指令设置的命令是容器启动时默认运行的命令,如果docker run没有指定任何的命令,并且Dockerfile中没有指定ENTRYPOINT,那容器启动的时候就会执行CMD指定的命令!有点类似代码中的缺省参数。
  3. 如果设置了ENTRYPOINT指令,则优先使用。并且可以通过docker run给该指令设置的命令传参。
  4. CMD有点类似代码中的缺省参数。


USER
USER <user>[:<group>]
USER <UID>[:<GID>]

用于指定运行镜像所使用的用户。

ARG
ARG <name>[=<default value>]

指定在镜像构建时可传递的变量,定义的变量可以通过docker build –build-arg =的方式在构建时设置。

ONBUILD
ONBUILD [INSTRUCTION]

当所构建的镜像被当做其他镜像的基础镜像时,ONBUILD指定的命令会被触发。

STOPSIGNAL
STOPSIGNAL signal

设置当容器停止时所要发送的系统调用信号。

HEALTHCHECK
HEALTHCHECK [OPTIONS] CMD command (在容器内运行运行命令检测容器的运行情况)
HEALTHCHECK NONE (禁止从父镜像继承检查)

该指令可以告诉Docker怎么去检测一个容器的运行状况。

SHELL
SHELL ["executable", "parameters"]

用于设置执行命令所使用的默认的shell类型。该指令在Windows操作系统下比较有用,因为Windows下通常会有CMD和PowerShell两种shell,甚至还有sh。

构建

Dockerfile执行顺序是从上到下,顺序执行。每条指令都会创建一个新的镜像层,并对镜像进行提交。编写好Dockerfile文件后,就需要使用docker build命令对镜像进行构建了。

docker build的格式:docker build [OPTIONS] PATH | URL | -
  • -f:指定要使用的Dockerfile路径,如果不指定,则在当前工作目录寻找Dockerfile文件。
  • -t:镜像的名字及标签,通常name:tag或者name格式;可以在一次构建中为一个镜像设置多个标签。


例如我们可以docker build -t myApp:1.0.1 .这样来构建自己的镜像,注意后面的 .,用于指定镜像构建过程中的上下文环境的目录。如果大家想了解那些官方镜像的Dockerfile文件都是怎么样写的,可以上https://hub.docker.com/ 进行搜索,以Tomcat镜像为例:


能看到Tomcat镜像的父镜像是OpenJDK镜像,我们再搜索OpenJDK的Dockerfile文件:


OpenJDK镜像的父镜像又是Oracle Linux镜像,我们再搜索Oracle Linux的Dockerfile文件:


OpenJDK镜像的父镜像是scratch,这是根镜像,所有的镜像都会依赖该镜像,就像我们代码中所有的对象的父类都是Object。所以能看到Tomcat镜像就是这样一层一层的构建出来的,这也是为什么前面通过docker images查看到的Tomcat镜像为什么会有四百多兆的原因啦!

看到这里的你,是否对Docker是什么?为什么需要Docker?Docker镜像、Docker容器的概念是什么?Docker中常用的命令有哪些?Dockerfile有哪些指令?怎么去构建自己的镜像?这些问题都能明白了么?

原文链接:http://www.17coding.info/article/24

0 个评论

要回复文章请先登录注册