在 Kubernetes 中開發應用程式的要求

今天我計劃談談如何編寫應用程式以及您的應用程式在 Kubernetes 中良好運行的要求是什麼。 這樣應用程式就不會出現任何令人頭疼的問題,這樣您就不必圍繞它發明和構建任何「缺陷」——一切都按照 Kubernetes 本身的預期運行。

本次講座是“Kubernetes 上的 Slurm 夜校」 您可以查看夜校公開理論講座 在 YouTube 上,分組到播放清單中。 對於那些喜歡文字而不是影片的人,我們準備了這篇文章。

我的名字是 Pavel Selivanov,目前我是 Mail.ru Cloud Solutions 的首席 DevOps 工程師,我們製作雲,我們製作管理 kubernetes 等等。 我現在的任務包括協助開發、推出這些雲端、推出我們編寫的應用程式以及直接開發我們為用戶提供的工具。

在 Kubernetes 中開發應用程式的要求

我想過去大概三年來我一直在從事 DevOps。 但是,原則上,我從事 DevOps 所做的事情可能已經有五年了。 在此之前,我主要從事管理工作。 我很久以前就開始使用 Kubernetes——自從我開始使用它以來大概已經過去了四年了。

總的來說,我是在 Kubernetes 1.3 版本(可能是 1.2 版本)時開始的——當時它還處於起步階段。 現在它已不再處於起步階段 - 很明顯,市場對希望能夠做 Kubernetes 的工程師有巨大的需求。 而企業對這樣的人的需求是非常高的。 所以,事實上,就出現了這個講座。

如果按照我要講的內容的計劃來講的話,看起來是這樣的,括號裡寫著(TL;DR)——「太長了;太長了」。 不要閱讀」。 我今天的演講將包含無窮無盡的清單。

在 Kubernetes 中開發應用程式的要求

事實上,我自己並不喜歡這樣的演示,但這是一個這樣的主題,當我準備這個演示時,我根本沒有真正弄清楚如何以不同的方式組織這些資訊。

因為總的來說,這些資訊是“ctrl+c、ctrl+v”,除其他外,來自我們的Wiki 中的DevOps 部分,我們在其中為開發人員編寫了要求:“夥計們,以便我們在以下位置啟動您的應用程式: Kubernetes,它應該是這樣的。”

這就是為什麼簡報的清單如此之大。 對不起。 我會盡力講述盡可能多的內容,以便盡可能不無聊。

我們現在要看什麼:

  • 首先是日誌(應用程式日誌?),在 Kubernetes 中如何處理它們,如何處理它們,它們應該是什麼;
  • 如何處理 Kubernetes 中的配置,為 Kubernetes 配置應用程式的最佳和最差方法是什麼;
  • 讓我們談談可訪問性檢查的一般含義以及它們應該是什麼樣子;
  • 我們來談談什麼是優雅關閉;
  • 我們再談談資源;
  • 讓我們再討論一下資料儲存的話題;
  • 最後我會告訴你這個神秘的雲端原生應用程式是什麼。 Cloudnativeness,作為該術語的形容詞。

日誌

我建議從日誌開始 - 這些日誌需要推送到 Kubernetes 中的位置。 現在您已經在 Kubernetes 中啟動了一個應用程式。 根據經典,以前的應用程式總是將日誌寫入檔案中的某個位置。 不良應用程式將日誌寫入啟動該應用程式的開發人員主目錄中的檔案中。 好的應用程式將日誌寫入某個位置的文件 /var/log.

在 Kubernetes 中開發應用程式的要求

因此,進一步,優秀的管理員在他們的基礎設施中配置了一些東西,這些日誌可以輪換- 相同的rsyslog,它查看這些日誌,當它們發生問題時,有很多日誌,它創建備份副本,將日誌放在那裡,刪除舊文件,超過一週、六個月等等。 理論上,我們應該做出規定,以便僅僅因為應用程式寫入日誌,生產伺服器(戰鬥伺服器?)上的空間就不會耗盡。 因此,整個生產並沒有因為原木而停止。

當我們進入 Kubernetes 世界並在那裡運行相同的東西時,您首先要注意的是,人們在將日誌寫入檔案時會繼續寫入它們。

事實證明,如果我們談論 Kubernetes,從 docker 容器中寫入日誌的正確位置就是將它們從應用程式寫入所謂的 Stdout/Stderr,即作業系統的標準輸出流,標準錯誤輸出。 這是原則上在 Docker、特別是在 Kubernetes 中放置日誌的最正確、最簡單、最合乎邏輯的方式。 因為如果您的應用程式將日誌寫入 Stdout/Stderr,則由 Docker 和 Kubernetes 外掛程式決定如何處理這些日誌。 Docker 預設會以 JSON 格式建置其特殊檔案。

那麼問題來了,你接下來要如何處理這些日誌呢? 最簡單的方法很明確,我們有能力做到 kubectl logs 並查看這些“pod”的日誌。 但是,這可能不是一個很好的選擇 - 需要對日誌執行其他操作。

現在,既然我們談到了日誌這個話題,那麼我們就同時談談日誌應該是什麼樣子吧。 也就是說,這並不直接適用於 Kubernetes,但是當我們開始考慮如何處理日誌時,最好也考慮一下這一點。

我們需要某種工具,以友好的方式,將我們的碼頭工人放入其文件中的這些日誌並將它們發送到某個地方。 總的來說,我們通常會在 Kubernetes 中以 DaemonSet 的形式啟動某種代理程式——日誌收集器,它只是告訴 Docker 收集的日誌位於哪裡。 這個收集代理只是簡單地獲取它們,甚至可能以某種方式一路解析它們,或許用一些額外的元資訊來豐富它們,並最終將它們發送到某個地方存儲。 那裡已經有可能出現變化。 最常見的可能是 Elasticsearch,您可以在其中儲存日誌,並且可以輕鬆地從那裡檢索它們。 然後,使用請求,例如使用 Kibana,基於它們建立圖表,基於它們建立警報,等等。

