vlambda博客
学习文章列表

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》Linux容器

Linux Containers

Linux 容器 是 Linux kernel (这就是为什么 < /span>经常听到这句话,Containers are Linux)。 Linux 容器在应用程序部署方面提供了很大的灵活性。事实上,不仅部署了应用程序,而且部署了整个软件堆栈。软件堆栈由应用程序本身、它的依赖项、操作系统以及在操作系统中运行的工具和进程组成。冻结完整的软件堆栈提供了巨大的可移植性。这就是为什么围绕 Docker 和 Linux 容器的这种永无止境的炒作已经持续了多年。

在本章中,我们将介绍以下概念:

  • Linux containers
  • Containers
  • Docker
  • Kubernetes

Linux Containers

Linux kernel 由几个组件和功能组成;与容器相关的如下:

  • Control groups (cgroups)
  • Namespaces
  • Security-Enhanced Linux (SELinux)

Cgroups

cgroup 功能允许限制和优先处理资源,例如 CPU、RAM、网络、文件系统等。主要目标是不超过资源——避免浪费其他流程可能需要的资源。

Namespaces

命名空间功能允许对内核资源进行分区,这样一组进程看到一组资源,而另一组进程看到一组不同的资源。该功能的工作原理是在不同的进程集中为这些资源使用相同的命名空间,但这些名称指的是不同的资源。可以存在于多个空间中的资源名称的示例(以便命名资源将被分区)是进程 ID、主机名、用户 ID、文件名以及与网络访问和进程间通信相关的一些名称。

当 Linux 系统启动时;也就是说,只创建了一个命名空间。进程和资源将加入同一个命名空间,直到创建不同的命名空间、分配给它的资源和进程加入它。

SELinux

SELinux 是 Linux 内核 的一个模块,它提供了一种通过特定策略来强制执行系统安全性的机制。

基本上,SELinux 可以限制程序访问文件和网络资源。这个想法是将程序和守护进程的权限限制在最低限度,这样就可以限制系统停止的风险。

前面的功能已经存在很多年了。命名空间于 2002 年首次发布,而 cgroups 于 2005 年由 Google 发布(cgroups 首先被命名为进程容器,然后是 cgroups)。例如,2005 年初发布的 SunSolaris 5.10 提供了对 Solaris 容器的支持。

如今,Linux 容器是新的流行词,有人认为它是一种新的虚拟化手段。

虚拟化 有完全不同的方法。虚拟化模拟;它不会直接在 CPU 上运行进程。虚拟化模拟资源;它不拥有该资源。仿真会在执行阶段产生开销,这可能仍然是高性能的,但它肯定需要更多资源。每个 VM 都有自己的专用操作系统,如下图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》Linux容器
Stack virtualization

容器化直接使用资源,完全不需要模拟器;资源越少,效率越高。不同的应用程序可以在同一台主机上运行:在内核级别隔离并由命名空间和 cgroup 隔离。内核(即操作系统)由所有容器共享,如下图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》Linux容器
Stack containerization

Containers

当我们谈论容器时,我们间接指的是两个主要概念——容器镜像和运行容器镜像。

容器映像是容器的定义,其中所有软件堆栈都作为附加层安装,如下图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》Linux容器
Layers of containers

容器映像通常由多个层组成。

第一层由基础镜像提供,它提供操作系统核心功能,以及启动所需的所有工具。团队通常通过在这些基础映像上构建自己的层来工作。用户还可以构建更高级的应用程序映像,这些映像不仅具有操作系统,还包括语言运行时、调试工具和库,如下图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》Linux容器
Example of container image layers

基本映像是从操作系统中包含的相同实用程序和库构建的。一个好的基础镜像提供了一个安全稳定的平台来构建应用程序。 Red Hat 为 Red Hat Enterprise Linux 提供基础镜像。这些图像可以像普通操作系统一样使用。用户可以根据需要为他们的应用程序定制它们,安装软件包并使服务能够像普通的红帽企业 Linux 服务器一样启动。

Red Hat 还提供了更高级的应用程序镜像,其中已经包含了额外的软件。示例包括语言运行时、数据库、中间件、存储和独立软件供应商 (ISV) 软件。这些图像中包含的软件经过测试、预配置和认证,可以开箱即用。这些预构建的映像可以轻松地从现有组件部署多层应用程序,而无需构建您自己的应用程序映像。

