分类 容器与云 下的文章

这是 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 命令行客户端预配置了几个镜像源。Ubuntu 的所有发行版和架构平台全都提供了官方镜像,但是对于其他的发行版也有大量的非官方镜像,那些镜像都是由社区制作并且被 LXC 上游贡献者所维护。

Ubuntu

如果你想要支持最为完善的 Ubuntu 版本,你可以按照下面的去做:

lxc launch ubuntu:

注意,这里意味着会随着 Ubuntu LTS 的发布而变化。因此,如果用于脚本,你需要指明你具体安装的版本(参见下面)。

Ubuntu14.04 LTS

得到最新更新的、已经测试过的、稳定的 Ubuntu 14.04 LTS 镜像,你可以简单的执行:

lxc launch ubuntu:14.04

在该模式下,会指定一个随机的容器名。

如果你更喜欢指定一个你自己的名字,你可以这样做:

lxc launch ubuntu:14.04 c1

如果你想要指定一个特定的体系架构(非主流平台),比如 32 位 Intel 镜像,你可以这样做:

lxc launch ubuntu:14.04/i386 c2

当前的 Ubuntu 开发版本

上面使用的“ubuntu:”远程仓库只会给你提供官方的并经过测试的 Ubuntu 镜像。但是如果你想要未经测试过的日常构建版本,开发版可能对你来说是合适的,你需要使用“ubuntu-daily:”远程仓库。

lxc launch ubuntu-daily:devel c3

在这个例子中,将会自动选中最新的 Ubuntu 开发版本。

你也可以更加精确,比如你可以使用代号名:

lxc launch ubuntu-daily:xenial c4

最新的Alpine Linux

Alpine 镜像可以在“Images:”远程仓库中找到,通过如下命令执行:

lxc launch images:alpine/3.3/amd64 c5

其他

全部的 Ubuntu 镜像列表可以这样获得:

lxc image list ubuntu:
lxc image list ubuntu-daily:

全部的非官方镜像:

lxc image list images:

某个给定的原程仓库的全部别名(易记名称)可以这样获得(比如对于“ubuntu:”远程仓库):

lxc image alias list ubuntu:

创建但不启动一个容器

如果你想创建一个容器或者一批容器,但是你不想马上启动它们,你可以使用lxc init替换掉lxc launch。所有的选项都是相同的,唯一的不同就是它并不会在你创建完成之后启动容器。

lxc init ubuntu:

关于你的容器的信息

列出所有的容器

要列出你的所有容器,你可以这样这做:

lxc list

有大量的选项供你选择来改变被显示出来的列。在一个拥有大量容器的系统上,默认显示的列可能会有点慢(因为必须获取容器中的网络信息),你可以这样做来避免这种情况:

lxc list --fast

上面的命令显示了另外一套列的组合,这个组合在服务器端需要处理的信息更少。

你也可以基于名字或者属性来过滤掉一些东西:

stgraber@dakara:~$ lxc list security.privileged=true
+------+---------+---------------------+-----------------------------------------------+------------+-----------+
| NAME |  STATE  |        IPV4         |                       IPV6                    |    TYPE    | SNAPSHOTS |
+------+---------+---------------------+-----------------------------------------------+------------+-----------+
| suse | RUNNING | 172.17.0.105 (eth0) | 2607:f2c0:f00f:2700:216:3eff:fef2:aff4 (eth0) | PERSISTENT | 0         |
+------+---------+---------------------+-----------------------------------------------+------------+-----------+

在这个例子中,只有那些特权容器(禁用了用户命名空间)才会被列出来。

stgraber@dakara:~$ lxc list --fast alpine
+-------------+---------+--------------+----------------------+----------+------------+
|    NAME     |  STATE  | ARCHITECTURE |      CREATED AT      | PROFILES |    TYPE    |
+-------------+---------+--------------+----------------------+----------+------------+
| alpine      | RUNNING | x86_64       | 2016/03/20 02:11 UTC | default  | PERSISTENT |
+-------------+---------+--------------+----------------------+----------+------------+
| alpine-edge | RUNNING | x86_64       | 2016/03/20 02:19 UTC | default  | PERSISTENT |
+-------------+---------+--------------+----------------------+----------+------------+

在这个例子中,只有在名字中带有“alpine”的容器才会被列出来(也支持复杂的正则表达式)。

获取容器的详细信息

由于 list 命令显然不能以一种友好的可读方式显示容器的所有信息,因此你可以使用如下方式来查询单个容器的信息:

lxc info <container>

例如:

stgraber@dakara:~$ lxc info zerotier
Name: zerotier
Architecture: x86_64
Created: 2016/02/20 20:01 UTC
Status: Running
Type: persistent
Profiles: default
Pid: 31715
Processes: 32
Ips:
 eth0: inet 172.17.0.101
 eth0: inet6 2607:f2c0:f00f:2700:216:3eff:feec:65a8
 eth0: inet6 fe80::216:3eff:feec:65a8
 lo: inet 127.0.0.1
 lo: inet6 ::1
 lxcbr0: inet 10.0.3.1
 lxcbr0: inet6 fe80::c0a4:ceff:fe52:4d51
 zt0: inet 29.17.181.59
 zt0: inet6 fd80:56c2:e21c:0:199:9379:e711:b3e1
 zt0: inet6 fe80::79:e7ff:fe0d:5123
