将应用程序迁移到 Kubernetes 时的本地文件

将应用程序迁移到 Kubernetes 时的本地文件

使用 Kubernetes 构建 CI/CD 流程时,有时会出现新基础设施的需求与转移到其中的应用程序之间不兼容的问题。 特别是,在应用程序构建阶段,重要的是获得 将使用的图像 所有 项目环境和集群。 这个原则是正确的基础 根据谷歌 容器管理(不止一次关于这个 говорил 和我们的技术部门)。

但是,您不会在网站代码使用现成框架的情况下看到任何人,该框架的使用对其进一步使用施加了限制。 虽然在“正常环境”中这很容易处理,但在 Kubernetes 中这种行为可能会成为一个问题,尤其是当您第一次遇到它时。 虽然富有创造力的头脑可以想出乍一看似乎显而易见甚至不错的基础设施解决方案……但重要的是要记住,大多数情况可以而且应该 从架构上解决.

让我们看看流行的存储文件的解决方案,这些解决方案在操作集群时可能会导致不愉快的后果,并指出更正确的路径。

静态存储

为了进行说明,请考虑一个 Web 应用程序,该应用程序使用某种静态生成器来获取一组图像、样式和其他内容。 例如,Yii PHP 框架有一个内置的资源管理器,可以生成唯一的目录名称。 因此,输出是静态站点的一组路径,这些路径显然彼此不相交(这样做有几个原因 - 例如,当多个组件使用相同资源时消除重复)。 因此,第一次访问 Web 资源模块时,系统会立即生成静态文件(实际上,通常是符号链接,但稍后会详细介绍),并使用此部署特有的公共根目录进行布局:

  • webroot/assets/2072c2df/css/…
  • webroot/assets/2072c2df/images/…
  • webroot/assets/2072c2df/js/…

这对于集群来说意味着什么?

最简单的例子

让我们举一个相当常见的情况,当 PHP 前面有 nginx 来分发静态数据并处理简单请求时。 最简单的方法—— 部署 有两个容器:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: site
spec:
  selector:
    matchLabels:
      component: backend
  template:
    metadata:
      labels:
        component: backend
    spec:
      volumes:
        - name: nginx-config
          configMap:
            name: nginx-configmap
      containers:
      - name: php
        image: own-image-with-php-backend:v1.0
        command: ["/usr/local/sbin/php-fpm","-F"]
        workingDir: /var/www
      - name: nginx
        image: nginx:1.16.0
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf

简单来说,nginx 配置可归结为以下内容:

apiVersion: v1
kind: ConfigMap
metadata:
  name: "nginx-configmap"
data:
  nginx.conf: |
    server {
        listen 80;
        server_name _;
        charset utf-8;
        root  /var/www;

        access_log /dev/stdout;
        error_log /dev/stderr;

        location / {
            index index.php;
            try_files $uri $uri/ /index.php?$args;
        }

        location ~ .php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            include fastcgi_params;
        }
    }

当您第一次访问该站点时,资产会出现在 PHP 容器中。 但如果一个 pod 中有两个容器,nginx 对这些静态文件一无所知,而应该将这些静态文件(根据配置)提供给它们。 因此,客户端对 CSS 和 JS 文件的所有请求都会看到 404 错误,最简单的解决方案是为容器组织一个公共目录。 原始选项 - 一般 emptyDir:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: site
spec:
  selector:
    matchLabels:
      component: backend
  template:
    metadata:
      labels:
        component: backend
    spec:
      volumes:
        - name: assets
          emptyDir: {}
        - name: nginx-config
          configMap:
            name: nginx-configmap
      containers:
      - name: php
        image: own-image-with-php-backend:v1.0
        command: ["/usr/local/sbin/php-fpm","-F"]
        workingDir: /var/www
        volumeMounts:
        - name: assets
          mountPath: /var/www/assets
      - name: nginx
        image: nginx:1.16.0
        command: ["/usr/sbin/nginx", "-g", "daemon off;"]
        volumeMounts:
        - name: assets
          mountPath: /var/www/assets
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: nginx.conf

现在容器中生成的静态文件可以由 nginx 正确提供。 但让我提醒您,这是一个原始的解决方案,这意味着它远非理想,并且有其自身的细微差别和缺点,这些将在下面讨论。

更先进的存储

现在想象这样一种情况:用户访问了该站点,加载了具有容器中可用样式的页面,当他阅读此页面时,我们重新部署了容器。 资产目录已变空,需要向 PHP 发出请求才能开始生成新资产目录。 然而,即使在此之后,旧静态数据的链接也将不再相关,这将导致显示静态数据时出现错误。

此外,我们很可能有一个或多或少加载的项目,这意味着应用程序的一份副本是不够的:

  • 让我们扩大规模 部署 最多两个副本。
  • 第一次访问该站点时,资产是在一个副本中创建的。
  • 在某个时刻,入口决定(出于负载平衡的目的)向第二个副本发送请求,而这些资产尚不存在。 或者也许它们已经不存在了,因为我们使用了 RollingUpdate 目前我们正在进行部署。

