标签 Docker 下的文章

这是 LXD 2.0 系列介绍文章的第一篇。

  1. LXD 入门
  2. 安装与配置
  3. 你的第一个 LXD 容器
  4. 资源控制
  5. 镜像管理
  6. 远程主机及容器迁移
  7. LXD 中的 Docker
  8. LXD 中的 LXD
  9. 实时迁移
  10. LXD 和 Juju
  11. LXD 和 OpenStack
  12. 调试,及给 LXD 做贡献

关于 LXD 几个常见问题

什么是 LXD ?

简单地说, LXD 就是一个提供了 REST API 的 LXC 容器管理器。

LXD 最主要的目标就是使用 Linux 容器而不是硬件虚拟化向用户提供一种接近虚拟机的使用体验。

LXD 和 Docker/Rkt 又有什么关系呢 ?

这是一个最常被问起的问题,现在就让我们直接指出其中的不同吧。

LXD 聚焦于系统容器,通常也被称为架构容器。这就是说 LXD 容器实际上如在裸机或虚拟机上运行一般运行了一个完整的 Linux 操作系统。

这些容器一般基于一个干净的发布镜像并会长时间运行。传统的配置管理工具和部署工具可以如在虚拟机、云实例和物理机器上一样与 LXD 一起使用。

相对的, Docker 关注于短期的、无状态的、最小化的容器,这些容器通常并不会升级或者重新配置,而是作为一个整体被替换掉。这就使得 Docker 及类似项目更像是一种软件发布机制,而不是一个机器管理工具。

这两种模型并不是完全互斥的。你完全可以使用 LXD 为你的用户提供一个完整的 Linux 系统,然后他们可以在 LXD 内安装 Docker 来运行他们想要的软件。

为什么要用 LXD?

我们已经持续开发并改进 LXC 好几年了。 LXC 成功的实现了它的目标,它提供了一系列很棒的用于创建和管理容器的底层工具和库。

然而这些底层工具的使用界面对用户并不是很友好。使用它们需要用户有很多的基础知识以理解它们的工作方式和目的。同时,向后兼容旧的容器和部署策略也使得 LXC 无法默认使用一些安全特性,这导致用户需要进行更多人工操作来实现本可以自动完成的工作。

我们把 LXD 作为解决这些缺陷的一个很好的机会。作为一个长时间运行的守护进程, LXD 可以绕开 LXC 的许多限制,比如动态资源限制、无法进行容器迁移和高效的在线迁移;同时,它也为创造新的默认体验提供了机会:默认开启安全特性,对用户更加友好。

LXD 的主要组件

LXD 是由几个主要组件构成的,这些组件都出现在 LXD 目录结构、命令行客户端和 API 结构体里。

容器

LXD 中的容器包括以下及部分:

  • 根文件系统(rootfs)
  • 配置选项列表,包括资源限制、环境、安全选项等等
  • 设备:包括磁盘、unix 字符/块设备、网络接口
  • 一组继承而来的容器配置文件
  • 属性(容器架构、暂时的还是持久的、容器名)
  • 运行时状态(当用 CRIU 来中断/恢复时)

快照

容器快照和容器是一回事,只不过快照是不可修改的,只能被重命名,销毁或者用来恢复系统,但是无论如何都不能被修改。

值得注意的是,因为我们允许用户保存容器的运行时状态,这就有效的为我们提供了“有状态”的快照的功能。这就是说我们可以使用快照回滚容器的状态,包括快照当时的 CPU 和内存状态。

镜像

LXD 是基于镜像实现的,所有的 LXD 容器都是来自于镜像。容器镜像通常是一些纯净的 Linux 发行版的镜像,类似于你们在虚拟机和云实例上使用的镜像。

所以可以「发布」一个容器:使用容器制作一个镜像并在本地或者远程 LXD 主机上使用。

镜像通常使用全部或部分 sha256 哈希码来区分。因为输入长长的哈希码对用户来说不方便,所以镜像可以使用几个自身的属性来区分,这就使得用户在镜像商店里方便搜索镜像。也可以使用别名来一对一地将一个用户好记的名字映射到某个镜像的哈希码上。

LXD 安装时已经配置好了三个远程镜像服务器(参见下面的远程一节):

  • “ubuntu”:提供稳定版的 Ubuntu 镜像
  • “ubuntu-daily”:提供 Ubuntu 的每日构建镜像
  • “images”: 社区维护的镜像服务器,提供一系列的其它 Linux 发布版,使用的是上游 LXC 的模板

LXD 守护进程会从镜像上次被使用开始自动缓存远程镜像一段时间(默认是 10 天),超过时限后这些镜像才会失效。

此外, LXD 还会自动更新远程镜像(除非指明不更新),所以本地的镜像会一直是最新版的。

配置

配置文件是一种在一个地方定义容器配置和容器设备,然后将其应用到一系列容器的方法。

一个容器可以被应用多个配置文件。当构建最终容器配置时(即通常的扩展配置),这些配置文件都会按照他们定义顺序被应用到容器上,当有重名的配置键或设备时,新的会覆盖掉旧的。然后本地容器设置会在这些基础上应用,覆盖所有来自配置文件的选项。

LXD 自带两种预配置的配置文件:

  • “default”配置是自动应用在所有容器之上,除非用户提供了一系列替代的配置文件。目前这个配置文件只做一件事,为容器定义 eth0 网络设备。
  • “docker”配置是一个允许你在容器里运行 Docker 容器的配置文件。它会要求 LXD 加载一些需要的内核模块以支持容器嵌套并创建一些设备。

远程

如我之前提到的, LXD 是一个基于网络的守护进程。附带的命令行客户端可以与多个远程 LXD 服务器、镜像服务器通信。

默认情况下,我们的命令行客户端会与下面几个预定义的远程服务器通信:

  • local:默认的远程服务器,使用 UNIX socket 和本地的 LXD 守护进程通信
  • ubuntu:Ubuntu 镜像服务器,提供稳定版的 Ubuntu 镜像
  • ubuntu-daily:Ubuntu 镜像服务器,提供 Ubuntu 的每日构建版
  • images:images.linuxcontainers.org 的镜像服务器

所有这些远程服务器的组合都可以在命令行客户端里使用。

你也可以添加任意数量的远程 LXD 主机,并配置它们监听网络。匿名的开放镜像服务器,或者通过认证可以管理远程容器的镜像服务器,都可以添加进来。

正是这种远程机制使得与远程镜像服务器交互及在主机间复制、移动容器成为可能。

安全性

我们设计 LXD 时的一个核心要求,就是在不修改现代 Linux 发行版的前提下,使容器尽可能的安全。

