Docker实践(19) – 保留容器的bash历史记录

我们知道在容器内做试验完成时,容器可以设置自动清除,这就省了好多事。不过仍然有一些不方便的地方。其中一个是我们可能需要在容器中多次执行一个相同的命令,而bash历史又没有了,只能手打。

问题

你想与容器共享你的主机bash历史记录。

解决方法

为docker run命令设置一个别名来与主机共享bash历史记录。

讨论

要理解这个问题,我们来展示一个场景。
假设你在Docker容器做试验,比如做一些有趣的和可重复使用的东西。这里我们使用echo命令,但这可能是一个长且复杂的一连串程序:

  1. $ docker run -ti –rm ubuntu /bin/bash
  2. $ echo my amazing command
  3. $ exit

过了一些时间,你想重新执行之间执行过的复杂的echo命令。不过,你不记得命令了,而且终端会话也找不到这个命令,你尝试找bash历史记录,但没有任何输出:

  1. $ history | grep amazing

为了解决这个问题,我们可以在你运行docker镜像时挂载一个volume,如下:

  1. $ docker run -e HIST_FILE=/root/.bash_history
  2.  -v=$HOME/.bash_history:/root/.bash_history
  3.  -ti ubuntu /bin/bash

命令太长太难记了,我们把它设置为一个别名吧,在~/.bashrc文件中添加:

  1. $ alias dockbash=’docker run -e HIST_FILE=/root/.bash_history
  2.  -v=$HOME/.bash_history:/root/.bash_history

这个体验仍然不好,因为当你要执行docker run命令时都要记住执行dockerbash命令。要获得更加无缝的体验,你您可以将这些添加到〜/ .bashrc文件:

  1. function basher() {
  2.   if [[ $1 = ‘run’ ]]
  3.   then
  4. shift
  5. /usr/bin/docker run
  6.   -e HIST_FILE=/root/.bash_history
  7.   -v $HOME/.bash_history:/root/.bash_history "$@"
  8.    else
  9.     /usr/bin/docker "$@"
  10. fi
  11. }alias docker=basher

现在当你打开bash shel并执行任意的docker run命令时都会自动把bash历史记录的设置添加到命令中去。

Docker实践(18) – 使用BitTorrent Sync分发volumes

在团队中试用Docker时,你可能希望能够在团队成员之间共享大量数据,不过你可能没有足够的空间来搭建一台共享服务器。最简单的方法是当你需要时再从其它团队成员复制最新的文件 – 不过在一个更大的团队中这个方法就不太可行了。
解决方案是使用BitTorrent Sync工具来共享文件 – 不需要专门的资源。

问题

你想通过internet在主机之间共享volumes。

解决方法

使用BitTorrent Sync镜像来共享volume。

讨论

下图说明了这个方法是如何运作的。
虚拟化技术
最终结果是通过因特网方便地同步的卷(/data),不需要任何复杂的设置。
执行如下命令在第一台主机上设置容器:

  1. [host1]$ docker run -d -p 8888:8888 -p 55555:55555 –name btsync ctlc/btsync
  2. $ docker logs btsync
  3. Starting btsync with secret: ALSVEUABQQ5ILRS2OQJKAOKCU5SIIP6A3
  4. By using this application, you agree to our Privacy Policy and Terms.
  5. http://www.bittorrent.com/legal/privacy
  6. http://www.bittorrent.com/legal/terms-of-use
  7. total physical memory 536870912 max disk cache 2097152
  8. Using IP address 172.17.4.121
  1. [host1]$ docker run -i -t –volumes-from btsync ubuntu /bin/bash
  2. $ touch /data/shared_from_server_one
  3. $ ls /data
  4. shared_from_server_one

在第二个服务器,打开一个终端运行这些命令来同步volume:

  1. [host2]$ docker run -d –name btsync-client -p 8888:8888 -p 55555:55555 ctlc/btsync ALSVEUABQQ5ILRS2OQJKAOKCU5SIIP6A3
  1. [host2]$ docker run -i -t –volumes-from btsync-client ubuntu bash
  2. $ ls /data
  3. shared_from_server_one
  4. $ touch /data/shared_from_server_two
  5. $ ls /data
  6. shared_from_server_one  shared_from_server_two