Snapshots:
 zerotier/blah (taken at 2016/03/08 23:55 UTC) (stateless)

生命周期管理命令

这些命令对于任何容器或者虚拟机管理器或许都是最普通的命令,但是它们仍然需要讲到。

所有的这些命令在批量操作时都能接受多个容器名。

启动

启动一个容器就向下面一样简单:

lxc start <container>

停止

停止一个容器可以这样来完成:

lxc stop <container>

如果容器不合作(即没有对发出的 SIGPWR 信号产生回应),这时候,你可以使用下面的方式强制执行:

lxc stop <container> --force

重启

通过下面的命令来重启一个容器:

lxc restart <container>

如果容器不合作(即没有对发出的 SIGINT 信号产生回应),你可以使用下面的方式强制执行:

lxc restart <container> --force

暂停

你也可以“暂停”一个容器,在这种模式下,所有的容器任务将会被发送相同的 SIGSTOP 信号,这也意味着它们将仍然是可见的,并且仍然会占用内存,但是它们不会从调度程序中得到任何的 CPU 时间片。

如果你有一个很占用 CPU 的容器,而这个容器需要一点时间来启动,但是你却并不会经常用到它。这时候,你可以先启动它,然后将它暂停,并在你需要它的时候再启动它。

lxc pause <container>

删除

最后,如果你不需要这个容器了,你可以用下面的命令删除它:

lxc delete <container>

注意,如果容器还处于运行状态时你将必须使用“-force”。

容器的配置

LXD 拥有大量的容器配置设定,包括资源限制,容器启动控制以及对各种设备是否允许访问的配置选项。完整的清单因为太长所以并没有在本文中列出,但是,你可以从[这里]获取它。

就设备而言,LXD 当前支持下面列出的这些设备类型:

  • 磁盘 既可以是一块物理磁盘,也可以只是一个被挂挂载到容器上的分区,还可以是一个来自主机的绑定挂载路径。
  • 网络接口卡 一块网卡。它可以是一块桥接的虚拟网卡,或者是一块点对点设备,还可以是一块以太局域网设备或者一块已经被连接到容器的真实物理接口。
  • unix 块设备 一个 UNIX 块设备,比如 /dev/sda
  • unix 字符设备 一个 UNIX 字符设备,比如 /dev/kvm
  • none 这种特殊类型被用来隐藏那种可以通过配置文件被继承的设备。

配置 profile 文件

所有可用的配置文件列表可以这样获取:

lxc profile list

为了看到给定配置文件的内容,最简单的方式是这样做:

lxc profile show <profile>

你可能想要改变文件里面的内容,可以这样做:

lxc profile edit <profile>

你可以使用如下命令来改变应用到给定容器的配置文件列表:

lxc profile apply <container> <profile1>,<profile2>,<profile3>,...

本地配置

有些配置是某个容器特定的,你并不想将它放到配置文件中,你可直接对容器设置它们:

lxc config edit <container>

上面的命令做的和“profile edit”命令是一样。

如果不想在文本编辑器中打开整个文件的内容,你也可以像这样修改单独的配置:

lxc config set <container> <key> <value>

或者添加设备,例如:

lxc config device add my-container kvm unix-char path=/dev/kvm

上面的命令将会为名为“my-container”的容器设置一个 /dev/kvm 项。

对一个配置文件使用lxc profile setlxc profile device add命令也能实现上面的功能。

读取配置

你可以使用如下命令来读取容器的本地配置:

lxc config show <container>

或者得到已经被展开了的配置(包含了所有的配置值):

lxc config show --expanded <container>

例如:

stgraber@dakara:~$ lxc config show --expanded zerotier
name: zerotier
profiles:
- default
config:
 security.nesting: "true"
 user.a: b
 volatile.base_image: a49d26ce5808075f5175bf31f5cb90561f5023dcd408da8ac5e834096d46b2d8
 volatile.eth0.hwaddr: 00:16:3e:ec:65:a8
 volatile.last_state.idmap: '[{"Isuid":true,"Isgid":false,"Hostid":100000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":100000,"Nsid":0,"Maprange":65536}]'
devices:
 eth0:
  name: eth0
  nictype: macvlan
  parent: eth0
  type: nic
  limits.ingress: 10Mbit
  limits.egress: 10Mbit
 root:
  path: /
  size: 30GB
  type: disk
 tun:
  path: /dev/net/tun
  type: unix-char
ephemeral: false

这样做可以很方便的检查有哪些配置属性被应用到了给定的容器。

实时配置更新

注意,除非在文档中已经被明确指出,否则所有的配置值和设备项的设置都会对容器实时发生影响。这意味着在不重启正在运行的容器的情况下,你可以添加和移除某些设备或者修改安全配置文件。