LXD 通过使用 LXC 库实现的主要安全特性有:

  • 内核名字空间。尤其是用户名字空间,它让容器和系统剩余部分完全分离。LXD 默认使用用户名字空间(和 LXC 相反),并允许用户在需要的时候以容器为单位关闭(将容器标为“特权的”)。
  • Seccomp 系统调用。用来隔离潜在危险的系统调用。
  • AppArmor。对 mount、socket、ptrace 和文件访问提供额外的限制。特别是限制跨容器通信。
  • Capabilities。阻止容器加载内核模块,修改主机系统时间,等等。
  • CGroups。限制资源使用,防止针对主机的 DoS 攻击。

为了对用户友好,LXD 构建了一个新的配置语言把大部分的这些特性都抽象封装起来,而不是如 LXC 一般直接将这些特性暴露出来。举了例子,一个用户可以告诉 LXD 把主机设备放进容器而不需要手动检查他们的主/次设备号来手动更新 CGroup 策略。

和 LXD 本身通信是基于使用 TLS 1.2 保护的链路,只允许使用有限的几个被允许的密钥算法。当和那些经过系统证书认证之外的主机通信时, LXD 会提示用户验证主机的远程指纹(SSH 方式),然后把指纹缓存起来以供以后使用。

REST 接口

LXD 的工作都是通过 REST 接口实现的。在客户端和守护进程之间并没有其他的通讯渠道。

REST 接口可以通过本地的 unix socket 访问,这只需要经过用户组认证,或者经过 HTTP 套接字使用客户端认证进行通信。

REST 接口的结构能够和上文所说的不同的组件匹配,是一种简单、直观的使用方法。

当需要一种复杂的通信机制时, LXD 将会进行 websocket 协商完成剩余的通信工作。这主要用于交互式终端会话、容器迁移和事件通知。

LXD 2.0 附带了 1.0 版的稳定 API。虽然我们在 1.0 版 API 添加了额外的特性,但是这不会在 1.0 版 API 端点里破坏向后兼容性,因为我们会声明额外的 API 扩展使得客户端可以找到新的接口。

容器规模化

虽然 LXD 提供了一个很好的命令行客户端,但是这个客户端并不能管理多个主机上大量的容器。在这种使用情况下,我们可以使用 OpenStack 的 nova-lxd 插件,它可以使 OpenStack 像使用虚拟机一样使用 LXD 容器。

这就允许在大量的主机上部署大量的 LXD 容器,然后使用 OpenStack 的 API 来管理网络、存储以及负载均衡。

额外信息

LXD 的主站在: https://linuxcontainers.org/lxd

LXD 的 GitHub 仓库: https://github.com/lxc/lxd

LXD 的邮件列表: https://lists.linuxcontainers.org

LXD 的 IRC 频道: #lxcontainers on irc.freenode.net

如果你不想或者不能在你的机器上安装 LXD ,你可以在 web 上试试在线版的 LXD


作者简介:我是 Stéphane Graber。我是 LXC 和 LXD 项目的领导者,目前在加拿大魁北克蒙特利尔的家所在的Canonical 有限公司担任 LXD 的技术主管。


via: https://www.stgraber.org/2016/03/11/lxd-2-0-introduction-to-lxd-112/

作者:Stéphane Graber 译者:ezio 校对:PurlingNayuki

本文由 LCTT 原创翻译,Linux中国 荣誉推出

当今世界会时不时地出现一波波科技浪潮,将以前的技术拍死在海滩上。针对 serverless 应用的概念我们已经谈了很多,它是指将你的应用程序按功能来部署,这些功能在被用到时才会启动。你不用费心去管理服务器和程序规模,因为它们会在需要的时候在一个集群中启动并运行。

但是 serverless 并不意味着没有 Docker 什么事儿,事实上 Docker 就是 serverless 的。你可以使用 Docker 来容器化这些功能,然后在 Swarm 中按需求来运行它们。serverless 是一项构建分布式应用的技术,而 Docker 是它们完美的构建平台。

从 servers 到 serverless

那如何才能写一个 serverless 应用呢?来看一下我们的例子,5个服务组成的投票系统

投票系统由下面5个服务组成:

  • 两个 web 前端
  • 一个后台处理投票的进程
  • 一个计票的消息队列
  • 一个数据库

后台处理投票的进程很容易转换成 serverless 构架,我们可以使用以下代码来实现:

import dockerrun
client = dockerrun.from_env()
client.run("bfirsh/serverless-record-vote-task", [voter_id, vote], detach=True)

这个投票处理进程和消息队列可以用运行在 Swarm 上的 Docker 容器来代替,并实现按需自动部署。

我们也可以用容器替换 web 前端,使用一个轻量级 HTTP 服务器来触发容器响应一个 HTTP 请求。Docker 容器代替长期运行的 HTTP 服务器来挑起响应请求的重担,这些容器可以自动扩容来支撑更大访问量。

新的架构就像这样:

红色框内是持续运行的服务,绿色框内是按需启动的容器。这个架构里需要你来管理的长期运行服务更少,并且可以自动扩容(最大容量由你的 Swarm 决定)。

我们可以做点什么?

你可以在你的应用中使用3种技术:

  1. 在 Docker 容器中按需运行代码。
  2. 使用 Swarm 来部署集群。
  3. 通过使用 Docker API 套接字在容器中运行容器。

结合这3种技术,你可以有很多方法搭建你的应用架构。用这种方法来部署后台环境真是非常有效,而在另一些场景,也可以这么玩,比如说:

  • 由于存在延时,使用容器实现面向用户的 HTTP 请求可能不是很合适,但你可以写一个负载均衡器,使用 Swarm 来对自己的 web 前端进行自动扩容。
  • 实现一个 MongoDB 容器,可以自检 Swarm 并且启动正确的分片和副本(LCTT 译注:分片技术为大规模并行检索提供支持,副本技术则是为数据提供冗余)。

下一步怎么做

我们提供了这些前卫的工具和概念来构建应用,并没有深入发掘它们的功能。我们的架构里还是存在长期运行的服务,将来我们需要使用 Swarm 来把所有服务都用按需扩容的方式实现。

希望本文能在你搭建架构时给你一些启发,但我们还是需要你的帮助。我们提供了所有的基本工具,但它们还不是很完善,我们需要更多更好的工具、库、应用案例、文档以及其他资料。

我们在这里发布了工具、库和文档。如果想了解更多,请贡献给我们一些你知道的资源,以便我们能够完善这篇文章。

玩得愉快。

更多关于 Docker 的资料


via: https://blog.docker.com/2016/06/building-serverless-apps-with-docker/

作者:Ben Firshman 译者:bazz2 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Docker之前发布了一个新的容器控制中心,称为Docker数据中心(DDC),其设计目的是用于大型和小型企业创建、管理和分发容器的一个集成管理控制台。

