Kissingwolf's Blog

Docker容器术语及概念总结

[TOC]

基本概念

镜像和容器

  • 镜像是指文件系统快照或tar包
  • 容器是指镜像的运行态

容器与虚拟机

  • 虚拟机持有整个操作系统和应用程序的快照
  • 虚拟机运行着自己的内核
  • 虚拟机可以运行Linux之外的其它操作系统
  • 容器只持有应用程序,但是容器中可以运行多个应用程序设置是真个Linux发行版
  • 容器与宿主机共享内核
  • 容器目前主要运行Linux,Windows的容器还在开放中,容器中的发行版可以选择,容器中的发行版可以和宿主机不同

持续集成/持续交付

  • 在应用程序新代码代码提交或出发更新条件时,系统自动构建新镜像并进行部署

宿主机管理

  • 部署一台物理机或虚拟机用以运行Docker容器环境

编排/编配(Orchestration)

  • 编配是个控制器进程,用于决定在那里运行容器,以及如何管理容器间的交互

调度

  • 用于决定那个容器在那种资源约束下运行在那个宿主机上

发现

  • 容器通过公开服务给集群,以及查找其他服务并与之通讯的过程

无状态/有状态服务

  • 无状态服务是指该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的。当该服务对一个请求作出响应之后,不会再有任何相关操作。实现无状态服务的高可用,只需要同时运行该服务的多个实例,并保证这些实例的负载均衡即可。
  • 有状态服务是指该服务的实例可以将一部分数据随时进行备份,并且在创建一个新的有状态服务时,可以通过备份恢复这些数据,以达到数据持久化的目的。同时客户端发送的后续请求依赖于之前相关请求的处理结果。由于单独一项操作可能涉及若干相关请求,有状态服务相对难于管理,只是通过多个实例和负载均衡无法实现高可用。
  • 无状态服务就好像是农场中的牲口,你不需要提别关注它们其中的某一个,像放牧一样管理他们。有状态服务就好像是宠物医院里的宠物猫,你需要记住它们的名字,细心照顾每一个,并且不能把它们混养。

容器在企业中业务应用

企业在实际生产中面对的问题

随着企业业务的增长和 微服务化 的演进,需要迫切解决的问题:

  • 环境隔离 :在IDG机房租用的机架物理机时代,由于每个业务依赖的系统环境不同,服务器之间很难通用,服务器负载也各不相同;各个业务由于安全性考量又都需要独立管理服务器资源并处理服务器故障,就对业务开发和变更造成较大负担。

  • 精细资源隔离 :如果说 环境隔离 还可以使用IaaS虚拟机来完成,精细的资源划分和高效的资源利用就需要用容器而非虚拟机了。服务隔离要求按照服务接口对进程物理隔离,防止由于某个接口而影响整体服务,同时也需要根据不同的接口特性设置不同的进程数、CPU计算资源和内存分配容量,实现更加精细且高利用率的资源管理。

  • 资源动态调整 :企业在部署和调整业务的时候需要服务随之自动适应计算、存储、流量等负载的变化。和虚拟机相比,容器技术更加快捷,在负载高峰期可以更加迅速的增加资源,并保证业务的服务质量;在负载的低峰期可以自动收缩以释放资源给其他服务,以提高集群整体的资源利用率。

容器技术介绍

使用Docker的多种方式

  • 使用镜像为基础的部署方式
  • 安全的在同一台机器上运行多种不同版本的应用
  • 使用一个工具链方便的迁移系统环境到面向服务的架构
  • 发挥云端或裸机的水平扩展和弹性扩展能力
  • 确保开发环境、测试环境和生产环境的一致性
  • 简化公司统一环境一致性及部署成本

构建容器环境的流程

  1. 构建并保存镜像快照
  2. 将镜像上传到仓库中
  3. 下载镜像到某台宿主机
  4. 以容器方式运行镜像
  5. 将容器连接到其他服务上
  6. 路由流量到容器中
  7. 将容器日志发送到指定位置
  8. 监控容器运行状态

