现代化Kubernetes应用程序

来自菜鸟教程
跳转至:导航、​搜索

介绍

现代无状态应用程序被构建和设计为在 Docker 等软件容器中运行,并由 Kubernetes 等容器集群管理。 它们是使用 Cloud NativeTwelve Factor 原则和模式开发的,以最大限度地减少人工干预并最大限度地提高可移植性和冗余性。 将基于虚拟机或裸机的应用程序迁移到容器中(称为“容器化”)并将它们部署到集群内通常会涉及这些应用程序的构建、打包和交付方式的重大转变。

基于 为 Kubernetes 构建应用程序 ,在本概念指南中,我们将讨论实现应用程序现代化的高级步骤,最终目标是在 Kubernetes 集群中运行和管理它们。 虽然您可以在 Kubernetes 上运行数据库等有状态应用程序,但本指南侧重于迁移和现代化无状态应用程序,并将持久数据卸载到外部数据存储。 Kubernetes 提供了用于高效管理和扩展无状态应用程序的高级功能,我们将探讨在 Kubernetes 上运行可扩展、可观察和可移植的应用程序所需的应用程序和基础架构更改。

准备移民申请

在容器化您的应用程序或编写 Kubernetes Pod 和部署配置文件之前,您应该实施应用程序级别的更改,以最大限度地提高您的应用程序在 Kubernetes 中的可移植性和可观察性。 Kubernetes 是一个高度自动化的环境,可以自动部署和重新启动失败的应用程序容器,因此构建适当的应用程序逻辑以与容器编排器进行通信并允许它根据需要自动扩展您的应用程序非常重要。

提取配置数据

要实现的第一个应用程序级更改之一是从应用程序代码中提取应用程序配置。 配置包含因部署和环境而异的任何信息,例如服务端点、数据库地址、凭据以及各种参数和选项。 例如,如果您有两个环境,例如 stagingproduction,并且每个环境都包含一个单独的数据库,那么您的应用程序不应在代码中显式声明数据库端点和凭据,而是存储在一个单独的位置,可以作为运行环境中的变量、本地文件或外部键值存储,从中将值读入应用程序。

将这些参数硬编码到您的代码中会带来安全风险,因为此配置数据通常包含敏感信息,然后您将这些信息签入您的版本控制系统。 它还增加了复杂性,因为您现在必须维护应用程序的多个版本,每个版本都包含相同的核心应用程序逻辑,但配置略有不同。 随着应用程序及其配置数据的增长,将配置硬编码到应用程序代码中会很快变得笨拙。

通过从您的应用程序代码中提取配置值,而不是从运行环境或本地文件中提取它们,您的应用程序成为一个通用的、可移植的包,可以部署到任何环境中,只要您为其提供随附的配置数据。 像 Docker 这样的容器软件和像 Kubernetes 这样的集群软件都是围绕这种范式设计的,内置了用于管理配置数据并将其注入应用程序容器的功能。 这些功能将在 ContainerizingKubernetes 部分中更详细地介绍。

这是一个快速示例,演示如何从 Python Flask 应用程序的代码中外部化两个配置值 DB_HOSTDB_USER。 我们会将它们作为环境变量在应用程序的运行环境中提供,应用程序将从中读取它们:

hardcoded_config.py

from flask import Flask

DB_HOST = 'mydb.mycloud.com'
DB_USER = 'sammy'

app = Flask(__name__)

@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

运行此应用程序(请参阅 Flask 快速入门 了解如何)并访问其 Web 端点将显示一个包含这两个配置值的页面。

现在,这是将配置值外部化到应用程序运行环境的相同示例:

env_config.py

import os

from flask import Flask

DB_HOST = os.environ.get('APP_DB_HOST')
DB_USER = os.environ.get('APP_DB_USER')

app = Flask(__name__)

@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

在运行应用程序之前,我们在本地环境中设置必要的配置变量:

export APP_DB_HOST=mydb.mycloud.com
export APP_DB_USER=sammy
flask run

显示的网页应该包含与第一个示例相同的文本,但现在可以独立于应用程序代码修改应用程序的配置。 您可以使用类似的方法从本地文件中读取配置参数。