DDC是由包括Docker Universal Control Plane(也是同时发布的)和Docker Trusted Registry等不同的商业组件组成。它也包括了开源组件比如Docker Engine。这个产品让企业在一个中心管理界面中就可以管理整个Docker化程序的生命周期。

负责产品管理的SVP Scott Johnston告诉TechCrunch:“客户催使了这个新工具的产生。公司不仅喜欢Docker给他们带来的敏捷性,它们也希望在创建和分发容器的过程中可以进行对安全和管理进行控制。”

Johnston说:“公司称这个为容器即服务(Caas),主要是因为当客户来询问这种类型的管理面板时,他们是这样描述的。”

像许多开源项目那样,Docker首先获得了许多开发者的追随,但是随着它的流行,那些开发者们所在的公司也希望积极推进它的使用。

这就是DDC设计的目的。它给开发者创建容器化应用的敏捷性,也让运维变得井井有条。

实际上这意味着开发者可以创建一系列容器化的组件,将其部署后就可以访问一个完全认证的镜像库。这可以让开发人员用碎片组成应用而不必每次重新发明轮子。这可以加速应用的开发和部署(理论上提升了容器提供的灵活性)。

这方面吸引了Beta客户ADP。这个服务特别喜欢将这个提供给开发人员的中心镜像仓库。

ADP的CTO Keith Fulton在声明中称:“作为我们将关键业务微服务化倡议的一部分,ADP正在研究能够让开发人员可以利用IT审核过的中央库和安全的核心服务进行快速迭代的方案。”

Docker在2010年由dotcloud的Solomon Hykes发布。他在2013年将公司的重心移到Docker上,并在8月出售了dotCloud,2014年完全转移到Docker上。

根据CrunchBase的消息,公司几年来在5轮融资后势如破竹般获得了1亿8000万美元融资(自从成为Docker后获得了1亿6千8百万美元)。吸引投资者关注的是Docker提供了一种称为容器的现在分发应用的方式,可以构建、管理何分发分布式应用。

容器化可以让开发者创建由多个分布在不同服务器上的碎片组成的分布式应用,而不是一个运行在一个单独服务器上的独立应用。

DDC服务每月每节点150美金起。


via: http://techcrunch.com/2016/02/23/new-docker-data-center-admin-suite-should-bring-order-to-containerization/

作者:Ron Miller 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这篇文章包含 Docker 的基本概念,以及如何通过创建一个定制的 Dockerfile 来 Docker 化 Dockerize 一个应用。

Docker 是一个过去两年来从某个 idea 中孕育而生的有趣技术,公司组织们用它在世界上每个角落来部署应用。在今天的文章中,我将讲述如何通过“ Docker 化 Dockerize ”一个现有的应用,来开始我们的 Docker 之旅。这里提到的应用指的就是这个博客!

什么是 Docker?

当我们开始学习 Docker 基本概念时,让我们先去搞清楚什么是 Docker 以及它为什么这么流行。Docker 是一个操作系统容器管理工具,它通过将应用打包在操作系统容器中,来方便我们管理和部署应用。

容器 vs. 虚拟机

容器和虚拟机并不完全相似,它是另外一种提供操作系统虚拟化的方式。它和标准的虚拟机还是有所不同。

标准的虚拟机一般会包括一个完整的操作系统、操作系统软件包、最后还有一至两个应用。这都得益于为虚拟机提供硬件虚拟化的管理程序。这样一来,一个单一的服务器就可以将许多独立的操作系统作为虚拟客户机运行了。

容器和虚拟机很相似,它们都支持在单一的服务器上运行多个操作环境,只是,在容器中,这些环境并不是一个个完整的操作系统。容器一般只包含必要的操作系统软件包和一些应用。它们通常不会包含一个完整的操作系统或者硬件的虚拟化。这也意味着容器比传统的虚拟机开销更少。

容器和虚拟机常被误认为是两种对立的技术。虚拟机采用一个物理服务器来提供全功能的操作环境,该环境会和其余虚拟机一起共享这些物理资源。容器一般用来隔离一个单一主机上运行的应用进程,以保证隔离后的进程之间不能相互影响。事实上,容器和 BSD Jails 以及 chroot 进程的相似度,超过了和完整虚拟机的相似度。

Docker 在容器之上提供了什么

Docker 本身不是一个容器运行环境,事实上,只是一个与具体实现无关的容器技术,Docker 正在努力支持 Solaris ZonesBSD Jails。Docker 提供了一种管理、打包和部署容器的方式。虽然一定程度上,虚拟机多多少少拥有这些类似的功能,但虚拟机并没有完整拥有绝大多数的容器功能,即使拥有,这些功能用起来都并没有 Docker 来的方便或那么完整。

现在,我们应该知道 Docker 是什么了,然后,我们将从安装 Docker,并部署一个公开的预构建好的容器开始,学习 Docker 是如何工作的。

从安装开始

默认情况下,Docker 并不会自动被安装在您的计算机中,所以,第一步就是安装 Docker 软件包;我们的教学机器系统是 Ubuntu 14.0.4,所以,我们将使用 Apt 软件包管理器,来执行安装操作。

# apt-get install docker.io
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  aufs-tools cgroup-lite git git-man liberror-perl
Suggested packages:
  btrfs-tools debootstrap lxc rinse git-daemon-run git-daemon-sysvinit git-doc
  git-el git-email git-gui gitk gitweb git-arch git-bzr git-cvs git-mediawiki
  git-svn
The following NEW packages will be installed:
  aufs-tools cgroup-lite docker.io git git-man liberror-perl
0 upgraded, 6 newly installed, 0 to remove and 0 not upgraded.
Need to get 7,553 kB of archives.
After this operation, 46.6 MB of additional disk space will be used.
Do you want to continue? [Y/n] y

为了检查当前是否有容器运行,我们可以执行docker命令,加上ps选项

# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

docker命令中的ps功能类似于 Linux 的ps命令。它将显示可找到的 Docker 容器及其状态。由于我们并没有启动任何 Docker 容器,所以命令没有显示任何正在运行的容器。

部署一个预构建好的 nginx Docker 容器

我比较喜欢的 Docker 特性之一就是 Docker 部署预先构建好的容器的方式,就像yumapt-get部署包一样。为了更好地解释,我们来部署一个运行着 nginx web 服务器的预构建容器。我们可以继续使用docker命令,这次选择run选项。

# docker run -d nginx
Unable to find image 'nginx' locally
Pulling repository nginx
5c82215b03d1: Download complete 
e2a4fb18da48: Download complete 
58016a5acc80: Download complete 
657abfa43d82: Download complete 
dcb2fe003d16: Download complete 
c79a417d7c6f: Download complete 
abb90243122c: Download complete 
d6137c9e2964: Download complete 
85e566ddc7ef: Download complete 
69f100eb42b5: Download complete 
cd720b803060: Download complete 
7cc81e9a118a: Download complete 

