Kubernetes 应用程序现代化

2025-05-25

Kubernetes 应用程序现代化

介绍

现代无状态应用程序的构建和设计旨在在 Docker 等软件容器中运行,并由 Kubernetes 等容器集群进行管理。它们采用云原生十二要素原则及模式开发,旨在最大限度地减少人工干预,并最大限度地提高可移植性和冗余性。将虚拟机或基于裸机的应用程序迁移到容器中(称为“容器化”)并将其部署到集群中,通常需要对这些应用程序的构建、打包和交付方式进行重大调整。

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

准备移民申请

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

提取配置数据

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

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

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

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

硬编码配置.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 端点将显示一个包含这两个配置值的页面。

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

环境配置

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 示例的最小健康端点:

环境配置

. . .  
@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)应/health等待 5 秒后探测端点,并periodSeconds告诉 Kubelet/health每 2 秒探测一次。

要了解有关活性和就绪探测的更多信息,请参阅Kubernetes 文档

记录和监测仪器代码

在 Kubernetes 等环境中运行容器化应用程序时,发布遥测和日志数据以监控和调试应用程序性能至关重要。内置发布性能指标(例如响应时长和错误率)的功能将帮助您监控应用程序,并在应用程序运行状况不佳时发出警报。

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

在设计应用的插桩工具时,RED 方法是一个非常有用的方法。它包含以下三个关键请求指标:

  • 速率:您的应用程序收到的请求数量
  • 错误:应用程序发出的错误数量
  • 时长:您的申请回复所需的时间

这组最基本的指标应该能提供足够的数据,让你在应用程序性能下降时发出警报。结合上面讨论的健康检查,可以快速检测并恢复故障应用程序。

要了解有关监控应用程序时要测量的信号的更多信息,请参阅《Google 站点可靠性工程》一书中的“监控分布式系统” 。

除了思考和设计发布遥测数据的功能之外,您还应该规划应用程序在分布式集群环境中的日志记录方式。理想情况下,您应该删除对本地日志文件和日志目录的硬编码配置引用,而是直接记录到标准输出 (stdout) 和标准错误输出 (stderr)。您应该将日志视为连续的事件流,或按时间顺序排列的事件序列。该输出流随后将被封装应用程序的容器捕获,并从容器转发到日志层,例如 EFK(Elasticsearch、Fluentd 和 Kibana)堆栈。Kubernetes 在设计日志架构方面提供了极大的灵活性,我们将在下文中详细探讨。

将管理逻辑构建到 API 中

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

概括

在这些部分中,我们讨论了在将应用程序容器化并迁移到 Kubernetes 之前,您可能希望实现的应用程序级别更改。有关构建云原生应用的更深入的演练,请参阅《为 Kubernetes 构建应用程序》

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

容器化您的应用程序

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

明确声明依赖关系

在为应用程序创建 Dockerfile 之前,第一步是评估应用程序正常运行所需的软件和操作系统依赖项。Dockerfile 允许您明确地为安装到镜像中的每个软件指定版本,您应该利用此功能,明确声明父镜像、软件库和编程语言的版本。

latest尽量避免使用标签和未版本控制的软件包,因为它们可能会发生变化,从而可能破坏您的应用程序。您可以创建一个私有镜像仓库或公共镜像仓库的私有镜像,以便更好地控制镜像版本,并防止上游更改无意中破坏您的镜像构建。

要了解有关设置私有镜像注册表的更多信息,请参阅Docker 官方文档中的部署注册表服务器以及下面的注册表部分。

保持图像尺寸较小

部署和拉取容器镜像时,大型镜像可能会显著降低速度并增加带宽成本。将一组最少的工具和应用程序文件打包到镜像中可带来以下几个好处:

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

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

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

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

注入配置

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

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

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对象管理配置。这允许您将配置与镜像清单分离,以便您可以将其与应用程序分开管理和版本控制。要了解如何使用 ConfigMap 和 Secrets 外部化配置,请参阅下面的“ConfigMap 和 Secrets”部分

将镜像发布到注册表

构建应用程序镜像后,为了使它们可供 Kubernetes 使用,您应该将它们上传到容器镜像仓库。像Docker Hub这样的公共镜像仓库托管着Node.jsnginx等热门开源项目的最新 Docker 镜像。私有镜像仓库允许您发布内部应用程序镜像,使其可供开发人员和基础设施使用,但不会向外界公开。

您可以使用现有基础架构(例如,基于云对象存储)部署私有镜像仓库,也可以选择使用Quay.io等 Docker 镜像仓库产品或付费 Docker Hub 方案。这些镜像仓库可以与 GitHub 等托管版本控制服务集成,以便在 Dockerfile 更新和推送时,镜像仓库服务会自动拉取新的 Dockerfile,构建容器镜像,并将更新后的镜像提供给您的服务。

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

实施构建管道

手动构建、测试、发布并将镜像部署到生产环境中很容易出错,而且扩展性较差。为了管理构建并将包含最新代码更改的容器持续发布到镜像注册表,您应该使用构建流水线。

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

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

市面上有许多付费的持续集成产品,它们内置了与 GitHub 等流行版本控制服务和 Docker Hub 等镜像仓库的集成。Jenkins 是这些产品的替代方案,它是一个免费的开源构建自动化服务器,经过配置即可执行上述所有功能。要了解如何设置 Jenkins 持续集成流水线,请参阅如何在 Ubuntu 16.04 上的 Jenkins 中设置持续集成流水线

实施容器日志记录和监控

