docker note from UC blog

--------------------------------------------------------------------------mysql

original url http://tech.uc.cn/?p=2726linux

 

最近接触PAAS相关的知识,在研发过程当中开始使用Docker搭建了本身完整的开发环境,感受生活在PAAS时代的程序员真是幸福,本文会简要介绍下Docker是什么,如何利用Docker来搭建本身的开发环境(本文主要是面向Mac OS X),以及期间所遇到的一些坑和解决方案。(本文会要求你对PAAS、LXC、CGroup、AUFS有必定的了解基础,请自行Google )nginx

大背景–虚拟化技术历史

计算机虚拟化技术由来已久,从硬件仿真到全虚拟化,再到准虚拟化和操做系统虚拟化,各类技术粉墨登场,种类繁多,说实在的有点眼花缭乱和复杂;但用户的核心诉求一直是比较简单的,下降信息技术(IT)的运营成本,提升资源利用率,提升安全性和可靠性等等;虽然说用户的核心诉求比较简单,但每一个时代的需求场景倒是不一样的。在大型机时代,虚拟化技术被用来支持多个用户可以同时使用大型机,在x86架构时代,随着企业服务的大规模部署,虚拟化技术主要是用来提升企业资源的利用率,而现现在,随着云计算时代的到来,人们对应用的安全性、隔离性愈来愈高,对于部署的标准化以及虚拟机的性能要求愈来愈高。现现在,一种叫Linux容器的虚拟化技术逐渐获得普遍的应用,它的优势有许多,本文不一一赘述,有太多的文章能够参考。git

什么是Docker?

docker的英文本意是码头工人,也就是搬运工,这种搬运工搬运的是集装箱(Container),集装箱里面装的可不是商品货物,而是任意类型的App,Docker把App(叫Payload)装在Container内,经过Linux Container技术的包装将App变成一种标准化的、可移植的、自管理的组件,这种组件能够在你的latop上开发、调试、运行,最终很是方便和一致地运行在production环境下。程序员

Docker的核心底层技术是LXC(Linux Container),Docker在其上面加了薄薄的一层,添加了许多有用的功能。这篇stackoverflow上的问题和答案很好地诠释了Docker和LXC的区别,可以让你更好的了解什么是Docker, 简单翻译下就是如下几点:github

  • Docker提供了一种可移植的配置标准化机制,容许你一致性地在不一样的机器上运行同一个Container;而LXC自己可能由于不一样机器的不一样配置而没法方便地移植运行;
  • Docker以App为中心,为应用的部署作了不少优化,而LXC的帮助脚本主要是聚焦于如何机器启动地更快和耗更少的内存;
  • Docker为App提供了一种自动化构建机制(Dockerfile),包括打包,基础设施依赖管理和安装等等;
  • Docker提供了一种相似git的Container版本化的机制,容许你对你建立过的容器进行版本管理,依靠这种机制,你还能够下载别人建立的Container,甚至像git那样进行合并;
  • Docker Container是可重用的,依赖于版本化机制,你很容易重用别人的Container(叫Image),做为基础版本进行扩展;
  • Docker Container是可共享的,有点相似github同样,Docker有本身的INDEX,你能够建立本身的Docker用户并上传和下载Docker Image;
  • Docker提供了不少的工具链,造成了一个生态系统;这些工具的目标是自动化、个性化和集成化,包括对PAAS平台的支持等;

那么Docker有什么用呢?对于运维来讲,Docker提供了一种可移植的标准化部署过程,使得规模化、自动化、异构化的部署成为可能甚至是轻松简单的事情;而对于开发者来讲,Docker提供了一种开发环境的管理方法,包括映像、构建、共享等功能,然后者是本文的主题。sql

 

Docker的安装和构成

Docker官方自己提供了很是具体的安装教程,这里不说具体的安装过程,请参考Docker安装(Mac系统),重要的是描述下原理和安装完成后的结构,好对Docker更好的了解。 因为LXC自己不支持Mac内核,所以须要跑一个VirtualBox虚拟机(TinyCoreLinux)来安装,幸亏Docker社区提供了一个很是方便的工具boot2docker(其实就是一个VBoxManage的包装shell脚本),用于安装Mac下的整个Docker环境。具体的结构以下:docker

docker-install