在下一节中,我们将讨论将应用程序状态移动到容器之外。

卸载应用程序状态

云原生应用程序在容器中运行,并由 Kubernetes 或 Docker Swarm 等集群软件动态编排。 给定的应用程序或服务可以跨多个副本进行负载平衡,并且任何单个应用程序容器都应该能够失败,而对客户端的服务中断最少或没有。 为了实现这种水平的、冗余的扩展,应用程序必须以无状态的方式设计。 这意味着它们响应客户端请求而不在本地存储持久的客户端和应用程序数据,并且在任何时间点,如果正在运行的应用程序容器被破坏或重新启动,关键数据都不会丢失。

例如,如果您正在运行通讯录应用程序,并且您的应用程序从通讯录中添加、删除和修改联系人,则通讯录数据存储应该是外部数据库或其他数据存储,并且容器内存中保存的唯一数据应该是本质上是短期的,并且是一次性的,不会丢失重要的信息。 像会话这样在用户访问中持续存在的数据也应该移动到像 Redis 这样的外部数据存储中。 只要有可能,您应该将任何状态从您的应用程序卸载到托管数据库或缓存等服务。

对于需要持久数据存储(如复制的 MySQL 数据库)的有状态应用程序,Kubernetes 内置了将持久块存储卷附加到容器和 Pod 的功能。 为了确保 Pod 可以在重启后保持状态并访问相同的持久卷,必须使用 StatefulSet 工作负载。 StatefulSet 非常适合将数据库和其他长期运行的数据存储部署到 Kubernetes。

无状态容器可实现最大的可移植性和充分利用可用的云资源,允许 Kubernetes 调度程序快速扩展和缩减您的应用程序,并在有资源的地方启动 Pod。 如果您不需要 StatefulSet 工作负载提供的稳定性和排序保证,您应该使用 Deployment 工作负载来管理和扩展您的应用程序。

要了解有关无状态、云原生微服务的设计和架构的更多信息,请参阅我们的 Kubernetes 白皮书

实施健康检查

在 Kubernetes 模型中,可以依靠集群控制平面来修复损坏的应用程序或服务。 它通过检查应用程序 Pod 的健康状况,并重新启动或重新安排不健康或无响应的容器来实现这一点。 默认情况下,如果您的应用程序容器正在运行,Kubernetes 会将您的 Pod 视为“健康”。 在许多情况下,这是运行应用程序健康状况的可靠指标。 但是,如果您的应用程序死锁并且没有执行任何有意义的工作,则应用程序进程和容器将继续无限期地运行,并且默认情况下 Kubernetes 将保持停滞的容器处于活动状态。

要将应用程序健康正确地传达给 Kubernetes 控制平面,您应该实施自定义应用程序健康检查,以指示应用程序何时正在运行并准备好接收流量。 第一种健康检查称为 就绪探测 ,它让 Kubernetes 知道您的应用程序何时准备好接收流量。 第二种检查称为 活跃度探测 ,它让 Kubernetes 知道您的应用程序何时健康并正在运行。 Kubelet 节点代理可以使用 3 种不同的方法对正在运行的 Pod 执行这些探测:

  • HTTP:Kubelet 探针对端点(如 /health)执行 HTTP GET 请求,如果响应状态在 200 到 399 之间,则成功
  • 容器命令:Kubelet 探针在正在运行的容器内执行命令。 如果退出代码为 0,则探测成功。
  • TCP:Kubelet 探针尝试在指定端口上连接到您的容器。 如果可以建立 TCP 连接,则探测成功。

您应该根据正在运行的应用程序、编程语言和框架选择适当的方法。 就绪和活跃度探测可以使用相同的探测方法并执行相同的检查,但是包含就绪探测将确保 Pod 在探测开始成功之前不会接收流量。

在计划和考虑将应用程序容器化并在 Kubernetes 上运行时,您应该分配计划时间来定义特定应用程序的“健康”和“就绪”意味着什么,以及用于实施和测试端点和/或检查命令的开发时间。

这是上面引用的 Flask 示例的最小健康端点:

env_config.py