一般来说,结果又是错误。

为了避免丢失旧资产,您可以更改 emptyDirhostPath,将静态物理添加到集群节点。 这种方法很糟糕,因为我们实际上必须 绑定到特定集群节点 您的应用程序,因为 - 如果移动到其他节点 - 该目录将不包含必要的文件。 或者需要节点之间某种后台目录同步。

解决办法有哪些?

  1. 如果硬件和资源允许,可以使用 头孢夫斯 组织一个同样可访问的目录以满足静态需求。 官方文档 建议使用SSD驱动器、至少三重复制以及集群节点之间稳定的“厚”连接。
  2. 一个要求较低的选择是组织 NFS 服务器。 但是,您需要考虑到 Web 服务器处理请求的响应时间可能会增加,并且容错能力将有很多不足之处。 失败的后果是灾难性的:挂载的丢失注定了集群在洛杉矶负载冲上天空的压力下死亡。

除此之外,创建持久存储的所有选项都需要 背景清理 在一段时间内积累的过时文件集。 在带有 PHP 的容器前面,您可以放置 守护程序集 来自缓存 nginx,它将在有限的时间内存储资产的副本。 使用以下命令可以轻松配置此行为 proxy_cache 存储深度以天或千兆字节的磁盘空间为单位。

将此方法与上述分布式文件系统相结合提供了巨大的想象空间,仅受实施和支持它的人的预算和技术潜力的限制。 根据经验,我们可以说系统越简单,运行起来就越稳定。 添加这些层后,维护基础设施变得更加困难,同时诊断故障和从故障中恢复所花费的时间也会增加。

建议

如果建议的存储选项的实施对您来说似乎也不合理(复杂、昂贵......),那么值得从另一方面考虑情况。 即,深入研究项目架构并 修复代码中的问题,与映像中的某些静态数据结构相关联,是映像组装阶段“预热”和/或预编译资产的内容或过程的明确定义。 这样,我们就可以为所有环境和正在运行的应用程序的副本获得绝对可预测的行为和相同的文件集。

如果我们回到 Yii 框架的具体示例,而不深入研究其结构(这不是本文的目的),那么指出两种流行的方法就足够了:

  1. 更改映像构建过程以将资产放置在可预测的位置。 这是在扩展中建议/实现的,例如 yii2-静态资产.
  2. 为资产目录定义特定的哈希值,如中所述。 这个演示文稿 (从第 35 幻灯片开始)。 顺便说一句,该报告的作者最终(并非没有理由!)建议在构建服务器上组装资产后,将它们上传到中央存储(如 S3),在其前面放置 CDN。

下载

将应用程序迁移到 Kubernetes 集群时肯定会发挥作用的另一种情况是将用户文件存储在文件系统中。 例如,我们再次有一个 PHP 应用程序,它通过上传表单接受文件,在操作期间对它们执行某些操作,然后将它们发送回来。

在 Kubernetes 中,这些文件的放置位置对于应用程序的所有副本都应该是通用的。 根据应用程序的复杂性和组织这些文件持久性的需要,上述共享设备选项可能就是这样的地方,但是,正如我们所见,它们有其缺点。

建议

一种解决方案是 使用 S3 兼容存储 (即使它是某种自托管类别,例如 minio)。 切换到 S3 需要进行更改 在代码级别,以及内容如何在前端交付,我们已经了解了 писали.

用户会话

另外,值得注意的是用户会话的存储组织。 通常这些也是磁盘上的文件,在 Kubernetes 的上下文中,如果用户的请求最终出现在另一个容器中,这将导致用户不断发出授权请求。

通过开启可以部分解决问题 stickySessions 进入时 (所有流行的入口控制器都支持该功能 - 有关更多详细信息,请参阅 我们的评论)将用户绑定到应用程序的特定 pod:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: nginx-test
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "route"
    nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"

spec:
  rules:
  - host: stickyingress.example.com
    http:
      paths:
      - backend:
          serviceName: http-svc
          servicePort: 80
        path: /

但这并不能消除重复部署的问题。

建议

更正确的方法是将应用程序转移到 在 memcached、Redis 和类似解决方案中存储会话 - 一般来说,完全放弃文件选项。

结论

文中讨论的基础设施解决方案仅以临时“拐杖”的形式才值得使用(作为解决方法,这在英语中听起来更漂亮)。 它们可能与将应用程序迁移到 Kubernetes 的第一阶段相关,但不应扎根。

一般推荐的路径是摆脱它们,以便根据许多人已经熟知的内容对应用程序进行架构修改 12 因素应用程序。 然而,这(将应用程序引入无状态形式)不可避免地意味着需要更改代码,因此在业务的功能/需求与实现和维护所选路径的前景之间找到平衡非常重要。

PS

另请阅读我们的博客:

来源: habr.com

添加评论