我想再次重複一遍,最重要的想法是,在 Docker 內部,特別是在 Kubernetes 內部,將日誌儲存在檔案中是一個非常糟糕的主意。

因為首先,很難將容器內的日誌放入檔案中。 您必須先進入容器,在那裡執行,然後查看日誌。 下一點是,如果檔案中有日誌,那麼容器通常具有極簡環境,並且不存在正常處理日誌所需的實用程式。 埋葬它們,查看它們,在文字編輯器中打開它們。 下一刻,當我們將日誌記錄在容器內的檔案中時,如果您刪除該容器,您知道,日誌將隨之消失。 因此,容器的任何重新啟動都意味著不再有日誌。 再次,糟糕的選擇。

最後一點是,在容器內通常有您的應用程序,僅此而已 - 它通常是唯一正在運行的進程。 根本沒有談論任何會隨日誌輪轉檔案的過程。 一旦日誌開始寫入文件,這意味著,對不起,我們將開始失去生產伺服器。 因為,首先,它們很難找到,沒有人追蹤它們,而且沒有人控制它們 - 因此,檔案會無休止地增長,直到伺服器上的空間耗盡。 因此,我再次強調,將 Docker(尤其是 Kubernetes)登入檔案是一個壞主意。

下一點,在這裡我想再次討論這一點 - 由於我們正在討論日誌主題,因此最好討論一下日誌應該是什麼樣子,以便於使用它們。 正如我所說,該主題與 Kubernetes 沒有直接關係,但與 DevOps 主題密切相關。 關於開發文化和這兩個不同部門(Dev 和 Ops)之間的友誼的話題,讓每個人都感到舒適。

這意味著理想情況下,今天日誌應該以 JSON 格式編寫。 如果您自己有一些難以理解的應用程序,因為您插入了某種打印或類似的內容,所以它以難以理解的格式寫入日誌,那麼是時候谷歌搜索某種框架,某種允許您實現正常日誌記錄的包裝器; 在那裡啟用 JSON 中的日誌記錄參數,因為 JSON 是一種簡單的格式,解析它很簡單。

如果您的 JSON 無法按照某些標準工作,沒有人知道是什麼,那麼至少以可解析的格式寫入日誌。 相反,在這裡,值得考慮的事實是,例如,如果您正在運行一堆容器或僅使用 nginx 運行進程,並且每個容器都有自己的日誌記錄設置,那麼您可能會覺得非常不方便解析它們。 因為對於每個新的 nginx 實例,您都需要編寫自己的解析器,因為它們寫入日誌的方式不同。 同樣,值得考慮的是確保所有這些 nginx 實例具有相同的日誌配置並絕對統一地寫入所有日誌。 這同樣適用於所有應用程式。

最後,我還想火上加油,理想情況下應該避免多行格式日誌。 事情是這樣的,如果您曾經使用過日誌收集器,那麼您很可能已經看到了他們向您承諾的內容,即他們可以處理多行日誌,知道如何收集它們,等等。 事實上,在我看來,現在沒有一個收集器能夠正常、完整、無錯誤地收集多行日誌。 以人性化的方式,方便且無錯誤。

在 Kubernetes 中開發應用程式的要求

但堆疊追蹤始終是多行日誌以及如何避免它們。 這裡的問題是,日誌是一個事件的記錄,而stactrace其實並不是日誌。 如果我們收集日誌並將它們放在Elasticsearch 中的某個位置,然後從中繪製圖表,在您的網站上建立一些使用者活動報告,那麼當您獲得堆疊追蹤時,這表示發生了意外的情況。您的應用程式中出現了未處理的情況。 自動將堆疊追蹤上傳到可以追蹤它們的系統中是有意義的。

這是專門用於處理堆疊追蹤的軟體(同一個 Sentry)。 它可以立即建立自動化任務,將其指派給某人,在 stacttraces 發生時發出警報,以一種類型對這些 stacttraces 進行分組,等等。 原則上,當我們談論日誌時談論 stactrace 並沒有多大意義,因為它們畢竟是具有不同用途的不同事物。

組態

接下來我們討論 Kubernetes 中的配置:如何使用它以及應該如何配置 Kubernetes 內的應用程式。 一般來說,我通常會說Docker不是關於容器的。 每個人都知道 Docker 是關於容器的,即使是那些沒有太多使用過 Docker 的人也是如此。 我再說一遍,Docker 與容器無關。

在我看來,Docker 是關於標準的。 幾乎所有事情都有標準:建立應用程式的標準、安裝應用程式的標準。

在 Kubernetes 中開發應用程式的要求

這個東西——我們以前用過它,隨著容器的出現它變得特別流行——這個東西叫做ENV(環境)變量,也就是你作業系統中的環境變量。 這通常是配置應用程式的理想方法,因為如果您有JAVA、Python、Go、Perl(上帝保佑)的應用程序,並且它們都可以讀取資料庫主機、資料庫用戶、資料庫密碼變量,那麼它是理想的。 您以相同的方式在資料庫計劃中配置了四種不同語言的應用程式。 沒有更多不同的配置。

一切都可以使用 ENV 變數進行配置。 當我們談論 Kubernetes 時,有一個很好的方法可以在 Deployment 中聲明 ENV 變數。 因此,如果我們談論的是秘密數據,那麼我們可以立即將 ENV 變量中的秘密數據(資料庫的密碼等)推送到秘密中,創建秘密集群並在 Deployment 中的 ENV 描述中指出我們不是直接聲明該變量的值,以及該資料庫密碼變數的值將從金鑰中讀取。 這是標準的 Kubernetes 行為。 這是配置您的應用程式的最理想的選擇。 僅在程式碼級別,這同樣適用於開發人員。 如果您是 DevOps,您可以問:「夥計們,請教您的應用程式讀取環境變數。 我們都會幸福的。”

如果公司中的每個人都讀取相同的命名環境變量,那就太好了。 這樣就不會出現有些人在等待 postgres 資料庫、其他人正在等待資料庫名稱、其他人正在等待其他東西、其他人正在等待某種 dbn 的情況,因此,相應地,存在統一性。