. . .  
@app.route('/')
def print_config():
    output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER)
    return output

@app.route('/health')
def return_ok():
    return 'Ok!', 200

检查此路径的 Kubernetes 活性探针将如下所示:

pod_spec.yaml

. . .
  livenessProbe:
      httpGet:
        path: /health
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 2

initialDelaySeconds 字段指定 Kubernetes(特别是 Node Kubelet)在等待 5 秒后应该探测 /health 端点,periodSeconds 告诉 Kubelet 探测 /health每 2 秒。

要了解有关 liveness 和 readiness 探针的更多信息,请参阅 Kubernetes 文档

用于记录和监控的仪器代码

在 Kubernetes 等环境中运行容器化应用程序时,发布遥测和日志数据以监控和调试应用程序的性能非常重要。 内置功能以发布响应持续时间和错误率等性能指标将帮助您监控应用程序并在应用程序运行状况不佳时提醒您。

您可以用来监控服务的一个工具是 Prometheus,这是一个开源系统监控和警报工具包,由云原生计算基金会 (CNCF) 托管。 Prometheus 提供了几个客户端库,用于使用各种指标类型来检测您的代码,以计算事件及其持续时间。 例如,如果您使用的是 Flask Python 框架,则可以使用 Prometheus Python 客户端 将装饰器添加到请求处理函数中,以跟踪处理请求所花费的时间。 然后,Prometheus 可以在 /metrics 等 HTTP 端点上抓取这些指标。

RED 方法是设计应用程序的一种有用的方法。 它由以下三个关键请求指标组成:

  • 速率:您的应用程序收到的请求数
  • 错误:应用程序发出的错误数
  • 持续时间:您的应用程序提供响应所需的时间

这组最小的指标应该为您提供足够的数据,以便在您的应用程序性能下降时生成警报。 与上面讨论的健康检查一起实施此检测将允许您快速检测到失败的应用程序并从失败的应用程序中恢复。

要了解有关监视应用程序时要测量的信号的更多信息,请参阅 Google Site Reliability Engineering 书中的 Monitoring Distributed Systems

除了考虑和设计用于发布遥测数据的功能外,您还应该决定您的应用程序将如何登录基于分布式集群的环境。 理想情况下,您应该删除对本地日志文件和日志目录的硬编码配置引用,而是直接登录到 stdoutstderr。 您应该将日志视为连续的事件流或按时间排序的事件序列。 然后,此输出流将被封装您的应用程序的容器捕获,然后可以将其转发到 EFK(Elasticsearch、Fluentd 和 Kibana)堆栈等日志记录层。 Kubernetes 在设计日志架构方面提供了很大的灵活性,我们将在下面更详细地探讨。

将管理逻辑构建到 API

一旦您的应用程序被容器化并在 Kubernetes 等集群环境中启动并运行,您可能不再拥有对运行您的应用程序的容器的 shell 访问权限。 如果您已经实施了足够的健康检查、日志记录和监控,您可以快速收到警报并调试生产问题,但除了重新启动和重新部署容器之外采取行动可能会很困难。 对于像刷新队列或清除缓存这样的快速操作和维护修复,您应该实现适当的 API 端点,以便您可以执行这些操作而无需重新启动容器或 docker exec 进入正在运行的容器。 容器应被视为不可变对象,在生产环境中应避免手动管理。 如果您必须执行一次性管理任务,例如清除缓存,您应该通过 API 公开此功能。

概括

在这些部分中,我们讨论了您可能希望在将应用程序容器化并将其移至 Kubernetes 之前实施的应用程序级更改。 有关构建 Cloud Native 应用程序的更深入演练,请参阅 Architecting Applications for Kubernetes

我们现在将讨论在为您的应用程序构建容器时要牢记的一些注意事项。

容器化您的应用程序

现在您已经实现了应用程序逻辑以在基于云的环境中最大限度地提高其可移植性和可观察性,是时候将您的应用程序打包到容器中了。 出于本指南的目的,我们将使用 Docker 容器,但您应该使用最适合您的生产需求的容器实现。

显式声明依赖关系