分层结构显着简化了修补程序,因为一个层只需要修补一次,然后使用该层构建的任何图像都将包含应用的修补程序。

像 OpenShift 这样的容器平台能够跟踪使用某个层的现有容器,如果打补丁,就会触发现有和正在运行的容器的一系列镜像构建和部署,以便将打补丁的层应用于所有容器并防止安全漏洞.

尽管可以在单个 VM 上运行每个应用程序,但所需的大量额外 VM 的操作开销和资源成本通常会使感知到的好处无效。

VM 具有为应用程序提供管理程序级别隔离的优势。然而,这是以为每个应用程序运行一个完整的操作系统为代价的,这会消耗更多的内存和 CPU 利用率。此外,VM 计算和内存容量是预定义的,当应用程序需要更多内存或计算能力时,不会提供任何弹性或消耗更多。

容器通过利用内核技术(如 cgroup、内核命名空间和 SELinux)提供隔离,这些技术已在 Google 和美国国防部经过多年的实战测试和使用,以提供应用程序隔离。

由于容器使用共享内核和容器主机,因此它们减少了容器本身所需的资源量,并且与 VM 相比更轻量级。因此,容器提供了无与伦比的敏捷性,这在 VM 中是不可行的;例如,启动一个新容器只需要几秒钟。此外,容器在 CPU 利用率和内存资源方面支持更灵活的模型,并允许资源突发模式,以便应用程序可以在需要时在定义的边界内消耗更多资源。

Linux 容器在某种程度上是一种虚拟化技术。 VM 用于虚拟化硬件,而容器用于虚拟化应用程序进程,无论它们在何处运行(裸机、虚拟化或云中)。

由于标准图像格式(Open Container Initiative)以及它们仅依赖于 Linux 的事实,容器提供了应用程序的可移植性。这使团队可以更好地控制他们部署应用程序的基础架构。容器主机为在任何基础设施上运行容器提供了共同基础,从您自己的笔记本电脑到裸机、虚拟化以及私有云和公共云。

然而,可移植性并不能保证兼容性。可移植性通常被认为意味着您可以获取容器映像并在由任何 Linux 发行版构建的任何容器主机上运行它,但这在技术上并不准确。 Linux 容器映像是文件的集合,包括特定于硬件架构和操作系统的库和二进制文件。运行容器镜像时,镜像中的二进制文件就像在普通 Linux 操作系统上一样运行。容器镜像和容器主机之间必须有兼容性。例如,您不能在 32 位主机上运行 64 位二进制文​​件。

这些不一致的范围可以从零星故障(难以调试)到完全故障,其中容器根本不会运行。更糟糕的是,不兼容会导致性能下降或安全问题。 Linux 容器的可移植性不是绝对的;使用不同的 Linux 发行版或同一 Linux 发行版的不同版本可能会导致严重的问题。

为了保证可移植性和兼容性,最佳实践是在所有容器和所有容器主机上,跨环境和基础架构运行一致的 Linux 版本和发行版。这保证了水平和垂直的兼容性。

OCI 标准容器与红帽企业 Linux 等标准容器主机结合使用时,可提供可移植且兼容的应用交付组合,确保您的应用在所有环境和基础架构中以完全相同的方式运行。

不变性也是 OCI 的主要优势之一——标准容器映像,无需像使用 VM 映像(例如 AWS 上的 AMI)那样绑定到特定的机器映像格式。这就是为什么您甚至看到 Netflix 采用容器来启用相同的 CD 工作流程,但以更便携和高效的方式。

但是,Linux 容器 一词主要与 Docker 相关。

Docker

Docker 是一款软件,可让您创建具有特定映像格式(即 Docker 映像格式)的 Linux 容器。

Docker 将 Linux 容器规模化,为开发人员和管理员提供了一个工具包。