获得一个 shell

LXD 允许你直接在容器中执行任务。最常用的做法是在容器中得到一个 shell 或者执行一些管理员任务。

和 SSH 相比,这样做的好处是你不需要容器是网络可达的,也不需要任何软件和特定的配置。

执行环境

与 LXD 在容器内执行命令的方式相比,有一点是不同的,那就是 shell 并不是在容器中运行。这也意味着容器不知道使用的是什么样的 shell,以及设置了什么样的环境变量和你的家目录在哪里。

通过 LXD 来执行命令总是使用最小的路径环境变量设置,并且 HOME 环境变量必定为 /root,以容器的超级用户身份来执行(即 uid 为 0,gid 为 0)。

其他的环境变量可以通过命令行来设置,或者在“environment.”配置中设置成永久环境变量。

执行命令

在容器中获得一个 shell 可以简单的执行下列命令得到:

lxc exec <container> bash

当然,这样做的前提是容器内已经安装了 bash。

更复杂的命令要求使用分隔符来合理分隔参数。

lxc exec <container> -- ls -lh /

如果想要设置或者重写变量,你可以使用“-env”参数,例如:

stgraber@dakara:~$ lxc exec zerotier --env mykey=myvalue env | grep mykey
mykey=myvalue

管理文件

因为 LXD 可以直接访问容器的文件系统,因此,它可以直接读取和写入容器中的任意文件。当我们需要提取日志文件或者与容器传递文件时,这个特性是很有用的。

从容器中取回一个文件

想要从容器中获得一个文件,简单的执行下列命令:

lxc file pull <container>/<path> <dest>

例如:

stgraber@dakara:~$ lxc file pull zerotier/etc/hosts hosts

或者将它读取到标准输出:

stgraber@dakara:~$ lxc file pull zerotier/etc/hosts -
127.0.0.1 localhost

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

向容器发送一个文件

发送以另一种简单的方式完成:

lxc file push <source> <container>/<path>

直接编辑一个文件

编辑是一个方便的功能,其实就是简单的提取一个给定的路径,在你的默认文本编辑器中打开它,在你关闭编辑器时会自动将编辑的内容保存到容器。

lxc file edit <container>/<path>

快照管理

LXD 允许你对容器执行快照功能并恢复它。快照包括了容器在某一时刻的完整状态(如果-stateful被使用的话将会包括运行状态),这也意味着所有的容器配置,容器设备和容器文件系统也会被保存。

创建一个快照

你可以使用下面的命令来执行快照功能:

lxc snapshot <container>

命令执行完成之后将会生成名为snapX(X 为一个自动增长的数)的记录。

除此之外,你还可以使用如下命令命名你的快照:

lxc snapshot <container> <snapshot name>

列出所有的快照

一个容器的所有快照的数量可以使用lxc list来得到,但是具体的快照列表只能执行lxc info命令才能看到。

lxc info <container>

恢复快照

为了恢复快照,你可以简单的执行下面的命令:

lxc restore <container> <snapshot name>

给快照重命名

可以使用如下命令来给快照重命名:

lxc move <container>/<snapshot name> <container>/<new snapshot name>

从快照中创建一个新的容器

你可以使用快照来创建一个新的容器,而这个新的容器除了一些可变的信息将会被重置之外(例如 MAC 地址)其余所有信息都将和快照完全相同。

lxc copy <source container>/<snapshot name> <destination container>

删除一个快照

最后,你可以执行下面的命令来删除一个快照:

lxc delete <container>/<snapshot name>

克隆并重命名

得到一个纯净的发行版镜像总是让人感到愉悦,但是,有时候你想要安装一系列的软件到你的容器中,这时,你需要配置它然后将它分支成多个其他的容器。

复制一个容器

为了复制一个容器并有效的将它克隆到一个新的容器中,你可以执行下面的命令:

lxc copy <source container> <destination container>

目标容器在所有方面将会完全和源容器等同。除了新的容器没有任何源容器的快照以及一些可变值将会被重置之外(例如 MAC 地址)。

移动一个快照

LXD 允许你复制容器并在主机之间移动它。但是,关于这一点将在后面的文章中介绍。

现在,“move”命令将会被用作给容器重命名。

lxc move <old name> <new name>

唯一的要求就是当容器应该被停止,容器内的任何事情都会被保存成它本来的样子,包括可变化的信息(类似 MAC 地址等)。

结论

这篇如此长的文章介绍了大多数你可能会在日常操作中使用到的命令。

很显然,这些如此之多的命令都会有不少选项,可以让你的命令更加有效率,或者可以让你指定你的 LXD 容器的某个具体方面。最好的学习这些命令的方式就是深入学习它们的帮助文档( -help)。

更多信息

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


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


来自于:https://www.stgraber.org/2016/03/19/lxd-2-0-your-first-lxd-container-312/ 作者:Stéphane Graber 译者:kylepeng93 校对:wxy

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

这是 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。我们推荐你配合最新版的 LXC 和 Linux 内核使用 LXD,这样就可以享受到它的全部特性。需要注意的是,我们现在也在慢慢的降低对旧版本 Linux 发布版的支持。