在为您的应用程序创建 Dockerfile 之前,第一步是评估应用程序正确运行所需的软件和操作系统依赖项。 Dockerfiles 允许您显式地对安装到映像中的每个软件进行版本控制,并且您应该通过显式声明父映像、软件库和编程语言版本来利用此功能。

尽可能避免使用 latest 标记和未版本控制的包,因为它们可能会发生变化,可能会破坏您的应用程序。 您可能希望创建一个私有注册表或公共注册表的私有镜像,以对映像版本控制施加更多控制,并防止上游更改无意中破坏您的映像构建。

要了解有关设置私有映像注册表的更多信息,请参阅 Docker 官方文档中的 Deploy a Registry Server 和下面的 Registries 部分。

保持较小的图像尺寸

在部署和拉取容器镜像时,大镜像会显着减慢速度并增加带宽成本。 将一组最小的工具和应用程序文件打包到一个映像中提供了几个好处:

  • 缩小图像尺寸
  • 加快图像构建
  • 减少容器启动滞后
  • 加快图像传输时间
  • 通过减少攻击面提高安全性

构建图像时可以考虑的一些步骤:

  • 使用像 alpine 这样的最小基本操作系统映像或从 scratch 构建而不是像 ubuntu 这样的全功能操作系统
  • 安装软件后清理不必要的文件和工件
  • 使用单独的“构建”和“运行时”容器来保持生产应用程序容器较小
  • 在大目录中复制时忽略不必要的构建工件和文件

有关优化 Docker 容器的完整指南(包括许多说明性示例),请参阅 为 Kubernetes 构建优化的容器

注入配置

Docker 提供了一些有用的功能,用于将配置数据注入应用程序的运行环境。

执行此操作的一种选择是使用 ENV 语句在 Dockerfile 中指定环境变量及其值,以便配置数据内置到映像中:

Dockerfile

...
ENV MYSQL_USER=my_db_user
...

然后,您的应用程序可以从其运行环境中解析这些值并适当地配置其设置。

您还可以在使用 docker run-e 标志启动容器时将环境变量作为参数传递:

docker run -e MYSQL_USER='my_db_user' IMAGE[:TAG] 

最后,您可以使用 env 文件,其中包含环境变量及其值的列表。 为此,请创建文件并使用 --env-file 参数将其传递给命令:

docker run --env-file var_list IMAGE[:TAG]

如果您正在对应用程序进行现代化改造以使用 Kubernetes 之类的集群管理器运行它,您应该进一步从镜像中外部化您的配置,并使用 Kubernetes 的内置 ConfigMapSecrets 管理配置] 对象。 这允许您将配置与图像清单分开,以便您可以与应用程序分开管理和版本化它。 要了解如何使用 ConfigMaps 和 Secrets 将配置外部化,请参阅下面的 ConfigMaps 和 Secrets 部分

将图像发布到注册表

构建应用程序映像后,为了使它们可供 Kubernetes 使用,您应该将它们上传到容器映像注册表。 Docker Hub 等公共注册中心托管 Node.jsnginx 等流行开源项目的最新 Docker 镜像。 私有注册表允许您发布内部应用程序映像,使它们可供开发人员和基础设施使用,但不能用于更广泛的世界。

您可以使用现有基础架构部署私有注册表(例如 在云对象存储之上),或者可选地使用多个 Docker 注册表产品之一,例如 Quay.io 或付费的 Docker Hub 计划。 这些注册中心可以与 GitHub 等托管版本控制服务集成,以便在更新和推送 Dockerfile 时,注册中心服务将自动拉取新的 Dockerfile,构建容器映像,并使更新后的映像可用于您的服务。

为了更好地控制容器映像的构建和测试以及它们的标记和发布,您可以实施持续集成 (CI) 管道。

实施构建管道

手动构建、测试、发布和部署图像到生产中可能容易出错并且不能很好地扩展。 要管理构建并持续将包含最新代码更改的容器发布到镜像注册表,您应该使用构建管道。