當您有太多環境變數以至於您只需打開 Deployment 時就會出現問題 - 並且有 XNUMX 行環境變數。 在這種情況下,你的環境變數已經超出了你的範圍——你不再需要折磨自己了。 在這種情況下,開始使用配置是有意義的。 也就是說,訓練您的應用程式使用配置。

唯一的問題是配置不是你想像的那樣。 Config.pi 不是一個使用起來很方便的設定。 或以您自己的格式進行一些配置,或者有天賦 - 這也不是我所說的配置。

我所說的是可用格式的配置,也就是說,迄今為止最受歡迎的標準是 .yaml 標準。 很清楚如何讀取它,它是人類可讀的,很清楚如何從應用程式中讀取它。

因此,除了 YAML 之外,您還可以使用 JSON,從那裡讀取應用程式設定方面,解析與 YAML 一樣方便。 人們的閱讀顯然更不方便。 您可以嘗試 a la ini 格式。 從人類的角度來看,它讀起來很方便,但自動處理它可能會很不方便,因為如果你想產生自己的配置,ini 格式可能已經不方便生成了。

但無論如何,無論你選擇什麼格式,關鍵是從 Kubernetes 的角度來看,它非常方便。 您可以將整個設定放入 Kubernetes 的 ConfigMap 中。 然後取得此 configmap 並要求將其安裝在 pod 內的某個特定目錄中,而您的應用程式將從該 configmap 中讀取配置,就好像它只是一個檔案一樣。 事實上,當您的應用程式中有很多配置選項時,最好這樣做。 或只是某種複雜的結構,有嵌套。

如果您有一個 configmap,那麼您可以很好地教導您的應用程序,例如,自動追蹤掛載 configmap 的檔案中的更改,並在配置更改時自動重新載入您的應用程式。 這通常是一個理想的選擇。

再說一次,我已經討論過這一點 - 秘密資訊不在 configmap 中,秘密資訊不在變數中,秘密資訊不在秘密中。 從那裡,將這些秘密訊息與外交聯繫起來。 通常我們將 Kubernetes 物件、部署、配置映射、服務的所有描述儲存在 git 中。 因此,將資料庫的密碼放在 git 中,即使它是您在公司內部擁有的 git,也是一個壞主意。 因為,至少,git 會記住所有內容,並且簡單地從那裡刪除密碼並不那麼容易。

健康檢查

下一點是健康檢查這個東西。 一般來說,健康檢查只是檢查您的應用程式是否正常運作。 同時,我們最常談論的是某些Web應用程序,相應地,從健康檢查的角度來看(最好不要在這裡翻譯和進一步翻譯),這將是一些特殊的URL,它們將其處理為標準,他們通常這樣做 /health.

因此,當訪問此 URL 時,我們的應用程式會說「是的,好吧,我一切都很好,200」或「不,我一切都不好,大約 500」。 因此,如果我們的應用程式不是http,不是web應用程序,我們現在談論的是某種守護進程,我們可以弄清楚如何進行健康檢查。 也就是說,沒有必要,如果應用程式不是 http,那麼一切都可以在沒有健康檢查的情況下運行,並且不能以任何方式完成。 您可以定期更新文件中的一些信息,您可以為您的守護程序提供一些特殊的命令,例如, daemon status,它會說“是的,一切都很好,守護進程正在工作,它還活著。”

它是做什麼用的? 第一個也是最明顯的事情可能是為什麼需要進行健康檢查 - 了解應用程式是否正在運行。 我的意思是,這很愚蠢,當它現在啟動時,看起來它正在工作,所以你可以確定它正在工作。 事實證明,應用程式正在運行,容器正在運行,實例正在工作,一切都很好 - 然後用戶已經切斷了所有技術支援的電話號碼,並說「你在做什麼...,你睡著了,什麼都沒有。”

健康檢查就是從使用者的角度來看它是否有效的一種方式。 方法之一。 讓我們這樣說吧。 從Kubernetes的角度來看,這也是理解應用程式何時啟動的一種方式,因為我們理解容器啟動、創建和啟動的時間與應用程式直接在這個容器中啟動的時間是有區別的。 因為如果我們使用一些普通的 java 應用程式並嘗試在擴充座中啟動它,那麼四十秒,甚至一分鐘,甚至十分鐘,它都可以正常啟動。 在這種情況下,你至少可以敲它的端口,它不會在那裡應答,也就是說,它還沒有準備好接收流量。

再次,借助健康檢查,借助我們正在轉向這裡的事實,我們可以在 Kubernetes 中了解到,應用程式中不僅出現了容器,而且應用程式本身也已啟動,它已經響應了健康檢查,這意味著我們可以在那裡發送流量。

在 Kubernetes 中開發應用程式的要求

我現在所說的是 Kubernetes 中的就緒/活躍測試;相應地,我們的就緒測試負責平衡應用程式的可用性。 也就是說,如果在應用程式中執行就緒測試,則一切正常,客戶端流量將流向應用程式。 如果未執行就緒測試,則應用程式完全不參與,該特定實例不參與平衡,它會從平衡中刪除,客戶端流量不會流動。 因此,需要在 Kubernetes 內進行 Liveness 測試,以便在應用程式卡住時可以重新啟動。 如果活性測試對於 Kubernetes 中聲明的應用程式不起作用,那麼該應用程式不僅會從平衡中刪除,還會重新啟動。

這裡我想提一下重要的一點:從實際的角度來看,就緒測試通常比活性測試更常用,也更常需要。 也就是說,簡單地不假思索地聲明就緒性和活躍性測試,因為 Kubernetes 可以做到這一點,並且讓我們使用它可以做的一切,這並不是一個好主意。 我會解釋原因。 因為測試中的第二點是,最好在健康檢查中檢查基礎服務。 這意味著,如果您有一個 Web 應用程式提供一些信息,那麼它自然必須從某個地方獲取這些信息。 例如,在資料庫中。 嗯,它將進入此 REST API 的資訊保存到同一個資料庫中。 然後,相應地,如果您的健康檢查響應簡單,就像聯繫的slashhealth,應用程式會說“200,好的,一切都很好”,同時您的應用程式的資料庫無法訪問,並且健康檢查應用程式會說“200,好的,一切都很好” ” - 這是一個糟糕的健康檢查。 這不應該是這樣的。