docker命令的run选项,用来通知 Docker 去寻找一个指定的 Docker 镜像,然后启动运行着该镜像的容器。默认情况下,Docker 容器运行在前台,这意味着当你运行docker run命令的时候,你的 shell 会被绑定到容器的控制台以及运行在容器中的进程。为了能在后台运行该 Docker 容器,我们使用了-d (detach)标志。

再次运行docker ps命令,可以看到 nginx 容器正在运行。

# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
f6d31ab01fc9        nginx:latest        nginx -g 'daemon off   4 seconds ago       Up 3 seconds        443/tcp, 80/tcp     desperate_lalande 

从上面的输出信息中,我们可以看到正在运行的名为desperate_lalande的容器,它是由nginx:latest image(LCTT 译注: nginx 最新版本的镜像)构建而来得。

Docker 镜像

镜像是 Docker 的核心特征之一,类似于虚拟机镜像。和虚拟机镜像一样,Docker 镜像是一个被保存并打包的容器。当然,Docker 不只是创建镜像,它还可以通过 Docker 仓库发布这些镜像,Docker 仓库和软件包仓库的概念差不多,它让 Docker 能够模仿yum部署软件包的方式来部署镜像。为了更好地理解这是怎么工作的,我们来回顾docker run执行后的输出。

# docker run -d nginx
Unable to find image 'nginx' locally

我们可以看到第一条信息是,Docker 不能在本地找到名叫 nginx 的镜像。这是因为当我们执行docker run命令时,告诉 Docker 运行一个基于 nginx 镜像的容器。既然 Docker 要启动一个基于特定镜像的容器,那么 Docker 首先需要找到那个指定镜像。在检查远程仓库之前,Docker 首先检查本地是否存在指定名称的本地镜像。

因为系统是崭新的,不存在 nginx 镜像,Docker 将选择从 Docker 仓库下载之。

Pulling repository nginx
5c82215b03d1: Download complete 
e2a4fb18da48: Download complete 
58016a5acc80: Download complete 
657abfa43d82: Download complete 
dcb2fe003d16: Download complete 
c79a417d7c6f: Download complete 
abb90243122c: Download complete 
d6137c9e2964: Download complete 
85e566ddc7ef: Download complete 
69f100eb42b5: Download complete 
cd720b803060: Download complete 
7cc81e9a118a: Download complete 

这就是第二部分输出信息显示给我们的内容。默认情况下,Docker 会使用 Docker Hub 仓库,该仓库由 Docker 公司维护。

和 Github 一样,在 Docker Hub 创建公共仓库是免费的,私人仓库就需要缴纳费用了。当然,部署你自己的 Docker 仓库也是可以的,事实上只需要简单地运行docker run registry命令就行了。但在这篇文章中,我们的重点将不是讲解如何部署一个定制的注册服务。

关闭并移除容器

在我们继续构建定制容器之前,我们先清理一下 Docker 环境,我们将关闭先前的容器,并移除它。

我们利用docker命令和run选项运行一个容器,所以,为了停止同一个容器,我们简单地在执行docker命令时,使用kill选项,并指定容器名。

# docker kill desperate_lalande
desperate_lalande

当我们再次执行docker ps,就不再有容器运行了

# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

但是,此时,我们这是停止了容器;虽然它不再运行,但仍然存在。默认情况下,docker ps只会显示正在运行的容器,如果我们附加-a (all) 标识,它会显示所有运行和未运行的容器。

# docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                           PORTS               NAMES
f6d31ab01fc9        5c82215b03d1        nginx -g 'daemon off   4 weeks ago         Exited (-1) About a minute ago                       desperate_lalande  

为了能完整地移除容器,我们在用docker命令时,附加rm选项。

# docker rm desperate_lalande
desperate_lalande

虽然容器被移除了;但是我们仍拥有可用的nginx镜像(LCTT 译注:镜像缓存)。如果我们重新运行docker run -d nginx,Docker 就无需再次拉取 nginx 镜像即可启动容器。这是因为我们本地系统中已经保存了一个副本。

为了列出系统中所有的本地镜像,我们运行docker命令,附加images选项。

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
nginx               latest              9fab4090484a        5 days ago          132.8 MB

构建我们自己的镜像

截至目前,我们已经使用了一些基础的 Docker 命令来启动、停止和移除一个预构建好的普通镜像。为了“Docker 化(Dockerize)”这篇博客,我们需要构建我们自己的镜像,也就是创建一个 Dockerfile

在大多数虚拟机环境中,如果你想创建一个机器镜像,首先,你需要建立一个新的虚拟机、安装操作系统、安装应用,最后将其转换为一个模板或者镜像。但在 Docker 中,所有这些步骤都可以通过 Dockerfile 实现全自动。Dockerfile 是向 Docker 提供构建指令去构建定制镜像的方式。在这一章节,我们将编写能用来部署这个博客的定制 Dockerfile。

理解应用

我们开始构建 Dockerfile 之前,第一步要搞明白,我们需要哪些东西来部署这个博客。

这个博客本质上是由一个静态站点生成器生成的静态 HTML 页面,这个生成器是我编写的,名为 hamerkop。这个生成器很简单,它所做的就是生成该博客站点。所有的代码和源文件都被我放在了一个公共的 Github 仓库。为了部署这篇博客,我们要先从 Github 仓库把这些内容拉取下来,然后安装 Python 和一些 Python 模块,最后执行hamerkop应用。我们还需要安装 nginx,来运行生成后的内容。

截止目前,这些还是一个简单的 Dockerfile,但它却给我们展示了相当多的 Dockerfile 语法)。我们需要克隆 Github 仓库,然后使用你最喜欢的编辑器编写 Dockerfile,我选择vi

# git clone https://github.com/madflojo/blog.git
Cloning into 'blog'...
remote: Counting objects: 622, done.
remote: Total 622 (delta 0), reused 0 (delta 0), pack-reused 622
Receiving objects: 100% (622/622), 14.80 MiB | 1.06 MiB/s, done.
Resolving deltas: 100% (242/242), done.
Checking connectivity... done.
# cd blog/
# vi Dockerfile

FROM - 继承一个 Docker 镜像

第一条 Dockerfile 指令是FROM指令。这将指定一个现存的镜像作为我们的基础镜像。这也从根本上给我们提供了继承其他 Docker 镜像的途径。在本例中,我们还是从刚刚我们使用的 nginx 开始,如果我们想从头开始,我们可以通过指定ubuntu:latest来使用 Ubuntu Docker 镜像。

## Dockerfile that generates an instance of http://bencane.com

FROM nginx:latest
MAINTAINER Benjamin Cane <[email protected]>

除了FROM指令,我还使用了MAINTAINER,它用来显示 Dockerfile 的作者。