Ubuntu 标准版

所有新发布的 LXD 都会在发布几分钟后上传到 Ubuntu 开发版的安装源里。这个安装包然后就会作为 Ubuntu 用户的其他安装包源的种子。

如果使用 Ubuntu 16.04,可以直接安装:

sudo apt install lxd

如果运行的是 Ubuntu 14.04,则可以这样安装:

sudo apt -t trusty-backports install lxd

Ubuntu Core

使用 Ubuntu Core 稳定版的用户可以使用下面的命令安装 LXD:

sudo snappy install lxd.stgraber

Ubuntu 官方 PPA

使用其他 Ubuntu 发布版 —— 比如 Ubuntu 15.10 —— 的用户可以添加下面的 PPA(Personal Package Archive)来安装:

sudo apt-add-repository ppa:ubuntu-lxc/stable
sudo apt update
sudo apt dist-upgrade
sudo apt install lxd

Gentoo

Gentoo 已经有了最新的 LXD 包,你可以直接安装:

sudo emerge --ask lxd

使用源代码安装

如果你曾经编译过 Go 语言的项目,那么从源代码编译 LXD 并不是十分困难。然而注意,你需要 LXC 的开发头文件。为了运行 LXD, 你的发布版需也要使用比较新的内核(最起码是 3.13)、比较新的 LXC (1.1.4 或更高版本)、LXCFS 以及支持用户子 uid/gid 分配的 shadow 文件。

从源代码编译 LXD 的最新教程可以在上游 README里找到。

Ubuntu 上的网络配置

Ubuntu 的安装包会很方便的给你提供一个“lxdbr0”网桥。这个网桥默认是没有配置过的,只提供通过 HTTP 代理的 IPv6 的本地连接。

要配置这个网桥并添加 IPv4 、 IPv6 子网,你可以运行下面的命令:

sudo dpkg-reconfigure -p medium lxd

或者直接通过 LXD 初始化命令一步一步的配置:

sudo lxd init

存储后端

LXD 提供了几种存储后端。在开始使用 LXD 之前,你应该决定将要使用的后端,因为我们不支持在后端之间迁移已经生成的容器。

各个后端特性比较表可以在这里找到。

ZFS

我们的推荐是 ZFS, 因为它能支持 LXD 的全部特性,同时提供最快和最可靠的容器体验。它包括了以容器为单位的磁盘配额、即时快照和恢复、优化后的迁移(发送/接收),以及快速从镜像创建容器的能力。它同时也被认为要比 btrfs 更成熟。

要和 LXD 一起使用 ZFS ,你需要首先在你的系统上安装 ZFS。

如果你是用的是 Ubuntu 16.04 , 你只需要简单的使用命令安装:

sudo apt install zfsutils-linux

在 Ubuntu 15.10 上你可以这样安装:

sudo apt install zfsutils-linux zfs-dkms

如果是更旧的版本,你需要从 zfsonlinux PPA 安装:

sudo apt-add-repository ppa:zfs-native/stable
sudo apt update
sudo apt install ubuntu-zfs

配置 LXD 只需要执行下面的命令:

sudo lxd init

这条命令接下来会向你提问一些 ZFS 的配置细节,然后为你配置好 ZFS。

btrfs

如果 ZFS 不可用,那么 btrfs 可以提供相同级别的集成,但不能正确地报告容器内的磁盘使用情况(虽然配额仍然可用)。

btrfs 同时拥有很好的嵌套属性,而这是 ZFS 所不具有的。也就是说如果你计划在 LXD 中再使用 LXD,那么 btrfs 就很值得你考虑。

使用 btrfs 的话,LXD 不需要进行任何的配置,你只需要保证 /var/lib/lxd 保存在 btrfs 文件系统中,然后 LXD 就会自动为你使用 btrfs 了。

LVM

如果 ZFS 和 btrfs 都不是你想要的,你还可以考虑使用 LVM 以获得部分特性。 LXD 会以自动精简配置的方式使用 LVM,为每个镜像和容器创建 LV,如果需要的话也会使用 LVM 的快照功能。

要配置 LXD 使用 LVM,需要创建一个 LVM 卷组,然后运行:

lxc config set storage.lvm_vg_name "THE-NAME-OF-YOUR-VG"

默认情况下 LXD 使用 ext4 作为全部逻辑卷的文件系统。如果你喜欢的话可以改成 XFS:

lxc config set storage.lvm_fstype xfs

简单目录

如果上面全部方案你都不打算使用,LXD 依然能在不使用任何高级特性情况下工作。它会为每个容器创建一个目录,然后在创建每个容器时解压缩镜像的压缩包,并在容器拷贝和快照时进行一次完整的文件系统拷贝。

除了磁盘配额以外的特性都是支持的,但是很浪费磁盘空间,并且非常慢。如果你没有其他选择,这还是可以工作的,但是你还是需要认真的考虑一下上面的几个替代方案。

配置篇

LXD 守护进程的完整配置项列表可以在这里找到

