Docker作为一把瑞士军刀在DevOps扮演的角色已经被很多文档提到过了。但实际上Docker管理应用容器比在云上部署服务器更加有用。Docker旨在在很多通用开发场景里显著提升开发效率。本教程从一个开发者的视角阐述Docker如何有用,我会介绍Docker,解释基本的概念和术语,并列举几个实际动手操作过的例子。
你能发现:
- Docker能使Node.js Web应用开发更便捷。
- 你能在Apache Tomcat服务器上用Docker快速创建并测试Java EE应用。
- Docker能加快通过Bottle框架开发、测试、部署Python web应用的时间。
- 你能使用Docker容器在几分钟内创建并测试一个三层架构的带有NoSQL数据库的Node.js应用。
你能在单台Linux系统上获取上述提到的所有好处,而不需预先做任何配置。再完成以下的例子后,你能很好的感受到通过Docker容器来增加日常开发的效率的乐趣。
Docker基本概念
Docker是一个容器管理器。容器打包一个应用以及它的依赖关系。你能从仓库的每个镜像里实例化出一个容器并在主机上各自隔离的环境里运行这些容器。因为容器运用的虚拟技术是轻量级的,所以你能同时运行多个容器。
你能使用Docker容器管理器做如下工作:
- 从镜像运行容器
- 监视和管理正在运行的容器的性能
- 停止运行的容器
- 在运行的容器里执行额外的任务
- 启动和重启运行的容器
- 执行其他管理任务
容器化的应用几乎能在任何地方运行:在PC桌面,在服务器,在公有云,甚至是在移动设备。
Linux是目前最广泛使用和支持的Docker平台。Docker使用的容器化技术(轻量级虚拟化)在Linux平台相当成熟,使用了最近的Linux特性如控制组和命名空间。
如何运行Docker
可以通过docker命令行运行Docker容器,运行时有很多选项和参数。如下是运行一个容器化应用的简单命令:
docker run -d mysql:5.5
Docker的公共仓库是 Docker Hub
。用户能注册并创建自己的镜像仓库,并将镜像给其他人用。例如,你能找到如下镜像:Tomcat server、Node.js,以及最流行的开源数据库。Docker Hub运营的宗旨跟 GitHub
一样,在这里,APP镜像被全球的DevOps以及开发者共享或者协同创建。
当运行docker run -d mysql:5.5命令的时候,MySQL(5.5版本)的镜像自动从Docker Hub下载下来,当本地没有的时候。
创建容器化镜像
你可以使用Docker命令行客户端来创建镜像。方式之一是通过使用Dockerfile-一个含有关于如何安装,构建,以及配置应用和依赖关系的文件本件。
另外一种使用Docker命令行客户端来创建镜像的方式是通过手工交互式的命令。你能在一个运行的容器里安装和配置一个应用和它的依赖关系,并提交和保存这个容器作为一个镜像。
一个Docker镜像由好几层组成,每一层大致等效于这个应用在安装的时候写到磁盘的改变。Docker管理这些层并存储有效的数据。例如,一个MySQL的镜像的各层,可能由Linux OS、Perl、共享库、MySQL安装文件、MySQL的基本配置文件这些层组成:
当你用Dockerfile创建一个容器或者手工交互式命令定制化一个容器,一般你不需要重头开始,而是会通过已有的Dockerfile或者Docker Hub里已有的镜像来做调整。这样而言,开发者们能够通过在创建和管理一系列有用的镜像的过程中分工和协作。
推送镜像到仓库(例如Docker Hub)的命令是docker push。下载镜像到本地的命令是docker pull。
开始之前
在开始前,你需要一台安装了Docker的Linux服务器。要安装Docker,请按照 安装说明
选择跟自己相符的操作系统版本进行。
如果你没有可用的Linux服务器,你可以使用云。大部分的云主机提供了可直接运行的虚拟机,这些虚拟机能通过SSH协议或者基于Web的终端在几分钟内连上。这些主机是典型的跟Docker兼容的主机。
本教程里提到的例子假设你已经在一个linux服务器上运行了Docker.如果你已经在MAC或者其他非Linux上运行着Docker,你的容器应该运行在一个额外的虚拟机上(通常是VirtualBox)。这种情况下,你需要按照下面中提到的修改下命令。
检查Docker版本
运行如下命令检查Docker版本:
docker --version
如果你正运行1.8.3或者更高的版本,你的系统已经是完整的了。即使要用Java、Tomcat、Node.js、Python、Apache CouchDB容器做开发,也不需要安装或者卸载这些软件。这就是用Docker做开发的魅力。
下载镜像
第一次从Docker Hub下载一个镜像,你必须先下载它到自己本地的仓库。下载可能很耗时,这取决于你Internet网速,但随后的镜像使用将会非常快。
运行下面的命令下载所有你在本例子中会用到的镜像:
docker pull node:0.10.40
docker pull java
docker pull tomcat:8
docker pull webratio/ant
docker pull python:3.5
docker pull frodenas/couchdb
如果想知道更多这些镜像的信息,请访问Docker Hub上 node
、 java
、 tomcat
、 webratio/ant
、 python
、 frodenas/couchdb
各自的相关页面。
当完成以上步骤,就可以开始用Docker来运行一个Node.js应用了。
快速建立并开发服务器端JavaScript代码
代码包中( 下载地址
)的JavaScript目录包含了用Node.js写的建网上商店的源代码。这个应用使用的是Express Web框架和 Jade
Node模板引擎(关于这个应用的更详细信息,见 部署一个Node.js应用到云端
)。通过使用Docker,你能在你的电脑上运行这个应用而不用装Node.js以及依赖包,并排查包冲突的问题。
main程序在app.js里面。CSS和其他javascript文件在javascript/public/static里面。Jade模板在javascript/view。
运行如下命令来安装这个应用的Node.js的依赖包:
docker run -it --rm --name lllnode -v "$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 npm install
这条命令启动了Node容器,在容器内挂在当前的APP目录,并在应用内运行npm安装:
- 主命令是docker run node:0.10.40,这会创建一个容器并运行node:0.10.40这个镜像。
- -it选项让Docker分配一个伪终端(可选的参数为-d,该参数要求Docker在后台运行容器)。
- -rm参数表示清除,意味着只要你退出容器,Docker就会删除容器。如果不指定这个参数,容器会以停止状态一直存在磁盘上,你能从停止状态重新运行它。然后,重启容器对于那些忘了删除容器的用户来说是一件挺繁琐切容易遗忘的事。如果你不用--rm选项,你可以通过
docker ps -a
这条命令在任何时候查看有多少停止状态的容器。
- --name lllnode选项指明了容器的名字,命令在其他Docker命令里应用容器的时候非常有用。如果不给容器命名,Docker会给容器分配一个名字-这个名字通常没有说明意义。可以通过容器的ID(一个长的,可读性很差的十六进制数)来引用容器。
- -v "$PWD":/usr/src/myapp选项创建了一个卷挂载点,将当前宿主机的工作目录($PWD)映射为容器内的/usr/src/myapp。应用的Node.js的源代码(或者其他放在当前工作目录的Node.js代码)就能在容器内被访问了。
- -w /usr/src/myapp选项设定了目前正在运行的命令的工作目录。本例中,工作目录改成了挂在点。
- 末尾的npm install命令会在容器的工作目录运行。这样的效果就是,你不用安装Node.js或任何的前置条件,就可以在容器的工作目录运行npm install命令。
在命令的输出结果里你可以看到所有的npm安装过程中的依赖关系:
在依赖包被安装后,就可以在容器里运行应用了。使用如下的命令
docker run -it --rm --name lllnode --env PORT=8011 --net="host" -v "$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 node app.js
这条命令跟之前跑的一条相似。但需要注意这条命令在容器里跑的命令是node app.js,这会启动应用。
仔细看看如下app.js的内容,你会发现如下行:
var appEnv = cfenv.getAppEnv();
app.listen(appEnv.port, appEnv.bind, function() {
console.log("server starting on " + appEnv.url);
});
这几行内容使得Express web server会监听环境变量PORT指定的端口。为了在容器里设置这个变量,刚才敲的Docker命令使用了--env PORT=8011这个选项。
此外,命令的--net="host"选项导致了容器使用了宿主机的内部网络。这个应用正在监听宿主机的8011端口。
在浏览器里输入 http://localhost:8011/
,你就能看到有刚才的容器运行产生的Lauren水墨画的页面。
你可以在任何时候退出容器修改代码,然后再重新运行容器-可以在几秒钟内完成,而不影响开发环境。
几秒内从Node.js切换到Java EE
现在你应该确信了Docker能够在几秒内启动并运行Node.js的应用,你可能想知道它怎么处理更庞大一些的开发,如Java EE开发。
这里假设你已经读过了我的" 用Vaadin进行全栈式Java web开发
"章节并且乐于尝试J2EE Vaadin的例子。但你并不想安装JDK和破坏目前系统的已有环境,那么Docker就可以来救场!
代码
里的java目录的ArticleViewer.war包是一个由多个Vaadin UI组件构成的应用。能够在Tomcat 8服务器上运行这个war包。首先从之前下载好的镜像里启动实例:
docker run -it --rm -p 8888:8080 --name=tomcat_server -v $PWD:/mnt tomcat:8
这条命令你应该很熟了。容器的名字叫做tomcat_server.容器里的工作目录指定的是/mnt。
打开一个浏览器并输入 http://localhost:8888/
,你就会看到熟悉的Tomcat 8的欢迎界面。
注意你是使容易运行在交互模式(通过-it选项),在这个模式下,日志会持续不断在标准输出界面出现,这有利于开发。
将应用附到运行的容器里
现在,容器内的/mnt目录是工作目录,ActiveViewer.war就在这个目录。
复制ActiveViewer.war包从/mnt目录到Tomcat 8的webapps目录,这个war包就会自动被部署:
docker exec tomcat_server cp /mnt/ArticleViewer.war /usr/local/tomcat/webapps
这次没有用docker run命令,因为tomcat_server容器已经在运行,反而,这次需要切换到运行的容器内部,执行一个拷贝命令。因此,需要执行docker exec命令,并在末尾加上supplying the cp /mnt/ArticleViewer.war /usr/local/tomcat/webapps。
观察ArticleViewer应用被部署后,窗口的日志。
现在在浏览器里输入应用的URL- http://localhost:8888/ArticleViewer/
,点击左侧列表的一篇文章,查看右侧的内容。
停下来思考一会儿。你没有在系统上安装JDK或者Tomcat 8,但你依然可以在几秒内在一个Tomcat 8服务器上运行Vaadin应用的代码-感谢Docker。
开发构建Java web应用
正如你所见,你能通过Docker轻松的部署一个web应用来做测试。同样地,你也能通过Docker从Java源代码里编译和构建web应用-同样不需要破坏你的开发环境或者花很长时间预先安装环境。
从 代码下载
的包切换目录到java/LaurenLandscapesJava。这个目录下面是一个Lauren水墨画应用的Java代码版本。
build.xml文件是一个标准的 Apache Ant
生成的文件,包含了编译代码和构建war包的说明。通过使用webratio镜像,你能快速编译生成war包,当代码发生改变的时候:
docker run -it --rm -v "$PWD":/mnt webratio/ant bash -c 'cd /mnt; ant'
现在这条docker命令你应该完全熟悉了。这条命令会在挂载到容器内的目录/mnt下面运行ant生成工具。ant编译在src子目录下的所有java代码,并生成一个WAR文件,将它放置在dist子目录下面,命名为lauren.war。
复制lauren.war包,从java/LaurenLandscapesJava/dist到Java目录
cp dist/lauren.war ..
你可以用下面的命令部署lauren.war应用到tomcat服务器上:
docker exec tomcat_server cp /mnt/lauren.war /usr/local/tomcat/webapps
这条命令会运行在tomcat_server容器内并复制lauren.war包到Tomcat的webapps子目录。
在浏览器里输入 http://localhost:8888/lauren
,你能看到运行在你的容器化的tomcat上的lauren水墨画。
从Java切换到Python
设想你是一个全才-你想从java代码里跳出并使用Python的Bottle Web框架的可行性。为了帮你尽快探索Python Web应用开发同时又没有安装环境和解决依赖的麻烦-Docker又帮了你大忙。
切换到代码包的python/Laurens.Lovely.Landscapes目录。在那个目录下面是Lauren水墨画的Python版本-用Bottle web框架写的。模板在views子目录。
运行这个Python应用-不需要安装python在你的系统里-通过下面的Docker命令:
docker run -it --rm --name lllpython -p 8000:8000 -v "$PWD":/usr/src/myapp -w
/usr/src/myapp python:3.5 python wsgi.py
在浏览器里输入 http://localhost:8000/
,你就能看到lauren水墨画的python版本了。
当用Python做开发的时候,你可以在简单修改完代码后,重新运行上面的命令来做测试。运行Docker容器和运行本地的开发工具速度是一样快的。
添加并运行一个单独的数据库
大部分现在的Web应用涉及三层架构:浏览器作为client;中间件应用服务器例如Express、Tomcat 8、Bottle;底层数据库。通过使用Docker,你能快速添加一个数据库而不是安装它到你的系统里。
在这个最后的例子里,你会添加Apache CouchDB数据库到你的开发环境里。你能使用CouchDB API,这些API跟IBM Cloudant NoSQL数据库100%兼容,来测试CouchDB或者Cloudant。
切换到代码包的database目录。这个目录里是lauren水墨画应用的高级Node.js版本。这个版本从CouchDB里抓取了商店的目录信息。网上商城根据版画的目录动态的改变外观。(你在 这里
能找到更多的代码)
用如下的Docker命令运行Apache CouchDB的实例
docker run -d --name couchdb -p 5984:5984 -e COUCHDB_USERNAME=user -e COUCHDB_PASSWORD=abc123 -e COUCHDB_DBNAME=prints -v $PWD/data:/data frodenas/couchdb
这条命令使用了COUCHDB_USERNAME,、COUCHDB_PASSWORD和 COUCHDB_DBNAME三个环境变量来配置实例已满足代码里设的值。当前的工作目录被挂载到容器内的/data目录。CouchDB会写数据到这个目录,这就使得重启容器后,数据不会丢失。
在这个例子中,你运行容器是以-d选项而不是-it --rm选项。-d选项会在后台启动数据库。你可以使用docker ps命令看到所有后台的容器。
接着,安装应用的Node.js依赖:
docker run -it --rm --name llldepends -v "$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 npm install
Apache CouchDB实例不会保留任何数据。使用如下命令来运行dataseeder.js代码,这会在Apache CouchDB的实例里创建一个文件:
docker run -it --rm --name dataseeder --net="host" -v "$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 node dataseeder.js
在端口8011运行lllnode应用
docker run -it --rm --name lllnode --env PORT=8011 --net="host" -v "$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 node app.js
在浏览器里输入 http://localhost:8011/
打开Lauren水墨画这个应用商店:
你能看到澳大利亚的照片已经没有现货了。产品类用红色高亮了并且不能被选中。
如果要模仿为澳洲的相片补货,需要访问CouchDB数据库并修改文件。
CouchDB提供了一个简单的图形化用户界面,叫做Futon,来访问存储的文件。在浏览器里输入 http://127.0.0.1:5984/_utils/
打开Futon的图形界面:
在Futon里,选中prints这列。打开文件并扩展条目3.双击澳大利亚的print条目,打开文件编辑。修改quan的值由0变成3.点击右边绿色的检查按钮,然后点击左边顶部的保存按钮。
随着刚才数据库的修改,重新在浏览器里输入 http://localhost:8011/
,你会看到现在商店里的货都是有现货的。澳大利亚的相片那栏不再是红色并且是可被选中的了。
当你工作中用到三层架构时,Docker可以简化开发流程并且使得底层数据库的部署更加容器。