HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

Docker 在 Coding WebIDE 项目中的运用

  • 2015-06-18
  • 本文字数:5406 字

    阅读完需:约 18 分钟

随着云计算技术的日新月异,云端的代码仓库、分工协作、演示运行已经被人们广为接受。云端开发的出现也正是顺应了这一趋势。Docker 作为一个轻量级的隔离环境,无疑是云端开发解决资源和效率问题的秘药良方。

记得 4 月份的杭州 Docker Meetup 有一参会者提问,“作为一个云主机的租户,向主机商购买的计算资源,其获得的配额不是真实值而只是上限,觉得不值。”这个问题似乎揭露了商家的生意经,但是本人却有不同的看法。正是因为共享技术的发展,才让云计算资源变得廉洁而被广为接受,Docker 最大的价值也在这里。

从技术特性上看,VM 和 Docker 有些重合点。但是虚拟机是基于 Hypervisor 技术的,而 Docker 是基于容器技术的。Hypervisor 要比 Container 更底层,不是同一层面的竞争关系,真实的场景多是先 Hypervisor 再 Container,通俗的说法就是在 VM 里跑 Container。

Hypervisor 技术让多个操作系统共享一个 CPU 硬件,这些操作系统独立运行,并不知道彼此的存在,仿佛独占了所有的硬件资源。

Container 技术让多个用户空间共享一个操作系统,这些用户空间彼此隔绝,仿佛独占整个操作系统。

我们都知道,文件是对 I/O 设备的抽象表示,虚拟存储器是对主存和磁盘 I/O 设备的抽象表示,进程则是对处理器、主存和 I/O 设备的抽象表示。相比之下,虚拟化将操作系统从硬件中抽象出来,容器技术将应用从操作系统抽象出来。

一个正在执行的进程,由于虚拟内存技术,就其视角来看,仿佛拥有了整个操作系统的计算资源。但是 Container 的抽象和进程抽象不是在一个层面的。简单说,一台物理设备可以借助于 Hypervisor 技术,运行多个 VM;而个操作系统可以借助 Container 技术,运行多个 Container,Container 里又可以有多个进程。

上面简单的介绍了一些 Docker 技术的背景,言归正传。

为什么选用 Docker 而不是更成熟的 VM

实现 WebIDE 首先解决的就是环境隔离,多个用户之间不会相互干扰。物理机是相互隔离的,但是为每一个用户分配一台真实的物理机,显然是不合现实的。

VM 可以提供和物理机一样的隔离效果,由于 VM 共享硬件,所以更省资源。一个可行的方案是借助 IaaS 平台商提供的 OpenAPI 来操作 VM。这样对物理主机和宿主操作系统的维护工作可以完全委托给 IaaS 平台商。

相比 Docker Container,VM 有一个很大的技术优势是支持休眠。操作系统在系统级实现了休眠,这样用户的工作状态,内存中的数据可以完整的持久化。作为一个常年不关机的开发者,个人觉得这个功能非常实用。可惜 Docker 只提供了睡眠(类似于进程级别的挂起),而做不到休眠。随着 CRIU 技术的发展,相信 Docker 很快会支持的。

另外 VM 在不同宿主机之间的迁移问题,经过多年社区的积累越来越成熟。如果选择向 IaaS 平台商购买 VM 服务,这部分工作也不用关心。Docker Container 数据的迁移,面临着自制。目前 Docker 官方提供迁移 Container(非 image)的命令,只能迁移文件,无法保留状态(比如外部 mount 的目录)。

考虑到架构的微服务化,如文件服务、Git 服务、Terminal 服务、Runtime 服务。有些服务是单例的,另一些则会随着用户会话状态而动态地创建和销毁。当应用实例很多的时候,虚拟化技术的 Overhead 是需要考虑的因素。为了某个服务而启动整个操作系统有些负担不起。除了过度的内存消耗,启动耗时也存在差异,Container 只是用户空间的一个或者一组进程,所以启动耗时基本是毫秒级别,而 VM 至少是秒级,有的甚至是分钟级(休眠还原的时候)。

做比较的时候总是各有优劣,但最终打动我们的除了 Docker 的轻量,还有其生机勃勃。我们相信备受社区关注的技术,许多顾虑的问题终究会有解决方案的。

基于 Container 的 Web Terminal

一个完整的 IDE 需要具备很多功能,比如文件管理、版本管理、编辑器、编译器、执行环境等等。初次上线的最小功能集合里,我们认为 Web IDE 区别于 Web Editor 的一个功能亮点就是 Web Terminal。