如图所示,安装完成后,具体状况以下:shell

  • 在Mac的home目录~/.boot2docker下建立了虚拟机所须要的文件,其中boot2docker.iso是虚拟机映像,这是一个由CD-ROM引导的TinyCoreLinux系统;而boot2docker-vm.vmdk文件则是你的虚拟机磁盘,你全部的持久化数据都存放在这里,包括docker建立的lxc容器等文件。
  • 在Mac下,docker被分为客户端docker-client和服务端docker-daemon两部分,若是是在linux(好比ubuntu),实际上则是同一个可执行文件同时充当客户端和服务端。docker-daemon能够监听unix scoket,也能够在tcp socket(默认端口为4234),docker-client会经过一个叫DOCKER_HOST的环境变量读取服务地址和端口,所以你应该在你的bash_profile文件里面添加这么一行:ubuntu

     

     

docker-daemon跑在虚拟机上,这个程序实际上就是接收docker-client发送过来的消息命令,建立、启动和销毁lxc容器,以及docker自己的版本管理、映像存储等等 运行你的第一个docker容器 安装完成后,就差很少能够开始建立和运行docker容器了,在这以前,你首先得下载一个Image,什么是Image?咱们先来了解docker的2个基础概念:ImageContainer

Container和Image 在Docker的世界里,Image是指一个只读的层(Layer),这里的层是AUFS里的概念,最直观的方式就是看一下docker官方给出的图:

docker-filesystems-multilayer

Docker使用了一种叫AUFS的文件系统,这种文件系统可让你一层一层地叠加修改你的文件,最底下的文件系统是只读的,若是须要修改文件,AUFS会增长一个可写的层(Layer),这样有不少好处,例如不一样的Container能够共享底层的只读文件系统(同一个Kernel),使得你能够跑N多个Container而不至于你的硬盘被挤爆了!这个只读的层就是Image!而如你所看到的,一个可写的层就是Container。

那Image和Container的区别是什么?很简单,他们的区别仅仅是一个是只读的层,一个是可写的层,你可使用docker commit 命令,将你的Container变成一个Image,也就是提交你所运行的Container的修改内容,变成一个新的只读的Image,这很是相似于git commit命令,感受真棒!

实际上这就是Docker对Container映像的版本管理基石,AUFS文件系统实在是太美妙了,更多细节能够参考DotCloud的这篇文章

运行和退出

在了解了Image和Container的概念后,咱们能够开始下载一个Image,Docker的好处就是提供了一个相似github的Image仓库管理,你能够很是方便pull别人的Image下来运行,例如,咱们能够下载一个ubuntu Image:

 

 

这里的13.10是一个Tag,相似于git的tag,这里的tag能够为你制定一个ubuntu的版本。下载完成后,执行docker images命令能够列出你已经下载或者本身构建的image:(请容许我使用可爱的马赛克 :) )

QQ20140322-1

你能够看到ubuntu:13.10的大小为178MB,以及它的IMAGE ID。 如今咱们开始运行一个Container,命令很简单,例如咱们想运行一个执行Shell终端的Container:

QQ20140322-2

如你看到的,你已经进入到一个Shell里面,能够执行你想执行的任何命令,就和在ubuntu里面同样,进去后默认是在根目录/下,能够看到经典的unix/linux目录结构,以及你所运行的bash版本等信息。你能够给你的Container定一个名字,经过–name选项,例如这里命名了shell,往后你就能够直接用这个名字引用Contanier。

退出一个Container也很简单,你直接exit就行了。 其余更多的命令这里不作赘述,由于官方的文档已经很是全面,这里只是给一个直观的初步印象。下面进入主题。

利用Docker搭建开发环境

咱们先看看程序员在搭建开发环境时遇到的一些问题:

  • 软件安装麻烦,好比不少公司都使用redhat,通常开发人员又不给root,安装一个nginx或者是mysql都得本身下载编译安装 权限问题,没有root,一些软件没法运行,例如dnsmasq;
  • 没有root,没法修改hosts,没法netstat -nptl,没法tcpdump,没法iptable
  • 隔离性差,例如不一样的开发人员若是在同一台主机环境下共享开发,虽然是用户隔离,但端口若是不规范可能会冲突;同一个Mysql若是权限管理很差颇有可能误删别人的数据
  • 可移植性差,例如和生产环境不一致,开发人员之间也没法共享;更严重的状况是当有新人入职时,一般须要又折腾一遍开发环境,没法快速搭建