第 4 章中,使用 Thorntail 构建微服务,第 5 章 Eclipse MicroProfile 和事务 – Narayana LRA , 我们将我们的云原生应用程序视为微服务。我们将整个应用程序分为两个前端和三个后端,还使用一个后端服务,例如 MySQL、Mongo 和 PostreSQL 数据库,这些数据库在 Docker 容器中作为持久层运行。在运行应用程序本身之前,我们必须运行数据库,然后是应用程序。

这种情况会导致整个系统的治理复杂。简化系统的第一步是尽可能地容器化。让我们首先对我们的后端微服务进行 docker 化。

football-player-microservice

以下是 football-player-microservice 的 Dockerfile:

FROM docker.io/openjdk
ADD football-player-microservice/target/football-player-microservice-thorntail.jar /opt/service.jar
ENTRYPOINT ["java","-jar","/opt/service.jar"]
CMD [""]

现在,在 Docker 文件的同一路径中,构建 Docker 映像,如下所示:

docker build -f football-player-microservice.dockerfile -t foogaro/football-player-microservice 

检查该图像是否确实在您的本地 Docker 注册表中,发出以下命令:

docker images

如果列出了刚刚构建的镜像(它应该出现在列表的顶部),则运行容器,如下所示:

docker run -it --rm=true --name="football-player-microservice" -p 8180:8180 foogaro/football-player-microservice -Dswarm.http.port=8180 -Dweb.primary.port=8180

由于连接 Postgres 数据库时出错,应用程序应该无法正常启动,如下所示:

Caused by: org.postgresql.util.PSQLException: Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.

我们需要挂钩在不同容器上运行的数据库,因此可能在不同的网络上。

为此,我们需要链接这两个容器,football-player-microservicepostgres_thorntail,以便它们能够相互通信。最好的情况是它们共享相同的网络层;这可以通过普通 Docker 以两种方式实现,如下所示:

  • Sharing the network provided by the host (that is, the server running Docker; your laptop, in the case)
  • Creating a dedicated network (a dedicated Docker network) for the two containers

第一个选项可能是最简单的,但在大型环境中,它不会扩展。另一方面,第二个选项增加了一点复杂性,但它提供了一个更具可扩展性的解决方案;而且,如果我们正在处理容器,我们想要扩展。

所以,让我们创建一个专用的 Docker 网络,如下所示:

