Home | Archives | Categories | Tags | About |
|
为什么需要着重讲一讲docker中的数据管理,在实际的运行过程中,经常需要存储一些配置信息,程序运行中产生的信息,这些都是需要持久化的数据。可以将这些数据写入容器的文件层中,这样的方式有很多的缺点:
volumes
相比,会有极大的性能损失。Docker提供三种不同的方式将宿主机的文件映射到容器内部,用于读写数据:
当你不确定使用那一种方式的时候,volumes
总会是一种不错的选择。
不管选择哪一种数据存储方式,在容器内部,这些数据的使用方式是一致的。要么以文件夹的方式,要么以文件的方式在容器内部展现。
下面这幅图清楚的展示了三种方式存储数据的不同:
/var/lib/docker/volumes/
),非Docker进程不能修改这一部分的内容。Volumes
是最好的管理数据的方式。Volumes始终是推荐的管理数据的方式,一些典型的应用场景如下:
一般情况下请选择使用volumes,在如下情况可以选择bind mounts:
/etc/resolv.conf
。target/
目录共享给容器,每一次编译工程之后,容器内部就可以从新构建服务。如果这种开发步骤,production Dockerfile
需要直接拷贝production-ready artifacts
。当既不想持久化数据到宿主机,也不想持久化到容器内部可以使用tmpfs。例如,当你的应用程序需要输出大量的状态数据,为了性能或者安全性考虑并不想持久化这些数据时,可以使用tmpfs。
无论使用volume或者mounts,记住以下几点:
Docker通过 network drivers支持容器的网络环境。默认情况下,Docker支持bridge
和 overlay
两种网络驱动。也可以支持自定义的网络驱动。
每一个Docker Engine都会自动支持三种默认的网络,可以使用docker network ls
列出所有的网络驱动:
|
|
bridge
是一个特殊的网络驱动,如果不做特别指定,所有的容器都会在bridge
下启动。尝试一下如下的命令:
|
|
通过docker network inspect bridge
命令可以查看一下容器的网络信息
|
|
从一个网络中移除一个容器,如下
|
|
以上的命令将networktest容器从bridge网络中移除。使用网络可以方便的将容器隔离。
Docker Engine支持bridge
和overlay
模式的网络,bridge
模式只能在单机上使用,overlay
支持多机使用。接下来,使用bridge
创建自定义的网络,
|
|
-d
参数指定网络的模式,如果不加-d
参数,默认也使用bridge
模式。检查一下是否创建成功,
|
|
使用inspect
命令检查新建网络的信息,
|
|
接下来,用一个简单的web应用展示如何安全的使用容器的网络。
第一步,在自定义的网络上创建数据库容器,
|
|
--net
参数指定使用的网络,检查是否创建成功,
|
|
第二步,在默认的网络下创建web应用,
|
|
检查网络信息,
|
|
查看web的地址,
|
|
当前的网络拓扑图如下,
第三步,查看网络的连通性,
|
|
会发现在db上,无法连通到web上。
第四步,将web连接到my_bridge上,
|
|
当前的网络拓扑,
第五步,再一次验证网络的连通性,
|
|
会发现db和web已经能够正常的连通。其它不在my_bridge上的容器不能连接到该网络环境中
通过自定义网络环境,可以将安全性要求较高的服务,放入单独的自定义的网络环境中,以此保证服务的安全行。
所谓的生命周期越短越好,是要强调,通过dockerfile
定义的镜像,可以使用最少的步骤和配置,很方便的停止、销毁、构建、部署,达到无状态的模式。
.dockerignore
文件docker build
指令运行的上下文环境有两种,其一,docker build
运行的当前目录以及所有的子目录;其二,-f
命令指定的目录及所有的子目录。当运行docker build
命令时,上下文环境中的所有文件及其目录都会被送到docker deamon
中,被认为是编译的上下文环境。上下文中的文件越多越大,编译所需要的时间,以及最终编译出来的image就会越大。也会直接的导致,pull、push以及run这个image的时间会越长。下面的这条信息告知了docker build上下文环境的大小,
|
|
一些编译环境的文件也不能被删除,为了优化这个问题,docker提供了.dockerginore
文件,它如同.gitignore
文件一样,支持排除模式。
|
|
Rule | Behavior |
---|---|
# comment |
忽略 |
*/temp* |
根目录的二级子目录中,以temp开头的文件或者文件夹,都会被忽略。例如,/somedir/temporary.txt 将会被忽略。 |
*/*/temp* |
根目录的三级子目录中,以temp开头的文件或者文件夹,都会被忽略。例如,/somedir/subdir/temporary.txt 将会被忽略。 |
temp? |
根目录下的以 /tempa 开头的文件或者文件夹,都会被忽略。 |
muitl-stage
多阶段构建使用muitl-stage
多阶段构建,可以有效的减少了image的大小,增加了dockerfile,部署的简洁性和可维护性。
为了减少复杂性、依赖、文件大小以及构建时间,最好避免安装不必要的包。例如,数据库的image中没有必要安装文件编辑工具。
将一个大的应用解耦分拆到不同的容器中,使其更好的水平扩展、重用。例如,一个web应用栈可以分为三个独立的容器,一个容器用做web界面,一个用做数据库存储,一个用于in-memory的缓存。
你可能听说过“一个容器就是一个进程”,这句话虽有夸张的成分,但是很好的解释了,使用容器的范式。尽量让容器功能单一,如果容器之间存在依赖,可以使用docker container networks相互通信。
docker 17.05以及1.10以前,减少文件层非常的重要。比较高版本的docker优化了这些问题;
docker 1.10 以及高版本,只有RUN
, COPY
以及 ADD
指令 会增加文件层,其它指令会创建临时的中间镜像,不会增加最终的image的大小。
Docker 17.05 以及高版本,增加了multi-stage特性,允许只拷贝想要的文件到最终的镜像。
只要有可能,将多参数的命令行安装字母顺序排序,保证格式清楚,可阅读性高。例如:
|
|
image的构建过程,就是一步一步按照指定的顺序执行dockerfile中的命令。每执行一条命令,docker会先在cache中查找,是否有以及存在可复用的缓存,如果没有,则会创建新的image。如果不想使用缓存功能,可以在运行docker build
命令是指定--no-cache=true
参数。
缓存的查找规则如下,
ADD
和COPY
命令来说,需要对比文件的内容,并会给每一个文件计算一个hash值,最近修改时间,以及访问次数,不参与hash值的计算。查找的过程,就是对比hash值是否相同,如果不同,则cache失效。ADD
和COPY
命令,其它的命令只会对比命令字符串本身。尽可能使用官方仓库最为自己的基础镜像,推荐使用Debian image。
仅可能给自己的镜像加上标签,方面管理,添加版权信息等。如下提供了一个标准的例子:
|
|
在dockr 1.10版本之前,推荐所有的标签写入一行,以便减少镜像的文件层。新版本无需特殊的考虑,以前版本的例子:
|
|
或者
|
|
尽可能将命令写入一行,对于多行命令,使用/
连接成一行。例如,
|
|
CMD
命令用来启动容器中的应用程序,并且可以传人一些参数。一般的使用格式CMD [“executable”, “param1”, “param2”…]
,对于service类型的应用,比如apache和rails,使用方式 CMD ["apache2","-DFOREGROUND"]
。
对于其他类型的应用, CMD
应该启动一个常用的交互shell,就像bash,python以及perl。例如CMD ["perl", "-de0"]
, CMD ["python"]
, 或者 CMD [“php”, “-a”]
。这样就能保证,当你运行docker run -it python
就能进入一个可用的shell环境。除非你对 ENTRYPOINT
命令非常的熟悉,最好不要用如下的格式CMD [“param”, “param”]
配合 ENTRYPOINT
使用。
EXPOSE
命令指定了容器中应用程序监听的端口号,最好使用应用程序常用的端口号,比如 Apache web server一般使用EXPOSE 80
,MongoDB使用EXPOSE 27017
。
对于外部访问来说,可以使用docker run -p 8080:80
映射到宿主机上的端口,对于容器的互连,docker也提供了环境变量的方式,比如MYSQL_PORT_3306_TCP
。
为了使新的软件更容易运行,可以使用ENV
命令设置容器的环境变量,比如,ENV PATH /usr/local/nginx/bin:$PATH
将nginx设置到环境变量中, CMD [“nginx”]
可以直接运行。
也可以设置环境变量,给应用程序使用,如下:
|
|
尽管ADD
和COPY
命令功能基本相同,仍然推荐优先使用COPY
命令。COPY
命令只支持基本的拷贝功能,ADD
有一些隐式的特性,支持打包文件的解压,指定网络路径等等。
如果你需要拷贝一些文件到容器中,最好单独的拷贝,不要一次性全拷贝,比如下面的例子:
|
|
如果COPY . /tmp/
放在run
之前,会大大的增加cache失效的可能性。
从镜像大小的角度考虑,尽量避免使用ADD
命令获取远程的资源,可以使用curl
或者wget
代替。看看下面的例子:
|
|
替换为:
|
|
VOLUME
指令用来将需要持久化的数据容器暴露到容器外,比如数据库的数据文件,配置文件或者其它的用户创建的文件或者目录。强烈推荐使用 VOLUME
命令保存有状态的数据。
如果一个服务并不需要一些root权限,可以使用USER
命令切换到非root用户。比如接下来的命令:RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
。
应该避免使用sudo
命令,sudo
命令会带来TTY以及信号方面的问题。如果确实需要sudo
的功能,推荐使用“gosu”。
最后一点,为了避免文件层以及复杂性,避免来回切换账号。
为了明确和清晰,尽量为工作目录指定绝对路径,不要使用相对路径。
ONBUILD
命令会在当前的 Dockerfile
构建完成之后执行。所有的子镜像都会执行父镜像的ONBUILD
,然后才会构建自己的 Dockerfile
。
multi-stage功能有什么功效?接下来将通过一个go程序例子进行讲解。开发go应用程序需要如下几个必备条件;
如果不使用multi-stage,为了尽量保证image比较小,可能会使用如下的方式构建docker image。
Dockerfile.build
|
|
Dockerfile
|
|
build.sh
|
|
以上的构建过程,运行build.sh
,会先构建编译环境用于构建可运行的应用程序,然后,将可执行应用程序app拷贝到local,用于构建最终的运行环境,最后,清理local的app临时文件。
整个构建过程,需要编写额外的build.sh
脚步,执行冗长的命令,最后还需要做一些清理工作。不管是用于编译的image还是用于运行的image都会占用本地的磁盘空间。
那么,有没有更好的方式?答案就是multi-stage
。
dockerfile
|
|
在dockerfile
中,使用两次FROM
命令,每一个FROM
可以使用不同的基础image
,它们处于不同的构建阶段。可以从一个stage
拷贝文件到另外一个stage
,使用COPY
命令,--from
指定从哪个stage拷贝,不需要的文件可以完全忽略。
构建命令如下,
|
|
默认情况下,每一个stage是没有命名的,只能通过序号进行引用,第一个FROM
的序号是0,后面的依次类推。也可以通过给stage命名,更方便的引用各个stage,并增加可维护性,避免重新排列FROM
命令造成混乱。接下来通过修改上面的例子展示了stage命名的用法;
|
|
multi-stage模式有效的减少了image的大小,增加了dockerfile,部署的简洁性和可维护性。
前两周看完了docker tutorial官方系列教程,了解了docker是什么,docker的使用场景,为什么要使用docker以及如何使用docker。接下来的一周,需要深入一点,修炼一些docker的基本功,比如,如何写好dockerfile。
这一系列的教程有如下几篇文档:
绝大多数的Dockerfile都会基于已有的基础镜像进行构建,如果你需要完全控制镜像的内容,从空白的镜像创建,需要使用FROM scratch
,或者不使用FROM
指令。
scratch
镜像是Docker保留的最小镜像。使用scratch
开始创建,scratch
的下一条命令,将会是镜像的第一层。
|
|
|
|
这篇文章介绍了如何使用docker创建一个简单的linux的编译环境,编译一个简单的hello-world二进制文件,并将这个二进制文件加入到空白的镜像中执行。
hexo创建一个html的静态网站
|
|
|
|
基于nginx基础镜像,将hexo生成的静态网站copy到镜像中。注意,html文件和Dockerfile文件在同一个目录。
|
|
|
|
|
|
|
|
登录远程的服务器,执行一下命令,从docker hub上下载生成好的镜像,直接启动部署。
|
|
经过一段时间的思考,终于下定决心好好的学习一下docker。找到官网的教程,前前后后花了一周的时间,按照教程一步一步的敲命令,完整的搭建了一个完整的swarm集群。
在做的过程中,遇到很多的问题,
针对上面提到的第一个和第三个问题,都在文档中做了记载,已备后来者。
第一个问题的解决方案,设置阿里云镜像。
第三个问题,关于2377和2376的区别,在docker入门系列(四)中有提到。。
最后的总结
看介绍文章,和一步一步实践,一行一行敲命令,还是有很大的区别。以前总是看一些技术性的介绍文章,觉得自己什么都懂了,什么概念都清楚了,开口闭口都是容器化,服务编排。等到一个任务交给你去做的时候,总是无从下手,这次动手实践,真真实实的感受到了,docker给编程,或者说是软件流程的革命性的变化,一件搭建编程环境,一键部署应用,几代工程师的梦想,看来越来越接近现实了。
由于众所周知的原因,在国内使用docker hub的体验让人奔溃,网速太慢,代理不稳定。好在可以使用国内镜像,接下来,记录一下,使用阿里云镜像的步骤。
|
|
对于10.10.3以上的用户 推荐使用
|
|
创建一台安装有Docker环境的Linux虚拟机,指定机器名称为default,同时配置Docker加速器地址。
|
|
查看机器的环境配置,并配置到本地,并通过Docker客户端访问Docker服务。
|
|
执行hello-world
,验证是否配置成功
|
|
恭喜你,可以愉快使用docker镜像了。
docker-compose.yml
docker-machine ssh myvm1 "docker node ls"
确保服务是ready
状态在第四节中,介绍了如何启动swarm,如何将服务部署到多台机器之上。在这一节中,将着重介绍stack
,所谓的stack
就是一组相互关联的服务,它们能够共享一些依赖,能够并一起编排和扩容。在第三节中,介绍了一个单服务的stack
,这个stack
中只有一个服务,只运行在一台宿主机上。在这一节中,将介绍多服务的stack,并运行在多台机器之上。
添加服务非常的简单,只需要编辑docker-compose.yml
,添加相关的服务信息。比如,给swarm机器添加一个可视化的服务,展示swarm集群的机器和服务信息。
docker-compose.yml
|
|
在docker-compose.yml
中增加了visualizer的相关配置项。
docker stack deploy
重新部署服务
|
|
B5308526-E53E-411C-9D85-86272C1B38D9
重复上面的过程,在给我们的stack
中添加redis服务。
docker-compose.yml
添加redis的依赖
|
|
Note:
|
|
docker stack deploy
|
|
docker service ls
验证服务启动情况
|
|
恭喜你,一个完成的stack配置完成了。