这些问题能够经过在本地搭建虚拟机来解决,但虚拟机是一个很笨重的解决方案,Docker是一个很是轻量级的方案,并且还拥有虚拟机没有的一些功能,例如标准化Image,Image共享等,更重要的是,利用Docker,你能够运行很是多的容器,在你的Mac下搭建一个分布式的开发环境根本不是什么大的问题,并且对内存、磁盘和cpu的消耗相比传统的虚拟机要低许多,这些都要归功于AUFS和LXC这两大神奇的技术。

构建基础Image

想要搭建一个节省磁盘空间和扩展性良好的开发环境,最重要的第一步就是构建一个基础性的Image,好比你的主要开发语言是Ruby,那么你确定须要一个已经安装好如下工具的基础Image:

  • ruby
  • bundler
  • gem

而后在此基础上,你能够扩展这个基础的Image(下面叫base)为不一样的开发环境,例如rails,或者是nats。固然,你的这个base也能够从别人的Image扩展而来,还记得咱们刚刚pull下来的ubuntu:13.10这个Image吗?你能够从这个Image扩展开始构建你的base,如何作呢?Docker提供了一种标准化的DSL方式,你只须要编写一个Dockerfile,运行docker build指令,就能够构建你本身的Image,这有点像Makefile和make命令同样,只是你们要构建的内容和构建语言不一样。

Dockerfile的语法请参考Dockerfile Reference,这里给出上面提到的Ruby开发的base Dockerfile示例:

 

 

这里只用到了很简单的2个指令:FROM和RUN,FROM指定了咱们要扩展的Image,RUN指定咱们要运行的命令,这里是安装ruby,gem、bundler等软件。写好Dockerfile后,运行如下指令就能够建立你的base image了:

 

 

-t 选项是你要构建的base image的tag,就比如ubuntu:13.10同样 –rm 选项是告诉Docker在构建完成后删除临时的Container,Dockerfile的每一行指令都会建立一个临时的Container,通常你是不须要这些临时生成的Container的 如你所想,咱们能够像运行ubuntu:13.10那样运行咱们的base了:

 

 

这里咱们使用dev:base这个Image运行了一个irb解释器(Ruby的交互式解释器)。 在构建完base以后,你能够依样画葫芦构建你的rails环境,很简单,只须要FROM dev:base,而后RUN安装你的rails组件就能够了,再也不赘述。最终你可能构建的开发环境是这样的:

docker-dev

如上图所示,base和service都是从ubutnu:13.10继承而来,他们做为不一样的基础开发环境,base是ruby开发环境(也许命名为dev:ruby更为合适?),而service是一些基础数据服务,例如mysql,memcache,我建议将这些第三方组件集中在一个Container中,由于他们的环境不常常修改,能够做为一种底层服务Container运行,除非你须要构建分布式的服务,例如memcache集群,那能够继续拆分。

指定Image入口

当你构建完你的base Image和其余应用的Image以后,你就能够启动这些Image了,还记得前面咱们给出的运行命令吗?

 

 

这里咱们运行了一个bash,这样你就能够在shell里面执行你所想要执行的任何命令了,可是咱们有时候并不想每次都启动一个shell,接着再在shell里面启动咱们的程序,好比一个mysql,而是想一启动一个容器,mysql服务就自动运行了,这很简单,Dockerfile提供了CMD和ENTRYPOINT这2个指令,容许你指定一个Image启动时的默认命令。CMD和ENTRYPOINT的区别是CMD的参数能够由docker run指令指定的参数覆盖,而ENTRYPOINT则不能够。例如咱们想运行一个memcached服务,能够这么写Dockerfile:

 

 

或者能够这么写:

 

 

注意不要把memcached启动为后台进程,即加上-d选项,不然docker启动的container会立刻stop掉,这点我也以为比较意外。 接着咱们build这个Image:

 

 

这样,当你build完你的Image后,你能够直接将该Image运行为一个容器,它会自动启动mysql服务:

 

 

注意使用-d (detach) 选项,这样这个container就会做为后台进程运行了,接着你可使用docker ps命令查看是否有在运行。

磁盘映射

大部分时候你会须要把你host主机(宿主)上的目录映射到Container里面,这样你就很是方便地在host主机上编辑代码,而后直接就能够在Container里面运行它们,而不用手动copy到Container里面再重启Container。按理将host的目录映射到guest(指Container)上应该是一件很容易的事情,就好像VMWare那样,但惋惜的是,因为Mac上的Docker多了一层虚拟机,所以多了一层周折,你必须先VM上的目录经过sshfs mount到host(指Mac)上,而后再将你的目录或文件copy到这个mount的目录,再将VM上的这个目录映射到Container里,听起来比较拗口,画个图会清晰不少。