docker network create football
c59ac3a846920891ce092868945caf316e5adbb4007715f9f0c38f8d2954583e
docker network inspect football
[
{
"Name": "football",
"Id": "c59ac3a846920891ce092868945caf316e5adbb4007715f9f0c38f8d2954583e",
"Created": "2018-10-23T10:08:03.496371735+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]

如果您已启动并运行 Postgres 容器,请停止它,然后使用 football 网络再次运行它,如下所示:

docker run --name postgres_thorntail --net="football" --rm="true" -e POSTGRES_PASSWORD=postgresPwd -e POSTGRES_DB=football_players_registry -d -p 5532:5432 postgres

如果我们再次检查网络,我们应该会看到属于它的 Postgres 实例:

docker network inspect football
[
{
"Name": "football",
"Id": "c59ac3a846920891ce092868945caf316e5adbb4007715f9f0c38f8d2954583e",
"Created": "2018-10-23T10:08:03.496371735+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {
"7a69aa8c0d700110d0ea4a49a2ee3f590f07856de4d060469758cf09fbdebf43": {
"Name": "postgres_thorntail",
"EndpointID": "0a0d0a55d5d3141babff26a2917ae8c838af4518cc842ac6da2afd1f56d8e4b7",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

现在,我们需要将我们的微服务与它用作支持服务的数据库链接起来,如下所示:

docker run -it --rm=true --net="football" --link="postgres_thorntail" --name="football-player-microservice" -p 8180:8180 foogaro/football-player-microservice -Dweb.primary.port=8180 -Dswarm.http.port=8180

如果我们再次检查网络,我们还应该看到属于它的 football-player-microservice,如下所示:

Docker network inspect football
[
{
"Name": "football",
"Id": "c59ac3a846920891ce092868945caf316e5adbb4007715f9f0c38f8d2954583e",
"Created": "2018-10-23T10:08:03.496371735+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {
"7a69aa8c0d700110d0ea4a49a2ee3f590f07856de4d060469758cf09fbdebf43": {
"Name": "postgres_thorntail",
"EndpointID": "0a0d0a55d5d3141babff26a2917ae8c838af4518cc842ac6da2afd1f56d8e4b7",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
},
"962c79c46821204345ab86a3cb40a5691140ccf100112904e678c044a68e5138": {
"Name": "football-player-microservice",
"EndpointID": "0d1232f4c3cfc3f82ddefa2bd4c0ceaf994aa6b87292341f761009c56ccbe80f",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

现在,我们确定容器之间可以相互通信,但是 football-player-microservice 应用程序需要绑定到特定 IP 和端口的数据库。

这可以通过将这些参数传递给容器作为环境变量来实现,如下:

docker run -it --rm=true --net="football" --link="postgres_thorntail" --name="football-player-microservice" -e FOOTBALL_POSTGRES_IP=172.18.0.2 -e FOOTBALL_POSTGRES_PORT=5432 -p 8180:8180 foogaro/football-player-microservice -Dweb.primary.port=8180 -Dswarm.http.port=8180

这些环境变量必须由应用程序获取;所以,代码需要稍作改动。

打开football-player-microservice应用的源码,编辑YAML文件project-defaults.yml;将 connection-url 替换为以下定义:

docker run -it --rm=true --net="football" --link="postgres_thorntail" --name="football-player-microservice" -e FOOTBALL_POSTGRES_IP=172.18.0.2 -e FOOTBALL_POSTGRES_PORT=5432 -p 8180:8180 foogaro/football-player-microservice -Dweb.primary.port=8180 -Dswarm.http.port=8180

现在,按照 再次编译应用程序 a>第 5 章 Eclipse MicroProfile 和事务 - Narayana LRA, 并重建Docker 镜像;然后,运行容器,如下:

docker run -it --rm=true --net="football" --link="postgres_thorntail" --name="football-player-microservice" -e FOOTBALL_POSTGRES_IP=172.18.0.2 -e FOOTBALL_POSTGRES_PORT=5432 -p 8180:8180 foogaro/football-player-microservice -Dweb.primary.port=8180 -Dswarm.http.port=8180

现在,让我们将 football-player-ui 前端应用程序容器化。

football-player-ui

首先,构建前端应用程序,如第5章中所述, Eclipse MicroProfile 和事务 – Narayana LRA;然后,为 football-player-ui 创建 Dockerfile,如下所示:

FROM docker.io/httpd
ADD football-player-ui/dist/football-player-ui/* /usr/local/apache2/htdocs/

现在,在 Docker 文件的同一路径中,构建 Docker 映像,如下所示:

docker build -f football-player-ui.dockerfile -t foogaro/football-player-ui 

检查该图像是否确实在您的本地 Docker 注册表中,发出以下命令:

docker images

如果列出了刚刚构建的镜像(它应该出现在列表的顶部),则运行容器,如下所示:

docker run -it --rm=true --name="football-player-ui" --net="football" -p 80:80 foogaro/football-player-ui

打开浏览器并指向 http://172.18.0.4/ 的 URL 后,应显示以下页面:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》Linux容器

正如您在前面描述 football-player-ui 应用程序的屏幕截图中看到的那样,页面是空白的,表格中没有数据。

那是因为我们还需要将前端与后端 football-player-microservice 挂钩。为了实现这样的集成,我们需要修改文件 src/app/footballPlayer/football-player.service.ts 并使用以下内容更新名为 apiUrl 的私有成员陈述:

private apiUrl = 'http://172.18.0.3:8180/footballplayer';

IP 和端口是指支持服务。重建前端、重建 Docker 映像 foogaro/football-player-ui 并重新运行容器后,整个系统现在应该可以正常运行,如下面的屏幕截图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》Linux容器

正如您在阅读本章时一定已经发现的那样,处理容器需要一些额外的工作,并且会增加系统的一些复杂性,但您肯定会从整个系统的自动化、可扩展性、一致性和完整性中受益。

Docker 本身对于开发和测试目的非常有用,但是在大型环境中,例如生产环境中,您需要自己编排所有容器,这可能非常繁琐且容易出错,这是您真正不想要的在生产环境中。

现在有提供容器编排的软件,比如Kubernetes。

Kubernetes

Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化工作负载和服务,促进声明式配置和自动化。 Kubernetes 是由 Google 开发的,Google 之后的第一个 committer 是 Red Hat。

Kubernetes 管理应用程序完整性、复制负载平衡和分配硬件资源。 Kubernetes 的一项基本任务是维护应用程序、使其工作并响应所有用户请求的工作。变得不健康或未完成您为它们决定的健康检查的应用程序可以自动刷新。 Kubernetes 提供的另一个优势是最大限度地利用硬件资源,包括内存、存储和网络带宽。应用程序可以对资源的使用有不同的限制。许多使用最少资源的应用程序可以组合在同一个硬件中,而需要资源增长的应用程序可以放置在有足够空间发展的系统中。此外,通过集群成员部署更新并在更新有问题时执行 rollback 的操作可以自动化。

Kubernetes Helm 图有助于部署预配置的应用程序。软件包管理器,例如 Debian Linux 的 APT 和 Python 的 PIP,可以帮助用户安装和配置应用程序。这在应用程序具有更多外部依赖项时特别有用。 Helm 是 Kubernetes 的一种包管理器。许多软件应用程序必须执行为多个容器并在 Kubernetes 中分组。 Helm 提供了一种机制,即所谓的图表,以便给定的软件在 Kubernetes 中作为容器组执行。您可以创建自己的 Helm 图表,如果您正在构建要在内部分发的自定义应用程序,则可能需要这样做。但是,如果您使用流行的分布模型和常见的分布模型来部署应用程序,那么很可能有人已经创建了一个可供使用的 Helm 图表;它可能会在 https://kubeapps.com/ 上发布。

Kubernetes 简化了应用程序的存储管理和其他资源。容器被设计成不可变的;用容器运送的任何东西都不应改变。但是,应用程序需要一种可靠的方式来管理外部存储卷。这是一个更复杂的场景,其中容器在应用程序的整个生命周期中运行、死亡(可能崩溃)并重新启动。 Kubernetes 具有抽象,允许电子邮件应用程序容器以与其他资源相同的解耦方法管理存储空间。您可以通过称为 volumes 的 Kubernetes 存储驱动程序访问多种归档类型的存储,从 Amazon EBS 卷到简单的 NFS 共享。大多数情况下,卷与特定的 pod 相关联,但称为 持久卷 的卷子类型可用于需要独立于任何 pod 生存的数据。容器通常需要使用所谓的秘密(PAI 令牌等凭证)。虽然此类凭证有第三方解决方案,如 Docker 机密和 HashiCorp Vault,但 Kubernetes 有自己的机制来本地管理机密。

Kubernetes 可以在混合和多云环境中实施。云的雄心勃勃的目标之一是可以在任何云环境中运行任何应用程序,或者在公共云和私有云的任何组合中运行。这不仅是为了避免供应商锁定,而且还意味着利用各个云的特定功能。 Kubernetes 提供了一组原语,众所周知的联邦来保持多个站点和多个云区域之间的同步集群。例如,它可能是应用程序的某个分布,在多个束之间保持一致。集群和多样化可以共享服务发现,以便任何人都可以访问集群后端资源。联合也可用于创建高可用性或容错的 Kubernetes 发行版,无论您是否在多个云环境中移动。

尽管如此,如前所述,Kubernetes 引入了许多新概念和抽象,这为用户提供了很高的学习曲线。出于这个原因,我们不希望您花太多时间学习命令行工具。相反,您应该直接切换到集成和使用 Kubernetes API 的企业级软件,以提供高效且易于使用的平台,例如 OpenShift。

Summary

在本章中,我们介绍了 Linux 容器,以及为什么它们在支持云架构方面如此强大和有用。

我们还描述了 Docker 是什么,以及我们如何将我们的应用程序 Docker 化并在任何地方运行它。您学习了如何创建 Docker 文件、如何构建 Docker 映像以及如何运行 Docker 容器。

最后,我们描述了 Docker 如何为平台增加一些复杂性;我们提到需要像 Kubernetes 这样的工具。

在下一章中,我们将了解如何将带有微服务的云原生应用程序迁移到 PaaS,例如 OpenShift,它利用并简化了 Kubernetes 的使用。