典型的Docker栈组件

  • 构建系统
  • 镜像仓库系统
  • 配置管理系统
  • 部署系统
  • 编配系统
  • 日志系统
  • 监控系统

构建系统

构建系统主要解决的是如何生成镜像文件,目前我们是使用Dockerfile来完成镜像构建生成的。构建系统需要生成、保存、管理这些Dockerfile编配文件,同时需要管理Dockerfile编配文件的版本。

理想状态下的Docker构建系统使用类似Jenkins的持续集成系统,在代码提交时自动通过Dockerfile构建镜像。容器镜像构建成功后,构建系统将发送镜像提交给镜像仓库

镜像仓库

镜像仓库保存Docker的容器镜像。Docker官方的镜像仓库连接速度慢、安全性不强、稳定性堪忧。企业基本都建立自己的镜像仓库

配置管理系统

配置管理系统定义容器组成集群的方式,定义宿主机资源分配和加密信息保存方式,定义宿主机网络连接方式和容器虚拟网络构架方式。

部署系统

部署系统负载将容器推送或拉取到特定的宿主机并使其运行起来。

  • 推送 — 部署系统编配系统将镜像发送到相关宿主机
  • 拉取 — 事先或安全有宿主机从仓库下载镜像

编配系统

编配系统通过预先编写的编配规则文件,设置容器的运行位置(宿主机),容器可持有资源容量,容器可访问网络地址及端口,容器挂接存储卷方式及位置,容器公开和发现服务的方式。

日志系统

日志系统负载汇总宿主机及容器的运行日志,可以通过分析日志得到宿主机和容器及容器中的服务运行过程记录。

监控系统

监控系统通过snmp、Agent和日志系统提供的信息,通过可视化得方法将宿主机和容器的运行状态、资源使用状态、健康状态呈现出来。

Docker的企业部署优势

  • Docker可以快速生成和销毁容器,提供了零停机时间部署
  • Docker为不同的服务版本提供依赖隔离
  • Docker支持即时回滚

安全

实用先行,安全在后

威胁模型

目前虚拟化是隔离恶意代码最成熟的技术,而容器是隔离延伸的形式,可以作为进一步的层次化保护,进一步减少攻击面。

容器从根本上是构建在命名空间cgroup以及权能(capability)之上的。

Linux容器的核心是一系列的命名空间。一个进程命名空间隐藏了所有命名空间之外的进程,给用户分配了一组新的进程ID,包括新的init(pid=1)。一个网络命名空间隐藏了系统的网络接口,并以新的集合取代原有接口。如果一个项目没有可以引用的名字,就不能与其进行交互,同时生成了隔离。

内核更新

最直接、基本和有效的方法是及时有效的更新内核和系统补丁。目前除了Oracle和RedHat外没有行之有效的在线更新内核方案,这就意味着更新内核需要重启容器的宿主机,也就会重启宿主机上的所有容器。

生产环境下为了保证性能和可用性,一般配置了容器集群作为服务的运行平台,客户不会想要同时重启集群中的所有容器,这样会造成服务整体下线。一般在这种情况下,容器管理系统应该配置教程重启机制。

容器更新

容器可按照基础系统分为胖容器瘦容器

胖容器包含了一个健壮和完整的操作系统,要保存它们的更新非常重要。

Docker容器的自定义性很容易导致开发人员把内容不明的随机容器放置到生产环境中。容器必须从头可重现的构造,如果发现组件中存在安全问题且无法再运行时更新的话,就必须重新构造。

瘦容器一般构造的是微服务环境,微服务环境只包含静态链接的二进制文件,如Java、Python、Ruby、Go的生产环境。这样的构造过程就只简单的利用更新后的依赖关系对应用程序进行重新构造。升级的问题就转换为构造时的依赖管理问题。

镜像验证

Docker1.3引入了Docker镜像验证,其目的是追逐镜像构造者和构造完整性,用户具备一组可信的秘钥,包括受信供应商以及用户所在组织的签名,未经签名的镜像则不予运行。

安全的运行Docker守护进程