大多数构建管道执行以下核心功能:

  • 观看源代码存储库的更改
  • 对修改后的代码运行冒烟和单元测试
  • 构建包含修改代码的容器镜像
  • 使用构建的容器镜像运行进一步的集成测试
  • 如果测试通过,标记图像并将其发布到注册表
  • (可选,在持续部署设置中)更新 Kubernetes 部署并将映像部署到暂存/生产集群

有许多付费的持续集成产品内置了与流行的版本控制服务(如 GitHub)和图像注册表(如 Docker Hub)的集成。 这些产品的替代品是 Jenkins,一个免费的开源构建自动化服务器,可以配置为执行上述所有功能。 要了解如何设置 Jenkins 持续集成管道,请参阅 How To Set Up Continuous Integration Pipelines in Jenkins on Ubuntu 20.04

实施容器日志记录和监控

使用容器时,重要的是要考虑将用于管理和存储所有正在运行和停止的容器的日志的日志记录基础设施。 有多种容器级别的模式可以用于日志记录,还有多种 Kubernetes 级别的模式。

在 Kubernetes 中,默认情况下,容器使用 json-file Docker 日志驱动程序,它捕获标准输出和标准错误流并将它们写入容器运行的节点上的 JSON 文件。 有时,直接将日志记录到 stderr 和 stdout 可能不足以满足您的应用程序容器的需求,您可能希望将应用程序容器与 Kubernetes Pod 中的日志记录 sidecar 容器配对。 然后,这个 sidecar 容器可以从文件系统、本地套接字或 systemd 日志中获取日志,比简单地使用 stderr 和 stdout 流提供更多的灵活性。 该容器还可以进行一些处理,然后将丰富的日志流式传输到 stdout/stderr,或直接传输到日志记录后端。 要了解有关 Kubernetes 日志记录模式的更多信息,请参阅本教程的 Kubernetes 日志记录和监控 部分

您的应用程序在容器级别记录的方式将取决于其复杂性。 对于单一用途的微服务,直接记录到 stdout/stderr 并让 Kubernetes 获取这些流是推荐的方法,因为您可以利用 kubectl logs 命令从 Kubernetes 部署的容器访问日志流。

与日志记录类似,您应该开始考虑在基于容器和集群的环境中进行监控。 Docker 提供了有用的 docker stats 命令来获取标准指标,例如在主机上运行容器的 CPU 和内存使用情况,并通过 Remote REST API 公开更多指标。 此外,开源工具 cAdvisor(默认安装在 Kubernetes 节点上)提供了更高级的功能,例如历史指标收集、指标数据导出以及用于对数据进行排序的有用 Web UI。

但是,在多节点、多容器的生产环境中,更复杂的指标堆栈(如 PrometheusGrafana)可能有助于组织和监控容器的性能数据。

概括

在这些部分中,我们简要讨论了构建容器、设置 CI/CD 管道和映像注册表的一些最佳实践,以及提高容器可观察性的一些注意事项。

在下一节中,我们将探索 Kubernetes 功能,这些功能允许您在集群中运行和扩展您的容器化应用程序。

在 Kubernetes 上部署

至此,您已经将您的应用程序容器化并实现了逻辑,以最大限度地提高其在云原生环境中的可移植性和可观察性。 我们现在将探索 Kubernetes 功能,这些功能提供了用于在 Kubernetes 集群中管理和扩展您的应用程序的接口。

编写部署和 Pod 配置文件

将应用程序容器化并将其发布到注册表后,您现在可以使用 Pod 工作负载将其部署到 Kubernetes 集群中。 Kubernetes 集群中最小的可部署单元不是容器,而是 Pod。 Pod 通常由一个应用程序容器(如容器化的 Flask Web 应用程序)或一个应用程序容器和任何执行某些辅助功能(如监控或日志记录)的“sidecar”容器组成。 Pod 中的容器共享存储资源、网络命名空间和端口空间。 它们可以使用 localhost 相互通信,并且可以使用挂载的卷共享数据。 此外,Pod 工作负载允许您定义 Init Containers 在主应用程序容器开始运行之前运行设置脚本或实用程序。