回到第一台主机运行的容器,你应该能看到第二台主机的创建的文件了:

  1. [host1]$ ls /data
  2. shared_from_server_one  shared_from_server_two

Docker实践(17) – 容器挂载主机目录

容器最强大的功能是它在迁移时能保持系统环境一致性。
不过有时候你不想把所有的文件放进容器中。你可能想在容器之间共享一些大的文件,或者单独管理这些文件。典型的例子是你希望容器访问大型集中式数据库,但是还希望其它客户端也能与容器一起访问。
解决方法是volumes,在容器生命周期外管理文件的Docker机制。虽然这违背了容器“部署在任何地方”的理念(例如,你将无法在没有挂载数据库的系统中部署容器),不过有时候在实际环境中不得不用。

问题

你想在容器内部访问主机的文件。

解决方法

使用Docker的volume参数来设置容器访问主机文件。

讨论

下面的命令显示主机的/var/db/tables目录被挂载到容器的/var/data1上。

  1. $ docker run -v /var/db/tables:/var/data1 -it debian bash

-v参数(–volume)表明需要为容器设置一个外部的volume。随后的参数值是由两个以冒号分隔的目录组成,冒号之前的目录是主机目录,之后是容器目录,如果这两个目录不存在则会自动创建。
下图说明容器访问主机目录是如何交互的:
虚拟化技术

Docker实践(16) – 引用特定镜像构建镜像

在你构建镜像大多数时候会引用镜像名称,如“node”或”ubuntu”,这个没有任何问题。
如果你引用镜像名称,它有可能tag名称保持一样而镜像被更改了。看起来这自相矛盾,不过它真实发生了!repository名称仅仅是个引用,它有可能被更改指向一个不同的镜像。指定一个带冒号的tag名称也不会消除这种风险,因为安全更新会使用相同的tag来自动重新构建有漏洞的镜像。
大多数时候你会想让它这样做 – 镜像维护者对镜像有所改进,并修补了安全漏洞是一件好事。不过有时候可能会给你带来痛苦。这不仅仅是一个理论上的风险:这已经出现过很多次了,中断了持续交付且很难调试。在Docker早期,镜像会添加和删除软件包(包括,记忆中消失了的passwd命令),使构建镜像突然地中断。

问题

你想确保镜像的构建始终从一个指定的不会被更改的镜像。

解决方法

从一个指定的镜像ID构建。

讨论

对于你想决定确认从一个指定的镜像构建镜像时,可以在Dockerfile中指定镜像ID.
下面是示例:

  1. FROM 8eaa4ff06b53
  2. RUN echo "Built from image id:" > /etc/buildinfo
  3. RUN echo "8eaa4ff06b53" >> /etc/buildinfo
  4. RUN echo "an ubuntu 14.4.01 image" >> /etc/buildinfo
  5. CMD ["echo","/etc/buildinfo"]

像这样从一个指定的镜像ID构建镜像,这个镜像必须已经保存在了本地。Docker registry不会在Docker Hub搜索这个镜像ID的。
注意你引用的镜像不需要tag。你可以从任意层来构建镜像。

Docker实践(15) – 通过Docker Hub分享镜像

如果你与其他人共享你的镜像,使用描述性名称tag镜像会更有帮助。为了满足这个需求,Docker能够轻松地将镜像移动到其它地方,Docker Inc则创建了免费的Docker Hub服务来鼓励这种共享。

为了使用Docker Hub服务,你需要注册一个Docker Hub帐号来使用docker login登录。注册地址为http://hub.docker.com。

问题

你想公开分享一个Docker镜像。

解决方法

使用Docker Hub registry分享你的镜像。

讨论

