现代化Kubernetes的应用程序

现代无状态应用程序的构建和设计可在Docker等软件容器中运行,并由Kubernetes等容器集群管理。它们是使用Cloud Native和[Twelve Factor]开发的(...

介绍

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

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

准备迁移应用程序

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

提取配置数据

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

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

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

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

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

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

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

卸载申请状态

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

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

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

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

要了解有关无状态Cloud Native微服务的设计和体系结构的更多信息,请参阅我们的Kubernetes白皮书

实施健康检查

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

要将应用程序运行状况正确地传递给Kubernetes控制平面,您应该实现自定义应用程序运行状况检查,以指示应用程序何时运行并准备好接收流量。 第一种类型的运行状况检查称为准备情况调查 ,并让Kubernetes知道您的应用程序何时准备好接收流量。 第二种类型的检查称为活动探测 ,让Kubernetes知道您的应用程序何时运行正常。 Kubelet Node代理可以使用3种不同的方法在运行Pod上执行这些探测:

  • HTTP:Kubelet探针对端点执行HTTP GET请求(如/health ),如果响应状态在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每2秒探测一次/health

要了解有关活体和准备情况探测的更多信息,请参阅Kubernetes文档

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

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

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

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

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

这个最小的度量标准应该为您提供足够的数据,以便在应用程序性能下降时发出警报。 实现此检测以及上面讨论的运行状况检查将允许您快速检测并从发生故障的应用程序中恢复。

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

除了考虑和设计用于发布遥测数据的功能之外,您还应该规划应用程序如何在基于群集的分布式环境中登录。 理想情况下,您应该删除对本地日志文件和日志目录的硬编码配置引用,而是直接登录到stdout和stderr。 您应该将日志视为连续事件流或时间顺序事件序列。 然后,包含应用程序的容器将捕获此输出流,然后可以将其转发到日志层,如EFK(Elasticsearch,Fluentd和Kibana)。 Kubernetes在设计日志记录体系结构时提供了很大的灵活性,我们将在下面详细介绍。

将管理逻辑构建到API中

一旦您的应用程序在Kubernetes等集群环境中进行了容器化并启动并运行,您就可能无法再运行运行应用程序的容器。 如果您已经实施了足够的运行状况检查,日志记录和监视,则可以快速收到警报并调试生产问题,但是在重新启动和重新部署容器之后采取措施可能会很困难。 对于快速操作和维护修复(如刷新队列或清除缓存),您应该实现适当的API端点,以便您可以执行这些操作,而无需重新启动容器或exec运行容器并执行一系列命令。 应将容器视为不可变对象,并应在生产环境中避免手动管理。 如果必须执行一次性管理任务(如清除缓存),则应通过API公开此功能。

概要

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

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

容纳您的应用程序

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

明确声明依赖关系

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

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

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

保持图像尺寸小

在部署和提取容器映像时,大型映像会显着减慢速度并增加带宽成本。 将最少的工具和应用程序文件打包到图像中可以带来以下好处:

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

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

  • 使用像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 and Secrets部分

将图像发布到注册表

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

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

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

实现构建管道

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

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

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

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

实施容器记录和监视

使用容器时,考虑将用于管理和存储所有正在运行和已停止的容器的日志的日志记录基础结构非常重要。 您可以使用多个容器级别模式进行日志记录,还可以使用多个Kubernetes级别模式。

在Kubernetes中,默认情况下,容器使用json-file Docker 日志记录驱动程序 ,该驱动程序捕获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和内存使用等标准指标,并通过Remote REST API公开更多指标。 此外,开源工具cAdvisor (默认情况下安装在Kubernetes Nodes上)提供了更高级的功能,如历史度量标准收集,度量标准数据导出以及用于对数据进行排序的有用Web UI。

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

概要

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

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

在Kubernetes上部署

此时,您已经将应用程序和实现的逻辑容器化,以最大化其在Cloud Native环境中的可移植性和可观察性。 我们现在将探索Kubernetes功能,这些功能提供了用于在Kubernetes集群中管理和扩展应用程序的简单界面。

编写部署和Pod配置文件

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

Pod通常使用Deployments推出,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

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

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

配置Pod存储

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

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

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卷挂载到nginx-web容器中的/usr/share/nginx/htmlnginx-sync中的/web-data容器。 我们还定义了一个名为emptyDir类型的nginx-web emptyDir

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

卷的生命周期与Pod的生命周期相关联,但与容器的生命周期无关 如果Pod中的容器死亡,则Volume仍然存在,并且新启动的容器将能够装载相同的卷并访问其数据。 当Pod重新启动或死亡时,其卷也会崩溃,但如果卷包含云块存储,则只需卸载未来Pod可访问的数据。

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

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

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

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

使用Kubernetes注入配置数据

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

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

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

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

ConfigMaps和Secrets

ConfigMaps允许您将配置数据保存为随后在Pod和Deployment配置文件中引用的对象,以便您可以避免硬编码配置数据并在Pod和部署中重复使用它。

这是一个例子,使用上面的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.7.9
        ports:
        - containerPort: 80
        env:
        - name: HOSTNAME
          valueFrom:
            configMapKeyRef:
              name: hostname
              key: HOSTNAME
...

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

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

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

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

创建服务

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

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

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

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

Note that creating a Service of type LoadBalancer for each Deployment running in your cluster will create a new cloud load balancer for each Service, which can become costly. To manage routing external requests to multiple services using a single load balancer, you can use an Ingress Controller. Ingress Controllers are beyond the scope of this article, but to learn more about them you can consult the Kubernetes documentation . A popular simple Ingress Controller is the NGINX Ingress Controller .

Here's a simple Service configuration file for the Flask example used in the Pods and Deployments section of this guide:

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

Here we choose to expose the flask-app Deployment using this flask-svc Service. We create a cloud load balancer to route traffic from load balancer port 80 to exposed container port 8080 .

To learn more about Kubernetes Services, consult the Services section of the Kubernetes docs.

Logging and Monitoring

Parsing through individual container and Pod logs using kubectl logs and docker logs can get tedious as the number of running applications grows. To help you debug application or cluster issues, you should implement centralized logging. At a high level, this consists of agents running on all the worker nodes that process Pod log files and streams, enrich them with metadata, and forward the logs off to a backend like Elasticsearch . From there, log data can be visualized, filtered, and organized using a visualization tool like Kibana .

In the container-level logging section, we discussed the recommended Kubernetes approach of having applications in containers log to the stdout/stderr streams. We also briefly discussed logging sidecar containers that can grant you more flexibility when logging from your application. You could also run logging agents directly in your Pods that capture local log data and forward them directly to your logging backend. Each approach has its pros and cons, and resource utilization tradeoffs (for example, running a logging agent container inside of each Pod can become resource-intensive and quickly overwhelm your logging backend). To learn more about different logging architectures and their tradeoffs, consult the Kubernetes documentation .

In a standard setup, each Node runs a logging agent like Filebeat or Fluentd that picks up container logs created by Kubernetes. Recall that Kubernetes creates JSON log files for containers on the Node (in most installations these can be found at /var/lib/docker/containers/ ). These should be rotated using a tool like logrotate. The Node logging agent should be run as a DaemonSet Controller , a type of Kubernetes Workload that ensures that every Node runs a copy of the DaemonSet Pod. In this case the Pod would contain the logging agent and its configuration, which processes logs from files and directories mounted into the logging DaemonSet Pod.

Similar to the bottleneck in using kubectl logs to debug container issues, eventually you may need to consider a more robust option than simply using kubectl top and the Kubernetes Dashboard to monitor Pod resource usage on your cluster. Cluster and application-level monitoring can be set up using the Prometheus monitoring system and time-series database, and Grafana metrics dashboard. Prometheus works using a "pull" model, which scrapes HTTP endpoints (like /metrics/cadvisor on the Nodes, or the /metrics application REST API endpoints) periodically for metric data, which it then processes and stores. This data can then be analyzed and visualized using Grafana dashboard. Prometheus and Grafana can be launched into a Kubernetes cluster like any other Deployment and Service.

For added resiliency, you may wish to run your logging and monitoring infrastructure on a separate Kubernetes cluster, or using external logging and metrics services.

结论

Migrating and modernizing an application so that it can efficiently run in a Kubernetes cluster often involves non-trivial amounts of planning and architecting of software and infrastructure changes. Once implemented, these changes allow service owners to continuously deploy new versions of their apps and easily scale them as necessary, with minimal amounts of manual intervention. Steps like externalizing configuration from your app, setting up proper logging and metrics publishing, and configuring health checks allow you to fully take advantage of the Cloud Native paradigm that Kubernetes has been designed around. By building portable containers and managing them using Kubernetes objects like Deployments and Services, you can fully use your available compute infrastructure and development resources.


分享按钮