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



使用Kubernetes构建CI / CD流程时,有时会出现新基础架构的要求与转移到该基础架构的应用程序不兼容的问题。 特别是,在应用程序组装阶段,获取一张将在所有项目环境和群集中使用的映像非常重要。 Google认为此原则是正确进行容器管理的基础(我们的技术代表已经多次说明了这一点)。

但是,当在站点代码中使用现成的框架时,对于任何情况,您都不会感到惊讶,因为使用该框架会对其进一步的操作施加限制。 而且,如果在“正常环境”中易于处理,则在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的容器访问站点时,将显示资产。 但是,在同一容器中有两个容器的情况下,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发送新的请求。 但是,即使在此之后,指向旧静态变量的链接也会过时,这将导致显示静态变量时出错。

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

  • 部署扩展到两个副本。
  • 当您首次使用一个副本访问站点时,就创建了资产。
  • 在某个时候,Ingress决定(为了平衡负载)发送第二个副本的请求,而这些资产尚不存在。 也许它们不再存在,因为我们使用RollingUpdate并且当前正在进行部署。

通常,结果再次是错误。

为了不丢失旧资产,您可以将emptyDir更改为hostPath ,以物理方式将静态hostPath添加到群集节点。 这种方法很不好,因为我们实际上必须使用我们的应用程序绑定到特定的群集节点 ,因为-在移至其他节点的情况下-该目录将不包含必需的文件。 或者,需要节点之间目录的某些后台同步。

有什么解决方案?

  1. 如果硬件和资源允许,则可以使用cephfs来组织一个可访问的目录,以满足静态对象的需求。 官方文档建议使用SSD,至少三重复制以及群集节点之间的稳固的“厚”连接。
  2. 要求较低的选择是组织NFS服务器。 但是,然后您需要考虑Web服务器处理请求的响应时间可能增加,并且容错能力还有很多不足之处。 失败的后果是灾难性的:在洛杉矶负载冲向天空的猛烈冲击下,安装的丢失将群集毁坏并致死。

除其他事项外,对于用于创建永久性存储的所有选项,将需要对在一定时间内累积过时文件集进行后台清理 。 在使用PHP的容器之前,您可以通过缓存nginx来放置DaemonSet ,它将在有限的时间内存储资产的副本。 可以使用proxy_cache轻松配置此行为,其存储深度以天或GB磁盘空间为单位。

将该方法与上面提到的分布式文件系统相结合,提供了广阔的想象空间,仅限制了实施和支持该方法的人员的预算和技术潜力。 根据经验,我们说系统越简单,其工作越稳定。 随着这些层的增加,维护基础架构变得更加困难,同时,在发生任何故障的情况下,用于诊断和恢复的时间也会增加。

推荐建议


如果建议的存储选项的实施对您来说似乎也不合理(复杂,昂贵……),那么您应该从另一侧看情况。 即,通过链接到图像中的某些静态数据结构,深入研究项目的体系结构并消除代码中的问题,在图像组装阶段提供内容的明确定义或“预热”和/或预编译资产的过程。 因此,对于正在运行的应用程序的所有环境和副本,我们可以获得绝对可预测的行为和相同的文件集。

如果我们返回有关Yii框架的特定示例并且不深入研究其结构(这不是本文的目的),则只需指出两种流行的方法即可:

  1. 修改组装图像的过程,以便将资产放置在可预测的位置。 因此,在yii2-static-assets之类的扩展中提供/实现。
  2. 定义资产目录的特定哈希值,例如,如本演示文稿中所述 (从幻灯片35开始)。 顺便说一下,报告的作者最终(并非没有理由!)建议在构建服务器上组装资产后,将其上载到中央存储库(如S3),并在其前面放置CDN。

可下载的文件


将应用程序转移到Kubernetes集群时肯定会触发的另一种情况是在文件系统中存储用户文件。 例如,我们又有了一个PHP应用程序,该应用程序通过上传表单接受文件,在处理过程中对它们进行处理,然后将其退还。

这些文件在Kubernetes现实中应放置的位置应为所有应用程序副本所共有。 根据应用程序的复杂性和组织这些文件的持久性的需要,上述位置可能是上述共享设备的选项,但正如我们所看到的,它们有其缺点。

推荐建议


一种解决方案是使用与S3兼容的存储 (即使使用诸如minio之类的自托管类别)。 要过渡到S3,需要在代码级别进行更改,并且我们已经编写了如何在前端返回内容。

定制会议


另外,值得注意的是用户会话存储的组织。 通常,这些文件也是磁盘上的文件,在Kubernetes的上下文中,如果用户的请求落入另一个容器中,则会导致来自用户的持续授权请求。

通过在入口上包括stickySessions (所有流行的入口控制器均支持此功能-有关更多详细信息,请参见我们的评论来解决部分问题,以便将用户绑定到应用程序的特定容器:

 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因子App改进应用程序的体系结构。 但是,这(使应用程序变成无状态形式)不可避免地意味着需要对代码进行更改,在这里重要的是找到业务的功能/需求与实现和维护所选路径的前景之间的平衡。

聚苯乙烯


另请参阅我们的博客:

Source: https://habr.com/ru/post/zh-CN471582/


All Articles