也就是說,當收到請求時,您的應用程式 /health,它不只是回應“200,好的”,它首先進入資料庫,嘗試連接到它,在那裡做一些非常基本的事情,例如選擇一個,只是檢查資料庫中是否有一個連接資料庫,並且可以查詢資料庫。 如果這一切都成功,那麼答案就是「200,好的」。 如果不成功,則表示出現錯誤,資料庫不可用。

因此,在這方面,我再次回到就緒/活躍測試 - 為什麼你很可能需要就緒測試,但活躍測試是有問題的。 因為如果你完全按照我剛才說的描述健康檢查,那麼結果會發現它在實例部分不可用в или со всех instance例如,在資料庫中。 當您聲明準備就緒測試時,我們的運行狀況檢查開始失敗,因此無法訪問資料庫的所有應用程序,它們只是從平衡狀態中關閉,實際上“掛起”處於被忽略的狀態,並等待其資料庫工作。

如果我們已經聲明了活性測試,那麼想像一下,我們的資料庫已經損壞,並且在 Kubernetes 中,由於活性測試失敗,所有內容的一半都開始重新啟動。 這意味著您需要重新啟動。 這根本不是你想要的,我甚至在實踐中也有過親身經歷。 我們有一個用 JS 編寫的聊天應用程序,並輸入到 Mongo 資料庫中。 問題是,當我開始使用 Kubernetes 時,我們根據 Kubernetes 可以做到的原則描述了測試的就緒性和活躍性,所以我們將使用它。 因此,在某個時候 Mongo 變得有點“遲鈍”,樣本開始失敗。 因此,根據降雨測試,豆莢開始「死亡」。

正如你所理解的,當他們被「殺死」時,這是一個聊天,也就是說,上面掛著很多來自客戶端的連結。 它們也會被“殺死” - 不,不是客戶端,只是連接 - 不是全部同時被殺死,並且由於它們不是同時被殺死,有的早,有的晚,所以它們不會同時啟動時間。 加上標準隨機,我們無法以毫秒精度預測應用程式每次的啟動時間,因此他們一次只執行一個實例。 一個資訊點上升,被添加到平衡中,所有客戶端都到那裡,它無法承受這樣的負載,因為它是孤獨的,而且,粗略地說,有十幾個在那裡工作,然後它就下降了。 下一個站起來,所有的重擔都壓在他身上,他也倒下了。 好吧,這些瀑布還在繼續傾瀉而下。 最後,這個問題是如何解決的——我們只需嚴格停止該應用程式的用戶流量,讓所有實例上升,然後立即啟動所有用戶流量,以便它已經分佈在所有十個實例中。

如果不是宣布了這個活性測試,這將迫使一切重新啟動,應用程式本來可以很好地處理它。 但是平衡的一切對我們來說都是禁用的,因為資料庫無法存取並且所有用戶都「斷線」了。 然後,當這個資料庫變得可用時,一切都包含在平衡中,但應用程式不需要重新啟動,也不需要為此浪費時間和資源。 他們都已經在這裡,準備好交通,所以交通剛剛開放,一切都很好 - 應用程式已就位,一切都繼續工作。

因此,就緒性和活性測試是不同的,而且,理論上你可以做不同的健康檢查,例如一種類型的半徑,一種類型的生活,並檢查不同的東西。 在準備測試期間,檢查您的後端。 例如,在活性測試中,您不會從活性測試通常只是應用程式回應的角度進行檢查(如果它能夠響應)。

因為總的來說,活性測試是我們「陷入困境」的時候。 無限循環已開始或發生其他情況 - 並且不再處理更多請求。 因此,將它們分開並在其中實現不同的邏輯是有意義的。

關於進行測試和健康檢查時需要回答的問題。 這真的很痛。 熟悉這一點的人可能會笑 - 但說真的,我一生中見過在 200% 的情況下回答“XNUMX”的服務。 也就是說,誰才是成功者。 但同時,他們在回覆正文中寫下了「這樣那樣的錯誤」。

也就是說,回應狀態出現在您面前 - 一切都成功。 但同時,您必須解析正文,因為正文會說“抱歉,請求以錯誤結束”,而這就是現實。 我在現實生活中看到過這個。

因此,有些人覺得它不好笑,而有些人覺得它非常痛苦,但仍然值得遵守一個簡單的規則。 在健康檢查中,原則上在使用 Web 應用程式時也是如此。

如果一切順利,則回覆第二百個答案。 原則上,任何百分之二的答案都適合你。 如果您非常熟悉 ragsy 並且知道某些回應狀態與其他回應狀態不同,請使用適當的狀態來回答:204、5、10、15 等等。 如果不太好,那就「二零零」。 如果一切都不順利且健康檢查沒有回應,則回答任意百分之五。 同樣,如果您了解如何回應,以及不同的回應狀態之間有何不同。 如果您不明白,那麼如果出現問題,您可以選擇 502 來回應健康檢查。

這是另外一點,我想回一點關於檢查底層服務。 例如,如果您開始檢查應用程式背後的所有底層服務 - 一般而言的所有內容。 從微服務架構的角度來看,我們有一個「低耦合」的概念——也就是說,當你的服務彼此之間的依賴程度最低時。 如果其中一個失敗,所有其他沒有此功能的其他將繼續工作。 有些功能根本不起作用。 因此,如果你將所有的健康檢查相互聯繫起來,那麼你最終會在基礎設施中出現一個東西掉落,並且因為它掉落,所有服務的所有健康檢查也開始失敗- 並且一般來說有更多的基礎設施可供使用整個微服務架構No. 那裡一切都變黑了。