docker-disk-map

如上图所示,VM里面的/mnt/sda1/dev/目录(你须要本身建立)经过sshfs命令mount到了host主机(Mac)的~/workspace/dev/目录 ,而VM里的/mnt/sda1/dev/目录又被映射到了Container的/src/目录下,这样你就能够在Container里面的/src/目录下访问你的host文件了。具体如何作呢?首先你须要安装sshfs命令,而后将VM的password写到一个文件中,例如~/.boot2docker/b2d-passwd,在用sshfs命令mount起VM的/mnt/sda1/dev目录:

 

 

接着你在run一个Container的时候须要经过-v选项来将/mnt/sda1/dev/映射到/src目录:

 

 

这样你就能够在你的Container的/src目录下看到你host里的文件了。 磁盘映射还有2个地方须要注意:

  • 你的文件其实是存储在VM里面的,也就是说你须要将你的目录或者文件copy到VM里面,你sshfs以后,就是copy到~/workspace/dev目录下
  • 千万不要sshfs mount非/mnt/sda1下的目录,由于VM里面跑的是TinyCoreLinux,这个OS的rootfs是临时性的(放在内存的,实际上就是boot2docker.iso文件里面的一个rootfs),所以其根目录/下的东西(包括/home)根本不会持久化,只有/mnt/sda1这个目录下的才能持久化。若是你放在/home目录下,只要VM一重启,就会丢失的,/mnt/sda1则不会,实际上就是那个~/.boot2docker-vm.vmdk文件挂载到了/mnt/sda1目录下

端口映射

和磁盘映射同样,你有时候会须要将Container的端口映射到host主机上,一样蛋疼的是,因为多了一层VM,端口映射也显得比较麻烦。首先你须要设置VirtualBox的端口映射,而后再将Container的端口映射到你的VM里面:

docker-port-map

具体是这么作的,经过2条命令:

 

 

也就是说在docker run的时候经过-p选项指定要映射的端口到VM,而boot2docker ssh命令则是将VM的8000端口映射到了host(Mac)的8000端口,这样你就能够经过Mac的localhost:8000访问Container的8000端口了。 其实,有另外一种解决方案就是你不用映射到host(Mac),而是直接登陆到VM里面进行访问就行了,boot2docker ssh就能够登陆到VM,这样就相似于你的host是ubuntu,但这种解决方案的问题是这个ubuntu太弱了(TinyCoreLinux),若是你在这个ubuntu里面开发代码,或者是运行浏览器,是很是蛋疼的事情,关键仍是这个ubuntu是每次重启都会复原的!因此我建议仍是作多一层映射好了。 最后,实际上在VM里面,你是能够直接访问全部的Container的端口的,由于VM到Container的网络都是桥接的。

其余的一些坑

在使用的过程当中,还遇到一些很多的坑:

  1. /etc/hosts文件没法修改,这样你就不能本身作域名解析
  2. VM的系统时间是UTC +0000的,并且貌似没法修改
  3. Container的IP没法指定为静态IP,所以每次重启Container时,IP可能会变化

第1个问题的解决方案是经过安装dnsmasq软件来作域名解析:

 

 

第2个问题的解决方案就稍微麻烦些,起码我没有找到更好的解决方案,我是将boot2docker.iso文件从新制做一次来解决这个问题的:

 

 

第三个问题暂时没法解决(可能须要编辑底层的LXC配置文件)。

docker的限制以及后续的一些想法

docker其实仍是有一些限制的:

  • 要求你的环境是Linux的,并且内核必须很新(>= 2.6.27 (29)),这实际上是LXC自己的限制,和docker无关
  • docker的Container目前host是不能修改的,固然有解决方案(dnsmasq)
  • docker的Container也暂时没法指定静态IP

用docker做为开发环境甚至是生产环境其实还有不少地方值得尝试:

  • 在团队内部构建本地的仓库,标准化全部的开发环境,使得团队的新人能够快速上手
  • 在生产环境部署docker,这实际上是PAAS的虚拟化和自动化的一种方式,利用LXC和Docker可以更便捷地实施PAAS
  • 尝试用docker作分布式集群模拟和测试,成本会更加低廉,更加容器维护
相关文章
相关标签/搜索