网络配置

默认情况下 LXD 不会监听网络。和它通信的唯一办法是通过 /var/lib/lxd/unix.socket 使用本地 unix 套接字进行通信。

要让 LXD 监听网络,下面有两个有用的命令:

lxc config set core.https_address [::]
lxc config set core.trust_password some-secret-string

第一条命令将 LXD 绑定到 IPv6 地址 “::”,也就是监听机器的所有 IPv6 地址。你可以显式的使用一个特定的 IPv4 或者 IPv6 地址替代默认地址,如果你想绑定某个 TCP 端口(默认是 8443)的话可以在地址后面添加端口号即可。

第二条命令设置了密码,用于让远程客户端把自己添加到 LXD 可信证书中心。如果已经给主机设置了密码,当添加 LXD 主机时会提示输入密码,LXD 守护进程会保存他们的客户端证书以确保客户端是可信的,这样就不需要再次输入密码(可以随时设置和取消)。

你也可以选择不设置密码,而是人工验证每个新客户端是否可信——让每个客户端发送“client.crt”(来自于 ~/.config/lxc)文件,然后把它添加到你自己的可信证书中心:

lxc config trust add client.crt

代理配置

大多数情况下,你会想让 LXD 守护进程从远程服务器上获取镜像。

如果你处在一个必须通过 HTTP(s) 代理链接外网的环境下,你需要对 LXD 做一些配置,或保证已在守护进程的环境中设置正确的 PROXY 环境变量。

lxc config set core.proxy_http http://squid01.internal:3128
lxc config set core.proxy_https http://squid01.internal:3128
lxc config set core.proxy_ignore_hosts image-server.local

以上代码使所有 LXD 发起的数据传输都使用 squid01.internal HTTP 代理,但与在 image-server.local 的服务器的数据传输则是例外。

镜像管理

LXD 使用动态镜像缓存。当从远程镜像创建容器的时候,它会自动把镜像下载到本地镜像商店,同时标志为已缓存并记录来源。几天后(默认 10 天)如果某个镜像没有被使用过,那么它就会自动地被删除。每隔几小时(默认是 6 小时)LXD 还会检查一下这个镜像是否有新版本,然后更新镜像的本地拷贝。

所有这些都可以通过下面的配置选项进行配置:

lxc config set images.remote_cache_expiry 5
lxc config set images.auto_update_interval 24
lxc config set images.auto_update_cached false

这些命令让 LXD 修改了它的默认属性,缓存期替换为 5 天,更新间隔为 24 小时,而且只更新那些标记为自动更新(–auto-update)的镜像(lxc 镜像拷贝被标记为 –auto-update)而不是 LXD 自动缓存的镜像。

总结

到这里为止,你就应该有了一个可以工作的、最新版的 LXD,现在你可以开始用 LXD 了,或者等待我们的下一篇博文,我们会在其中介绍如何创建第一个容器以及使用 LXD 命令行工具操作容器。

额外信息

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


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


via: https://www.stgraber.org/2016/03/15/lxd-2-0-installing-and-configuring-lxd-212/

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

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

欢迎来到“Fedora 中的容器技术”系列!本文是该系列文章中的第一篇,它将说明你可以怎样使用 Fedora 中各种可用的容器技术。本文将学习 systemd-nspawn 的相关知识。

容器是什么?

一个容器就是一个用户空间实例,它能够在与托管容器的系统(叫做宿主系统)相隔离的环境中运行一个程序或者一个操作系统。这和 chroot虚拟机 的思想非常类似。运行在容器中的进程是由与宿主操作系统相同的内核来管理的,但它们是与宿主文件系统以及其它进程隔离开的。

什么是 systemd-nspawn?

systemd 项目认为应当将容器技术变成桌面的基础部分,并且应当和用户的其余系统集成在一起。为此,systemd 提供了 systemd-nspawn,这款工具能够使用多种 Linux 技术创建容器。它也提供了一些容器管理工具。

systemd-nspawnchroot 在许多方面都是类似的,但是前者更加强大。它虚拟化了文件系统、进程树以及客户系统中的进程间通信。它的吸引力在于它提供了很多用于管理容器的工具,例如用来管理容器的 machinectl。由 systemd-nspawn 运行的容器将会与 systemd 组件一同运行在宿主系统上。举例来说,一个容器的日志可以输出到宿主系统的日志中。

在 Fedora 24 上,systemd-nspawn 已经从 systemd 软件包分离出来了,所以你需要安装 systemd-container 软件包。一如往常,你可以使用 dnf install systemd-container 进行安装。

创建容器

使用 systemd-nspawn 创建一个容器是很容易的。假设你有一个专门为 Debian 创造的应用,并且无法在其它发行版中正常运行。那并不是一个问题,我们可以创造一个容器!为了设置容器使用最新版本的 Debian(现在是 Jessie),你需要挑选一个目录来放置你的系统。我暂时将使用目录 ~/DebianJessie

一旦你创建完目录,你需要运行 debootstrap,你可以从 Fedora 仓库中安装它。对于 Debian Jessie,你运行下面的命令来初始化一个 Debian 文件系统。