因此,我想再次重複一遍,您需要檢查底層服務,如果沒有這些服務,您的應用程式在百分之一百的情況下無法完成其工作。 也就是說,如果您有一個 REST API,使用者可以透過它儲存到資料庫或從資料庫檢索,那麼在沒有資料庫的情況下,您就無法保證與您的使用者一起工作,這是合乎邏輯的。

但是,如果您的用戶在您將其從資料庫中取出時,還從另一個後端豐富了一些其他元數據,您在向前端發送響應之前輸入這些元數據- 並且該後端不可用,這意味著您給了您的沒有任何元資料部分的答案。

接下來,我們在啟動應用程式時還遇到一個令人痛苦的問題。

事實上,這不僅適用於 Kubernetes;碰巧的是,某種大規模開發、特別是 DevOps 的文化與 Kubernetes 幾乎同時開始傳播。 因此,總的來說,您需要在沒有 Kubernetes 的情況下優雅地關閉應用程式。 甚至在 Kubernetes 之前,人們就這樣做了,但隨著 Kubernetes 的出現,我們開始集體討論它。

優雅關機

一般來說,什麼是優雅關機以及為什麼需要它? 這是關於當你的應用程式由於某種原因崩潰時,你需要做的 app stop - 或者,例如,您收到來自作業系統的訊號,您的應用程式必須理解它並對其採取措施。 當然,最糟糕的情況是當您的應用程式收到 SIGTERM 並且類似於“SIGTERM,讓我們堅持下去,工作,什麼都不做”。 這是一個徹頭徹尾的糟糕選擇。

在 Kubernetes 中開發應用程式的要求

一個幾乎同樣糟糕的選擇是,當您的應用程式收到SIGTERM 並且就像「他們說segterm,這意味著我們即將結束,我還沒有看到,我不知道任何用戶請求,我不知道什麼樣的「我現在正在處理的請求,他們說 SIGTERM,這意味著我們即將結束” 這也是一個糟糕的選擇。

哪個選項好? 第一點是考慮操作的完成情況。 一個好的選擇是您的伺服器仍然考慮收到 SIGTERM 時它會做什麼。

SIGTERM是一個軟關閉,它是專門設計的,它可以在代碼級別攔截,它可以處理,說現在,等等,我們先完成我們手頭的工作,然後我們就退出。

從 Kubernetes 的角度來看,這就是它的樣子。 當我們對 Kubernetes 叢集中運行的 pod 說“請停止,走開”,或者我們重新啟動,或者 Kubernetes 重新創建 pod 時發生更新時,Kubernetes 會向 pod 發送相同的 SIGTERM 訊息,等待還有,這個就是他等待的時間,也是配置的,文憑裡有一個特殊的參數,叫做Graceful ShutdownTimeout。 正如你所理解的,它不是無緣無故的,我們現在談論它也不是無緣無故的。

在那裡,我們可以具體地說,從向應用程式發送 SIGTERM 到我們了解到應用程式似乎因某些原因而瘋狂或「卡住」並且不會結束,我們需要等待多長時間 - 我們需要發送SIGKILL給它,即努力完成它的工作。 也就是說,相應地,我們有某種守護程序在運行,它處理操作。 我們知道,平均而言,守護程式所執行的操作一次持續時間不會超過 30 秒。 因此,當 SIGTERM 到達時,我們知道我們的守護程序最多可以在 SIGTERM 之後 30 秒完成。 例如,為了以防萬一,我們將其寫為 45 秒,並表示 SIGTERM。 之後我們等待 45 秒。 照理說,在這段時間裡,惡魔應該已經完成了它的工作,結束了自己。 但如果突然不能,則意味著它很可能被卡住了——它不再正常處理我們的請求。 實際上,在 45 秒內,您就可以安全地鎖定他。

事實上,這裡甚至可以考慮兩個方面。 首先,請了解,如果您收到請求,您就開始以某種方式處理該請求,並且沒有向用戶回應,但您收到了 SIGTERM。 對其進行完善並向用戶提供答案是有意義的。 這是這方面的第一點。 這裡的第二點是,如果您編寫自己的應用程序,通常會以這樣的方式建立架構:您收到應用程式的請求,然後開始一些工作,開始從某個地方下載文件,下載資料庫等等。 -那。 一般來說,你的用戶,你的請求會掛起半個小時,等待你回答他——然後,很可能,你需要在架構上工作。 也就是說,只要考慮到常識,如果您的操作很短,那麼忽略 SIGTERM 並修改它是有意義的。 如果您的操作很長,那麼在這種情況下忽略 SIGTERM 是沒有意義的。 重新設計架構以避免如此長的操作是有意義的。 這樣用戶就不會只是閒逛和等待。 我不知道,在那裡做某種 websocket,做反向掛鉤,你的伺服器已經發送到客戶端,其他什麼,但不要強迫用戶掛起半個小時,只是等待會話,直到你回答他。 因為無法預測它可能在哪裡破裂。

當您的應用程式終止時,您應該提供一些適當的退出代碼。 也就是說,如果您的應用程式被要求關閉、停止,並且它能夠正常自行停止,那麼您不需要返回某種退出代碼 1,5,255 等。 我確信,任何不是零程式碼的東西,至少在 Linux 系統中,都被認為是不成功的。 也就是說,在這種情況下,您的應用程式被認為以錯誤結束。 因此,以友好的方式,如果您的應用程式完成且沒有錯誤,您可以在輸出中說 0。 如果您的應用程式因某種原因失敗,您會在輸出中顯示非 0。 您可以使用這些資訊。

還有最後一個選擇。 當您的用戶發送請求並在您處理該請求時掛起半個小時時,情況會很糟糕。 但總的來說,我還想談談從客戶角度來看什麼是值得的。 如果您有行動應用程式、前端等,那並不重要。 有必要考慮到,一般來說,使用者的會話可能會被終止,任何事情都可能發生。 例如,請求可能被發送,處理不足並且沒有回傳回應。 您的前端或行動應用程式(一般來說任何前端,讓我們這樣說)應該考慮到這一點。 如果你使用 websocket,這通常是我經歷過的最痛苦的事情。