Docker 支持使用#作为注释,我将经常使用该语法,来解释 Dockerfile 的部分内容。

运行一次测试构建

因为我们继承了 nginx Docker镜像,我们现在的 Dockerfile 也就包括了用来构建 nginx 镜像的 Dockerfile 中所有指令。这意味着,此时我们可以从该 Dockerfile 中构建出一个 Docker 镜像,然后以该镜像运行一个容器。虽然,最终的镜像和 nginx 镜像本质上是一样的,但是我们这次是通过构建 Dockerfile 的形式,然后我们将讲解 Docker 构建镜像的过程。

想要从 Dockerfile 构建镜像,我们只需要在运行 docker 命令的时候,加上 build 选项。

# docker build -t blog /root/blog 
Sending build context to Docker daemon  23.6 MB
Sending build context to Docker daemon 
Step 0 : FROM nginx:latest
 ---> 9fab4090484a
Step 1 : MAINTAINER Benjamin Cane <[email protected]>
 ---> Running in c97f36450343
 ---> 60a44f78d194
Removing intermediate container c97f36450343
Successfully built 60a44f78d194

上面的例子,我们使用了-t (tag)标识给镜像添加“blog”的标签。实质上我们就是在给镜像命名,如果我们不指定标签,就只能通过 Docker 分配的 Image ID 来访问镜像了。本例中,从 Docker 构建成功的信息可以看出,Image ID值为 60a44f78d194

除了-t标识外,我还指定了目录/root/blog。该目录被称作“构建目录”,它将包含 Dockerfile,以及其它需要构建该容器的文件。

现在我们构建成功了,下面我们开始定制该镜像。

使用 RUN 来执行 apt-get

用来生成 HTML 页面的静态站点生成器是用 Python 语言编写的,所以,在 Dockerfile 中需要做的第一件定制任务是安装 Python。我们将使用 Apt 软件包管理器来安装 Python 软件包,这意味着在 Dockerfile 中我们要指定运行apt-get updateapt-get install python-dev;为了完成这一点,我们可以使用RUN指令。

## Dockerfile that generates an instance of http://bencane.com

FROM nginx:latest
MAINTAINER Benjamin Cane <[email protected]>

## Install python and pip
RUN apt-get update
RUN apt-get install -y python-dev python-pip

如上所示,我们只是简单地告知 Docker 构建镜像的时候,要去执行指定的apt-get命令。比较有趣的是,这些命令只会在该容器的上下文中执行。这意味着,即使在容器中安装了python-devpython-pip,但主机本身并没有安装这些。说的更简单点,pip命令将只在容器中执行,出了容器,pip命令不存在。

还有一点比较重要的是,Docker 构建过程中不接受用户输入。这说明任何被RUN指令执行的命令必须在没有用户输入的时候完成。由于很多应用在安装的过程中需要用户的输入信息,所以这增加了一点难度。不过我们例子中,RUN命令执行的命令都不需要用户输入。

安装 Python 模块

Python 安装完毕后,我们现在需要安装 Python 模块。如果在 Docker 外做这些事,我们通常使用pip命令,然后参考我的博客 Git 仓库中名叫requirements.txt的文件。在之前的步骤中,我们已经使用git命令成功地将 Github 仓库“克隆”到了/root/blog目录;这个目录碰巧也是我们创建Dockerfile的目录。这很重要,因为这意味着 Docker 在构建过程中可以访问这个 Git 仓库中的内容。

当我们执行构建后,Docker 将构建的上下文环境设置为指定的“构建目录”。这意味着目录中的所有文件都可以在构建过程中被使用,目录之外的文件(构建环境之外)是不能访问的。

为了能安装所需的 Python 模块,我们需要将requirements.txt从构建目录拷贝到容器中。我们可以在Dockerfile中使用COPY指令完成这一需求。

## Dockerfile that generates an instance of http://bencane.com

FROM nginx:latest
MAINTAINER Benjamin Cane <[email protected]>

## Install python and pip
RUN apt-get update
RUN apt-get install -y python-dev python-pip

## Create a directory for required files
RUN mkdir -p /build/

## Add requirements file and run pip
COPY requirements.txt /build/
RUN pip install -r /build/requirements.txt

Dockerfile中,我们增加了3条指令。第一条指令使用RUN在容器中创建了/build/目录。该目录用来拷贝生成静态 HTML 页面所需的一切应用文件。第二条指令是COPY指令,它将requirements.txt从“构建目录”(/root/blog)拷贝到容器中的/build/目录。第三条使用RUN指令来执行pip命令;安装requirements.txt文件中指定的所有模块。

当构建定制镜像时,COPY是条重要的指令。如果在 Dockerfile 中不指定拷贝文件,Docker 镜像将不会包含requirements.txt 这个文件。在 Docker 容器中,所有东西都是隔离的,除非在 Dockerfile 中指定执行,否则容器中不会包括所需的依赖。

重新运行构建

现在,我们让 Docker 执行了一些定制任务,现在我们尝试另一次 blog 镜像的构建。

# docker build -t blog /root/blog
Sending build context to Docker daemon 19.52 MB
Sending build context to Docker daemon 
Step 0 : FROM nginx:latest
 ---> 9fab4090484a
Step 1 : MAINTAINER Benjamin Cane <[email protected]>
 ---> Using cache
 ---> 8e0f1899d1eb
Step 2 : RUN apt-get update
 ---> Using cache
 ---> 78b36ef1a1a2
Step 3 : RUN apt-get install -y python-dev python-pip
 ---> Using cache
 ---> ef4f9382658a
Step 4 : RUN mkdir -p /build/
 ---> Running in bde05cf1e8fe
 ---> f4b66e09fa61
Removing intermediate container bde05cf1e8fe
Step 5 : COPY requirements.txt /build/
 ---> cef11c3fb97c
Removing intermediate container 9aa8ff43f4b0
Step 6 : RUN pip install -r /build/requirements.txt
 ---> Running in c50b15ddd8b1
Downloading/unpacking jinja2 (from -r /build/requirements.txt (line 1))
Downloading/unpacking PyYaml (from -r /build/requirements.txt (line 2))
<truncated to reduce noise>
Successfully installed jinja2 PyYaml mistune markdown MarkupSafe
Cleaning up...
 ---> abab55c20962
Removing intermediate container c50b15ddd8b1
Successfully built abab55c20962

上述输出所示,我们可以看到构建成功了,我们还可以看到另外一个有趣的信息---> Using cache。这条信息告诉我们,Docker 在构建该镜像时使用了它的构建缓存。

Docker 构建缓存

当 Docker 构建镜像时,它不仅仅构建一个单独的镜像;事实上,在构建过程中,它会构建许多镜像。从上面的输出信息可以看出,在每一“步”执行后,Docker 都在创建新的镜像。

 Step 5 : COPY requirements.txt /build/
  ---> cef11c3fb97c