Pod 通常使用 Deployments 推出,它们是由 YAML 文件定义的控制器,用于声明特定的所需状态。 例如,应用程序状态可能正在运行 Flask Web 应用程序容器的三个副本并公开端口 8080。 一旦创建,控制平面通过根据需要将容器调度到节点上,逐渐使集群的实际状态与部署中声明的期望状态相匹配。 要扩展集群中运行的应用程序副本的数量,例如从 3 到 5,请更新 Deployment 配置文件的 replicas 字段,然后更新 kubectl apply 新配置文件。 使用这些配置文件,扩展和部署操作都可以使用您现有的源代码控制服务和集成进行跟踪和版本控制。

这是 Flask 应用程序的示例 Kubernetes 部署配置文件:

flask_deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  labels:
    app: flask-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask
        image: sammy/flask_app:1.0
        ports:
        - containerPort: 8080

此 Deployment 使用 sammy/flask_app 映像(版本 1.0)启动 3 个 Pod,它们运行一个名为 flask 的容器,并打开端口 8080。 部署称为 flask-app

要了解有关 Kubernetes Pod 和部署的更多信息,请参阅 Kubernetes 官方文档的 PodsDeployments 部分。

配置 Pod 存储

Kubernetes 使用卷、持久卷 (PV) 和持久卷声明 (PVC) 来管理 Pod 存储。 卷是用于管理 Pod 存储的 Kubernetes 抽象,支持大多数云提供商块存储产品,以及托管运行 Pod 的节点上的本地存储。 要查看支持的卷类型的完整列表,请参阅 Kubernetes 文档

例如,如果您的 Pod 包含两个需要在它们之间共享数据的 NGINX 容器(例如,第一个名为 nginx 服务于网页,第二个名为 nginx-sync 从外部获取页面位置并更新由 nginx 容器提供的页面),您的 Pod 规范看起来像这样(这里我们使用 emptyDir 卷类型):

pod_volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: nginx-web
      mountPath: /usr/share/nginx/html
   
  - name: nginx-sync
    image: nginx-sync
    volumeMounts:
    - name: nginx-web
      mountPath: /web-data
  
  volumes:
  - name: nginx-web
    emptyDir: {}

我们为每个容器使用一个 volumeMount,表示我们想在 nginx 容器的 /usr/share/nginx/html 处挂载包含网页文件的 nginx-web 卷并在 nginx-sync 容器中的 /web-data 处。 我们还定义了一个 volume,称为 nginx-web,类型为 emptyDir

以类似的方式,您可以通过将 volume 类型从 emptyDir 修改为相关的云存储卷类型来使用云块存储产品配置 Pod 存储。

Volume 的生命周期与 Pod 的生命周期相关联,但 而不是 与容器的生命周期相关联。 如果 Pod 中的容器死亡,Volume 仍然存在,新启动的容器将能够挂载相同的 Volume 并访问其数据。 当一个 Pod 重新启动或死亡时,它的 Volumes 也会如此,尽管如果 Volumes 由云块存储组成,它们将被简单地卸载,并且未来的 Pod 仍然可以访问数据。

要在 Pod 重新启动和更新期间保留数据,必须使用 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 对象。

PersistentVolume 是代表持久存储片段的抽象,例如云块存储卷或 NFS 存储。 它们是与 PersistentVolumeClaims 分开创建的,后者是开发人员对存储块的需求。 在他们的 Pod 配置中,开发人员使用 PVC 请求持久存储,Kubernetes 与可用的 PV Volumes 匹配(如果使用云块存储,Kubernetes 可以在创建 PersistentVolumeClaims 时动态创建 PersistentVolumes)。

如果您的应用程序需要每个副本一个持久卷(许多数据库都是这种情况),则不应使用 Deployment,而应使用 StatefulSet 控制器,该控制器专为需要稳定网络标识符、稳定持久存储和排序保证的应用程序而设计。 部署应该用于无状态应用程序,如果您定义一个 PersistentVolumeClaim 用于部署配置,则该 PVC 将由所有部署的副本共享。

要了解有关 StatefulSet 控制器的更多信息,请参阅 Kubernetes 文档 。 要了解有关 PersistentVolume 和 PersistentVolume 声明的更多信息,请参阅 Kubernetes 存储 文档