當一些常規聊天的開發人員不知道這一點時,事實證明,websocket 可能會崩潰。 對他們來說,當代理程式發生問題時,我們只需更改配置,然後它就會重新載入。 當然,在這種情況下,所有長期會話都會被破壞。 開發人員跑過來對我們說:“夥計們,你們在做什麼,我們所有客戶的聊天都中斷了!” 我們告訴他們:「你在做什麼? 您的客戶端無法重新連線嗎? 他們說:“不,我們需要不讓會議被撕裂。” 總之,這其實是無稽之談。 需要考慮客戶端。 特別是,正如我所說,對於 Websocket 等長期會話,它可能會中斷,並且在使用者不注意的情況下,您需要能夠重新安裝此類會話。 然後一切就都很完美了。

Ресурсы

其實,我在這裡只是在跟大家講一個正經的故事。 再次來自現實生活。 我聽過的關於資源的最噁心的事情。

在這種情況下,資源是指您可以對 Kubernetes 叢集中的 Pod 施加的某種請求和限制。 我從開發人員那裡聽到的最有趣的事情......我之前工作地點的一位開發同事曾經說過:“我的應用程式無法在叢集中啟動。” 我發現它沒有啟動,但要么它不適合資源,要么他們設置了非常小的限制。 簡而言之,應用程式因資源而無法啟動。 我說:“不會因為資源而啟動,你決定需要多少並設定一個足夠的值。” 他說:“什麼樣的資源?” 我開始向他解釋 Kubernetes、請求限制以及需要設定的等等。 那個人聽了五分鐘,點頭說:「我來這裡是身為開發人員,我不想了解任何資源。 我來這裡是為了寫程式碼,僅此而已。” 這是可悲的。 從開發人員的角度來看,這是一個非常可悲的概念。 特別是在現代世界,可以說,是進步的 devops。

為什麼需要資源? Kubernetes 中有兩種類型的資源。 有些稱為請求,有些稱為限制。 透過資源我們會明白,基本上總是只有兩個基本限制。 即在 Kubernetes 中運行的容器的 CPU 時間限制和 RAM 限制。

限制對應用程式中資源的使用方式設定了上限。 也就是說,相應地,如果您將 1GB RAM 設定為限制,那麼您的應用程式將無法使用超過 1GB 的 RAM。 如果他突然想要並嘗試這樣做,那麼一個名為 oomkiller 的進程(即記憶體不足)將會出現並殺死您的應用程式 - 也就是說,它只會重新啟動。 應用程式不會根據CPU重新啟動。 就 CPU 而言,如果應用程式嘗試使用大量 CPU,超過限制中指定的值,則只會嚴格選擇 CPU。 這不會導致重新啟動。 這就是極限——這就是上限。

並且有一個請求。 請求是 Kubernetes 如何了解 Kubernetes 叢集中的節點如何填充應用程式的方式。 也就是說,請求是應用程式的一種提交。 它說明了我想要使用的內容:“我希望您為我保留這麼多的 CPU 和記憶體。” 如此簡單的比喻。 如果我們有節點總共有 8 個 CPU(我不知道)怎麼辦? 一個 Pod 到達那裡,其請求為 1 個 CPU,這表示該節點剩下 7 個 CPU。 也就是說,一旦有8 個pod 到達該節點,每個pod 的請求都有1 個CPU,那麼從Kubernetes 的角度來看,該節點就好像CPU 已經耗盡,並且無法再接收更多有請求的pod。在此節點上啟動。 如果所有節點都耗盡了 CPU,那麼 Kubernetes 將開始提示叢集中沒有合適的節點來運行您的 pod,因為 CPU 已耗盡。

為什麼需要請求,為什麼沒有請求,我認為沒有必要在 Kubernetes 中啟動任何東西? 讓我們想像一個假設的情況。 你在沒有請求的情況下啟動應用程序,Kubernetes 不知道你有多少內容,你可以將其推送到哪些節點。 好吧,他推、推、推到節點上。 在某個時候,您將開始獲得應用程式的流量。 其中一個應用程式突然開始使用資源,直到達到其根據限制所具有的限制。 原來附近還有另一個應用程序,它也需要資源。 該節點實際上開始耗盡物理資源,例如 OP。 該節點實際上開始耗盡物理資源,例如隨機存取記憶體(RAM)。 當節點斷電時,首先 docker 將停止回應,然後是 Cubelet,然後是作業系統。 他們只會失去意識,一切都將不再對你有用。 也就是說,這將導致您的節點卡住,您需要重新啟動它。 總之,情況不太好。

而且當你有請求的時候,限制相差不是很大,至少不會比限製或請求多很多倍,那麼你就可以跨Kubernetes集群的節點有這樣正常、合理的應用填充。 同時,Kubernetes 大約知道它把多少東西放在哪裡,在哪裡使用了多少東西。 也就是說,只是這樣一個時刻。 理解它很重要。 控制這一點很重要。

數據存儲

我們的下一點是關於資料儲存的。 如何處理它們?一般而言,如何處理 Kubernetes 中的持久性?

我再次認為,在我們的範圍內 夜校,有一個關於 Kubernetes 中資料庫的主題。 在我看來,我甚至大致知道你的同事在被問到「是否可以在 Kubernetes 中運行資料庫?」時告訴你的內容。 出於某種原因,在我看來,你的同事應該告訴你,如果你問是否可以在 Kubernetes 中運行資料庫的問題,那麼這是不可能的。

這裡的邏輯很簡單。 以防萬一,我將再次解釋一下,如果您是一個非常酷的人,可以建立一個相當容錯的分散式網路儲存系統,了解如何將資料庫適合這種情況,容器中的雲端原生應該如何運作一般在資料庫中。 您很可能對如何運行它沒有任何疑問。 如果你有這樣的問題,並且你想確保這一切都展開並在生產中直立到死並且永遠不會掉落,那麼這種情況就不會發生。 這種做法一定會搬石頭砸自己的腳。 所以最好不要這樣做。

