我的 Docker 的初体验

首先必须要说:Docker 真的很有趣,很好玩。

一直有个说法,2014年是 Docker 年,Docker 发展得顺风顺水,开发社区活跃,国内外巨头迅速接纳,资本青睐,相关创业公司雨后春笋... 几乎任何跟 Docker 相关的新闻一出来,就立即盘踞各大技术媒体的头条,似乎所有人都突然间达成了一个共识:Docker 就是云计算的未来。

我自己算是后知后觉,最近折腾这个长草多年的博客的时候,顺便玩了一下,不准备深谈 Docker 技术的未来,就谈谈自己的初体验。

Docker 目前的生态环境非常好

各家云主机服务商只要提供 Ubuntu 14 或者 CentOS 7,就能完美支持 Docker。在 Mac 上有kitematic,非常傻瓜化的GUI工具,让配置,下载,安装,管理 Docker 容器变的超级轻松。

激进的云主机服务商如 DigitalOcean,EC2,Rackspace,QingCloud等直接提供 CoreOS 镜像,CoreOS 是一个专门为运行容器(Container)的云平台而量身打造的系统,CoreOS 也特别有趣,以后找机会专门写点 CoreOS 的体验。

目前这个博客跑在 DigitalOcean 上,$5/mo 的 VPS,内存 512MB,20G SSD,CoreOS 607 stable 系统, 跑了 Ghost,Shadowsocks 等若干个 Docker 应用,感觉绰绰有余。

Docker 易入门难深入

如果你想用 Docker 搭建一个博客比如本站,具体怎么做呢?只需要一条命令,一个用 ghost 的博客就搭建好了,访问 IP 或者域名,你就可以开始写文章了。

docker run --name blog -p 80:2368 -d ghost  

如果你只想尽快通过 Docker 把应用跑起来,那么你开始只需要了解 docker 命令行的用法就够了,网上找到大量的入门教程,包括视频,差不多半个下午的时间就能成为 docker 命令行高手了。

如果你想灵活运用 Docker,那么你需要用抽丝剥茧一般精神,一层一层搞明白很多层次的东西才行:

  • 深入理解 Linux 系统的运维,积累一定的运维的最佳实践经验
  • 理解你部署的每一个应用的运行机制,如果不通过 docker,你应该如何构建部署这个应用,只有这样才能正确的使用 Docker 容器去运行应用
  • Docker 的命令行工具以及管理镜像和应用容器的生命周期的原则
  • Dockfile 的用法,Docker 是如何通过 Dockfile 把应用打包成镜像的
  • Docker Hub 的用法,搜索并下载别人创建的镜像,或者把自己的镜像推上去
  • Docker 的网络原理,如何把容器的服务端口暴露给 host 或者跟其他 docker 容器通过网络协作
  • Docker 如何持久化应用的数据,理解 Docker 的文件系统,数据卷和数据卷容器的原理
  • Docker 的底层原理,包括 Linux 容器,联合文件系统,CGroups,Network namespace,User namespace,资源管理等等

如果你真的要把 Docker 用好,管理好,所有这些涉及到的 docker 底层知识是跳不过去的。推荐一本入门书,因为 Docker 还在快速发展,最新的内容请关注官网,或者订阅Docker weekly newsletter

应不应该用?什么时候用?

我的答案是用!现在就应该开始用,可以先从周边服务开始。

基于容器思想来部署服务和应用,对资源的利用率,自动化都有意义,但是最大的好处是简化了服务器构建,比如安装配置 MySQL,Redis,Queue,Nginx,等,基本都是一条命令搞定。甚至你交付应用给客户,可能也只需要客户执行一条命令,安装,升级,部署就完成了。

其实计算机领域里面有一个亘古不变的道理:为了让一个东西看上去很简单所需要付出的努力往往反而更多,Docker 也是一样。Docker 并不能让你免去运维,而是相反,对运维人员水平的要求反而更高了,为了支撑 Docker 的简单易用特性,背后需要运维人员在镜像的维护和容器管理上,要考虑的东西其实更多,环境更加复杂。