上面片段的最后一行可以看出,Docker 在告诉我们它在创建一个新镜像,因为它打印了Image ID : cef11c3fb97c。这种方式有用之处在于,Docker能在随后构建这个 blog 镜像时将这些镜像作为缓存使用。这很有用处,因为这样, Docker 就能加速同一个容器中新构建任务的构建流程。从上面的例子中,我们可以看出,Docker 没有重新安装python-devpython-pip包,Docker 则使用了缓存镜像。但是由于 Docker 并没有找到执行mkdir命令的构建缓存,随后的步骤就被一一执行了。

Docker 构建缓存一定程度上是福音,但有时也是噩梦。这是因为决定使用缓存或者重新运行指令的因素很少。比如,如果requirements.txt文件发生了修改,Docker 会在构建时检测到该变化,然后 Docker 会重新执行该执行那个点往后的所有指令。这得益于 Docker 能查看requirements.txt的文件内容。但是,apt-get命令的执行就是另一回事了。如果提供 Python 软件包的 Apt 仓库包含了一个更新的 python-pip 包;Docker 不会检测到这个变化,转而去使用构建缓存。这会导致之前旧版本的包将被安装。虽然对python-pip来说,这不是主要的问题,但对使用了存在某个致命攻击缺陷的软件包缓存来说,这是个大问题。

出于这个原因,抛弃 Docker 缓存,定期地重新构建镜像是有好处的。这时,当我们执行 Docker 构建时,我简单地指定--no-cache=True即可。

部署博客的剩余部分

Python 软件包和模块安装后,接下来我们将拷贝需要用到的应用文件,然后运行hamerkop应用。我们只需要使用更多的COPYRUN指令就可完成。

## Dockerfile that generates an instance of http://bencane.com

FROM nginx:latest
MAINTAINER Benjamin Cane <[email protected]>

## Install python and pip
RUN apt-get update
RUN apt-get install -y python-dev python-pip

## Create a directory for required files
RUN mkdir -p /build/

## Add requirements file and run pip
COPY requirements.txt /build/
RUN pip install -r /build/requirements.txt

## Add blog code nd required files
COPY static /build/static
COPY templates /build/templates
COPY hamerkop /build/
COPY config.yml /build/
COPY articles /build/articles

## Run Generator
RUN /build/hamerkop -c /build/config.yml

现在我们已经写出了剩余的构建指令,我们再次运行另一次构建,并确保镜像构建成功。

# docker build -t blog /root/blog/
Sending build context to Docker daemon 19.52 MB
Sending build context to Docker daemon 
Step 0 : FROM nginx:latest
 ---> 9fab4090484a
Step 1 : MAINTAINER Benjamin Cane <[email protected]>
 ---> Using cache
 ---> 8e0f1899d1eb
Step 2 : RUN apt-get update
 ---> Using cache
 ---> 78b36ef1a1a2
Step 3 : RUN apt-get install -y python-dev python-pip
 ---> Using cache
 ---> ef4f9382658a
Step 4 : RUN mkdir -p /build/
 ---> Using cache
 ---> f4b66e09fa61
Step 5 : COPY requirements.txt /build/
 ---> Using cache
 ---> cef11c3fb97c
Step 6 : RUN pip install -r /build/requirements.txt
 ---> Using cache
 ---> abab55c20962
Step 7 : COPY static /build/static
 ---> 15cb91531038
Removing intermediate container d478b42b7906
Step 8 : COPY templates /build/templates
 ---> ecded5d1a52e
Removing intermediate container ac2390607e9f
Step 9 : COPY hamerkop /build/
 ---> 59efd1ca1771
Removing intermediate container b5fbf7e817b7
Step 10 : COPY config.yml /build/
 ---> bfa3db6c05b7
Removing intermediate container 1aebef300933
Step 11 : COPY articles /build/articles
 ---> 6b61cc9dde27
Removing intermediate container be78d0eb1213
Step 12 : RUN /build/hamerkop -c /build/config.yml
 ---> Running in fbc0b5e574c5
Successfully created file /usr/share/nginx/html//2011/06/25/checking-the-number-of-lwp-threads-in-linux
Successfully created file /usr/share/nginx/html//2011/06/checking-the-number-of-lwp-threads-in-linux
<truncated to reduce noise>
Successfully created file /usr/share/nginx/html//archive.html
Successfully created file /usr/share/nginx/html//sitemap.xml
 ---> 3b25263113e1
Removing intermediate container fbc0b5e574c5
Successfully built 3b25263113e1

运行定制的容器

成功的一次构建后,我们现在就可以通过运行docker命令和run选项来运行我们定制的容器,和之前我们启动 nginx 容器一样。

# docker run -d -p 80:80 --name=blog blog
5f6c7a2217dcdc0da8af05225c4d1294e3e6bb28a41ea898a1c63fb821989ba1

我们这次又使用了-d (detach)标识来让Docker在后台运行。但是,我们也可以看到两个新标识。第一个新标识是--name,这用来给容器指定一个用户名称。之前的例子,我们没有指定名称,因为 Docker 随机帮我们生成了一个。第二个新标识是-p,这个标识允许用户从主机映射一个端口到容器中的一个端口。

之前我们使用的基础 nginx 镜像分配了80端口给 HTTP 服务。默认情况下,容器内的端口通道并没有绑定到主机系统。为了让外部系统能访问容器内部端口,我们必须使用-p标识将主机端口映射到容器内部端口。上面的命令,我们通过-p 80:80语法将主机80端口映射到容器内部的80端口。

经过上面的命令,我们的容器看起来成功启动了,我们可以通过执行docker ps核实。

# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                         NAMES
d264c7ef92bd        blog:latest         nginx -g 'daemon off   3 seconds ago       Up 3 seconds        443/tcp, 0.0.0.0:80->80/tcp   blog  

总结

截止目前,我们拥有了一个运行中的定制 Docker 容器。虽然在这篇文章中,我们只接触了一些 Dockerfile 指令用法,但是我们还是要学习所有的指令。我们可以检查 Docker's reference page 来获取所有的 Dockerfile 指令用法,那里对指令的用法说明得很详细。

另一个比较好的资源是 Dockerfile Best Practices page,它有许多构建定制 Dockerfile 的最佳练习。有些技巧非常有用,比如战略性地组织好 Dockerfile 中的命令。上面的例子中,我们将articles目录的COPY指令作为 Dockerfile 中最后的COPY指令。这是因为articles目录会经常变动。所以,将那些经常变化的指令尽可能地放在最后面的位置,来最优化那些可以被缓存的步骤。