$ debootstrap --arch=amd64 stable ~/DebianJessie

以上默认你的架构是 x86\_64。如果不是的话,你必须将架构的名称改为 amd64。你可以使用 uname -m 得知你的机器架构。

一旦设置好你的根目录,你就可以使用下面的命令来启动你的容器。

$ systemd-nspawn -bD ~/DebianJessie

容器将会在数秒后准备好并运行,当你试图登录时就会注意到:你无法使用你的系统上任何账户。这是因为 systemd-nspawn 虚拟化了用户。修复的方法很简单:将之前的命令中的 -b 移除即可。你将直接进入容器的 root 用户的 shell。此时,你只能使用 passwd 命令为 root 设置密码,或者使用 adduser 命令添加一个新用户。一旦设置好密码或添加好用户,你就可以把 -b 标志添加回去然后继续了。你会进入到熟悉的登录控制台,然后你使用设置好的认证信息登录进去。

以上对于任意你想在容器中运行的发行版都适用,但前提是你需要使用正确的包管理器创建系统。对于 Fedora,你应使用 DNF 而非 debootstrap。想要设置一个最小化的 Fedora 系统,你可以运行下面的命令,要将“/absolute/path/”替换成任何你希望容器存放的位置。

$ sudo dnf --releasever=24 --installroot=/absolute/path/ install systemd passwd dnf fedora-release

设置网络

如果你尝试启动一个服务,但它绑定了你宿主机正在使用的端口,你将会注意到这个问题:你的容器正在使用和宿主机相同的网络接口。幸运的是,systemd-nspawn 提供了几种可以将网络从宿主机分开的方法。

本地网络

第一种方法是使用 --private-network 标志,它默认仅创建一个回环设备。这对于你不需要使用网络的环境是非常理想的,例如构建系统和其它持续集成系统。

多个网络接口

如果你有多个网络接口设备,你可以使用 --network-interface 标志给容器分配一个接口。想要给我的容器分配 eno1,我会添加选项 --network-interface=eno1。当某个接口分配给一个容器后,宿主机就不能同时使用那个接口了。只有当容器彻底关闭后,宿主机才可以使用那个接口。

共享网络接口

对于我们中那些并没有额外的网络设备的人来说,还有其它方法可以访问容器。一种就是使用 --port 选项。这会将容器中的一个端口定向到宿主机。使用格式是 协议:宿主机端口:容器端口,这里的协议可以是 tcp 或者 udp宿主机端口 是宿主机的一个合法端口,容器端口 则是容器中的一个合法端口。你可以省略协议,只指定 宿主机端口:容器端口。我通常的用法类似 --port=2222:22

你可以使用 --network-veth 启用完全的、仅宿主机模式的网络,这会在宿主机和容器之间创建一个虚拟的网络接口。你也可以使用 --network-bridge 桥接二者的连接。

使用 systemd 组件

如果你容器中的系统含有 D-Bus,你可以使用 systemd 提供的实用工具来控制并监视你的容器。基础安装的 Debian 并不包含 dbus。如果你想在 Debian Jessie 中使用 dbus,你需要运行命令 apt install dbus

machinectl

为了能够轻松地管理容器,systemd 提供了 machinectl 实用工具。使用 machinectl,你可以使用 machinectl login name 登录到一个容器中、使用 machinectl status name检查状态、使用 machinectl reboot name 启动容器或者使用 machinectl poweroff name 关闭容器。

其它 systemd 命令

多数 systemd 命令,例如 journalctl, systemd-analyzesystemctl,都支持使用 --machine 选项来指定容器。例如,如果你想查看一个名为 “foobar” 的容器的日志,你可以使用 journalctl --machine=foobar。你也可以使用 systemctl --machine=foobar status service 来查看运行在这个容器中的服务状态。

和 SELinux 一起工作

如果你要使用 SELinux 强制模式(Fedora 默认模式),你需要为你的容器设置 SELinux 环境。想要那样的话,你需要在宿主系统上运行下面两行命令。

$ semanage fcontext -a -t svirt_sandbox_file_t "/path/to/container(/.*)?"
$ restorecon -R /path/to/container/

确保使用你的容器路径替换 “/path/to/container”。对于我的容器 "DebianJessie",我会运行下面的命令:

$ semanage fcontext -a -t svirt_sandbox_file_t "/home/johnmh/DebianJessie(/.*)?"
$ restorecon -R /home/johnmh/DebianJessie/

via: https://fedoramagazine.org/container-technologies-fedora-systemd-nspawn/

作者:John M. Harris, Jr. 译者:ChrisLeeGit 校对:wxy

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

这是 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中国 荣誉推出

我的上一篇文章里, 我介绍了 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中国 荣誉推出

