Chapter14 - Linux 容器和 Docker
Linux 容器简介
Linux 容器
窗口是一种轻量级应用程序隔离机制,允许内核在其自身的隔离用户空间(独立于主机系统)运行数组进程。容器具有自己的进程列表、网络堆栈、文件系统和其他资源,但是与主机以及系统上运行的其他容器共享内核。Linux 容器是通过以下三个内核功能的组合来实现的:用于隔离的名称空间、用于资源控制的组以及用于安全性的SELinux 。
一个名为Docker 的工具用户创建、控制和管理容器。Docker 向Linux 容器添加一个API 、一种映像格式以及一个发送和共享模型。Docker 映像包含一个应用程序及其所有依赖项。当容器启动时,将使用 LVM 精简配置将该容器的读写层与只读映像进行组合。Docker 映像可移植,并且可以保存和导出到其他系统和用户。

容器技术
内核通过名称空间来提供容器隔离,而名称空间创建一个新环境,其中包含系统中资源子集的唯一视图。一些人将名称空间称为“增强版chroot ”,因为名称空间不仅为进程提供不同的根文件系统,而且进程还具有进程表、联网堆栈等等不同的视图。
容器现在使用五种不同类型的名称空间:
Linux 容器使用的名称空间:
名称空间 | 用途 |
---|---|
挂载 | 隔离容器所看到的文件系统。容器的 / 彼此不同,与主机系统也不同 |
PID | 每个容器具有自己的进程 ID(PID)表。容器中的进程看不到外部进程,并且 PID 1 在每个容器中都不同。容器中的所有进程都在主机中显示。但具有不同的 PID 编号 |
网络 | 每个容器都具有自己的网络接口、路由表和防火墙规则 ,而这些内容无法直接在主机的默认网络名称空间中显示,并且彼此也无法查看。它们可以连接到主机的网络基础架构和错上外界。 |
IPC | 隔离进程间通信(IPC)资源,如 System V共享内存和 POSIX 消息队列。两个容器不能与彼此的共享内存空间进行交互。 |
UTS | 一个容器的主机名和域名可以不同于其他容器和主机系统。 |
控制组
控制级(cgroups)由内核用于管理系统资源。cgroups 允许在进程和进程组间公平(或非公平)分配 CPU 时间、内存和 I/O 带宽。容器使用 cgroups 来管理资源消耗,以便容器将获得一定份额的系统资源,而不是窃取所有系统资源。
cgroups 的管理不在本课程的范围之内。熟悉 cgroups 的系统管理员应该意识到,RHEL7 现在使用的 systemd 作用域和切片单元以更加轻松的管理 cgroups 。
SELinux 和容器
并非所有对象都能进程名称空间化,并且容器的安全性也低于虚拟机。为了防止主机以及其他容器变为被入侵的容器,将使用 SELinux。
强制使用 SELinux 时,容器进程只能写入到容器文件。容器进程作为 svirt_lxc_net_t 类型来运行,而映像文件通过svirt_sandbox_file_t 类型来标记。
为了使两个窗口能够彼此访问(即使它们具有相同的 SELinux 类型),将使用多类别安全性(MCS)类型。每个容器都作为 svirt_lxc_net_t 来运行。但在由 Docker 启动时,将被分配一个随机且唯一的类别。如果某个进程具有正确的 SELinux 类型,但是类别不匹配,则会拒绝访问。
警告
始终在 SELinux 处于强制模式的情况下运行主机系统。
如果用于控制容器的主机系统在禁用了 SELinux 的情况下运行或者设置为许可模式,则窗口中运行的恶意进程可能会执行并破坏主机环境以及系统上的所有其他容器。
Docker
Docker 提供了用户接口、API、映像格式以及用于在 RHEL 7 中管理 linux 容器的其他工具。
docker 软件包是由特殊存储库 RHEL 7 Extras (rhel-7-server-extras-rpms)提供的,该存储库的更新工程标准要高于核心分发版。
Docker 运行由 systemd 单元 docker.service 启动的守护进程以管理容器。用户通过 docker 命令来与此守护进程进行交互。映像存储在本地索引(保存在 /var/lib/docker 目录中),但使用docker 命令进行加载和导出。
Docker 映像
docker 映像是容器配置的静态快照,用于启动容器。映像是绝不会被修改的只读层。而Docker 增加了读写覆盖,对其进行所有更改。通过创建新映像来保存更改。一个映像可用于生成很多有细微差别的容器,只是需要足够磁盘空间来存储很小数量的差异。
平台映像是不含任何父代的映像。这是一个基线映像,用于定义运行某个应用程序所需要的核心运行时环境、软件包和实用程序。红帽为 RHEL 7 提供了一个极端最小的平台映像,其中包含要支持容器bash 以及要使用 yum 下载附加软件包将其安装到窗口中而至少必需的软件包。

