原文:
annas-archive.org/md5/3c34d287e2879a0f121f1884a118ac03译者:飞龙
第十三章:保护和测试您的 CI/CD 管道
在前几章中,我们探讨了持续集成 (CI) 和持续部署/交付 (CD) ,并将 GitOps 作为核心概念。这两个概念及其相关工具帮助我们更快速地交付更好的软件。然而,技术的一个关键方面是安全性和质量保障。虽然在 DevOps 的早期并没有考虑到安全性,但随着 DevSecOps 的出现,现代 DevOps 现在非常重视安全性。在本章中,我们将尝试理解容器应用程序安全性和测试的相关概念,以及如何在 CI 和 CD 中应用这些概念。
在本章中,我们将涵盖以下主要内容:
-
安全性和测试 CI/CD 管道
-
回顾博客应用程序
-
容器漏洞扫描
-
管理机密
-
二进制授权
-
使用拉取请求进行发布门控,并将我们的应用部署到生产环境
-
现代 DevOps 管道的安全性和测试最佳实践
技术要求
在本章中,我们将启动一个基于云的 Kubernetes 集群,Google Kubernetes Engine (GKE),用于练习。目前,Google Cloud Platform (GCP) 提供一个免费的 $300 试用,持续 90 天,因此你可以前往console.cloud.google.com/注册。
你还需要克隆以下 GitHub 仓库,以便进行一些练习:
github.com/PacktPublishing/Modern-DevOps-Practices-2e。
你可以使用 GCP 上提供的 Cloud Shell 服务来跟随本章内容。进入 Cloud Shell 并启动一个新会话。运行以下命令将仓库克隆到你的主目录,以便访问所需资源:
$ git clone https://github.com/PacktPublishing/Modern-DevOps-Practices-2e.git \
modern-devops
我们还需要设置项目 ID,并启用一些我们将在本章中使用的 GCP API。为此,请运行以下命令:
$ PROJECT_ID=<YOUR_PROJECT_ID>
$ gcloud services enable iam.googleapis.com \
container.googleapis.com \
binaryauthorization.googleapis.com \
containeranalysis.googleapis.com \
secretmanager.googleapis.com \
cloudresourcemanager.googleapis.com \
cloudkms.googleapis.com
接下来,让我们看看如何保护和测试 CI/CD 管道。
安全性和测试 CI/CD 管道
随着持续的网络威胁和网络安全专家与网络犯罪分子之间的持续斗争,安全性一直是大多数组织的首要任务,并且它也构成了成熟组织投资的一个重要部分。
然而,安全性伴随着成本。大多数组织都有网络安全团队定期审核他们的代码并提供反馈。然而,这一过程通常较慢,并且发生在大部分代码已经开发完成且难以修改时。
类似地,虽然大多数组织都强调自动化测试,但许多仍然严重依赖手动测试。手动测试不仅劳动密集,而且缺乏可重复性。DevOps 非常重视自动化测试,以确保每次发布时都能重复执行,能够发现现有问题并彻底测试新功能。此外,自动化对于高效地进行回归测试至关重要,因为在这种情况下,手动测试效率低下。
因此,在开发初期嵌入安全性和测试是现代 DevOps 的一个关键目标。将安全性与 DevOps 相结合催生了 DevSecOps 的概念,在这个概念中,开发人员、网络安全专家和运维团队协同工作,更快地创建更好、更安全的软件。
使用 CI/CD 流水线进行软件安全和测试可以带来许多显著的商业优势。首先,它通过保护敏感数据、防止漏洞并确保合规性来确保安全性。其次,它通过早期发现问题、一致性和更高的产品质量来提升质量和可靠性。这将通过减少返工、加速上市时间和优化资源使用来降低成本。此外,它通过增强韧性和进行压力测试来降低风险。更重要的是,它通过灾难恢复和高效的回滚程序来确保业务连续性。此外,它通过促进更快的创新和市场响应来提供竞争优势。最后,通过增强客户对产品和服务的信任以及保护品牌声誉,它提高了声誉和客户信任。总之,保护和测试 CI/CD 流水线不仅是技术上的必要性,也是战略性的商业迫切需求,它提升了安全性、质量和可靠性,同时降低了成本和风险,最终提高了客户满意度、业务连续性和市场中的竞争力。
在软件供应链中嵌入安全性的方式有很多种。某些方法可能包括静态代码分析、安全性测试,以及在过程中应用组织特定的安全策略,但安全的目的是不让开发速度变慢。我们可以用工具来代替人工输入,这些工具能够显著改善我们开发软件的安全性。类似地,测试不必是手动和缓慢的,相反,应该使用自动化来与 CI/CD 流水线无缝对接。
CI/CD 流水线是现代 DevOps 的核心特性之一,它协调所有流程并结合所有工具以更快交付更好的软件,但你如何确保它们的安全性呢?你可能想问以下问题:
-
如何扫描容器镜像以查找漏洞?
-
我如何安全地存储和管理敏感信息和机密?
-
如何确保在部署到生产环境之前测试我的应用程序?
-
如何确保只有经过测试和批准的容器映像才能部署到生产环境?
在本章中,我们将尝试使用最佳实践和工具来回答这些问题。作为参考,请查看以下工作流程图:
图 13.1 – 安全的 CI/CD 工作流程
如前图所示,我们需要修改 CI 管道以包括额外的漏洞扫描步骤。我们还需要两个 CD 管道,一个用于开发环境,另一个用于生产环境。为增强可重用性,我们将重新构造 GitHub Actions 工作流程。我们将工作流划分为父工作流和子工作流。让我们从检查用于开发环境的 CD 工作流开始,以获取概述:
name: Dev Continuous Delivery Workflow
on:
push:
branches: [ dev ]
jobs:
create-environment-and-deploy-app:
name: Create Environment and Deploy App
uses: ./.github/workflows/create-cluster.yml
secrets: inherit
run-tests:
name: Run Integration Tests
needs: [create-environment-and-deploy-app]
uses: ./.github/workflows/run-tests.yml
secrets: inherit
binary-auth:
name: Attest Images
needs: [run-tests]
uses: ./.github/workflows/attest-images.yml
secrets: inherit
raise-pull-request:
name: Raise Pull Request
needs: [binary-auth]
uses: ./.github/workflows/raise-pr.yml
secrets: inherit
工作流程从 name 开始,然后声明 on push branches dev。此配置确保工作流在每次推送到 dev 分支时触发。我们按顺序定义多个作业,每个作业都使用 needs 属性依赖于前一个作业。每个作业通过设置 uses 属性调用子工作流,并通过为 secrets 属性设置 inherit 来向这些子工作流提供 GitHub 秘密。
该工作流程完成以下任务:
-
设置开发 Kubernetes 集群,配置 Argo CD 和支持工具以建立环境,并部署示例博客应用程序。
-
在部署的博客应用上执行集成测试。
-
如果测试通过,它会利用二进制授权(详细信息将在后续提供)来证明图像,确保只有经过测试的构件允许部署到生产环境。
-
为部署到生产环境启动拉取请求。
类似地,我们有以下生产 CD 工作流文件:
name: Prod Continuous Delivery Workflow
on:
push:
branches: [ prod ]
jobs:
create-environment-and-deploy-app:
name: Create Environment and Deploy App
uses: ./.github/workflows/create-cluster.yml
secrets: inherit
run-tests:
name: Run Integration Tests
needs: [create-environment-and-deploy-app]
uses: ./.github/workflows/run-tests.yml
secrets: inherit
此工作流类似于开发工作流,但不包括 binary-auth 和 raise-pull-request 步骤,因为在这个阶段它们是不必要的。为了更好地理解它,让我们从检查开发工作流开始。开发工作流的初始步骤涉及创建环境并部署应用程序。但在继续之前,让我们在下一节重新审视博客应用程序。
重新审视博客应用程序
正如我们在上一章节中已经讨论的博客应用程序,让我们再次查看服务及其交互如下图所示:
图 13.2 – 博客应用服务和交互
我们已经使用 GitHub Actions 创建了 CI 和 CD 管道,用于构建、测试和推送我们的博客应用程序微服务容器,并在 GKE 集群中使用 Argo CD 部署它们。
如果你还记得,我们为应用程序创建了以下资源,以使其无缝运行:
-
MongoDB – 我们部署了一个启用了身份验证的 MongoDB 数据库,并设置了 root 凭证。凭证通过来自 Kubernetes Secret资源的环境变量注入。为了持久化数据库数据,我们创建了一个挂载到容器的PersistentVolume,并使用PersistentVolumeClaim动态提供它。由于容器是有状态的,我们使用了StatefulSet来管理它,因此需要一个无头的 Service 来暴露数据库。
-
帖子、评论、评分和用户 – 帖子、评论、评分和用户微服务通过来自同一Secret资源的环境变量注入的 root 凭证与 MongoDB 进行交互。我们使用各自的Deployment资源部署了这些服务,并通过各自的ClusterIP服务暴露它们。
-
Frontend – 前端微服务不需要与 MongoDB 交互,因此不与Secret资源发生交互。我们同样使用Deployment资源部署了该服务。由于我们希望将该服务暴露到互联网,我们为它创建了一个LoadBalancer Service。
我们可以通过以下图表来总结它们:
图 13.3 – 博客应用程序 – Kubernetes 资源与交互
在随后的章节中,我们将涵盖实施此工作流程的所有方面,从漏洞扫描开始。
容器漏洞扫描
完美的软件开发和维护成本高昂,每当有人对正在运行的软件进行更改时,破坏某些功能的可能性就很高。除了其他错误之外,更改还会引入大量软件漏洞。作为软件开发人员,您无法避免这些问题。网络安全专家和网络犯罪分子之间的斗争是持续进行的,并随着时间不断发展。每天都会发现并报告一组新的漏洞。
在容器中,漏洞可能在多个方面存在,且可能与您负责的部分完全无关。好吧,开发人员编写代码,优秀的开发人员会确保代码的安全性。但您永远不知道基础镜像中是否存在开发人员可能完全忽视的漏洞。在现代的 DevOps 中,漏洞是不可避免的,关键是尽可能地减少它们。我们应该减少漏洞,但手动处理漏洞是耗时的,容易导致繁琐的工作。
市场上有多个工具可以提供容器漏洞扫描服务。其中一些是开源工具,如Anchore、Clair、Dagda、OpenSCAP、Sysdig 的Falco,或者是可通过Google Container Registry(GCR)、Amazon Elastic Container Registry(ECR)和Azure Defender提供的SaaS服务。对于本章内容,我们将讨论Anchore Grype。
Anchore Grype (github.com/anchore/grype) 是一个容器漏洞扫描器,它会扫描你的镜像中的已知漏洞并报告其严重性。根据这些信息,你可以采取适当的措施,通过更换基础镜像或修改层来删除易受攻击的组件,从而防止漏洞的发生。
Anchore Grype 是一个简单的命令行界面(CLI)工具,可以作为二进制文件安装并在任何地方运行——无论是在本地系统还是 CI/CD 流水线中。你还可以配置它,如果漏洞级别超过特定阈值,自动使流水线失败,从而将安全性嵌入到你的自动化中——所有这些都不会给开发或安全团队带来麻烦。
现在,让我们继续看看 Anchore Grype 的实际操作情况。
安装 Anchore Grype
由于我们希望在 CI 流水线中实施漏洞扫描,让我们修改我们在第十一章中创建的 mdo-posts 仓库。
让我们首先使用以下命令克隆仓库,并 cd 进入 workflows 目录:
$ git clone git@github.com:<your_github_user>/mdo-posts.git
$ cd mdo-posts/.github/workflows/
Anchore Grype 在其 GitHub 仓库中提供了一个安装脚本,你可以下载并运行,它应该会为你设置好。我们将修改 build.yaml 文件,在 登录到 Docker Hub 步骤之前添加以下步骤,以便在 CI 工作流中安装 Grype:
- name: Install Grype
id: install-grype
run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s
-- -b /usr/local/bin
接下来,我们需要使用 Grype 扫描我们的镜像以检查漏洞。
扫描镜像
要运行容器漏洞扫描,我们可以使用以下命令:
$ grype <container-image>
这将报告镜像中漏洞的列表,并按严重性划分—Negligible(轻微)、Low(低)、Medium(中等)、High(高)、Critical(严重)或Unknown(未知)。我们还可以在 Grype 中设置阈值,当任何漏洞的级别等于或高于该阈值时使其失败。例如,如果我们不希望容器中出现任何Critical(严重)漏洞,我们可以使用以下命令:
$ grype -f critical <container-image>
为此,我们将在 build.yaml 文件中,在 构建 Docker 镜像 步骤后添加以下步骤:
- name: Scan Image for Vulnerabilities
id: vul-scan
run: grype -f critical ${{ secrets.DOCKER_USER }}/mdo-posts:$(git rev-parse --short
"$GITHUB_SHA")
由于我们已经做出了所有更改,让我们使用以下命令推送修改后的 CI 流水线:
$ cp ~/modern-devops/ch13/grype/build.yaml .
$ git add --all
$ git commit -m "Added grype"
$ git push
一旦我们推送镜像,我们将在 GitHub Actions 标签页中看到以下内容:
图 13.4 – 漏洞扫描失败
如我们所见,Grype 已报告了多个漏洞,其中一个为 Critical(严重)。它还导致了 CI 流水线失败。这就是自动化漏洞扫描的实际应用。它会发现漏洞,并且只有当构建满足最低安全标准时,才会将其发布到你的容器注册表中。
我们需要修复这里的问题,因此让我们查看一个更新的镜像,看看它是否能解决这个问题。因此,除了使用python:3.7-alpine,我们将使用python:alpine3.18。让我们这样做,并使用以下命令将代码推送到 GitHub:
$ cd ~/mdo-posts && cp ~/modern-devops/ch13/grype/Dockerfile .
$ git add --all
$ git commit -m "Updated base image"
$ git push
让我们重新查看 GitHub Actions,看看在build输出中得到什么:
图 13.5 – 漏洞扫描成功
这次漏洞扫描没有阻止我们的 CI 构建,因为没有发现Critical漏洞。
提示
随着时间的推移,不断更新基础镜像,因为更新后的镜像包含更少的漏洞,并修复了旧版中的漏洞。
现在我们已经确保镜像没有漏洞,我们的 CI 管道已经完成。您可以根据需要为其他微服务复制此过程。让我们继续讨论 CD 管道。
如果您记得,在上一章中,我们按照 GitOps 模式将所有资源的清单存储在 Git 上。然而,由于 Kubernetes Secrets 存在安全隐患,我们使用了SealedSecrets来安全地管理它们。
然而,由于以下固有问题,这可能并不是所有团队的理想解决方案:
-
SealedSecrets 依赖于加密它们的控制器。如果我们丢失了这个控制器,我们也失去了重新创建机密的能力,从而实质上丧失了该机密。
-
访问机密仅限于登录集群并使用
kubectl,这种方式不会为非管理员提供管理机密的能力。虽然这种方法可能适合某些团队,但可能不适合其他团队。
因此,我们将探索使用密钥管理工具来管理密钥,以建立一种标准化的方法,集中管理密钥,并对访问权限进行更精细的控制。让我们在下一节深入探讨这个话题。
管理机密
软件始终需要访问敏感信息,如用户数据、凭证、开放授权(OAuth)令牌、密码以及其他被称为机密的信息。在开发和管理软件时,确保这些方面的安全性一直是一个关注点。CI/CD 管道可能会处理这些信息,因为它们通过结合代码和来自多个源的其他依赖项来构建并交付工作软件,其中可能包括敏感信息。保持这些信息的安全性至关重要;因此,需要使用现代的 DevOps 工具和技术,将安全性嵌入到 CI/CD 管道中。
大多数应用程序代码都需要访问敏感信息。这些信息在 DevOps 领域被称为机密。机密是任何有助于某人证明其身份、进行身份验证以及授权特权账户、应用程序和服务的数据。以下是一些可能构成机密的候选项:
-
密码
-
API 令牌、GitHub 令牌以及其他任何应用程序密钥
-
安全外壳(SSH)密钥
-
传输层安全性(TLS)、安全套接字层(SSL)和相当好的隐私(PGP)私钥
-
一次性密码
一个好的例子是,容器需要访问 API 密钥以进行第三方 API 身份验证,或者需要用户名和密码来进行后端数据库的身份验证。开发人员需要理解在哪里以及如何存储机密,以确保它们不会无意中暴露给不应查看它们的人。
当我们运行 CI/CD 管道时,必须理解如何处理这些机密,因为在 CI/CD 管道中,我们从源代码开始构建一切。“不要将机密与代码一起存储”是我们都听过的重要建议。
提示
永远不要将硬编码的机密存储在 CI/CD 管道中,或将机密存储在源代码仓库中,如 Git。
我们如何在不将机密包含在代码中的情况下访问它们,从而运行完全自动化的 GitOps 基于的 CI/CD 管道呢?好吧,这就是我们需要弄清楚的事情。
提示
使用容器时,应该避免将机密嵌入到镜像中。虽然这是一个广为人知的建议,但许多开发人员无意中这么做,导致许多安全漏洞。这是非常不安全的,你应该避免这样做。
你可以通过使用某种形式的秘密管理解决方案来克服这个问题。秘密管理解决方案或密钥管理解决方案帮助存储和管理机密,并通过静态和传输加密保护它们。云服务提供商内部有秘密管理工具,如 GCP 中的秘密管理器和亚马逊 Web 服务(AWS),或者如果你希望使用与云无关的工具,可以使用第三方工具,如HashiCorp Vault。所有这些解决方案都提供 API 用于在运行时创建和查询机密,并通过 HTTPS 安全 API 以允许加密传输。这样,你就不需要将机密与代码一起存储或嵌入镜像中。
在本讨论中,我们将使用 GCP 提供的秘密管理器解决方案来存储机密,并在运行 CI/CD 管道时访问它们。秘密管理器是 Google Cloud 的秘密管理系统,它帮助你集中存储和管理机密。它非常安全,使用硬件安全模块(HSMs)进一步加固你的机密。
在本章中,我们将着眼于改进上章讨论的博客应用程序的 CI/CD 管道,并将使用相同的示例应用程序。因此,接下来我们将创建 Google Cloud Secret Manager 中的 mongodb-creds 机密。
在 Google Cloud Secret Manager 中创建一个机密
让我们创建一个名为 external-secrets 的机密,在其中我们将以 JSON 格式传递 MongoDB 凭据。为此,请运行以下命令:
$ echo -ne \
'{"MONGO_INITDB_ROOT_USERNAME": "root", "MONGO_INITDB_ROOT_PASSWORD": "itsasecret"}' \
| gcloud secrets create external-secrets --locations=us-central1 \
--replication-policy=user-managed --data-file=-
Created version [1] of the secret [external-secrets].
在前面的命令中,我们将包含 MONGO_INITDB_ROOT_USERNAME 和 PASSWORD 的 JSON 直接传递给 gcloud secrets create 命令。我们指定了一个特定的位置,以避免在其他区域重复它,从而节省成本。然而,强烈建议复制密钥,以防在区域性故障时发生潜在的丢失。该 JSON 被存储为密钥的新版本。Secret Manager 使用版本控制来管理密钥,因此分配给密钥(external-secrets)的任何新值都会被版本化并存储在 Secret Manager 中。你可以通过版本号或使用 latest 关键字来引用特定版本,以访问最新的版本。
从输出中可以看到,我们已经创建了密钥的第一个版本(版本 1)。通常,这是在开发阶段完成的,应该保持在 CI/CD 流程之外。你可以将 Secret 资源清单保存在 Secret Manager 中,而不是存储在源代码库中。
现在我们已经创建了密钥,我们必须在应用程序中访问它。为此,我们需要一个工具来访问 Kubernetes 集群中的 Secret Manager 中存储的密钥。为此,我们将使用External Secrets Operator。
使用 External Secrets Operator 访问外部密钥
External Secrets Operator (external-secrets.io/latest/) 是一个 Kubernetes 操作器,用于在 Kubernetes 集群中安全地管理外部密钥。它旨在自动检索和管理存储在外部密钥存储系统中的密钥,如 AWS Secret Manager、GCP Secret Manager、Hashicorp Vault 等,并将它们作为 Kubernetes Secrets 注入到 Kubernetes pod 中。操作器是一种扩展 Kubernetes 功能并自动化任务的方式。
它是如何工作的
External Secrets Operator 作为 Kubernetes 集群与外部密钥管理系统之间的桥梁。我们在 Kubernetes 集群中定义一个 ExternalSecret 自定义资源,操作器会监视它。当创建或更新 ExternalSecret 资源时,操作器与 ClusterSecretStore CRD 中指定的外部密钥存储交互,以检索密钥数据。然后,它创建或更新相应的 Kubernetes Secrets。该过程在下面的图示中展示:
图 13.6 – 外部密钥操作器
现在,这个过程有很多好处,其中一些如下:
-
增强安全性:密钥保存在专用的安全密钥存储中
-
自动化:自动化密钥的检索和轮换
-
简化部署:简化了 Kubernetes 应用程序中密钥的管理
-
兼容性:支持多种外部密钥存储,使其具有多功能性
现在,让我们继续在 Kubernetes 集群中安装 External Secrets Operator。
安装 External Secrets Operator
外部密钥操作符可通过 manifests/argocd/external-secrets.yaml 清单文件获取:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-secrets
namespace: argocd
spec:
project: default
source:
chart: external-secrets/external-secrets
repoURL: https://charts.external-secrets.io
targetRevision: 0.9.4
helm:
releaseName: external-secrets
destination:
server: "https://kubernetes.default.svc"
namespace: external-secrets
应用清单在 default 项目中的 argocd 命名空间下创建了一个 external-secrets 应用。它从 external-secrets Helm 图表仓库下载 0.9.4 版本,并将图表部署到 Kubernetes 集群中的 external-secrets 命名空间。
要安装此应用,我们需要使用 Terraform 应用此清单。因此,我们在 app.tf 文件中做出如下输入:
data "kubectl_file_documents" "external-secrets" {
content = file("../manifests/argocd/external-secrets.yaml")
}
resource "kubectl_manifest" "external-secrets" {
depends_on = [
kubectl_manifest.argocd,
]
for_each = data.kubectl_file_documents.external-secrets.manifests
yaml_body = each.value
override_namespace = "argocd"
}
为了部署这一应用,我们必须将这些文件提交到源代码管理。让我们克隆上一章节中创建的 mdo-environments 仓库。
如果你没有按照上一章节的步骤操作,可以通过以下方法设置一个基础环境。如果你已经在第十二章中完成了环境设置,可以跳过下一节,继续进行持续部署/交付与 Argo CD。
设置基础环境
为了确保与上一章节的连续性,让我们首先创建一个服务帐户,以便 Terraform 可以与我们的 GCP 项目交互,使用以下命令:
$ gcloud iam service-accounts create terraform \
--description="Service Account for terraform" \
--display-name="Terraform"
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:terraform@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/editor"
$ gcloud iam service-accounts keys create key-file \
--iam-account=terraform@$PROJECT_ID.iam.gserviceaccount.com
你会在工作目录中看到一个名为 key-file 的文件。现在,在 GitHub 上创建一个名为 mdo-environments 的新仓库,并添加一个 README.md 文件,重命名 main 分支为 prod,并使用 GitHub 创建一个名为 dev 的新分支。然后访问 https://github.com/<your_github_user>/mdo-environments/settings/secrets/actions/new,创建一个名为 GCP_CREDENTIALS 的密钥。在值字段中,打印出 key-file 文件的内容,复制并粘贴到 GitHub 密钥的 values 字段中。
接下来,创建另一个密钥 PROJECT_ID,并在 values 字段中指定你的 GCP 项目 ID。
接下来,我们需要为 Terraform 创建一个 GCS 存储桶,作为远程后端使用。为此,运行以下命令:
$ gsutil mb gs://tf-state-mdo-terraform-${PROJECT_ID}
现在,所有前提条件都已满足,我们可以克隆我们的仓库并复制基础代码。运行以下命令来完成此操作:
$ cd ~ && git clone git@github.com:<your_github_user>/mdo-environments.git
$ cd mdo-environments/
$ git checkout dev
$ cp -r ~/modern-devops/ch13/baseline/* .
$ cp -r ~/modern-devops/ch13/baseline/.github .
既然我们已经设置了基础环境,接下来让我们继续使用 Terraform 安装外部密钥。
使用 Terraform 安装外部密钥
让我们配置本地仓库来安装外部密钥清单。为此,使用以下命令复制应用清单和 app.tf 文件:
$ cp ~/modern-devops/ch13/install-external-secrets/app.tf terraform/app.tf
$ cp ~/modern-devops/ch13/install-external-secrets/external-secrets.yaml \
manifests/argocd/
既然一切准备就绪,让我们使用以下命令提交并推送我们的代码:
$ git add --all
$ git commit -m "Install external secrets operator"
$ git push
一旦我们推送代码,就会看到 GitHub Actions 工作流被触发。要访问该工作流,请访问 https://github.com/<your_github_user>/mdo-environments/actions。不久后,工作流会应用配置,创建 Kubernetes 集群,并部署 Argo CD、Sealed Secrets 控制器和 External Secrets Operator。
一旦工作流成功执行,我们可以按照以下步骤访问 Argo Web UI。
我们首先需要对 GKE 集群进行身份验证。为此,运行以下命令:
$ gcloud container clusters get-credentials \
mdo-cluster-dev --zone us-central1-a --project $PROJECT_ID
要使用 Argo CD Web UI,您需要argo-server服务的外部 IP 地址。要获取该地址,请运行以下命令:
$ kubectl get svc argocd-server -n argocd
NAME TYPE EXTERNAL-IP PORTS AGE
argocd-server LoadBalaner 34.122.51.25 80/TCP,443/TCP 6m15s
所以,现在我们知道可以通过https://34.122.51.25/访问 Argo CD。
接下来,我们将运行以下命令来重置管理员密码:
$ kubectl patch secret argocd-secret -n argocd \
-p '{"data": {"admin.password": null, "admin.passwordMtime": null}}'
$ kubectl scale deployment argocd-server --replicas 0 -n argocd
$ kubectl scale deployment argocd-server --replicas 1 -n argocd
现在,允许两分钟来生成新的凭证。然后,执行以下命令来获取密码:
$ kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d && echo
现在我们已经拥有了凭证,登录后将看到以下页面:
图 13.7 – Argo CD Web UI – 主页
如我们所见,有三个应用程序——SealedSecret清单,它是我们在上一章中创建的,因为它是由不同的 Sealed Secrets 控制器生成的。
我们不需要 Sealed Secrets 操作员,我们将改用 Google Cloud Secret Manager。所以,让我们使用以下命令从集群中移除它:
$ rm -rf manifests/sealed-secrets
$ git add --all
$ git commit -m "Removed sealed secrets"
$ git push
我们已移除 Sealed Secrets 操作员,Argo CD Web UI 应该很快反映出这一变化。然而,博客应用程序将保持降级状态,因为mongodb-creds Secret 仍然缺失。在接下来的部分中,我们将使用 External Secrets Operator 生成mongodb-creds Secret。
使用 External Secrets Operator 生成 MongoDB Kubernetes Secret
为了生成mongodb-creds Secret,我们需要创建以下资源:
-
一个
Secret资源——这是一个标准的 Kubernetes Secret 资源,包含服务账户凭证,以便 Kubernetes 与 GCP Secret Manager 连接。 -
一个
ClusterSecretStore资源——该资源包含与密钥存储(在此情况下为 GCP Secret Manager)连接的配置,并使用Secret资源提供服务账户凭证。 -
一个
ExternalSecret资源——该资源包含配置,用于从密钥存储中提取的 Secret 生成所需的 Kubernetes Secret(mongodb-creds)。
所以,首先让我们定义Secret资源:
为了创建Secret资源,我们首先需要创建一个 GCP 服务账户,以便与 Secret Manager 交互,使用以下命令:
$ cd ~
$ gcloud iam service-accounts create external-secrets
由于我们遵循最小权限原则,我们将添加以下角色绑定,仅提供对external-secrets Secret 的访问,如下所示:
$ gcloud secrets add-iam-policy-binding external-secrets \
--member "serviceAccount:external-secrets@$PROJECT_ID.iam.gserviceaccount.com" \
--role "roles/secretmanager.secretAccessor"
现在,使用以下命令生成服务账户密钥文件:
$ gcloud iam service-accounts keys create key.json \
--iam-account=external-secrets@$PROJECT_ID.iam.gserviceaccount.com
现在,将key.json文件的内容复制到一个新的 GitHub Actions 密钥中,命名为GCP_SM_CREDENTIALS。我们将在运行时使用 GitHub Actions 动态设置此值;因此,以下密钥清单将包含一个占位符:
apiVersion: v1
data:
secret-access-credentials: SECRET_ACCESS_CREDS_PH
kind: Secret
metadata:
name: gcpsm-secret
type: Opaque
接下来,让我们看一下ClusterSecretStore资源:
apiVersion: external-secrets.io/v1alpha1
kind: ClusterSecretStore
metadata:
name: gcp-backend
spec:
provider:
gcpsm:
auth:
secretRef:
secretAccessKeySecretRef:
name: gcpsm-secret
key: secret-access-credentials
projectID: PROJECT_ID_PH
清单定义了以下内容:
-
一个名为
gcp-backend的ClusterSecretStore资源 -
使用
gcpsm类型的提供程序配置,利用我们之前定义的gcpsm-secret密钥中的身份验证信息
现在,让我们看一下ExternalSecret资源的清单:
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: mongodb-creds
namespace: blog-app
spec:
secretStoreRef:
kind: SecretStore
name: gcp-backend
target:
name: mongodb-creds
data:
- secretKey: MONGO_INITDB_ROOT_USERNAME
remoteRef:
key: external-secrets
property: MONGO_INITDB_ROOT_USERNAME
- secretKey: MONGO_INITDB_ROOT_PASSWORD
remoteRef:
key: external-secrets
property: MONGO_INITDB_ROOT_PASSWORD
清单定义了一个具有以下规格的 ExternalSecret 资源:
-
它在
blog-app命名空间中名为mongodb-creds。 -
它引用了我们定义的
gcp-backendClusterSecretStore。 -
它将
MONGO_INITDB_ROOT_USERNAME从external-secretsSecret Manager 密钥映射到mongodb-credsKubernetes 密钥的MONGO_INITDB_ROOT_USERNAME键。它对MONGO_INITDB_ROOT_PASSWORD执行相同操作。
现在,让我们使用以下命令部署这些资源:
$ cd ~/mdo-environments
$ cp ~/modern-devops/ch13/configure-external-secrets/app.tf terraform/app.tf
$ cp ~/modern-devops/ch13/configure-external-secrets/gcpsm-secret.yaml \
manifests/argocd/
$ cp ~/modern-devops/ch13/configure-external-secrets/mongodb-creds-external.yaml \
manifests/blog-app/
$ cp -r ~/modern-devops/ch13/configure-external-secrets/.github .
$ git add --all
$ git commit -m "Configure External Secrets"
$ git push
这应该再次触发 GitHub Actions 流程,很快我们应该会看到创建了 ClusterSecretStore 和 ExternalSecret。要检查这一点,请运行以下命令:
$ kubectl get secret gcpsm-secret
NAME TYPE DATA AGE
gcpsm-secret Opaque 1 1m
$ kubectl get clustersecretstore gcp-backend
NAME AGE STATUS CAPABILITIES READY
gcp-backend 19m Valid ReadWrite True
$ kubectl get externalsecret -n blog-app mongodb-creds
NAME STORE REFRESHINTERVAL STATUS READY
mongodb-creds gcp-backend 1h0m0s SecretSynced True
$ kubectl get secret -n blog-app mongodb-creds
NAME TYPE DATA AGE
mongodb-creds Opaque 2 4m45s
这应该反映在 Argo CD 中的 blog-app 应用中,且应用应正常启动,如以下截图所示:
图 13.8 – 显示为健康状态的博客应用
然后,你可以通过以下命令获取前端服务的外部 IP,从而访问应用:
$ kubectl get svc -n blog-app frontend
NAME TYPE EXTERNAL-IP PORT(S) AGE
frontend LoadBalancer 34.122.58.73 80:30867/TCP 153m
你可以通过浏览器访问 http://<EXTERNAL_IP> 来访问应用:
图 13.9 – 博客应用主页
如我们所见,我们可以成功访问博客应用。这是正确的密钥管理,因为我们没有将密钥存储在源代码仓库(Git)中。我们在应用密钥时没有查看或记录密钥,这意味着日志中没有任何密钥的痕迹,只有具有访问该应用运行命名空间权限的应用或人员才能访问它。现在,让我们看看另一个关键方面:测试你的应用。
在 CD 流程中测试你的应用
直到现在,我们已在 Kubernetes 集群中部署了应用并手动验证它正在运行。接下来我们有两个选项:继续进行手动测试或创建自动化测试,也就是测试套件。虽然手动测试是传统方法,但 DevOps 强烈强调自动化测试,并将其集成到 CD 流程中。这样,我们可以消除许多重复性任务,通常被称为苦力活。
我们为应用开发了一个基于 Python 的集成测试套件,涵盖了各种场景。这个测试套件的一个重要优点是,它将应用视为一个黑箱。它不了解应用是如何实现的,而专注于模拟最终用户交互。这种方法提供了关于应用功能方面的宝贵见解。
此外,由于这是集成测试,它评估的是整个应用作为一个整体单元,而与我们在 CI 流程中进行的单元测试不同,后者是在隔离的情况下测试每个微服务。
不再拖延,让我们将集成测试集成到我们的 CD 管道中。
CD 流程更改
到目前为止,我们在 CD 流程中有以下内容:
.
├── create-cluster.yml
├── dev-cd-workflow.yaml
└── prod-cd-workflow.yaml
开发和生产 CD 流程都包含以下任务:
jobs:
create-environment-and-deploy-app:
name: Create Environment and Deploy App
uses: ./.github/workflows/create-cluster.yml
secrets: inherit
如我们所见,它们都调用了 create-cluster.yml 工作流,该工作流创建我们的环境并部署我们的应用程序。我们需要在 Dev 和 Prod 环境中运行集成测试,因此我们需要将两个工作流更改为包括运行集成测试步骤,如下所示:
run-tests:
name: Run Integration Tests
needs: [deploy-app]
uses: ./.github/workflows/run-tests.yml
secrets: inherit
如我们所见,该步骤调用了 run-tests.yml 工作流。那就是执行集成测试的工作流。让我们看看工作流,以便更好地理解:
name: Run Integration Tests
on: [workflow_call]
jobs:
test-application:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./tests
steps:
- uses: actions/checkout@v2
- name: Extract branch name
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
id: extract_branch
- id: gcloud-auth
name: Authenticate with gcloud
uses: 'google-github-actions/auth@v1'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: Set up Cloud SDK
id: setup-gcloud-sdk
uses: 'google-github-actions/setup-gcloud@v1'
- name: Get kubectl credentials
id: 'get-credentials'
uses: 'google-github-actions/get-gke-credentials@v1'
with:
cluster_name: mdo-cluster-${{ steps.extract_branch.outputs.branch }}
location: ${{ secrets.CLUSTER_LOCATION }}
- name: Compute Application URL
id: compute-application-url
run: external_ip=$(kubectl get svc -n blog-app frontend --output jsonpath='{.status.
loadBalancer.ingress[0].ip}') && echo ${external_ip} && sed -i "s/localhost/${external_
ip}/g" integration-test.py
- id: run-integration-test
name: Run Integration Test
run: python3 integration-test.py
该工作流执行以下任务:
-
它仅通过
workflow调用触发。 -
它具有
./tests工作目录。 -
它检出已提交的代码。
-
它安装
gcloudCLI 并使用GCP_CREDENTIALS服务账户凭据进行 Google Cloud 身份验证。 -
它将
kubectl连接到 Kubernetes 集群,以检索应用程序的 URL。 -
使用应用程序的 URL,它执行集成测试。
现在,让我们继续更新工作流,并使用以下命令添加测试:
$ cp -r ~/modern-devops/ch13/integration-tests/.github .
$ cp -r ~/modern-devops/ch13/integration-tests/tests .
$ git add --all
$ git commit -m "Added tests"
$ git push
这应该再次触发 Dev CD GitHub Actions 工作流。您应该看到如下内容:
图 13.10 – 添加测试工作流运行
如我们所见,工作流中有两个步骤,且两者现在都已成功。要查看测试的内容,您可以点击运行集成测试步骤,它应该显示以下输出:
图 13.11 – 运行集成测试工作流步骤
如我们所见,运行集成测试步骤报告所有测试已通过。
当镜像在 CI/CD 工具链中构建、部署和测试时,工作流之间没有任何东西可以防止某人将镜像直接部署到 Kubernetes 集群中。您可能正在扫描所有镜像以寻找漏洞并加以修复,但某处可能有人绕过所有控制,直接将容器部署到集群中。那么,如何防止这种情况发生呢?答案就是通过二进制授权。我们将在下一节中探讨这一点。
二进制授权
二进制授权是一种部署时的安全机制,确保仅信任的二进制文件被部署到您的环境中。在容器和 Kubernetes 的背景下,二进制授权使用签名验证,确保只有由受信任的授权机构签名的容器镜像才会在 Kubernetes 集群中部署。
使用二进制授权可以更严格地控制在集群中部署的内容。它确保只有经过测试并且由特定授权机构(如安全工具或人员)批准和验证的容器才会出现在集群中。
二进制授权通过在集群内强制执行规则来工作,这意味着你可以创建规则集,仅允许由认证机构签名的镜像在集群中部署。在实际场景中,你的 质量保证(QA)团队可以成为一个很好的审核员。你也可以将认证嵌入到 CI/CD 流水线中。认证意味着你的镜像已经过测试并扫描了漏洞,并且已经通过了最低标准,准备部署到集群中。
GCP 提供了嵌入在 GKE 中的二进制授权,基于开源项目 Kritis (github.com/grafeas/kritis)。它使用 公钥基础设施(PKI)来认证和验证镜像——因此你的镜像是由审核员使用私钥签名的,Kubernetes 使用公钥来验证镜像。以下图表生动地解释了这一过程:
图 13.12 – 二进制授权过程
在实际操作中,我们将使用 Google Cloud KMS 设置二进制授权和 PKI。接下来,我们将为所有启用二进制授权的 GKE 集群创建一个 QA 审核员和认证策略,确保只有经过认证的镜像可以被部署。由于我们的应用程序现在已经通过测试,下一步是对经过测试的镜像进行认证。因此,让我们继续在下一部分中设置 Dev CD 工作流中的二进制授权。
设置二进制授权
由于我们从一开始就使用 GitOps,因此我们将使用 Terraform 来为我们设置二进制授权。我们将从设置一些 GitHub Actions 密码开始。请访问 https://github.com/<your_github_user>/mdo-environments/settings/secrets/actions 并创建以下密码:
ATTESTOR_NAME=quality-assurance-attestor
KMS_KEY_LOCATION=us-central1
KMS_KEYRING_NAME=qa-attestor-keyring
KMS_KEY_NAME=quality-assurance-attestor-key
KMS_KEY_VERSION=1
然后,我们将创建一个 binaryauth.tf 文件,其中包含以下资源。
我们将首先创建一个 Google KMS 密钥环。由于二进制授权利用 PKI 来创建和验证认证,因此这个密钥环将使我们的审核员能够为镜像的认证进行数字签名。请注意以下代码中定义的 count 属性。它确保仅在我们打算使用审核员来认证镜像的 dev 环境中创建:
resource "google_kms_key_ring" "qa-attestor-keyring" {
count = var.branch == "dev" ? 1 : 0
name = "qa-attestor-keyring"
location = var.region
lifecycle {
prevent_destroy = false
}
}
然后我们将使用 Google 提供的 binary-authorization Terraform 模块来创建我们的 quality-assurance 审核员。该审核员使用我们之前创建的 Google KMS 密钥环:
module "qa-attestor" {
count = var.branch == "dev" ? 1 : 0
source = "terraform-google-modules/kubernetes-engine/google//modules/binary-
authorization"
attestor-name = "quality-assurance"
project_id = var.project_id
keyring-id = google_kms_key_ring.qa-attestor-keyring[0].id
}
最后,我们将创建一个二进制授权策略,指定在部署容器时集群的行为。在此场景中,我们的目标是仅部署经过认证的镜像。然而,我们将做一些例外,允许使用 Google 提供的系统镜像、Argo CD 和 External Secrets Operator 镜像。我们将把 global_policy_evaluation_mode 属性设置为 ENABLE,以避免强制执行 Google 管理的系统镜像上的策略。
admission_whitelist_patterns部分定义了允许在没有验证的情况下部署的容器镜像模式。这包括适用于 Google 管理的系统镜像、Argo CD 注册表、外部 Secrets 注册表以及 Argo CD 使用的 Redis 容器的模式。
defaultAdmissionRule部分要求使用我们创建的验证器进行验证。因此,任何其他镜像都需要验证才能在集群中运行:
resource "google_binary_authorization_policy" "policy" {
count = var.branch == "dev" ? 1 : 0
admission_whitelist_patterns {
name_pattern = "gcr.io/google_containers/*"...
name_pattern = "gcr.io/google-containers/*"...
name_pattern = "k8s.gcr.io/**"...
name_pattern = "gke.gcr.io/**"...
name_pattern = "gcr.io/stackdriver-agents/*"...
name_pattern = "quay.io/argoproj/*"...
name_pattern = "ghcr.io/dexidp/*"...
name_pattern = "docker.io/redis[@:]*"...
name_pattern = "ghcr.io/external-secrets/*"
}
global_policy_evaluation_mode = "ENABLE"
default_admission_rule {
evaluation_mode = "REQUIRE_ATTESTATION"
enforcement_mode = "ENFORCED_BLOCK_AND_AUDIT_LOG"
require_attestations_by = [
module.qa-attestor[0].attestor
]
}
}
为了在集群中强制执行二进制授权策略,我们还必须启用二进制授权。为此,我们需要在cluster.tf文件中添加以下内容:
resource "google_container_cluster" "main" {
...
dynamic "binary_authorization" {
for_each = var.branch == "prod" ? [1] : []
content {
evaluation_mode = "PROJECT_SINGLETON_POLICY_ENFORCE"
}
}
...
}
这个动态块仅在分支名称为prod时创建。采用这种方法的原因是我们打算将代码部署到开发环境,而无需验证镜像,进行测试,成功后再验证镜像。因此,只有生产集群应该禁止未验证的镜像。为此,我们将在开发 CD 工作流中包含以下步骤:
binary-auth:
name: Attest Images
needs: [run-tests]
uses: ./.github/workflows/attest-images.yml
secrets: inherit
正如您所看到的,这会调用attest-images.yml工作流。让我们现在来看一下:
...
steps:
- uses: actions/checkout@v2
- id: gcloud-auth ...
- name: Set up Cloud SDK ...
- name: Install gcloud beta
id: install-gcloud-beta
run: gcloud components install beta
- name: Attest Images
run: |
for image in $(cat ./images); do
no_of_slash=$(echo $image | tr -cd '/' | wc -c)
prefix=""
if [ $no_of_slash -eq 1 ]; then
prefix="docker.io/"
fi
if [ $no_of_slash -eq 0 ]; then
prefix="docker.io/library/"
fi
image_to_attest=$image
if [[ $image =~ "@" ]]; then
echo "Image $image has DIGEST"
image_to_attest="${prefix}${image}"
else
echo "All images should be in the SHA256 digest format"
exit 1
fi
echo "Processing $image"
attestation_present=$(gcloud beta container binauthz attestations list
--attestor-project="${{ secrets.PROJECT_ID }}" --attestor="${{ secrets.ATTESTOR_NAME }}"
--artifact-url="${image_to_attest}")
if [ -z "${attestation_present// }" ]; then
gcloud beta container binauthz attestations sign-and-create --artifact-
url="${image_to_attest}" --attestor="${{ secrets.ATTESTOR_NAME }}" --attestor-project="${{
secrets.PROJECT_ID }}" --keyversion-project="${{ secrets.PROJECT_ID }}" --keyversion-
location="${{ secrets.KMS_KEY_LOCATION }}" --keyversion-keyring="${{ secrets.KMS_KEYRING_
NAME }}" --keyversion-key="${{ secrets.KMS_KEY_NAME }}" --keyversion="${{ secrets.KMS_KEY_
VERSION }}"
fi
done
该 YAML 文件执行多个任务,包括安装gcloud并与 GCP 进行身份验证。它还安装了gcloud beta CLI,最重要的是,它验证了镜像。
为了验证镜像,它会在blog-app.yaml清单中搜索所有镜像。对于每个镜像,它检查该镜像是否采用sha256摘要格式。如果是,它将继续验证该镜像。
值得注意的是,工作流验证镜像时,使用的是sha256摘要格式,而不是镜像定义中的标签。这个选择在使用二进制授权时至关重要。为什么?因为二进制授权要求使用sha256摘要而不是标签来部署镜像。这个预防措施非常必要,因为使用标签时,任何人都可以将一个不同的镜像与已验证镜像的标签关联,并将其推送到容器注册表。而摘要是由 Docker 镜像生成的哈希值。因此,只要镜像内容保持不变,摘要就保持相同。这可以防止任何绕过二进制授权控制的尝试。
以这种方式指定镜像的格式如下:
<repo_url>/<image_name>@sha256:<sha256-digest>
因此,在将更改推送到远程仓库之前,我们需要将镜像标签替换为sha256摘要。使用以下命令进行操作:
$ grep -ir "image:" ./manifests/blog-app |\
awk {'print $3'} | sort -t: -u -k1,1 > ./images
$ for image in $(cat ./images); do
no_of_slash=$(echo $image | tr -cd '/' | wc -c)
prefix=""
if [ $no_of_slash -eq 1 ]; then
prefix="docker.io/"
fi
if [ $no_of_slash -eq 0 ]; then
prefix="docker.io/library/"
fi
image_to_attest=$image
if [[ $image =~ "@" ]]; then
echo "Image $image has DIGEST"
image_to_attest="${prefix}${image}"
else
DIGEST=$(docker pull $image | grep Digest | awk {'print $2'})
image_name=$(echo $image | awk -F ':' {'print $1'})
image_to_attest="${prefix}${image_name}@${DIGEST}"
fi
escaped_image=$(printf '%s\n' "${image}" | sed -e 's/[]\/$*.^[]/\\&/g')
escaped_image_to_attest=$(printf '%s\n' "${image_to_attest}" | \
sed -e 's/[]\/$*.^[]/\\&/g')
echo "Processing $image"
grep -rl $image ./manifests | \
xargs sed -i "s/${escaped_image}/${escaped_image_to_attest}/g"
done
要验证更改是否成功,请运行以下命令:
$ cat manifests/blog-app/blog-app.yaml | grep "image:"
image: docker.io/library/mongo@sha256:2a1093b275d9bc...
image: docker.io/bharamicrosystems/mdo-posts@sha256:b5bc...
image: docker.io/bharamicrosystems/mdo-reviews@sha256:073..
image: docker.io/bharamicrosystems/mdo-ratings@sha256:271..
image: docker.io/bharamicrosystems/mdo-users@sha256:5f5a...
image: docker.io/bharamicrosystems/mdo-frontend@sha256:87..
正如我们所见,镜像已经更新。现在,让我们使用以下命令将更改推送到远程仓库:
$ cp ~/modern-devops/ch13/binaryauth/binaryauth.tf terraform/
$ cp ~/modern-devops/ch13/binaryauth/cluster.tf terraform/
$ cp ~/modern-devops/ch13/binaryauth/variables.tf terraform/
$ cp -r ~/modern-devops/ch13/binaryauth/.github .
$ git add --all
$ git commit -m "Enabled Binary Auth"
$ git push
现在,让我们回顾一下 GitHub Actions 上的开发 CD 工作流,我们应该观察以下内容:
图 13.13 – 开发 CD 工作流 – 验证镜像
正如所见,工作流已成功配置二进制授权并验证了我们的镜像。为了验证,请执行以下命令:
$ gcloud beta container binauthz attestations list \
--attestor-project="$PROJECT_ID" \
--attestor="quality-assurance-attestor" | grep resourceUri
resourceUri: docker.io/bharamicrosystems/mdo-ratings@
sha256:271981faefafb86c2d30f7d3ce39cd8b977b7dd07...
resourceUri: docker.io/library/mongo@sha256:2a1093b275d9bc546135ec2e2...
resourceUri: docker.io/bharamicrosystems/mdo-posts@
sha256:b5bc1fc976a93a88cc312d24916bd1423dbb3efe25e...
resourceUri: docker.io/bharamicrosystems/mdo-frontend@
sha256:873526fe6de10e04c42566bbaa47b76c18f265fd...
resourceUri: docker.io/bharamicrosystems/mdo-users@
sha256:5f5aa595bc03c53b86dadf39c928eff4b3f05533239...
resourceUri: docker.io/bharamicrosystems/mdo-reviews@
sha256:07370e90859000ff809b1cd1fd2fc45a14c5ad46e...
如我们所见,证明已经成功创建。在将应用程序部署到开发环境并进行测试后,所有镜像均已验证,我们现在可以继续将代码部署到生产环境。这涉及到将代码与prod分支合并,我们将为此实施拉取请求门控。
通过拉取请求进行发布门控并部署到生产环境
拉取请求门控的过程很简单。在开发 CD 工作流的末尾,我们将添加一个步骤来发起一个拉取请求,将dev分支合并到prod分支。此步骤需要人工审批才能继续合并拉取请求。这一步展示了不同组织可能采用不同方式来验证和推广经过测试的代码。一些可能选择自动合并,而其他可能优先考虑人工触发的操作。代码成功合并到prod分支后,会触发生产 CD 工作流。该工作流会创建生产环境并部署我们的应用程序,还会执行与在开发环境中运行的相同的集成测试,以确保生产环境中部署的应用程序保持完好。
这是我们将添加到开发 CD 工作流中的步骤:
raise-pull-request:
name: Raise Pull Request
needs: [binary-auth]
uses: ./.github/workflows/raise-pr.yml
secrets: inherit
如我们所见,这一步骤调用了raise-pr.yml文件。我们来看一下这个文件:
...
steps:
- uses: actions/checkout@v2
- name: Raise a Pull Request
id: pull-request
uses: repo-sync/pull-request@v2
with:
destination_branch: prod
github_token: ${{ secrets.GH_TOKEN }}
此工作流执行以下操作:
-
从仓库中检出代码
-
提交一个拉取请求以使用
GH_TOKEN密钥与prod分支合并
为了启用工作流的功能,我们需要定义一个 GitHub 令牌。这个令牌允许工作流代表当前用户创建拉取请求。以下是步骤:
-
在
mdo-environments仓库中创建一个新的令牌,并授予它读写拉取请求权限。这种方法符合最小权限原则,提供了更细粒度的控制。 -
创建令牌后,复制它。
-
现在,创建一个名为
GH_TOKEN的 GitHub Actions 密钥,并将复制的令牌粘贴为其值。你可以通过访问https://github.com/<your_github_user>/mdo-environments/settings/secrets/actions来完成此操作。
接下来,我们继续使用以下命令复制工作流文件:
$ cd ~/mdo-environments/.github/workflows
$ cp ~/modern-devops/ch13/raise-pr/.github/workflows/dev-cd-workflow.yml .
$ cp ~/modern-devops/ch13/raise-pr/.github/workflows/raise-pr.yml .
我们准备将这段代码推送到 GitHub 了。运行以下命令以提交并推送更改到你的 GitHub 仓库:
$ git add --all
$ git commit -m "Added PR Gating"
$ git push
这应该会触发 GitHub Actions 工作流在你的 GitHub 仓库中运行,你应该会看到类似如下内容:
图 13.14 – 提交拉取请求
GitHub 已经生成了一个拉取请求,将代码合并到prod分支,且开发 CD 工作流正按预期运行。现在我们可以审查拉取请求并将代码合并到prod分支。
合并代码并部署到生产环境
如前一节所示,Dev CD 工作流创建了我们的环境,部署了应用程序,进行了测试,并验证了应用程序镜像。然后,它自动发起了一个拉取请求,将代码合并到prod分支。
我们已进入prod分支。
既然我们知道拉取请求已经创建,让我们继续检查并批准它。为此,访问https://github.com/<your_github_user>/mdo-environments/pulls,你会找到该拉取请求。点击该拉取请求,你会看到以下内容:
图 13.15 – 拉取请求
我们看到拉取请求已经准备好合并。点击prod分支。
如果你访问https://github.com/<your_user>/mdo-environments/actions,你会发现 Prod CD 工作流已经触发。当你点击工作流时,你会看到类似以下的工作流运行:
图 13.16 – Prod CD 工作流
当我们合并了拉取请求时,它会自动触发 Prod CD 工作流,因为它会响应prod分支中的任何新变化。工作流完成了它的任务,构建了生产环境,部署了我们的应用程序,并对其进行了测试。请注意,这个集群启用了二进制授权。
为了确认二进制授权是否正常工作,让我们进行一些检查,确保无法部署未经验证的镜像。
首先,让我们使用以下命令与prod集群建立连接:
$ gcloud container clusters get-credentials \
mdo-cluster-prod --zone us-central1-a --project ${PROJECT_ID}
让我们尝试使用nginx镜像将一个 pod 部署到集群中。请使用以下命令:
$ kubectl run nginx --image=nginx
Error from server (VIOLATES_POLICY): admission webhook "imagepolicywebhook.image-policy.
k8s.io" denied the request: Image nginx denied by Binary Authorization default admission
rule. Image nginx denied by attestor projects/<PROJECT_ID>/attestors/quality-assurance-
attestor: Expected digest with sha256 scheme, but got tag or malformed digest
现在,正如预期的那样,部署失败了,但如果你检查失败的原因,还有一些需要注意的地方。失败发生是因为我们指定了一个标签,而不是sha256摘要。让我们再次尝试部署镜像,但这次使用摘要。
为了做到这一点,让我们通过以下命令获取镜像摘要并将其设置为名为DIGEST的变量:
$ DIGEST=$(docker pull nginx | grep Digest | awk {'print $2'})
现在,让我们使用摘要重新部署镜像,使用以下命令:
$ kubectl run nginx --image=nginx@$DIGEST
Error from server (VIOLATES_POLICY): admission webhook "imagepolicywebhook.image-
policy.k8s.io" denied the request: Image nginx@sha256:6926dd8... denied by Binary
Authorization default admission rule. Image nginx@sha256:6926dd8... denied by attestor
projects/<PROJECT_ID>/attestors/quality-assurance-attestor: No attestations found that
were valid and signed by a key trusted by the attestor
这一次,部署因正当理由被拒绝,确认了二进制授权功能正常。这确保了 Kubernetes 集群的安全,防止了未经验证的镜像被部署,并让你对环境拥有完全控制权。有了这个措施,任何问题都不会源自部署未经测试或存在漏洞的镜像。
我们已经涵盖了将安全性和质量保障集成到 CI/CD 流水线中的许多内容。现在,让我们探讨一些保护现代 DevOps 流水线的最佳实践。
现代 DevOps 流水线的安全性和测试最佳实践
工具并不是唯一能帮助你在 DevSecOps 旅程中取得进展的东西。这里有一些有用的建议,帮助你解决安全风险,并在你的组织内建立更安全的文化。
采纳 DevSecOps 文化
采用 DevSecOps 方法在实施现代 DevOps 中至关重要。因此,必须将安全性融入组织的文化中。你可以通过实现开发、运维和安全团队之间的有效沟通与协作来实现这一点。虽然大多数组织都有安全政策,但这些政策不能仅仅为了遵守规则和法规而执行。相反,员工应该进行跨技能培训和提升技能,以便采纳 DevSecOps 方法,并在开发初期就将安全性嵌入其中。安全团队需要学习如何编写代码并与 API 协作,而开发人员则需要理解安全性并利用自动化来实现这一点。
建立访问控制
你在本书中已经多次听说过最小权限原则(PoLP)。这正是你需要实施的措施,以提高安全性,这意味着你应该尽量只为人们完成工作所需的权限,而不授予其他不必要的权限。通过简化授予访问权限的过程,减少“以防万一”心态,让人们不会感到受到阻碍,进而避免他们寻求超出需求的权限。
实施左移策略
左移意味着在软件开发的早期阶段就将安全性嵌入软件中。这意味着安全专家需要与开发人员密切合作,使他们从一开始就能构建安全的软件。安全功能不仅仅是审查功能,还应与开发人员和架构师积极合作,开发安全强化的设计和代码。
一致地管理安全风险
你应该接受不可避免的风险,并在发生攻击时拥有标准操作程序(SOP)。你应该在软件开发和基础设施管理的各个方面(如配置管理、访问控制、漏洞测试、代码审查和防火墙)制定简明易懂的政策和实践,以确保从安全角度出发做好准备。
实施漏洞扫描
目前,开源软件正在快速增长,许多软件实现依赖于现成的开源框架、软件库和第三方软件,而这些软件并不提供任何保证或责任。虽然开源生态系统正在以前所未有的方式构建技术世界,但它也有自己的漏洞问题,你不希望在自己的软件中引入这些漏洞。漏洞扫描至关重要,因为扫描可以发现任何带有漏洞的第三方依赖,并在初期阶段提醒你。
自动化安全
安全性不应妨碍 DevOps 团队的速度;因此,为了跟上 DevOps 的快速节奏,你应该考虑将安全性嵌入到你的 CI/CD 流程中。你可以通过代码分析、漏洞扫描、配置管理和基础设施扫描,并使用策略即代码和二进制授权来确保只有经过测试并且安全的软件才能部署。自动化有助于在软件开发生命周期的早期发现潜在漏洞,从而降低软件开发和返工的成本。
类似地,QA 是软件交付的核心,而现代 DevSecOps 则高度重视自动化测试。以下是一些你可以遵循的建议,以实现现代化的测试方法。
在 CI/CD 管道中进行自动化测试
自动化测试是关键。这意味着要涵盖广泛的领域,从单元测试、集成测试到功能测试、安全测试和性能测试。目标是将这些测试无缝地嵌入到你的 CI/CD 管道中,确保持续不断的验证。在这个过程中,创建隔离且可重现的测试环境至关重要,以避免测试之间的相互干扰。在这方面,容器化和虚拟化等方法是环境隔离的宝贵工具。
有效管理你的测试数据
测试数据管理是另一个关键环节。有效处理测试数据至关重要,不仅要确保其一致性,还要保护数据隐私。在这方面,利用数据生成工具可以大大改变游戏规则,帮助你创建适合测试需求的数据集。此外,在处理敏感信息时,考虑数据匿名化是明智的做法。这能确保你在保持最高数据保护标准的同时,依然能够进行全面的测试。
测试应用的各个方面
CI 的关键是保持开发过程的顺畅进行。这包括频繁地合并代码并自动运行测试,确保代码库保持稳定。当测试失败时,立即关注并迅速解决问题至关重要。
端到端测试是确保整个应用工作流按预期运行的指南针。自动化框架在复制真实用户交互方面发挥着重要作用,使你能够全面评估应用的表现。
负载测试是流程中不可或缺的一部分,它评估应用在不同负载下的表现,提供关于应用的健壮性和容量的洞察。此外,可扩展性测试确保系统能够应对增长的需求,这是保证应用长期健康发展的重要因素。
实施混沌工程
纳入混沌工程实践是一种积极主动的策略,用于发现和解决潜在的系统弱点。通过进行受控实验,你可以评估系统的弹性,更好地为意外的挑战做好准备。这些实验通过故意在环境中引入混沌,观察系统的反应。这不仅帮助你识别弱点,还提供了关于如何增强系统的健壮性和可靠性的宝贵见解。
在测试时监控和观察应用程序
设置强大的监控和可观察性工具对于深入了解系统的性能和行为至关重要。这些工具允许你收集关键的指标、日志和追踪信息,从而提供应用程序健康状况和性能的全面视图。
在生产环境中进行有效测试
实施特性标志和金丝雀发布是测试新功能在真实生产环境中表现的明智策略,同时可以最小化风险。特性标志允许你在运行时启用或禁用某些功能,从而对它们的激活进行控制。金丝雀发布则涉及将新功能推出给少量用户,允许你在全面发布之前监控其影响。
通过利用特性标志,你可以将新功能推出给有限的用户群体,而不会影响整个用户基础。这种受控的方式使你能够观察用户互动、收集反馈并评估功能在实际场景中的表现。同时,金丝雀发布允许你将这些功能部署给一小部分代表性用户,从而监控他们的行为、收集性能数据并识别潜在问题。
至关重要的是,在此过程中持续监控是必不可少的。通过密切观察新功能的影响,你可以迅速发现可能出现的问题。如果发生问题,你可以通过简单地关闭特性标志或恢复到先前版本来灵活地回滚更改。这种迭代和谨慎的方法最大程度地减少了潜在问题的影响,确保了更加顺畅的用户体验,并维持生产环境的稳定性。
文档和知识共享
记录测试程序、测试用例和最佳实践对于确保开发和测试过程中的一致性和可靠性至关重要。全面的文档为团队成员提供了清晰的指导,说明如何进行测试、预期的结果以及应遵循的最佳实践。这些文档是新老团队成员宝贵的资源,促进了对测试程序的共同理解。
鼓励团队成员之间的知识共享进一步增强了团队的集体专业知识。通过促进开放的沟通和分享经验,团队成员可以互相学习,获得不同测试场景的见解,并发现应对常见挑战的创新解决方案。这种协作的环境促进了持续学习,并确保团队保持对软件测试领域最新发展和技术的了解。
通过遵循这些最佳实践,团队可以增强 CI/CD 管道的安全性和可靠性。适当的文档化流程和测试用例使得测试保持一致,从而减少将错误引入代码库的可能性。知识共享确保团队能够从成员的集体智慧和经验中受益,进而做出更明智的决策并高效解决问题。
此外,通过良好的文档化测试流程和传播最佳实践,能够有效管理安全风险。团队可以在开发过程中及早发现潜在的安全漏洞,从而在这些问题升级为重大威胁之前予以解决。定期的知识共享会议也可以包括关于安全最佳实践的讨论,确保团队成员了解最新的安全威胁和应对措施。
最终,这些最佳实践有助于建立一个强大的测试和开发文化。它们使团队能够更快、更自信地交付软件,因为他们知道 CI/CD 管道是安全、可靠的,并且能够应对现代软件开发的挑战。
总结
本章已经涵盖了 CI/CD 管道的安全性和测试,我们了解了围绕它的各种工具、技术和最佳实践。我们参考了一个安全的 CI/CD 工作流。然后,我们通过实际操作,理解了让它保持安全的各个方面,比如密钥管理、容器漏洞扫描和二进制授权。
运用本章学到的技能,你现在可以适当地保护你的 CI/CD 管道,并使你的应用程序更安全。
在下一章,我们将探索运行应用程序在生产环境中的操作元素以及关键性能指标。
问题
-
以下哪个是存储密钥的推荐位置?
A. 私有 Git 仓库
B. 公共 Git 仓库
C. Docker 镜像
D. 密钥管理系统
-
以下哪项是开源的密钥管理系统?
A. 密钥管理器
B. HashiCorp Vault
C. Anchore Grype
-
在你的 CD 管道的文件系统中下载一个密钥是一个好习惯吗?
-
哪种基础镜像通常被认为更安全,并且包含最少的漏洞?
A. Alpine
B. Slim
C. Buster
D. 默认
-
以下哪项关于二进制授权的说法是正确的?(选择两项)
A. 它会扫描你的镜像以检测漏洞。
B. 它仅允许部署经过验证的镜像。
C. 它防止人们绕过你的 CI/CD 管道。
答案
-
D
-
B
-
否
-
A
-
B 和 C
第五部分:在生产环境中操作应用
本部分提供了在生产环境中管理容器的全面指南。我们将首先介绍关键性能指标和可靠性原则,然后探索 Istio 以实现高级安全性、流量管理和可观察性。本节将帮助你掌握优化生产环境中基于容器的应用所需的关键技能。
本部分包括以下章节:
-
第十四章,理解生产服务的关键绩效指标(KPI)
-
第十五章,使用 Istio 操作生产环境中的容器
第十四章:理解生产服务的关键绩效指标(KPI)
在前几章中,我们讨论了现代 DevOps 的核心概念——持续集成(CI)和持续部署/交付(CD)。我们还探讨了各种工具和技术,这些工具和技术可以帮助我们在组织内实现一个成熟且安全的 DevOps 渠道。在这一章中,尽管重点是理论,我们将试图理解一些运营生产应用程序时的关键绩效指标(KPI)。
在这一章中,我们将讨论以下主要话题:
-
理解可靠性的重要性
-
服务水平目标(SLO)、服务水平协议(SLA)和服务水平指标(SLI)
-
错误预算
-
恢复时间目标(RPO)和恢复点目标(RTO)
-
在生产环境中运行分布式应用程序
那么,让我们开始吧!
理解可靠性的重要性
开发软件是一回事,而在生产环境中运行它是另一回事。这种差距的原因在于,大多数开发团队无法在非生产环境中模拟生产条件。因此,许多漏洞只有在软件已投入生产时才被发现。大多数遇到的问题都是非功能性问题——例如,服务可能无法随着额外的流量适当扩展,分配给应用程序的资源不足,导致网站崩溃,等等。这些问题需要得到管理,以提高软件的可靠性。
为了理解软件可靠性的重要性,我们来看一个零售银行应用的例子。软件可靠性对于多个原因至关重要:
-
用户满意度:可靠的软件能够确保良好的用户体验。用户希望软件能够按预期工作,而当它无法按预期工作时,可能会导致沮丧、失去信任以及软件或背后组织的声誉受损。对于银行的零售客户来说,这可能意味着客户无法进行必要的交易,因此可能会在支付和收款过程中遇到麻烦,导致用户满意度下降。
-
商业声誉:软件故障可能会损害公司的声誉和品牌形象。对于我们的银行来说,如果问题频繁出现,客户会寻找其他选择,导致客户流失和业务损失。
-
财务影响:软件故障可能会非常昂贵。它们可能导致销售损失、客户支持费用,甚至在软件故障导致用户损失或财务损害时,还可能引发法律责任。对于银行应用程序来说,特别关键的是,这涉及到客户的资金。如果交易未能及时完成,可能会导致客户流失,从长远来看,会对银行造成伤害。
-
竞争优势:可靠的软件可以提供竞争优势。用户更倾向于选择并坚持使用一款能持续满足他们需求和期望的银行在线银行软件。
-
生产力与效率:在组织内部,可靠的软件对保持生产力至关重要。试想一下,客户支持和前台工作人员在这种中断中的痛苦!你还需要更多资源来管理这些问题,这会干扰操作,导致时间和资源的浪费。
-
安全性:可靠的软件通常更安全。攻击者可以利用不可靠软件中的漏洞和错误。对于银行来说,安全性至关重要,因为任何安全漏洞都可能导致直接的财务损失。确保可靠性是网络安全的基础部分。
-
合规性:在一些行业,特别是银行业,有与软件可靠性相关的监管要求。未能满足这些要求可能导致法律和财务处罚。
-
客户信任:信任是软件使用中的关键因素,尤其是在银行应用的情况下。用户必须相信他们的资金和数据会被安全处理,并且软件会按预期执行。软件可靠性是建立和维持这种信任的关键因素。
-
可维护性:可靠的软件通常更易于维护。当软件不可靠时,修复漏洞和更新变得更加困难,这可能导致可靠性不断下降的恶性循环。
-
扩展性与增长:随着软件使用量的增加,可靠性变得更加关键。适用于小规模用户群体的软件,在没有适当的可靠性措施的情况下,可能难以满足大规模用户群体的需求。
总结来说,软件可靠性不仅仅是一个技术问题;它对用户满意度、商业成功甚至法律和财务方面都有深远的影响。因此,投资确保软件可靠性是组织的一项明智和战略性的决策。
历史上,运行和管理生产中的软件是运维团队的工作,至今大多数组织仍然如此。运维团队由一群系统管理员(SysAdmins)组成,他们必须处理运行生产中软件的日常问题。他们通过软件实现扩展和容错,修补和升级软件,处理支持票务,保持系统运行,确保软件应用程序的顺利运行。
我们都经历过开发和运维团队之间的鸿沟,每个团队都有自己的目标、规则和优先事项。通常,他们会因为开发团队受益的东西(软件更改和快速发布)给运维团队带来挑战(稳定性和可靠性)而发生冲突。
然而,DevOps 的出现改变了这一动态。用 Andrew Shafer 和 Patrick Debois 的话说,DevOps 是一种文化和实践,旨在弥合软件开发与运维之间的差距。
从运维的角度看待 DevOps,Google 提出了网站可靠性工程(SRE)作为一种体现 DevOps 原则的方法。它鼓励共享所有权,使用共同的工具和实践,并承诺从失败中学习,以防止问题反复出现。其主要目标是开发和维护一个可靠的应用程序,同时不牺牲交付速度——这一平衡曾被认为是矛盾的(即,更快地创建更好的软件)。
SRE 的理念是关于如果允许软件工程师来管理生产环境,会发生什么的新思考。因此,Google 为其运维团队设计了以下方法。
对于 Google 来说,加入 SRE 团队的理想候选人应该具备两个关键特征:
-
首先,他们很快对手动任务失去兴趣,寻求将其自动化的机会。
-
其次,他们具备开发软件解决方案所需的技能,即使面临复杂的挑战。
此外,SRE(网站可靠性工程)人员应与更广泛的开发团队共享学术和智力背景。本质上,SRE 工作,传统上由运维团队承担,是由具有强大软件专业知识的工程师来完成的。这一策略依赖于这些工程师天生的倾向和能力,设计并实施自动化解决方案,从而减少对人工劳动的依赖。
从设计上讲,SRE 团队保持着强大的工程聚焦。如果没有持续的工程努力,操作工作量会急剧增加,迫使团队扩大以应对日益增长的需求。相比之下,传统的以运维为中心的团队会直接按照服务的增长来扩展。如果他们支持的服务繁荣发展,操作需求将随流量增加而激增,迫使雇佣更多人员来执行重复性工作。
为了避免这种情况,负责服务管理的团队必须将编码纳入其职责范围;否则,他们将面临被淹没的风险。
因此,Google 为分配给所有 SRE 的总“运维”工作设定了 50% 的上限,包括处理工单、值班任务和手动工作等活动。这个限制保证了 SRE 团队将大量时间用于提升服务的稳定性和功能性。虽然这个上限作为一个上界存在,但理想的结果是,随着服务逐步发展为自我维持的状态,SRE 承担的操作性工作量最小化,主要从事开发工作。Google 的目标是创建不仅仅是自动化的系统,而是固有的自我调节系统。然而,实际问题如扩展和引入新功能持续对 SRE 提出挑战。
SRE 在其方法上非常细致,依赖可衡量的指标来跟踪向特定目标的进展。例如,简单地说一个网站运行缓慢在工程背景下是模糊且无帮助的。然而,声明响应时间的第 95 百分位已超出服务级目标(SLO)10%则提供了精确的信息。SRE 还专注于通过自动化减少重复性任务,这些任务被称为劳累,以防止倦怠。现在,让我们来看看一些关键的 SRE 性能指标。
理解 SLI、SLO 和 SLA
在网站可靠性领域,有三个关键参数指导 SRE:可用性指标 —— 服务级指标(SLI),可用性定义 —— SLO,以及不可用的后果 —— 服务级协议(SLA)。让我们首先详细探索 SLI。
SLI
SLI 作为可量化的可靠性指标。谷歌将其定义为“仔细定义的某个服务水平方面的定量衡量标准”。常见的例子包括请求延迟、失败率和数据吞吐量。SLI 特定于用户旅程,即用户为实现特定目标而执行的一系列操作。例如,我们示例中的博客应用的用户旅程可能包括创建一篇新的博客文章。
谷歌,作为 SRE 的初创倡导者,已确定了四个黄金信号,适用于大多数用户旅程:
-
延迟:衡量服务响应用户请求所需的时间
-
错误:表示失败请求的百分比,突显了服务可靠性的问题
-
流量:流量代表指向你服务的需求,反映了服务的使用情况
-
饱和度:饱和度评估你的基础设施组件的使用情况
谷歌推荐的一种计算 SLI 的方法是通过确定良好事件与有效事件的比率:
SLI = (Good Events * 100) / Valid Events
完美的 SLI 得分为 100,意味着一切正常,而得分为 0 则表示存在广泛的问题。
一个有价值的 SLI 应该与用户体验紧密对齐。例如,较低的 SLI 值应与客户满意度下降相对应。如果这种对齐缺失,则该 SLI 可能无法提供有意义的见解或不值得衡量。
让我们通过以下图形更好地理解这一点:
图 14.1 – 好的与不好的 SLI
正如我们所看到的,CPU 使用率 SLI 并不直接反映客户满意度;换句话说,除非 CPU 使用率超过 80% 阈值,否则增加 CPU 使用率与客户满意度下降之间没有直接关系。相比之下,延迟 SLI 与客户满意度直接相关,随着延迟的增加,客户满意度下降,特别是在 300ms 和 500ms 级别之后。因此,使用延迟作为 SLI 比使用 CPU 使用率更为合适。
同时,建议将 SLI 的数量限制在一个可管理的范围内。SLI 过多会导致团队混乱,并引发大量误报。最好专注于四个或五个与客户满意度直接相关的指标。例如,与其监控 CPU 和内存使用情况,不如优先考虑请求延迟和错误率等指标。
此外,优先考虑用户旅程至关重要,应该给予对客户影响较大的旅程更高的优先级,而对客户影响较小的旅程则给予较低的优先级。例如,确保我们的博客应用中创建和更新帖子体验的流畅性,比评论和评分服务更为关键。仅凭 SLI(服务级别指标)并没有太多意义,因为它们只是可衡量的指标。我们需要为 SLI 设定目标。因此,让我们来看看 SLO(服务级别目标)。
SLOs
谷歌对 SLO 的定义指出,它们“为你的服务可靠性设定了目标水平”。它们指定了考虑你的网站是否可靠所需遵循的 SLI 合规百分比。SLO 是通过结合一个或多个 SLI 来制定的。
例如,如果你有一个 SLI,要求在过去 15 分钟内请求延迟保持低于 500 毫秒,且按 95 百分位测量,那么一个 SLO 就需要在 99%的时间内满足该 SLI,以实现 99%的 SLO。
尽管每个组织都追求 100%的可靠性,但设定 100%的 SLO 并不是一个实际的目标。拥有 100%SLO 的系统往往成本高昂、技术复杂,并且对于大多数应用程序而言,用户接受度通常不需要如此高的可靠性。
在软件服务和系统领域,追求 100%的可用性通常是错误的,因为用户无法在一个 100%可用的系统和一个 99.999%可用的系统之间感知到任何实际差异。用户与服务之间存在多个中间系统,如他们的个人电脑、家庭 Wi-Fi、互联网服务提供商(ISP)和电力网,这些系统的可用性远低于 99.999%。因此,99.999%与 100%之间的微小差异在其他不可用来源的背景噪声中变得难以察觉。因此,投入大量精力去实现最后的 0.001%的可用性,对最终用户没有明显的好处。
根据这一理解,一个问题浮现:如果 100%不是一个合适的可靠性目标,那么系统的正确可靠性目标是什么?有趣的是,这并不是一个技术性的问题,而是一个与产品相关的问题,需要考虑以下几个因素:
-
用户满意度:确定与用户满意度相符的可用性水平,考虑用户的典型使用模式和期望。
-
替代方案:评估不满意用户在产品当前可用性水平不满意时是否会寻找替代方案,以及这些替代方案的可用性。
-
用户行为:研究用户在不同可用性水平下对产品的使用变化,认识到用户行为可能会因可用性的波动而发生变化。
此外,一个完全可靠的应用程序不留有引入新功能的空间,因为任何新的添加都有可能干扰现有服务。因此,必须在你的 SLO 中留出一定的容错空间。
SLO 代表内部目标,需要团队和内部利益相关者(包括开发人员、产品经理、SRE 和 CTO)之间达成共识。它们需要整个组织的承诺。未能满足 SLO 不会带来显式或隐式的惩罚。
例如,如果未满足 SLO,客户不能要求赔偿,但这可能会导致组织领导层的不满。这并不意味着未能满足 SLO 就应该没有后果。未达 SLO 通常会导致较少的变化和减少的功能开发,可能表明质量下降,且更加注重开发和测试职能。
SLO 应当是现实可行的,团队应积极努力达成它们。它们应与客户体验保持一致,确保当服务符合 SLO 时,客户不会察觉到任何服务质量问题。如果性能低于定义的 SLO,可能会影响客户体验,但不会到客户提出支持工单的程度。
一些组织实施两种类型的 SLO:可实现的和理想的。可实现的 SLO 代表整个团队应该达到的目标,而理想的 SLO 设定了更高的目标,是持续改进过程的一部分。
SLA
根据 Google 的说法,SLA 是“与用户的正式或隐式协议,概述了满足(或未能满足)所包含 SLO 时的后果。”
这些协议具有更为结构化的性质,代表了对客户做出的业务层级承诺,明确了如果组织未能履行 SLA 时将采取的措施。SLA 可以是显式的,也可以是隐式的。显式 SLA 涉及明确定义的后果,通常是通过服务信用的方式来补偿未达预期可靠性时的损失。隐式 SLA 则是通过评估对组织声誉的潜在损害和客户转向替代方案的可能性来进行评估。
SLA 通常设定在足以防止客户寻找替代方案的水平,因此,它们的阈值通常比 SLO 低。例如,在考虑请求延迟 SLI 时,SLO 可能定义为 300ms 的 SLI 值,而 SLA 可能设定为 500ms 的 SLI 值。这种区别源于 SLO 是与可靠性相关的内部目标,而 SLA 则是外部承诺。通过努力实现 SLO,团队自动满足 SLA,为组织提供了一层额外的保护,以防出现意外故障。
为了理解 SLIs、SLOs 和 SLAs 之间的关系,我们来看下图:
图 14.2 – SLIs、SLOs 和 SLAs
这张图展示了随着延迟水平变化,客户体验如何变化。如果我们将延迟 SLO 保持在 300ms 并满足它,一切正常!在 300ms 到 500ms 之间,客户开始感受到性能下降,但这不足以让他们失去冷静并开始提交支持工单。因此,将 SLA 设定为 500ms 是一个不错的策略。一旦超越 500ms 阈值,不满情绪就会出现,客户开始因服务延迟而提交支持工单。如果延迟超过 10s,那么这将成为运维团队的关注问题,一切都在着火。然而,正如我们所知,SLO 的措辞与我们在此想象的略有不同。当我们说我们有一个 300ms 延迟的 SLO 时,这并不意味着什么。一个现实的 SLO 对于要求 请求延迟在过去 15 分钟内保持低于 300ms 且按 95 分位测量 的 SLI 来说,就是要在 x% 的时间内 达到 SLI。那么这个 x 应该是多少呢?应该是 99%,还是 95%?我们该如何决定这个数字呢?要回答这个问题,我们需要看看 误差预算。
误差预算
根据 Liz Fong-Jones 和 Seth Vargo 的定义,误差预算表示 “产品团队和 SRE 团队之间共享的量化衡量标准,用于平衡创新 和稳定性。”
简而言之,误差预算量化了在引入新功能、进行服务维护、执行例行改进、管理网络和基础设施中断以及应对突发情况时,可以承受的风险水平。通常,监控系统会测量服务的正常运行时间,而 SLO 则设定了你希望达到的目标。误差预算是这两个指标之间的差值,表示在误差预算范围内可以用于发布新版本的时间。
这正是为什么通常不会一开始就设定*100%*的 SLO。错误预算起着至关重要的作用,帮助团队在创新与可靠性之间找到平衡。错误预算的基本理念来源于 SRE 视角,即故障是系统操作中自然且预期的一部分。因此,每当将新变化引入生产环境时,总会存在破坏服务的风险。因此,较高的错误预算允许引入更多的功能:
Error Budget = 100% — SLO
例如,如果你的 SLO 是99%,那么你的错误预算将是1%。如果你将此计算在一个月内,假设30 天/月和24 小时/天,那么你将有一个7.2 小时的错误预算,用于维护或其他活动。对于99.9%的 SLO,错误预算为每月43.2 分钟,而对于99.99%的 SLO,则为每月4.32 分钟。你可以参考下图获取更多细节:
图 14.3 – 错误预算与 SLO 比较
这些时间段表示实际的停机时间,但如果你的服务有冗余、高可用性措施和灾难恢复计划,那么你可以将这些时长延长,因为服务仍然可以正常运行,同时你可以修补或处理某一台服务器的问题。
现在,是否想在 SLO 中继续增加9,还是追求一个较低的数字,取决于你的终端用户、业务重要性和可用性需求。较高的 SLO 比较低的 SLO 更昂贵,且需要更多资源。然而,有时仅仅正确架构你的应用程序就能帮助你达到更好的 SLO 目标。
现在我们已经了解了 SLO、SLI、SLA 和错误预算,让我们谈谈灾难恢复。
灾难恢复、RTO 和 RPO
灾难恢复是一项综合策略,旨在确保组织在面对突发、破坏性事件(如自然灾害、网络攻击或系统故障)时的韧性。它涉及到必要的规划、政策、程序和技术,以快速有效地恢复关键的 IT 系统、数据和运营至正常状态。实施良好的灾难恢复计划能帮助企业最大限度地减少停机时间、数据丢失和财务影响,确保业务连续性,保护声誉,并迅速从逆境中恢复,从而最终保障企业的长期成功。
每个组织在不同程度上都包含了灾难恢复。有些选择定期备份或快照,而其他组织则投资于创建生产环境的故障切换副本。尽管故障切换副本提供了更高的韧性,但也会导致基础设施成本翻倍。组织采用的灾难恢复机制选择,依赖于两个关键的 KPI —— 恢复时间目标(RTO)和 恢复点目标(RPO)。
RTO 和 RPO 是灾难恢复和业务连续性规划中的关键指标。RTO 表示系统或应用程序的最大可接受停机时间,指定在发生中断后,系统应该在多长时间内恢复。它量化了服务不可用的可接受时长,并推动恢复工作的紧迫性。
另一方面,RPO 定义了在灾难发生时,最大可容忍的数据丢失量。它标示了数据必须恢复到的时间点,以确保业务连续性。实现较低的 RPO 意味着数据丢失最小化,通常通过频繁的数据备份和复制来实现。下图很好地解释了 RTO 和 RPO:
图 14.4 – RTO 和 RPO
较短的 RTO 和 RPO 需要一个更强大的灾难恢复计划,这将导致更高的基础设施和人力资源成本。因此,平衡 RTO 和 RPO 对于确保一个有韧性的 IT 基础设施至关重要。组织必须将其恢复策略与这些目标对齐,以最小化停机时间和数据丢失,从而在不可预见的中断期间保护业务运营和数据完整性。
运行分布式应用程序于生产环境中
到目前为止,我们一直在讨论运行应用程序于生产环境中的 KPI,并从 SRE 原则中获得灵感。现在,让我们理解如何将这些思路集中到一个地方,以便运行分布式应用程序于生产环境中。
分布式应用程序 或 微服务 与单体应用本质上不同。管理单体应用的工作是确保一个应用的所有操作方面,而随着微服务的出现,复杂性呈指数级增长。因此,我们应该采取不同的方式来处理它。
从 SRE 的角度来看,运行分布式应用程序于生产环境中意味着专注于确保应用程序的 可靠性、可扩展性 和 性能。以下是 SRE 如何处理这一任务:
-
SLO:SRE 从定义明确的 SLO 开始,SLO 规定了分布式应用程序所需的可靠性水平。SLO 指定了可接受的 延迟、错误率 和 可用性 水平。这些 SLO 在指导团队的工作以及判断系统是否达到了其可靠性目标方面起着至关重要的作用。
-
SLI:SRE 会建立 SLI,SLI 是用于衡量应用程序可靠性的可量化指标。这些指标可能包括响应时间、错误率和其他性能指标。SLI 提供了一种具体的方法来评估应用程序是否达到了其 SLO。
-
错误预算:错误预算是 SRE 中的一个关键概念。它们表示在违反 SLO(服务级别目标)之前,允许发生的停机或错误的最大数量。SRE 通过错误预算来平衡可靠性和创新。如果错误预算用尽,可能需要将重点放在稳定性和可靠性上,而不是推出新特性。
-
监控和警报:SRE 实施强大的监控和警报系统,持续跟踪应用程序的性能和健康状况。他们根据服务级指标(SLIs)和服务级目标(SLOs)设置警报,使他们能够主动响应事件或性能水平的偏差。在分布式应用领域,使用如Istio或Linkerd的服务网格可以提供帮助。它们帮助你通过单一视图来可视化应用程序的各个部分,并让你轻松监控应用程序并发出警报。
-
容量规划:SRE 确保支撑分布式应用程序的基础设施能够应对预期的负载和流量。他们进行容量规划,以便根据需要扩展资源,防止流量激增时出现性能瓶颈。借助现代公有云平台,自动化流量的可扩展性变得更加容易实现,尤其是在分布式应用的情况下。
-
自动化修复:自动化是 SRE 实践的基石。SRE 开发自动化系统用于事件响应和修复。这包括自动扩展、自愈机制和自动回滚程序,以最小化停机时间。
-
混沌工程:SRE 经常采用混沌工程实践,故意将受控故障引入系统。这有助于识别分布式应用中的弱点和漏洞,从而提前采取措施减轻潜在问题。一些最流行的混沌工程工具包括 Chaos Monkey、Gremlin、Chaos Toolkit、Chaos Blade、Pumba、ToxiProxy 和 Chaos Mesh。
-
值班和事件管理:SRE(站点可靠性工程师)保持值班轮换,以确保全天候覆盖。他们遵循明确的事件管理流程,迅速解决问题,并从事件中吸取经验教训,防止问题的重复发生。大多数 SRE 开发积压来自于此过程,因为他们从失败中学习,因此会自动化可重复的任务。
-
持续改进:SRE 是一种持续改进的文化。SRE 团队定期进行事件后复盘(PIRs)和根本原因分析(RCAs),以识别改进的领域。从事件中学到的经验教训被用来优化 SLOs,并提高应用程序的整体可靠性。
-
文档编写和知识共享:SRE 编写最佳实践、运行手册和操作流程。他们强调跨团队的知识共享,确保专业知识不被孤立,并且所有团队成员都能够有效地管理和排查分布式应用程序的问题。他们还致力于自动化运行手册,以确保手动过程保持在最低限度。
总结来说,SRE 在生产环境中运行分布式应用程序的方法专注于 可靠性、自动化 和 持续改进。它设定明确的目标,建立度量标准,并采用主动监控和事件管理实践,向最终用户提供高度可用和高性能的服务。
总结
本章介绍了 SRE 和在生产环境中运行服务的关键绩效指标(KPI)。我们从理解软件可靠性开始,探讨了如何利用 SRE 管理生产环境中的应用程序。我们讨论了 SRE 的三个关键参数:SLI、SLO 和 SLA。我们还探索了错误预算及其在系统变更引入中的重要性。接着,我们讲解了软件灾难恢复、RPO 和 RTO 以及它们如何定义我们的灾难恢复措施的复杂性或成本。最后,我们了解了 DevOps 或 SRE 如何使用这些概念来管理生产环境中的分布式应用。
在下一章中,我们将把所学的知识应用于实际,探索如何使用名为 Istio 的服务网格来管理所有这些方面。
问题
回答以下问题,测试您对本章内容的掌握情况:
-
以下哪项是 SLI 的良好示例?
A. 响应时间不应超过 300 毫秒。
B. 15 分钟窗口中的响应时间的第 95 百分位应不超过 300 毫秒。
C. 99% 的所有请求应在 300 毫秒内响应。
D. 故障数量不应超过 1%。
-
一个成熟的组织应当有 100% 的 SLO。(对/错)
-
SLO 与任何客户发起的惩罚性措施无关。(对/错)
-
在决定 SLO 时,您应考虑以下哪些因素?(选择三项)
A. 用户满意度
B. 替代方案
C. 用户行为
D. 系统容量
-
SLA 通常保持比 SLO 更严格的 SLI 值。(对/错)
-
在定义 SLI 时,您应考虑以下哪些因素?
A. CPU、内存和磁盘利用率
B. 延迟、错误、流量和饱和度
C. 利用率、容量和规模
-
1% 的错误预算每月提供多少停机时间?
A. 72 小时
B. 43.2 分钟
C. 7.2 小时
D. 4.32 分钟
-
SRE 是一名从事运维的开发人员。(对/错)
-
SRE 应分配多少最小时间用于开发工作?
A. 30%
B. 40%
C. 50%
D. 60%
答案
以下是本章问题的答案:
-
B
-
错
-
对
-
A, B, C
-
错
-
B
-
C
-
对
-
C
第十五章:使用 Istio 实施流量管理、安全性和可观察性
在上一章中,我们讨论了站点可靠性工程(SRE)及其如何通过 DevOps 实践帮助管理生产环境。在本章中,我们将深入探讨一种名为 Istio 的服务网格技术,它将帮助我们实施 SRE 实践,并在生产中更好地管理我们的应用。
在本章中,我们将讨论以下主要主题:
-
重新审视博客应用
-
服务网格简介
-
Istio 简介
-
了解 Istio 架构
-
安装 Istio
-
使用 Istio Ingress 来允许流量
-
使用 Istio 保护你的微服务
-
使用 Istio 管理流量
-
使用 Istio 观察流量并设置告警
技术要求
本章中,我们将启动一个基于云的 Kubernetes 集群——Google Kubernetes Engine(GKE),用于练习。在撰写本文时,Google Cloud Platform(GCP)提供了一个为期 90 天、价值 300 美元的免费试用,你可以在console.cloud.google.com/注册一个。
你还需要克隆以下 GitHub 仓库以完成一些练习:github.com/PacktPublishing/Modern-DevOps-Practices-2e。
你可以使用 GCP 提供的 Cloud Shell 来完成本章内容。进入 Cloud Shell 并启动一个新的会话。运行以下命令将仓库克隆到你的主目录中,以便访问所需资源:
$ git clone https://github.com/PacktPublishing/Modern-DevOps-Practices-2e.git \
modern-devops
我们还需要设置项目 ID,并启用一些我们将在本章中使用的 GCP API。为此,运行以下命令:
$ PROJECT_ID=<YOUR_PROJECT_ID>
$ gcloud services enable iam.googleapis.com \
container.googleapis.com \
binaryauthorization.googleapis.com \
containeranalysis.googleapis.com \
secretmanager.googleapis.com \
cloudresourcemanager.googleapis.com \
cloudkms.googleapis.com
如果你没有按照前几章的内容进行学习,想要快速开始,可以继续阅读下一个部分,设置基础环境,不过我强烈建议你先阅读前几章,了解整个流程。如果你已经跟着前几章的实操练习走了,那么可以跳过这一部分。
设置基础环境
为了确保与前几章的连续性,我们首先创建一个服务账户,用于 Terraform 与我们的 GCP 项目进行交互:
$ gcloud iam service-accounts create terraform \
--description="Service Account for terraform" --display-name="Terraform"
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:terraform@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/editor"
$ gcloud iam service-accounts keys create key-file \
--iam-account=terraform@$PROJECT_ID.iam.gserviceaccount.com
你会看到一个名为key-file的文件已经在你的工作目录中创建。现在,在 GitHub 上创建一个名为mdo-environments的新仓库,并添加一个README.md文件,重新命名main分支为prod,并使用 GitHub 创建一个名为dev的新分支。导航至https://github.com/<your_github_user>/mdo-environments/settings/secrets/actions/new,创建一个名为GCP_CREDENTIALS的密钥。在values字段中,打印key-file文件,复制其内容并粘贴到 GitHub 密钥的values字段中。
接下来,创建另一个密钥PROJECT_ID,并在values字段中指定你的 GCP 项目 ID。
接下来,我们需要为 Terraform 创建一个GCS bucket作为远程后端。为此,运行以下命令:
$ gsutil mb gs://tf-state-mdo-terraform-${PROJECT_ID}
接下来,我们需要设置我们的 Secrets Manager。让我们创建一个名为 external-secrets 的密钥,在其中传递 MongoDB 的凭证,格式为 JSON。为此,请运行以下命令:
$ echo -ne '{"MONGO_INITDB_ROOT_USERNAME": "root", \
"MONGO_INITDB_ROOT_PASSWORD": "itsasecret"}' | \
gcloud secrets create external-secrets --locations=us-central1 \
--replication-policy=user-managed --data-file=-
Created version [1] of the secret [external-secrets].
我们需要创建 Secret 资源,它将与 GCP 交互以获取存储的密钥。首先,我们需要使用以下命令创建一个 GCP 服务账户与 Secrets Manager 进行交互:
$ cd ~
$ gcloud iam service-accounts create external-secrets
由于我们遵循最小权限原则,我们将添加以下角色绑定,仅提供对 external-secrets 密钥的访问,具体如下:
$ gcloud secrets add-iam-policy-binding external-secrets \
--member "serviceAccount:external-secrets@$PROJECT_ID.iam.gserviceaccount.com" \
--role "roles/secretmanager.secretAccessor"
现在,让我们使用以下命令生成服务账户密钥文件:
$ gcloud iam service-accounts keys create key.json \
--iam-account=external-secrets@$PROJECT_ID.iam.gserviceaccount.com
接下来,将 key.json 文件的内容复制到一个新的 GitHub Actions 密钥 GCP_SM_CREDENTIALS 中。
我们还需要创建以下 GitHub Actions 密钥,以使二进制授权生效:
ATTESTOR_NAME=quality-assurance-attestor
KMS_KEY_LOCATION=us-central1
KMS_KEYRING_NAME=qa-attestor-keyring
KMS_KEY_NAME=quality-assurance-attestor-key
KMS_KEY_VERSION=1
由于工作流在结束时会自动提交拉取请求,我们需要定义一个 GitHub token。这个 token 允许工作流在创建拉取请求时代表当前用户执行操作。以下是步骤:
-
为
mdo-environments仓库创建一个具有“仓库”访问权限的新 token,授予它读写拉取请求权限。这种方法符合最小权限原则,提供更精细的控制。 -
创建 token 后,复制它。
-
现在,创建一个名为
GH_TOKEN的 GitHub Actions 密钥,并将复制的 token 作为值粘贴进去。
现在所有先决条件都已满足,我们可以克隆我们的仓库并复制基础代码。运行以下命令进行此操作:
$ cd ~ && git clone git@github.com:<your_github_user>/mdo-environments.git
$ cd mdo-environments/
$ git checkout dev
$ cp -r ~/modern-devops/ch15/baseline/* .
$ cp -r ~/modern-devops/ch15/baseline/.github .
现在我们处于基础阶段,让我们进一步了解本章将要部署和管理的示例博客应用。
重温博客应用
既然我们之前讨论过博客应用,那么让我们再看一遍它的服务及其交互:
图 15.1 – 博客应用及其服务和交互
到目前为止,我们已经使用 GitHub Actions 为构建、测试和推送博客应用微服务容器创建了 CI 和 CD 管道,并通过 Argo CD 在 GKE 集群中部署它们。
正如你可能记得的,我们为确保应用的顺利运行创建了以下资源:
-
MongoDB:我们部署了一个启用了身份验证的 MongoDB 数据库,并使用 root 凭证。凭证通过来自 Kubernetes Secret 资源的环境变量注入。为了持久化数据库数据,我们创建了一个挂载到容器的 PersistentVolume,并使用 PersistentVolumeClaim 动态供应它。由于容器是有状态的,我们使用 StatefulSet 来管理它,并因此使用无头 Service 来公开数据库。
-
帖子、评论、评分和用户:帖子、评论、评分和用户微服务通过根凭证与 MongoDB 交互,这些凭证通过来自同一Secret的环境变量注入。我们使用各自的Deployment资源部署它们,并通过单独的ClusterIP Services暴露它们。
-
前端:前端微服务不需要与 MongoDB 交互,因此没有与 Secret 资源的交互。我们也使用Deployment资源部署了该服务。由于我们希望将该服务暴露到互联网,我们为其创建了一个LoadBalancer Service。
我们可以通过以下图示来总结它们:
图 15.2 – 博客应用 – Kubernetes 资源和交互
现在我们了解了应用的结构,接下来我们了解一下服务网格是什么以及它在这个用例中的优势。
服务网格介绍
想象一下你处在一个繁忙的城市中,城市里有着复杂的道路和高速公路网络。你正在驾驶汽车,从城市的一侧开到另一侧。在这种情况下,你会接触到以下实体:
-
你的汽车:你的汽车代表计算机系统中的一个独立服务或应用。它有一个特定的目的,就像软件架构中的微服务或应用一样。
-
道路和高速公路:道路和高速公路就像是你应用中不同服务之间的网络连接和通信路径。服务需要互相交互和通信来执行各种功能,就像车辆需要道路从一个地方开到另一个地方。
-
交通灯和标志:交通灯、标志和交通规则帮助管理交通流量,确保车辆(服务)能够安全、高效地在城市中行驶。这些就像服务网格中的规则、协议和工具,调节服务之间的通信和数据交换。
-
交通控制中心:可以把交通控制中心看作是服务网格。它是一个集中式的系统,监控和管理城市的交通流量。类似地,服务网格是一个集中式基础设施,监督并促进服务之间的通信,确保它们能够可靠、安全地通信。
-
流量监控与优化:交通控制中心确保安全通行并能够优化交通流量。它可以重新规划车辆路线,避免拥堵或事故。在服务网格的上下文中,它能够优化数据和请求在服务之间的流动,确保高效和弹性的通信。
-
安全性和可靠性:在城市中,交通控制中心有助于防止事故,确保每个人都能安全到达目的地。同样,服务网格通过提供负载均衡、安全性和容错等功能,提高计算机系统的安全性和可靠性。
就像交通控制中心让你在复杂的城市中更容易、安全地出行一样,计算机系统中的服务网格简化并保护了不同服务之间的通信,确保数据和请求能够顺畅、可靠、安全地流动。
容器及其管理平台,如 Kubernetes,简化了我们处理微服务的方式。容器技术的引入在推广这一概念方面起到了关键作用,它使得各个应用组件能够作为独立的实体执行和扩展,每个组件都有一个隔离的运行环境。
尽管采用微服务架构提供了加速开发、增强系统稳定性、简化测试和能够独立扩展应用各个部分等优势,但它也有其挑战。管理微服务可能是一项复杂的工作。你不再处理单一的单体应用,而是拥有多个动态组件,每个组件都承担特定的功能。
在大规模应用的背景下,看到数百个微服务相互交互并不罕见,这可能会迅速变得令人不知所措。你的安全和运维团队可能会提出以下主要问题:
-
确保微服务之间的安全通信。你需要保护许多小服务,而不是保护一个单体应用。
-
在出现问题时,如何隔离一个有问题的微服务。
-
在全面发布前,以有限比例的流量进行部署测试,以建立信任。
-
整合现在分布在多个来源的应用日志。
-
监控服务的健康状况变得更加复杂,因为应用由许多组件组成。
虽然 Kubernetes 有效地解决了一些管理问题,但它主要作为容器编排平台,并且在这一角色中表现出色。然而,它并没有固有地解决微服务架构的所有复杂性,因为这些架构需要特定的解决方案。Kubernetes 本身并不提供强大的服务管理功能。
默认情况下,Kubernetes 容器之间的通信缺乏安全措施,强制在 Pod 之间使用 TLS 需要管理大量的 TLS 证书。Pod 之间的身份和访问管理也不是开箱即用的。
虽然像 Kubernetes 网络策略这样的工具可以用于在 Pod 之间实施防火墙,但它们在第 3 层而非第 7 层运作,而现代防火墙正是基于第 7 层操作。这意味着你可以识别流量的来源,但无法检查数据包,从而做出基于元数据的决策,例如基于 HTTP 头进行路由。
尽管 Kubernetes 提供了部署 Pod、进行 A/B 测试和金丝雀发布的方法,但这些过程通常涉及容器副本的扩展。例如,部署一个新版本的微服务并将 10% 的流量导向它,至少需要 10 个容器:9 个用于旧版本,1 个用于新版本。Kubernetes 在 Pod 之间平均分配流量,而没有智能流量拆分。
每个 Kubernetes 容器在 Pod 内保持独立的日志记录,因此需要一个定制的解决方案来捕获和汇总日志。
虽然 Kubernetes 仪表盘提供了监控 Pod 和检查其健康状况等功能,但它无法提供关于组件如何交互、Pod 之间流量分配情况或构成应用程序的容器链的信息。无法追踪流量在 Kubernetes Pod 中的流动意味着你无法确定请求在链中遇到故障的位置。
为了全面应对这些挑战,像 Istio 这样的服务网格技术可以提供极大的帮助。它可以有效应对 Kubernetes 中微服务管理的复杂性,并为安全通信、智能流量管理、监控等提供解决方案。让我们通过简要介绍来了解 Istio 服务网格是什么。
Istio 简介
Istio 是一种服务网格技术,旨在简化服务连接、安全性、治理和监控。
在微服务应用的背景下,每个微服务独立运行,使用容器,从而产生了一个复杂的交互网络。这就是服务网格发挥作用的地方,它简化了这些交互的发现、管理和控制,通常通过旁车代理来实现。让我一步一步为你解析。
想象一个标准的 Kubernetes 应用,由前端和后端 Pod 组成。Kubernetes 提供了使用 Kubernetes 服务和 CoreDNS 进行 Pod 之间的内建服务发现。因此,你可以使用服务名称将流量从一个 Pod 引导到另一个 Pod。然而,你对这些交互和运行时流量管理的控制将有限。
Istio 通过将旁车容器注入到 Pod 中,充当代理。你的容器通过这个代理与其他容器进行通信。这种架构使得所有请求都通过代理流动,从而使你能够控制流量并收集数据以供进一步分析。此外,Istio 提供了加密 Pod 之间通信的手段,并通过统一的控制平面实施身份和访问管理。
由于这种架构,Istio 拥有一系列核心功能,能够提升微服务环境中的流量管理、安全性和可观测性。
流量管理
Istio 通过利用 sidecar 代理(通常称为 envoy 代理)以及入口和出口网关,有效地管理流量。借助这些组件,Istio 使你能够塑造流量并定义服务交互规则。这包括实现诸如超时、重试、熔断器等功能,所有这些都可以通过控制面中的配置进行设置。
这些功能为智能实践提供了可能,比如A/B 测试、金丝雀部署和分阶段发布,以及基于百分比的流量划分。你可以无缝执行渐进式发布,从现有版本(Blue)过渡到新版本(Green),所有操作都可以通过用户友好的控制界面完成。
此外,Istio 允许你在生产环境中进行操作测试,通过实时流量镜像来测试实例。这使你能够收集实时数据并在问题影响应用程序之前识别潜在的生产问题。此外,你还可以基于地理位置或用户档案等因素,将请求路由到不同语言版本的微服务。
安全性
Istio 重视安全性,通过 envoy 代理保护你的微服务,并通过互信 TLS 在 pods 之间建立身份访问管理。这是一种强大的防御机制,能够防止中间人攻击,且在 pods 之间提供开箱即用的流量加密。这种互认证确保只有受信任的前端可以连接到后台,从而建立强大的信任关系。因此,即使其中一个 pod 被攻破,它也无法威胁到应用程序的其他部分。Istio 进一步增强了安全性,提供了细粒度的访问控制策略,并引入了目前 Kubernetes 中缺乏的审计工具,从而提升了集群的整体安全性。
可观察性
由于 envoy sidecar 的存在,Istio 能够敏锐地感知流经 pod 的流量,从而使你能够从服务中收集至关重要的遥测数据。这些丰富的数据有助于深入了解服务行为,并为应用程序未来的优化提供了一个窗口。此外,Istio 还整合了应用程序日志,并通过多个微服务实现流量追踪。这些功能使你能够更迅速地识别和解决问题,帮助你隔离有问题的服务并加快调试过程。
面向开发者的友好性
Istio 最显著的特点是,它能够减轻开发者在实现过程中管理安全性和操作复杂性的负担。
Istio 对 Kubernetes 的深刻理解使得开发者可以继续像标准 Kubernetes 部署那样构建应用程序。Istio 会无缝且自动地将 sidecar 容器注入到 pods 中,免去开发者对这些技术细节的担忧。
一旦这些边车容器被集成,运维和安全团队就可以介入,执行与流量管理、安全性及应用程序整体运营相关的政策。这为所有相关方创造了互利的局面。
Istio 使安全和运维团队能够高效地监管微服务应用程序,而不妨碍开发团队的生产力。这种协作方法确保组织内的每个团队都能保持其专业焦点,并有效地为应用程序的成功做出贡献。现在我们已经了解了 Istio,接下来我们将看看它的架构。
理解 Istio 架构
Istio 通过两个基本组件简化了微服务的管理:
-
数据平面:数据平面由 Istio 注入到微服务中的边车 envoy 代理组成。这些代理承担着在不同服务之间路由流量的关键角色,并收集重要的遥测数据,以便于监控和分析。
-
控制平面:控制平面充当指挥中心,指示数据平面如何有效地路由流量。它还负责配置细节的存储和管理,便于管理员与边车代理交互并控制 Istio 服务网格。本质上,控制平面是 Istio 的智能和决策中心。
类似地,Istio 管理两种类型的流量:
-
数据平面流量:这种流量由微服务之间交换的核心业务相关数据组成。它涵盖了应用程序处理的实际交互和事务。
-
控制平面流量:相反,控制平面流量由 Istio 组件之间的消息和通信组成,主要负责管理服务网格的行为。它充当着控制机制,协调微服务架构中的路由、安全性和整体功能。
下图详细描述了 Istio 架构:
图 15.3 – Istio 架构
正如我们在前面的图示中看到的,控制平面和数据平面是两个不同的部分,接下来我们将深入了解它们。
控制平面架构
Istio 将控制平面作为一个单独的 istiod 组件进行部署。Istio 控制平面,或称 istiod,包含多个关键组件,每个组件在管理服务网格中扮演着独特的角色。
Pilot
Pilot 作为服务网格的中央控制中心,使用 Envoy API 与 envoy sidecar 通信,并将 Istio 清单中指定的高级规则转换为 envoy 配置。Pilot 支持服务发现、智能流量管理和路由功能。它使您能够实施 A/B 测试、蓝绿部署、金丝雀发布等实践。此外,Pilot 通过配置 sidecar 处理超时、重试和断路等任务,增强了服务网格的弹性。它的一个显著特点是提供 Istio 配置与底层基础设施之间的桥梁,使 Istio 能够在多种平台上运行,如 Kubernetes、Nomad 和 Consul。无论平台如何,Pilot 都能确保一致的流量管理。
Citadel
Citadel 专注于在您的服务网格内进行身份和访问管理,促进 Kubernetes pod 之间的安全通信。它通过确保加密通信来保护您的 pod,即使您的开发人员设计的组件使用了不安全的 TCP 连接。Citadel 通过管理证书的复杂性简化了相互 TLS 的实现。它提供用户身份验证、凭证管理、证书处理和流量加密,确保 pod 在必要时能够安全地相互验证。
Galley
Galley 负责服务网格中的基本配置任务。它验证、处理并分发配置更改到 Istio 控制平面。例如,当您应用新的策略时,Galley 会摄取该配置,验证其准确性,处理并为目标组件准备,最后无缝地在服务网格中分发。简而言之,Galley 作为 Istio 控制平面与底层 API 之间的接口,促进了服务网格的顺利管理。
现在,让我们深入了解数据平面架构。
数据平面架构
Istio 的数据平面组件由 envoy 代理、入口网关 和 出口网关 组成。
Envoy 代理
Envoy 代理在启用服务网格的各个方面中发挥着至关重要的作用。这些 第 7 层 代理能够根据它们处理的消息内容做出重要决策,并且它们是唯一直接与您的业务流量交互的组件。以下是这些 envoy 代理如何贡献于 Istio 的功能:
-
流量控制:它们提供对服务网格内流量流动的细粒度控制,允许您为各种类型的流量定义路由规则,包括 HTTP、TCP、WebSockets 和 gRPC。
-
安全性和认证:Envoy 代理执行身份和访问管理,确保只有经过授权的 pod 可以相互交互。它们实现相互 TLS和流量加密,以防止中间人攻击,并提供如速率限制等特性,以防止超出预算的成本和拒绝服务攻击。
-
网络弹性:它们通过支持重试、故障转移、断路器和故障注入等特性来增强网络弹性,保持服务的可靠性和健壮性。
接下来,我们来看看入站和出站网关。
入站和出站网关
在 Istio 中,入站网关是一组一个或多个 envoy 代理,Pilot 在其部署时动态配置这些代理。这些 envoy 代理在控制和路由外部流量进入您的服务网格中至关重要,确保流量根据定义的路由规则和策略正确地指向相关服务。这种动态配置使得 Istio 能够有效地管理和保护外部流量流动,而无需大量的人工干预,确保您的应用程序可以在服务网格内高效、安全地运行。
出站网关与入站网关相似,但它们处理的是出站流量。为了更好地理解这一点,我们可以参考图 15.3,了解服务 A和服务 B之间的流量流向。
在这个架构中,服务网格中的流量遵循一个结构化的路径,通过入站、微服务(服务 A和服务 B)以及出站,确保高效的路由和安全措施。让我们分解一下流量包在服务网格中的流动过程。
入站
流量通过入站资源进入服务网格,入站资源本质上是一个 envoy 代理集群。Pilot 在部署时配置这些 envoy 代理。由于基于 Kubernetes 服务端点的配置,入站代理了解其后端服务。入站代理执行健康检查、负载均衡,并基于负载、数据包、配额和流量平衡等指标做出智能路由决策。
服务 A
一旦入站网关将流量路由到一个 pod,它会遇到服务 A pod 的边车代理容器,而不是实际的微服务容器。envoy 代理和微服务容器共享 pod 内的同一网络命名空间,并且具有相同的 IP 地址和 IP 表规则。envoy 代理控制 pod,处理通过它的所有流量。该代理与 Citadel 进行交互,执行策略,检查流量是否需要加密,并与后端 pod 建立 TLS 连接。
服务 B
服务 A 的加密数据包被发送到服务 B,服务 B 会遵循类似的步骤。服务 B 的代理通过与源代理进行 TLS 握手来验证发送者的身份。在建立信任后,数据包被转发到服务 B 的容器,继续流向出口层。
出口
出口资源管理着来自网格的出站流量。出口定义了哪些流量可以离开网格,并使用 Pilot 进行配置,类似于入口层。出口资源使得可以实施限制出站流量只流向必要服务的策略。
遥测数据收集
在这些步骤中,代理收集来自流量的遥测数据。这些遥测数据会发送到Prometheus进行存储和分析。这些数据可以在Grafana中可视化,提供对服务网格行为的洞察。遥测数据也可以发送到外部工具,如ELK,以便对收集的指标进行更深入的分析和机器学习应用。
这一结构化流程确保流量在服务网格中安全高效地流动,同时为监控、分析和决策过程提供宝贵的洞察。
现在我们已经理解了 Istio 的架构和特点,接下来让我们看看如何安装它。
安装 Istio
安装 Istio 的一般方式是通过提供的链接下载 Istio 并运行一个 shell,这将把 Istio 安装到我们的系统中,包括istioctl组件。然后,我们需要使用istioctl在 Kubernetes 集群中安装 Istio。然而,由于我们使用的是 GitOps,我们将使用 GitOps 原则来安装它。Istio 还提供了另一种安装方式——使用 Helm。由于我们知道 Argo CD 支持 Helm,因此我们将使用它。
因此,我们将创建新的 Argo CD 应用程序来部署它。我们将为istio-base创建一个 Argo CD 应用程序:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-base
namespace: argo
spec:
project: default
source:
chart: base
repoURL: https://istio-release.storage.googleapis.com/charts
targetRevision: 1.19.1
helm:
releaseName: istio-base
destination:
server: "https://kubernetes.default.svc"
namespace: istio-system
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
selfHeal: true
如我们所见,它将从istio-release.storage.googleapis.com/charts部署istio-base的v1.19.1 helm 图表到 Kubernetes 集群的istio-system命名空间。类似地,我们将使用以下配置将istiod部署到istio-system命名空间:
...
source:
chart: istiod
repoURL: https://istio-release.storage.googleapis.com/charts
targetRevision: 1.19.1
helm:
releaseName: istiod
destination:
server: "https://kubernetes.default.svc"
namespace: istio-system
...
最后,我们将使用以下配置在istio-ingress命名空间中安装istio-ingress组件:
...
source:
chart: gateway
repoURL: https://istio-release.storage.googleapis.com/charts
targetRevision: 1.19.1
helm:
releaseName: istio-ingress
destination:
server: "https://kubernetes.default.svc"
namespace: istio-ingress
...
我们还将在 Terraform 中定义配置,以便我们可以使用基于推送的 GitOps 自动创建我们的应用。因此,我们将以下内容附加到app.tf文件中:
data "kubectl_file_documents" "istio" {
content = file("../manifests/argocd/istio.yaml")
}
resource "kubectl_manifest" "istio" {
depends_on = [
kubectl_manifest.gcpsm-secrets,
]
for_each = data.kubectl_file_documents.istio.manifests
yaml_body = each.value
override_namespace = "argocd"
}
现在,我们可以提交并推送这些文件到我们的远程仓库,并等待 Argo CD 使用以下命令进行变更协调:
$ cd ~
$ cp -a ~/modern-devops/ch15/install-istio/app.tf \
~/mdo-environments/terraform/app.tf
$ cp -a ~/modern-devops/ch15/install-istio/istio.yaml \
~/mdo-environments/manifests/argocd/istio.yaml
$ git add --all
$ git commit -m "Install istio"
$ git push
一旦我们推送代码,我们将看到 GitHub Actions 工作流已触发。要访问该工作流,请前往https://github.com/<your_github_user>/mdo-environments/actions。不久后,工作流将应用配置并创建 Kubernetes 集群,部署 Argo CD、外部机密、我们的 Blog 应用和 Istio。
一旦工作流成功执行,我们必须访问 Argo Web UI。为此,我们需要使用 GKE 集群进行身份验证。执行以下命令来完成身份验证:
$ gcloud container clusters get-credentials \
mdo-cluster-dev --zone us-central1-a --project $PROJECT_ID
要使用 Argo CD Web UI,您需要argo-server服务的外部 IP 地址。要获取该地址,请运行以下命令:
$ kubectl get svc argocd-server -n argocd
NAME TYPE EXTERNAL-IP PORTS AGE
argocd-server LoadBalaner 34.122.51.25 80/TCP,443/TCP 6m15s
现在,我们知道可以通过https://34.122.51.25/访问 Argo CD。
接下来,我们将运行以下命令来重置管理员密码:
$ kubectl patch secret argocd-secret -n argocd \
-p '{"data": {"admin.password": null, "admin.passwordMtime": null}}'
$ kubectl scale deployment argocd-server --replicas 0 -n argocd
$ kubectl scale deployment argocd-server --replicas 1 -n argocd
现在,等待 2 分钟让新凭证生成。之后,执行以下命令以获取密码:
$ kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d && echo
现在我们已经有了凭证,可以登录了。我们将看到以下页面:
图 15.4 – Argo CD Web UI – 主页
如我们所见,Istio 应用程序已启动并运行。尽管 Istio 已经安装并运行,但除非我们要求 Istio 注入侧车,否则侧车不会被注入。接下来我们会讲解这个部分。
启用自动侧车注入
由于 envoy 侧车是 Istio 功能背后的关键技术,它们必须添加到现有的 Pod 中,以便 Istio 能够管理它们。更新每个 Pod 的配置以包含这些侧车可能具有挑战性。为了解决这个问题,Istio 提供了解决方案,通过启用这些侧车的自动注入功能。要在命名空间上启用自动侧车注入,我们必须添加一个标签——即istio-injection: enabled。为此,我们将修改blog-app.yaml文件,并将标签添加到命名空间资源中:
apiVersion: v1
kind: Namespace
metadata:
name: blog-app
labels:
istio-injection: enabled
...
现在,我们可以将此资源提交到 Git 并使用以下命令将更改推送到远程:
$ cd ~
$ cp -a ~/modern-devops/ch15/install-istio/blog-app.yaml \
~/mdo-environments/manifests/blog-app/blog-app.yaml
$ git add --all
$ git commit -m "Enable sidecar injection"
$ git push
在下一次 Argo CD 同步中,我们将很快找到附加到命名空间的标签。标签应用后,我们需要重新启动部署和有状态集,此时新 Pod 将启动并带有注入的侧车。使用以下命令进行操作:
$ kubectl -n blog-app rollout restart deploy frontend
$ kubectl -n blog-app rollout restart deploy posts
$ kubectl -n blog-app rollout restart deploy users
$ kubectl -n blog-app rollout restart deploy reviews
$ kubectl -n blog-app rollout restart deploy ratings
$ kubectl -n blog-app rollout restart statefulset mongodb
现在,让我们使用以下命令列出blog-app命名空间中的 Pod:
$ kubectl get pod -n blog-app
NAME READY STATUS RESTARTS AGE
frontend-759f58f579-gqkp9 2/2 Running 0 109s
mongodb-0 2/2 Running 0 98s
posts-5cdcb5cdf6-6wjrr 2/2 Running 0 108s
ratings-9888d6fb5-j27l2 2/2 Running 0 105s
reviews-55ccb7fbd9-vw72m 2/2 Running 0 106s
users-5dbd56c4c5-stgjp 2/2 Running 0 107s
如我们所见,Pod 现在显示两个容器,而不是一个。额外的容器是 envoy 侧车。Istio 的安装和设置已完成。
现在,我们的应用已经注入了 Istio 侧车,我们可以使用 Istio 入口控制器来允许流量访问我们的应用,当前该应用通过负载均衡服务暴露。
使用 Istio 入口控制器来允许流量
我们需要创建一个 Blog 应用的入口网关,将我们的应用与 Istio 入口网关关联起来。这是配置我们的应用通过 Istio 入口网关路由流量所必需的,因为我们希望利用 Istio 的流量管理和安全功能。
Istio 在安装过程中会部署 Istio 入口网关,并且默认将其暴露在负载均衡器上。要确定负载均衡器的 IP 地址和端口,你可以运行以下命令:
$ kubectl get svc istio-ingress -n istio-ingress
NAME EXTERNAL-IP PORT(S)
istio-ingress 34.30.247.164 80:30950/TCP,443:32100/TCP
如我们所见,Istio 在负载均衡器上暴露了多个端口,而我们的应用程序需要运行在端口 80,因此我们可以使用http://<IngressLoadBalancerExternalIP>:80来访问它。
下一步是使用这个入口网关并暴露我们的应用程序。为此,我们需要创建**网关(Gateway)和虚拟服务(VirtualService)**资源。
Istio 网关是一个自定义资源定义(CRD),它帮助你定义外部流量如何访问你的服务网格中的服务。它充当服务的入口点,并作为传入流量的负载均衡器。当外部流量到达网关时,它会根据指定的路由规则来决定如何将流量路由到相应的服务。
当我们定义一个 Istio 网关时,我们还需要定义一个使用该网关并描述流量路由规则的VirtualService资源。如果没有VirtualService资源,Istio 网关将不知道如何以及将流量路由到哪里。VirtualService资源不仅用于从网关路由流量,还用于在网格中的不同服务之间路由流量。它允许你定义复杂的路由规则,包括流量分配、重试、超时等。虚拟服务通常与特定的服务或工作负载关联,并决定如何将流量路由到这些服务。你可以使用虚拟服务来控制流量如何在服务的不同版本之间分配,从而支持 A/B 测试、金丝雀发布和蓝绿部署等实践。虚拟服务还可以根据 HTTP 头、路径或其他请求属性来路由流量。在当前的上下文中,我们将使用VirtualService资源根据路径过滤流量,并将它们全部路由到前端微服务。
让我们首先查看Gateway资源的定义:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: blog-app-gateway
namespace: blog-app
spec:
selector:
istio: ingress
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
如我们所见,我们定义了一个使用 Istio 入口网关(由istio: ingress选择器定义)的Gateway资源,并监听 HTTP 端口80。它允许连接到所有主机,因为我们将其设置为"*"”。为了使网关正常工作,我们需要定义一个VirtualService`资源。接下来我们来看看这个资源:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: blog-app
namespace: blog-app
spec:
hosts:
- "*"
gateways:
- blog-app-gateway
http:
- match:
- uri:
exact: /
- uri:
prefix: /static
- uri:
prefix: /posts
- uri:
exact: /login
- uri:
exact: /logout
- uri:
exact: /register
- uri:
exact: /updateprofile
route:
- destination:
host: frontend
port:
number: 80
VirtualService资源监听所有主机,并如指定的那样应用于blog-app-gateway。它允许/static和/posts作为前缀(prefix)匹配。这意味着所有以这些路径开头的 URI 请求都会被路由。/login、/logout、/register、/updateprofile和/路径具有精确(exact)匹配,这意味着只有完全匹配的 URI 才会被允许。这些请求将被路由到frontend服务的端口80。
我们还必须修改blog-app.yaml文件中的frontend服务,将服务类型更改为ClusterIP。这样会将附加的负载均衡器从服务中移除,所有请求将通过入口网关路由。
现在,让我们通过以下命令来应用这些更改:
$ cd ~/mdo-environments
$ cp ~/modern-devops/ch15/istio-ingressgateway/gateway.yaml \
manifests/blog-app/gateway.yaml
$ cp ~/modern-devops/ch15/istio-ingressgateway/blog-app.yaml \
manifests/blog-app/blog-app.yaml
$ git add --all
$ git commit -m "Added gateway"
$ git push
我们将等待 5 分钟以便同步生效,之后可以访问http://<Ingress LoadBalancerExternalIP> 来访问我们的博客应用。你应该能看到以下页面。这表明应用程序运行正常:
图 15.5 – 博客应用 – 主页
你可以通过注册、登录、创建帖子和写评论来玩一下这个应用程序。尝试更新帖子和评论,看看应用的各个方面是否正常工作。现在,让我们看看我们微服务的安全性方面。
使用 Istio 保护你的微服务
在生产环境中运行微服务具有众多优势,如独立的可扩展性、增强的敏捷性、减少的变更范围、频繁的部署和可重用性。然而,它们也带来了独特的挑战,尤其是在安全方面。
在单体架构中,安全性关注的是保护单一应用程序。然而,在典型的企业级微服务应用中,可能需要数百个微服务相互安全地互动。Kubernetes 是托管和编排微服务的绝佳平台。然而,微服务之间的默认通信是不安全的,因为它们通常使用明文 HTTP。这可能无法满足你的安全需求。为了将与传统企业单体应用相同的安全原则应用到微服务中,必须确保以下几点:
-
加密通信:微服务之间的所有交互必须加密,以防止潜在的中间人攻击。
-
访问控制:需要实施访问控制机制,以确保只有经过授权的微服务才能相互通信。
-
遥测和审计日志:捕获、记录和审计遥测数据对于了解流量行为并主动检测入侵至关重要。
Istio 简化了处理这些安全问题,并提供了开箱即用的核心安全功能。通过 Istio,你可以强制执行强大的身份和访问管理、相互TLS和加密、认证和授权以及全面的审计日志—所有这些都在统一的控制平面内。这意味着你可以为你的微服务建立强大的安全实践,在动态分布的环境中提升应用程序的安全性和可靠性。
在 Istio 的背景下,你应该了解它会自动将边车代理注入到你的 Pod 中,并修改 Kubernetes 集群的 IP 表,以确保所有连接都通过这些代理进行。这种设置旨在默认强制启用 TLS 加密,增强你的微服务安全性,而无需特定配置。这些 Envoy 代理之间的通信会通过 TLS 自动加密。
尽管默认配置提供了基础的安全性,并有效防止了中间人攻击,但建议通过应用特定策略来进一步增强微服务的安全性。在深入了解详细功能之前,理解 Istio 中安全性如何运作是有益的。
Istio 包含以下关键组件来执行安全策略:
-
证书颁发机构(CA):此组件管理密钥和证书,确保在服务网格中进行安全和认证的通信。
-
配置 API 服务器:配置 API 服务器将身份验证策略、授权策略和安全命名信息分发给 Envoy 代理。这些策略定义了服务如何进行身份验证和授权,并管理安全通信。
-
边车代理:边车代理作为微服务的配套部署,对于强制执行安全策略至关重要。它们充当策略执行点,实施提供给它们的策略。
-
Envoy 代理扩展:这些扩展使得可以收集遥测数据和审计信息,提供流量行为的洞察,并帮助识别和缓解安全问题。
在这些组件协同工作下,Istio 为你的微服务提供了一个强大的安全框架,可以通过定义和执行特定的安全策略进一步优化,以满足你应用的需求。
由于我们的应用程序目前运行在 HTTP 上,因此在博客应用中实现 TLS 并通过 HTTPS 暴露是一个很好的主意。让我们从创建一个安全的入口网关开始。
创建安全的入口网关
安全的入口网关就是启用了 TLS 的入口网关。要在入口网关上启用 TLS,我们必须为其提供私钥和证书链。我们将在这个练习中使用自签名证书链,但在生产环境中你必须使用正确的 CA 证书链。CA 证书是由可信的 CA(如 Verisign 或 Entrust)授予的数字证书,属于公钥基础设施(PKI)的一部分。它在保障数字交互和交易的安全性与可靠性方面发挥着关键作用。
让我们从创建根证书和私钥开始,通过以下命令签署应用程序的证书:
$ openssl req -x509 -sha256 -nodes -days 365 \
-newkey rsa:2048 -subj '/O=example Inc./CN=example.com' \
-keyout example.com.key -out example.com.crt
使用生成的根证书,我们现在可以使用以下命令生成 服务器证书 和密钥:
$ openssl req -out blogapp.example.com.csr \
-newkey rsa:2048 -nodes -keyout blogapp.example.com.key \
-subj "/CN=blogapp.example.com/O=blogapp organization"
$ openssl x509 -req -sha256 -days 365 \
-CA example.com.crt -CAkey example.com.key -set_serial 1 \
-in blogapp.example.com.csr -out blogapp.example.com.crt
下一步是在 istio-ingress 命名空间中生成一个 Kubernetes TLS 秘密供我们的入口网关读取。然而,考虑到我们不希望将 TLS 密钥和证书存储在 Git 仓库中,我们将改用 Google Secrets Manager。因此,我们将运行以下命令来实现:
$ echo -ne "{\"MONGO_INITDB_ROOT_USERNAME\": \"root\", \
\"MONGO_INITDB_ROOT_PASSWORD\": \"itsasecret\", \
\"blogapptlskey\": \"$(base64 blogapp.example.com.key -w 0)\", \
\"blogapptlscert\": \"$(base64 blogapp.example.com.crt -w 0)\"}" | \
gcloud secrets versions add external-secrets --data-file=-
Created version [2] of the secret [external-secrets].
现在,我们必须创建一个外部秘密清单,从 Secrets Manager 获取密钥和证书,并生成 TLS 秘密。以下清单将帮助我们实现这一目标:
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: blogapp-tls-credentials
namespace: istio-ingress
spec:
secretStoreRef:
kind: ClusterSecretStore
name: gcp-backend
target:
template:
type: kubernetes.io/tls
data:
tls.crt: "{{ .blogapptlscert | base64decode | toString }}"
tls.key: "{{ .blogapptlskey | base64decode | toString }}"
name: blogapp-tls-credentials
data:
- secretKey: blogapptlskey
remoteRef:
key: external-secrets
property: blogapptlskey
- secretKey: blogapptlscert
remoteRef:
key: external-secrets
property: blogapptlscert
现在,让我们在环境仓库中创建一个目录,并将外部秘密清单复制到其中。使用以下命令进行操作:
$ mkdir ~/mdo-environments/manifests/istio-ingress
$ cp ~/modern-devops/ch15/security/blogapp-tls-credentials.yaml \
~/mdo-environments/manifests/istio-ingress
接下来,我们需要修改入口网关资源以配置 TLS。为此,我们必须将 Gateway 资源修改为以下内容:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: blog-app-gateway
namespace: blog-app
spec:
selector:
istio: ingress
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: blogapp-tls-credentials
hosts:
- "*"
网关配置与之前类似,但我们使用 port 443 来替代 port 80,用于 HTTPS。我们还有一个 tls 部分,并设置为 SIMPLE 模式,这意味着它是一个标准的 TLS 连接。我们已经指定了 credentialName,指向我们使用 TLS 密钥和证书创建的秘密。由于所有设置已就绪,让我们使用以下命令提交并推送代码:
$ cp ~/modern-devops/ch15/security/gateway.yaml \
~/mdo-environments/manifests/blog-app/
$ cp ~/modern-devops/ch15/security/run-tests.yml \
~/mdo-environments/.github/workflows/
$ git add --all
$ git commit -m "Enabled frontend TLS"
$ git push
等待 blog-app 同步。一旦完成,我们可以通过 https: // 访问我们的应用程序。这样,进入应用程序的连接就已经加密。
尽管我们已经保护了进入服务网格的连接,但作为额外的安全层,保护所有内部服务之间的 TLS 通信也是非常重要的。接下来,我们将实施这一措施。
在服务网格中强制启用 TLS
如我们所知,默认情况下,Istio 为注入了 sidecar 代理的工作负载之间的通信提供 TLS 加密。然而,需要注意的是,这个默认设置处于兼容模式。在这种模式下,两个具有 sidecar 代理的服务之间的流量是加密的。但是,没有 sidecar 代理的工作负载仍然可以通过明文 HTTP 与后端微服务通信。这个设计选择是为了简化 Istio 的采用,因为新引入 Istio 的团队不需要立即解决将所有源流量启用 TLS 的问题。
让我们创建并进入 default 命名空间中的一个 Pod 的 shell。由于该命名空间没有自动的 sidecar 注入,后端流量将是明文的。然后,我们将在那里 curl frontend 微服务,看看是否能收到响应。运行以下命令进行操作:
$ kubectl run -it --rm --image=curlimages/curl curly -- curl -v http://frontend.blog-app
* Trying 10.71.246.145:80…
* Connected to frontend (10.71.246.145) port 80
> GET / HTTP/1.1
> Host: frontend
> User-Agent: curl/8.4.0
> Accept: */*
< HTTP/1.1 200 OK
< server: envoy
< date: Sat, 21 Oct 2023 07:19:18 GMT
< content-type: text/html; charset=utf-8
< content-length: 5950
< x-envoy-upstream-service-time: 32
<!doctype html>
<html la"g="en">
...
如我们所见,我们得到了 HTTP 200 响应。
这种方法在安全性和兼容性之间取得了平衡,允许逐步过渡到完全加密的通信模型。随着时间的推移,随着更多服务注入了边车代理,微服务应用程序的整体安全态势将得到改善。然而,由于我们是从头开始,强制执行严格的 TLS 来保护我们的 Blog 应用程序是有意义的。所以,让我们这样做。
要在工作负载、命名空间或整个集群启用严格的 TLS,Istio 提供了使用 PeerAuthentication 资源的对等身份验证策略。由于我们只需要在 Blog 应用程序上实现严格的 TLS,因此在命名空间级别启用它是有意义的。为此,我们将使用以下 PeerAuthentication 资源:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: blog-app
spec:
mtls:
mode: STRICT
现在,让我们使用以下命令应用此配置:
$ cp ~/modern-devops/ch15/security/strict-mtls.yaml \
~/mdo-environments/manifests/blog-app/
$ git add --all
$ git commit -m "Enable strict TLS"
$ git push
一旦我们推送更改,Argo CD 应该会接收新配置并应用严格的 TLS 策略。等待 Argo CD 同步处于干净状态,并运行以下命令检查严格的 TLS 是否工作:
$ kubectl run -it --rm --image=curlimages/curl curly -- curl -v http://frontend.blog-app
* Trying 10.71.246.145:80...
* Connected to frontend.blog-app (10.71.246.145) port 80
> GET / HTTP/1.1
> Host: frontend.blog-app
> User-Agent: curl/8.4.0
> Accept: */*
* Recv failure: Connection reset by peer
* Closing connection
curl: (56) Recv failure: Connection reset by peer
如我们所见,请求已被拒绝,因为它是明文请求,后台将只允许 TLS。这表明严格的 TLS 工作正常。现在,让我们继续并进一步增强服务的安全性。
从我们的设计中,我们知道服务如何相互交互:
-
frontend微服务只能连接到posts、reviews和users微服务。 -
只有
reviews微服务可以连接到ratings微服务。 -
只有
posts、reviews、users和ratings微服务可以连接到mongodb数据库
因此,我们可以定义这些交互,并仅显式允许这些连接。因此,frontend 微服务将无法直接连接到 mongodb 数据库,即使它尝试这样做。
Istio 提供了 AuthorizationPolicy 资源来管理这个问题。让我们使用它来实现上述场景。
让我们从 posts 微服务开始:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: posts
namespace: blog-app
spec:
selector:
matchLabels:
app: posts
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/blog-app/sa/frontend"]
AuthorizationPolicy 有多个部分。它从 name 和 namespace 开始,分别为 posts 和 blog-app。spec 部分包含 selector,在这里我们指定需要将此策略应用于所有具有 app: posts 标签的 pod。我们为此使用 ALLOW 动作。请注意,Istio 对所有匹配选择器的 pod 有一个隐式的 deny-all 策略,任何 ALLOW 规则都将在此基础上应用。任何不匹配 ALLOW 规则的流量将默认被拒绝。我们有规则来定义允许哪些流量;在这里,我们使用 from > source > principals,并将 frontend 服务帐户设置在此。因此,总结起来,这条规则将应用于 posts 微服务,并仅允许来自 frontend 微服务的流量。
同样,我们将对 reviews 微服务应用相同的策略,如下所示:
...
name: reviews
...
rules:
- from:
- source:
principals: ["cluster.local/ns/blog-app/sa/frontend"]
users 微服务也只需要接受来自 frontend 微服务的流量:
...
name: users
...
rules:
- from:
- source:
principals: ["cluster.local/ns/blog-app/sa/frontend"]
ratings 微服务应该仅接受来自 reviews 微服务的流量,因此我们将对主体部分进行一些小的修改,如下所示:
...
name: ratings
...
rules:
- from:
- source:
principals: ["cluster.local/ns/blog-app/sa/reviews"]
最后,mongodb 服务需要来自所有微服务的连接,除了 frontend,因此我们必须在主体部分指定多个条目:
...
name: mongodb
...
rules:
- from:
- source:
principals: ["cluster.local/ns/blog-app/sa/posts", "cluster.local/ns/blog-app/sa/
reviews", "cluster.local/ns/blog-app/sa/ratings", "cluster.local/ns/blog-app/sa/users"]
由于我们使用了服务帐户来了解请求的来源,因此我们还必须为相应的服务创建并分配服务帐户。所以,我们将修改 blog-app.yaml 文件,并为每个服务添加服务帐户,类似于以下内容:
apiVersion: v1
kind: ServiceAccount
metadata:
name: mongodb
namespace: blog-app
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
...
spec:
...
template:
...
spec:
serviceAccountName: mongodb
containers:
...
我已经在新的 blog-app.yaml 文件中复制了相同的设置。让我们提交更改并推送到 GitHub,以便我们可以将其应用到集群中:
$ cp ~/modern-devops/ch15/security/authorization-policies.yaml \
~/mdo-environments/manifests/blog-app/
$ cp ~/modern-devops/ch15/security/blog-app.yaml \
~/mdo-environments/manifests/blog-app/
$ git add --all
$ git commit -m "Added auth policies"
$ git push
现在,我们必须等待同步完成,然后验证设置。首先,我们将获取 frontend pod 的 shell,并尝试使用 wget 连接后端微服务。我们将尝试与每个微服务连接,看看结果。如果返回 HTTP 200 或 404,则表示后端允许连接;如果返回 HTTP 403 或 Error,则表示后端正在阻止连接。运行以下命令进行操作:
$ kubectl -n blog-app exec -it $(kubectl get pod -n blog-app | \
grep frontend | awk {'print $1'}) -- /bin/sh
/ # wget posts:5000
Connecting to posts:5000 (10.71.255.204:5000)
wget: server returned error: HTTP/1.1 404 Not Found
/ # wget reviews:5000
Connecting to reviews:5000 (10.71.244.177:5000)
wget: server returned error: HTTP/1.1 404 Not Found
/ # wget ratings:5000
Connecting to ratings:5000 (10.71.242.178:5000)
wget: server returned error: HTTP/1.1 403 Forbidden
/ # wget users:5000
Connecting to users:5000 (10.71.241.255:5000)
wget: server returned error: HTTP/1.1 404 Not Found
/ # wget mongodb:27017
Connecting to mongodb:27017 (10.68.0.18:27017)
wget: error getting response: Resource temporarily unavailable
/ # exit
command terminated with exit code 1
如我们所见,posts、reviews 和 users 微服务返回了 HTTP 404 响应。ratings 微服务返回了 403 Forbidden 响应,而 mongodb 服务报告资源不可用。这意味着我们的设置工作正常。
我们来试试 posts 微服务:
$ kubectl -n blog-app exec -it $(kubectl get pod -n blog-app | \
grep posts | awk {'print $1'}) -- /bin/sh
/ # wget mongodb:27017
Connecting to mongodb:27017 (10.68.0.18:27017)
saving to 'index.html'
index.html 100% |************| 85 0:00:00 ETA
'index.html' saved
/ # wget ratings:5000
Connecting to ratings:5000 (10.71.242.178:5000)
wget: server returned error: HTTP/1.1 403 Forbidden
/ # wget reviews:5000
Connecting to reviews:5000 (10.71.244.177:5000)
wget: server returned error: HTTP/1.1 403 Forbidden
/ # wget users:5000
Connecting to users:5000 (10.71.241.255:5000)
wget: server returned error: HTTP/1.1 403 Forbidden
/ # exit
command terminated with exit code 1
如我们所见,posts 微服务可以成功与 mongodb 通信,但其他微服务返回 403 Forbidden。这正是我们预期的结果。现在,我们来试试 reviews 微服务:
$ kubectl -n blog-app exec -it $(kubectl get pod -n blog-app | \
grep reviews | awk {'print $1'}) -- /bin/sh
/ # wget ratings:5000
Connecting to ratings:5000 (10.71.242.178:5000)
wget: server returned error: HTTP/1.1 404 Not Found
/ # wget mongodb:27017
Connecting to mongodb:27017 (10.68.0.18:27017)
saving to 'index.html'
index.html 100% |**********| 85 0:00:00 ETA
'index.html' saved
/ # wget users:5000
Connecting to users:5000 (10.71.241.255:5000)
wget: server returned error: HTTP/1.1 403 Forbidden
/ # exit
command terminated with exit code 1
如我们所见,reviews 微服务能够成功地与 ratings 微服务和 mongodb 连接,同时从其他微服务获得 403 响应。这正是我们预期的结果。现在,我们来检查 ratings 微服务:
$ kubectl -n blog-app exec -it $(kubectl get pod -n blog-app \
| grep ratings | awk {'print $1'}) -- /bin/sh
/ # wget mongodb:27017
Connecting to mongodb:27017 (10.68.0.18:27017)
saving to 'index.html'
index.html 100% |************| 85 0:00:00 ETA
'index.html' saved
/ # wget ratings:5000
Connecting to ratings:5000 (10.71.242.178:5000)
wget: server returned error: HTTP/1.1 403 Forbidden
/ # exit
command terminated with exit code 1
如我们所见,ratings 微服务只能成功连接到 mongodb 数据库,并且对其他服务返回 403 响应。
现在我们已经测试了所有服务,设置正常工作。我们的微服务已经得到了极大程度的安全保护!接下来,让我们看看使用 Istio 管理微服务的另一个方面——流量管理。
使用 Istio 管理流量
Istio 提供了强大的流量管理功能,构成了其核心功能的一部分。当您在 Kubernetes 环境中使用 Istio 进行微服务管理时,您可以精确控制这些服务之间的通信方式。这使您能够在服务网格中精确地定义流量路径。
以下是您可以使用的一些流量管理功能:
-
请求路由
-
故障注入
-
流量切换
-
TCP 流量切换
-
请求超时
-
电路断路
-
镜像
前一节使用了入口网关来启用流量进入我们的服务网格,并使用虚拟服务将流量分配到各个服务。在虚拟服务的情况下,流量分配默认是轮询方式。然而,我们可以通过目的地规则来更改这一点。这些规则为我们提供了对服务网格行为的精细控制,允许我们在 Istio 生态系统中更细粒度地管理流量。
在深入讨论之前,我们需要更新我们的博客应用程序,使其包含一个作为 ratings-v2 部署的新版本的 ratings 服务,该服务将返回黑色星星而不是橙色星星。我已经在代码库中更新了该清单。因此,我们只需要将其复制到 mdo-environments 仓库,提交并通过以下命令推送到远程:
$ cd ~/mdo-environments/manifests/blog-app/
$ cp ~/modern-devops/ch15/traffic-management/blog-app.yaml .
$ git add --all
$ git commit -m "Added ratings-v2 service"
$ git push
等待应用程序同步。完成后,我们需要做以下几件事:
-
转到博客应用主页 > 登录 > 还不是用户?创建帐户,然后创建一个新帐户。
-
点击 操作 标签 > 添加帖子,添加一个新帖子,选择标题和内容,然后点击提交。
-
使用添加评论文本框添加评论,提供评分,并点击提交。
-
再次点击帖子并访问我们创建的帖子。
现在,继续刷新页面。我们会看到,流量一半时间会得到橙色星星,另一半时间则是黑色星星。流量在 v1 和 v2 之间平分(即橙色和黑色星星):
图 15.6 – 循环路由
之所以发生这种情况,是因为缺少目的地规则,导致 Istio 无法区分 v1 和 v2。让我们为我们的微服务定义目的地规则,以此纠正问题,清楚地告诉 Istio 这些版本的区别。在我们的例子中,每个微服务有一个版本,除了 ratings 微服务,因此我们将相应地定义以下目的地规则。
让我们从定义 frontend 微服务的目的地规则开始:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: frontend
namespace: blog-app
spec:
host: frontend
subsets:
- name: v1
labels:
version: v1
提供的 YAML 清单在 blog-app 命名空间内引入了一个名为 frontend 的 DestinationRule 资源。这个资源与名为 frontend 的主机相关联。随后,我们定义了标记为 v1 的子集,目标是带有 version: v1 标签的 pods。因此,配置我们的虚拟服务将流量定向到 v1 目的地时,将请求路由到带有 version: v1 标签的 pods。
同样的配置方式可以复制到 posts、users 和 reviews 微服务。但是,由于部署了两个版本,ratings 微服务需要稍微不同的配置,如下所示:
...
spec:
host: ratings
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
ratings 微服务的 YAML 清单与其他微服务非常相似,唯一显著的区别是:它包含一个标记为 v2 的第二子集,对应着带有 version: v2 标签的 pods。
因此,路由到 v1 目标的请求会定向到所有具有 version: v1 标签的 Pod,而路由到 v2 目标的请求则会定向到标记为 version: v2 的 Pod。
为了在实际应用中说明这一点,我们将继续为每个微服务定义虚拟服务。我们的起点是定义 frontend 微服务的虚拟服务,如以下清单所示:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: frontend
namespace: blog-app
spec:
hosts:
- frontend
http:
- route:
- destination:
host: frontend
subset: v1
提供的 YAML 清单描述了一个名为 frontend 的 VirtualService 资源,位于 blog-app 命名空间内。该资源将主机 frontend 配置为具有 HTTP 路由目标,所有流量都被导向 frontend 主机,并指定了 v1 子集。因此,所有面向 frontend 主机的请求都会路由到我们之前定义的 v1 目标。
我们将为 posts、reviews 和 users 微服务复制这种配置方式,创建相应的 VirtualService 资源。对于 ratings 微服务,决定将所有流量路由到 v1(橙色星标)版本。因此,我们也为 ratings 微服务应用类似的 VirtualService 资源。
现在,让我们将清单复制到 mdo-environments 仓库,并使用以下命令提交并推送代码到远程仓库:
$ cd ~/mdo-environments/manifests/blog-app
$ cp ~/modern-devops/ch15/traffic-management/destination-rules.yaml .
$ cp ~/modern-devops/ch15/traffic-management/virtual-services-v1.yaml .
$ git add --all
$ git commit -m "Route to v1"
$ git push
等待 Argo CD 同步更改。现在,所有请求都将路由到 v1。因此,您将只看到评论中的橙色星标,如下图所示:
图 15.7 – 路由到 v1
现在,让我们尝试通过金丝雀发布方法发布 v2。
流量切换和金丝雀发布
假设你已经开发了一个新的微服务版本,并迫切希望将其推向用户群。然而,你显然会对其对整个服务的潜在影响感到谨慎。在这种情况下,你可以选择一种名为金丝雀发布(也称为蓝绿部署)的部署策略。
金丝雀发布的本质在于其渐进式方法。与其进行突兀的版本过渡,不如通过有条不紊地将流量从先前版本(称为蓝色)转移到新版本(绿色)。这种渐进的迁移方式允许你在将其应用到整个用户群体之前,先在有限的用户子集上彻底测试新版本的功能性和可靠性。这种方法能够最大程度地减少发布新特性或更新时的风险,并确保发布过程更加可控和安全。下图形象地展示了这一过程:
图 15.8 – 金丝雀发布
以下是金丝雀发布策略的工作原理:
-
初始发布:现有版本(称为基准或当前版本)继续服务大多数用户。
-
早期访问:一个小的用户或系统组,通常选定为具有代表性的样本,被认定为金丝雀组。他们将接收到新版本。
-
监控与评估:金丝雀组中软件的性能和行为会被密切监控。通过收集度量、日志和用户反馈,及时发现问题或异常。
-
逐步扩展:如果新版本在金丝雀组中表现稳定且符合预期,其曝光度将逐步扩展到更广泛的用户群体。此扩展可以分阶段进行,每个阶段将“晋升”一定百分比的用户使用新版本。
-
持续监控:在发布过程中,持续的监控和分析至关重要,以便及时识别并解决任何新出现的问题。如果发现问题,可以暂停或回滚发布,以保护大多数用户。
-
完全部署:一旦新版本通过金丝雀发布阶段成功验证,它将最终对所有用户开放。
所以,让我们将ratings-v2服务推送到20%的用户中。为此,我们将使用以下VirtualService资源:
...
http:
- route:
- destination:
host: ratings
subset: v1
weight: 80
- destination:
host: ratings
subset: v2
weight: 20
正如我们所看到的,我们已修改了ratings虚拟服务,引入了指向v2子集的第二个目标。在此配置中一个值得注意的新增项是引入了weight属性。对于v1目标,我们分配了80的权重,而v2目标的权重为20。这意味着20%的流量将被引导到ratings微服务的v2版本,从而提供了两版本之间的流量控制和可调分配。
让我们复制清单,然后使用以下命令将更改提交并推送到远程仓库:
$ cd ~/mdo-environments/manifests/blog-app
$ cp ~/modern-devops/ch15/traffic-management/virtual-services-canary.yaml \
virtual-services.yaml
$ git add --all
$ git commit -m "Canary rollout"
$ git push
完成 Argo CD 同步后,如果你刷新页面 10 次,你会观察到在其中的 10 次中,黑色星星会出现两次。这是金丝雀发布实践的一个典型例子。你可以继续监控应用程序,并逐步调整权重,将流量转向v2。金丝雀发布有效地降低了生产发布过程中的风险,提供了一种应对未知风险的方法,尤其是在实施重大变更时。
然而,还有一种方法可以在生产环境中测试你的代码,这种方法涉及使用实时流量,但不会将应用程序暴露给最终用户。这种方法称为流量镜像。我们将在接下来的讨论中深入探讨它。
流量镜像
流量镜像,也称为影像,是一种最近受到关注的概念。它是一种强大的方法,允许你在生产环境中评估发布,而不对最终用户造成任何风险。
传统上,许多企业维持了一个与生产环境高度相似的暂存环境。在这种设置中,运维团队将新版本发布到暂存环境,而测试人员生成合成流量来模拟真实世界的使用。这种方法为团队提供了一种评估代码在生产环境中表现的方式,评估其功能性和非功能性方面,在推广到生产环境之前。暂存环境充当性能、体量和操作验收测试的基础。虽然这种方法有其优点,但也并非没有挑战。维持静态测试环境是其中之一,这需要大量的成本和资源。创建和维护一个生产环境的副本需要一支工程师团队,从而导致高额的开销。
此外,合成流量通常偏离真实的实时流量,因为前者依赖于历史数据,而后者反映的是当前用户的交互。这种差异偶尔会导致某些场景被忽视。
另一方面,流量镜像提供了一种解决方案,它同样使得操作验收测试成为可能,同时更进一步。它允许你使用实时流量进行测试,而不会对终端用户产生任何影响。
下面是流量镜像的工作原理:
-
部署应用程序的新版本并激活流量镜像。
-
旧版本继续照常响应请求,同时将流量的异步副本发送到新版本。
-
新版本处理镜像流量,但不会对终端用户做出响应。
-
运维团队监控新版本的行为,并将任何问题报告给开发团队。
该过程如以下图所示:
图 15.9 – 流量镜像
流量镜像通过使团队能够发现传统暂存环境中可能隐藏的问题,彻底改变了测试过程。此外,你可以利用诸如 Prometheus 和 Grafana 等监控工具来记录和监控测试结果,提升发布的整体质量和可靠性。
现在,废话不多说,让我们为我们的ratings服务配置流量镜像。流量镜像通过VirtualService资源进行管理,因此让我们将ratings虚拟服务修改为以下内容:
...
http:
- route:
- destination:
host: ratings
subset: v1
weight: 100
mirror:
host: ratings
subset: v2
mirror_percent: 100
在此配置中,我们设置了一个单一的destination,目标为v1,weight值为100。此外,我们定义了一个mirror部分,将流量引导到ratings:v2,并设置mirror_percent为 100。这意味着,所有最初路由到ratings:v1的流量都被镜像并同时发送到v2。
让我们使用以下命令提交更改并将其推送到远程仓库:
$ cp ~/modern-devops/ch15/traffic-management/virtual-services-mirroring.yaml \
virtual-services.yaml
$ git add --all
$ git commit -m "Mirror traffic"
$ git push
在完成 Argo CD 同步过程后,我们将刷新页面五次。随后,我们可以使用以下命令检查ratings:v1服务的日志:
$ kubectl logs $(kubectl get pod -n blog-app | \
grep "ratings-" | awk '{print $1}') -n blog-app
127.0.0.6 - - [22/Oct/2023 08:33:19] "GET /review/6534cba72485f5a51cbdcef0/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:19] "GET /review/6534cbb32485f5a51cbdcef1/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:23] "GET /review/6534cba72485f5a51cbdcef0/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:23] "GET /review/6534cbb32485f5a51cbdcef1/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:25] "GET /review/6534cba72485f5a51cbdcef0/rating
HTTP/1.1" 200 -
在启用流量镜像后,预计在ratings:v1服务中观察到的相同日志集也会被镜像到ratings:v2服务中。为了确认这一点,我们可以使用以下命令列出ratings:v2服务的日志:
$ kubectl logs $(kubectl get pod -n blog-app | \
grep "ratings-v2" | awk '{print $1}') -n blog-app
127.0.0.6 - - [22/Oct/2023 08:33:19] "GET /review/6534cba72485f5a51cbdcef0/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:19] "GET /review/6534cbb32485f5a51cbdcef1/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:23] "GET /review/6534cba72485f5a51cbdcef0/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:23] "GET /review/6534cbb32485f5a51cbdcef1/rating
HTTP/1.1" 200 -
127.0.0.6 - - [22/Oct/2023 08:33:25] "GET /review/6534cba72485f5a51cbdcef0/rating
HTTP/1.1" 200 -
确实,日志和时间戳完全匹配,提供了ratings:v1和ratings:v2中并发日志条目的清晰证据。这一观察有效地展示了镜像功能的运作,展示了如何在两个版本中复制流量以进行实时监控和分析。
流量镜像是一种非常有效的方法,用于识别传统基础设施设置中常常难以发现的问题。它是一种强大的方法,用于对软件版本进行操作验收测试。这种做法简化了测试并防范了潜在的客户事件和操作挑战。
Istio 提供了其他流量管理方面的功能,但本章无法涵盖所有内容。请随时访问 Istio 文档,探索它的其他功能:istio.io/latest/docs/tasks/traffic-management/。
如我们所知,Istio 利用 envoy 代理作为边车组件,配合你的微服务容器。鉴于这些代理在流量的引导和管理中发挥着核心作用,它们还会收集宝贵的遥测数据。
这些遥测数据随后被传输到 Prometheus,一个监控和告警工具,在那里它可以被存储并有效地可视化。Grafana 等工具通常与 Prometheus 一起使用,为这些遥测数据提供深刻且易于访问的可视化,帮助你有效地监控和管理服务网格。因此,我们将在下一节中进一步探讨 Istio 的可观察性部分。
使用 Istio 观察流量并发出警报
Istio 提供了多种工具,通过 Istio 附加组件来可视化流经我们网格的流量。Prometheus是遥测数据收集、存储和查询的核心层,Grafana和Kiali为我们提供了互动的图形工具,帮助我们与这些数据进行交互。
让我们通过以下命令安装可观察性附加组件来开始这一节:
$ cd ~
$ mkdir ~/mdo-environments/manifests/istio-system
$ cd ~/mdo-environments/manifests/istio-system/
$ cp ~/modern-devops/ch15/observability/*.yaml .
$ git add --all
$ git commit -m "Added observability"
$ git push
一旦我们推送代码,Argo CD 应该会创建一个新的istio-system命名空间并安装附加组件。安装完成后,我们可以从访问 Kiali 仪表盘开始。
访问 Kiali 仪表盘
Kiali 是一个强大的可观察性和可视化工具,专门用于微服务和服务网格管理。它提供了有关你的服务网格行为的实时洞察,帮助你高效地监控和故障排除问题。
由于 Kiali 服务部署在集群 IP 上,因此不对外暴露,我们需要做一个端口转发,以便使用以下命令访问 Kiali 仪表板:
$ kubectl port-forward deploy/kiali -n istio-system 20001:20001
一旦端口转发会话开始,点击 Google Cloud Shell 的网页预览图标,选择更改端口为 20001,然后点击预览。你将看到以下仪表板。该仪表板为我们提供了有关在服务网格中运行的应用程序的宝贵见解:
图 15.10 – Kiali 仪表板
为了可视化服务之间的交互,我们可以通过点击blog-app命名空间切换到图形视图。我们将看到以下仪表板,该仪表板提供了流量流动情况、成功流量百分比以及其他指标的准确视图:
图 15.11 – Kiali 服务交互图
虽然 Kiali 仪表板提供了有关我们的服务网格的宝贵见解,并帮助我们实时观察服务交互,但它缺乏提供高级监控和告警功能的能力。为此,我们可以使用 Grafana。
使用 Grafana 进行监控和告警
Grafana 是一个领先的开源平台,用于可观察性和监控,提供动态仪表板和强大的告警功能。它使用户能够可视化来自不同来源的数据,同时设置告警以主动检测问题。
由于我们已经安装了带有必要插件的 Grafana,让我们通过打开一个端口转发会话来访问它。确保终止现有的 Kiali 端口转发会话,或者使用不同的端口。运行以下命令来实现:
$ kubectl port-forward deploy/grafana -n istio-system 20001:3000
一旦端口转发会话开始,就像我们对 Kiali 所做的那样访问 Grafana 页面,然后进入首页 > 仪表板 > Istio > Istio 服务仪表板。我们应该能看到类似于以下的仪表板:
图 15.12 – Istio 服务仪表板
该仪表板提供了有关我们可能想要监控的一些标准 SLI(服务级指标)的丰富可视化,例如请求的成功率、持续时间、大小、流量和延迟。它帮助你细致地观察你的服务网格,并且你还可以通过使用Prometheus 查询语言(PromQL)构建额外的可视化,PromQL 学习和应用起来都很简单。
然而,监控和可视化必须与告警相结合,才能确保完整的可靠性。所以,让我们深入探讨一下。
使用 Grafana 进行告警
为了启动告警过程,建立清晰的标准至关重要。鉴于目前的流量较低,模拟准确的 SLO 违约可能具有挑战性。为简化起见,我们的告警标准将在流量超过每秒一个事务时触发。
该过程的初始阶段涉及编写查询以获取必要的度量指标。我们将使用以下查询来实现这一目标:
round(sum(irate(istio_requests_total{connection_security_policy="mutual_tls",destination_
service=~"frontend.blog-app.svc.cluster.local",reporter=~"destination",source_
workload=~"istio-ingress",source_workload_namespace=~"istio-ingress"}[5m])) by (source_
workload, source_workload_namespace, response_code), 0.001)
提供的查询确定了通过 Istio 入口网关传递到frontend微服务的所有事务的流量速率。
下一步是创建警报规则并应用查询。为此,导航至首页 > 告警 > 告警规则。然后,填写表单,如下方截图所示:
图 15.13 – 定义警报规则
警报规则配置为在 1 分钟的间隔内监控违反情况,持续 2 分钟。一旦设置了警报规则,触发警报就非常简单,只需每 1 到 2 分钟快速刷新博客应用首页 15 到 20 次即可。这一操作应该会激活警报。要观察此过程,导航至首页 > 告警 > 告警规则。你会发现警报在第一分钟内处于待处理状态。这意味着它已经在其中一次检查中检测到违反情况,并将在 2 分钟内等待另一次违反情况,之后触发警报。
在生产环境中,通常会设置较长的检查间隔,通常为 5 分钟,并设置 15 分钟的告警间隔。这种做法有助于避免对自我解决的短暂问题发出过多警报,从而确保 SRE 团队不会被虚假警报淹没。目标是保持平衡,避免团队将每个警报都视为潜在的虚假警报。以下截图显示了一个待处理的警报:
图 15.14 – 警报待处理
在 2 分钟的监控期结束后,你应该观察到警报被触发,如下方截图所示。这表示警报规则已经成功识别出持续违反定义标准的情况,并正在积极通知相关方或系统:
图 15.15 – 警报触发
由于在此上下文中未配置特定的警报通道,触发的警报将仅在 Grafana 仪表盘中可见。强烈建议设置专门的警报目标,将警报发送到指定的通道,使用如PagerDuty之类的工具通知值班工程师,或通过Slack通知你的值班团队。合适的警报通道确保相关个人或团队能及时收到关键问题通知,从而快速响应并解决问题。
总结
随着我们结束本章并总结本书,我们的旅程带领我们走过了各种各样的概念和功能。虽然本章我们已覆盖了大量内容,但必须认识到 Istio 是一项丰富而多面的技术,很难在一章内涵盖其所有复杂性。
本章标志着我们进入了服务网格的世界,阐明了它在微服务环境中的独特优势。我们的探索涉及 Istio 的各个方面,从安装 Istio 开始,到通过自动 sidecar 注入扩展我们的示例博客应用。随后,我们进入了安全部分,深入探讨了如何使用 mTLS 加强入口网关的安全性,强制微服务间使用严格的 mTLS,并利用授权策略来管理流量。
接着我们的旅程带领我们进入流量管理,我们介绍了诸如目标规则和虚拟服务等基本概念。这些概念使我们能够执行金丝雀发布和流量镜像,展示了受控部署和实时流量分析的强大功能。我们的探索最终进入了可观察性,我们利用 Kiali 仪表盘可视化服务交互,并深入探讨了使用 Grafana 进行高级监控和告警功能。
随着我们结束这段精彩的旅程,我想对你表达衷心的感谢,感谢你选择了这本书并陪伴我走过它的每一页。我相信你已经从书中的每个部分获得了愉悦和启发。我希望这本书能为你提供必要的技能,让你在不断发展的现代 DevOps 领域中脱颖而出。祝愿你在当前和未来的所有努力中取得最大的成功。
问题
回答以下问题以测试你对本章的理解:
-
在使用 GitOps 方法的可选选项中,你会选择哪种方式来安装 Istio?
A. Istioctl
B. Helm charts
C. Kustomize
D. Manifest bundle
-
要让 Istio 自动将 sidecar 注入到你的工作负载中,必须进行哪些配置?
A. 将
istio-injection-enabled: true标签应用到命名空间B. 不需要配置 — Istio 会自动将 sidecar 注入所有 pod
C. 修改清单以包含 Istio sidecar 并重新部署
-
Istio sidecar 会自动使用 mTLS 进行相互通信。(正确/错误)
-
哪种资源强制执行指定哪些服务可以相互通信的策略?
A.
AuthenticationPolicyB.
AuthorizationPolicyC.
PeerAuthentication -
以下哪项资源适用于金丝雀发布?(选择两项)
A.
VirtualServiceB.
IngressGatewayC.
DestinationRuleD.
Egress Gateway -
为什么在生产环境中使用流量镜像?(选择三项)
A. 实时监控生产环境性能和行为分析
B. 将流量路由到新版本并复制流量以测试后端服务的性能
C. 在不冒生产中断风险的情况下安全地测试更改或更新
D. 简化故障排除和调试,以便识别和解决问题
-
你会使用哪种可观察性工具来可视化实时服务交互?
A. Prometheus
B. Grafana
C. Kiali
D. Loki
答案
以下是本章问题的答案:
-
B
-
A
-
正确
-
B
-
A 和 C
-
A、C 和 D
-
C
附录:AI 在 DevOps 中的角色
随着 ChatGPT 推出的生成性 AI,人工智能(AI)的最新发展震动了科技行业。许多现有的 AI 参与者纷纷转型,且大多数公司现在都在寻找最佳方式将其应用到产品中。自然,DevOps 及其相关工具也不例外,AI 正在慢慢地在这一领域站稳脚跟,而这个领域历史上更多依赖传统的自动化方法。在我们深入探讨 AI 如何改变 DevOps 之前,让我们先了解什么是 AI。
本附录将涵盖以下主题:
-
什么是 AI?
-
AI 在 DevOps 无限循环中的角色
什么是 AI?
AI 模拟计算中的人类智能。你知道我们的计算机能做出惊人的事情,但它们需要告诉每一步该做什么,对吧?而 AI 不需要那样,它通过大量的信息学习,就像我们从经验中学习一样。这样,它就能独立发现模式,并做出决策,而不需要每次都有人告诉它该做什么。这使得 AI 变得智能,因为它能够不断学习新事物,并在其执行的任务中不断变得更好。
想象一下,如果你的计算机能够从它所看到的每一件事中学习,就像你从周围的一切中记住一样。这就是 AI 的工作原理——它是计算机变得更智能的一种方式。AI 不再需要逐步指令,它可以从大量信息中学习。这使得它非常擅长在数据中发现模式并做出决策。而在 DevOps 中,AI 可以提供极大的帮助!我们接下来来看一下。
AI 在 DevOps 无限循环中的角色
正如我们已经知道的那样,DevOps 实践通常遵循一个无限循环,而不是传统的线性软件交付路径,如下图所示:
图 A.1 – DevOps 无限循环
DevOps 实践极大地强调自动化,以确保这个无限循环顺利运行,我们需要工具。大多数这些工具帮助构建、部署和运营软件。通常,你会在集成开发环境(IDE)中开始编写代码,然后将代码提交到如 Git 这样的中央源代码仓库。会有一个持续集成流水线,它从你的 Git 仓库构建代码并将其推送到制品仓库。你的 QA 团队可能会编写自动化测试,以确保在使用持续部署流水线将制品部署到更高环境之前进行测试。
在 AI 出现之前,设置所有工具链并操作它们依赖于传统的编码方法;也就是说,你仍然需要编写代码来自动化过程,而自动化会更可预测地按照指令执行。然而,随着 AI 的出现,事情正在发生变化。
AI 正在通过自动化任务、预测故障和优化性能来改变 DevOps。换句话说,通过利用 AI 的能力,DevOps 团队可以实现更高的效率,减少错误,并更快、更可靠地交付软件。
下面是 AI 在 DevOps 中的一些关键角色:
-
自动化重复任务:AI 可以自动化重复且乏味的任务,如代码测试、部署和基础设施供应。这让 DevOps 工程师能够专注于更具战略性和创造性的工作,如开发新特性和改进应用性能。
-
预测和防止故障:AI 可以分析大量数据,包括日志、性能指标和用户反馈,以识别模式并预测潜在故障。这种主动的方式使 DevOps 团队能够在问题影响用户或导致重大中断之前就进行处理。
-
优化资源利用率:AI 可以分析资源使用数据,优化基础设施分配并防止资源瓶颈。这确保了应用程序能够获得所需的资源以实现最佳性能,从而减少停机时间并提高整体系统效率。
-
增强安全性:AI 可以通过分析网络流量、识别异常行为并标记可疑活动来检测和防止安全威胁。这帮助 DevOps 团队保持强大的安全态势并保护敏感数据。
-
改善协作与沟通:AI 可以通过提供实时见解、自动化工作流和启用无缝沟通渠道来促进 DevOps 团队之间的协作与沟通。这打破了信息孤岛,促进了更具凝聚力的 DevOps 文化。
让我们看看 DevOps 无限循环的各个领域,看看 AI 如何影响它们。
代码开发
在这个领域,我们看到了生成式 AI 和其他 AI 技术的最大影响。AI 通过自动化任务如代码生成、错误检测、优化和测试,彻底改变了代码开发。通过自动完成功能、错误检测算法和预测分析,AI 加速了编码过程,提升了代码质量,并确保了更好的性能,同时帮助文档编写和代码安全分析。它的角色贯穿于从辅助编写代码到预测问题,最终简化软件开发生命周期,赋能开发者创造更高效、更可靠和更安全的应用程序。
许多工具在代码开发中应用了 AI,其中最受欢迎的工具之一是GitHub Copilot。
GitHub Copilot 是 GitHub 和 OpenAI 的合作成果,推出了一项代码补全功能,利用 OpenAI 的 Codex。Codex 在 GitHub 上大量的代码库中进行训练,能够根据当前文件的内容和光标位置快速生成代码。它兼容流行的代码编辑器,如 Visual Studio Code、Visual Studio、Neovim 和 JetBrains IDEs,并支持 Python、JavaScript、TypeScript、Ruby 和 Go 等语言。
受到 GitHub 和用户们的共同赞誉,Copilot 能生成完整的代码行、函数、测试和文档。其功能依赖于所提供的上下文以及 GitHub 上开发者的广泛代码贡献,无论其软件许可如何。被微软称为世界上首个 AI 配对程序员,它是一个付费工具,提供 60 天试用期后,每位用户每月收取 10 美元或每年 100 美元的订阅费。
使用 Copilot,你可以通过先写下你打算做的事情的注释来开始,它会为你生成所需的代码。这大大加快了开发速度,大多数时候,你只需要审查和测试你的代码,看看它是否按预期运行。的确是一股强大的力量!它能够优化现有代码并通过生成代码片段提供反馈。它还可以扫描你的代码,查找安全漏洞,并建议替代方案。
如果你不想支付那 10 美元,你也可以选择一些免费的替代工具,如 Tabnine、Captain Stack、GPT-Code Clippy、Second Mate 和 Intellicode。付费替代工具包括亚马逊的 Code Whisperer 和谷歌的 基于 ML 的 代码补全。
AI 工具不仅有助于增强开发工作流,还能帮助软件测试和质量保证。接下来我们来看看这一点。
软件测试与质量保证
传统上,软件测试更倾向于手动方式,因为大多数开发者并不希望将软件测试作为全职职业。尽管自动化测试近年来逐渐普及,但知识差距仍然制约着大多数组织的这一进程。因此,AI 将对测试功能产生重大影响,因为它弥补了人类与机器之间的差距。
集成 AI 的测试技术正在革新 软件测试生命周期(STLC)的每个阶段,其中一些如下:
-
测试脚本生成:传统上,创建测试脚本是一个耗时的过程,需要对系统有深入的理解。现在,AI 和 机器学习(ML)通过分析需求、现有测试用例和应用行为,快速生成更优化的测试脚本,提供可直接使用的模板,内含预配置的代码片段和详尽的注释,并使用 自然语言处理(NLP)技术将简单语言指令转化为完整的测试脚本。
-
测试数据生成:AI 装备的测试工具提供详细且丰富的测试数据,以实现全面覆盖。它们通过从现有数据集中生成合成数据来针对特定测试目标,转换数据以创建多样的测试场景,优化现有数据以提高精度和相关性,以及扫描大规模代码库以理解上下文。
-
智能测试执行:AI 通过自动分类和组织测试用例,针对不同设备、操作系统和配置智能地选择测试,并巧妙地执行关键功能的回归测试,从而缓解了测试执行的挑战。
-
智能测试维护:AI/机器学习通过实施自我修复机制处理选择器故障,并分析 UI 和代码变更关系,以识别受影响的区域,从而最大限度减少测试维护的挑战。
-
根本原因分析:AI 通过分析日志、性能指标和异常来帮助理解和修复问题,准确找出影响区域,追溯问题到受影响的用户故事和功能需求,并利用知识库进行全面的根本原因分析。
市场上有多个工具可以帮助你实现这一切,其中一些最流行的工具如下:
-
Katalon 平台:一款全面的质量管理工具,简化了跨多个应用和环境的测试创建、执行和维护。它拥有 AI 功能,如TrueTest、StudioAssist、自我修复、视觉测试和AI 驱动的测试失败分析。
-
TestCraft:基于Selenium构建,TestCraft 提供了手动和自动化测试功能,具有用户友好的界面和 AI 驱动的元素识别,支持多浏览器并行运行测试。
-
Applitools:以其基于 AI 的视觉测试而闻名,Applitools 高效地识别视觉缺陷,监控应用程序的视觉方面,并使用 AI 和机器学习提供准确的视觉测试分析。
-
功能:利用 AI/机器学习进行功能、性能和负载测试,简单易用,支持通过简单的英语输入创建测试、自动修复、测试分析和多浏览器支持。
-
Mabl:一款 AI 驱动的工具,提供低代码测试、直观智能、数据驱动功能、端到端测试以及有价值的洞察生成,促进团队协作。
-
AccelQ:自动化 UI、移动端、API 和 PC 软件的测试设计、计划和执行,具备自动化测试生成、预测分析和全面的测试管理功能。
-
Testim:通过机器学习加速测试创建和维护,允许快速的端到端测试创建、智能定位器用于可靠测试,并结合录制功能和编码来创建强大的测试。
正如我们已经看到的,AI 在开发和测试中的好处,我们接下来看看软件交付。
持续集成和交付
在持续集成(CI)和持续交付(CD)中,人工智能通过优化和自动化软件开发流程的各个阶段,带来了变革性的优势。人工智能增强了持续集成,通过自动化代码分析、识别模式和预测潜在的集成问题。它通过分析代码变化、建议适当的测试用例并促进更快的集成周期,从而简化了流程。通过机器学习(ML),人工智能可以理解过去构建的历史数据,识别导致失败的模式,从而帮助更高效地调试和改善代码质量。
在持续交付(CD)中,人工智能通过自动化发布策略、预测性能瓶颈并建议优化方案,以实现更顺畅的交付。它分析部署模式、用户反馈和系统性能数据,推荐最有效的交付路线。此外,人工智能驱动的 CD 工具提高了风险预测能力,帮助团队预见潜在的部署失败,并在生产环境受到影响之前做出明智决策以减轻风险。最终,人工智能在持续集成/持续交付(CI/CD)中的作用加速了开发周期,提升了软件质量,并增强了软件发布的可靠性。
以下是一些用于软件发布和交付的人工智能驱动工具:
-
Harness:Harness 利用人工智能自动化软件交付流程,包括持续集成、部署和验证。它通过机器学习分析部署管道中的模式,预测潜在问题,并优化发布策略,以提高效率和可靠性。
-
GitClear:GitClear 采用人工智能算法分析代码库,提供关于开发者生产力、代码贡献和团队表现的洞察。它帮助了解代码库变化、识别瓶颈并优化开发工作流。
-
Jenkins:得益于其插件架构,广泛使用的自动化服务器 Jenkins 采用了大量人工智能插件和扩展,增强了其在 CI/CD 中的能力。人工智能驱动的插件通过分析历史数据,帮助自动化任务、优化构建时间并预测构建失败。
-
CircleCI:CircleCI 整合了人工智能和机器学习,以优化 CI/CD 工作流。它分析构建日志,识别导致失败的模式,并提供改善构建性能和可靠性的建议。
这些人工智能驱动的工具通过自动化任务、优化工作流、预测问题并提供有价值的洞察,改善了软件发布和交付过程的速度、质量和可靠性,从而帮助做出更好的决策。
现在,让我们看一下流程中的下一阶段——软件运维。
软件运维
AI 在现代软件运维中至关重要,彻底改变了系统监控、管理和优化的方式。通过利用机器学习算法,AI 帮助自动化日常任务,如监控系统性能、分析日志和实时识别异常。它通过检测系统故障前的模式来实现预测性维护,从而进行主动干预,防止潜在的停机。此外,AI 驱动的工具通过关联警报、优先处理关键问题和提供可操作的见解来简化事件管理,增强了软件运维的整体弹性和可靠性。
此外,AI 通过分析大量数据来增强决策过程,识别趋势、预测资源需求,并优化基础设施利用率。AI 通过其持续学习的能力适应变化的环境,使软件运维团队能够应对不断变化的挑战和复杂性。总体而言,AI 在软件运维中的作用确保了更高的效率、提升的系统性能和主动的问题解决,从而显著地促进了 IT 基础设施的无缝运行。
以下是一些在软件运维中使用的 AI 驱动工具:
-
Dynatrace:Dynatrace 利用 AI 进行应用性能监控和管理。它使用 AI 算法分析大量数据,提供应用性能的实时洞察,识别瓶颈,并在问题影响最终用户之前预测潜在问题。
-
PagerDuty:PagerDuty 集成了 AI 驱动的事件管理、警报和值班调度。它使用机器学习关联事件和警报,减少噪音并为关键事件提供智能通知。
-
Opsani:Opsani 利用 AI 实现云应用的自主优化。它分析应用性能,动态调整配置,并优化资源以最大化性能和成本效益。
-
Moogsoft:Moogsoft 提供 AI 驱动的 IT 运维和 AIOps 平台。它使用机器学习检测异常、关联事件并自动化事件解决,帮助团队主动管理和解决复杂 IT 环境中的问题。
-
Sumo Logic:Sumo Logic 使用 AI 进行日志管理、监控和分析。它利用机器学习识别日志和操作数据中的模式、异常和安全威胁,从而实现主动故障排除和安全事件检测。
-
New Relic:New Relic 利用 AI 进行应用和基础设施监控。其 AI 驱动的平台有助于识别性能问题、预测系统行为,并优化资源利用,以提高应用性能。
-
LogicMonitor:LogicMonitor 利用 AI 进行基础设施监控和可观察性分析。它分析度量数据和性能数据,提供系统健康状况的洞察,预测潜在问题,并在复杂环境中优化资源分配。
-
OpsRamp:OpsRamp 利用 AI 进行 IT 运维管理,提供监控、事件管理和自动化功能。它使用机器学习(ML)来检测异常、自动化常规任务并优化工作流,从而提高运维效率。
这些 AI 驱动的工具有助于自动化任务、预测和防止问题、优化资源分配,并提高软件运维中整体系统的可靠性。
AI 在 DevOps 实践中的集成仍处于初级阶段,但其潜在影响巨大。通过自动化任务、优化流程和增强协作,AI 可以彻底改变软件的开发、部署和管理方式。随着 AI 技术的不断发展,我们可以期待看到更多 AI 被用于改进 DevOps 过程的方式。
总结
AI 通过在每个开发和运维周期阶段注入智能,彻底改变了 DevOps 实践。它简化了流程,提高了效率,并确保开发和运维团队之间的协作更加顺畅。AI 自动化常规任务,预测潜在瓶颈,优化工作流,改变了软件的构建、测试、部署和监控方式。从自动化代码分析到预测系统故障,AI 通过加快决策过程、减少错误以及促进更加敏捷和响应迅速的软件开发环境,赋能 DevOps。
本质上,AI 充当着一个无声的伙伴,持续从数据中学习,提出改进建议,并帮助 DevOps 团队在问题影响软件性能之前预见并解决问题。它是推动敏捷性和创新的催化剂,使 DevOps 从仅仅是团队之间的协作,发展为 AI 提升开发和运维双方能力的共生关系,为更高效、可靠的软件交付铺平道路。
934

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



