原文:
annas-archive.org/md5/55f0ee1b5d0f6f58bdd7da1ffd9f7954译者:飞龙
第九章:深入了解 Docker
Docker 的出现彻底改变了我们运行、部署和维护应用程序的方式。随着容器化的兴起,我们能够抽象出应用程序所依赖的许多底层基础设施和依赖项,使得跨不同环境部署和管理应用程序变得比以往更容易。然而,强大的能力也伴随着巨大的责任,我们必须理解 Docker 的内部机制,并建立良好的实践,以确保我们的应用程序是安全、可靠和高效的。
在本章中,我们将深入探讨 Docker 的细节,探索它的架构、组件和关键特性。我们还将研究一些在 Docker 之上出现的辅助项目,如 Docker Compose 和 Kubernetes,并学习如何使用它们来构建更复杂和可扩展的应用程序。在整个过程中,我们将强调与 Docker 一起工作的最佳实践,如创建高效的 Docker 镜像、管理容器和优化性能。到本章结束时,你将能够自信地在 Docker 中运行应用程序,并利用其全部功能构建强大和可扩展的系统。
本章包含以下主题:
-
Docker 的高级用例
-
Docker Compose
-
高级 Dockerfile 技术
-
Docker 编排
Docker 的高级用例
在使用 Docker 及其 CLI 时,我们需要处理许多关于容器生命周期、构建过程、数据卷和网络的事项。你可以通过使用其他工具来自动化其中的一些任务,但了解底层的工作原理仍然是很有用的。
运行公共镜像
你可以在 Docker Hub 上找到的许多公共镜像(hub.docker.com)都提供了初始化脚本,这些脚本从环境变量或挂载的文件中获取配置,写入到预定义的目录中。
最常用的镜像是使用了两种技术的数据库镜像。让我们来看看官方的Docker PostgreSQL镜像。我们将使用的镜像可以在这里找到:hub.docker.com/_/postgres。
要运行官方的 PostgreSQL Docker 镜像,你可以使用以下命令:
admin@myhome:~$ docker run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres
在这个命令中,我们有以下内容:
-
--name some-postgres给容器指定了一个名称some-postgres -
-e POSTGRES_PASSWORD=mysecretpassword为默认的 PostgreSQL 用户(postgres)设置密码 -
-d在后台运行容器;postgres指定使用的镜像
还可以通过添加POSTGRES_USER环境变量来覆盖默认用户(postgres)。其他配置环境变量可以在文档中找到。
当你使用官方 PostgreSQL 镜像时,一个非常有用的功能是通过 SQL 脚本进行数据库预填充。为此,你需要将一个本地目录与脚本绑定挂载到容器内的 /docker-entrypoint-initdb.d。有两件事需要注意:空数据目录和确保所有脚本成功完成。空数据目录是必要的,因为它将充当你可以加载 SQL 或 shell 脚本的入口点;它还可以防止数据丢失。如果任何脚本出现错误,数据库将无法启动。
对于在 Docker Hub 中运行的其他数据库,类似的功能也可以使用。
你可以使用的另一个有用的官方镜像是nginx:它可能更简单,因为你已经有一个配置好的 web 服务器,并且你需要提供内容(HTML 文件、JavaScript 或 CSS)供其提供,或者覆盖默认配置。
下面是将一个静态 HTML 网站挂载到容器的示例:
admin@myhome:~$ docker run -p 8080:80 -v /your/webpage:/usr/share/nginx/html:ro -d nginx
在这个命令中,我们有以下内容:
-
-p 8080:80:这个选项将主机上的8080端口映射到容器内的80端口。这意味着当有人访问主机上的8080端口时,它将被重定向到容器内的80端口。 -
-v /your/webpage:/usr/share/nginx/html:ro:这个选项将主机上的/your/webpage目录挂载到容器内的/usr/share/nginx/html目录。ro选项意味着挂载是只读的,这意味着容器不能修改/your/webpage目录中的文件。 -
-d:这个选项告诉 Docker 以分离模式运行容器,这意味着它将在后台运行。 -
nginx:这是将用于运行容器的 Docker 镜像的名称。在本例中,它是来自 Docker Hub 的官方 nginx 镜像。
我们可以像这样覆盖默认的 nginx 配置:
admin@myhome:~$ docker run -p 8080:80 -v ./config/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx
在这个命令中,大部分之前的选项重复出现,只有一个例外:-v ./config/nginx.conf:/etc/nginx/nginx.conf:ro。这个选项将主机上的 ./config/nginx.conf 文件挂载到容器内的 /etc/nginx/nginx.conf 文件。ro 选项表示挂载是只读的,意味着容器不能修改 nginx.conf 文件。
运行调试容器
在生产环境中运行的容器通常只有很少的工具对故障排除有帮助。更重要的是,这些容器不是以 root 用户身份运行,并且有多个安全机制来防止篡改。考虑到这一点,如果出现问题,我们该如何进入 Docker 网络进行调试呢?
这个问题的答案就是运行另一个我们可以进入的容器。这个容器会预安装一些工具,或者允许我们在运行时安装所需的工具。我们可以使用多种技术来实现这一点。
首先,我们需要一个会无限运行的进程,直到我们手动停止它。这个进程运行时,我们可以进入并使用 docker exec 命令进入正在运行的 Docker 容器中。
了解 Bash 脚本后,执行这个过程最简单的方式是创建一个 while 循环:
admin@myhome:~$ docker run -d ubuntu while true; do sleep 1; done
另一种方法是使用 sleep 程序:
admin@myhome:~$ docker run -d ubuntu sleep infinity
或者,你可以尝试读取一个特殊设备,/dev/null,它什么都不输出,以及使用tail命令:
admin@myhome:~$ docker run -d ubuntu tail -f /dev/null
最后,当这些命令之一在你正在尝试排查故障的网络中运行时,你可以在其中运行一个命令,并有效地能够从你需要调查的环境中运行命令:
admin@myhome:~$ docker exec -it container_name /bin/bash
现在让我们来看看如何清理未使用的容器。
清理未使用的容器
随着时间的推移,Docker 镜像可能会积累,尤其是当你频繁构建和实验容器时。这些镜像中的一些可能不再需要,并且它们可能占用宝贵的磁盘空间。要清理这些未使用的镜像,你可以使用 docker image prune 命令。该命令会删除所有未与容器关联的镜像,也就是所谓的悬挂镜像。
除了未使用的镜像外,可能还有一些未正确移除的容器。这些容器可以通过 docker ps -a 命令来识别。要删除特定的容器,你可以使用 docker rm <container_id> 命令,其中 <container_id> 是你想要删除的容器的标识符。如果你想删除所有已停止的容器,你可以使用 docker container prune 命令。
定期执行镜像和容器清理是一个良好的实践,有助于保持健康的 Docker 环境。这不仅可以节省磁盘空间,还可以防止与未使用资源相关的潜在安全漏洞。最好还要从容器和镜像中移除所有敏感信息,如密码和密钥。
这是一个使用 docker image prune 命令来删除悬挂镜像的示例:
admin@myhome:~$ docker image prune
Deleted Images:
deleted:
sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef
deleted:sha256:c937c4dd0c2eaf57972d4f80f55058b3685f87420a9a9fb9ef0dfe3c7c3e60bc
Total reclaimed space: 65.03MB
这是一个使用 docker container prune 命令删除所有已停止容器的示例:
admin@myhome:~$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
8c922b2d9708fcee6959af04c8f29d2d6850b3a3b3f3b966b0c360f6f30ed6c8
6d90b6cbc47dd99b2f78a086e959a1d18b7e0cf4778b823d6b0c6b0f6b64b6c64
Total reclaimed space: 0B
为了自动化这些任务,你可以使用 crontab 来安排定期清理。要编辑你的 crontab 文件,你可以使用 crontab -e 命令。以下是一个例子,展示如何在每天凌晨 3 点安排定期清理悬挂镜像:
0 3 * * * docker image prune -f
这一行由五个字段组成,字段之间由空格分隔。这些字段表示命令执行时的分钟、小时、月日、月份和星期几。在这个例子中,命令将在每天的凌晨 3 点执行。让我们详细看看每个元素:
-
第一个字段,
0,表示分钟。在这种情况下,我们希望命令在整点的 0 分钟执行。 -
第二个字段,
3,表示小时。在这个例子中,我们希望命令在凌晨 3 点执行。 -
第三个字段,
*,表示每月的天数。星号表示“任何”一天。 -
第四个字段,
*,表示月份。星号意味着“任何”一个月份。 -
第五个字段,
*,表示星期几。星号意味着“任何”一天。1表示星期一,2表示星期二,以此类推,直到7表示星期天。
下面是一个示例,演示如何安排在每周日凌晨 4 点清理停止的容器:
0 4 * * 7 docker container prune -f
-f标志用于强制删除镜像或容器,而不需要用户确认。
要列出当前用户的所有 cron 作业,你可以使用crontab -l命令。关于crontab的更多信息可以在线查找或使用man crontab命令。关于如何使用它的一篇优秀教程文章可以在 Ubuntu Linux 知识库中找到:help.ubuntu.com/community/CronHowto。
Docker 卷和绑定挂载
如前一章所述,Docker 卷和绑定挂载是两种不同的持久化 Docker 容器生成的数据的方法。卷由 Docker 管理,并存在于容器的文件系统之外。它们可以在容器之间共享和重用,即使原始容器被删除,它们也会持久存在。而绑定挂载则将主机系统上的文件或目录链接到容器中的文件或目录。绑定挂载中的数据可以从主机和容器直接访问,并且只要主机文件或目录存在,它就会持续存在。
要使用 Docker 卷,你可以在运行docker run命令时使用-v或--mount标志,并指定主机源和容器目标。例如,要创建一个卷并将其挂载到容器的/app/data,你可以运行以下命令:
admin@myhome:~$ docker run -v my_data:/app/data <image_name>
要使用绑定挂载,你可以使用相同的标志并指定主机源和容器目标,就像使用卷一样。然而,与你使用卷名称不同的是,你需要使用主机文件或目录的路径。例如,要将主机目录/host/data绑定挂载到容器的/app/data,你可以运行以下命令:
admin@myhome:~$ docker run -v /host/data:/app/data <image_name>
在使用 Docker 的绑定挂载时,你可能会遇到与挂载中文件和目录的权限问题。这是因为主机和容器的用户 ID(UIDs)和组 ID(GIDs)可能不匹配,从而导致无法访问或修改绑定挂载中的数据。
例如,如果主机文件或目录的所有者是 UID 为 1000 的用户,而容器中的 UID 不同,那么容器可能无法访问或修改绑定挂载中的数据。同样,如果组 ID 不匹配,由于组权限,容器也可能无法访问或修改数据。
为了避免这些权限问题,你可以在运行docker run命令时指定主机文件或目录的 UID 和 GID。例如,要使用 UID 和 GID 为 1000 的绑定挂载运行容器,你可以运行以下命令:
admin@myhome:~$ docker run -v /local/data:/app/data:ro,Z --user 1000:1000 <image_name>
在此示例中,:ro 标志指定绑定挂载应为只读,,Z 标志告诉 Docker 使用私有标签标记绑定挂载,以防它与其他容器交互。--user 标志将容器内运行的进程的 UID 和 GID 设置为 1000。
通过在容器中指定主机文件或目录的 UID 和 GID,可以避免 Docker 中绑定挂载的权限问题,并确保容器可以按预期访问和修改数据。
Docker 网络高级用法
Docker 提供了一种便捷的方式来管理用户定义网络中容器的网络。通过使用 Docker 网络,您可以轻松控制容器之间的通信,并将它们与主机网络隔离开来。
Docker 桥接网络是一种默认的网络配置,可以使运行在同一主机上的容器之间进行通信。它通过在主机系统上创建一个虚拟网络接口来实现容器与主机网络之间的桥接。桥接网络上的每个容器都被分配一个唯一的 IP 地址,允许它与其他容器和主机进行通信。
桥接网络彼此隔离,这意味着连接到不同桥接网络的容器无法直接通信。要在不同网络的容器之间实现通信,可以使用 Docker 服务发现机制,例如连接到特定容器 IP 地址或使用负载均衡器。
在实际中使用桥接网络,您可以使用 Docker CLI 创建一个新的桥接网络。例如,您可以使用命令 docker network create --driver bridge production-network 来创建一个名为 production-network 的新桥接网络。网络创建后,您可以使用 docker run 命令中的 --network 选项将容器连接到网络。您可以使用命令 docker run --network production-network my-image 在 production-network 网络上运行来自 my-image 镜像的容器。
除了创建新的桥接网络外,还可以将容器连接到安装 Docker 时自动创建的默认 bridge 网络。要将容器连接到默认网络,您无需在 docker run 命令中指定 --network 选项。容器将自动连接到默认 bridge 网络,并从桥接网络子网中分配 IP 地址。
现在,如果创建多个网络,默认情况下它们将被隔离,不允许它们之间进行通信。要允许两个桥接网络之间的通信,例如 production-network 和 shared-network,您需要通过连接您选择的容器到这两个网络或允许这两个网络之间的所有通信来创建网络连接。如果可能的话,后者选项不受支持。
最后一个选项是使用Docker Swarm模式和覆盖网络模式,我们将在本章的Docker 编排部分稍后讨论。
以下是将容器同时连接到两个网络的示例。首先,让我们创建一个生产网络和一个共享网络:
admin@myhome:~$ docker network create production-network
fd1144b9a9fb8cc2d9f65c913cef343ebd20a6b30c4b3ba94fdb1fb50aa1333c
admin@myhome:~$ docker network create shared-network
a544f0d39196b95d772e591b9071be38bafbfe49c0fdf54282c55e0a6ebe05ad
现在,我们可以启动一个连接到production-network的容器:
admin@myhome:~$ docker run -itd --name prod-container --network production-network alpine sh
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
8921db27df28: Pull complete
Digest:sha256:f271e74b17ced29b915d351685fd4644785c6d1559dd1f2d4189a5e851ef753a
Status: Downloaded newer image for alpine:latest
287be9c1c76bd8aa058aded284124f666d7ee76c73204f9c73136aa0329d6bb8
我们也对shared-network做同样的操作:
admin@myhome:~$ docker run -itd --name shared-container --network shared-network alpine sh
38974225686ebe9c0049147801e5bc777e552541a9f7b2eb2e681d5da9b8060b
让我们检查一下两个容器是否都在运行:
admin@myhome:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
38974225686e alpine "sh" 4 seconds ago Up 3 seconds shared-container
287be9c1c76b alpine "sh" 16 seconds ago Up 14 seconds prod-container
最后,我们还需要将prod-container连接到共享网络:
admin@myhome:~$ docker network connect shared-network prod-container
然后,我们可以进入prod-container并 ping shared-container:
admin@myhome:~$ docker exec -it prod-container /bin/sh
/ # ping shared-container
PING shared-container (172.24.0.2): 56 data bytes
64 bytes from 172.24.0.2: seq=0 ttl=64 time=0.267 ms
64 bytes from 172.24.0.2: seq=1 ttl=64 time=0.171 ms
^C
--- shared-container ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.171/0.219/0.267 ms
/ # ping prod-container
PING prod-container (172.23.0.2): 56 data bytes
64 bytes from 172.23.0.2: seq=0 ttl=64 time=0.167 ms
64 bytes from 172.23.0.2: seq=1 ttl=64 time=0.410 ms
64 bytes from 172.23.0.2: seq=2 ttl=64 time=0.108 ms
^C
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.108/0.188/0.410 ms
你可以在 Docker 官方网页上了解更多关于网络的内容:docs.docker.com/network/。
Docker 的安全特性
Docker 的核心并非旨在作为安全工具。这是在后期通过 Linux 内核特性的支持逐步构建的,且仍在开发中,更多的安全特性也在不断加入。
这个话题涉及很多内容,但我们将重点介绍四个最常用的安全特性:
-
命名空间
-
安全计算 模式(seccomp)
-
无根模式
-
Docker 签名镜像
Linux 内核命名空间
内核命名空间是 Docker 安全的重要组成部分,因为它们为容器和主机系统之间提供隔离。它们允许每个容器查看系统资源,如文件系统、网络和进程表,而不会影响主机或其他容器。这意味着运行在容器内的进程不能访问或修改主机文件系统、网络配置或进程,从而帮助保护主机系统免受恶意或不受控制的容器的威胁。
Docker 使用多个 Linux 内核命名空间为容器提供隔离环境。这些命名空间用于为进程、网络、挂载点等创建隔离的环境。
Docker 守护进程的USER命名空间将确保容器内的 root 用户与主机环境中的 root 用户处于不同的上下文。这是为了确保容器内的 root 用户与主机上的 root 用户不相同。
PID命名空间隔离容器之间的进程 ID。每个容器只能看到自己的进程集,且与其他容器和主机隔离。
NET命名空间的功能是隔离每个容器的网络堆栈,使得每个容器都有自己的虚拟网络堆栈,包括网络设备、IP 地址和路由。
IPC命名空间处理容器之间的进程间通信(IPC)资源。每个容器都有自己独立的 IPC 资源,如 System V IPC 对象、信号量和消息队列。
UTS命名空间用于容器的主机名和域名隔离。在这里,每个容器都有自己的主机名和域名,这些不会影响其他容器或主机。
最后,MNT命名空间隔离了每个容器的挂载点。这意味着每个容器都有一个私有的文件系统层级,拥有自己的根文件系统、挂载的文件系统和绑定挂载。
通过使用这些命名空间,Docker 容器彼此之间以及与主机之间都被隔离,这有助于确保容器和主机系统的安全性。
USER命名空间是最难使用的,因为它需要特别的 UID 和 GID 映射配置。默认情况下并未启用它,因为与主机共享PID或NET命名空间(–pid=host或–network=host)是不可能的。此外,使用docker run时如果没有指定–userns=host(从而禁用USER命名空间的隔离),将无法使用–privileged mode标志。之前列出的其他命名空间大多无需任何特别配置即可生效。
Seccomp
Seccomp,即安全计算模式,是一个 Linux 内核功能,允许进程指定其允许进行的系统调用。这使得可以限制容器能够执行的系统调用类型,从而通过减少容器逃逸或特权提升的风险来提高主机系统的安全性。
当进程指定其 seccomp 配置文件时,Linux 内核会过滤传入的系统调用,并仅允许在配置文件中指定的那些调用。这意味着,即使攻击者获得了容器的访问权限,他们也会受到能执行的操作类型的限制,从而减少攻击的影响。
要为容器创建 seccomp 配置文件,可以在docker run命令中使用seccomp 配置选项。这允许你在启动容器时指定要使用的 seccomp 配置文件。
创建 seccomp 配置文件有两种主要方式:使用预定义的配置文件或创建自定义配置文件。预定义配置文件适用于常见的使用场景,可以在docker run命令中轻松指定。例如,默认配置文件允许所有系统调用,而限制配置文件仅允许一组被认为对大多数使用场景安全的系统调用。
要创建自定义的 seccomp 配置文件,可以使用Podman(podman.io/blogs/2019/10/15/generate-seccomp-profiles.xhtml)或seccomp-gen(github.com/blacktop/seccomp-gen)工具。这两个工具自动识别容器在生产环境中使用时会进行的系统调用,并生成一个 JSON 文件,可用作 seccomp 配置文件。
Seccomp 并不能保证安全。了解你的应用所需的系统调用并确保它们在 seccomp 配置文件中被允许,是非常重要的。
以下是一个 seccomp 配置文件示例,它允许运行 Web 服务器应用的容器使用有限的系统调用:
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
{
"name": "accept",
"action": "SCMP_ACT_ALLOW"
},
{
"name": "bind",
"action": "SCMP_ACT_ALLOW"
},
{
"name": "connect",
"action": "SCMP_ACT_ALLOW"
},
{
"name": "listen",
"action": "SCMP_ACT_ALLOW"
},
{
"name": "sendto",
"action": "SCMP_ACT_ALLOW"
},
{
"name": "recvfrom",
"action": "SCMP_ACT_ALLOW"
},
{
"name": "read",
"action": "SCMP_ACT_ALLOW"
},
{
"name": "write",
"action": "SCMP_ACT_ALLOW"
}
]
}
在这个示例中,defaultAction被设置为SCMP_ACT_ALLOW,意味着所有未在syscalls数组中特别列出的系统调用都会被允许。为了阻止所有未定义的调用,你可以将默认操作设置为SCMP_ACT_ERRNO。所有可用的操作都在seccomp_rule_add过滤器规格的在线手册中有所描述:man7.org/linux/man-pages/man3/seccomp_rule_add.3.xhtml。
syscalls数组列出了应该允许容器使用的系统调用,并为每个调用指定了要采取的行动(在这种情况下,所有调用都被允许)。该配置文件仅允许 Web 服务器运行所需的系统调用,阻止所有其他系统调用,从而提高容器的安全性。
有关系统调用的更多信息,请参阅:docs.docker.com/engine/security/seccomp/。
无根模式
Docker 无根模式是一个功能,允许用户在不以 root 用户身份运行 Docker 守护进程的情况下运行 Docker 容器。该模式通过减少主机系统的攻击面并最小化特权升级的风险,提供了额外的安全层。
让我们在 Ubuntu Linux 或 Debian Linux 上设置一个无根 Docker 守护进程。首先,确保你已从官方 Docker 软件包仓库安装了 Docker,而不是从 Ubuntu/Debian 软件包中安装:
admin@myhome:~$ sudo apt-get install -y -qq apt-transport-https ca-certificates curl
admin@myhome:~$ sudo mkdir -p /etc/apt/keyrings && sudo chmod -R 0755 /etc/apt/keyrings
admin@myhome:~$ curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | sudo gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
admin@myhome:~$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
admin@myhome:~$ echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" | sudo tee /etc/apt/sources.list.d/docker.list
admin@myhome:~$ sudo apt-get update
admin@myhome:~$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-scan-plugin docker-compose-plugin docker-ce-rootless-extras docker-buildx-plugin
docker-ce-rootless-extras将在你的/usr/bin目录中安装一个名为dockerd-rootless-setuptool.sh的脚本,它将自动化整个过程:
admin@myhome~$ dockerd-rootless-setuptool.sh --help
Usage: /usr/bin/dockerd-rootless-setuptool.sh [OPTIONS] COMMAND
A setup tool for Rootless Docker (dockerd-rootless.sh).
Documentation: https://docs.docker.com/go/rootless/
Options:
-f, --force Ignore rootful Docker (/var/run/docker.sock)
--skip-iptables Ignore missing iptables
Commands:
check Check prerequisites
install Install systemd unit (if systemd is available) and show how to manage the service
uninstall Uninstall systemd unit
要运行此脚本,我们需要一个具有配置环境的非 root 用户,以便能够运行 Docker 守护进程。让我们首先创建一个dockeruser用户:
admin@myhome~$ sudo adduser dockeruser
Adding user `dockeruser' ...
Adding new group `dockeruser' (1001) ...
Adding new user `dockeruser' (1001) with group `dockeruser' ...
Creating home directory `/home/dockeruser' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for dockeruser
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] y
在继续之前,我们还需要创建一个 UID 映射配置。为此,我们需要安装uidmap软件包,并创建/etc/subuid和/etc/subgid配置文件:
admin@myhome~$ sudo apt install -y uidmap
admin@myhome~$ echo "dockeruser:100000:65536" | sudo tee /etc/subuid
admin@myhome~$ echo "dockeruser:100000:65536" | sudo tee /etc/subgid
以dockeruser身份登录并运行dockerd-rootless-setuptool.sh脚本:
admin@myhome~$ sudo -i -u dockeruser
确保设置了environment XDG_RUNTIME_DIR,并且 systemd 可以从dockeruser读取环境变量:
$ export XDG_RUNTIME_DIR=/run/user/$UID
$ echo 'export XDG_RUNTIME_DIR=/run/user/$UID' >> ~/.bashrc
$ systemctl --user show-environment
HOME=/home/dockeruser
LANG=en_US.UTF-8
LOGNAME=dockeruser
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin
SHELL=/bin/bash
SYSTEMD_EXEC_PID=720
USER=dockeruser
XDG_RUNTIME_DIR=/run/user/1001
XDG_DATA_DIRS=/usr/local/share/:/usr/share/:/var/lib/snapd/desktop
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus
现在,你可以使用dockerd-rootless-setuptool.sh脚本安装无根(rootless)Docker(部分输出已被截断以提高可读性):
$ dockerd-rootless-setuptool.sh install
[INFO] Creating [condensed for brevity]
Active: active (running) since Fri 2023-02-17 14:19:04 UTC; 3s ago
+ DOCKER_HOST=unix:///run/user/1001/docker.sock /usr/bin/docker version
Client: Docker Engine - Community
Version: 23.0.1
[condensed for brevity]
Server: Docker Engine - Community
Engine:
Version: 23.0.1
[condensed for brevity]
rootlesskit:
Version: 1.1.0
[condensed for brevity]
+ systemctl --user enable docker.service
Created symlink /home/dockeruser/.config/systemd/user/default.target.wants/docker.service → /home/dockeruser/.config/systemd/user/docker.service.
[INFO] Installed docker.service successfully.
现在,让我们验证一下能否使用 Docker 无根守护进程:
dockeruser@vagrant:~$ export DOCKER_HOST=unix:///run/user/1001/docker.sock
dockeruser@vagrant:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
此时,我们已经有一个以dockeruser系统用户身份运行的 Docker 守护进程,而不是 root 用户。我们将能够像在标准配置中一样运行所有需要的服务。某些情况,如 Docker 中的 Docker 配置,需要进一步的配置。
有关无根模式的更多详细信息,请访问docs.docker.com/engine/security/rootless/。
Docker 签名镜像
Docker 签名镜像是一种安全措施,它确保用户知道 Docker 镜像来自受信任的源,并且没有被篡改。Docker 使用数字签名对镜像进行签名,Docker 引擎可以验证签名,以确保镜像与发布者签名时完全一致。
Docker 签名镜像可以通过检查签名者的公钥来验证,公钥可以从受信任的注册中心(如 Docker Hub)获取。如果镜像有效,Docker 会允许你在本地拉取并运行该镜像。
签名 Docker 镜像的第一步是生成一个签名密钥。可以使用 docker trust key generate 命令:
admin@myhome:~/$ docker trust key generate devops
Generating key for devops...
Enter passphrase for new devops key with ID 6b6b768:
Repeat passphrase for new devops key with ID 6b6b768:
Successfully generated and loaded private key. Corresponding public key available: /home/admin/devops.pub
记得使用强密码来保护密钥免受访问。私钥将保存在你的主目录中——例如,~/.docker/trust/private。文件的名称会被哈希化。
一旦你生成了签名密钥,下一步就是初始化镜像的信任元数据。信任元数据包含关于镜像的信息,包括授权签名镜像的密钥列表。要初始化信任元数据,可以使用 docker trust signer add 命令。请注意,你需要登录到所使用的 Docker 注册中心(通过 docker login 命令):
admin@myhome:~/$ docker trust signer add --key devops.pub private-registry.mycompany.tld/registries/pythonapps
Adding signer "devops" to private-registry.mycompany.tld/registries/pythonapps/my-python-app...
Initializing signed repository for private-registry.mycompany.tld/registries/pythonapps/my-python-app...
You are about to create a new root signing key passphrase. This passphrase
will be used to protect the most sensitive key in your signing system. Please
choose a long, complex passphrase and be careful to keep the password and the
key file itself secure and backed up. It is highly recommended that you use a
password manager to generate the passphrase and keep it safe. There will be no
way to recover this key. You can find the key in your config directory.
Enter passphrase for new root key with ID a23d653:
Repeat passphrase for new root key with ID a23d653:
Enter passphrase for new repository key with ID de78215:
Repeat passphrase for new repository key with ID de78215:
Successfully initialized "private-registry.mycompany.tld/registries/pythonapps/my-python-app"
Successfully added signer: devops to private-registry.mycompany.tld/registries/pythonapps/my-python-app
你可以在成功构建 Docker 镜像并用注册中心名称进行标签标记后,使用 docker trust sign 命令对镜像进行签名。此命令会使用信任元数据中的授权密钥对镜像进行签名,并将该信息连同你的 Docker 镜像一起推送到注册中心:
admin@myhome:~/$ docker trust sign private-registry.mycompany.tld/registries/pythonapps/my-python-app:2.9BETA
Signing and pushing trust data for local image private-registry.mycompany.tld/registries/pythonapps/my-python-app:2.9BETA, may overwrite remote trust data
The push refers to repository [private-registry.mycompany.tld/registries/pythonapps]
c5ff2d88f679: Mounted from library/ubuntu
latest: digest:sha256:41c1003bfccce22a81a49062ddb088ea6478eabea1457430e6235828298593e6 size: 529
Signing and pushing trust metadata
Enter passphrase for devops key with ID 6b6b768:
Successfully signed private-registry.mycompany.tld/registries/pythonapps/my-python-app:2.9BETA
要验证你的 Docker 镜像是否已经签名,并且使用了哪个密钥,你可以使用 docker trust inspect 命令:
admin@myhome:~/$ docker trust inspect --pretty private-registry.mycompany.tld/registries/pythonapps/my-python-app:2.9BETA
Signatures for private-registry.mycompany.tld/registries/pythonapps/my-python-app:2.9BETA
SIGNED TAG DIGEST SIGNERS
latest 41c1003bfccce22a81a49062ddb088ea6478eabea1457430e6235828298593e6 devops
List of signers and their keys for private-registry.mycompany.tld/registries/pythonapps/my-python-app:2.9BETA
SIGNER KEYS
devops 6b6b7688a444
Administrative keys for private-registry.mycompany.tld/registries/pythonapps/my-python-app:2.9BETA
Repository Key: de782153295086a2f13e432db342c879d8d8d9fdd55a77f685b79075a44a5c37
Root Key: c6d5d339c75b77121439a97e893bc68a804368a48d4fd167d4d9ba0114a7336b
将 DOCKER_CONTENT_TRUST 环境变量设置为 1。这样将防止 Docker 下载未签名和未验证的镜像到本地存储。
关于 DCT 的更详细信息可以在官方站点找到:docs.docker.com/engine/security/trust/。
Docker 用于 CI/CD 管道集成
持续集成(CI)和 持续部署(CD)是流行的软件开发实践,旨在确保软件开发过程高效流畅,并保持代码质量。
CI 指的是在共享代码库中自动构建和测试代码更改的实践。CD 是 CI 之后的下一步,其中代码更改会自动部署到生产或暂存环境中。
Docker 是在 CI/CD 管道中广泛使用的工具,它提供了一种高效的方式来打包和分发应用程序。在这一小节中,我们将展示如何使用 GitHub Actions 构建并将 Docker 镜像推送到 AWS Elastic Container Registry(ECR)。
让我们来看一个示例,展示如何设置 GitHub Action 来构建并将 Docker 镜像推送到 AWS ECR。
通过在你的仓库的 .github/workflows 目录中创建一个名为 main.yml 的新文件来创建一个新的 GitHub Actions 工作流。添加并推送到主分支后,它将在每次该分支有新的推送时可用并被触发。
在main.yml文件中,定义工作流的步骤,如下所示:
name: Build and Push Docker Image
on:
push:
branches:
- main
env:
AWS_REGION: eu-central-1
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Build Docker image
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ env.AWS_REGION }}/my-image:${{ env.GITHUB_SHA }}
- name: Push Docker image to AWS ECR
uses: aws-actions/amazon-ecr-push-action@v1
with:
region: ${{ env.AWS_REGION }}
registry-url: ${{ env.AWS_REGISTRY_URL }}
tags: ${{ env.AWS_REGION }}/my-image:${{ env.GITHUB_SHA }}
用你的特定值替换 AWS_REGION 和 AWS_REGISTRY_URL 环境变量。你还应该将 my-image 替换为你 Docker 镜像的名称。
在你的 GitHub 仓库设置中,创建两个密钥,命名为 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY,并使用具有推送权限的 AWS 凭证。或者,你可以使用你自己的运行器和附加到运行器的 AWS IAM 角色,或 GitHub OIDC,这将通过 AWS 账户进行身份验证。你可以在这里找到相关文档:docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services。
完成这些步骤后,每当你将代码更改推送到主分支时,你的 GitHub Action 将自动构建并推送 Docker 镜像到 AWS ECR。推送之后,你可以触发服务器端的另一个过程,评估并将新 Docker 镜像部署到你的某个环境中,而无需进一步的手动操作。这有助于简化你的 CI/CD 管道,并确保你的代码更改可以放心地部署到生产环境。
也可以以类似的方式将相同的管道集成到 GitLab 或其他 CI/CD 工具中。
在本节中,我们学习了一些容器的非常见使用场景,如无根模式、受保护计算模式、网络高级用例以及如何启动调试容器。在下一节中,我们将进一步集中讨论如何自动化设置 Docker 容器的过程,并探讨如何比手动逐个启动容器更好地进行编排。
Docker Compose
Docker Compose 是一个控制台工具,用于通过一个命令运行多个容器。它提供了一种简便的方式来管理和协调多个容器,使构建、测试和部署复杂应用变得更加容易。使用 Docker Compose,你可以在一个 YAML 文件中定义应用程序的服务、网络和卷,然后通过命令行启动和停止所有服务。
要使用 Docker Compose,首先需要在 docker-compose.yml 文件中定义应用程序的服务。该文件应包括有关你希望运行的服务、它们的配置以及它们如何连接的信息。文件还应指定每个服务使用的 Docker 镜像。
docker-compose.yaml 文件是一个核心配置文件,用于 Docker Compose 管理应用程序的部署和运行。它采用 YAML 语法编写。
docker-compose.yaml 文件的结构分为几个部分,每个部分定义部署的不同方面。第一部分 version 指定了使用的 Docker Compose 文件格式的版本。第二部分 services 定义了组成应用程序的服务,包括它们的镜像名称、环境变量、端口和其他配置选项。
services 部分是 docker-compose.yaml 文件中最重要的部分,因为它定义了应用程序的构建、运行和连接方式。每个服务通过其自己的键值对集合来定义其配置选项。例如,image 键用于指定要用于服务的 Docker 镜像的名称,而 ports 键用于指定服务的端口映射。
docker-compose.yaml 文件还可以包含其他部分,比如 volumes 和 networks,这些部分允许您为应用程序定义共享数据存储和网络配置。总体来说,docker-compose.yaml 文件提供了一种集中式、声明式的方式来定义、配置和运行使用 Docker Compose 的多容器应用程序。借助其简单的语法和强大的功能,它是简化复杂应用程序开发和部署的关键工具。
环境变量是键值对,允许您在运行服务时传递配置信息。在 docker-compose.yaml 文件中,可以使用服务定义中的环境键来指定环境变量。
在 docker-compose.yaml 文件中指定环境变量的一种方法是在环境部分中简单地列出它们作为键值对。以下是一个例子:
version: '3'
services:
db:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: example_db
除了直接在 docker-compose.yaml 文件中指定环境变量之外,还可以将它们存储在外部文件中,并在 docker-compose.yaml 文件中使用 env_file 键引用该文件。以下是一个例子:
version: '3'
services:
db:
image: mariadb
env_file:
- db.env
db.env 文件的内容可能如下所示:
MYSQL_ROOT_PASSWORD=example
MYSQL_DATABASE=example_db
通过使用外部的 env_file 键,您可以将敏感信息与 docker-compose.yaml 文件分开,并在不同的环境中轻松管理环境变量。
举例来说,考虑一个 MariaDB Docker 镜像。MariaDB 镜像需要设置几个环境变量来配置数据库,例如 MYSQL_ROOT_PASSWORD 用于 root 密码,MYSQL_DATABASE 用于默认数据库的名称,以及其他变量。这些环境变量可以在 docker-compose.yaml 文件中定义以配置 MariaDB 服务。
让我们看一个使用 Docker Compose 设置 nginx 容器、PHP-FPM 容器、WordPress 容器和 MySQL 容器的例子。我们将在 docker-compose.yml 文件中定义我们的服务,并通过注释将其分解成较小的块:
version: '3'
前一行定义了 Docker Compose 文件语法的版本。接下来,我们将定义所有要运行并与彼此交互的 Docker 镜像:
services:
web:
image: nginx:latest
depends_on:
- wordpress
ports:
- 80:80
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- wordpress:/var/www/html
networks:
- wordpress
这定义了我们应用程序栈中名为web的组件。它将使用来自 Docker Hub 的nginx镜像,标签为latest。以下是一些其他重要设置:
-
depends_on: 这告诉 Docker Compose 在wordpress服务之后启动此组件。 -
ports: 这将您的主机端口转发到 Docker 端口;在这种情况下,它将在计算机上打开端口80并将所有传入流量转发到 Docker 镜像中的相同端口,就像在命令行中启动单个 Docker 容器时所做的-p设置一样。 -
volumes: 这个设置等同于 Docker 命令行工具中的-v选项,因此它将从本地目录将一个nginx.conf文件挂载到 Docker 镜像内的/etc/nginx/conf.d/default.conf文件。 -
wordpress:/var/www/html: 这一行将一个名为wordpress的 Docker 卷挂载到 Docker 镜像内部的目录。卷将在前面定义。 -
networks: 在这里,我们将此服务连接到名为wordpress的 Docker 网络,其定义如下:
wordpress:
image: wordpress:php8.2-fpm-alpine
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: example_user
WORDPRESS_DB_NAME: example_database
WORDPRESS_DB_PASSWORD: example_password
restart: unless-stopped
volumes:
- wordpress:/var/www/html
networks:
- wordpress
前面的服务与web服务非常相似,具有以下附加项:
-
environment: 这定义了 Docker 镜像内部存在的环境变量。 -
restart: 这配置了服务,使其在某些原因导致进程停止工作时自动重启。如果我们手动停止了该服务,Docker Compose 将不会尝试重新启动它。 -
depends_on: 该服务器只会在db服务启动后才会启动。
让我们看看db服务:
db:
image: mariadb:10.4
environment:
MYSQL_ROOT_PASSWORD: example_password
MYSQL_DATABASE: example_database
MYSQL_USER: example_user
MYSQL_PASSWORD: example_password
restart: unless-stopped
volumes:
- dbdata:/var/lib/mysql
networks:
- wordpress
此服务正在设置 MariaDB 数据库,以便存储 WordPress 数据。请注意,我们可以为 MariaDB 或 WordPress 镜像使用的所有环境变量都记录在它们各自的 Docker Hub 页面上:
volumes:
wordpress:
dbdata:
在这里,我们正在定义用于 WordPress 和 MariaDB 的 Docker 卷。这些是存储在本地的常规 Docker 卷,但通过安装 Docker 引擎插件,可以将它们作为分布式文件系统(如 GlusterFS 或 MooseFS)分发:
networks:
wordpress:
name: wordpress
driver: bridge
最后,我们定义了一个wordpress网络,使用bridge驱动程序,允许所有前述服务之间的通信,并与在其上运行的 Docker 镜像隔离开来。
在前面的示例中,除了本节已经涵盖的选项之外,还有一个服务依赖项(depends_on),这将允许我们强制容器启动的顺序。
我们定义的两个卷(wordpress和dbdata)用于数据持久化。wordpress卷用于托管所有 WordPress 文件,并且它也被挂载到运行 nginx Web 服务器的 web 容器上。这样,Web 服务器将能够提供静态文件,如 CSS、图像和 JavaScript,同时将请求转发到 PHP-FPM 服务器。
这是 nginx 配置,它使用 fastcgi 连接到运行 PHP-FPM 守护进程的 WordPress 容器:
server {
listen 80;
listen [::]:80;
index index.php index.htm index.xhtml;
server_name _;
error_log /dev/stderr;
access_log /dev/stdout;
root /var/www/html;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_pass wordpress:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
}
}
使用这个 docker-compose.yml 文件,你可以通过 docker-compose up 和 docker-compose down 命令分别启动和停止文件中定义的所有服务。当你运行 docker-compose up 时,Docker 将下载必要的镜像并启动容器,你就可以在 localhost 访问你的 WordPress 网站。
docker-compose 是一个非常有用的工具,用于以简单且可重复的方式运行需要多个服务的应用程序。它通常在本地开发时运行应用程序时使用,但一些组织决定在生产系统中使用 docker-compose,在这种情况下它也能发挥作用。
如果你能使用现成的 Docker 镜像进行本地开发或生产环境,实际上是极为罕见的。使用公共镜像作为自定义的基础是所有使用 Docker 的组织普遍采用的做法。考虑到这一点,在接下来的章节中,我们将学习如何使用多阶段构建构建 Docker 镜像,以及如何正确使用每个 Dockerfile 命令。
高级 Dockerfile 技巧
Dockerfile 用于定义应用程序在 Docker 容器内应如何构建。我们在 第八章 中介绍了大部分可用命令。在这里,我们将介绍一些更高级的技术,例如多阶段构建或不太常见的 ADD 命令用法。
多阶段构建
多阶段构建是 Docker 的一个功能,允许你使用多个 Docker 镜像创建一个最终的单一镜像。通过创建多个阶段,你可以将构建过程分为不同的步骤,从而减少最终镜像的大小。多阶段构建在构建需要多个依赖的复杂应用程序时特别有用,因为它允许开发人员将必要的依赖项放在一个阶段,而将应用程序本身放在另一个阶段。
使用多阶段构建来构建 Golang 应用程序的一个例子涉及创建两个阶段:一个用于构建应用程序,另一个用于运行它。在第一个阶段,Dockerfile 拉取必要的依赖项并编译应用程序代码。在第二个阶段,仅从第一个阶段复制已编译的二进制文件,从而减少最终镜像的大小。以下是一个 Golang 应用程序的 Dockerfile 示例:
# Build stage
FROM golang:alpine AS build
RUN apk add --no-cache git
WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o /go/bin/app
# Final stage
FROM alpine
COPY --from=build /go/bin/app /go/bin/app
EXPOSE 8080
ENTRYPOINT ["/go/bin/app"]
在上述示例中,Dockerfile 创建了两个阶段。第一个阶段使用 golang:alpine 镜像并安装必要的依赖项。然后,它编译应用程序并将二进制文件放置在 /go/bin/app 目录下。第二个阶段使用更小的 Alpine 镜像,并将第一个阶段中的二进制文件复制到 /go/bin/app 目录下。最后,它将入口点设置为 /go/bin/app。
ADD 命令的用法示例
Dockerfile 中的 ADD 命令用于将文件或目录添加到 Docker 镜像中。它的工作方式与 COPY 相同,但具有一些额外的功能。我们之前已经讨论过基本的用法,但还有其他用例。
第二种用法允许你即时解压 ZIP 或 TAR 和 gzip 工具压缩的文件。在将压缩文件添加到镜像时,文件将被解压,所有文件将被提取到目标文件夹中。以下是一个示例:
ADD my-tar-file.tar.gz /app
使用 ADD 命令的第三种方式是从 URL 将远程文件复制到 Docker 镜像中。例如,要从 URL yourdomain.tld/configurations/nginx.conf 下载名为 file.txt 的文件并将其复制到 Docker 镜像中的 nginx 配置目录 /etc/nginx,可以使用以下 ADD 命令:
ADD https://yourdomain.tld/configurations/nginx.conf /etc/nginx/nginx.conf
你也可以使用 Git 仓库来添加代码:
ADD --keep-git-dir=true https://github.com/your-user-or-organization/some-repo.git#main /app/code
要通过 SSH 克隆 Git 仓库,你需要允许 Docker 内的 ssh 命令访问拥有访问你要克隆仓库的私钥。你可以通过在多阶段构建中添加私钥,并在克隆仓库的阶段结束时将其移除来实现这一点。如果可能的话,一般不推荐这样做。你可以通过使用 Docker secrets 并在构建时挂载该 secret 来更安全地实现。
下面是使用 ARG 配合私钥的示例:
ARG SSH_PRIVATE_KEY
RUN mkdir /root/.ssh/
RUN echo "${SSH_PRIVATE_KEY}" > /root/.ssh/id_rsa
# make sure your domain is accepted
RUN touch /root/.ssh/known_hosts
RUN ssh-keyscan gitlab.com >> /root/.ssh/known_hosts
RUN git clone git@gitlab.com:your-user/your-repo.git
下面是使用 Docker secret 和挂载的示例:
FROM python:3.9-alpine
WORKDIR /app
RUN --mount=type=secret,id=ssh_id_rsa,dst=~/id_rsa chmod 400 ~/id_rsa \
&& ssh-agent bash -c 'ssh-add ~/id_rsa; git clone git@gitlab.com:your-user/your-repo.git' && rm -f ~/id_rsa
# Rest of the build process follows…
在前面的示例中,我们假设你的私钥没有密码保护,并且你的密钥保存在 ssh_id_rsa 文件中。
使用 ADD 命令的最后一种方式是从主机机器中提取 TAR 压缩包并将其内容复制到 Docker 镜像中。例如,要从主机机器中提取一个名为 data.tar.gz 的 TAR 压缩包并将其内容复制到 Docker 镜像中的 /data 目录,可以使用以下 ADD 命令:
ADD data.tar.gz /data/
Secrets 管理
Docker secrets 管理是构建安全可靠的容器化应用程序的一个重要方面。
Secrets 是应用程序需要运行的敏感信息,但不应暴露给未授权的用户或进程。Secrets 的例子包括密码、API 密钥、SSL 证书以及其他身份验证或授权令牌。这些 secrets 通常在应用程序运行时需要,但如果将其以明文形式存储在代码或配置文件中,则存在安全风险。
保护 secrets 对于确保应用程序的安全性和可靠性至关重要。泄露 secrets 可能导致数据泄露、服务中断和其他安全事件。
在基本的 Docker 设置中,您只能通过环境变量将密钥提供给 Docker 镜像,正如我们在第八章中介绍的那样。Docker 还提供了一个内置的密钥管理机制,可以安全地存储和管理密钥。但是,它仅在启用 Swarm 模式时可用(我们将在本章的Docker 编排部分进一步探讨 Swarm)。
要使密钥可用于运行在 Docker 中的应用程序,您可以使用docker secret create命令。例如,要为 MySQL 数据库密码创建一个密钥,您可以使用以下命令:
admin@myhome:~/$ echo "mysecretpassword" | docker secret create mysql_password -
此命令创建了一个名为mysql_password的密钥,值为mysecretpassword。
要在服务中使用密钥,您需要在服务配置文件中定义该密钥。例如,要在服务中使用mysql_password密钥,您可以在docker-compose.yml文件中定义它,如下所示:
version: '3'
services:
db:
image: mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_password
secrets:
- mysql_password
volumes:
- db_data:/var/lib/mysql
secrets:
mysql_password:
external: true
volumes:
db_data:
在此配置文件中,mysql_password密钥定义在secrets部分,且MYSQL_ROOT_PASSWORD_FILE环境变量设置为密钥文件的路径,即/run/secrets/mysql_password。
要部署服务,您可以使用docker stack deploy命令。例如,要部署在docker-compose.yml文件中定义的服务,您可以使用以下命令:
admin@myhome:~/$ docker stack deploy -c docker-compose.yml myapp
从安全角度来看,小心处理密钥至关重要。最常见的错误是将密钥直接放入 Docker 镜像、环境文件或提交到 Git 仓库的应用程序配置文件中。虽然已有现成的应急措施防止用户这么做(如 GitHub 中的 Dependabot),但如果它们失败,之后从 Git 历史中删除这些密钥将非常困难。
在这一部分中,我们介绍了如何处理构建容器的不同方面以及高级构建技巧。掌握这些知识并使用 Docker Compose,您将能够以相当程度的自动化构建和运行您的应用程序。如果您有 10 个这样的应用程序呢?100 个?甚至更多呢?
在接下来的部分,我们将深入探讨集群,它将进一步自动化操作,并将您的应用程序同时部署到多个主机。
Docker 编排
在容器化的世界中,编排是自动化部署、管理和扩展应用程序到多个主机的过程。编排解决方案通过提供一个抽象层,帮助简化容器化应用程序的管理,提升可用性,并改善可扩展性,使您可以在更高层次上管理容器,而不是手动管理每个容器。
Docker Swarm 是一个原生的 Docker 集群和编排工具,允许你创建并管理 Docker 节点的集群,使用户能够在多个主机上部署和管理 Docker 容器。Docker Swarm 是一个易于使用的解决方案,并且与 Docker 一起内置,成为了那些已经熟悉 Docker 的用户的热门选择。
Kubernetes 是一个开源容器编排平台,最初由 Google 开发。Kubernetes 允许你在多个主机上部署、扩展和管理容器化应用,同时提供了自愈、自动发布和回滚等高级功能。Kubernetes 是目前最流行的编排解决方案之一,并广泛应用于生产环境。
OpenShift 是一个建立在 Kubernetes 基础上的容器应用平台,由 Red Hat 开发。这个平台提供了一个完整的解决方案,用于部署、管理和扩展容器化应用,附加功能包括内建的 CI/CD 流水线、集成监控和自动扩展。OpenShift 设计为企业级平台,具备多租户支持和基于角色的访问控制(RBAC)等功能,是需要管理复杂容器化环境的大型组织的热门选择。
市场上有各种各样的编排解决方案,每种方案都有其优缺点。选择使用哪种方案最终取决于你的具体需求,但 Docker Swarm、Kubernetes 和 OpenShift 都是提供强大且可靠的容器化应用编排功能的热门选择。
Docker Swarm
Docker Swarm 是一个为 Docker 容器提供的原生集群和编排解决方案。它提供了一种简单而强大的方式来管理和扩展跨主机的 Docker 化应用。通过 Docker Swarm,用户可以创建并管理一个由 Docker 节点组成的集群,这些节点作为一个虚拟系统共同工作。
Docker Swarm 的基本组件如下:
-
节点:这些是构成 Swarm 的 Docker 主机。节点可以是运行 Docker 守护进程的物理或虚拟机,并可以根据需要加入或退出 Swarm。
-
服务:这些是在 Swarm 上运行的应用程序。服务是一个可扩展的工作单元,定义了应用程序应该运行多少个副本,以及如何在 Swarm 上部署和管理它们。
-
管理节点:这些节点负责管理 Swarm 状态并协调服务的部署。管理节点负责维持服务的期望状态,确保它们按预期运行。
-
工作节点:这些节点运行实际的容器。工作节点从管理节点接收指令,并运行所需的服务副本。
-
覆盖网络:这些是允许服务相互通信的网络,无论它们运行在哪个节点上。覆盖网络提供了一个跨越整个 Swarm 的透明网络。
Docker Swarm 提供了一种简单且易于使用的方式来管理容器化应用程序。它与 Docker 生态系统紧密集成,为 Docker 用户提供了熟悉的界面。借助其内置的服务发现、负载均衡、滚动更新和扩展功能,Docker Swarm 是刚开始使用容器编排的组织的热门选择。
要初始化 Docker Swarm 模式并将两个工作节点添加到集群中,你需要初始化 Swarm 模式:
admin@myhome:~/$ docker swarm init
Swarm initialized: current node (i050z7b0tjoew7hxlz419cd8l) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-0hu2dmht259tb4skyetrpzl2qhxgeddij3bc1wof3jxh7febmd-6pzkhrh4ak345m8022hauviil 10.0.2.15:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
这将创建一个新的 Swarm,并将当前节点设置为 Swarm 管理器。
一旦 Swarm 初始化完成,你可以将工作节点添加到集群中。为此,你需要在每个工作节点上运行以下命令:
admin@myhome:~/$ docker swarm join --token <token> <manager-ip>
在这里,<token> 是 docker swarm init 命令输出中生成的令牌,你可以在前面的代码块中找到它,而 <manager-ip> 是 Swarm 管理器的 IP 地址。
例如,如果令牌是 SWMTKN-1-0hu2dmht259tb4skyetrpzl2qhxgeddij3bc1wof3jxh7febmd-6pzkhrh4ak345m8022hauviil,而管理节点 IP 地址是 10.0.2.15,命令将如下所示:
admin@myhome:~/$ docker swarm join --token SWMTKN-1-0hu2dmht259tb4skyetrpzl2qhxgeddij3bc1wof3jxh7febmd-6pzkhrh4ak345m8022hauviil 10.0.2.15
在每个工作节点上运行 docker swarm join 命令后,你可以通过在 Swarm 管理节点上运行以下命令来验证它们是否已加入 Swarm:
admin@myhome:~/$ docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
i050z7b0tjoew7hxlz419cd8l * myhome Ready Active Leader 23.0.1
这将显示 Swarm 中所有节点的列表,包括管理节点和你已添加的任何工作节点。
在那之后,你可以添加更多节点并开始将应用程序部署到 Docker Swarm。你可以重用任何正在使用的 Docker Compose 文件或 Kubernetes 清单。
要部署一个示例应用程序,我们可以通过部署 wordpress 服务来重用 Docker Compose 模板,但我们需要稍微更新它,通过在环境变量中使用 MySQL 用户和密码文件:
wordpress:
image: wordpress:php8.2-fpm-alpine
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER_FILE: /run/secrets/mysql_user
WORDPRESS_DB_NAME: example_database
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/mysql_password
以下是向 wordpress 和 db 服务添加机密的示例:
secrets:
- mysql_user
- mysql_password
db:
image: mariadb:10.4
environment:
MYSQL_ROOT_PASSWORD: example_password
MYSQL_DATABASE: example_database
MYSQL_USER_FILE: /run/secrets/mysql_user
MYSQL_PASSWORD_FILE: /run/secrets/mysql_password
以下是一个在 docker-compose.yml:secrets 文件底部添加机密定义的示例:
mysql_user:
external: true
mysql_password:
external: true
external: true 设置告诉 docker-compose 机密已经存在,它不应该尝试更新或重新创建这些机密。
在这个版本的 Compose 文件中,我们使用机密来存储 wordpress 和 db 服务的 MySQL 用户和密码。
要将此文件部署到 Docker Swarm,我们可以使用以下命令:
admin@myhome:~/$ echo "root" | docker secret create mysql_user -
vhjhswo2qg3bug9w7id08y34f
echo "mysqlpwd" | docker secret create mysql_password -
oy9hsbzmzrh0jrgjo6bgsydol
然后,我们可以部署堆栈:
admin@myhome:~/$ docker stack deploy -c docker-compose.yml wordpress
Ignoring unsupported options: restart
Creating network wordpress_wordpress
Creating service wordpress_web
Creating service wordpress_wordpress
Creating service wordpress_db
这里,docker-compose.yaml 是 Compose 文件的名称,my-stack-name 是 Docker 堆栈的名称。
一旦堆栈部署完成,wordpress、web 和 db 服务将使用在机密中指定的 MySQL 用户和密码运行。你可以通过列出堆栈并检查容器是否正在运行来验证这一点:
admin@myhome:~/$ root@vagrant:~# docker stack ls
NAME SERVICES
wordpress 3
root@vagrant:~# docker ps
CONTAINER ID IMAGE CREATED STATUS PORTS NAMES
7ea803c289b0 mariadb:10.4 "docker-entrypoint.s…" 28 seconds ago Up 27 seconds 3306/tcp wordpress_db.1.dogyh5rf52zzsiq0t95nrhuhc
ed25de3273a2 wordpress:php8.2-fpm-alpine "docker-entrypoint.s…" 33 seconds ago Up 31 seconds 9000/tcp wordpress_wordpress.1.xmmljnd640ff9xs1249jpym45
Docker Swarm 是一个很好的项目,可以让你开始学习 Docker 编排方法。通过使用各种插件扩展其默认功能,它也可以用于生产级系统。
Kubernetes 和 OpenShift
两个最受欢迎的 Docker 容器编排工具是 Kubernetes 和 OpenShift。尽管它们有一些相似之处,但也存在一些显著的区别。以下是 Kubernetes 和 OpenShift 之间的主要区别:
-
架构:Kubernetes 是一个独立的编排平台,旨在与多个容器运行时(包括 Docker)一起工作。而 OpenShift 是建立在 Kubernetes 之上的平台。它提供了额外的功能和工具,如源代码管理、持续集成和部署。这些附加功能使 OpenShift 成为一个更全面的解决方案,适合需要端到端 DevOps 功能的企业。
-
易用性:Kubernetes 是一个强大的编排工具,需要较高的技术水平才能设置和操作。而 OpenShift 则旨在更加用户友好,适合具有不同技术背景的开发人员。OpenShift 提供了一个基于 Web 的界面来管理应用,并且可以与各种开发工具集成,方便开发人员使用。
-
成本:Kubernetes 是一个开源项目,可以免费使用,但企业可能需要投入额外的工具和资源来搭建和操作它。OpenShift 是一个企业级平台,完全访问其功能和支持需要订阅。OpenShift 的成本可能高于 Kubernetes,但它提供了额外的功能和支持,对于需要高级 DevOps 功能的企业来说,可能值得投资。
这两种解决方案都是强大的 Docker 编排工具,提供不同的优势和权衡。Kubernetes 高度可定制,适合更具技术背景的用户。另一方面,OpenShift 提供了更全面的解决方案,具有额外的功能和更易于使用的界面,但成本较高。在选择这两种工具时,你应该考虑你组织的具体需求,同时记住 Docker Swarm 也是一个可选项。云服务提供商也开发了自己的解决方案,其中 Elastic Container Service 就是其中之一。
总结
本章中,我们讨论了关于 Docker 的更高级话题,仅涉及了容器编排相关的内容。Kubernetes、OpenShift 和云服务提供商提供的 SaaS 解决方案正在推动新工具的创造,这些工具将进一步简化 Docker 在现代应用中的使用。
Docker 对软件开发和部署的世界产生了深远影响,使我们能够比以往更加高效和可靠地构建、发布和运行应用程序。通过了解 Docker 的内部机制并遵循最佳实践,我们可以确保我们的应用程序在各种环境中都是安全、高效和可扩展的。
在下一章,我们将探讨在基于 Docker 容器的分布式环境中,如何监控和收集日志的挑战。
第三部分:DevOps 云工具包
本书的最后部分将更多地关注通过配置即代码(CaC)和基础设施即代码(IaC)实现自动化。我们还将讨论监控和追踪作为现代应用程序开发和维护的关键部分。在最后一章,我们将讨论我们在许多项目中经历过的 DevOps 陷阱。
本部分包含以下章节:
-
第十章,监控、追踪与分布式日志记录
-
第十一章,使用 Ansible 进行配置即代码
-
第十二章,利用基础设施即代码
-
第十三章,使用 Terraform、GitHub 和 Atlantis 进行 CI/CD
-
第十四章,避免 DevOps 中的陷阱
第十章:监控、追踪和分布式日志
现代开发的应用程序往往运行在 Docker 容器中或作为无服务器应用栈。传统上,应用程序是作为一个单体实体构建的——一个进程在服务器上运行。所有的日志都存储在磁盘上,便于快速获取相关信息。若要诊断应用程序的问题,您需要登录到服务器,查找日志或堆栈跟踪以找出问题所在。但当您将应用程序运行在多个容器中的 Kubernetes 集群里,并且这些容器在不同的服务器上执行时,事情就变得复杂了。
这也使得存储日志变得非常困难,更不用说查看它们了。实际上,在容器中运行应用程序时,不建议在其中保存任何文件。我们通常在只读文件系统中运行这些容器。这是可以理解的,因为您应该将正在运行的容器视为一个短暂的身份,随时可能被销毁。
运行无服务器应用程序时,我们也面临类似的情况;在 Amazon Web Services (AWS) Lambda 上,进程在收到请求时开始,处理完请求中的数据后,应用程序会在完成任务后终止。如果您保存了任何数据到磁盘,它将在处理完成后被删除。
最合逻辑的解决方案当然是将所有日志发送到某个外部系统,该系统将保存、编目并使您的日志可以被搜索。有多种解决方案,包括 软件即服务 (SaaS) 和特定于云的应用程序。
顺便提一下,将日志发送到外部系统对于裸金属服务器也是有益的——无论是用于分析、警报,还是诊断服务器无法访问或停止响应的情况。
除了系统和应用程序日志外,我们还可以发送应用程序追踪指标。追踪是比指标更深入的一种形式,它可以提供更多关于系统性能及应用程序在特定情况下表现的洞察。追踪数据的示例包括:某个请求被应用程序处理的时间、耗费的 CPU 周期数,以及应用程序等待数据库响应的时间。
在本章中,您将学习以下内容:
-
监控、追踪和日志是什么?
-
如何选择并配置一种云原生的日志解决方案
-
自托管解决方案及如何选择它们
-
SaaS 解决方案及如何评估哪些对您的组织最有用
此外,我们将涵盖以下主题:
-
监控、追踪和日志之间的区别
-
云解决方案
-
自托管的开源解决方案
-
SaaS 解决方案
-
日志和指标的保留
那么,让我们直接开始吧!
监控、追踪和日志之间的区别
根据上下文和交流对象的不同,您会听到这些术语交替使用,但它们之间存在微妙且非常重要的差异。
监控是指对你的服务器和应用程序进行仪表化,并收集关于它们的数据以进行处理、识别问题,最终将结果呈现给相关方。这也包括警报功能。
追踪,另一方面,更为具体,正如我们已经提到的。追踪数据可以告诉你很多关于系统性能的信息。通过追踪,你可以观察对开发者非常有用的统计数据(例如一个函数的运行时间,以及 SQL 查询是否快速或存在瓶颈),对 DevOps 工程师有用的数据(例如我们等待数据库或网络的时间),甚至对业务部门有用的数据(例如用户在我们的应用程序中的体验)。所以,你可以看到,当它被正确使用时,它可以成为你手中的一个非常强大的工具。
日志记录的目的是以集中化的方式提供可操作的信息,通常就是将所有消息保存到文件中(称为日志文件)。这些消息通常包括给定操作的成功或失败,并可配置详细程度。日志记录主要由系统管理员或 DevOps 工程师使用,以便更好地了解操作系统或任何给定应用程序中的情况。
澄清了这些后,我们可以进入云中分布式监控解决方案的具体实现,无论是自定义解决方案,还是作为 SaaS 应用程序使用。
云解决方案
每个云服务提供商都完全意识到进行适当监控和分布式日志记录的必要性,因此他们都会开发自己的本地解决方案。有时候,使用本地解决方案是值得的,但并非总是如此。让我们来看一看主要的云服务提供商及其提供的服务。
AWS 最早提供的服务之一就是CloudWatch。最初,它只是收集各种度量数据,并允许你创建仪表板以更好地理解系统性能,轻松发现问题,或者简单地识别是否发生了拒绝服务攻击,从而使你能够快速响应。
CloudWatch 的另一个功能是警报,但它仅限于使用另一个 Amazon 服务——简单邮件服务(Simple Email Service)发送电子邮件。警报和指标也可以触发 AWS 账户中的其他操作,比如扩展或缩减运行实例的数量。
截至本书撰写时,CloudWatch 的功能已经远不止监控。该服务的开发者新增了收集和搜索日志的功能(CloudWatch Logs Insights),监控 AWS 资源本身的变化,以及触发操作的能力。我们还能够利用CloudWatch 异常检测在应用程序中检测异常。
至于追踪,AWS 准备了一个名为AWS X-Ray的服务,这是一个先进的分布式追踪系统,几乎可以实时提供有关应用程序在生产环境中如何运行的信息。不幸的是,它的能力仅限于几个语言:Node.js、Java 和.NET。如果你的应用程序是用 Python 编写的,那么就没那么幸运了。
看看其他流行的云解决方案,谷歌提供了Google Cloud Platform(GCP),这是一个智能的日志收集、查询和错误报告解决方案,称为… Cloud Logging。如果在 GCP 中使用此服务,与 CloudWatch Logs 类似,你将能够发送应用程序日志,存储它们,搜索所需数据(IP 地址、查询字符串、调试数据等),并使用类似 SQL 的查询分析日志。
然而,相似之处到此为止,因为谷歌在一些额外功能上走得更远,比如能够创建带有可视化错误报告的日志仪表板,或者创建基于日志的指标。
在 GCP 中,监控是由另一个完全不同的服务来完成的——Google Cloud Monitoring。它专注于收集应用程序数据,创建服务级目标(SLOs),从 Kubernetes(Google Kubernetes Engine,或GKE)收集大量指标,并进行第三方集成,例如与知名服务Prometheus的集成。
看看微软的云平台——Azure,你会找到Azure Monitor Service,它由多个部分组成,涵盖了完整应用监控和追踪的需求。显然,Azure Monitor Logs用于收集日志。还有Azure Monitor Metrics,用于监控和可视化你推送到平台的所有指标。你还可以像在 GCP 或 AWS 中一样分析、查询并设置警报。追踪是由Azure Application Insights完成的。微软将其推广为应用性能管理(APM)解决方案,并且它是Azure Monitor的一部分。它提供了应用程序的可视化地图、实时指标、代码分析、使用数据和许多其他功能。显然,不同云服务商及其解决方案的实现方式有所不同。你需要参考文档来了解如何对这些服务进行工具化和配置。
我们将重点介绍 AWS 服务。我们将为我们的应用程序创建一个日志组,并从 EC2 实例收集指标。我们还将讨论如何在 Python 中使用 AWS X-Ray 进行追踪,无论底层服务如何,这都可以用于在 AWS 基础设施中运行的应用程序。
CloudWatch 日志和指标
CloudWatch Logs 是 AWS 提供的日志管理服务,使您能够集中、搜索并监控来自多个来源的日志数据。它允许您排查操作问题和安全事件,以及监控资源利用率和性能。
CloudWatch 指标是 AWS 提供的一项监控服务,允许您收集、跟踪并监控 AWS 资源和应用程序的各种指标。
CloudWatch 指标通过收集并显示关键指标(如 CPU 利用率、网络流量、磁盘 I/O 以及与 AWS 资源相关的其他指标,如 EC2 实例、RDS 实例、S3 存储桶和 Lambda 函数)为用户提供详细的 AWS 资源性能视图。
用户可以使用 CloudWatch 指标设置警报,当某些阈值被超越时会通知他们,并且创建自定义仪表板,近实时显示重要指标。CloudWatch 指标还允许用户检索和分析历史数据,这些数据可用于识别趋势并优化资源使用。
为了能够将日志和指标发送到 CloudWatch,我们需要以下内容:
-
一项 IAM 策略,授予将日志发送到 CloudWatch Logs 的权限。此外,我们还将允许将指标数据与日志一起推送。
-
创建一个 IAM 角色,并将之前创建的策略附加到其中。此角色然后可以被 EC2 实例、Lambda 函数或任何其他需要将日志发送到 CloudWatch Logs 的 AWS 服务假设。
-
将角色附加到我们希望将日志发送到 CloudWatch Logs 的资源。为了我们的目的,我们将角色附加到 EC2 实例。
一个 IAM 策略的示例如下:
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Sid”: “CloudWatchLogsPermissions”,
“Effect”: “Allow”,
“Action”: [
“logs:CreateLogStream”,
“logs:CreateLogGroup”,
“logs:PutLogEvents”
],
“Resource”: “arn:aws:logs:*:*:*”
},
{
“Sid”: “CloudWatchMetricsPermissions”,
“Effect”: “Allow”,
“Action”: [
“cloudwatch:PutMetricData”
],
“Resource”: “*”
}
]
}
在此策略中,logs:CreateLogStream 和 logs:PutLogEvents 操作对于所有 CloudWatch Logs 资源(arn:aws:logs:*:*:*)都是允许的,cloudwatch:PutMetricData 操作对于所有 CloudWatch 指标资源(*)也是允许的。
我们还需要一项信任策略,允许 EC2 假设我们为其创建的角色,以便能够发送指标和日志。信任策略如下所示:
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Principal”: { “Service”: “ec2.amazonaws.com”},
“Action”: “sts:AssumeRole”
}
]
}
将其保存为 trust-policy.json 文件,我们稍后将使用它。
使用 AWS CLI 工具,创建实例配置文件并将之前的策略附加到其中,您需要运行以下命令:
admin@myhome:~$ aws iam create-instance-profile --instance-profile-name DefaultInstanceProfile
{
“InstanceProfile”: {
“Path”: “/”,
“InstanceProfileName”: “DefaultInstanceProfile”,
“InstanceProfileId”: “AIPAZZUIKRXR3HEDBS72R”,
“Arn”: “arn:aws:iam::673522028003:instance-profile/DefaultInstanceProfile”,
“CreateDate”: “2023-03-07T10:59:01+00:00”,
“Roles”: []
}
}
我们还需要一个附加了信任策略的角色:
admin@myhome:~$ aws iam create-role --role-name DefaultInstanceProfile --assume-role-policy-document file://trust-policy.json
{
“Role”: {
“Path”: “/”,
“RoleName”: “DefaultInstanceProfile”,
“RoleId”: “AROAZZUIKRXRYB6HO35BL”,
“Arn”: “arn:aws:iam::673522028003:role/DefaultInstanceProfile”,
“CreateDate”: “2023-03-07T11:13:54+00:00”,
“AssumeRolePolicyDocument”: {
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Principal”: {
“Service”: “ec2.amazonaws.com”
},
“Action”: “sts:AssumeRole”
}
]
}
}
}
现在,我们可以将刚刚创建的角色附加到实例配置文件上,以便在 EC2 实例中使用它:
admin@myhome:~$ aws iam add-role-to-instance-profile --role-name DefaultInstanceProfile --instance-profile-name DefaultInstanceProfile
现在,让我们附加一个用于 EC2 服务的策略:
admin@myhome:~$ aws iam put-role-policy --policy-name DefaultInstanceProfilePolicy --role-name DefaultInstanceProfile --policy-document file://policy.json
policy.json 文件是您保存策略的文件。
实例配置文件,顾名思义,仅适用于 EC2 实例。要在 Lambda 函数中使用相同的策略,我们需要创建一个 IAM 角色并将新创建的角色附加到函数上。
让我们也使用 AWS CLI 创建一个新实例,并附加我们刚刚创建的实例配置文件。这个特定的实例将被放置在默认 VPC 和公共子网中。这将导致实例获得一个公网 IP 地址,并且可以从公共互联网访问。
要在默认 VPC 的公共子网上创建一个 EC2 实例,并使用DefaultInstanceProfile,你可以按照以下步骤操作:
- 获取默认 VPC 的 ID:
admin@myhome:~$ aws ec2 describe-vpcs --filters “Name=isDefault,Values=true” --query “Vpcs[0].VpcId” --output text
vpc-0030a3a495df38a0e
该命令将返回默认 VPC 的 ID。我们将在后续步骤中使用它。
- 获取默认 VPC 中一个公共子网的 ID,并保存以备后用:
admin@myhome:~$ aws ec2 describe-subnets --filters “Name=vpc-id,Values=vpc-0030a3a495df38a0e” “Name=map-public-ip-on-launch,Values=true” --query “Subnets[0].SubnetId” --output text subnet-0704b611fe8a6a169
要启动 EC2 实例,我们需要一个叫做Amazon 机器镜像(AMI)的实例模板和一个我们将用于访问该实例的 SSH 密钥。为了获得 Ubuntu 镜像的 ID,我们也可以使用 AWS CLI 工具。
- 我们将通过以下命令筛选出最新的 Ubuntu 20.04 的 AMI ID:
admin@myhome:~$ aws ec2 describe-images --owners 099720109477 --filters “Name=name,Values=*ubuntu/images/ hvm-ssd/ubuntu-focal-20.04*” “Name=state,Values=available” “Name=architecture,Values=x86_64” --query “reverse(sort_by(Images, &CreationDate))[:1].ImageId” --output text
ami-0a3823a4502bba678
该命令将列出 Canonical(099720109477)拥有的所有可用的 Ubuntu 20.04 镜像,并通过名称(ubuntu-focal-20.04-*)、架构(我们需要x86_64,而非 ARM)以及是否可用(状态为可用)进行过滤。它还将按照创建日期降序排序,并返回最新的(列表中的第一个)镜像 ID。
- 现在,要创建一个 SSH 密钥,你需要为自己生成一个密钥,或者使用你已经在机器上拥有的密钥。我们需要将密钥的公钥部分上传到 AWS。你可以简单地运行另一个 CLI 命令来实现这一点:
admin@myhome:~$ aws ec2 import-key-pair --key-name admin-key --public-key-material fileb://home/admin/.ssh/admin-key.pub
{
“KeyFingerprint”: “12:97:23:0f:d6:2f:2b:28:4d:a0:ad:62:a7:20:e3:f8”,
“KeyName”: “admin-key”,
“KeyPairId”: “key-0831b2bc5c2a08d82”
}
完成所有这些步骤后,最后,我们准备好在公共子网中使用DefaultInstanceProfile启动一个新的实例:
admin@myhome:~$ aws ec2 run-instances --image-id ami-0abbe417ed83c0b29 --count 1 --instance-type t2.micro --key-name admin-key --subnet-id subnet-0704b611fe8a6a169 --associate-public-ip-address --iam-instance-profile Name=DefaultInstanceProfile
{
“Groups”: [],
“Instances”: [
{
“AmiLaunchIndex”: 0,
“ImageId”: “ami-0abbe417ed83c0b29”,
“InstanceId”: “i-06f35cbb39f6e5cdb”,
“InstanceType”: “t2.micro”,
“KeyName”: “admin-key”,
“LaunchTime”: “2023-03-08T14:12:00+00:00”,
“Monitoring”: {
“State”: “disabled”
},
“Placement”: {
“AvailabilityZone”: “eu-central-1a”,
“GroupName”: “”,
“Tenancy”: “default”
},
“PrivateDnsName”: “ip-172-31-17-127.eu-central-1.compute.internal”,
“PrivateIpAddress”: “172.31.17.127”,
“ProductCodes”: [],
“PublicDnsName”: “”,
“State”: {
“Code”: 0,
“Name”: “pending”
},
“StateTransitionReason”: “”,
“SubnetId”: “subnet-0704b611fe8a6a169”,
“VpcId”: “vpc-0030a3a495df38a0e”,
“Architecture”: “x86_64”,
“BlockDeviceMappings”: [],
“ClientToken”: “5e4a0dd0-665b-4878-b852-0a6ff21c09d3”,
“EbsOptimized”: false,
“EnaSupport”: true,
“Hypervisor”: “xen”,
“IamInstanceProfile”: {
“Arn”: “arn:aws:iam::673522028003:instance-profile/DefaultInstanceProfile”,
“Id”: “AIPAZZUIKRXR3HEDBS72R”
},
# output cut for readability
上述命令的输出是有关新启动实例的信息,你可以将其用于脚本化操作,或者仅仅保存实例的 IP 地址以备后用。
目前,你还无法连接到机器,因为默认情况下,所有端口都被关闭。为了打开 SSH 端口(22),我们需要创建一个新的安全组。
- 使用以下命令来实现:
admin@myhome:~$ aws ec2 create-security-group --group-name ssh-access-sg --description “Security group for SSH access” --vpc-id vpc-0030a3a495df38a0e
{
“GroupId”: “sg-076f8fad4e60192d8”
}
我们使用的 VPC ID 是我们在之前步骤中保存的,而输出是我们新安全组的 ID。我们需要为其添加一个入站规则,并将其连接到我们的 EC2 实例。在机器创建后,查看长输出中的InstanceID值(例如:i-06f35cbb39f6e5cdb)。
- 使用以下命令为安全组添加一个入站规则,允许来自
0.0.0.0/0的 SSH 访问:
admin@myhome:~$ aws ec2 authorize-security-group-ingress --group-id sg-076f8fad4e60192d8 --protocol tcp --port 22 --cidr 0.0.0.0/0
{
“Return”: true,
“SecurityGroupRules”: [
{
“SecurityGroupRuleId”: “sgr-0f3b4be7d2b01a7f6”,
“GroupId”: “sg-076f8fad4e60192d8”,
“GroupOwnerId”: “673522028003”,
“IsEgress”: false,
“IpProtocol”: “tcp”,
“FromPort”: 22,
“ToPort”: 22,
“CidrIpv4”: “0.0.0.0/0”
}
]
}
我们使用了在之前步骤中创建的安全组的 ID。
这个命令向安全组添加了一个新的入站规则,允许来自任何 IP 地址(0.0.0.0/0)的 TCP 流量通过端口22(SSH)。你也可以选择使用自己的公网 IP 地址,而不是允许新 EC2 实例完全访问互联网。
- 现在,我们可以将这个安全组附加到一个实例上:
admin@myhome:~$ aws ec2 modify-instance-attribute --instance-id i-06f35cbb39f6e5cdb --groups sg-076f8fad4e60192d8
目前,端口22应该已经打开,并准备接收连接。
让我们暂时停下来。你可能会想,是否有更好的方法来完成此操作,而不是使用 AWS CLI。是的,确实有;有许多工具可以自动化创建基础设施。这些工具通常被称为基础设施即代码(IaC),我们将在第十二章中讨论它们。在这个示例中,我们本可以使用多种选择,从 AWS 的首选 IaC 工具 CloudFormation,到 HashiCorp 的 Terraform,再到逐渐受到关注的 Pulumi 项目。
现在我们有了一个 EC2 实例,我们可以连接到它并安装CloudWatch 代理。之所以需要它,是因为 AWS 默认只监控两个指标:CPU 和内存使用率。如果您想要监控磁盘空间并将附加数据(如日志或自定义指标)发送到 CloudWatch,则必须使用该代理。
- 进入 SSH 控制台后,我们需要下载 CloudWatch 代理的
deb包并使用dpkg工具安装:
admin@myhome:~$ ssh -i ~/.ssh/admin-key ubuntu@3.121.74.46
ubuntu@ip-172-31-17-127:~$ wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
ubuntu@ip-172-31-17-127:~$ sudo dpkg -i -E ./amazon-cloudwatch-agent.deb
让我们切换到root用户,以便可以在每个命令中省略sudo:
ubuntu@ip-172-31-17-127:~$ sudo -i
root@ip-172-31-17-127:~# /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
================================================================
= Welcome to the Amazon CloudWatch Agent Configuration Manager =
= =
= CloudWatch Agent allows you to collect metrics and logs from =
= your host and send them to CloudWatch. Additional CloudWatch =
= charges may apply. =
================================================================
On which OS are you planning to use the agent?
1\. linux
2\. windows
3\. darwin
default choice: [1]:
它会询问很多问题,但大多数问题可以保持默认设置,直接按 Enter 即可。然而,有一些问题需要我们特别注意:
Do you want to monitor metrics from CollectD? WARNING: CollectD must be installed or the Agent will fail to start
1\. yes
2\. no
default choice: [1]:
- 如果您回答了
yes(1)这个问题,您将需要通过以下命令安装 collectd:
root@ip-172-31-17-127:~# apt install -y collectd
- 对于以下问题,除非您希望上传某些特定的日志文件到 CloudWatch Logs,否则请回答
no(2):
Do you want to monitor any log files?
1\. yes
2\. no
default choice: [1]:
2
- 最后的问题是是否将代理配置保存在 AWS SSM 中,您可以安全地回答
no(2):
Do you want to store the config in the SSM parameter store?
1\. yes
2\. no
default choice: [1]:
2
Program exits now.
向导会将配置保存在/opt/aws/amazon-cloudwatch-agent/bin/config.json中。如果需要,您可以稍后修改它或再次启动向导。
- 在启动代理之前,我们需要将输出的 JSON 文件转换为新的Tom 的显而易见、最小化语言(TOML)格式,这是代理使用的格式。幸运的是,执行此任务的命令也有。我们将使用代理控制脚本加载现有的架构,保存 TOML 文件,并在一切就绪时可选择启动代理:
root@ip-172-31-17-127:~# /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json
root@ip-172-31-17-127:~# systemctl status amazon-cloudwatch-agent
amazon-cloudwatch-agent.service - Amazon CloudWatch Agent
Loaded: loaded (/etc/systemd/system/amazon-cloudwatch-agent.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2023-03-08 15:00:30 UTC; 4min 54s ago
Main PID: 20130 (amazon-cloudwat)
Tasks: 6 (limit: 1141)
Memory: 14.3M
CGroup: /system.slice/amazon-cloudwatch-agent.service
└─20130 /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent -config /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.toml -envconfig /opt/aws/amazon-cloudwatch-agent/e>
Mar 08 15:00:30 ip-172-31-17-127 systemd[1]: Started Amazon CloudWatch Agent.
Mar 08 15:00:30 ip-172-31-17-127 start-amazon-cloudwatch-agent[20130]: /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json does not exist or cannot read. Skipping it.
Mar 08 15:00:30 ip-172-31-17-127 start-amazon-cloudwatch-agent[20130]: I! Detecting run_as_user...
现在,我们可以前往 AWS Web 控制台并导航到 CloudWatch,查看是否能看到传入的指标。可能需要几分钟才能显示。
在启动 CloudWatch 代理之前,我们将为 EC2 实例获取大约 17 个不同的指标,如下图所示:
图 10.1 – 在未安装 CloudWatch 代理的情况下,CloudWatch 中的基本 EC2 和 EBS 指标
启动 CloudWatch 代理后,我们将开始接收更多的指标,并且在 CloudWatch Metrics 面板中会看到一个额外的命名空间。请参见以下屏幕截图:
图 10.2 – 在 EC2 实例上成功启用 CloudWatch 代理后的 CloudWatch 指标
我们接收到的所有指标都可以用于在 CloudWatch 服务中创建仪表板和警报(包括异常检测)。
AWS X-Ray
AWS X-Ray 是一项服务,允许您跟踪分布式系统和微服务应用程序中的请求。它提供了请求在应用程序中流动的端到端视图,使开发人员能够识别性能瓶颈、诊断错误并提高整体应用程序效率。
使用 X-Ray,可以可视化应用程序的不同组件,并查看请求如何在各个组件间流动时被处理。这包括诸如完成每个组件所需时间、发生的任何错误以及错误的原因等细节。
X-Ray 还提供了一系列分析工具,包括统计分析和热图,帮助开发人员识别请求处理中的趋势和模式。这些见解可以用于优化性能,确保应用程序尽可能高效地运行。
AWS X-Ray 支持多种编程语言,包括以下几种:
-
Node.js
-
Java
-
.NET
-
Go
-
Python
-
Ruby
-
PHP
要使用 AWS X-Ray 提供的诊断工具来为应用程序进行监控,您可以使用 AWS SDK。考虑以下代码(可以在 GitHub 仓库中找到:github.com/Sysnove/flask-hello-world):
from flask import Flask
app = Flask(__name__)
@app.route(‘/’)
def hello_world():
return ‘Hello World!’
if __name__ == ‘__main__’:
app.run()
要收集此服务的追踪数据,您需要使用 pip 包管理器安装 aws_xray_sdk 包。然后,将 xray_recorder 子包导入到我们的代码中。在这种情况下,我们还将使用此 SDK 与 Flask 框架的集成。修改后的代码如下所示:
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.ext.flask.middleware import XRayMiddleware
xray_recorder.configure(service=’FlaskHelloWorldApp’)
app = Flask(__name__)
XRayMiddleware(app, xray_recorder)
其余代码可以保持不变。在这里,我们将配置 X-Ray 记录器使用服务名称 FlaskHelloWorldApp,该名称将在 X-Ray 控制台中显示为我们的服务名称。当服务开始运行时,您可以进入 X-Ray 控制台,查看服务名称 FlaskHelloWorldApp 以及相关的追踪列表。
AWS X-Ray SDK 的完整文档可以在以下网站找到:docs.aws.amazon.com/xray-sdk-for-python/latest/reference/index.xhtml。
当在我们之前创建的 EC2 实例上运行上述应用程序时,您将看到应用程序运行环境的完整图像,包括 Flask 进程的内部情况。
有多个项目涉及应用程序监控、追踪和日志收集。除了云环境中可用的云托管解决方案外,还有一些值得了解的商业和开源解决方案。这些认知在处理越来越常见的混合解决方案时可能会非常有用。
自托管的开源解决方案
最受欢迎的监控相关项目之一,也是商业解决方案采用的项目是OpenTelemetry。它是一个开源的应用程序监控与可观察性项目,提供了一整套 API、库、代理和集成,用于收集、处理和导出来自分布式系统中不同来源的遥测数据,如跟踪、指标和日志。OpenTelemetry 设计上是供应商无关的且云原生的,这意味着它可以与各种云服务提供商、编程语言、框架和架构兼容。
OpenTelemetry 的主要目标是为开发者和运维人员提供一种统一和标准化的方式,在其应用程序和服务的整个堆栈中对遥测数据进行注入、收集和分析,而不管底层基础设施是什么。OpenTelemetry 支持不同的数据格式、协议和导出目标,包括流行的可观察性平台,如Prometheus、Jaeger、Zipkin、Grafana 和 SigNoz。这使得用户可以根据需要灵活组合他们喜欢的工具和服务,构建一个全面的可观察性管道。
一些采用 OpenTelemetry 的商业软件示例包括Datadog、AWS 和New Relic。AWS 提供 OpenTelemetry Collector 作为托管服务,用于收集和导出遥测数据到 AWS 服务,如 Amazon CloudWatch、AWS X-Ray 和 AWS App Runner。
Prometheus
Prometheus 是一个开源的监控解决方案,广泛用于收集和查询分布式系统中的指标。它由 SoundCloud 的开发人员创建,现在由云原生计算基金会(CNCF)维护。Prometheus 设计上具有高度的可扩展性和适应性,支持多种数据源和集成选项。它允许用户定义和收集自定义指标,通过内置仪表板可视化数据,并基于预定义的阈值或异常设置警报。Prometheus 常与 Kubernetes 及其他云原生技术一起使用,但也可以用于监控传统的基础设施和应用程序。
一个常见的使用场景是跟踪请求延迟和错误率,这有助于识别应用程序中的性能瓶颈和潜在问题。要开始使用 Prometheus 监控 Flask 应用程序,您可以使用 Python 的 Prometheus 客户端库。该库提供了可以添加到 Flask 路由中的装饰器,自动生成请求计数、请求持续时间和 HTTP 响应码等指标。然后,这些指标可以由 Prometheus 服务器收集,并显示在 Grafana 仪表板上进行可视化和分析。
这是一个示例,展示如何将“Hello World” Flask 应用程序与 Prometheus 配合使用以发送指标。我们在前一节中使用了相同的应用程序和 AWS X-Ray。
首先,你需要使用 pip 安装 prometheus_client 库:
$ pip install prometheus_client
接下来,你可以修改 flask-hello-world 仓库中的 app.py 文件,添加 Prometheus 客户端库,并用指标对路由进行仪器化。以下是一个示例:
from flask import Flask
from prometheus_client import Counter, Histogram, start_http_server
app = Flask(__name__)
# Define Prometheus metrics
REQUEST_COUNT = Counter(‘hello_world_request_count’, ‘Hello World Request Count’)
REQUEST_LATENCY = Histogram(‘hello_world_request_latency_seconds’, ‘Hello World Request Latency’,
bins=[0.1, 0.2, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0])
# Instrument Flask routes with Prometheus metrics
@app.route(‘/’)
@REQUEST_LATENCY.time()
def hello():
REQUEST_COUNT.inc()
return “Hello World!”
# Start the Prometheus server on port 8000
if __name__ == ‘__main__’:
start_http_server(8000)
app.run(debug=True)
在这个示例中,我们定义了两个 Prometheus 指标:hello_world_request_count 和 hello_world_request_latency_seconds。hello() 路由使用装饰器对这些指标进行了仪器化。REQUEST_LATENCY 直方图度量每个请求的延迟,而 REQUEST_COUNT 计数器在每个请求时递增。
我们已经通过 start_http_server() 在端口 8000 启动了 Prometheus 服务器。这样可以使这些指标可供 Prometheus 服务器收集。
要查看这些指标,你可以在浏览器中访问 localhost:8000/metrics。这将以 Prometheus 格式显示原始的指标数据。你也可以使用像 Grafana 这样的工具在仪表盘上可视化这些指标。
Grafana
Grafana 是一个流行的开源仪表盘和数据可视化平台,允许用户创建交互式和可定制的仪表盘,用于监控和分析来自各种数据源的指标。它通常与 Prometheus 一起使用。
使用 Grafana,用户可以创建可视化图表、警报规则和仪表盘,以便深入了解他们应用程序和基础设施的性能和行为。Grafana 支持多种数据源,包括流行的时序数据库,如 Prometheus、InfluxDB 和 Graphite,使其成为一个多功能的监控和可视化工具。一旦你连接了数据源,你就可以通过添加面板来开始创建仪表盘,以便可视化数据。这些面板可以包含各种类型的可视化图表,包括折线图、条形图和仪表盘。你还可以自定义仪表盘布局、添加注释,并设置警报以在指标出现异常或问题时通知你。凭借其强大的功能和灵活性,Grafana 成为可视化和分析应用程序及基础设施指标的首选工具。
Grafana Labs 还创建了 Grafana Loki 项目,可以用来扩展你的监控,提供日志可视化。Grafana Loki 是一个水平可扩展的日志聚合系统,提供了一种集中化来自不同源的日志并快速搜索和分析它们的方法。它被视为 Prometheus 的替代品,但这两种工具有不同的使用场景,并且可以相互补充。
与传统的日志管理解决方案不同,Loki 不会预先对日志进行索引或解析。相反,它使用一个流式管道,提取日志标签并以紧凑高效的格式存储,这使得它非常适合实时摄取和查询大量日志。Grafana Loki 与 Grafana 无缝集成,允许用户将日志与指标关联,并创建强大的仪表盘,深入了解他们的应用程序和基础设施的行为。
要使用 Grafana Loki,你需要设置一个 Loki 服务器并配置它接收来自你的应用程序和基础设施的日志数据。一旦 Loki 设置完成,你可以使用 Grafana 的 Explore 功能实时搜索和可视化日志。Explore 提供了一个用户友好的界面,使你能够使用各种过滤器(如标签、时间范围和查询表达式)来搜索日志。
SigNoz
SigNoz 是一个可观察性平台,使用户能够收集、存储和分析应用程序的指标遥测数据,并提供统一的日志管理界面。它基于 OpenTelemetry 规范构建,这是一个分布式追踪和指标收集的行业标准框架。SigNoz 提供了一个简单、直观的界面,供用户查看其应用程序性能和健康状况的实时和历史数据。
SigNoz 有自己的代理程序,可以安装在你的服务器上,但它也支持 Prometheus 作为数据源。因此,如果你已经在使用 Prometheus,你可以在不对监控基础设施进行重大更改的情况下使用 SigNoz。
要在服务器上安装 SigNoz,你可以参考官方项目网站上的详细安装指南:signoz.io/docs/install/。
New Relic Pixie
New Relic 是一个知名的监控 SaaS 解决方案;我们将在本章的SaaS 解决方案部分稍后详细介绍它。Pixie 是一个由 New Relic 启动的开源项目,并且已贡献给 CNCF。
CNCF 是一个开源软件基金会,成立于 2015 年,旨在推动云原生技术的发展和采用。CNCF 是许多流行项目的家园,例如 Kubernetes、Prometheus 和 Envoy,这些项目在现代云原生应用中得到了广泛应用。该基金会的目标是创建一个云原生计算的供应商中立生态系统,促进不同云平台和技术之间的互操作性和标准化。CNCF 还主持多个认证项目,帮助开发者和组织验证他们在云原生技术方面的能力。CNCF 在推动云原生领域快速发展的创新和标准化方面起着至关重要的作用。
New Relic Pixie 是一个开源的、Kubernetes 原生的可观测性解决方案,提供现代应用程序的实时监控和追踪功能。它可以帮助开发人员和运维团队快速识别和排查在 Kubernetes 集群上运行的微服务应用程序中的性能问题。Pixie 可以轻松部署在任何 Kubernetes 集群上,并提供对流行的开源工具(如 Prometheus、Jaeger 和 OpenTelemetry)的开箱即用支持。
使用 New Relic Pixie 的一个主要好处是,它提供了从应用代码到底层 Kubernetes 资源的端到端性能可见性。通过收集和分析来自不同来源的数据,包括日志、度量和追踪,Pixie 可以帮助准确找出性能瓶颈和问题的根本原因。这可以显著减少 平均修复时间 (MTTR),并提高应用程序的可靠性和正常运行时间。
New Relic Pixie 的另一个优势是,它采用了一种独特的监控方式,无需任何代码更改或配置。Pixie 使用 扩展伯克利数据包过滤器 (eBPF) 技术在内核级别收集性能数据,从而实现低开销监控,且不会给应用程序或基础设施
Graylog
Graylog 是一个开源的日志管理平台,允许用户从各种来源收集、索引和分析日志数据。该平台提供了一个集中位置,用于监控和排查应用程序、系统和网络基础设施的问题。它构建在 Elasticsearch、MongoDB 和 Apache Kafka 之上,确保了高可扩展性和可用性。
Graylog 具有横向扩展能力,这意味着您可以添加额外的 Graylog 节点来处理增加的日志数据量和查询负载。该系统还可以将工作负载分配到多个节点,从而实现资源的高效利用和数据的快速处理。这种可扩展性使 Graylog 适用于任何规模的组织,从小型初创公司到大型企业。
Graylog 使用 Elasticsearch 作为索引和搜索日志数据的主要数据存储。Elasticsearch 是一个强大的搜索和分析引擎,可以快速高效地查询大型数据集。Graylog 中的 MongoDB 用于存储有关日志数据的元数据,并管理系统的配置和状态。
Graylog 还提供了一个基于 Web 的用户界面,允许用户搜索和可视化日志数据,以及管理系统配置和设置:
图 10.3 – Graylog 日志系统架构
该解决方案的架构非常简单,正如你在前面的图示中所看到的。
Sentry
Sentry 是一个开源的错误追踪工具,帮助开发者监控和修复他们应用中的错误。它允许开发者实时追踪错误和异常,使他们能够在问题变得严重之前迅速诊断和修复问题。Sentry 支持多种编程语言,包括 Python、Java、JavaScript 和 Ruby 等。
使用 Sentry 的一个关键优势是其易于设置和集成。Sentry 可以轻松与流行的框架和平台集成,如 Django、Flask 和 Rails 等。它还提供了一系列插件和与第三方工具的集成,如 Slack 和 GitHub,以帮助开发者简化工作流程并更有效地协作。
Sentry 为开发者提供详细的错误报告,其中包含关于错误的信息,如堆栈跟踪、环境变量和请求参数。这使得开发者能够快速识别错误的根本原因并采取纠正措施。Sentry 还在错误发生时提供实时通知,帮助开发者及时响应。
使用 Sentry 的另一个好处是它能够分析错误随时间的变化。Sentry 允许开发者追踪错误率并识别错误发生的模式,这使得发现和解决应用中的系统性问题变得更容易。这些数据还可以用于改善应用的整体性能和可靠性。
Sentry 提供与 Jira 的集成,Jira 是一个流行的工单和问题追踪系统。该集成使得开发者可以直接从 Sentry 内创建 Jira 问题,从而更方便地管理和追踪通过 Sentry 发现的问题。
要设置集成,首先需要创建 Jira API 令牌并在 Sentry 中配置集成设置。集成设置完成后,你可以通过点击 创建 JIRA 问题 按钮,在 错误详情 页面上直接从 Sentry 创建 Jira 问题。这将自动填充有关错误的相关信息,如错误消息、堆栈跟踪和请求参数。你可以在官方文档页面上找到详细的操作说明:docs.sentry.io/product/integrations/issue-tracking/jira/。
Sentry 提供与其他多个流行的工单和问题追踪系统的集成,如 GitHub、Trello、Asana、Clubhouse 和 PagerDuty,允许你直接从 Sentry 触发 PagerDuty 事件。
在本节中,我们向您展示了几种领先的解决方案,这些解决方案既是开源的,又适合自托管。然而,如果您希望降低部署和维护的复杂性,自托管可能并不是您需要的。下一节将介绍由第三方公司为您托管的监控和日志软件。
SaaS 解决方案
SaaS 监控解决方案是最容易(也是最昂贵)使用的。在大多数情况下,您需要做的就是在服务器或集群内安装并配置一个小型守护进程(代理)。这样,所有的监控数据就会在几分钟内显示出来。如果您的团队没有能力实施其他解决方案,但预算允许您使用 SaaS,那它就是一个不错的选择。以下是一些更受欢迎的应用程序,用于处理您的监控、追踪和日志需求。
Datadog
Datadog 是一个监控和分析平台,提供对应用程序、基础设施和网络性能与健康状况的可视化。它由 Olivier Pomel 和 Alexis Lê-Quôc 于 2010 年创立,总部位于纽约市,并在全球设有办公室。根据 Datadog 2021 财年(截至 2021 年 12 月 31 日)的财务报告,公司的总收入为 20.65 亿美元,同比增长了 60%(相比 2020 财年)。
Datadog 的平台与超过 450 种技术集成,包括云服务提供商、数据库和容器,使用户能够收集和关联来自整个技术栈的数据。它提供实时监控、警报和协作工具,使团队能够排查问题、优化性能并改善用户体验。
Datadog 允许用户监控其服务器、容器和云服务的健康状况和性能,提供关于 CPU 使用率、内存利用率、网络流量等方面的见解。
Datadog 的 APM 工具提供关于 Web 应用程序、微服务和其他分布式系统性能的详细见解,使用户能够识别并诊断瓶颈和问题。
Datadog 的日志管理工具使用户能够收集、处理和分析来自整个基础设施的日志,帮助排查问题并识别趋势。
最后,Datadog 的安全监控通过分析网络流量、识别异常并与安全解决方案集成,帮助检测和响应威胁。
Datadog 中的仪表板功能使用户能够在集中位置可视化和分析来自应用、基础设施和网络的数据。用户可以通过点击创建仪表板按钮并选择他们希望创建的仪表板类型(例如,基础设施、APM、日志或自定义)来创建 Datadog 中的仪表板。然后,他们可以向仪表板添加小部件并配置其设置。有多个自动化仪表板可用;例如,如果您开始从 Kubernetes 集群发送数据,Datadog 将显示一个相关的仪表板。您可以在 Datadog 文档网站上找到更多关于如何使用仪表板的详细信息:docs.datadoghq.com/getting_started/dashboards/。
小部件是 Datadog 仪表板的构建模块。它们可以显示指标、日志、追踪、事件或自定义数据。要添加小部件,用户可以点击**+**按钮并选择他们希望添加的小部件类型。然后,他们可以配置小部件的设置,如选择数据源、应用过滤器和设置时间范围。例如,您可以在 Datadog 网页上查看 nginx Web 服务器的示例仪表板:www.datadoghq.com/dashboards/nginx-dashboard/。
除了在仪表板上显示数据外,Datadog 还提供了各种工具来探索和分析数据,例如查询构建器、Live Tail 和追踪。用户可以使用这些工具深入挖掘数据并排查问题。
New Relic
New Relic是一家基于云的软件分析公司,提供有关 Web 和移动应用性能的实时洞察。New Relic 由 Lew Cirne(拥有在苹果公司和 Wily Technology 等公司工作经验的软件工程师和企业家)于 2008 年创立,现已成为应用性能管理(APM)市场的领先者。公司总部位于旧金山,并在全球多个城市设有办事处。New Relic 于 2014 年上市,并在纽约证券交易所交易,股票代码为NEWR。
New Relic 于 2021 年 5 月公布了 2021 财年的财务报告。根据报告,New Relic 在 2021 财年的总收入为 6.008 亿美元,较上一财年增长了 3%。
值得注意的是,New Relic 在 2021 财年面临了一些挑战,包括 COVID-19 大流行的影响以及向新定价模式的战略转变。
New Relic 的主要目的是帮助公司优化其应用性能,并在问题变成重大问题之前识别它们。该平台提供了对整个应用堆栈的实时可见性,从前端用户界面到后端基础设施,使开发人员和运维团队能够快速识别瓶颈并优化性能。
New Relic 的 APM 解决方案提供多种功能,包括代码级可见性、事务追踪、实时监控和告警。该平台还提供有关应用依赖、数据库性能和用户行为的洞察。
除了 APM,New Relic 还提供一系列其他产品和服务,包括基础设施监控、移动 APM 和浏览器监控。
Ruxit
Ruxit 是一款全面的 APM 解决方案,帮助企业在复杂的分布式应用、微服务和云原生环境中识别和排查性能问题。它最初于 2012 年作为独立公司成立,并在 2015 年被 Dynatrace 收购,扩展了 Dynatrace 的 APM 能力。
Ruxit 的一个关键特性是其能够提供应用性能的端到端可视化,包括代码级诊断、用户体验监控和基础设施监控。这意味着它可以帮助企业快速找出性能问题的根本原因,并发现优化的机会。
Ruxit 还具有一系列其他功能,旨在使监控和故障排除变得更加简单和高效。例如,它使用人工智能和机器学习自动检测异常和性能下降,并实时警报。它还提供一系列分析和可视化工具,帮助用户了解应用性能趋势,并识别随着时间推移的模式。
除了监控功能外,Ruxit 还提供与现代应用环境中常用的其他工具和服务的集成。这包括与容器编排平台(如 Kubernetes)的集成,以及与流行的应用开发框架和工具的集成。
Splunk
Splunk 成立于 2003 年,由 Erik Swan、Rob Das 和 Michael Baum 在美国加利福尼亚州旧金山创办。从那时起,公司的规模迅速扩大,并成为一家全球化的上市公司。Splunk 的软件解决方案被各行各业的组织广泛使用,包括金融服务、医疗保健、政府和零售等行业。
正如你所猜到的,Splunk 是一款数据分析和监控软件解决方案,用于实时监控、搜索、分析和可视化机器生成的数据。该软件可以从多个来源收集和分析数据,包括服务器、应用、网络和移动设备,并提供关于组织 IT 基础设施性能和行为的洞察。
Splunk 的主要用途包括安全监控、应用监控、日志管理和业务分析。使用 Splunk,用户可以识别安全威胁、排查应用性能问题、监控网络活动,并深入了解业务运营。
Splunk 的一个关键特点是其能够从多种来源收集和分析数据,包括结构化数据和非结构化数据。该软件还可以扩展以处理大量数据,使其成为一个强大的工具。
在本节中,我们向您展示了一些由第三方公司托管的领先解决方案,这些解决方案已经可以使用;它们只需要与您的系统进行集成。在下一节中,我们将描述并解释日志和指标的保留策略。
日志和指标保留
数据保留是指保留数据或将数据存储一定时间的做法。这可以包括将数据存储在服务器、硬盘或其他存储设备上。数据保留的目的是确保数据在未来可以用于使用或分析。
数据保留策略通常由组织制定,以确定特定类型的数据应保留多久。这些策略可能受到法规要求、法律义务或业务需求的推动。例如,一些法规可能要求金融机构保留交易数据若干年,而企业可能会选择保留客户数据用于营销或分析目的。
数据保留策略通常包括有关如何存储数据、数据应保留多长时间以及何时应删除数据的指南。有效的数据保留策略可以帮助组织更高效地管理数据、减少存储成本,并确保遵守适用的法规和法律。
在数据保留策略方面,组织有多种选择可供考虑。根据组织的具体需求,不同的策略可能更适合或不适合。
完全保留
在这种策略中,所有数据将被无限期保留。通常用于合规目的,例如满足法规要求,要求数据在特定时间段内进行保留。虽然这种策略可能会很昂贵,因为它需要大量的存储,但它也可以在历史分析和趋势分析方面提供显著的好处。
基于时间的保留
基于时间的保留是一种策略,其中数据在被删除之前会被保留特定的时间段。这种策略通常用于平衡数据需求与存储成本。保留期限可以根据法规要求、业务需求或其他因素来设定。
基于事件的保留
基于事件的保留是一种策略,其中数据根据特定事件或触发条件进行保留。例如,数据可以根据特定的客户或交易进行保留,或者根据事件的严重性进行保留。这种策略可以帮助减少存储成本,同时仍保持对重要数据的访问。
有选择的保留
选择性保留是一种仅保留特定类型数据的策略。这种策略可以用来优先保留最重要的数据,同时减少存储成本。例如,一个组织可能选择仅保留与财务交易或客户互动相关的数据。
分层保留
分层保留是一种根据数据的年龄或重要性将数据存储在不同层级中的策略。例如,最近的数据可能会存储在快速且昂贵的存储上,而较旧的数据则移至速度较慢且成本较低的存储。这种策略可以帮助平衡对近期数据快速访问的需求与逐步降低存储成本的需求。
每种数据保留策略都有其自身的优点和缺点,适合组织的最佳策略将取决于其特定的需求和目标。在选择数据保留策略时,仔细考虑成本、存储容量和所保留数据的价值之间的权衡是非常重要的。
组织中最常见的错误是采用全量保留策略以防万一,这通常会导致磁盘空间耗尽和云成本增加。有时这种策略是合理的,但在大多数情况下并不适用。
总结
在本章中,我们介绍了监控、追踪和日志记录之间的差异。监控是观察和收集系统数据的过程,以确保系统正常运行。追踪是跟踪请求在系统中流动的过程,以识别性能问题。日志记录是记录系统事件和错误的过程,以便后续分析。
我们还讨论了在 Azure、GCP 和 AWS 中的云解决方案,用于监控、日志记录和追踪。对于 Azure,我们提到了 Azure Monitor 用于监控,Azure Application Insights 用于追踪。对于 AWS,我们提到了 CloudWatch 用于监控和日志记录,X-Ray 用于追踪。
接下来,我们解释了如何配置 AWS CloudWatch 代理在 EC2 实例上运行,并通过一个代码示例介绍了如何使用 AWS X-Ray 在分布式系统中追踪请求。
最后,我们列举了一些用于监控、日志记录和追踪的开源和 SaaS 解决方案,包括 Grafana、Prometheus、Datadog、New Relic 和 Splunk。这些解决方案根据用户的需求和偏好,提供了不同的监控和故障排除功能。
在下一章中,我们将通过使用配置即代码解决方案:Ansible,亲自实践自动化服务器配置。
第十一章:使用 Ansible 实现配置即代码
本章我们将介绍 配置管理(CM)、配置即代码(CaC)以及我们选择的工具:Ansible。
我们将覆盖以下主题:
-
CM 系统与 CaC
-
Ansible
-
Ansible Galaxy
-
处理机密信息
-
Ansible Tower 和替代方案
-
高级话题
技术要求
对于本章内容,你需要一个可以通过 ssh 访问的 Linux 系统。如果你的主要操作系统是 Windows,你将需要另外一台 Linux 系统来充当控制节点。到目前为止,Ansible 项目尚不支持 Windows 作为控制节点。
CM 系统与 CaC
设置和维护一个非业余服务器的系统(甚至是业余服务器,可能也需要这样做)是一个严峻的挑战:如何确保系统按照预期正确安装和配置?当你需要安装一台与现有配置完全相同的服务器时,如何确保这一点?过去,一种做法是在安装过程完成后记录当前的配置。这将是一份描述硬件、操作系统、已安装软件、创建的用户以及应用的配置的文档。任何想要重建该配置的人,都需要按照文档中的步骤操作以达到描述的配置。
接下来的合乎逻辑的步骤是编写 shell 脚本,达到与手动过程相同的目标,但有一个额外的改进:这些脚本——只要编写得当、经过测试并且维护良好——不需要人工操作,除非,可能,在初始系统安装时需要人工干预。但一个正确设置的环境将自动处理这一点。
然而,这些脚本也存在一些缺陷或不足。其一是你需要在脚本中考虑未完成执行的情况。这可能由于各种原因发生,导致系统处于部分配置状态。再次执行脚本时,所有配置操作将从头开始,有时会导致意外结果。应对未完成的执行的一种方式是将每个配置操作都包装在检查中,查看该操作是否之前已执行过。这将导致配置脚本变得更大,最终演变成一个配置函数和检查函数的库。
开发和维护这样一个工具的任务可能非常艰巨,并且可能需要一个完整的团队。但最终的结果可能值得付出这些努力。
编写和维护描述系统期望状态的文档,乍一看可能比前面提到的自动化方法更简单且更具吸引力。脚本无法从未完成的执行中恢复过来。它所能做的最好的事情就是通知系统管理员失败,记录错误并优雅地停止。手动配置允许系统管理员绕过程序中的任何障碍和不足,并实时编辑文档以反映当前状态。
尽管如此,一个经过良好开发和测试的脚本最终还是更好。让我们列举出一些原因:
-
如果脚本执行没有出错,保证会执行正确的操作。一次又一次地证明,在 IT 领域,最容易出错的元素是人类。
-
如果脚本提前退出,更新它以满足新需求的行为与更新文档的过程完全相同。
-
众所周知,人类在维护文档方面很差。编程的圣杯是自文档化代码,使注释变得不再必要,从而消除了注释与代码不同步的风险。
-
脚本可以在多个系统上同时执行,扩展性非常好,几乎是无限的。而人类只能一次配置一个系统,且犯错的风险较小。
-
以脚本或程序形式保存的配置得益于典型的编程技巧,例如自动化测试、演练和静态分析。更重要的是,将代码保存在版本库中让我们能够轻松追踪更改历史,并与问题跟踪工具集成。
-
代码是明确的,而书面语言却不能如此。文档可能留有解释的空间,但脚本不会。
-
自动化配置让你可以转向其他更有趣的任务,留给计算机去做它们最擅长的事情——执行重复性且枯燥的任务。
编程和系统管理的世界倾向于将小项目转变为更大的项目,并且有一个充满活力的开发者和用户社区。CM(配置管理)系统的诞生只是时间问题。它们将开发和管理负责配置操作的代码部分的负担从你肩上移走。CM 系统的开发者编写代码、测试并认为它稳定。剩下的工作就是编写配置文件或指令,告诉系统应该做什么。这些系统大多数能够覆盖最流行的平台,使你能够只描述一次配置,并在商业 Unix 系统(如 AIX 或 Solaris)与 Linux 或 Windows 上获得相同的预期结果。
这些系统的配置文件可以轻松地存储在 Git 等版本控制系统中。它们易于理解,这使得同事可以轻松进行审查。它们可以通过自动化工具检查语法错误,并使你能够专注于整个工作中的最重要部分:配置。
这种将配置保留为一组脚本或其他数据,而不是手动遵循的过程的方法,被称为 CaC。
随着需要管理的系统数量不断增加,以及对快速高效配置的需求不断扩大,CaC(配置即代码)方法变得越来越重要。在 DevOps 的世界里,通常需要每天设置数十个或数百个系统:包括开发人员、测试人员和生产系统,以应对服务需求的新水平。手动管理将是一项不可能完成的任务。良好实现的 CaC 允许通过点击按钮来完成这项任务。因此,开发人员和测试人员可以自行部署系统,而不需要打扰系统运维人员。你的任务将是开发、维护和测试配置数据。
如果编程世界里有一件事是可以确定的,那就是永远不会只有一个解决方案。CM 工具也不例外。
Ansible 的替代方案包括SaltStack、Chef、Puppet 和 CFEngine,后者是最古老的,它的首次发布是在 1993 年,因此截至本书撰写时已存在 30 年。通常,这些解决方案通过强制配置的方法(拉取或推送)和描述系统状态的方法(命令式或声明式)有所不同。
命令式(Imperative)方法意味着我们通过命令描述服务器的状态,让工具执行。命令式编程侧重于一步步描述给定工具的操作方式。
声明式(Declarative)方法意味着我们关注 CaC 工具应该完成什么,而不是指定它应该如何实现结果的所有细节。
SaltStack
SaltStack 是一个开源的 CM 工具,允许在大规模的 IT 基础设施中进行管理。它使得日常任务的自动化成为可能,如软件包安装、用户管理和软件配置,并设计用于跨多种操作系统和平台工作。SaltStack 由 Thomas Hatch 于 2011 年创立。SaltStack 的第一个版本 0.8.0 也发布于 2011 年。
SaltStack 通过利用主从架构工作,其中一个中央的 salt-master 与运行在远程机器上的 salt-minion 进行通信,执行命令并管理配置。它采用拉取方式来强制执行配置:minion 从主服务器拉取最新的清单。
一旦 minion 被安装并配置好,我们可以使用 SaltStack 来管理服务器的配置。以下是一个安装并配置 nginx 的示例 nginx.sls 文件:
nginx:
pkg.installed
/etc/nginx/sites-available/yourdomain.tld.conf:
file.managed:
- source: salt://nginx/yourdomain.tld.conf
- user: root
- group: root
- mode: 644
在这个示例中,第一行指定应该在目标服务器上安装nginx包。接下来的两行定义了一个假设网站example.com的配置文件,该配置文件将被复制到/etc/nginx/sites-available/yourdomain.tld.conf。
要将此状态文件应用到服务器,我们需要在 SaltStack 命令行界面中使用state.apply命令,并指定状态文件的名称作为参数:
admin@myhome:~$ salt 'webserver' state.apply nginx
这将把nginx.sls文件中的指令发送到运行在 Web 服务器上的 salt-minion,salt-minion 将执行必要的步骤以确保nginx正确安装和配置。
Chef
Chef是一个强大的开源配置管理(CM)工具,允许用户自动化基础设施、应用程序和服务的部署与管理。它最初由 Opscode 于 2009 年发布,后被 Chef Software Inc.收购。从那时起,Chef 被 IT 专业人员和 DevOps 团队广泛采用,以简化工作流程并减少管理复杂系统所需的时间和精力。
Chef 通过在一组代码文件中定义基础设施的期望状态来工作,这些代码文件称为食谱(cookbooks)。**食谱(cookbook)**是描述如何安装、配置和管理特定软件或服务的一组指令。每个食谱包含一系列资源,资源是预构建的模块,可以执行特定任务,例如安装软件包或配置文件。Chef 使用声明式的配置管理(CM)方法,意味着用户定义系统的期望状态,而 Chef 负责处理如何实现该状态的细节。
要使用 Chef 安装nginx,你首先需要创建一个包含安装nginx食谱的 cookbook。这个食谱将使用package资源来安装nginx包,并使用service资源确保nginx服务正在运行。根据需求,你还可以使用其他资源,例如file、directory或template,来配置nginx的设置。
一旦创建了食谱(cookbook),你需要将其上传到 Chef 服务器,Chef 服务器作为食谱及其相关元数据的中央仓库。然后,你可以使用 Chef 的命令行工具knife来配置目标系统使用该食谱。这涉及将系统与 Chef 环境关联,Chef 环境定义了应应用于系统的食谱及其版本。接下来,你可以使用chef-client命令在目标系统上运行 Chef 客户端,该客户端将下载并应用必要的食谱和配置,以使系统达到所需状态。
这是安装和配置nginx的示例:
# Install Nginx package
package 'nginx'
# Configure Nginx service
service 'nginx' do
action [:enable, :start]
end
# Configure Nginx site
template '/etc/nginx/sites-available/yourdomain.tld.conf' do
source 'nginx-site.erb'
owner 'root'
group 'root'
mode '0644'
notifies :restart, 'service[nginx]'
end
这个食谱使用了三个资源,如下所示:
-
package:使用系统默认的软件包管理器安装nginx包。 -
service:这将启动并启用nginx服务,使其在启动时自动启动并保持运行。 -
template:通过从模板文件生成配置文件,为nginx创建一个配置文件。模板文件(nginx-site.erb)位于食谱的templates目录中。notifies属性告诉 Chef 在配置文件更改时重新启动nginx服务。
一旦你在食谱中创建了这个食谱,你可以使用knife命令将食谱上传到 Chef 服务器。然后,你可以使用chef-client命令将食谱应用于目标系统,按照食谱安装和配置nginx。
Puppet
2.0。
Puppet 通过在声明性语言中定义基础设施资源的期望状态来工作,这种语言被称为 Puppet 语言。管理员可以在 Puppet 代码中定义服务器、应用程序和其他基础设施组件的配置,然后将其一致地应用于多个系统。
Puppet 由一个主服务器和多个代理节点组成。主服务器充当 Puppet 代码和配置数据的中央存储库,而代理节点则执行 Puppet 代码并将期望的状态应用到系统中。
Puppet 有一个强大的模块生态系统,这些模块是预先编写的 Puppet 代码,可以用于配置常见的基础设施资源。这些模块可在Puppet Forge中找到,Puppet Forge 是一个公开的 Puppet 代码存储库。
下面是一个示例 Puppet 清单,它安装nginx并创建一个类似我们在 SaltStack 和 Chef 中所做的配置文件:
# Install Nginx
package { 'nginx':
ensure => installed,
}
# Define the configuration template for the domain
file { '/etc/nginx/sites-available/yourdomain.tld.conf':
content => template('nginx/yourdomain.tld.conf.erb'),
owner => 'root',
group => 'root',
mode => '0644',
notify => Service['nginx'],
}
# Enable the site by creating a symbolic link from sites-available to sites-enabled
file { '/etc/nginx/sites-enabled/yourdomain.tld.conf':
ensure => 'link',
target => '/etc/nginx/sites-available/yourdomain.tld.conf',
require => File['/etc/nginx/sites-available/yourdomain.tld.conf'],
}
# Restart Nginx when the configuration changes
service { 'nginx':
ensure => running,
enable => true,
subscribe => File['/etc/nginx/sites-enabled/yourdomain.tld.conf'],
}
一旦你创建了清单并将其放置在 Puppet 服务器上,它将被安装在服务器上的 Puppet 代理拾取并执行。通信方式与 SaltStack 相同,通过 TLS 协议确保安全,使用与互联网上的 HTTPS 服务器相同的机制。
代理节点运行 Puppet 代理进程,通过 TCP 端口8140连接到主服务器。代理向服务器发送证书签名请求(CSR),管理员必须批准此请求。一旦 CSR 被批准,代理将获得对服务器 Puppet 配置的访问权限。
当代理运行时,它向主服务器发送请求以获取其配置。服务器响应并提供一个资源目录,说明应应用于节点的内容。该目录是根据存储在服务器上的 Puppet 代码和清单,以及配置的任何外部数据源或层次结构生成的。
然后,代理将目录应用到节点,这包括对节点配置进行必要的更改,以确保其与目录中定义的期望状态匹配。这可能包括安装软件包、更新配置文件或启动或停止服务。
代理在应用完目录后将报告发送回服务器,这些报告可用于监控和审计。服务器还可以使用这些信息检测通过 Puppet 未做的节点配置更改,并在必要时采取纠正措施。
CFEngine
CFEngine 是一个开源的配置管理系统,允许用户自动化部署、配置和维护 IT 系统。它由 Mark Burgess 于 1993 年创立,之后成为管理大规模 IT 基础设施的流行工具。CFEngine 以其强大而灵活的语言来描述系统配置和执行策略而闻名,是处理复杂 IT 环境的理想选择。
CFEngine 的第一次发布是在 1994 年,使其成为现存最古老的配置管理工具之一。此后,CFEngine 经历了许多更新和改进,以适应不断变化的 IT 环境和新兴技术。CFEngine 最新版本 3.18 包括改进的加密功能、增强的监控能力和对云基础设施的更好支持。
多年来,CFEngine 因其强大的功能、易用性和强大的社区支持而广受欢迎。今天,许多组织仍在使用它,并且它仍在积极开发,因此它是一个安全的选择,可以用来管理你的服务器配置。
这里将展示一个 CFEngine 配置示例。由于需要,它只是一个片段,而非完整配置:
##############################################################
# cf.main - for master infrastructure server
##################################################################
###
# BEGIN cf.main
###
control:
access = ( root ) # Only root should run this
site = ( main )
domain = ( example.com )
sysadm = ( admin@example.com )
repository = ( /var/spool/cfengine )
netmask = ( 255.255.255.0 )
timezone = ( CET )
#################################################################
files:
Prepare::
/etc/motd m=0644 r=0 o=root act=touch
在本节中,我们已经解释了什么是 CaC,以及为什么它是系统管理员工具箱中的一个重要工具。我们简要描述了你可以使用的最流行的工具。在下一节中,我们将介绍我们首选的工具——Ansible。
Ansible
在本节中,我们将向你介绍 Ansible,这是我们在 CaC 方面的首选工具。
Ansible 是一款用于管理系统和设备配置的工具。它是用 Python 编写的,源代码可以自由下载和修改(在 Apache License 2.0 许可范围内)。名字“Ansible”来源于 Ursula K. Le Guin 的小说 Rocannon’s World,指的是一种无论距离多远都能实现瞬时通信的设备。
这里列出了 Ansible 一些有趣的特点:
-
模块化:Ansible 不是一个单体工具。它是一个核心程序,每个它知道如何执行的任务都被写作一个独立的模块——如果你愿意的话,可以把它看作是一个库。由于从一开始就是这种设计,它产生了一个干净的 API,任何人都可以使用这个 API 来编写自己的模块。
-
幂等性:无论执行多少次配置,结果始终保持不变。这是 Ansible 最重要和最基本的特点之一。你不必知道哪些操作已经执行过。当你扩展配置并再次运行工具时,它的工作是找出系统的状态并仅应用新的操作。
-
无代理:Ansible 不会在配置系统上安装代理。这并不意味着它完全不需要任何东西。为了执行 Ansible 脚本,目标系统需要能够连接到它的一些手段(通常是运行的 SSH 服务器)以及安装 Python 语言。这个范式带来了几个优势,包括以下几点:
-
Ansible 与通信协议无关。它使用 SSH,但并不实现该协议,而是将细节留给操作系统、SSH 服务器和客户端。一个优势是,你可以根据需要自由地将一个 SSH 解决方案替换为另一个,而你的 Ansible 剧本应该照常工作。此外,Ansible 也不关心 SSH 配置的安全性,这让开发人员可以集中精力处理系统的真正问题:配置你的系统。
-
Ansible 项目无需为被管理的节点开发和维护独立的程序。这不仅减轻了开发人员的不必要负担,还限制了安全漏洞被发现并用于攻击目标机器的可能性。
-
在使用代理的解决方案中,如果由于任何原因,代理程序停止工作,就无法将新的配置传递到系统中。SSH 服务器通常使用广泛,故障的概率几乎可以忽略不计。
-
使用 SSH 作为通信协议可以降低防火墙阻止 CM 系统通信端口的风险。
-
-
nginx已安装,正确的配置项应该如下所示:- name: install nginxpackage:name: nginxstate: present
与 Ansible 交互的主要方式是通过编写使用一种叫做 YAML 的特殊语法的配置文件。YAML 是一种专为配置文件设计的语法,基于 Python 的格式化方式。缩进在 YAML 文件中起着重要作用。YAML 的主页提供了完整的备忘单卡片(yaml.org/refcard.xhtml),其中涵盖了该语法。然而,这里将呈现最重要的部分,因为我们在本章中将主要与这些文件打交道:
-
它们是明文文件。这意味着可以使用最简单的编辑器(如 Notepad、Vim、Emacs,或任何你喜欢的文本编辑工具)查看和编辑它们。
-
缩进用于表示范围。不允许使用制表符进行缩进,通常使用空格来进行缩进。
-
新文档以三个短横线(
-)开头。一个文件可以包含多个文档。 -
注释与 Python 中相同,以井号(
#)开始,直到行尾。注释必须被空白字符包围,否则它将被视为文本中的字面井号(#)。 -
文本(字符串)可以不加引号、单引号(
')或双引号(")括起来。 -
当指定一个列表时,每个成员由一个连字符(
-)表示。每个项目将占一行。如果需要单行表示,可以将列表项用方括号([])括起来,并用逗号(,)分隔。 -
关联数组通过键值对表示,每个键和值之间用冒号和空格分隔。如果必须在一行中呈现,数组将用大括号(
{})括起来,键值对之间用逗号(,)分隔。
如果前面的规则现在还不太清楚,别担心。我们将编写正确的 Ansible 配置文件,随着过程的推进,所有问题都会明朗。
Ansible 将机器分为两组:控制节点是存储配置指令的计算机,它将连接到目标机器并进行配置。控制节点可以有多个。目标机器称为库存。Ansible 将在列出的库存中的计算机上运行操作。库存通常以初始化(INI)格式编写,这种格式简单易懂,如下所示:
-
注释以分号(
;)开头。 -
各个部分有名称,并且名称用方括号(
[])括起来。 -
配置指令以键值对的形式存储,每一对独占一行,键和值之间用等号(
=)分隔。
我们稍后会看到一个库存文件的例子。然而,也有可能存在所谓的动态库存,它是通过脚本或每次运行 Ansible 时由系统自动生成的。
我们将要交互的主要配置文件称为剧本。剧本是 Ansible 的入口点,工具将从这里开始执行。剧本可以包括其他文件(这通常会这么做)。
目标主机可以根据自定义标准进行分组:操作系统、组织中的角色、物理位置——任何需要的内容。这样的分组称为角色。
一项要执行的单一操作称为任务。
使用 Ansible 的基础知识
首先要做的是安装 Ansible。这在所有主要的 Linux 发行版上都非常简单,macOS 也同样如此。我们建议您根据所选择的操作系统找到相应的解决方案。
对于基于 Debian 的发行版,以下命令应该足够:
$ sudo apt-get install ansible
对于 Fedora Linux 发行版,您可以运行以下命令:
$ sudo dnf install ansible
然而,要安装最新版本的 Ansible,我们建议使用 Python 虚拟环境及其pip工具,如下所示:
$ python3 -m venv venv
在前面的代码中,我们使用venv python3模块激活了虚拟环境。它将创建一个特殊的venv目录,其中包含所有重要的文件和库,允许我们设置 Python 虚拟环境。接下来,我们有以下内容:
$ source venv/bin/activate
在前面的代码行中,我们读取了一个特殊的文件,该文件通过配置 shell 来设置环境。接下来,我们有以下内容:
$ pip install -U pip
Requirement already satisfied: pip in ./venv/lib/python3.11/site-packages (22.3.1)
Collecting pip
Using cached pip-23.0.1-py3-none-any.whl (2.1 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 22.3.1
Uninstalling pip-22.3.1:
Successfully uninstalled pip-22.3.1
Successfully installed pip-23.0.1
在上面的代码中,我们已经升级了 pip,一个 Python 包管理器。在下一步中,我们将实际安装 Ansible,也使用 pip。为了简洁起见,输出将被缩短:
$ pip install ansible
Collecting ansible
Using cached ansible-7.3.0-py3-none-any.whl (43.1 MB)
Collecting ansible-core~=2.14.3
Installing collected packages: resolvelib, PyYAML, pycparser, packaging, MarkupSafe, jinja2, cffi, cryptography, ansible-core, ansible
Successfully installed MarkupSafe-2.1.2 PyYAML-6.0 ansible-7.3.0 ansible-core-2.14.3 cffi-1.15.1 cryptography-39.0.2 jinja2-3.1.2 packaging-23.0 pycparser-2.21 resolvelib-0.8.1
正如你在上面的代码中看到的,pip 告诉我们 Ansible 及其依赖项已成功安装。
这两种安装 Ansible 的方式都将下载并安装运行 Ansible 所需的所有软件包。
你可以运行的最简单命令是所谓的临时 ping。它如此基础,以至于它成为了 Ansible 在教程和书籍中最常见的入门用法之一。我们也不打算偏离它。以下命令尝试连接到指定主机,并打印连接结果:
$ ansible all -m ping -i inventory
hostone | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
在上面的命令中,我们告诉 Ansible 运行针对清单中的所有主机,使用 ping 模块(-m ping),并使用名为 inventory 的清单文件(-i inventory)。如果你没有指定清单文件,Ansible 将尝试使用 /etc/ansible/hosts,但通常这样做不是个好主意。
我们将在稍后的段落中深入探讨清单文件。
一个 ping 模块(我们也可以理解为一个 ping 命令)。然而,它与操作系统中的 ping 命令不同,后者发送一个特别制作的网络数据包来判断主机是否在线。而 Ansible 的 ping 命令则会尝试登录,以确定凭证是否正确。
默认情况下,Ansible 使用 SSH 协议和一对公私密钥。在正常操作中,你通常不想使用基于密码的身份验证,而是会选择基于密钥的身份验证。
Ansible 在执行结果中非常擅长提供自解释的信息。上面的输出告诉我们,Ansible 成功连接到清单中的所有节点(这里只有一个),python3 已安装,并且没有做任何更改。
清单文件是一个简单但强大的工具,用于列出、分组和为被管理的节点提供变量。我们的示例清单文件如下所示:
[www]
hostone ansible_host=192.168.1.2 ansible_ssh_private_key_file=~/.ssh/hostone.pem ansible_user=admin
上面的代码有两行。第一行声明了一个名为 www 的节点组。第二行声明了一个名为 hostone 的节点。由于这是一个无法通过 DNS 解析的名称,我们通过 ansible_host 变量声明了它的 IP 地址。然后,我们指定了正确的 ssh 密钥文件,并声明了登录时应该使用的用户名(admin)。在这个文件中,我们还可以定义更多内容。有关编写清单文件的详细信息,可以参考 Ansible 项目的文档 (docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.xhtml)。
我们无法涵盖 Ansible 的所有方面,也没有深入探讨我们在这里将使用的大部分功能。然而,Packt 出版社有很多很好的 Ansible 书籍,如果你想深化你的知识,可能会有兴趣选择其中的一些书籍——例如 James Freeman 和 Jesse Keating 的*《Mastering Ansible,第四版》*。
任务(Tasks)
任务是 Ansible 配置的核心。它们正是那些要在托管节点上执行的任务。任务包含在剧本中。它们可以直接放入剧本中(写入剧本文件),也可以间接地通过角色包含。
有一种特殊类型的任务叫做处理(handles)。这是一个只有在另一个任务通知时才会执行的任务。
角色(Roles)
Ansible 的文档将**角色(roles)**定义为“可重用 Ansible 内容(任务、处理程序、变量、插件、模板和文件)的一种有限分发,用于剧本内部使用。”就我们而言,我们可以将它们视为一种将剧本部分进行分组的机制。一个角色可以是我们要执行剧本的主机类型:Web 服务器、数据库服务器、Kubernetes 节点等等。通过将剧本分解为角色,我们可以更容易、更高效地管理单独的任务。更不用说,这些包含文件的内容也变得更加可读,因为我们限制了其中的任务数量,并按其功能进行分组。
剧本和剧本集
**剧本(Plays)**是在任务执行的上下文中。虽然剧本是一个有些短暂的概念,**剧本集(playbooks)**是其物理表现:定义剧本的 YAML 文件。
让我们看一个剧本示例。以下剧本将在托管节点上安装nginx和php软件包:
---
- name: Install nginx and php
hosts: www
become: yes
tasks:
- name: Install nginx
package:
name: nginx
state: present
- name: Install php
package:
name: php8
state: present
- name: Start nginx
service:
name: nginx
state: started
第一行(三个破折号)标志着一个新的 YAML 文档的开始。下一行是整个剧本的名称。剧本的名称应简短但具有描述性。它们最终会出现在日志和调试信息中。
接下来,我们通知 Ansible 该剧本应该在www组中的节点上执行。我们还告诉 Ansible 在执行命令时使用sudo。这是必需的,因为我们在本指南中覆盖的所有发行版都需要 root 权限来安装和删除软件包。
然后,我们开始tasks部分。每个任务都会被命名,并指定我们将要使用的模块(命令),同时命令会附带选项和参数。如你所见,缩进声明了范围。如果你熟悉 Python 编程语言,这对你来说应该是直观的。
在运行这个剧本之前,让我们使用一个非常有用的工具,ansible-lint:
$ ansible-lint install.yaml
WARNING: PATH altered to expand ~ in it. Read https://stackoverflow.com/a/44704799/99834 and correct your system configuration.
WARNING Listing 9 violation(s) that are fatal
yaml[trailing-spaces]: Trailing spaces
install.yaml:1
yaml[truthy]: Truthy value should be one of [false, true]
install.yaml:4
fqcn[action-core]: Use FQCN for builtin module actions (package).
install.yaml:6 Use `ansible.builtin.package` or `ansible.legacy.package` instead.
[...]
yaml[empty-lines]: Too many blank lines (1 > 0)
install.yaml:18
Read documentation for instructions on how to ignore specific rule violations.
Rule Violation Summary
count tag profile rule associated tags
1 yaml[empty-lines] basic formatting, yaml
1 yaml[indentation] basic formatting, yaml
3 yaml[trailing-spaces] basic formatting, yaml
1 yaml[truthy] basic formatting, yaml
3 fqcn[action-core] production formatting
Failed after min profile: 9 failure(s), 0 warning(s) on 1 files.
为了简洁,我已经删除了一部分输出,但你可以看到工具打印了关于 YAML 语法和 Ansible 最佳实践违规的信息。失败是会停止剧本执行的错误类型。警告仅仅是警告:剧本将继续执行,但存在一些违反最佳实践的错误。让我们按如下方式修正我们的剧本:
---
- name: Install nginx and php
hosts: www
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Install php
ansible.builtin.package:
name: php8
state: present
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
现在我们可以使用ansible-playbook命令运行 playbook:
$ ansible-playbook -i inventory install.yaml
PLAY [Install nginx and php] ********************************************************************
TASK [Gathering Facts] ********************************************************************
ok: [hostone]
TASK [Install nginx] ********************************************************************
changed: [hostone]
TASK [Install php] ********************************************************************
fatal: [hostone]: FAILED! => {"changed": false, "msg": "No package matching 'php8' is available"}
PLAY RECAP ********************************************************************
hostone : ok=2 changed=1 reachable=0 failed=1 skipped=0 rescued=0 ignored=0
在前面的输出中,我们指示ansible-playbook命令使用名为inventory的清单文件,并运行名为install.yaml的 playbook。输出应该是自解释的:我们会看到我们运行的 play 的名称。接下来,我们会看到 Ansible 将尝试执行操作的受管节点列表。然后,我们看到任务和任务成功或失败的节点列表。nginx任务在hostone上成功执行。然而,安装php失败了。Ansible 给出了确切的原因:我们的受管节点上没有php8包。有时,解决方案很明显,但有时则需要一些挖掘。经过与我们的发行版检查后,我们发现实际可用的php包是php7.4。在快速更正有问题的行后,我们再次运行 playbook,如下所示:
$ ansible-playbook -i inventory install.yaml
PLAY [Install nginx and php] ********************************************************************
TASK [Gathering Facts] ********************************************************************
ok: [hostone]
TASK [Install nginx] ********************************************************************
ok: [hostone]
TASK [Install php] ********************************************************************
changed: [hostone]
TASK [Start nginx] ********************************************************************
ok: [hostone]
PLAY RECAP ********************************************************************
hostone : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
注意输出中的变化。首先,Ansible 告诉我们hostone上的nginx是正常的。这意味着 Ansible 能够确认包已安装,因此没有采取任何行动。接着,它告诉我们hostone服务器上的php7.4安装成功(changed: [hostone])。
前面的 playbook 很短,但我们希望它能够展示这个工具的有用性。Playbook 是以线性方式执行的,从上到下。
我们的 playbook 存在一个问题。虽然它只安装了两个包,但如果需要安装数十个甚至上百个包,你可能会担心可维护性和可读性。为每个包单独创建任务是麻烦的。幸好,有解决方案。你可以为给定任务创建一个项目列表,任务将对该列表中的每个项目执行——类似于一个循环。让我们如下编辑 playbook 的tasks部分:
tasks:
- name: Install nginx
ansible.builtin.package:
name: '{{ item }}'
state: present
with_items:
- nginx
- php7.4
- gcc
- g++
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
我们添加了两个包,以展示一个更完整的运行过程。注意没有为php7.4单独创建任务。
在运行这个 play 之前,最好先用ansible-lint检查一下。下面是如何操作:
$ ansible-lint install.yaml
Passed with production profile: 0 failure(s), 0 warning(s) on 1 files.
现在,在ansible-lint给出绿色信号后,让我们运行这个 playbook:
$ ansible-playbook -i inventory install.yaml
PLAY [Install nginx and php] ********************************************************************
TASK [Gathering Facts] ********************************************************************
ok: [hostone]
TASK [Install nginx] ********************************************************************
ok: [hostone] => (item=nginx)
ok: [hostone] => (item=php7.4)
changed: [hostone] => (item=gcc)
changed: [hostone] => (item=g++)
TASK [Start nginx] ********************************************************************
ok: [hostone]
PLAY RECAP ********************************************************************
hostone : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
假设我们想在nginx中安装页面配置。Ansible 可以复制文件。我们可以设置它来复制nginx虚拟服务器配置,但我们只希望在服务设置时重启nginx一次。
我们可以通过notify和handlers来实现这一点。我会先粘贴整个 playbook:
---
- name: Install nginx and php
hosts: www
become: true
tasks:
- name: Install nginx
ansible.builtin.package:
name: '{{ item }}'
state: present
with_items:
- nginx
- php7.4
- gcc
- g++
notify:
- Start nginx
- name: Copy service configuration
ansible.builtin.copy:
src: "files/service.cfg"
dest: "/etc/nginx/sites-available/service.cfg"
owner: root
group: root
mode: '0640'
- name: Enable site
ansible.builtin.file:
src: "/etc/nginx/sites-available/service.cfg"
dest: "/etc/nginx/sites-enabled/default"
state: link
notify:
- Restart nginx
handlers:
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
注意到与复制文件相关的整个新部分。我们还创建了一个nginx。为了简洁起见,我们简化了它,只是为了展示一个原理。
执行此 play 后,得到以下输出:
$ ansible-playbook -i inventory install.yaml
PLAY [Install nginx and php] ********************************************************************
TASK [Gathering Facts] ********************************************************************
ok: [hostone]
TASK [Install nginx] ********************************************************************
ok: [hostone] => (item=nginx)
ok: [hostone] => (item=php7.4)
ok: [hostone] => (item=gcc)
ok: [hostone] => (item=g++)
TASK [Copy service configuration] ********************************************************************
ok: [hostone]
TASK [Enable site] ********************************************************************
ok: [hostone]
PLAY RECAP ********************************************************************
hostone ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
一旦你开始在实际生产环境中使用 Ansible,就会明显发现,即使使用我们之前展示的循环技巧,playbook 也会迅速增长并变得难以管理。幸运的是,有一种方法可以将 playbook 分解成更小的文件。
其中一种方法是将任务划分为不同的角色。如前所述,角色是一个抽象的概念,因此最简单的方式是将其视为一个组。你根据对你来说相关的标准将任务分组。虽然标准完全由你决定,但通常情况下,角色会根据给定的受管节点执行的功能类型来命名。一个受管节点可以执行多个功能,但仍然最好将这些功能分开到不同的角色中。因此,HTTP 服务器将是一个角色,数据库服务器将是另一个角色,文件服务器将是另一个角色。
Ansible 中的角色有一个预定的目录结构。定义了八个标准目录,尽管你只需要创建其中一个。
以下代码摘自 Ansible 文档(docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.xhtml):
roles/
common/ # this hierarchy represents a "role"
tasks/ #
main.yml # <-- tasks file can include smaller files if warranted
handlers/ #
main.yml # <-- handlers file
templates/ # <-- files for use with the template resource
ntp.conf.j2 # <------- templates end in .j2
files/ #
bar.txt # <-- files for use with the copy resource
foo.sh # <-- script files for use with the script resource
vars/ #
main.yml # <-- variables associated with this role
defaults/ #
main.yml # <-- default lower priority variables for this role
meta/ #
main.yml # <-- role dependencies
library/ # roles can also include custom modules
module_utils/ # roles can also include custom module_utils
lookup_plugins/ # or other types of plugins, like lookup in this case
roles 目录与 playbook 位于同一级别。你可能会看到的最常见子目录是 tasks、templates、files、vars 和 handlers。在每个子目录中,Ansible 会查找一个 main.yml、main.yaml 或 main 文件(所有这些文件必须是有效的 YAML 文件)。它们的内容将自动提供给 playbook。那么,这在实践中是如何工作的呢?在与我们当前 install.yaml playbook 同一目录下,我们将创建一个 roles 目录。在该目录下,我们将再创建一个目录:www。在这个目录中,我们将创建:tasks、files 和 handlers 目录。我们将为 development 角色创建一个类似的结构:
roles/
www/ # this hierarchy represents a "role"
tasks/ #
main.yml # <-- tasks file can include smaller files if warranted
handlers/ #
main.yml # <-- handlers file
files/ #
service.cfg # <-- files for use with the copy resource
development/ # this hierarchy represents a "role"
tasks/ #
main.yml # <-- tasks file can include smaller files if warranted
development 角色只有任务子目录,因为我们不需要任何额外的附加功能。那么,为什么是开发角色呢?当你回顾我们的当前 playbook 时,你会发现我们将 www 服务器和开发软件包(即编译器)的安装混在一起了。这是一个不好的做法,即使它们最终会安装在同一台物理服务器上。
因此,我们将编辑我们的清单文件,以便我们有两个独立的角色:
[www]
hostone ansible_host=192.168.1.2 ansible_ssh_private_key_file=~/.ssh/hostone.pem ansible_user=admin
[development]
hostone ansible_host=192.168.1.3 ansible_ssh_private_key_file=~/.ssh/hostone.pem ansible_user=admin
两个组只包含一个节点,这种做法整体上是不好的。我们这样做仅仅是为了本指南的目的。不要在 HTTP 服务器上安装编译器和开发软件,尤其是在生产环境中。
现在,我们需要在 playbook 中移动一些内容。install.yml 文件将变得更短,正如我们在这里看到的:
---
- name: Install nginx and php
hosts: www
roles:
- www
become: true
- name: Install development packages
hosts: development
roles:
- development
become: true
其实我们在这个 playbook 中有两个 play。一个是针对 www 主机的,另一个是针对 development 主机的。我们为每个 play 起个名字,并列出希望它运行的主机组,然后使用关键字 roles 列出实际使用的角色。如你所见,只要你遵循之前解释的目录结构,Ansible 会自动定位到正确的角色。当然,也可以通过指定角色的完整路径直接包含角色,但我们这里不做详细讲解。
现在,对于 www 角色,我们将执行以下代码:
---
- name: Install nginx
ansible.builtin.package:
name: '{{ item }}'
state: present
with_items:
- nginx
- php7.4
notify:
- Start nginx
- name: Copy service configuration
ansible.builtin.copy:
src: "files/service.cfg"
dest: "/etc/nginx/sites-available/service.cfg"
owner: root
group: root
mode: '0640'
- name: Enable site
ansible.builtin.file:
src: "/etc/nginx/sites-available/service.cfg"
dest: "/etc/nginx/sites-enabled/default"
state: link
notify:
- Restart nginx
从一开始你就应该注意到几个变化:
-
这份文档中没有
tasks关键字。这是因为该文件是tasks子目录中的main.yaml文件,默认包含任务。 -
缩进已经向左移动。
-
这里没有 handlers。
-
我们已经移除了
gcc和g++包的安装。
现在,让我们来看一下 handlers:
---
- name: Start nginx
ansible.builtin.service:
name: nginx
state: started
listen: "Start nginx"
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
listen: "Restart nginx"
总体变化如这里所示:
-
我们已经移除了
handlers关键字 -
我们已将缩进移到左侧
现在,让我们来看一下 roles/development/tasks/main.yaml:
---
- name: Install compilers
ansible.builtin.package:
name: '{{ item }}'
state: present
with_items:
- gcc
- g++
这非常简单。我们可能增加了目录结构的复杂性,但我们也简化了任务和 play 的编写。当你的 playbook 越来越大,越来越复杂时,这些好处完全值得这个权衡。
使用 CaC 工具有许多优势,具体如下:
-
配置可以进行 lint 检查——也就是说,可以通过自动化工具检查语法错误
-
它可以无限次应用,且结果相同
-
它可以在多个系统上并行运行
-
它可以存放在版本控制系统中,比如 Git,其中保存了变更的历史记录和评论,可以随时查看
-
它可以由自动化工具运行,从而不再需要人工干预,只需编写 playbook 即可
在这一小节中,我们展示了如何编写简单的 Ansible playbook。我们解释了什么是 Ansible,以及配置脚本的基本组成部分。我们介绍了 playbook、角色和清单。Ansible 还可以做很多其他事情。你可以管理设备、文件系统、用户、组、权限、网络等等。Ansible 默认自带的所有模块列表令人印象深刻。你可以随时查看这个列表,访问 docs.ansible.com/ansible/latest/module_plugin_guide/index.xhtml。记得查看你所使用的 Ansible 版本的列表。
所以,现在我们已经介绍了如何安装、配置以及使用 Ansible 来管理你的服务器(安装软件、创建配置文件和管理服务),接下来我们将探讨 Ansible Galaxy:一个社区开发的模块,能够提升 Ansible 的实用性。
Ansible Galaxy
Ansible 是一个强大的自动化工具,使用户能够轻松配置、部署和管理复杂的 IT 基础设施。然而,创建和维护 Ansible playbook 可能会耗费时间,特别是在处理大规模环境时。幸运的是,Ansible Galaxy 通过提供一个预构建角色和 playbook 的中心化存储库,来帮助简化这个过程。
Ansible Galaxy是一个社区驱动的平台,托管着大量的 Ansible 角色和 playbook。这些角色和 playbook 由全球用户提交,并由 Ansible 的维护人员进行审查和策划。Ansible Galaxy 提供了一种简单高效的方法来查找和使用预构建的自动化内容,可以节省用户的时间和精力,同时确保质量和一致性。
使用 Ansible Galaxy,用户可以快速找到、下载和使用流行应用程序、服务和基础设施组件的预构建角色和 playbook。这些预构建组件可以帮助加快部署时间,确保遵循最佳实践,并减少错误或不一致性的可能性。Ansible Galaxy 还可以帮助用户从他人的经验中学习,并获得同行最佳实践的见解。
让我们使用一个 Galaxy 角色来在我们的webserver角色上安装nginx web 服务器。为了做到这一点,我们需要从 Ansible Galaxy 安装角色。首先,请确保在系统上安装了 Ansible,运行以下命令:
admin@myhome:~$ ansible-galaxy install nginxinc.nginx
此命令将从 Ansible Galaxy 下载并安装nginx角色。默认情况下,所有安装的角色都放置在~/.ansible/roles目录中。您可以通过在您的家目录中创建全局 Ansible 配置文件~/.ansible.cfg来更改这一设置。
更改roles_path目录的配置文件示例如下:
[defaults]
roles_path = /home/admin/myansibleroles
一个良好的做法是固定角色版本号,并将该版本保存在与 Ansible playbook 同一 Git 存储库中的 YAML 文件中。为了实现这一点,让我们创建一个ansible_requirements.yml文件:
---
- src: nginxinc.nginx
version: 0.24.0
使用该文件从 Ansible Galaxy 安装角色,您可以运行以下命令:
admin@myhome:~$ ansible-galaxy install -r ansible_requirements.yml
安装角色后,可以通过将以下行添加到 playbook 中,在 Ansible playbook 中使用该角色:
roles:
- nginxinc.nginx
这是一个示例 playbook,使用 Ansible Galaxy 中的nginx角色在远程服务器上安装和配置nginx:
---
- name: Install and configure Nginx
hosts: webservers
become: true
roles:
- nginxinc.nginx
vars:
nginx_sites:
myapp:
template: "{{ playbook_dir }}/templates/myapp.conf.j2"
在这个 playbook 中,我们将webservers组指定为目标主机,并使用nginxinc.nginx角色来安装和配置nginx。我们还定义了一个名为nginx_sites的变量,该变量指定了在 playbook 的templates目录中使用的 Jinja2 模板来创建一个nginx服务器块的配置。
通过使用 Ansible Galaxy 和预构建角色,如 nginxinc.nginx,用户可以快速而可靠地自动化复杂任务,确保一致性并减少错误的风险。
处理机密
保护诸如密码、令牌和证书等机密在任何 IT 基础设施中都至关重要。这些机密是访问敏感信息和服务的钥匙,它们的泄露可能导致严重的安全漏洞。因此,保持它们的安全至关重要。Ansible 提供了多种管理机密的方法,例如 Ansible Vault,它允许用户使用密码或密钥文件加密和解密敏感数据。此功能有助于保护机密,并确保只有授权用户才能访问它们。
将机密保存在 Git 仓库或任何其他公共地方是一个重大的安全风险。这类仓库通常对多个用户开放,其中一些用户可能没有访问敏感数据的必要权限。此外,像 Git 这样的版本控制系统会保留文件修改的历史记录,这可能导致机密不小心泄露。如果用户不小心将机密提交到仓库,或者黑客获取了仓库的提交历史,机密就可能会被暴露。因此,必须避免将机密保存到公共地方,以降低未经授权访问的风险。相反,Ansible 提供了安全的方式来管理机密,确保它们被加密且仅授权用户可访问。这样,用户可以确信他们的机密是安全的。
Ansible Vault
Ansible Vault 是 Ansible 提供的一个功能,允许用户加密和解密敏感数据,如密码、密钥和证书。该保险库创建一个加密文件,只有授权用户才能解密,从而确保敏感数据的安全。保险库可用于存储文件、变量或其他 Ansible 使用的源中的机密。
Ansible Vault 使用多种加密方法来保护其中存储的机密。默认情况下,Ansible Vault 使用 AES 256 加密,这是一个广泛接受且安全的加密算法。此外,Ansible Vault 还支持其他加密算法,如 AES 192 和 AES 128,为加密强度提供了灵活性。当使用 Ansible Vault 加密数据时,用户可以选择使用密码或密钥文件进行加密。这确保了只有持有密码或密钥文件的授权用户才能解密存储在保险库中的机密。
要使用 Ansible Vault 创建一个新的保险库,可以使用以下命令:
admin@myhome:~$ ansible-vault create secrets.yml
New Vault password:
Confirm New Vault password:
这将创建一个名为 secrets.yml 的新加密保险库文件。系统会提示你输入一个密码来加密文件。一旦输入密码,保险库文件将被创建并在默认编辑器中打开。以下是一个示例 secrets.yml 文件:
somesecret: pleaseEncryptMe
secret_pgsql_password: veryPasswordyPassword
要编辑这个秘密文件,你需要使用ansible-vault edit secrets.yml命令,并在之后输入加密密码。
要编写一个 Ansible 任务,从保险库中读取pgsql_password秘密,你可以使用ansible.builtin.include_vars模块,并指定vault_password_file参数。以下是一个示例任务:
- name: Read pgsql_password from Ansible Vault
include_vars:
file: secrets.yml
vault_password_file: /path/to/vault/password/file
vars:
pgsql_password: "{{ secret_pgsql_password }}"
在这个任务中,我们使用include_vars模块从secrets.yml保险库文件中读取变量。vault_password_file参数指定包含解密保险库密码的文件位置。然后,我们将secret_pgsql_password的值赋给pgsql_password变量,可以在剧本的其他地方使用该变量。
请注意,secret_pgsql_password变量应该在保险库中定义。secret_前缀表示该密码是从保险库中检索的。Ansible 不区分常规变量和秘密变量。
当你以提高的 Ansible 调试级别运行这个剧本时,你会注意到 PostgreSQL 密码暴露在调试输出中。为了防止这种情况发生,任何处理敏感信息的任务都可以启用no_log: True选项来执行。
SOPS
Secrets OPerationS(SOPS)是 Mozilla 开发的一个开源工具,允许用户在各种配置文件中安全地存储和管理他们的秘密信息,包括 Ansible 剧本。SOPS 采用混合加密方法,即结合了对称加密和非对称加密,以确保最大安全性。
SOPS 使用主密钥加密秘密信息,主密钥可以是对称的或非对称的。对称加密使用密码或密码短语来加密和解密秘密,而非对称加密使用一对密钥,一个公钥和一个私钥,来加密和解密秘密。SOPS 通过密钥包装加密主密钥,密钥包装是一种使用另一个密钥加密密钥的技术。在 SOPS 中,通常用于密钥包装的密钥是 AWS 密钥管理服务(KMS)密钥,但也可以是Pretty Good Privacy(PGP)密钥或 Google Cloud KMS 密钥。
SOPS 与 Ansible 无缝集成,支持多种文件格式,包括 YAML、JSON 和 INI。它还支持多种云提供商,包括 AWS、Google Cloud 和 Azure,使其成为跨不同环境管理秘密的多功能工具。
这是如何创建一个 SOPS 加密的 YAML 文件并使用community.sops.load_vars将其内容加载到 Ansible 剧本中的示例:
首先,创建一个名为secrets.yaml的 YAML 文件,内容如下:
postgresql_password: So.VerySecret
然后,使用以下命令通过 SOPS 加密文件:
admin@myhome:~$ sops secrets.yaml > secrets.sops.yaml
这将创建一个名为secrets.sops.yaml的加密版本的secrets.yaml文件。现在可以安全地删除secrets.yaml的明文文件。最常见的错误是忘记删除这些文件并将它们提交到 Git 仓库中。
接下来,创建一个新的 Ansible 剧本,命名为database.yml,并包含以下内容:
---
- hosts: dbserver
become: true
vars:
postgresql_password: "{{ lookup('community.sops.load_vars', 'secrets.sops.yaml')['postgresql_password'] }}"
tasks:
- name: Install PostgreSQL
apt:
name: postgresql
state: present
- name: Create PostgreSQL user and database
postgresql_user:
db: mydatabase
login_user: postgres
login_password: "{{ postgresql_password }}"
name: myuser
password: "{{ postgresql_password }}"
state: present
在这个示例中,我们使用 community.sops.load_vars 从加密的 secrets.sops.yaml 文件中加载 postgresql_password 变量。然后,使用 {{ postgresql_password }} Jinja2 语法将 postgresql_password 变量传递给 postgresql_user 任务。
当您使用 Ansible 运行这个 playbook 时,它会使用 SOPS 解密 secrets.sops.yaml 文件,并将 postgresql_password 变量加载到 playbook 中的 postgresql_password 变量中。这确保密码不会以明文形式存储在 playbook 中,为您的敏感信息提供额外的安全层。
有关 SOPS 的更多信息,请参见其官方 GitHub 仓库:github.com/mozilla/sops。
其他解决方案
有几种替代 Ansible Vault 和 SOPS 的解决方案,可以用于安全地管理敏感数据,具体如下:
-
HashiCorp Vault:一个开源工具,用于安全地存储和访问秘密。它提供了一种服务,用于安全和集中存储秘密、访问控制和审计。
-
Blackbox:一个命令行工具,使用 GNU Privacy Guard(GPG)加密和解密文件。它通过为每个需要访问加密数据的用户或团队创建一个单独的 GPG 密钥来工作。
-
Keywhiz:另一个开源的秘密管理系统,提供一种简单的方法来安全地存储和分发秘密。它包括一个用于管理秘密的 Web 界面和一个用于访问秘密的命令行工具。
-
Azure Key Vault, AWS Secrets Manager 或 Google Cloud Secret Manager:在处理云环境时,您可能想要考虑使用的解决方案来存储秘密。
当然,这不是所有可用选项的完整列表。
在本节中,我们介绍了 Ansible Vault 和 SOPS 作为处理秘密(如密码)的方法。在下一节中,我们将介绍 Ansible 的图形前端(GUI)。
Ansible Tower 和替代方案
Ansible Tower 提供了一个集中平台,用于管理 Ansible 自动化工作流,使 IT 团队更容易合作、共享知识和维护基础设施。其一些关键功能包括用于管理 Ansible playbook、库存和作业运行的基于 Web 的界面、用于管理用户权限的 基于角色的访问控制(RBAC)、内置仪表板用于监控作业状态和结果,以及与其他工具和平台集成的 API。
它最早由 Ansible, Inc.(现在是 Red Hat 的一部分)于 2013 年发布,此后成为自动化 IT 工作流的最受欢迎工具之一。
自首次发布以来,Ansible Tower 经历了多次更新和增强,包括支持更复杂的自动化工作流、与 AWS 和 Azure 等云平台的集成以及改进的可扩展性和性能。Ansible Tower 是由 Red Hat 公司发布的商业产品。与 Ansible Tower 最相似的替代品是Ansible WorX(AWX)。
Ansible AWX是 Ansible Tower 的开源替代品,提供与 Tower 类似的许多功能,但具有更大的定制性和灵活性。AWX 于 2017 年首次发布,并迅速成为寻求在不需要商业许可证的情况下大规模实施 Ansible 自动化的组织的热门选择。
Ansible Tower 和 Ansible AWX 之间的主要区别之一是它们的许可模型。Ansible Tower 需要商业许可证,并作为 Red Hat Ansible Automation Platform 的一部分进行销售,而 Ansible AWX 是开源的,可以从 Ansible 官网免费下载。这意味着组织可以在自己的基础设施上部署 AWX,并根据特定需求进行定制,而无需依赖预构建的商业解决方案。
从功能上来看,Ansible Tower 和 Ansible AWX 非常相似,两者都提供基于 Web 的界面用于管理 Ansible 剧本、清单和任务运行,RBAC 用于管理用户权限,并且具有内置的仪表板来监控任务状态和结果。然而,Ansible Tower 提供了一些 Ansible AWX 没有的附加功能,比如与 Red Hat Ansible Automation Platform 的本地集成、先进的分析和报告功能,以及认证模块和集合。
另一个 Ansible Tower 的开源替代品是Ansible Semaphore。与 Tower 类似,它是一个基于 Web 的应用程序,旨在简化 Ansible 剧本和项目的管理。它是一个开源、免费的、易于使用的 Ansible Tower 替代品,允许用户轻松自动化其基础设施任务,而无需大量的编码知识。Ansible Semaphore 的首次发布是在 2016 年,从那时起,它已成为那些希望获得简单而强大的 Web 界面的用户的热门选择,用于管理他们的 Ansible 自动化工作流。
你可以在各自的官方网站上了解更多关于这些替代品的信息:
-
Ansible Tower:
access.redhat.com/products/ansible-tower-red-hat -
Ansible AWX:
github.com/ansible/awx -
Ansible Semaphore:
www.ansible-semaphore.com/
高级主题
在本节中,我们将展示如何处理高级 Ansible 功能和调试技术,以及如何自动检查剧本中可能存在的错误。
调试
为了调试 Ansible 剧本运行中的问题,通常将详细程度提高是有用的,以便获得关于 Ansible 正在执行的操作的更详细输出。Ansible 有四个详细程度:-v,-vv,-vvv和-vvvv。你添加的v越多,输出越详细。
默认情况下,Ansible 以-v运行,提供有关执行任务的基本信息。然而,如果你在剧本中遇到问题,增加详细程度可能会有帮助,以便获得更详细的输出。例如,使用-vv将提供有关正在执行的剧本、角色和任务的额外信息,而使用-vvv还会显示 Ansible 跳过的任务。
要提高 Ansible 剧本运行的详细程度,只需在ansible-playbook命令中添加一个或多个-v选项。以下是一个示例:
admin@myhome:~$ ansible-playbook playbook.yml -vv
这将以-vv详细程度运行剧本。如果你需要更详细的输出,可以添加额外的-v选项。你可以在这里看到一个示例:
admin@myhome:~$ ansible-playbook playbook.yml -vvv
除了使用-v选项外,你还可以通过在ansible.cfg文件中添加以下行来设置详细程度:
[defaults]
verbosity = 2
这将为所有ansible-playbook命令设置-vv的详细程度。你可以将值更改为3或4,以进一步增加详细程度。
如果你需要在详细模式中添加一些自定义通信(例如打印变量),你可以通过使用debug任务来实现。
这是一个示例剧本,演示了如何在-vv debug模式下打印一个变量:
---
- hosts: all
gather_facts: true
vars:
my_variable: "Hello, World!"
tasks:
- name: Print variable in debug mode
debug:
msg: "{{ my_variable }}"
verbosity: 2
在这个剧本中,我们定义了一个my_variable变量,存储了字符串"Hello, World!"。然后,我们使用debug模块通过msg参数打印出这个变量的值。
verbosity: 2这一行启用了debug模式。它告诉 Ansible 将详细程度设置为-vv,这样我们就可以看到debug模块的输出。
Ansible 剧本代码检查
Ansible 代码检查是分析和验证 Ansible 代码语法和风格的过程,确保其符合最佳实践和标准。检查的目的是在代码执行之前捕捉潜在错误或问题,从而节省时间和精力。
最常用的 Ansible 代码检查工具是ansible-lint。这是一个开源命令行工具,用于分析 Ansible 剧本和角色,找出潜在问题并提供改进建议。
要运行ansible-lint检查示例剧本,你可以在终端中执行以下命令:
admin@myhome:~$ ansible-lint sample-playbook.yml
假设示例剧本已保存为sample-playbook.yml,并位于当前工作目录中,其内容如下:
---
- name: (Debian/Ubuntu) {{ (nginx_setup == 'uninstall') | ternary('Remove', 'Configure') }} NGINX repository
ansible.builtin.apt_repository:
filename: nginx
repo: "{{ item }}"
update_cache: true
mode: "0644"
state: "{{ (nginx_state == 'uninstall') | ternary('absent', 'present') }}"
loop: "{{ nginx_repository | default(nginx_default_repository_debian) }}"
when: nginx_manage_repo | bool
- name: (Debian/Ubuntu) {{ (nginx_setup == 'uninstall') | ternary('Unpin', 'Pin') }} NGINX repository
ansible.builtin.blockinfile:
path: /etc/apt/preferences.d/99nginx
create: true
block: |
Package: *
Pin: origin nginx.org
Pin: release o=nginx
Pin-Priority: 900
mode: "0644"
state: "{{ (nginx_state == 'uninstall') | ternary('absent', 'present') }}"
when: nginx_repository is not defined
- name: (Debian/Ubuntu) {{ nginx_setup | capitalize }} NGINX
ansible.builtin.apt:
name: nginx{{ nginx_version | default('') }}
state: "{{ nginx_state }}"
update_cache: true
allow_downgrade: "{{ omit if ansible_version['full'] is version('2.12', '<') else true }}"
ignore_errors: "{{ ansible_check_mode }}"
notify: (Handler) Run NGINX
请注意,ansible-lint的输出可能会根据所使用的 Ansible 版本和ansible-lint版本以及启用的特定规则而有所不同。
作为示例,以下是运行ansible-lint检查提供的示例剧本后的输出:
[WARNING]: empty path for ansible.builtin.blockinfile, path set to ''
[WARNING]: error loading version info from /usr/lib/python3.10/site-packages/ansible/modules/system/setup.py: __version__ = '2.10.7'
[WARNING]: 3.0.0 includes an experimental document syntax parser that could result in parsing errors for documents that used the previous parser. Use `--syntax-check` to verify new documents before use or consider setting `document_start_marker` to avoid using the experimental parser.
sample-playbook.yml:1:1: ELL0011: Trailing whitespace
sample-playbook.yml:7:1: ELL0011: Trailing whitespace
sample-playbook.yml:9:1: ELL0011: Trailing whitespace
sample-playbook.yml:17:1: ELL0011: Trailing whitespace
sample-playbook.yml:22:1: ELL0011: Trailing whitespace
sample-playbook.yml:26:1: ELL0011: Trailing whitespace
sample-playbook.yml:29:1: ELL0011: Trailing whitespace
sample-playbook.yml:35:1: ELL0011: Trailing whitespace
sample-playbook.yml:38:1: ELL0011: Trailing whitespace
sample-playbook.yml:41:1: ELL0011: Trailing whitespace
sample-playbook.yml:44:1: ELL0011: Trailing whitespace
sample-playbook.yml:47:1: ELL0011: Trailing whitespace
sample-playbook.yml:50:1: ELL0011: Trailing whitespace
sample-playbook.yml:53:1: ELL0011: Trailing whitespace
输出指示 Playbook 的多个行存在行尾空白,违反了 ansible-lint 的 ELL0011 规则。关于空路径和版本信息加载的警告消息并不是关键问题,可以安全地忽略。
要修复行尾空白问题,只需删除每个受影响行末尾的额外空格。问题修复后,可以重新运行 ansible-lint 来确保 Playbook 没有进一步的问题。
加速 SSH 连接
Ansible 是一款开源自动化工具,广泛用于部署和管理 IT 基础设施。Ansible 的一个关键特性是能够使用 SSH 与远程服务器进行安全通信。然而,为每个单独任务使用 SSH 可能会耗费时间并影响性能,特别是在处理大量服务器时。为了解决这个问题,Ansible 支持SSH 多路复用,允许多个 SSH 连接共享单个 TCP 连接。
SSH 多路复用通过重用现有的 SSH 连接而不是为每个任务创建新连接来工作。当 Ansible 建立与远程服务器的 SSH 连接时,它打开一个 TCP 套接字并为该连接创建一个控制套接字。控制套接字是用于管理 SSH 连接的特殊套接字。当请求到同一主机的另一个 SSH 连接时,Ansible 会检查是否已存在该连接的控制套接字。如果存在,则 Ansible 会重用现有的控制套接字,并在同一 SSH 连接内为新任务创建一个新通道。
SSH 多路复用的好处在于通过减少 Ansible 需要建立的 SSH 连接数量来节省时间和资源。此外,它可以通过减少为每个任务创建和拆除 SSH 连接的开销来提高 Ansible 的性能。
要在 Ansible 中启用 SSH 多路复用,需要在 SSH 客户端配置文件中配置 ControlMaster 和 ControlPath 选项。ControlMaster 选项启用 SSH 多路复用的使用,而 ControlPath 选项指定控制套接字的位置。默认情况下,Ansible 使用 ~/.ansible/cp 目录存储控制套接字。您还可以通过设置 ControlPersist 选项来配置可以同时复用的最大 SSH 连接数。
要自定义 SSH 多路复用配置,可以通过在 ~/.ansible.cfg 中添加 [ssh_connection] 部分来将 SSH 选项放入默认的 Ansible 配置文件中,如下所示:
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=3600
control_path = ~/.ssh/multiplexing/ansible-ssh-%%r@%%h:%%p
在添加此配置后,确保还创建了 ~/.ssh/multiplexing 目录。
ControlMaster=auto 选项会自动创建一个主会话,如果已经存在主会话,则后续会话将自动进行复用。设置 ControlPersist=3600 将会在后台保持主连接打开,以接受 3600 秒(1 小时)内的新连接。
动态清单
在 AWS 等云环境中,服务器可以根据需求动态创建和终止。手动管理这些服务器可能是一个艰巨的任务,这就是为什么自动化工具,如 Ansible,非常重要。Ansible 的一个关键特性是动态清单,它使其非常适合云环境。
动态清单 是 Ansible 的一个功能,它支持在云环境中自动发现主机(服务器)和组(标签)。在 AWS 中,Ansible 可以使用 弹性计算云(EC2)清单插件来查询 AWS API,获取有关 EC2 实例和组的信息。
要在 AWS 中使用动态清单,你需要在 Ansible 配置中配置 EC2 清单插件。
amazon.aws.aws_ec2 库插件是一个官方的 Ansible 插件,它支持 Amazon EC2 实例的动态清单。要使用此插件,你需要按照接下来的步骤操作。
根据你使用的 Ansible 版本,以及是否安装了完整的 Ansible(而不仅仅是 Ansible Core),你可能需要使用 Ansible Galaxy 安装 AWS 集合插件,方法如下:
admin@myhome:~$ ansible-galaxy collection install amazon.aws
在你的 Ansible 控制节点上安装 boto3 和 botocore 库。你可以使用 pip 包管理器安装它们,方法如下:
admin@myhome:~$ pip install boto3 botocore
创建一个具有必要权限以访问 EC2 实例和组的身份和访问管理(IAM)用户。你可以使用 AWS 管理控制台或 AWS CLI 创建 IAM 用户。确保保存 IAM 用户的访问密钥和秘密访问密钥。
在你的 Ansible 控制节点上创建一个 AWS 凭证文件(~/.aws/credentials),并添加 IAM 用户的访问密钥和秘密访问密钥,如下所示:
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY
在你的项目目录中创建一个 Ansible 清单文件(inventory.yml),并配置它使用 amazon.aws.aws_ec2 插件,方法如下:
plugin: amazon.aws.aws_ec2
regions:
- eu-central-1
filters:
tag:Environment:
- webserver
- frontend
下面是配置选项的简要说明:
-
plugin:指定要使用的清单插件 -
regions:指定要搜索实例的 AWS 区域 -
filters:允许你按标签筛选 EC2 实例
通过运行以下命令测试清单:
admin@myhome:~$ ansible-inventory -i inventory.yml --list
该命令应输出一个 JSON 对象,列出指定区域内所有 EC2 实例,并按 Ansible 标签分组。
这样,你就不需要在每次运行 Ansible 剧本时都更新清单文件,以确保拥有最新的服务器列表。
总结
在本章中,我们介绍了 Ansible CaC 工具。我们已经解释并演示了如何将配置从部落知识和文档(以及描述使系统达到目标状态所需的步骤)转移到能够基于定义良好的语法实现该配置的工具,这为你的组织带来了诸如可重复性、能够并行运行多个配置、自动化测试和执行等好处。
在下一章中,我们将向你介绍基础设施即代码(IaC)。
进一步阅读
-
掌握 Ansible(第四版) 由 James Freeman 和 Jesse Keating 编写
-
Ansible Playbook 基础 由 Gourav Shah 编写
-
Ansible 实际应用自动化 由 Gineesh Madapparambath 编写

被折叠的 条评论
为什么被折叠?