通过这篇文章,我们涉及了如何运行一个预构建的容器,以及如何构建,然后部署定制容器。虽然关于 Docker 你还有许多需要继续学习的地方,但我想这篇文章给了你如何继续开始的好建议。当然,如果你认为还有一些需要继续补充的内容,在下面评论即可。


via: http://bencane.com/2015/12/01/getting-started-with-docker-by-dockerizing-this-blog/

作者:Benjamin Cane 译者:su-kaiyao 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

Docker 在 开放容器项目 Open Container Project,OCP 中的参与度达成圆满,最新构建的 Docker 采用了 Docker 贡献给 OCP 的组件。

新发布的 Docker 1.11 的最大新闻并不是它的功能,而是它使用了在 OCP 支持下的标准化的组件版本。

去年,Docker 贡献了它的 runC 核心给 OCP 作为构建构建容器工具的基础。同样还有 containerd,作为守护进程或者服务端用于控制 runC 的实例。Docker 1.11 现在使用的就是这个捐赠和公开的版本。

Docker 此举挑战了它的容器生态仍主要由 Docker 自身决定这个说法。它并不是为了作秀才将容器规范和运行时细节贡献给 OCP。它希望项目将来的开发越开放和广泛越好。

Docker 1.11 已经用贡献给 OCP 的 runC 和 containerd 进行了重构。runC 如果需要的话可以换成另外一个。

runC 的两位主要提交者来自 Docker,但是来自 Virtuozzo(Parallels fame)、OpenShift、Project Atomic、华为、GE Healthcare、Suse Linux 也都是提交人员里面的常客。

Docker 1.11 中一个更明显的变化是先前 Docker runtime 在 Docker 中是唯一可用的,并且评论家认为这个会限制用户的选择。runC runtime 现在是可替换的;虽然 Docker 在发布时将 runC 作为默认引擎,但是任何兼容的引擎都可以用来替换它。(Docker 同样希望它可以不用杀死并重启现在运行的容器,但是这个作为今后的改进规划。)

Docker 正在将基于 OCP 的开发流程作为内部创建其产品的更好方式。在它发布 1.11 的官方博客中称:“将 Docker 切分成独立的工具意味着更专注的维护者,最终会有更好的软件质量。”

除了修复长期以来存在的问题和确保 Docker 的 runC/containerd 跟上步伐,Docker 还在 Docker 1.11 中加入了一些改进。Docker Engine 现在支持 VLAN 和 IPv6 服务发现,并且会自动在多个相同别名容器间执行 DNS 轮询负载均衡。


via: http://www.infoworld.com/article/3055966/open-source-tools/docker-111-adopts-open-container-project-components.html

作者:Serdar Yegulalp 译者:geekpi 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

我的上一篇文章里, 我介绍了 Linux 容器背后的技术的概念。我写了我知道的一切。容器对我来说也是比较新的概念。我写这篇文章的目的就是鼓励我真正的来学习这些东西。

我打算在使用中学习。首先实践,然后上手并记录下我是怎么走过来的。我假设这里肯定有很多像 "Hello World" 这种类型的知识帮助我快速的掌握基础。然后我能够更进一步,构建一个微服务容器或者其它东西。

我想,它应该不会有多难的。

但是我错了。

可能对某些人来说这很简单,因为他们在运维工作方面付出了大量的时间。但是对我来说实际上是很困难的,可以从我在Facebook 上的状态展示出来的挫折感就可以看出了。

但是还有一个好消息:我最终搞定了。而且它工作的还不错。所以我准备分享向你分享我如何制作我的第一个微服务容器。我的痛苦可能会节省你不少时间呢。

如果你曾经发现你也处于过这种境地,不要害怕:像我这样的人都能搞定,所以你也肯定行。

让我们开始吧。

一个缩略图微服务

我设计的微服务在理论上很简单。以 JPG 或者 PNG 格式在 HTTP 终端发布一张数字照片,然后获得一个100像素宽的缩略图。

下面是它的流程:

container-diagram-0

我决定使用 NodeJS 作为我的开发语言,使用 ImageMagick 来转换缩略图。

我的服务的第一版的逻辑如下所示:

container-diagram-1

我下载了 Docker Toolbox,用它安装了 Docker 的快速启动终端(Docker Quickstart Terminal)。Docker 快速启动终端使得创建容器更简单了。终端会启动一个装好了 Docker 的 Linux 虚拟机,它允许你在一个终端里运行 Docker 命令。

虽然在我的例子里,我的操作系统是 Mac OS X。但是 Windows 下也有相同的工具。

我准备使用 Docker 快速启动终端里为我的微服务创建一个容器镜像,然后从这个镜像运行容器。

Docker 快速启动终端就运行在你使用的普通终端里,就像这样:

container-diagram-2

第一个小问题和第一个大问题

我用 NodeJS 和 ImageMagick 瞎搞了一通,然后让我的服务在本地运行起来了。

然后我创建了 Dockerfile,这是 Docker 用来构建容器的配置脚本。(我会在后面深入介绍构建过程和 Dockerfile)

这是我运行 Docker 快速启动终端的命令:

$ docker build -t thumbnailer:0.1

获得如下回应:

docker: "build" requires 1 argument.

呃。

我估摸着过了15分钟我才反应过来:我忘记了在末尾参数输入一个点.

正确的指令应该是这样的:

$ docker build -t thumbnailer:0.1 .

但是这不是我遇到的最后一个问题。

我让这个镜像构建好了,然后我在 Docker 快速启动终端输入了 run 命令来启动容器,名字叫 thumbnailer:0.1:

$ docker run -d -p 3001:3000 thumbnailer:0.1

参数 -p 3001:3000 让 NodeJS 微服务在 Docker 内运行在端口3000,而绑定在宿主主机上的3001。

到目前看起来都很好,对吧?

错了。事情要马上变糟了。

我通过运行 docker-machine 命令为这个 Docker 快速启动终端里创建的虚拟机指定了 ip 地址:

$ docker-machine ip default

这句话返回了默认虚拟机的 IP 地址,它运行在 Docker 快速启动终端里。在我这里,这个 ip 地址是 192.168.99.100。

我浏览网页 http://192.168.99.100:3001/ ,然后找到了我创建的上传图片的网页:

container-diagram-3

我选择了一个文件,然后点击上传图片的按钮。

但是它并没有工作。

终端告诉我他无法找到我的微服务需要的 /upload 目录。

现在,你要知道,我已经在此耗费了将近一天的时间-从浪费时间到研究问题。我此时感到了一些挫折感。

然后灵光一闪。某人记起来微服务不应该自己做任何数据持久化的工作!保存数据应该是另一个服务的工作。

所以容器找不到目录 /upload 的原因到底是什么?这个问题的根本就是我的微服务在基础设计上就有问题。

让我们看看另一幅图:

container-diagram-4

