Docker 是时下最为流行的开源容器技术,而 Coding 又是从来以革命者先驱的姿态拥抱新技术的,所以我们也与 Docker 擦出了火花,有爱有恨,有苦涩的过去时光,也有甜蜜的未来规划。
关于容器
注:本文容器仅仅代表 linux 容器,不包含其他操作系统的相关技术,更不是什么瓶子罐子之类的。:)
-
静态视角观察 镜像: 程序、数据以及它们所依赖的所有文件、目录和配置的集合 表现为: rootfs 文件系统结构和library 目录: /dev /proc /bin /etc /lib /usr /tmp 指令: sh cd cp mv rm… 配置: rc inittab fstab etc… 设备: /dev/hd* /dev/tty* /dev/fd0 etc…
-
动态视角观察 容器 : 程序运行起来的执行环境的视图和边界 表现为: 阉割版的虚拟机?高级版的进程? What ever。我认为容器就是容器,既不是阉割版的虚拟机,也不是高级版的进程。为了更好的理解,什么叫 “程序运行起来的执行环境的视图和边界”,我们需要透过现象看本质,关于容器的本质。
容器的本质
容器依赖高版本 linux kernel 提供的如下几种技术实现:
- linux namespace 实现容器隔离 例如可以在进程 PID,网络等级别上实现隔离。 namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的namespace。每个namespace下的资源对于其他namespace下的资源都是透明,不可见的。因此在操作系统层面上看,就会出现多个相同pid的进程。
例如,我们可以在 /proc/PID/ns 目录下找到一些 namespace 相关的信息:
关于 namespace 这里就不再做过多介绍,有兴趣的读者可以自行去查找相关资料
- cgroups 实现资源配额分配管理, cgroups 可以设定容器的 CPU 份额,内存占用量等比较重要的计算资源,以防止某些程序占用过多资源影响到其他程序 我们可以在 /proc/cgroups 和 /proc/PID/cgroup 文件中查看 cgroups 相关的信息。
-
selinux 以及 capabilities 实现安全权限控制
-
是的,你没看错,容器就是依赖这些 linux kernel 提供的技术实现,以至于有人用 shell 写了个容 器的实现,名叫 Bocker (https://github.com/p8952/bocker)
Docker 是什么
想了又想,觉得这个概念还是没啥好说的,因为我觉得你去 docker 的官方网站看一看,或者随便找个搜索引擎搜一搜都比我解释的清楚。这里谈一谈个人认为的 Docker 的一些看法吧。
docker 是一种容器技术,又在容器技术之上创造了很多新的实用的功能和概念,例如 image,registry,layer,RESTful API 等等。docker 的特点是简单能快速上手,目标是要解决 Build,Ship ,Run。这也是 docker 官方的 slogan。
关于 libcontainer 和 runc
Google 是最早研究容器技术的公司,并在内部有大规模部署,libcontainer 是 docker 最初根据 Google 提出的一些关于容器相关的论文等做出得一个实现库。当然实现不止 docker 一个 ,还有例如 rocket ,以及很多大公司内部的一些实现等。为解决容器技术分裂化,各大技术厂商组建了 opencontainer 联盟,希望能统一容器标准。随后 docker 把 libcontainer 捐献给了 opencontainer联盟。 源码位于:https://github.com/opencontainers/runc 的 libcontainer 目录中
docker 的优势
我觉得 docker 优势中最重要的一点,也是 docker 官方的标语 Build, Ship, Run ,简化标准化了应用生命周期流程。
而如果配合编排系统后,还能做到如下几点:
- 使快速的分布式大规模部署成为可能
- 使基础资源和应用分离成为了可能
- 使弹性计算可以做的更好
docker 常见周边容器编排系统
有了容器,要想在一定规模的硬件计算资源上应用,往往需要做容器编排,例如定义哪些容器在哪些服务器上运行,相互之间的关系是什么,负载均衡,热备等等的一系列操作。时下有如下这些编排软件或框架比较流行,这里做一个简要介绍:
-
Apache Mesos:其本身是一个计算资源调度系统,更适用于大数据计算场景,其并不关心线调度的计算任务是进程,还是容器,现在有一些爱好者给 Mesos 做了 Docker 的provider,使得两者可以合作愉快。
-
Swarm:是 docker 官方推出的一个 docker 容器集群软件,可以管理批量的 docker daemon ,可以将运行在多个服务器上的 docker daemon 模拟成一个 docker daemon,基本能做到 90% 的 API 兼容度。个人认为使用场景有限,而且目前才发布几个月,仍处于重度开发中。
-
compose:也是 docker 官方推出的一个容器集群的编排软件,但是其设计简单,以静态配置文件,或者说模板的形式在文件中定义容器运行位置,监听端口,挂载目录卷等信息。
-
kubernetes: 是 Google 推出一套容器编排技术,是 Google 内部强大容器框架 Borg 的一个简单抽象和模拟。以 Pod ,Service,Controller 等概念架构,兼具部分负载均衡和热备功能。
Coding 目前使用容器技术的状况
Coding 踏入容器技术的大门最早是演示平台( 基于 CloudFoundry 的 PaaS 系统 ),演示平台依赖容器技术进行应用隔离。但是一开始 Coding 并没有系统中其他功能服务中使用容器技术,随着 DevOps 发展,越来越发觉容器技术是必不可少的一环,是未来发展的方向,开始在其他功能和服务中逐渐使用 docker 来取代一些传统的做法。
-
Coding 目前使用 docker 在生产环境部署了相当规模的容器来运行 Coding 的一些微服务,例如 发送 email,用户头像更新,发送 APP 推送,编译 Markdown 等微服务,包括 blog.coding.net 等也都运行在 docker 里。这一步在内部我们称之为 dockerize。也就是把传统的运行的应用实例挪到 docker 中去,目前我们在系统中已经实现了 80% 的应用实例 docker 化。
-
Coding 暂时没有引入现成的容器编排系统,而是选择像 compose 那样,自定义了容器配置定义文件模板,然后使用一个程序读取这些配置,从而操作相应的服务器上的 docker daemon 来管理服务器,管理容器,管理应用更新等等。
-
Coding 正在推进的一项工作是,推进各个应用组件在 docker 中构建项目,在 docker 中运行项目。
Coding 开始使用 Docker 后的一个观念就是 everything in docker。所以我们也把之前在本地跑得程序构建过程移到了 docker 中。我们给每个应用写了一个构建脚本,这个脚本就负责启动一个 docker container 然后在里面把项目构建完成, 得到一个 ready to run 的package ,可能是一个 jar 包,可能是一个 可执行程序,也可能是一个下载完依赖的 文件夹;然后编写一个 Dockerfile 用以描述该应用在什么样的环境下如何运行。直接交付 一个 docker image 给运维
示例 build.sh 文件
VERSION="test"
cur_dir=`pwd`
mkdir -p ./.src/target
cd ../../../CodingBlog
docker run -it -v "`pwd`:/app:rw" -v "$HOME/.gradle:/root/.gradle:rw" java:8-jdk sh -c "cd /app && ./gradlew clean build"
cp ./build/libs/CodingBlog-1.0.jar $cur_dir/.src/target/CodingBlog-1.0.jar
mkdir -p /data/git
cd $cur_dir
echo "$VERSION" > ./.src/.version
这个文件的执行结果就是要在特定的目录下生成特定的 readytorun 的jar 包,好处是执行者只需要安装有 docker 即可执行,不需在额外安装 jdk gradle 等等。注意上面一个小细节:把 .gralde 目录挂载进去,可以将依赖的 jar 包缓存下来,加快下次构建速度。
示例 Dockerfile 文件
# Base
FROM java:8-jdk
COPY ./.src/target/RepoManager-1.0.jar /app/
RUN useradd -m -u 1000 coding
USER coding
WORKDIR /app
CMD [ "java", "-server", \
"-Xms2048m", "-Xmx2048m","-XX:+UseFastAccessorMethods", \
"-jar", "./CodingBlog-1.0.jar" \
]
没有什么特别的就是 java -jar 运行就好了。
显而易见的,利用 docker,我们轻而易举的完成了应用构建的标准化,以及应用交付流程的简化。 另外,我们为了 image 的统一管理,搭建了 docker registry ,目前是 V1版本,方便给生产环境的快速分发。
Coding 目前简单的容器编排
Coding 在参考调研过目前的一些容器编排系统之后,决定自行写一套简易的符合 Coding 现状的编排系统,其实类似于 compose 或者之前的 fig 的概念,即用配置文件定义容器如何编排,包括容器配置,环境变量等等信息,然后写一个程序读取这个配置文件,再来操作 docker daemon。
我们的配置示例:
job: <
name: "repo-manager-5"
image: "repo-manager:latest"
run_on_host: "git-5"
envs: <
key: "port"
value: "8866"
>
volumes: <
container_path: "/data"
host_path: "/data"
read_only: false
>
>
我们以 job 的形式组织一个应用实例,目前并没有使用容器技术中的诸如 CPU 内存限制等,而且目前 所有的 docker 容器网络都配置为 host 模式,一方面是出于性能考虑,另一方面,也是为了与之前老的系统兼容,方便迁移。
我们写了一个命令行工具,可以用来读取这个文件,并操作 docker daemon。有诸如 start ,stop,restart,update,log,shell,inspect 这些功能。
例如,执行 update 操作,会列出当前的 image 列表,选择后,就可以进行全自动更新。
Coding 的开发环境
我们在开发团队内部推行一套 Coding-local 的机制,即使用一个 Vagrant VM,配合 Coding-local 这样一套构建脚本和 Dockerfile,得以让任何一个同事都可以执行很简单的几部操作就可以在一个虚拟机里面运行起整个 Coding 系统,方便不熟悉,却又需要使用的同事也能快速上手开发。
为保持生产环境和开发环境一致性,我们在 coding-local 环境里面也使用 job 这样的方式,以及 build.sh 构建脚本 Dockerfile 这样的方式来支撑整个系统,唯独少了 docker-registry, 因为在 coding-local 里面不存在 image 分发的问题。
目前 Coding 存在的一些问题
- 没有统一的构建项目和构建docker image 的规范,没有基础指导 base image
- 配置文件是一并打包入项目构建包和 docker image 中的,导致不同的实例使用不同的配置,或者需要修改配置时需要重新构建 docker image
- 少量组件之间的配置是 IP 或者域名强依赖的,导致组件无法在服务器之前漂移
- 开发环境太庞大,导致速度较慢,影响效率
- CI 系统不完善,无法做到开发,构建,测试,上线全自动化流程
发现 docker 的一些问题
- docker 容器在 stdout 有大量内容传输的时候,docker daemon 的内存会疯长,直到 docker daemon 被 OOM kill。
我们的应对办法:
- 向docker 官方提 issue
- 分析源码尝试找出原因
- 放弃使用 stdout 进行数据传输改为网络接口
- docker daemon 在频繁创建启动停止删除容器后,会残留很多垃圾文件,久而久之会导致磁盘 inode 节点被耗尽。该问题目前还没有找到特别好的应对办法
- docker 是推荐单个容器里面只有一个进程的,但是容器技术本质上是没这个限制,然后 如果在 docker 容器里面 fork 很多进程,会在系统中出现很多僵尸进程,最终导致 docker daemon 出现问题。
Coding 关于 docker 以及 容器未来的发展方向
- 持续对 K8s,Swarm,docker,runc 等技术保持关注,并有可能的话在 coding 内部试点解决一些问题
- 尽早脱离应用和硬件计算资源的绑定,做到任何一个服务器宕机都不影响整套系统运行
- 实现更轻量化,定制化的 coding-local 环境,以保持本地开发的高效
- 完善 CI 系统,做到完全以 docker image 作为交付件。 PS:目前交付件还是源码,源码在生产环境上进行构建,以及打包 docker image。