例如,我們應該如何處理應用程式想要儲存的資料、使用者上傳的一些圖片、應用程式在運行期間(啟動時)產生的一些內容? 在 Kubernetes 中如何處理它們?

一般來說,理想情況下,是的,當然,Kubernetes 設計得非常好,通常最初是為無狀態應用程式構思的。 也就是說,對於那些根本不儲存資訊的應用程式。 這是理想的。

但是,當然,理想的選擇並不總是存在。 所以呢? 第一個也是最簡單的一點是採用某種 S3,但不是自製的,也不清楚它是如何運作的,而是來自某個提供者。 一個好的、正常的提供者 - 並教您的應用程式使用 S3。 也就是說,當您的用戶想要上傳文件時,請說「請在此處將其上傳到 S3」。 當他想要接收時,請說:“這是 S3 的鏈接,從這裡獲取。” 這是理想的。

如果突然由於某種原因這個理想的選項不適合,您有一個應用程式不是您編寫的,您沒有開發的,或者它是某種可怕的遺產,它不能使用 S3 協議,但必須使用本地目錄本地文件夾。 採取或多或少簡單的方式,部署 Kubernetes。 也就是說,在我看來,立即隔離 Ceph 來執行一些最小的任務是一個壞主意。 當然,因為 Ceph 很好而且很時尚。 但是,如果您並不真正了解自己在做什麼,那麼一旦您將某些內容放在 Ceph 上,您就可以非常輕鬆且再也無法將其從那裡取出。 因為,如您所知,Ceph 在其叢集中以二進位形式儲存數據,而不是以簡單檔案的形式。 因此,如果 Ceph 叢集突然崩潰,那麼您很有可能再也無法從那裡獲取資料。

我們將開設有關 Ceph 的課程,您可以 熟悉該計劃並提交申請.

因此,最好做一些簡單的事情,例如 NFS 伺服器。 Kubernetes 可以與它們一起工作,您可以在 NFS 伺服器下掛載一個目錄 - 您的應用程式就像本機目錄一樣。 同時,自然地,您需要再次了解,您需要對 NFS 執行某些操作,您需要了解有時它可能會變得無法訪問,並考慮在這種情況下您將做什麼的問題。 也許它應該備份在單獨的機器上的某個地方。

我接下來要講的是如果你的應用程式在運行過程中產生了一些檔案該怎麼辦。 例如,當它啟動時,它會產生一些靜態文件,該文件基於應用程式僅在啟動時收到的一些資訊。 多麼美好的一刻。 如果此類數據不多,那麼您根本不必費心,只需為自己安裝此應用程式即可運作。 這裡唯一的問題是什麼,看。 很多時候,各種遺留系統,例如WordPress等等,尤其是修改了某種巧妙的插件,聰明的PHP開發人員,他們往往知道如何製作,以便為自己產生某種文件。 因此,一個產生一個文件,第二個產生第二個文件。 他們是不同的。 平衡發生在客戶端的 Kubernetes 叢集中只是偶然的。 因此,事實證明他們不知道如何一起工作。 一個提供一種訊息,另一個向使用者提供另一種資訊。 這是你應該避免的事情。 也就是說,在 Kubernetes 中,您啟動的所有內容保證能夠在多個執行個體中運作。 因為 Kubernetes 是個會移動的東西。 因此,他可以隨時移動任何東西,而無需詢問任何人。 因此,你需要依靠這一點。 在一個實例中啟動的所有內容遲早都會失敗。 您的預訂越多越好。 但我再說一遍,如果你有幾個這樣的文件,那麼你可以把它們放在你的下面,它們的重量很小。 如果它們的數量較多,您可能不應該將它們推入容器內。

我建議,Kubernetes中有這麼美妙的東西,你可以使用volume。 特別是,有一個空目錄類型的磁碟區。 也就是說,只是 Kubernetes 會自動在你啟動的伺服器上的服務目錄中建立一個目錄。 他會把它給你,以便你可以使用它。 只有一點很重要。 也就是說,您的資料不會儲存在容器內,而是儲存在您執行的主機上。 此外,Kubernetes可以在正常配置下控制此類空目錄,並且能夠控制它們的最大大小並且不允許超過它。 唯一的一點是,在空目錄中寫入的內容在 pod 重新啟動期間不會遺失。 也就是說,如果你的 pod 錯誤地跌落並再次升起,空目錄中的資訊將不會去任何地方。 他可以在新的開始時再次使用它——這很好。 如果你的 Pod 離開某個地方,那麼他自然會在沒有數據的情況下離開。 也就是說,一旦使用空目錄啟動的節點中的 pod 消失,空目錄就會被刪除。

空目錄還有什麼好處? 例如,它可以用作快取。 讓我們想像一下,我們的應用程式動態生成一些東西,將其提供給用戶,並持續很長時間。 因此,應用程式例如生成並交給用戶,同時將其儲存在某個地方,以便下次用戶來獲取相同的東西時,立即生成給它會更快。 可以請 Kubernetes 在記憶體中建立空目錄。 因此,就磁碟存取速度而言,您的快取通常可以以閃電般的速度運行。 也就是說,你在記憶體中有一個空目錄,在作業系統中它儲存在記憶體中,但對於你,對於 pod 內的使用者來說,它看起來只是一個本地目錄。 您不需要該應用程式來專門教授任何魔法。 您只需直接將檔案放入目錄中,但實際上是在作業系統的記憶體中。 這對 Kubernetes 來說也是一個非常方便的功能。

Minio有什麼問題? Minio 的主要問題是,為了讓這個東西工作,它需要在某個地方運行,並且必須有某種檔案系統,即儲存。 在這裡我們遇到了與 Ceph 相同的問題。 也就是說,Minio 必須將其檔案儲存在某個地方。 它只是檔案的 HTTP 介面。 而且,功能上明顯比亞馬遜的S3差。 此前,它無法正確授權用戶。 現在,據我所知,它已經可以創建具有不同授權的儲存桶,但在我看來,主要問題至少是底層儲存系統。