使用容器时,务必考虑用于管理和存储所有正在运行和已停止容器日志的日志记录基础架构。您可以使用多种容器级模式进行日志记录,也可以使用多种 Kubernetes 级模式。

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

应用程序在容器级别如何记录日志取决于其复杂性。对于简单、单一用途的微服务,建议直接将日志记录到 stdout/stderr 并让 Kubernetes 获取这些流,因为这样您就可以利用该kubectl logs命令从 Kubernetes 部署的容器中访问日志流。

与日志记录类似,您应该开始考虑在容器和基于集群的环境中进行监控。Docker 提供了实用的docker stats命令,用于获取主机上运行容器的标准指标(例如 CPU 和内存使用情况),并通过远程 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 工作负载允许您定义初始化容器,用于在主应用容器开始运行之前运行设置脚本或实用程序。

Pod 通常使用 Deployment 部署。Deployment 是由 YAML 文件定义的控制器,用于声明特定的期望状态。例如,一个应用程序状态可能是运行 Flask Web 应用容器的三个副本并公开 8080 端口。创建后,控制平面会根据需要将容器调度到节点上,从而逐渐使集群的实际状态与 Deployment 中声明的期望状态相匹配。要扩展集群中运行的应用程序副本数量,例如从 3 个增加到 5 个,您需要更新replicasDeployment 配置文件的字段,然后更新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

flask此 Deployment 启动 3 个 Pod,每个 Pod使用sammy/flask_app镜像(版本1.0运行名为 的容器,并8080开放端口。此 Deployment 名为flask-app

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

配置 Pod 存储

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

例如,如果您的 Pod 包含两个需要在它们之间共享数据的 NGINX 容器(假设第一个nginx容器提供网页服务,第二个容器nginx-sync从外部位置获取页面并更新容器提供的页面nginx),您的 Pod 规范将如下所示(这里我们使用emptyDirVolume 类型):

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-web包含网页文件的卷挂载到/usr/share/nginx/html容器中nginx,并将 挂载到容器/web-datanginx-sync。我们还定义了一个类型的volume调用nginx-webemptyDir

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

卷的生命周期与 Pod 的生命周期相关,但与容器的生命周期无关。如果 Pod 中的容器挂载,卷会继续存在,新启动的容器将能够挂载相同的卷并访问其数据。当 Pod 重启或挂载时,其卷也会随之失效。但如果卷包含云块存储,则卷会被卸载,但后续 Pod 仍然可以访问其数据。

为了在 Pod 重启和更新时保留数据,必须使用 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 对象。

持久卷 (PersistentVolume) 是代表持久存储(例如云块存储卷或 NFS 存储)的抽象概念。它们与持久卷声明 (PersistentVolumeClaims) 分开创建,而持久卷声明是开发者对存储的需求。在 Pod 配置中,开发者使用 PVC 请求持久存储,Kubernetes 会将 PVC 与可用的 PV 卷进行匹配(如果使用云块存储,Kubernetes 可以在创建持久卷声明时动态创建持久卷)。

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

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

使用 Kubernetes 注入配置数据

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

示例_pod.yaml

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

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

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

ConfigMap 和 Secrets

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

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

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

为了从 Pod 配置文件中引用它,我们使用valueFromconfigMapKeyRef构造:

示例_pod_配置图.yaml

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

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

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

Secrets 提供与 ConfigMaps 相同的基本功能,但由于其值是 base64 编码的,因此应用于数据库凭证等敏感数据。

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

创建服务

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

对于长期运行且向内部和/或外部客户端开放功能的服务,您可能希望为一组执行相同功能的 Pod(或 Deployment)分配一个稳定的 IP 地址,以便在其容器之间对请求进行负载均衡。您可以使用 Kubernetes 服务来实现这一点。

typeKubernetes 服务有 4 种类型,由服务配置文件中的字段指定:

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

LoadBalancer请注意,为集群中运行的每个 Deployment创建 类型的 Service会为每个 Service 创建一个新的云负载均衡器,这可能会增加成本。要使用单个负载均衡器将外部请求路由到多个服务,可以使用 Ingress 控制器。Ingress 控制器超出了本文的讨论范围,但要了解更多信息,您可以查阅 Kubernetes文档。NGINX Ingress 控制器是一个常用的简单Ingress 控制器

这是本指南的Pods 和 Deployments部分中使用的 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-app使用此flask-svc服务来暴露 Deployment。我们创建一个云负载均衡器,将流量从负载均衡器端口路由80到暴露的容器端口8080

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

日志记录和监控

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

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

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

与使用 Kubernetes 调试容器问题时遇到的瓶颈类似kubectl logs,最终您可能需要考虑一个比仅仅使用kubectl topKubernetes 仪表板来监控集群中 Pod 资源使用情况更强大的方案。您可以使用Prometheus监控系统、时间序列数据库以及Grafana指标仪表板来设置集群和应用程序级别的监控。Prometheus 采用“拉取”模型,定期从 HTTP 端点(例如/metrics/cadvisor节点或/metrics应用程序 REST API 端点)抓取指标数据,然后进行处理和存储。之后,您可以使用 Grafana 仪表板分析和可视化这些数据。Prometheus 和 Grafana 可以像任何其他部署和服务一样启动到 Kubernetes 集群中。

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

结论

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


CC 4.0许可证

本作品采用Creative Commons Attribution-NonCommercial-ShareAlike 4.0 国际许可协议进行许可

文章来源:https://dev.to/digitalocean/modernizing-applications-for-kubernetes-1hon
PREV
Vue 与 React:2021 年该选择哪个?
NEXT
React + TypeScript ❤️:优点⚡