Web Terminal 和 SSH 的工作原理类似,通过架设在 TCP 之上的应用层协议实现对主机的远程控制。相信大多数开发者都有 SSH 的使用经验,理解其工作原理的仅占少数。开始研究之初,我们也和大多数人一样搞不清楚 terminal、tty、pty、shell、bash 之间的区别,所以先来理理概念。

什么是 Terminal?

从用户的角度来看,Terminal 是键盘和显示器的组合,也称为 TTY(电传打字机的缩写)。键盘输入字符,显示器显示字符。从进程的角度来看,终端是字符设备,可以通过 read、write、ioctl 等系统调用来读写和控制该设备。

TTY 早已进入了博物馆,桌面系统上字符界面基本被 GUI 界面替代。取而代之是一个称之为 Terminal Emulator(终端模拟器)的窗口程序,该程序显示的字符界面就是曾经物理显示器里的完整内容。

Terminal 作为真实的物理设备已经不复存在了,但是为了和面向终端的程序(比如 Bash)进行通信,于是就了发明了 pty(Pseudoterminal,伪终端)。pty 是一对 master-slave 设备,master 设备表现得像一个文件,slave 设备表现得像一个终端设备,当 Terminal Emulator 作为一个非面向终端的程序不直接与 pty slave 通讯,而是通过文件读写流与 pty master 通讯,pty master 再将字符输入经过线路规程的转换传送给 slave,slave 进一步传递给 bash。

Bash 是一个命令行的解释器,通常也是进程会话的主进程,其职责是解释执行终端设备(或者伪终端的 slave 设备)传递过来的字符串和控制字符,执行命令。

Web Terminal 的工作原理

理解了上面背景知识之后,再看 SSH 的原理图。

SSH 是一个典型的 server-client 模式架构,用户通过终端将字符流传递给 SSH client。SSH client 和 SSH server 之间通过 TCP/IP 协议进行通讯。远端的 server 创建一对 pty,并且 fork+exec 一个 bash 进程,server 进程通过 pty 对与 bash 进行交互。

仿照 SSH 的工作原理,我们在 HTTP 协议之上设计了 Web Terminal,见下图:

真实实现中,Socket.io 是应用层的通讯协议。Terminal Emulator 是一个纯 JS 的实现,Node.js 后端使用 pty.js 模块来创建 pty 对。

当解决了 Web Terminal 的整体架构以后,嵌入 Docker Container 已是水到渠成。

僵尸进程问题

我们知道 Docker 由于缺少 init 0 而导致僵尸进程无法回收的问题迄今存在。Terminal 作为控制终端,会在使用过程中执行若干命令,这些命令对应进程如果与其父进程脱离父子关系,那僵尸进程问题就来了。

Docker 官方推荐的一个 Container 只跑一个进程。如果 Container 与进程同生共死,僵尸进程的问题基本不会遇到。但是 Web Terminal 所在 Container 里启动了 bash,而 bash 可以随意执行命令启动进程,僵尸进程问题很难避免。好在社区提供了更好的解决方案: phusion/baseimage 。在 Dockerfile 里将的 FROM ubuntu改为FROM phusion/baseimage,再按照文档说明做些调整基本就好了。

Container 作为构建和管理工具

通常,我们都是把 App 部署到 Docker 里去。大致步骤就是编写 Dockerfile,再构建成 image,然后借助 private registry 在分布式的集群中分发。由于开发环境、测试环境和生产环境存在差异,往往构建交付物涉及到大量参数和环境变量的设定,过程非常繁琐,一般都会脚本化。所以 IDE 项目基本都是 Dockerfile 旁边放置了一个 Gemfile 和 Rakefile。通过 Ruby Rake 来驱动整个构建过程。