我为什么要把文件保存到磁盘?微服务按理来说是很快的。为什么不能让我的全部工作都在内存里完成?使用内存缓冲可以解决“找不到目录”这个问题,而且可以提高我的应用的性能。

这就是我现在所做的。下面是我的计划:

container-diagram-5

这是我用 NodeJS 写的在内存运行、生成缩略图的代码:

// Bind to the packages
var express = require('express');
var router = express.Router();
var path = require('path'); // used for file path
var im = require("imagemagick");

// Simple get that allows you test that you can access the thumbnail process
router.get('/', function (req, res, next) {
 res.status(200).send('Thumbnailer processor is up and running');
});

// This is the POST handler. It will take the uploaded file and make a thumbnail from the 
// submitted byte array. I know, it's not rocket science, but it serves a purpose
router.post('/', function (req, res, next) {
 req.pipe(req.busboy);
 req.busboy.on('file', function (fieldname, file, filename) {
   var ext = path.extname(filename)

   // Make sure that only png and jpg is allowed 
   if(ext.toLowerCase() != '.jpg' && ext.toLowerCase() != '.png'){
     res.status(406).send("Service accepts only jpg or png files");
   }

   var bytes = [];

   // put the bytes from the request into a byte array 
   file.on('data', function(data) {
     for (var i = 0; i < data.length; ++i) {
       bytes.push(data[i]);
     }
     console.log('File [' + fieldname + '] got bytes ' + bytes.length + ' bytes');
   });

   // Once the request is finished pushing the file bytes into the array, put the bytes in 
   // a buffer and process that buffer with the imagemagick resize function
   file.on('end', function() {
     var buffer = new Buffer(bytes,'binary');
     console.log('Bytes  got ' + bytes.length + ' bytes');

     //resize
     im.resize({
         srcData: buffer,
         height: 100
     }, function(err, stdout, stderr){
       if (err){
         throw err;
       }
       // get the extension without the period
       var typ = path.extname(filename).replace('.','');
       res.setHeader("content-type", "image/" + typ);
       res.status(200);
       // send the image back as a response
       res.send(new Buffer(stdout,'binary'));
     });
   });
 });
});

module.exports = router;

好了,一切回到了正轨,已经可以在我的本地机器正常工作了。我该去休息了。

但是,在我测试把这个微服务当作一个普通的 Node 应用运行在本地时...

Containers Hard

它工作的很好。现在我要做的就是让它在容器里面工作。

第二天我起床后喝点咖啡,然后创建一个镜像——这次没有忘记那个"."!

$ docker build -t thumbnailer:01 .

我从缩略图项目的根目录开始构建。构建命令使用了根目录下的 Dockerfile。它是这样工作的:把 Dockerfile 放到你想构建镜像的地方,然后系统就默认使用这个 Dockerfile。

下面是我使用的Dockerfile 的内容:

FROM ubuntu:latest
MAINTAINER [email protected]

RUN apt-get update
RUN apt-get install -y nodejs nodejs-legacy npm
RUN apt-get install imagemagick libmagickcore-dev libmagickwand-dev
RUN apt-get clean

COPY ./package.json src/

RUN cd src && npm install

COPY . /src

WORKDIR src/

CMD npm start

这怎么可能出错呢?

第二个大问题

我运行了 build 命令,然后出了这个错:

Do you want to continue? [Y/n] Abort.

The command '/bin/sh -c apt-get install imagemagick libmagickcore-dev libmagickwand-dev' returned a non-zero code: 1

我猜测微服务出错了。我回到本地机器,从本机启动微服务,然后试着上传文件。

然后我从 NodeJS 获得了这个错误:

Error: spawn convert ENOENT

怎么回事?之前还是好好的啊!

我搜索了我能想到的所有的错误原因。差不多4个小时后,我想:为什么不重启一下机器呢?

重启了,你猜猜结果?错误消失了!(LCTT 译注:万能的“重启试试”)

继续。

将精灵关进瓶子里

跳回正题:我需要完成构建工作。

我使用 rm 命令删除了虚拟机里所有的容器。

$ docker rm -f $(docker ps -a -q)

-f 在这里的用处是强制删除运行中的镜像。

然后删除了全部 Docker 镜像,用的是命令 rmi:

$ docker rmi if $(docker images | tail -n +2 | awk '{print $3}')

我重新执行了重新构建镜像、安装容器、运行微服务的整个过程。然后过了一个充满自我怀疑和沮丧的一个小时,我告诉我自己:这个错误可能不是微服务的原因。

所以我重新看到了这个错误:

Do you want to continue? [Y/n] Abort.

The command '/bin/sh -c apt-get install imagemagick libmagickcore-dev libmagickwand-dev' returned a non-zero code: 1

这太打击我了:构建脚本好像需要有人从键盘输入 Y! 但是,这是一个非交互的 Dockerfile 脚本啊。这里并没有键盘。

回到 Dockerfile,脚本原来是这样的:

RUN apt-get update
RUN apt-get install -y nodejs nodejs-legacy npm
RUN apt-get install imagemagick libmagickcore-dev libmagickwand-dev
RUN apt-get clean

第二个apt-get 忘记了-y 标志,它用于自动应答提示所需要的“yes”。这才是错误的根本原因。

我在这条命令后面添加了-y

RUN apt-get update
RUN apt-get install -y nodejs nodejs-legacy npm
RUN apt-get install -y imagemagick libmagickcore-dev libmagickwand-dev
RUN apt-get clean

猜一猜结果:经过将近两天的尝试和痛苦,容器终于正常工作了!整整两天啊!

我完成了构建工作:

$ docker build -t thumbnailer:0.1 .

启动了容器:

$ docker run -d -p 3001:3000 thumbnailer:0.1

获取了虚拟机的IP 地址:

$ docker-machine ip default

在我的浏览器里面输入 http://192.168.99.100:3001/

上传页面打开了。

我选择了一个图片,然后得到了这个:

container-diagram-7

工作了!

在容器里面工作了,我的第一次啊!

这让我学到了什么?

很久以前,我接受了这样一个道理:当你刚开始尝试某项技术时,即使是最简单的事情也会变得很困难。因此,我不会把自己当成最聪明的那个人,然而最近几天尝试容器的过程就是一个充满自我怀疑的旅程。

但是你想知道一些其它的事情吗?这篇文章是我在凌晨2点完成的,而每一个受折磨的时刻都值得了。为什么?因为这段时间你将自己全身心投入了喜欢的工作里。这件事很难,对于所有人来说都不是很容易就获得结果的。但是不要忘记:你在学习技术,运行世界的技术。

P.S. 了解一下Hello World 容器的两段视频,这里会有 Raziel Tabib’s 的精彩工作内容。

千万被忘记第二部分...


via: https://deis.com/blog/2015/beyond-hello-world-containers-hard-stuff

作者:Bob Reselman 译者:Ezio 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出