我告诉你一个秘密:DevOps 云计算之类的东西可以把我的程序运行在世界上任何一个地方,这对我来说仍然有一点神秘。但随着时间流逝,我意识到理解大规模的机器增减和应用程序部署的来龙去脉对一个开发者来说是非常重要的知识。这类似于成为一个专业的音乐家,当然你肯定需要知道如何使用你的乐器,但是,如果你不知道一个录音棚是如何工作的,或者如何适应一个交响乐团,那么你在这样的环境中工作会变得非常困难。

在软件开发的世界里,使你的代码进入我们的更大的世界如同把它编写出来一样重要。DevOps 重要,而且是很重要。

因此,为了弥合 开发 Dev 部署 Ops 之间的空隙,我会从头开始介绍容器技术。为什么是容器?因为有强力的证据表明,容器是机器抽象的下一步:使计算机成为场所而不再是一个东西。理解容器是我们共同的旅程。

在这篇文章中,我会介绍 容器化 containerization 背后的概念。包括容器和虚拟机的区别,以及容器构建背后的逻辑以及它是如何适应应用程序架构的。我会探讨轻量级的 Linux 操作系统是如何适应容器生态系统。我还会讨论使用镜像创建可重用的容器。最后我会介绍容器集群如何使你的应用程序可以快速扩展。

在后面的文章中,我会一步一步向你介绍容器化一个示例应用程序的过程,以及如何为你的应用程序容器创建一个托管集群。同时,我会向你展示如何使用 Deis 将你的示例应用程序部署到你本地系统以及多种云供应商的虚拟机上。

让我们开始吧。

虚拟机的好处

为了理解容器如何适应事物发展,你首先要了解容器的前任:虚拟机。

虚拟机 virtual machine(VM) 是运行在物理宿主机上的软件抽象。配置一个虚拟机就像是购买一台计算机:你需要定义你想要的 CPU 数目、RAM 和磁盘存储容量。配置好了机器后,你为它加载操作系统,以及你想让虚拟机支持的任何服务器或者应用程序。

虚拟机允许你在一台硬件主机上运行多个模拟计算机。这是一个简单的示意图:

虚拟机可以让你能充分利用你的硬件资源。你可以购买一台巨大的、轰隆作响的机器,然后在上面运行多个虚拟机。你可以有一个数据库虚拟机以及很多运行相同版本的定制应用程序的虚拟机所构成的集群。你可以在有限的硬件资源获得很多的扩展能力。如果你觉得你需要更多的虚拟机而且你的宿主硬件还有容量,你可以添加任何你需要的虚拟机。或者,如果你不再需要一个虚拟机,你可以关闭该虚拟机并删除虚拟机镜像。

虚拟机的局限

但是,虚拟机确实有局限。

如上面所示,假如你在一个主机上创建了三个虚拟机。主机有 12 个 CPU,48 GB 内存和 3TB 的存储空间。每个虚拟机配置为有 4 个 CPU,16 GB 内存和 1TB 存储空间。到现在为止,一切都还好。主机有这个容量。

但这里有个缺陷。所有分配给一个虚拟机的资源,无论是什么,都是专有的。每台机器都分配了 16 GB 的内存。但是,如果第一个虚拟机永不会使用超过 1GB 分配的内存,剩余的 15 GB 就会被浪费在那里。如果第三个虚拟机只使用分配的 1TB 存储空间中的 100GB,其余的 900GB 就成为浪费空间。

这里没有资源的流动。每台虚拟机拥有分配给它的所有资源。因此,在某种方式上我们又回到了虚拟机之前,把大部分金钱花费在未使用的资源上。

虚拟机还有另一个缺陷。让它们跑起来需要很长时间。如果你处于基础设施需要快速增长的情形,即使增加虚拟机是自动的,你仍然会发现你的很多时间都浪费在等待机器上线。

来到:容器

概念上来说,容器是一个 Linux 进程,Linux 认为它只是一个运行中的进程。该进程只知道它被告知的东西。另外,在容器化方面,该容器进程也分配了它自己的 IP 地址。这点很重要,重要的事情讲三遍,这是第二遍。在容器化方面,容器进程有它自己的 IP 地址。一旦给予了一个 IP 地址,该进程就是宿主网络中可识别的资源。然后,你可以在容器管理器上运行命令,使容器 IP 映射到主机中能访问公网的 IP 地址。建立了该映射,无论出于什么意图和目的,容器就是网络上一个可访问的独立机器,从概念上类似于虚拟机。

这是第三遍,容器是拥有不同 IP 地址从而使其成为网络上可识别的独立 Linux 进程。下面是一个示意图:

容器/进程以动态、合作的方式共享主机上的资源。如果容器只需要 1GB 内存,它就只会使用 1GB。如果它需要 4GB,就会使用 4GB。CPU 和存储空间利用也是如此。CPU、内存和存储空间的分配是动态的,和典型虚拟机的静态方式不同。所有这些资源的共享都由容器管理器来管理。

最后,容器能非常快速地启动。

因此,容器的好处是:你获得了虚拟机独立和封装的好处,而抛弃了静态资源专有的缺陷。另外,由于容器能快速加载到内存,在扩展到多个容器时你能获得更好的性能。

容器托管、配置和管理