Docker守护进程默认只能通过本地Unix套接字访问,保持这个默认配置,不要使用-H参数将Docker守护进程监听帮到TCP端口上实现远程控制。

设备

容器可以通过–device参数直接访问宿主机的设备,尽量以只读方式共享它们,只提供需要的设备和最新的权限是最佳的策略。

SSH服务

不要在容器内运行SSH,要系统与在宿主机上管理容器

简化的容器更易于管理,SSH不是必须的,且它还会增加复杂度。

构造镜像

构造镜像的方法将决定容器部署的速度、容器日志获取的难度、可配置性和安全程度。

容器镜像 VS 虚拟机镜像

虚拟机镜像提供了完整的文件系统虚拟化,镜像中的文件系统可以和宿主机文件系统完成不同。通常虚拟机镜像以数据卷的形式实现,以大文件的形式存储在宿主机中。虚拟机系统运行时,用户可以对其进行格式化、分区或修改文件系统。这种方法提供了巨大的隔离性和灵活性,但是性能、效率和成本表现不佳。

容器镜像使用写时复制(copy-on-write CoW)技术来提高镜像的安全性和空间的使用效率。在创建和运行多个同一基线数据镜像时,写时复制技术可以节省大量时间和空间。每当操作尝试对文件系统进行更改时,实际上是发生在独立的叠加层的,基础镜像保持原封不动。

Docker对写时复制的使用

Docker使用写时复制将容器运行于一个叠加层上,而非直接运行于镜像上。原始镜像是以只读方式使用的,容器所有操作都这是对叠加层的修改。

Docker镜像是不可改变的”,你能做的其实是在它之上构造新的镜像。

Docker叠加层可以跨宿主机共享,每个叠加层都包含了对基础镜像的引用,而基础镜像又是其他镜像的叠加层。

每个叠加层都拥有一个唯一的”ID”,以及一个可选的名称和版本号。

Docker在镜像仓库中索引镜像,并拉取该镜像的所有叠加层的引用,然后确定哪些层已经存在本地,并下载哪些缺失的叠加层。

镜像构造的基本原理

构造一个镜像可以通过两种方法完成:

  • 从一个基础镜像启动一个容器,在容器中运行一系列的命令,如安装软件包、编辑配置文件和下载数据等,直到容器处于期望状态,软后对其进行保存。

    1
    2
    3
    4
    # docker run -ti centos /bin/bash
    root@45678abcd:/# touch test_foo_file
    # docker commit 45678abcd newimage
    # docker run -ti newimage /bin/bash

    这种构造镜像的方法交互且直观,但很难实现重现自动化

  • 另一种方法是使用基于Dockerfile文件的方法。在Dockerfile文件中包含一系列的指令,Docker在容器中运行这些指令以产生镜像。这些指令分为两组:

    • 修改镜像的文件系统
      • ADD:将URL指定的远程或本地文件写入镜像文件系统中
      • RUN:在镜像上执行命令
    • 修改镜像的元数据
      • CMD: 设置容器进程运行时的默认命令及其参数

    使用docker build命令构造基于Dockerfile文件的镜像,以Dockerfile文件中FROM指令指定的基本镜像来启动一个临时容器,然后在这个容器中运行每条指令,Docker为每条指令建立一个中间镜像。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    创建一个保存Dockerfile文件的目录
    # mkdir myimage
    # cd myimage
    创建一个Dockerfile文件
    # cat >>Dockerfile <<ENDF
    FROM centos
    MAINTAINER kissingwolf@gmail.com
    RUN touch test_foo_file
    ENDF
    接下里可以使用docker build命令构造指定镜像
    # docker build -t newimage .

分层的文件系统和空间接管

镜像是由一堆文件系统叠加层组成,每一层都是源自前一层的增加、修改和删除。

  • 在层里添加文件时,新文件将被创建。
  • 在层里删除文件时,这个文件会被标记为已删除,但是这个文件还包含在上一层中。
  • 在层里修改文件时,根据Docker运行时使用的存储驱动不同,有可能这个文件在新层中被重新创建,也有课程这个文件的部分磁盘扇区在新层中被新磁盘扇区替换。不论哪种方法,旧文件依旧在上一层中保持不变,新层包含新的修改。