Docker 注册表
可以作为tar 归档来提供平台映像,也可以手动加载。而更常见的是,它们拉取自 Docker 注册表。此存储库可能 公开提供。红帽 registry.access.rehat.com 中提供了一个只读注册表,通过此注册表,可以拉取 RHEL7 基本平台映像。Docker 保持一个公共注册表,可以通过此注册表来拉取开源社区提供的映像。
还可以使用 docker-registry 软件包来设置一个简单的专用存储库。可以将映像推送(push)到此专线存储库并且可由网络中的其他计算机进行如拉取(pull)。

容器和虚拟化
容器和虚拟化是两种技术,提供了划分系统资源的互补方法。结合这两种技术,容器可以在虚拟机和云计算环境中运行。如果说虚拟化是以“垂直”方式抽象化硬件,则容器可以说是“水平”分段操作系统。
不同用例适合不同的技术。Docker 容器的一些优点包括:
- 容器在资源使用方面属于轻量级,因此相同硬件上可以运行更多容器。
- 与虚拟化相比,可以更快速的创建和销毁容器。
- 与虚拟机不同的是,容器不需要支持整个操作系统;应用程序仅需要核心运行时。这允许快速部署应用程序。
- Docker 映像具有一个版本控制流,因此可以跟踪甚至恢复某一映像的连续版本。组件复用来自其他层的组件,从而使容器映像变得非常轻量级。
- Docker 映像可以轻松的传输到具有Docker 的其他计算机。
相比之下
- 虚拟机运行自己的的内核和完整操作系统,这能够在主机系统和管理程序与虚拟机之间进行更强的隔离。
- 虚拟机可以轻松运行与系统管理程序主机的操作系统完成不同的操作系统和内核。
- 虚拟机可以在运行期间实时从一个系统管理程序节点迁移到另一个系统管理程序节点,而容器在从一个计算机移动到另一个计算机之前必须停止。