托管容器的计算机运行着被剥离的只剩下主要部分的某个 Linux 版本。现在,宿主计算机流行的底层操作系统是之前提到的 CoreOS。当然还有其它,例如 Red Hat Atomic HostUbuntu Snappy

该 Linux 操作系统被所有容器所共享,减少了容器足迹的重复和冗余。每个容器只包括该容器特有的部分。下面是一个示意图:

你可以用它所需的组件来配置容器。一个容器组件被称为 layer 。层是一个容器镜像,(你会在后面的部分看到更多关于容器镜像的介绍)。你从一个基本层开始,这通常是你想在容器中使用的操作系统。(容器管理器只提供你所要的操作系统在宿主操作系统中不存在的部分。)当你构建你的容器配置时,你需要添加层,例如你想要添加网络服务器时这个层就是 Apache,如果容器要运行脚本,则需要添加 PHP 或 Python 运行时环境。

分层非常灵活。如果应用程序或者服务容器需要 PHP 5.2 版本,你相应地配置该容器即可。如果你有另一个应用程序或者服务需要 PHP 5.6 版本,没问题,你可以使用 PHP 5.6 配置该容器。不像虚拟机,更改一个版本的运行时依赖时你需要经过大量的配置和安装过程;对于容器你只需要在容器配置文件中重新定义层。

所有上面描述的容器的各种功能都由一个称为 容器管理器 container manager 的软件控制。现在,最流行的容器管理器是 DockerRocket。上面的示意图展示了容器管理器是 Docker,宿主操作系统是 CentOS 的主机情景。

容器由镜像构成

当你需要将我们的应用程序构建到容器时,你就要编译镜像。镜像代表了你的容器需要完成其工作的容器模板。(容器里可以在容器里面,如下图)。镜像存储在 注册库 registry 中,注册库通过网络访问。

从概念上讲,注册库类似于一个使用 Java 的人眼中的 Maven 仓库、使用 .NET 的人眼中的 NuGet 服务器。你会创建一个列出了你应用程序所需镜像的容器配置文件。然后你使用容器管理器创建一个包括了你的应用程序代码以及从容器注册库中下载的部分资源。例如,如果你的应用程序包括了一些 PHP 文件,你的容器配置文件会声明你会从注册库中获取 PHP 运行时环境。另外,你还要使用容器配置文件声明需要复制到容器文件系统中的 .php 文件。容器管理器会封装你应用程序的所有东西为一个独立容器,该容器将会在容器管理器的管理下运行在宿主计算机上。

这是一个容器创建背后概念的示意图:

让我们仔细看看这个示意图。

(1)代表一个定义了你容器所需东西以及你容器如何构建的容器配置文件。当你在主机上运行容器时,容器管理器会读取该配置文件,从云上的注册库中获取你需要的容器镜像,(2)将镜像作为层添加到你的容器中。

另外,如果组成镜像需要其它镜像,容器管理器也会获取这些镜像并把它们作为层添加进来。(3)容器管理器会将需要的文件复制到容器中。

如果你使用了 配置 provisioning 服务,例如 Deis,你刚刚创建的应用程序容器做成镜像,(4)配置服务会将它部署到你选择的云供应商上,比如类似 AWS 和 Rackspace 云供应商。

集群中的容器

好了。这里有一个很好的例子说明了容器比虚拟机提供了更好的配置灵活性和资源利用率。但是,这并不是全部。

容器真正的灵活是在集群中。记住,每个容器有一个独立的 IP 地址。因此,能把它放到负载均衡器后面。将容器放到负载均衡器后面,这就上升了一个层面。

你可以在一个负载均衡容器后运行容器集群以获得更高的性能和高可用计算。这是一个例子:

假如你开发了一个资源密集型的应用程序,例如图片处理。使用类似 Deis 的容器配置技术,你可以创建一个包括了你图片处理程序以及你图片处理程序需要的所有资源的容器镜像。然后,你可以部署一个或多个容器镜像到主机上的负载均衡器下。一旦创建了容器镜像,你可以随时使用它。当系统繁忙时可以添加更多的容器实例来满足手中的工作。

这里还有更多好消息。每次添加实例到环境中时,你不需要手动配置负载均衡器以便接受你的容器镜像。你可以使用服务发现技术让容器告知均衡器它可用。然后,一旦获知,均衡器就会将流量分发到新的结点。

全部放在一起

容器技术完善了虚拟机缺失的部分。类似 CoreOS、RHEL Atomic、和 Ubuntu 的 Snappy 宿主操作系统,和类似 Docker 和 Rocket 的容器管理技术结合起来,使得容器变得日益流行。

尽管容器变得更加越来越普遍,掌握它们还是需要一段时间。但是,一旦你懂得了它们的窍门,你可以使用类似 Deis 这样的配置技术使容器创建和部署变得更加简单。

从概念上理解容器和进一步实际使用它们完成工作一样重要。但我认为不实际动手把想法付诸实践,概念也难以理解。因此,我们该系列的下一阶段就是:创建一些容器。


via: https://deis.com/blog/2015/developer-journey-linux-containers

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

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