記憶體中的空目錄如何影響限制? 不會以任何方式影響限制。 它位於主機的記憶體中,而不是容器的記憶體中。 也就是說,您的容器不會將記憶體中的空目錄視為其佔用記憶體的一部分。 樓主看到這個了因此,是的,從 kubernetes 的角度來看,當您開始使用它時,最好了解您將部分記憶體分配給空目錄。 因此,要了解記憶體耗盡不僅是因為應用程序,而且還因為有人寫入這些空目錄。

雲端原生

最後一個子主題是 Cloudnative 是什麼。 為什麼需要它? 雲原生等。

也就是說,那些能夠在現代雲端基礎架構中運行的應用程式。 但事實上,Cloudnative 還有另一個這樣的面向。 這不僅是一個考慮到現代雲端基礎設施的所有要求的應用程序,而且還知道如何與這種現代雲端基礎設施一起工作,利用它在這些雲端中工作的優點和缺點。 不要只是過度地在雲端工作,而是要充分利用在雲端工作的好處。

在 Kubernetes 中開發應用程式的要求

我們以 Kubernetes 為例。 您的應用程式正在 Kubernetes 中運行。 您的應用程序,或者更確切地說,您的應用程式的管理員,始終可以建立服務帳戶。 即 Kubernetes 本身在其伺服器中的一個授權帳戶。 在那裡添加一些我們需要的權利。 您可以從應用程式內存取 Kubernetes。 這樣你能做什麼呢? 例如,從應用程式接收有關其他應用程式、其他類似實例所在位置的數據,並以某種方式聚集在 Kubernetes 之上(如果有這樣的需求)。

再說一次,我們最近確實遇到了一個案例。 我們有一名監控隊列的控制器。 當這個佇列中出現一些新任務時,它會轉到 Kubernetes,並在 Kubernetes 內部建立一個新的 pod。 為該 pod 提供一些新任務,並在此 pod 的框架內,該 pod 執行該任務,向控制器本身發送回應,然後控制器使用該資訊執行某些操作。 例如,它添加了一個資料庫。 也就是說,這又是我們的應用程式在 Kubernetes 中運作的優點。 我們可以使用內建的 Kubernetes 功能本身以某種方式擴展並使我們的應用程式的功能更加方便。 也就是說,不要隱藏有關如何啟動應用程式、如何啟動工作執行緒的某種魔法。 在 Kubernetes 中,如果應用程式是用 Python 編寫的,則只需在應用程式中發送請求即可。

如果我們超越 Kubernetes,這同樣適用。 我們的 Kubernetes 在某個地方運行 - 如果它在某種雲中,那就太好了。 同樣,我相信我們可以使用,甚至應該使用我們運行所在的雲端本身的功能。 從雲端為我們提供的基本東西開始。 平衡,即我們可以創建雲端平衡器並使用它們。 這是我們可以利用的直接優勢。 因為雲端平衡首先愚蠢地免除了我們對其工作方式和配置方式的責任。 而且它非常方便,因為常規 Kubernetes 可以與雲端整合。

縮放也是如此。 常規 Kubernetes 可以與雲端提供者整合。 知道如何理解,如果叢集用完節點,即節點空間已經用完,那麼您需要新增 - Kubernetes 本身會為您的叢集新增節點,並開始在它們上啟動 pod。 也就是說,當你的負載到來時,爐床的數量就開始增加。 當叢集中的節點耗盡這些 Pod 時,Kubernetes 會啟動新節點,因此 Pod 的數量仍然會增加。 而且非常方便。 這是動態擴展叢集的直接機會。 不是很快,從某種意義上說,它不是一秒鐘,更像是一分鐘才能添加新節點。

但根據我的經驗,這又是我見過的最酷的事情。 當 Cloudnative 叢集根據一天中的時間進行擴展時。 這是一個供後台人員使用的後端服務。 也就是說,他們早上 9 點上班,開始登入系統,相應地,所有運行的 Cloudnative 叢集開始膨脹,啟動新的 Pod,以便每個上班的人都可以使用該應用程式。 當他們晚上 8 點或 6 點下班時,Kubernetes 群集注意到沒有人再使用該應用程式並開始收縮。 保證節省高達 30% 的費用。 當時它在亞馬遜行得通;當時俄羅斯沒有人能做得這麼好。

我直接告訴你,節省了 30%,只是因為我們使用 Kubernetes 並利用了雲端的功能。 現在這可以在俄羅斯完成。 當然,我不會向任何人做廣告,但我們只能說,有些提供者可以做到這一點,透過按鈕直接提供開箱即用的服務。

我還想提請大家注意最後一點。 為了使您的應用程式、您的基礎設施成為Cloudnative,最終開始採用稱為「基礎設施即程式碼」的方法是有意義的。也就是說,這意味著您的應用程序,或者更確切地說,您的基礎設施,需要以與程式碼 以程式碼的形式描述您的應用程式、您的業務邏輯。 並將其作為程式碼使用,即測試它、推出它、將其儲存在 git 中、對其應用 CICD。

首先,這正是讓您隨時控制基礎架構、始終了解其狀態的原因。 其次,避免手工操作導致錯誤。 第三,當您不斷需要執行相同的手動任務時,避免所謂的人員流動。 第四,它可以讓您在發生故障時更快恢復。 在俄羅斯,每當我談到這個問題時,總會有很多人說:“是的,很清楚,但你有辦法,總之,沒有必要解決任何問題。” 但這是真的。 如果您的基礎架構中出現問題,那麼從 Cloudnative 方法的角度以及基礎架構即程式碼的角度來看,與其修復它,不如前往伺服器,找出損壞的部分並修復它,這會更容易刪除伺服器並重新創建它。 我會讓這一切恢復原狀。

所有這些問題都在以下位置進行了更詳細的討論: Kubernetes 視訊課程:初級、基礎、超級。 點擊鏈接,您可以熟悉該計劃和條件。 方便的是,每天在家或工作學習1-2小時就可以掌握Kubernetes。

來源: www.habr.com

添加評論