將應用程序遷移到 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 因素應用程式。 然而,這(將應用程式引入無狀態形式)不可避免地意味著需要更改程式碼,因此在業務的功能/需求與實現和維護所選路徑的前景之間找到平衡非常重要。

聚苯乙烯

另請閱讀我們的博客:

來源: www.habr.com

添加評論