作为脚本语言与 Shell 相比,Ruby 的好处是:

  • 隔绝了 Darwin,Linux 平台之间某些命令的细微差异;
  • 对于 Shell 擅长的部分,可以通过’`'符号方便的嵌入调用;
  • 具备完备正则等字符串处理功能;
  • 方便调用 Docker api 的;
  • 可以集成 Capistrano 等分布式管理工具;

但 Ruby 不像 Shell 那样信手拈来,需要进行适当的配置,比如,RVM 安装指定版本,修改 gem source 之类的。

从前配置这些基础环境,都是记录成 Markdown 文档,一堆 apt-get、sed 指令。但是引入 Docker 以后,有更好的选择。

我们的方式如下:

编写一个配置构建环境的 Dockerfile,构建成 image。

复制代码
docker build --rm -t="ide-docker-registry.coding.local/ide-builder:0.0.5" .

push 到 registry 里。

复制代码
docker push ide-docker-registry.coding.local/ide-builder

在构建服务器创建构建所需的 builder,通过 mount 外部目录的方式,构建环境和外部环境交互文件。

复制代码
docker run --name coding_ide_builder -d -t -v $CODING_IDE_HOME:/data/coding-ide-home --net=host --restart=always ide-docker-registry.coding.local/ide-builder

进入构建环境执行命令。

复制代码
docker exec -i -t coding_ide_builder bash

或者直接构建。

复制代码
docker exec -i -t coding_ide_builder rake
Container 环境的资源限制问题

资源限制主要针对 CPU、内存、磁盘和网络带宽等共享资源的限制。一方面,我们提倡共享,事实上不是所有的用户都需要长时间的占满所需的资源配额,不需要的时候可以释放出来分享给其他用户,因为共享才会更便宜。另一方面,也需要对可共享资源设定一个最大的限制配额,以防止某些用户过度占用而影响其他用户的使用体验。

CPU 限制

Docker 提供了两个参数来控制 CPU 的分配策略,--cpuset--cpu-shares

--cpuset="0" [...] 将 Container 限定于某几个 CPU 核心上。针对这一特性,我们制定的策略是将重要的 Container 服务分配在独立的核心上,以保证服务的质量。

--cpu-shares可以调节 Container 获得的时间片。我们通过这个配置来调节 Web Terminal 所创建进程对 CPU 的占用率。

内存限制

Web Terminal 里用户的自由度是很大的,对内存限制可以减少恶意破坏。Docker 配置内存限制相对简单。另外,我们禁用了 swap 分区,以减少对磁盘的压力。

磁盘限制

由于用户可以完全自由的访问磁盘,我们最希望 Container 磁盘镜像文件具备 thin provisioning 特性,不需要预分配所有空间也可以限定其大小。

对于 Container 的磁盘限制分为两部分,对最上层可写 layer 的限制和对被 mount 的可写目录的限制。

限制可写 layer

Docker Daemon 提供了四种 storage-driver:aufsdevicemapperbtrfsoverlay。如果 Linux 发行版本支持 aufs,那它就是默认的 storage-driver,反之则是 devicemapper。aufs 最早被 Docker 支持,而且支持共享二级制文件和动态库文件所占用的内存,btrfs 和 overlay 不支持此特性,但是比 aufs 速度更快。devicemapper 的特点是支持 thin provisioning 和 copy on write。

限制 layer 的大小,devicemapper 是目前唯一的选择。启动 devicemapper 后,Docker 会为所有的 Container 创建一个共享存储池,其实质上是一个大文件,另外也会限定每个 Container 的大小。这两个数字的制定需要慎重,因为考虑到数据迁移,修改很不容易。

限制被 mount 的可写目录

Docker run 的时候 mount 进 Container 的可写目录是不受 devicemapper 的限制,所以需要额外处理。WebIDE 场景中 workspace 目录是被多个 Container 实例中共享读写的,作为用户工作目录,需要设定一个最大的空间限制。

谈到 Linux 磁盘空间限制,最先想到 quota,它常用于 ftp 服务中限定用户最大可用空间。但 quota 有一个技术限制,仅仅适用于整个文件系统而无法针对单个目录。所以 quota 方案在共享目录的场景不可行。

Linux 支持将一个磁盘镜像文件 mount 成目录,磁盘镜像文件可以限定大小。当镜像文件撑满的时候,目录就不可写了。这是我们目前找到最靠谱的方案。

限制网络带宽

Docker 没有直接提供限制网络带宽的命令行参数,但借助 Docker 的底层技术 Cgroup 可以实现。创建一个 Network classifier group,对 cgroup 进行带宽限制的设定,将 Container 都指定到该组里去。Traffic Controller(tc) 和 Netfilter(iptables) 都支持针对 cgroup 指定规则。

关于 Dockerize 的程度与思考

基于 Docker 更容易实现架构的微服务化。借助于 Docker 的 link 特性和 Fig 工具,Container 可以像乐高积木一样把所有的组件都组合起来。Nginx、Jetty、MySQL、Redis 等一系列服务都可以封装到独立的 Container 中去。

全面 Dockerize 的最大好处是整个体系都是一致的,所有的组件都是 Container。WebIDE 在架构初期,考虑全面 Dockerize 的方案,比如把 MySQL 分成两个 Container,一个存放安装文件,另一个存放数据文件。应用服务器自不必说也在 Container 里。但是当考虑 Nginx 是否也要放进 Container 里,大家想法有些分歧,Container 的价值在 Nginx 上是否明显值得探讨。也正因为存在不同的声音,我们放弃了全面 Dockerize。个人的觉得已有的经验和脚本不应该放弃,我们应该节省出更多的精力来做更重要和紧迫的事情。

作者简介

杜万 Coding.net 全栈工程师,目前负责 Coding WebIDE 项目的架构和研发。从事了近 10 年以 Java 语言为主的软件开发工作,热衷于整合框架和开发工具,关注交互设计,喜欢写技术博客,Linux 拥趸。近期开始学习和关注 Elixir 函数语言。

参考阅读

  1. Hypervisor - Wikipedia
  2. Operating-system-level virtualization - Wikipedia
  3. User space - Wikipedia
  4. Basics – Docker, Containers, Hypervisors, CoreOS
  5. devicemapper - a storage backend based on Device Mapper
  6. Resizing Docker Containers with the Device Mapper plugin
  7. Network classifier cgroup

感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015-06-18 23:226089

评论

发布
暂无评论
发现更多内容

Github星标88.8k,阿里新产的Spring Cloud进阶小册!面面俱到

Java你猿哥

Java 架构 微服务 微服务架构 Spring Cloud

Go 语言 map 如何顺序读取?

AlwaysBeta

Go 面试 map

不止缓存!Redis这16种妙用你可能没见识过……

Java你猿哥

redis 缓存 分布式 消息队列 全局唯一ID

线程是如何通讯的?

Java你猿哥

Java 线程 多线程 ssm 通讯

京东首席系统架构师教你如何搭建高可用高并发系统架构

做梦都在改BUG

Java 高可用 系统架构 高并发

面试官:SpringBoot可以同时处理多少请求?

做梦都在改BUG

Java spring Spring Boot 框架

腾讯T8架构师基于SpringBoot2.x搭建分布式架构

做梦都在改BUG

Java spring Spring Boot 框架

公司来了一个腾讯做优化的大佬,三下五除二让我程序快了200%

做梦都在改BUG

Java 性能优化 JVM 性能调优

美团T9大牛总结的神仙微服务架构设计模式PDF

做梦都在改BUG

Java 架构 微服务

解决缓存与数据库数据不一致的问题,这篇文章告诉你如何做!

做梦都在改BUG

Java 数据库 缓存 一致性

Java 修改项目名称及其相关信息

Andy

SpringBoot 整合 MyBatis 组合 Redis 作为数据源缓存

Java你猿哥

Java redis Spring Boot mybatis ssm

首页推荐!阿里大佬带你一周刷完Java面试题1700页,offer拿到手软

做梦都在改BUG

Java java面试 Java八股文 Java面试题 Java面试八股文

分布式事务的21种武器 - 6

俞凡

架构 云原生

未来边缘计算:趋于分布式智能

华为云开发者联盟

云计算 后端 华为云 华为云开发者联盟 企业号 5 月 PK 榜

腾讯T4大牛整理的SpringBoot文档,覆盖你认知中的所有操作

程序知音

Java 架构 微服务 springboot Java进阶

PoseiSwap  参赛,参与斯坦福、Nautilus等联合主办的 Hackathon 活动

鳄鱼视界

RoCE多网卡时,报文可以过去,但是回不来

华为云开发者联盟

后端 开发 华为云 华为云开发者联盟 企业号 5 月 PK 榜

火爆Github的1000道Java面试题:无死角打击所有Java面试问题,按这个学,找工作完全没问题!

架构师之道

Java 编程

如何通过Python将JSON格式文件导入redis

华为云开发者联盟

Python redis 华为云 华为云开发者联盟 企业号 5 月 PK 榜

不愧是阿里巴巴内网的“高并发系统设计”学习笔记,全程不讲一句废话!

采菊东篱下

Java 高并发

mac端摄影师青睐软件:ON1 Photo RAW 2023.5 中文激活版

真大的脸盆

Mac Mac 软件 图像编辑 编辑图像 照片编辑

一张图感受真实的 TCP 状态转移

九零后程序员

TCP 网络 Linux Kenel ebpf

一个字牛!腾讯大牛把《数据结构与算法》讲透了,带源码笔记

程序知音

Java 数据结构 算法 后端 数据结构与算法

Go 语言 map 是并发安全的吗?

AlwaysBeta

Go 面试 map

这个线上BUG,让你彻底搞懂了MySQL的字符集,别问我咋知道的

Java你猿哥

Java MySQL ssm 字符串 字符集

线程的生命周期和常用方法

Java你猿哥

源码 jdk 线程 多线程 Monitor

GaussDB(DWS)条件表达式函数返回错误结果集排查

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 5 月 PK 榜

Nautilus Chain上线主网,为DeFi和流支付的未来构建基础

BlockChain先知

WritingGPT: 基于ChatGPT和AutoGPT打造个人写作团队

俞凡

人工智能

无惧面试!2023最新最全Java面试手册全网首次开放下载

程序员小毕

程序员 多线程 高并发 架构师 java面试

Docker在Coding WebIDE项目中的运用_语言 & 开发_杜万_InfoQ精选文章