使用 Kubernetes 注入配置数据

与 Docker 类似,Kubernetes 提供了 envenvFrom 字段用于在 Pod 配置文件中设置环境变量。 这是 Pod 配置文件中的示例片段,它将正在运行的 Pod 中的 HOSTNAME 环境变量设置为 my_hostname

sample_pod.yaml

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.6
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          value: my_hostname
...

这允许您将配置移出 Dockerfiles 并移入 Pod 和 Deployment 配置文件。 从 Dockerfile 进一步外部化配置的一个关键优势是,您现在可以独立于应用程序容器定义修改这些 Kubernetes 工作负载配置(例如,通过将 HOSTNAME 值更改为 my_hostname_2)。 修改 Pod 配置文件后,您可以使用新环境重新部署 Pod,而无需重新构建、测试和推送底层容器映像(通过其 Dockerfile 定义)。 您还可以将这些 Pod 和 Deployment 配置与 Dockerfile 分开进行版本控制,从而使您能够快速检测重大更改并进一步将配置问题与应用程序错误分开。

Kubernetes 提供了另一种结构来进一步外部化和管理配置数据:ConfigMaps 和 Secrets。

ConfigMap 和 Secret

ConfigMap 允许您将配置数据保存为对象,然后在您的 Pod 和 Deployment 配置文件中引用这些对象,这样您就可以避免硬编码配置数据并在 Pod 和 Deployment 之间重复使用它。

这是一个示例,使用上面的 Pod 配置。 我们首先将 HOSTNAME 环境变量保存为 ConfigMap,然后在 Pod 配置中引用它:

kubectl create configmap hostname --from-literal=HOSTNAME=my_host_name

要从 Pod 配置文件中引用它,我们使用 valueFromconfigMapKeyRef 结构:

sample_pod_configmap.yaml

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.6
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          valueFrom:
            configMapKeyRef:
              name: hostname
              key: HOSTNAME
...

所以 HOSTNAME 环境变量的值已经完全从配置文件中外化了。 然后,我们可以在引用它们的所有 Deployment 和 Pod 中更新这些变量,并重新启动 Pod 以使更改生效。

如果您的应用程序使用配置文件,ConfigMaps 还允许您将这些文件存储为 ConfigMap 对象(使用 --from-file 标志),然后您可以将其作为配置文件挂载到容器中。

Secret 提供与 ConfigMap 相同的基本功能,但应用于敏感数据,如数据库凭据,因为这些值是 base64 编码的。

要了解有关 ConfigMap 和 Secret 的更多信息,请参阅 Kubernetes 文档

创建服务

一旦你的应用程序在 Kubernetes 中启动并运行,每个 Pod 将被分配一个(内部)IP 地址,由其容器共享。 如果其中一个 Pod 被移除或死亡,新启动的 Pod 将被分配不同的 IP 地址。

对于向内部和/或外部客户端公开功能的长时间运行的服务,您可能希望为一组执行相同功能(或部署)的 Pod 授予一个稳定的 IP 地址,以便在其容器之间对请求进行负载平衡。 您可以使用 Kubernetes 服务来执行此操作。

Kubernetes Services 有 4 种类型,由 Service 配置文件中的 type 字段指定:

  • ClusterIP:这是默认类型,它授予服务一个稳定的内部 IP,可以从集群内的任何地方访问。
  • NodePort:这将在每个节点上的静态端口上公开您的服务,默认情况下在 30000-32767 之间。 当请求在其节点 IP 地址和您的服务的 NodePort 命中节点时,请求将被负载平衡并路由到您的服务的应用程序容器。
  • LoadBalancer:这将使用您的云提供商的负载均衡产品创建一个负载均衡器,并为您的服务配置一个 NodePortClusterIP,外部请求将被路由到该服务。
  • ExternalName:此服务类型允许您将 Kubernetes 服务映射到 DNS 记录。 它可用于使用 Kubernetes DNS 从您的 Pod 访问外部服务。