分层也带来一个问题,镜像无法缩小。随着多次重构镜像,镜像容量会随着层的增加不断增大。对于追求小型化的微服务而言,这也是不小的麻烦。

解决空间增长问题的关键是从小做起,标准化一个特定的镜像版本,尽可能的将其用于所有容器。

  • BusyBox基础镜像,大约2.5MB,支持运行静态编译二进制文件,且性能良好。
  • Alpine基础镜像,在BusyBox的基础上构建了apk包管理器,并基于更轻更快的Musl libc。
  • 主流发行版Ubuntu和CentOS,镜像一般在几百兆,提供全功能有技术支持的镜像。

保持镜像小巧

选定一个较小的镜像后,接下来就是在运行Dockerfile之后保持镜像的小巧。

每运行Dockerfile文件中的一个命令,就会生成一个新的镜像层。层一旦生成,其大小就确定下来了,即使后一条Dockerfile指令删除了文件,也不会是否任何空间。

可以像这样在Dockerfile中执行RUN命令,将所有命令放在一个步骤中:

1
2
3
RUN curl http://classroom.example.com/foo.tgz -o /tmp/foo.tgz \
&& tar xzf /tmp/foo.tgz -C /usr/local/bin \
&& rm -f /tmp/foo.tgz

让镜像可重用

两种配置容器中运行进程的方法:

  • 通过环境变量将配置传递给容器内部
  • 将配置文件或目录挂接到容器中

使用变量或配置文件使镜像重用的好处是,用户可以使用相同的镜像,根据不同的变量或配置灵活的启动容器,完成不同的事物处理。变量和配置可以来自于启动脚本的硬编码或来自于文件,也可以来自于分布式配置服务或是调度器。

使用-e参数传递环境变量的例子:

1
2
3
4
5
6
# docker run --rm busybox /bin/bash -c 'echo "my variable is $MY_VAR"'
my variable is
未设置环境变量时,调用为空
# docker run -e "MY_VAR=var_foo" --rm busybox /bin/bash -c 'echo "my variable is $MY_VAR"'
my variable is var_foo
通过 -e 参数传递了环境变量

使用挂载文件或目录的方式进行配置容器,挂载发生在容器进程启动之前。

使用-v参数挂接宿主机上文件配置容器运行进程的例子:

1
2
3
4
5
6
7
首先在宿主机上创建配置文件
# cat >>env.conf<<ENDF
Name=kevin
Mail=kissingwolf@gmail.com
ENDF
通过-v参数挂接配置文件到容器,通过容器内部程序调用执行
# docker run -v ./evn.conf:/evn.conf --rm busybox /bin/bash -c 'source /evn.conf ; echo "My Name is $Name , My Email is $Mail"'

镜像在Docker变化时对自身进行重新配置

docker-gen工具在Docker提供的容器信息基础上,使用提供的模板来生成配置文件。

信任与安全

在使用Docker镜像时,最重要的问题就是镜像是否可信。这个信任来自两个层面:

  • 镜像本身是否可信,镜像是否由受信任的开发者编写并提交。
  • 镜像的下载渠道是否可信,下载到的镜像是否是我们想要下载的。

目前Docker尚未提高端到端的信任链,要保护自己避免运行包含恶意软件和其他风险的镜像,最好的方法是自主构建镜像。要完整的确认镜像的安全性,用户需要检查所有镜像层,包括基础操作系统镜像层。

保证镜像不可修改

虽然容器文件系统是可写的,但最好将其视为只读,既不要在容器里保持数据,也不要试图去修改镜像内的文件,仅在容器启动阶段去生成配置文件,所有需要保持的数据要么放在网络存储要么以“”的方式挂在宿主机上。

将容器文件系统视为只读的原因主要是容器内的文件系统比宿主机的文件系统慢很多,而且容器在销毁后数据记忆丢失。