下面有几个术语需要理解,以免造成混乱。

  • Username – Docker registry用户名
  • Registry – registries保存镜像。你可以上传镜像到registry和从它下载镜像。Registries可以是公开或私有的。
  • Registry host – 运行Docker registry的主机
  • Docker Hub – 默认公开的registry,位于https://hub.docker.com
  • Index – 与registry host一样。待废弃的术语。
  • 正如你以前看到的,你可以根据需要对镜像进行多次tag。这对复制镜像是有用的,这样你可以管理它。
    我们假设你Docker Hub的用户名为”adev”。下面的三个命令展示如何复制来自Docker Hub的debian:wheezy镜像到你自己用户下。

    1. docker pull debian:wheezy
    2. docker tag debian:wheezy adev/debian:mywheezy1
    3. docker push adev/debian:mywheezy1

    你现在有一个Debian wheezy镜像的引用,你可以自己维护它了。
    如果你有一个私有的repository需要推送,除了在tag之前需要指定registry的地方外,其它步骤一样。我们假设你repository的地址为http://mycorp.private.dockerregistry。下面的示例tag和推送镜像。

    1. docker pull debian
    2. docker tag debian:wheezy mycorp.private.dockerregistry/adev/debian:mywheezy1
    3. docker push mycorp.private.dockerregistry/adev/debian:mywheezy1

    以上的命令不会把镜像推送到公共的Docker Hub,会推送到你的私有repository,所以有这个repository的访问权限的用户能拉取这个镜像。

    Docker实践(14) – Docker tag

    上一篇文章中你通过docker commit保存了容器的状态,并且得到一个随机的镜像ID。记住和管理巨大数量的镜像ID非常困难。使用Docker的tag功能可以给镜像设置一个可读的名称,提醒你镜像创建的目的是什么。掌握这个技术使你对镜像的用途一目了然,使得管理你机器上的镜像变得非常容易。

    问题

    你想方便地引用和存储一个Docker commit。

    解决方法

    使用docker tag命名你的提交。

    讨论

    tag的基本用法很简单:

    1. $ docker tag 071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036 imagename

    创建了一个可以引用此镜像的名称,如:

    1. docker run imagename

    这个名称比随机的ID容易记多了。
    如果你想与他人共享你的镜像,还需要了解更多的tag知识。tag的相关术语可能会有点混乱。像image,name和repository有时候可互相交换使用。如下定义:

  • Image – 一个只读层
  • Name – 镜像的名称,如“todoapp”
  • Tag – 作为动词,指的是给镜像设置一个名称,作为名词,是镜像名称的修饰符
  • Repository – 已tag的镜像集合一起组成容器的文件系统
  • 可能最容易引起混乱的术语是image和repository。我们一直使用术语image来宽松地表示产生容器的层集合,但技术上,image是递归地引用其父层的单个层。repository是托管的,意味着它存储在某个地方(不管是在你的Docker daemon或者一个registry上)。此外,repository是tagged的镜像的集合来组成容器的文件系统。
    可以用Git类比帮助我们理解。当克隆一个Git repository时,你check out了当时请求点的文件状态。这类似于image。repository是文件每次提交的整体历史记录。因为check out是指向了最上层。当你克隆时也会包含其它所有层。
    我们来看下Ubuntu镜像。当你拉取Ubuntu镜像并执行docker images时,你会看到如下输出:

    1. $ docker images
    2. REPOSITORY  TAG    IMAGE ID        CREATED       VIRTUAL SIZE
    3. ubuntu    trusty   8eaa4ff06b53    4 weeks ago    192.7 MB
    4. ubuntu    14.04    8eaa4ff06b53    4 weeks ago    192.7 MB
    5. ubuntu    14.04.1  8eaa4ff06b53    4 weeks ago    192.7 MB
    6. ubuntu    latest   8eaa4ff06b53    4 weeks ago    192.7 MB

    Repository列列出托管层集合称为“ubuntu”。通常称为镜像。Tag列列出四个不同的名称(trusty, 14.04, 14.04.1, 和latest)。Image ID列列出相同的镜像ID。因为这些不同的tag名称指向同一个镜像。这说明了你可以对同一个镜像设置多个tag。

    Docker实践(13) – 保存开发环境状态

    如果你曾经开发过软件,你可能至少一次地像这样呼叫过,”奇怪了,之前明明正常的!”不过没办法快速地恢复到之前的正常状态,你只能匆忙地去修改代码以尽快完成任务而不至于延期。这就浪费了许多时间。
    版本控制软件已经能帮助你快速恢复到指定的正常版本,不过但下面的两个特殊情况会存在问题:

  • 代码无法体现你工作环境系统的状态
  • 你可能还不愿意提交代码
  • 第一个问题比第二个值得关注。虽然像Git这样的现代源代码控制工具可以轻松地创建分支,不过捕获整个开发文件系统的状态不是Git的目的。
    Docker通过它的commit功能提供了一个成本低廉和快速的方法来保存容器开发系统的状态,这就是我们下面将要探讨的。

    问题

    你想保存你开发环境的状态。

    解决方法

    使用docker commit保存状态

    讨论

    假如你对你的to-do应用做更改。ToDoCorp的CEO对浏览器显示的标题”Swarm+React – TodoMVC.”不满意,要改为“ToDo- Corp’s ToDo App”。
    你不确定如何完成这个任务,所以你可能需要启动应用,并更改文件做试验看会怎样:

    1. $ docker run -d -p 8000:8000 –name todobug1 dockerinpractice/todoapp  3c3d5d3ffd70d17e7e47e90801af7d12d6fc0b8b14a8b33131fc708423ee4372
    2. $ docker exec -i -t todobug1 /bin/bash

    docker run命令后台(-d)启动to-do应用容器,映射容器的8000端口到主机的8000端口(-p 8000:8000),命名为todobug1(–name todobug1)。
    第二个命令在已运行的容器中启动/bin/bash。-i激活交互模式,-t创建一个TTY。
    现在你已经进入容器了,所以试验前先安装编辑器。我们喜欢用vim,所以用以下命令:

    1. apt-get update
    2. apt-get install vim

    经过了一翻努力你意识到需要更改local.html,因为你更改如下:
    ToDoCorp’s ToDo App
    不过CEO想让标题使小写字符,因为她听说这看起来更现代。你想把现在的更改保存下,在另一个终端执行如下命令:

    1. $ docker commit todobug1
    2. ca76b45144f2cb31fda6a31e55f784c93df8c9d4c96bbeacd73cad9cd55d2970

    你现在已经提交了刚才的更改,所以之后你可以从镜像运行包含此更改的容器。
    下一步你更改local.html:
    todocorp’s todo app
    再次提交:

    1. $ docker commit todobug1
    2. 071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036

    现在在我们这个示例已经有两个镜像ID(ca76b45144f2cb31fda6a31e55f784c93df8c9d4c96bbeacd73cad9cd55d2970和071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036)。当CEO来评估她想要哪个时,你可以运行任意一个镜像来让你决定。
    你可以在新终端运行如下命令来启动两个镜像:

    1. $ docker run -p 8001:8000 ca76b45144f2cb31fda6a31e55f784c93df8c9d4c96bbeacd73cad9cd55d2970
    2. $ docker run -p 8002:8000 071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036

    这样你可以打开http://localhost:8001显示大写的标题页面和http://localhost:8002显示小写页面。
    你肯定想知道有没有更好的方法来引用这两个镜像,而不是需要输入这么长的ID。下一篇文章我们会给这些镜像一个名称来更好的引用它。

    Docker实践(12) – 管理容器服务启动

    当尝试Docker作为VM的替代品在容器内运行多个服务可能会比较方便,或者完成VM到容器的初始转换后,运行重要服务是有必要的。
    不管是什么原因,当想尝试管理容器内的进程时尽量避免重复造轮子。

    问题

    你想管理容器内的多个进程。

    解决方法

    使用Supervisor应用(http://supervisord.org/)来管理你的进程启动。

    讨论

    我们准备演示如何创建一个包含Tomcat和Apache web服务器的容器,并以Supervisor的管理方式启动应用。
    首先在一个新的空的目录创建一个Dockerfile,如下:
    虚拟化技术
    需要一个用来指定启动哪些应用的supervisord配置文件,如下:
    虚拟化技术
    虚拟化技术
    使用刚才创建的Dockerfile生成镜像:

    1. docker build -t supervised .

    开始运行容器:
    虚拟化技术
    如果你打开http://localhost:9000,你应该能看到Apache的默认页面。
    如果要清除容器,执行如下命令:

    1. docker rm -f supervised

    Docker实践(11) – 将系统拆分为微服务容器

    我们已经探讨了如何作为一个整体使用容器(像一个经典的服务器),并解释它可以是一个快速移动系统架构到Docker的好方法。不过在Docker世界中,通常认为最好的做法是尽可能多地分割系统,直到每个容器只运行一个服务,然后通过links连接所有容器。因为这是推荐的Docker方法,你会发现Docker Hub中的大多数容器都是这种方法,理解如何以这种方式构建镜像对于与Docker生态系统进行交互非常重要。
    一个容器一个服务的主要原因是通过单一责任原则更容易分离关注点。
    如果一个容器只做一项工作,那么将更容易地把容器放到开发,测试和生产的软件开发生命周期中,而不用担心它与其它组件的交互。这使得交付更灵活和软件项目更可扩展。不过它增加了维护开销,所以最好考虑在你的用例中是否值得这样做。

    问题

    你希望将你的应用程序分解成更易于管理的服务

    解决方法

    使用Docker将你的应用程序集分解为基于容器的服务

    讨论

    在Docker社区中,关于如何严格地遵循“一容器一服务”规则的一些争论,其中一部分源于对定义的不同意见 – 是一个单独的进程,还是根据需要把一组进程放到一起完成一项服务?它往往归结为一个声明,给予从头重新设计系统的能力,微服务是最可能的选择。但有时候,实用性与理想主义相克 – 当我们为我们的组织评估Docker时,我们发现自己处于必须走整条路线的位置,以便让Docker尽可能快速和轻松地工作。 让我们来看看在Docker中跑多个进程的一个缺点。首先我们需要展示如何创建一个带有数据库,应用和web服务器的容器。

    这些示例是为了阐明目的,所以简化了。尝试直接运行它们不一定能用。

    设置简单的PostgreSQL,NodeJS和Nginx应用:

    1. FROM ubuntu:14.04
    2. RUN apt-get update && apt-get install postgresql nodejs npm nginx
    3. WORKDIR /opt
    4. COPY . /opt/                             # {*}
    5. RUN service postgresql start &&
    6.     cat db/schema.sql | psql &&
    7.     service postgresql stop
    8. RUN cd app && npm install
    9. RUN cp conf/mysite /etc/nginx/sites-available/ &&
    10.     cd /etc/nginx/sites-enabled &&
    11.     ln -s ../sites-available/mysite

    在RUN语句中使用&&有效地确保了几个命令作为一个命令运行。这对于减小你的镜像大小会有用。每个Dockerfile命令都会在上一个层之上创建一个新层。如果你以这种方式 运行软件包更新命令(如apt-get update)和install命令,你可以确保无论何时安装软件包,它们都将来自更新的软件包缓存。

    前面的示例是一个概念上简单的Dockerfile,它在容器中安装我们需要的一切,然后设置数据库,应用程序和Web服务器。不过在你想快速重载容器时会有一个问题 – 对存储库中任何文件的任何更改都将从{*}处开始重建所有内容,因为缓存无法重复使用。如果你的一些步骤需要时间比较久(如数据库创建或npm安装),你可能需要一段时间等待容器重建。
    解决方案是拆分COPY . /opt指令为三部分,对应数据库,应用程序和Web设置。

    1. FROM ubuntu:14.04
    2. RUN apt-get update && apt-get install postgresql nodejs npm nginx
    3. WORKDIR /opt
    4. COPY db /opt/db                                     -+
    5. RUN service postgresql start &&                    |- db setup
    6.     cat db/schema.sql | psql && |
    7.     service postgresql stop                         -+
    8. COPY app /opt/app                                   -+
    9. RUN cd app && npm install                            |- app setup
    10. RUN cd app && ./minify_static.sh                    -+
    11. COPY conf /opt/conf                                 -+
    12. RUN cp conf/mysite /etc/nginx/sites-available/ &&   +
    13.     cd /etc/nginx/sites-enabled &&                  |- web setup
    14.     ln -s ../sites-available/mysite                 -+

    在上面的代码中,COPY命令分成三个单独的指令。这意味着数据库将不会在每次代码更改时重建,因为在COPY app /opt/app之前缓存可以重用。
    不过由于缓存功能相当简单,容器仍然必须在每次对schema.sql进行更改时完全重建 – 解决这个问题的唯一方法是按顺序为三个应用程序分别创建Dockerfile,如下:
    Database Dockerfile

    1. FROM ubuntu:14.04
    2. RUN apt-get update && apt-get install postgresql
    3. WORKDIR /opt
    4. COPY db /opt/db
    5. RUN service postgresql start &&
    6.     cat db/schema.sql | psql &&
    7.     service postgresql stop

    App Dockerfile

    1. FROM ubuntu:14.04
    2. RUN apt-get update && apt-get install nodejs npm
    3. WORKDIR /opt
    4. COPY app /opt/app
    5. RUN cd app && npm install
    6. RUN cd app && ./minify_static.sh

    Web server Dockerfile

    1. FROM ubuntu:14.04
    2. RUN apt-get update && apt-get install nginx
    3. WORKDIR /opt
    4. COPY conf /opt/conf
    5. RUN cp conf/mysite /etc/nginx/sites-available/ &&
    6.     cd /etc/nginx/sites-enabled &&
    7.     ln -s ../sites-available/mysite

    不管何时db,app或conf目录中的哪一个更改,仅仅有一个容器需要重建。当你有三个以上的容器或者需要大量时间的设置步骤时这种方法非常有用 – 你可以在每步中添加最少必要的文件以便获得更有用的Dockerfile缓存。在app Dockerfile中的npm install的操作中,只依赖了package.json文件,所以我们可以更改这个Dockerfile以充分利用dockerfile层缓存。

    1. FROM ubuntu:14.04
    2. RUN apt-get update && apt-get install nodejs npm
    3. WORKDIR /opt
    4. COPY app/package.json /opt/app/package.json
    5. RUN cd app && npm install
    6. COPY app /opt/app
    7. RUN cd app && ./minify_static.sh

    不过事情没有那么简单,从单个dockerfile文件分割为多个dockerfile,增加了不少重复代码。你可以通过添加另一个dockerfile作为基础镜像来部分解决这个问题。此外,启动你的镜像还有一些复杂的问题 – 除了EXPOSE步骤使适当的端口可用于链接和更改Postgres配置之外,你还需要确保在每次启动时链接容器。 幸运的是,有一个叫做docker-compose(以前的fig)的工具来帮我们完成这件事。

    Docker实践(10) – 类主机容器

    我们现在把讨论转到Docker社区最具争议性的领域之一 – 运行一个包含多个进程的类主机镜像。
    这个在Docker社区中部分人认为是一种不好的形式。容器不是虚拟机 – 它们有显著的差异 – 假装不会造成混乱和没有问题。
    不管是好还是坏,本文展示如何运行一个类主机镜像,讨论这其中的一些问题。

    运行一个类主机镜像是一个说服Docker反对者的好方法,Docker很有用。当他们更多地使用Docker,他们会更理解Docker范式,微服务方法对他们更有意义。在我们介绍Docker的公司中,我们发现这种单一的方法是将人们从开发服务器和笔记本电脑上的开发移动到更加包容和可管理的环境的好方法。

    虚拟机与Docker容器的不同之处
    这些是VM和Docker容器之间的一些区别:
    Docker是面向应用的,而VM是面向操作系统的;
    Docker容器与其他Docker容器共享操作系统,相比之下,VM各自有它们自己的操作系统;
    Docker容器设计为运行一个主要进程,而不是管理多个进程集。

    问题

    你需要为容器设置一个类主机的环境,并设置多个进程和服务。

    解决方法

    使用旨在模拟主机的镜像,并为其提供所需的应用程序。

    讨论

    在下面的示例中我们使用phusion/baseimage Docker镜像,一个设计用来运行多个进程的镜像。
    第一步是运行镜像并使用docker exec进入到其bash环境:

    1. user@docker-host$ docker run -d phusion/baseimage
    2. 3c3f8e3fb05d795edf9d791969b21f7f73e99eb1926a6e3d5ed9e1e52d0b446e
    3. user@docker-host$ docker exec -i -t 3c3f8e3fb05d795 /bin/bash
    4. root@3c3f8e3fb05d:/#

    在这段代码中,docker run命令后台启动一个镜像(第一行),默认命令启动镜像并返回了新建容器的ID(第二行)。
    然后传容器ID给docker exec命令(第三),这个命令是在已运行的容器中启动一个新的进程。-i参数允许你与新进程交互,-t参数设置一个TTY,允许你在容器内部创建一个终端(/bin/bash)(第四行)。
    如果你等一分钟然后查看进程列表,输出类似如下:
    虚拟化技术
    虚拟化技术
    你可以看到容器启动起来很像一个主机,初始化服务如cron和sshd使它看起来像一个标准的Linux主机。这对于给新的Docker工程师初始演示很有用。
    这是否构成违反“一个容器一个服务”的微服务原则是Docker社区内部的一个争论。类主机镜像支持者认为这不违反这一原则,因为容器仍然可以实现其运行所在的系统的单个离散函数。