红帽企业 Linux Atomic 主机
红帽与 Project Atomic (http://www.projectatomic.io) 协作推出了红帽企业 Linux 7 的一个变体,(红帽企业 Linux Atomic 主机),此变体经过优化,以较小的占用面积运行容器,并且可以自动更新以便于维护。在 Project Atomic 系统上运行容器有一点类似于在瘦系统管理程序主机上运行虚拟机。主机环境将仅提供容器所需要的组件 。
使用 Docker
安装和启动 Docker
要下载和使用 RHEL 7 随附的Docker 软件包,RHEL 7 系统需要使用 subscription-manager 进行注册并正确请阅 RHEL 7 ,一旦完成此操作,需要启用对 RHEL 7 Optional 和 RHEL 7 Extras 频道的访问权限并安装 docker 软件包。
[root@demo ~]# subscription-manager repos --enable=rhel-7-server-extras-rpms
[root@demo ~]# subscription-manager repos --enable=rhel-7-server-optional-rpms
[root@demo ~]# yum install docker
一旦安装docker ,必须启动并启用 docker 服务。
[root@demo ~]# systemctl start docker;systemctl enable docker
拉取或加载平台映像
一旦安装Docker ,下一步是将基本平台映像放入计算机的本地索引中。执行此操作的一种方法是从远程Docker 注册表中拉取映像。可以使用命令 docker pull repository/image-name 来完成此操作。如果未指定存储库,侧假设社区 Docker 中心存储库。红帽在 registry.access.redhat.com 中提供了名为 redhat/rhel7 的官方基地映像。
[root@demo ~]#docker pull registry.access.redhat.com/redhat/rhel7
Pulling repository registry.access.redhat.com/redhat/rhely
elf5733f050b:Download complete
获取基地映像的另一种方法是导出的 tar 归档的形式来获取并进行加载。红帽在 https://access.redhat.com/search/browse/docker-images#? 中以下列形式提供了基本映像。使用当前目录中的tar 归档,以下命令将其加载到本地索引中:
[root@demo ~]# docker load -i rhel-server-docker-7.0-21.4-x86_64.tar.gz
一旦映像加载到本地索引中,请使用命令 docker images 来列出可用的映像:
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.access.redhat.com/redhat/rhel7 0 elf5722f050b 3 days ago 140.2 MB
运行 Docker 容器
docker run 命令用于运行容器。其最简单的形式是采用两个参数:要使用的映像的名称以及要在容器中运行的命令的名称。以下是一个将以交互模式运行窗口的示例,其使用映像 redis 并运行命令 cat /etc/hosts. 当命令退出时,容器将停止。 –rm 命令将在容器退出时删除容器。
[root@localhost ~]# docker run -i -t --rm redis cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 3afe5b4033c2
[root@localhost ~]# docker run -i -t redis bash
root@7c22e2abd103:/data# echo hello > /tmp/redis.tmp
root@7c22e2abd103:/data# exit
exit
[root@localhost ~]# ls /tmp/
systemd-private-8be2a238a3064a42b0a237f8afa0e613-chronyd.service-8IcGxc tmp.90ZVRFHsjd vmware-root_6392-969588466
[root@localhost ~]# find / -name redis.tmp
/var/lib/docker/overlay2/7392a8a6625d5f7135bfd8b471a2296ff60a771a3f253c6f3ae8a11a4870e175/diff/tmp/redis.tmp
[root@localhost ~]#
这次,当容器退出时,其不会删除自己的文件系统,因为未包含 –rm 选项。可以使用 docker ps -a 命令来查看。 -a 选项显示系统上的所有容器,包括正在运行的和已经停止的容器。(如果没有 -a 则仅显示正在运行的容器。)
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c22e2abd103 redis "docker-entrypoint..." 3 minutes ago Exited (0) 3 minutes ago boring_varahamihira
[root@localhost ~]#
由于容器退出时不会删除,使用 docker start 可重新启动容器并以交互方式再次连接到容器(使用 -ai 选项)。容器的名称是随机分配的,因为在首次创建容器时未指定名称。可以按名称或 ID 来选择容器。
[root@localhost ~]# docker start -ai boring_varahamihira
root@7c22e2abd103:/data# cat /tmp/redis.tmp
hello
root@7c22e2abd103:/data# exit
exit
[root@localhost ~]#
创建 Docker 映像
在下一个示例中,将通过包含 httpd 软件包的基础映像来创建Docker 映像。新映像随后将用于运行容器。
从RHEL 7 基地映像中运行容器一次,以手动安装 httpd 软件包。
[root@demo ~]# docker run -i rhel7 bash -c "yum install -y httpd;yum clean all"
在已经安装了软件包并且容器退出后,识别最后一个使用 docker ps -l 运行的容器。利用窗口的名称或 ID ,docker commit 命令可用于通过容器的文件系统来创建新映像。以下命令还将包括一条注释(使用 -m )。
[root@localhost ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e41ce4f9ad40 redis "docker-entrypoint..." 2 minutes ago Exited (127) 2 minutes ago boring_galileo
[root@localhost ~]# docker commit -m "RHEL 7 + HTTPD" boring_galileo rhel_httpd
sha256:d6a4cea3551e65397eb8931ba84c67dae8f8578c4e877232923698da8bcd13f6
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
rhel_httpd latest d6a4cea3551e 6 seconds ago 95 MB
docker.io/redis latest a55fbf438dfd 2 weeks ago 95 MB
[root@localhost ~]#
现在,可以运行一个使用此新映像作为其基线的容器。以下命令将引入两个新选项。 -p 8080:80 选项可将主机上的端口 8080/TCP 映射到容器上的端口80/TCP 。 -d 将使容器在后台启动。 /usr/sbin/httpd -DFOREGROUND 命令将在容器中以非常基本的模式启动 Apache HTTPD.
[root@localhost ~]# docker run -p 8080:80 -d httpd /usr/sbin/httpd -DFOREGROUND
7b8c6308b2f04101b5fe3af3ec184555eadcc66ed0a2a11b8fd43b194c62e838
或下载一下 httpd 映像,如下
[root@localhost ~]# docker run -p 8080:80 -d httpd
4498b109d1ee3029c0cee7a19aa49e6a47801dda9c2aeb0281c62ddf2714e661
[root@localhost ~]#
[root@localhost ~]# curl http://localhost:8080
<html><body><h1>It works!</h1></body></html>
[root@localhost ~]#
docker history image 命令可用于查看某个映像的提交日志并查看它所基于的映像层。
注意
构造映像的一种更好方法是使用 docker build 和 Dockerfile 来根据其结构编写脚本。与 Kickstart 一样,使用 Dockerfile 优势是,构件可以在将来某个时间进行重复。本节末尾列出的参考中包含有关如何进行操作的更多信息。
启动专用Docker 注册表
为了更简单地将完成映像分发到其他主机,系统管理员可以将其映像推送到 Docker 注册表。红帽提供了一个软件包docker-registry ,可用于设置组织内部使用的专用注册表。此服务器不需要牌 Docker 自身的主机上。
设备步骤很简单:
1、通过 RHEL 7 Extras 安装 docker-registry.
2、启动并启用 docker-registry 服务。
3、确保可通过系统防火墙来访问端口 5000/tcp
[root@localhost ~]# yum install docker-registry -y
Package docker-registry is obsoleted by docker-distribution, trying to install docker-distribution-2.6.2-2.git48294d9.el7.x86_64 instead
[root@localhost ~]# systemctl start docker-distribution #centos 没有registry 包,使用的是 distribution 代替
[root@localhost ~]# systemctl enable docker-distribution
Created symlink from /etc/systemd/system/multi-user.target.wants/docker-distribution.service to /usr/lib/systemd/system/docker-distribution.service.
[root@localhost ~]# firewall-cmd --permanent --add-port=5000/tcp
success
[root@localhost ~]# firewall-cmd --reload
success
将映像推送专用注册表
要将映像推送到专用存储库,必须首先使用 dokcer tag ,以存储库的主机地址和端口来标记该映像。然后可以使用 docker push 来推送该映像。
[root@localhost ~]# docker tag httpd localhost:5000/user/httpd
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest 6a9e94a15c58 17 minutes ago 341 MB
localhost:5000/user/httpd latest 6a9e94a15c58 17 minutes ago 341 MB
docker.io/centos latest 9f38484d220f 4 weeks ago 202 MB
[root@localhost ~]# docker push localhost:5000/user/httpd
The push refers to a repository [localhost:5000/user/httpd]
655b8b0c3a18: Pushed
647857b7339a: Pushed
d69483a6face: Pushed
latest: digest: sha256:6c95dd095b533de175f8631a2332786e518817d715a9e47aaec19167b99fffe9 size: 953
[root@localhost ~]#
将映像导出到文件
docker save 命令可用于将映像文件保存到未压缩的 tar 归档。然后可以将此文件复制到另一个Docker 主机,并使用 docker load 加载到其索引中。此归档包含重新构造映像所需要的所有层。
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest 6a9e94a15c58 17 minutes ago 341 MB
localhost:5000/user/httpd latest 6a9e94a15c58 17 minutes ago 341 MB
docker.io/centos latest 9f38484d220f 4 weeks ago 202 MB
[root@localhost ~]# docker save -o rhel_httpd.tar.gz localhost:5000/user/httpd
[root@localhost ~]# ll -h
total 334M
-rw-------. 1 root root 1.3K Mar 9 19:42 anaconda-ks.cfg
-rw-------. 1 root root 334M Apr 16 09:57 rhel_httpd.tar.gz
[root@localhost ~]#
清除
有以下几个命令可用于清除容器:
- docker stop container 将正常停止容器,而 docker kill container 会向其发送 kill 信号。
- docker rm container 会从本地索引中永久删除容器的文件系统映像。一旦删除,此容器便无法重新启动;必须重新创建些容器。
- docker rmi image 将从主机的索引中删除映像。它不会从注册表中删除此映像。注意不要移除主机目录可能在使用的任何映像。
- docker info 将提供一些有关 Docker 环境和当前资源消耗情况的基本信息。