容器中一个通用的模型是将日志写入容器进程的标准输出,而非写入文件系统。用户依赖Docker自己的日志收集器来提取哪些日志,而不再需要对容器的文件系统进行写入。

如果容器中的服务默认无法将日志输出到标准输出,简单的实现方法是将日志文件链接到标准输出。

1
2
3
4
5
例如Nginx的日志配置
access_log /var/log/nginx/access_log main;
我们可以在Dockerfile文件中创建链接文件
RUN ln -sf 、dev/stdout /var/log/nginx/access_log
这样容器nginx运行时日志就输出到标准输出了

存储Docker镜像

存储Docker镜像有三种方式:

  • 公共镜像注册仓库服务
  • 私有镜像注册仓库服务
  • 本地保存/载入模式

使用公共镜像注册仓库服务存储Docker镜像

对于普遍的开放环境或是公共用途,使用Docker Hub(hub.docker.com)保存镜像比较方便,Docker Hub就行是Docker的GitHub。使用Docker Hub很方便,注册、登录、推送或拉取镜像。

1
2
3
4
将本地只有镜像推送到Docker Hub
# docker push -t kissingwolf/busybox
将Docker Hub上的用户镜像拉取到本地
# docker pull kissingwolf/busybox

使用公共仓库时,一定要加强使用者安全意识的培养。虽然公共仓库中的镜像也可以设置为私有状态,但是如果镜像中存储了源代码、安全密钥或配置细节(例如用户名和密码),将会带来很严重的安全性问题。

公共仓库自动化构造

用户可以使用Docker Hub自动构建功能完成镜像的升级或重新构造。将Docker Hub仓库指向一个包含Dockerfile的GitHub仓库或仓库下的路径。设置自动化构建后,每当修改配置源文件(Dockerfile)时,Docker Hub都会自动化的构造新镜像。

自动化构造的优点:

  • 减少用户的工作量
  • 安全补丁的自动化更新
  • 以事件驱动的方式更新镜像

私有仓库

私有仓库可以让企业安全的将镜像保存在防火墙或VPN之后,以确保代码和镜像仓库的安全。

建立私有仓库需要考虑 网络带宽登录凭证SSL安全性监控 以及 磁盘存储等问题。

私有仓库有以下优点:

  • 速度–将仓库放在本地网络或自有网络中可以加速镜像的推送和拉取,还可以解决由于GFW导致的访问拒绝问题。
  • 安全性–将仓库放在可控的范围内,并分离内外网,可以做到物理阻隔危险。

私有仓库的扩展

在运行内部私有仓库的时候,建议使用网络存储方式作为仓库镜像存储目录,并根据镜像请求的实际情况做负载均衡以达成冗余。

除了Docker提供的官方Registry服务外,还有其他公司也提供了第三方的Registry扩展服务,例如VMware提供了harbor。

Harbor是由VMware中国研发团队为企业用户设计的Registry Server开源项目,包括了权限管理(RBAC)、图形管理界面、LDAP/AD集成、审计、自我注册、HA等企业必需的功能,同时针对中国用户的特点,原生支持中文,并计划实现镜像复制(roadmap)等功能。

本地保存/载入

本地的保持和载入镜像问题是非常常见的需求,并且在没有私有仓库的情况下是绕过GFW的一种简单的方法。

保持/载入的命令方法如下:

1
2
3
# docker build fooOS
# docker save fooOS >./fooOS.tar
# docker load <./fooOS.tar

build 命令用来根据 Dockerfile 创建镜像。save 用来保持镜像文件,保持的格式为 tar 打包格式。load 用来载入 tar 打包的镜像文件。

需要注意的是除了 save/load 命令之外,Docker还提供了 export/import 命令,其主要特点如下:

  • save/load 操作 镜像 不同,export/import 操作的是 容器

  • export/import 操作因为会丢失历史和元数据,而 save/loaded 的镜像没有丢失历史和层,所以 export/import 产生的tar包文件体积较 save/load 小。

  • export/import 无法回滚到之前的层,而 save/loaded 持久化整个镜像,可以做到层回滚(可以执行docker tag 来回滚之前的层)。