吐槽

槽点1:从宿主系统拷贝文件到容器内很麻烦

以Ghost博客为例,我把之前所有的文章从原平台全部导出,在本地将文章在本地运行的博客上处理好,得到一个 sqlite3 的数据库文件,我希望把这个文件直接用于服务器上的博客。

Docker 1.0 开始提供了一个命令 docker cp 可以让你很方便的把容器内的文件拷贝到宿主环境:

docker cp container:/path/to/file .  

但是反过来,想把文件拷贝到容器内怎么办?官方的说法是 Docker 1.6 发布后,docker cp 才会支持从宿主环境拷贝文件到容器内,目前的解决方案是通过 docker exec 加 shell 管道的方式,非常 trick

docker exec -i blog sh -c 'cat > /var/lib/ghost/data/ghost-dev.db' < ./ghost-dev.db  

槽点2: 数据卷和文件权限管理

Docker 的镜像文件系统则是一种类似一层一层累加起来的只读文件系统,当容器启动的时候,除了载入所有来自镜像的只读文件系统外,还会在其上挂载一层可读写的文件系统层,当删除或者重启容器后,位于读写层中的内容就会丢失。

为了备份和持久化应用数据,可以创建一个数据卷(Volume)来解决,以当前的博客为例,我需要备份配置文件,自定义的theme,以及数据库数据。用数据卷存储数据可以绕过 Docker 文件系统,把容器内某个文件或者目录,映射到宿主环境中。理解起来就是可以在宿主环境中直接访问容器内的文件,就像访问本地文件系统一样。

Docker 内通常是以 root 用户运行应用的,而宿主环境则通常为了安全,不建议直接使用root用户,这就导致了在容器内看到的文件的权限跟宿主环境看到的不一样,虽然可以在启动容器的时候传入一个用户id进去,命令如下:

docker run -i -t -user="myuser" ...  

但是这里 myuser 在容器中的 uid 跟宿主的 uid 是不一样的,所以容器内的同名用户权限不等同于宿主用户权限。其实也没有必要一样,因为各自环境中的用户权限应该由各自的环境来定义。docker 容器内的用户权限是继承了宿主的 docker 进程的权限,缺乏一个宿主和容器内用户的权限映射机制,详情见 github issue#7198

我在这里踩到一个坑,当首次在容器内运行 Ghost 后,我直接在容器外部用备份覆盖了某个配置文件,因为文件权限的变化,容器重启后,enterpoint.sh 执行到此处会报错并自动终止当前容器的运行,换句话说,就是这个容器再也 run 不起来了,错误信息说是文件不存在或者权限错误。对于一个无法再次启动的容器,你什么也做不了,可能只能干掉这个容器,然后从镜像再次创建一个新的了。

文件的 ownership 和 permission 对于 Linux 来说非常重要,是整个系统安全的基石,希望将来 Docker 能尽快妥善解决。其实这个问题 Docker 社区讨论了很久,但是当前的解决方案在目前看来都不成熟,目前社区推荐的做法是使用 data-only 容器来持久化应用数据,一开始我并不接受,容器的数据存储在另外一个容器内,感觉还是不太放心,如果完全依赖容器,万一容器挂了怎么办?后来我看到一篇文章介绍说应该放弃数据从逻辑上到物理上必须保存在宿主环境中这一执念。如果应用数据也存放在容器中,那么对于运维人员来说就不需要再去关心数据的物理存放位置了。别以为 data-only 容器那么简单,想要正确使用 data-only 容器你必须要先理解它背后的原理才不会踩坑。

其实还有其他槽点,也可能是因为我对 Docker 的理解还太粗浅,这里就暂时按下不表了。

Docker 已经事实上改变了整个当前的云生态,我希望 Docker 发展的更好,希望各家云平台大力增加对 docker 的支持,也同时看好一些基于 Docker 的创业公司能在未来有非常好的前景,比如 https://www.daocloud.io