请注意,为集群中运行的每个部署创建类型为 LoadBalancer 的服务将为每个服务创建一个新的云负载均衡器,这可能会变得昂贵。 要使用单个负载均衡器管理将外部请求路由到多个服务,您可以使用 Ingress Controller。 Ingress Controller 超出了本文的范围,但要了解有关它们的更多信息,您可以查阅 Kubernetes 文档 。 一个流行的入口控制器是 NGINX 入口控制器

这是本指南的 Pods 和部署 部分 中使用的 Flask 示例的服务配置文件:

flask_app_svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: flask-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: flask-app
  type: LoadBalancer

这里我们选择使用这个 flask-svc 服务公开 flask-app 部署。 我们创建了一个云负载均衡器来将流量从负载均衡器端口 80 路由到暴露的容器端口 8080

要了解有关 Kubernetes 服务的更多信息,请参阅 Kubernetes 文档的 Services 部分。

记录和监控

随着正在运行的应用程序数量的增加,使用 kubectl logsdocker logs 解析单个容器和 Pod 日志可能会变得乏味。 为了帮助您调试应用程序或集群问题,您应该实施集中式日志记录。 在高层次上,这包括在所有工作节点上运行的代理,这些节点处理 Pod 日志文件和流,使用元数据丰富它们,并将日志转发到 Elasticsearch 等后端。 从那里,可以使用 Kibana 等可视化工具对日志数据进行可视化、过滤和组织。

在容器级日志记录部分,我们讨论了推荐的 Kubernetes 方法,即让容器中的应用程序记录到 stdout/stderr 流。 我们还简要讨论了记录 sidecar 容器,它可以在从应用程序记录时为您提供更大的灵活性。 您还可以直接在您的 Pod 中运行日志代理,以捕获本地日志数据并将它们直接转发到您的日志后端。 每种方法都有其优点和缺点,以及资源利用率的权衡(例如,在每个 Pod 内运行日志代理容器可能会变得资源密集型并很快使您的日志后端不堪重负)。 要了解有关不同日志架构及其权衡的更多信息,请参阅 Kubernetes 文档

在标准设置中,每个节点都运行一个日志代理,例如 FilebeatFluentd,用于获取 Kubernetes 创建的容器日志。 回想一下,Kubernetes 为节点上的容器创建 JSON 日志文件(在大多数安装中,这些文件可以在 /var/lib/docker/containers/ 找到)。 这些应该使用像 logrotate 这样的工具来旋转。 节点日志代理应该作为 DaemonSet 控制器 运行,这是一种 Kubernetes 工作负载,可确保每个节点运行 DaemonSet Pod 的副本。 在这种情况下,Pod 将包含日志代理及其配置,它处理来自挂载到日志记录 DaemonSet Pod 的文件和目录的日志。

类似于使用 kubectl logs 来调试容器问题的瓶颈,最终您可能需要考虑一个比简单地使用 kubectl top 和 Kubernetes Dashboard 来监控集群上 Pod 资源使用情况的更强大的选项。 可以使用 Prometheus 监控系统和时序数据库以及 Grafana 指标仪表板来设置集群和应用程序级监控。 Prometheus 使用“拉”模型工作,该模型定期抓取 HTTP 端点(如节点上的 /metrics/cadvisor/metrics 应用程序 REST API 端点)以获取度量数据,然后处理和存储这些数据。 然后可以使用 Grafana 仪表板分析和可视化这些数据。 Prometheus 和 Grafana 可以像任何其他部署和服务一样启动到 Kubernetes 集群中。

为了增加弹性,您可能希望在单独的 Kubernetes 集群上运行日志记录和监控基础架构,或者使用外部日志记录和指标服务。

结论

迁移和现代化应用程序以便它可以在 Kubernetes 集群中高效运行通常涉及大量的软件和基础架构更改的规划和架构。 一旦实施,这些更改允许服务所有者持续部署其应用程序的新版本,并根据需要轻松扩展它们,而人工干预最少。 从您的应用程序外部化配置、设置适当的日志记录和指标发布以及配置健康检查等步骤可以让您充分利用 Kubernetes 围绕设计的云原生范例。 通过构建可移植容器并使用部署和服务等 Kubernetes 对象管理它们,您可以充分利用可用的计算基础架构和开发资源。