1
2
3
# docker build fooOS
# docker export fooOS >./fooOS.tar
# docker import fooOS:laster <./fooOS.tar

可以通过docker history image_id 命令参看镜像的层结构。

压缩Docker镜像文件体积

由于用于构建镜像的Linux版本、环境、依赖库和操作不同,Docker镜像的体积可能会越来越大。使用CentOS、RHEL、Ubuntu等发行版作为基础镜像,然后使用RPM、YUM或APT-GET等更新和安装软件包后会遗留一些缓冲文件和依赖项,通过删除和合并镜像层可以减小镜像的文件大小。

CentOS和RHEL作为基础镜像的Docker镜像可以使用goldmann的docker-squash工具压缩镜像空间。

Ubuntu作为基础镜像的Docker镜像可以使用jwilder的docker-squash工具压缩镜像空间。ki

自动化集成和持续部署(CI/CD)

Docker构造和推送的方便简单,结合自动化集成和持续部署是目前最强大的DevOps服务部署方式。

Docker本质上是一个应用程序交付框架,Docker的座右铭是:构造、交付与运行。

开发人员期望的是迅速且频繁的交付代码,而企业交付的是产品(打包且经过测试的代码合集),Docker可以将整个产品打包到一个容器中,这不仅仅可以迅速且频繁的打包交付代码,也可以交付已经过测试的产品。

利用Dockerfile结合Gitlab(Github的开源实现)很容易的就可以实现Docker镜像自身的自动化集成和持续部署(CI/CD)。

生产环境下的团队使用Docker标准

不要让所有人构造并推送容器,如果所有人都随心所欲的选择自己喜欢的基础镜像,并使用自己熟悉的应用服务运行自己编写的代码,最终的结果和无政府主义狂徒构造的社会一样,将是一场结局惨淡的噩梦。

在一个构造系统中构造所有镜像

一致性是扩展环境和传播知识的关键。

应该仔细考虑有关构造、添加、使用和运行Docker容器的标准。规定好基础服务、代码库、依赖库和基础程序编写规范,由统一的提交、审核和发布规则为你的企业产品发布保驾护航。

安全也是构造统一标准的一部分,什么可以放入容器,什么不能放入容器,都要体现在统一标准中。相信你也不希望在容器中保存着你的明文密码或者秘钥吧。

强制使用标准做法

你和你的团队应该制定统一的标准,例如Web服务是什么、Java使用什么版本等等,细化到如何组织目录结构和环境参数规范。这样可以保证即使你和你的团队在相当长的时候后去调试一个被遗忘的容器或镜像,依旧可以有章可循。越早的强制推行标准会越早的体现它的优势。

使用标准基础镜像

树立一个规范,让所有镜像继承于一个标准基础镜像,例如Python应用,运维团队创建一个基于Python2.6且有安全保证的基础镜像版本“ uplooking/python_base:2016_07 ”,开放团队仅仅需要使用“FROM”调用这个基础镜像就可以了,然后在构造的时候使用“ADD”将代码添加入镜像,就像这样:

1
2
3
FROM uplooking/python_base:2016_07
ADD python_code.py /code.py
CMD ["python","./code.py"]

这样可以大大简化镜像的创建过程,将大量的细节交给拥有并维护这个基础镜像的人负责。

使用Dcoker进行集中测试

用户可以把静态环境转换为一个在测试基础设施中集成Docker镜像的动态环境。Docker可以非常迅速的启动和停止,加上制作优质镜像的简易性,可以根据需要对测试基础设施进行支持,并且很容易的就可以在多个物理机上完成基础测试环境的部署,然后在结束测试后将其关闭。这样不仅部署迅速、环境统一,而且也可以为公司省下保持静态基础设备运行的费用。

配置管理(Configuration Management,CM)

配置管理工具的目标是完成新上线服务器的配置和现有线上服务配置的更新等任务的标准化和自动化。