可能大家会比较奇怪明明讲的是Django为啥要介绍Docker,实不相瞒如果大家不知道Docker对Django的学习并无影响,但是个人之前很早就听说Docker了,借着这个机会也学习下,趁着有这个应用场景也事件下,其实个人对Docker也不是很熟悉,只是现学现卖。将整个过程贯通起来,个人接触新东西一般喜欢围绕着问题展开,遇到不明白的在网上找资料或者找书去了解。这篇博客也采用这种方式,下面就围绕着几个问题展开,这篇博客不会对Docker进行太深入了解,目标是够用就好。深入的知识在后续大家使用的时候遇到问题再在实践中解决,毕竟精力有限。
1. 容器化 vs 虚拟化 虚拟化是通过中间件将一台或者多台独立机器虚拟运行与物理硬件之上,用户并不能感知为他们服务的到底是哪台机器,事实上呈现在用户面前的就和使用一部机器是一样的感觉,只不过这部机器在物理范畴上可能不是单纯一台主机,可能有多台机器组成的一个集群。虚拟机是抽象硬件资源,每一个虚拟机实例占用指定数量的CPU、内存、硬盘等资源,这些资源每个虚拟机实例之间不会共享。
而什么是容器化呢?容器化从应用出发,将应用分割成多个容器,而这些容器直接运行在操作系统内核上的用户空间,容器技术可以让多个独立用户空间运行在同一台宿主机上。也就是说容器技术是抽象软件资源,它和Linux上运行的一个应用程序没有太大区别。
早期,大家都认为虚拟化方式可以最大程度上提供虚拟化管理的灵活性。但是随着时间推移,大家发现,虚拟化技术有个问题就是:每个虚拟机都需要运行一个完整的操作系统以及其中安装好的大量应用程序。但实际生产开发环境里,我们更关注的是自己部署的应用程序,如果每次部署发布我都得搞一个完整操作系统和附带的依赖环境,那么这让任务和性能变得很重和很低下。这时候,人们就在想,有没有其他什么方式能让人更加的关注应用程序本身,底层多余的操作系统和环境可以共享和复用?换句话来说,那就是我部署一个服务运行好后,我再想移植到另外一个地方,我可以不用再安装一套操作系统和依赖环境,这就是容器化提出的场景。
2. 容器化的特点 容器是自包含的,它打包了应用程序及其所有依赖,可以直接运行。 容器是可移植的,这就可以确保应用在开发环境、测试环境、生产环境等都有完全一样的运行环境。 容器是互相隔离的,同一主机上运行的多个容器,不会互相影响。 容器是轻量级的,体现在容器的秒级启动,并且占用资源很少。
3. 什么是Docker,Docker的组成及镜像结构 Docker是一个能够把开发应用程序自动部署到容器的开源引擎,使用Docker开发人员只需要关心容器中运行的应用程序,运维人员只需要关心如何管理容器。 它能保证写代码的开发环境与应用程序要部署的生产环境一致性。这对经常出现开发环境是好的,等到部署上去各种问题,又得各种联调的程序员来说无疑是巨大的福音。
Docker 目前大量用于: 持续集成和持续部署 (CI/CD), 加速应用管道自动化和应用部署, 以及结合微服务技术构建可伸缩扩展的服务框架, 服务器资源共享 创建隔离的运行环境 这些场景。
那Docker又是由那些组件组成的呢? Docker 主要由
Docker引擎:Docker引擎是由客户端服务器架构的程序,客户端通过docker命令行工具以及一套restful API向Docker服务器发出请求,Docker服务器或者称为守护进程完成所有工作并返回。Docker服务器和客户端可以在同一台宿主机器上运行,也可以从本地的Docker客户端连接到另一台宿主机上远程Docker服务器。
Docker镜像:用户基于镜像运行自己的容器,可以把镜像当作容器的源代码,或者相当于我们安装系统的光盘,写Dockerfile就相当于刻录系统光盘。
Docker容器:如果说Docker镜像相当于系统光盘,那么Docker容器就是由这个系统光盘制作出来的可以跑的系统。
Registry: 和我们的github类型,github存储的是代码,而Registry存储的是Docker的镜像,换句话说它就是Docker镜像仓库。
下面是整个Docker组件的组成图:
从 Docker 的使用角度来说最为关键的是镜像的制作,Docker 镜像的制作是通过Dockerfile来完成的,Dockerfile的编写我们会在下面进行介绍,这里我们先来看下Docker 镜像的组成:
容器基于镜像启动和运行。可以说Docker镜像是容器的基石,Docker的镜像是一个层叠的只读文件系统,它的最底端是一个引导文件系统及bootfs。 Docker用户几乎永远都不会和引导文件系统有交互,实际上当一个容器启动后,bootfs会被移到内存中,引导文件将被卸载。Docker镜像的第二层是rootfs(root文件系统),位于引导文件系统之上,可以有多种操作系统。 在传统的linux系统中root文件系统最先会以只读的方式加载,当引导和启动完成后他才会被切换成读写模式。 但是在Docker里,root文件系统永远只能是只读,并且Docker会用联合加载系统在rootfs之上加载更多的只读文件系统。 联合加载只得是一次加载多个文件系统。但是在外面看来只有一个文件系统。联合加载会将各层文件系统加载到一起, 这样最终的文件系统会包含所有的文件及目录。Docker将这样的文件系统称为镜像。 一个镜像可以放到另一个镜像顶部,位于下面的镜像称为父镜像。一个容器中可以运行用户的一个或多个进程。当一个容器启动时,Docker会在镜像的最顶层增加一个读写文件系统,我们在Docker中运行的程序就是在这个层运行并执行的。第一次启动Docker时,读写层是空的,当文件发生变化后都会应用到这一层。比如修改一个文件,先将该文件从只读层复制到读写层,然后隐藏只读层,这就是Docker的写时复制。
4. Docker的常用操作命令 镜像操作:
将镜像拉到本地 [docker pull ubuntu] 查看当前已经有的镜像 [docker images] 查找镜像 [docker search xxx] 删除镜像 [docker rmi d5a6e75613ea] 登录注销docker hub [docker login/logout] 上传镜像:在docker hub 上创建一个docker地址。 标准格式为 用户名/docker镜像名 比如我这边创建的docker镜像名 为testdocker 构建命令如下: docker build -t "tbfungeek/testdocker:0.0.1" . 使用下面命令就可以进行push到dockerhub了 docker push tbfungeek/testdocker:0.0 .1
容器操作:
查看docker info [docker info] 查看当前正在运行的容器 [docker ps -a] 创建容器 [docker run -dit -p 8888 :80 --name test ubuntu /bin/ bash] 删除容器 [docker rm 容器id] 启动容器 [docker start xxxx] 重启容器 [docker restart xxxx] 附加到容器中 [docker attach xxxx] 退出容器 [exit ] 停止容器 [docker stop] 查看日志 [docker logs -f xxxx] 查看端口 [docker port 4 d17d19e34e2]
5. DockerFile的常用指令 构建会在Docker后台守护进程(daemon)中执行,而不是CLI中。构建前,构建进程会将全部内容(递归)发送到守护进程。 在创建一个Docker 镜像的时候推荐重新新建一个空的目录作为构建Docker的上下文,并且将Dockerfile放在上下文目录下的顶层目录(虽然可以通过-f参数来指定构建Docker的目录但是推荐还是放在上下文目录的顶层),在这个上下文文件夹中只存放用于构建当前 Docker镜像所必须的文件,对于不需要的文件通过dockerignore文件进行忽略。
Docker 守护进程会一条一条的执行Dockerfile中的指令,而且会在每一步提交并生成一个新镜像,最后会输出最终镜像的ID。生成完成后,Docker 守护进程会自动清理你发送的上下文。 Dockerfile文件中的每条指令会被独立执行,并会创建一个新镜像,RUN cd /tmp等命令不会对下条指令产生影响。 Docker 会重用已生成的中间镜像,以加速docker build的构建速度。
1. 创建目录 2. 创建Dockerfile 3. 编写Dockerfile FROM ubuntu:latestMAINTAINER linxiaohai "tbfungeek@163.com" RUN apt-get update && apt-get install vimEXPOSE 80 4. 编译 Dockerfile生成镜像 docker build -f web_container/Dockerfile . docker build --no-cache -t "标签linxiaohai/web:v1" . docker build --no-cache -t "标签linxiaohai/web:v1" git@github.com:xxx/web_container
FROM FROM <image >FROM <image >:<tag>FROM <image >@<digest>
在Dockerfile中第一条非注释指令一定是FROM,它指定了以哪一个镜像作为基准镜像,首先会先判断本地是否存在,如果不存在则会从仓库下载,这里推荐使用官方镜像
LABEL 给构建的镜像打标签。 如果base image中也有标签,则继承,如果是同名标签,则覆盖。为了减少layer数量,尽量将标签写在一个LABEL指令中去,如:
LABEL author="lin xiaohai" \ version ="0.0.1" 指定后可以通过docker inspect 查看: "Labels" : { "author" : "lin xiaohai" , "version" : "0.0.1" }
VOLUME VOLUME用于创建挂载点,即向基于所构建镜像创始的容器添加卷
VOLUME ["/var/log" ] VOLUME /var/ log /var/ db
如,通过VOLUME创建一个挂载点:
ENV volum "/home/mydata" VOLUME ${volum}
构建的镜像,并指定镜像名为docker_file。构建镜像后,使用新构建的运行一个容器。运行容器时,需-v参将能本地目录绑定到容器的卷(挂载点)上,以使容器可以访问宿主机的数据。
docker run -dit -v ~/test:/ home/mydata/ --name "volumetests" docker_file
USER USER用于指定运行镜像所使用的用户:
使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。以下都是合法的指定试:
USER user USER user :group USER uidUSER uid :gidUSER user :gidUSER uid :group
使用USER指定用户后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户。镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。
WORKDIR
WORKDIR指令用于设置Dockerfile中的RUN、CMD和ENTRYPOINT指令执行命令的工作目录(默认为/目录),该指令在Dockerfile文件中可以出现多次,如果使用相对路径则为相对于WORKDIR上一次的值
ARG ARG用于指定传递给构建运行时的变量:
ARG <name > [=<default value > ]
在使用docker build构建镜像时,可以通过–build-arg =参数来指定或重设置这些变量的值。 docker内置了一批构建参数,可以不用在Dockerfile中声明:HTTP_PROXY、http_proxy、HTTPS_PROXY、https_proxy、FTP_PROXY、ftp_proxy、NO_PROXY、no_proxy
RUN RUN指令会在当前镜像的顶层执行任何命令,并commit成新的(中间)镜像,提交的镜像会在后面继续用到。 上面看到RUN后的格式有两种写法。
shell格式,相当于执行/bin/sh -c ““:
RUN apt-get install vim -y
exec格式,不会触发shell,所以$HOME这样的环境变量无法使用,但它可以在没有bash的镜像中执行,而且可以避免错误的解析命令字符串:
RUN ["apt-get" , "install" , "vim" , "-y" ]或 RUN ["/bin/bash" , "-c" , "apt-get install vim -y" ] 与shell风格相同
RUN可以执行任何命令,然后在当前镜像上创建一个新层并提交。提交后的结果镜像将会用在Dockerfile文件的下一步。 通过RUN执行多条命令时,可以通过\换行执行,也可以在同一行中,通过分号分隔命令:
CMD 一个Dockerfile里只能有一个CMD,如果有多个,只有最后一个生效。CMD指令的主要功能是在build完成后,为了给docker run启动到容器时提供默认命令或参数,这些默认值可以包含可执行的命令,也可以只是参数(此时可执行命令就必须提前在ENTRYPOINT中指定)。 它与ENTRYPOINT的功能极为相似,区别在于如果docker run后面出现与CMD指定的相同命令,那么CMD会被覆盖;而ENTRYPOINT会把容器名后面的所有内容都当成参数传递给其指定的命令(不会对命令覆盖)。另外CMD还可以单独作为ENTRYPOINT的所接命令的可选参数。 CMD与RUN的区别在于,RUN是在build成镜像时就运行的,先于CMD和ENTRYPOINT的,CMD会在每次启动容器的时候运行,而RUN只在创建镜像时执行一次,固化在image中。
ENTRYPOINT ENTRYPOINT命令设置在容器启动时执行命令,如果有多个ENTRYPOINT指令,那只有最后一个生效。 使用exec格式,在docker run 的所有参数,都会追加到ENTRYPOINT之后,并且会覆盖CMD所指定的参数(如果有的话)。当然可以在run时使用–entrypoint来覆盖ENTRYPOINT指令。 以推荐使用的exec格式为例: 我们可以使用ENTRYPOINT来设置基本不会变化的命令,用CMD来设置其它的可能改变的默认启动命令或选项(docker run会覆盖的)。
ENV 用于设置环境变量:
ENV <key > <value > 设置了后,后续的RUN命令都可以使用,当运行生成的镜像时这些环境变量依然有效,如果需要在运行时更改这些环境变量可以在运行docker run时添加-env <key > =<value > 参数来修改
ADD 在构建镜像时,复制上下文中的文件到镜像内,格式:
ADD <src>.. . <dest>ADD ["<src>" ,.. . "<dest>" ]
可以是文件、目录,也可以是文件URL。可以使用模糊匹配(wildcards,类似shell的匹配),可以指定多个,必须是在上下文目录和子目录中,无法添加../a.txt这样的文件。如果是个目录,则复制的是目录下的所有内容,但不包括该目录。如果是个可被docker识别的压缩包,docker会以tar -x的方式解压后将内容复制到。可以是绝对路径,也可以是相对WORKDIR目录的相对路径。如果路径不存在则会自动级联创建,根据你的需要是里是否需要反斜杠/,习惯使用/结尾从而避免被当成文件。
COPY COPY的语法与功能与ADD相同,只是不支持上面讲到的是远程URL、自动解压这两个特性,但是Best Practices for Writing Dockerfiles建议尽量使用COPY,并使用RUN与COPY的组合来代替ADD,这是因为虽然COPY只支持本地文件拷贝到container,但它的处理比ADD更加透明,建议只在复制tar文件时使用ADD,如ADD trusty-core-amd64.tar.gz /。
EXPOSE EXPOSE指令告诉容器在运行时要监听的端口,但是这个端口是用于多个容器之间通信用的(links),外面的host是访问不到的。要把端口暴露给外面的主机,在启动容器时使用-p选项。
ONBUILD 向镜像中添加一个触发器,当以该镜像为base image再次构建新的镜像时,会触发执行其中的指令。格式:
比如我们生成的镜像是用来部署Python代码的,但是因为有多个项目可能会复用该镜像。所以一个合适的方式是:
[...] ONBUILD ADD . /app/ src ONBUILD RUN /usr/ local/bin/ python-build --dir /app/ src [...]
注意 ONBUILD只会继承给子节点的镜像,不会再继承给孙子节点。 ONBUILD ONBUILD或者ONBUILD FROM或者ONBUILD MAINTAINER是不允许的。
STOPSIGNAL STOPSIGNAL用于设置停止容器所要发送的系统调用信号:
STOPSIGNAL signal 所使用的信号必须是内核系统调用表中的合法的值,如:9、SIGKILL
可以通过如下材料进行进一步学习:
http://www.cnblogs.com/qcloud1001/p/9273549.html https://legacy.gitbook.com/book/yeasy/docker_practice/details http://product.dangdang.com/23941643.html https://github.com/qianlei90/Blog/issues/35 https://github.com/qianlei90/Blog/issues/36 http://seanlook.com/2014/11/17/dockerfile-introduction/ https://docs.docker.com/get-started/#docker-concepts
https://www.zhihu.com/question/22969309 https://juejin.im/post/5b4615b0f265da0f6d72c130