三小時學會Kubernetes:容器編排詳細指南


如果誰都可以在三個小時內學會Kubernetes,銀行為何要為這么簡單的東西付一大筆錢?

如果你心存疑慮,我建議你不妨跟着我試一試!在完成本文的學習后,你就能在Kubernetes集群上運行基於微服務的應用程序。我之所以能保證這一點,是因為我就是這么向客戶介紹Kubernetes的。
fig0.jpeg


這份指南與其他文章有何不同之處?

相當多!大多數指南是從Kubernetes概念和kubectl命令這類簡單的東西開始的。它們假定讀者熟悉應用程序開發、微服務和Docker容器。

而在我們這篇文章中,步驟是:
  1. 在你的計算機上運行基於微服務的應用程序。
  2. 為微服務應用程序的每個服務構建容器鏡像。
  3. 介紹Kubernetes。將基於微服務的應用程序部署到Kubernetes管理的集群中。

循序漸進的過程為普通人掌握Kubernetes的 簡易性提供了必要的深度。沒錯,當你了解使用Kubernetes的上下文時,一切就變得很簡單了。廢話不多說,接下來看看我們所要構建的東西。

應用程序演示

該應用程序只有一個功能:它以一個句子作為輸入,使用文本分析計算出句子的情感值。
fig1.gif

圖1. 情緒分析Web應用程序

從技術角度來看,這個應用程序由三個微服務組成。每個微服務都具有一個特定功能:
  • SA-Frontend:提供ReactJS靜態文件訪問的Nginx Web服務器。
  • SA-WebApp:處理來自前端請求的Java Web應用程序。
  • SA-Logic:執行情緒分析的Python應用程序。

微服務不是孤立存在的,它們可以實現“關注點分離”,但還是需要互相交互,理解這一點非常重要。
fig2.gif

圖2. 情緒分析WebApp中的數據流

通過展示數據在它們之間的流動方式是對這種交互最好的說明:
  1. 客戶端應用程序請求index.html(繼而請求ReactJS應用程序的捆綁腳本)
  2. 用戶與應用程序交互會觸發對Spring WebApp的請求。
  3. Spring WebApp將情緒分析請求轉發給Python應用程序。
  4. Python應用程序計算情感值並將結果作為響應返回。
  5. Spring WebApp將響應返回給React應用程序(然后將信息展示給用戶)。

所有這些應用程序的代碼都可以在 本倉庫中找到。建議讀者現在將代碼克隆下來,因為我們即將一起構建出很棒的東西。

1. 在你的計算機上運行基於微服務的應用程序

我們需要啟動所有三項服務。就從最具吸引力的前端應用程序開始吧。

為本地開發設置React

要啟動React應用程序,你需要在計算機上安裝NodeJS和NPM。安裝完后,使用終端定位到sa-frontend目錄。輸入以下命令:
npm install

這會下載該React應用程序依賴的所有JavaScript,並將其放置在node_modules文件夾中(依賴關系定義在package.json文件中)。在處理完所有依賴關系后,執行下一條命令:
npm start

搞定!我們的React應用程序啟動了,默認情況下你可以通過localhost:3000對其進行訪問。你可隨意修改代碼並立即在瀏覽器上看到效果,這是通過模塊熱替換(Hot Module Replacement)實現的,從而大大降低了前端開發的難度!

發布我們的React應用

在生產環境中,我們需要將應用程序構建成靜態文件,並使用Web服務器提供訪問。

要構建該React應用程序,請在終端中定位到sa-frontend目錄。然后執行以下命令:
npm run build

這會在項目樹中生成一個名為build的文件夾。該文件夾包含了我們的ReactJS應用程序所需的所有靜態文件。

用Nginx提供靜態文件訪問

安裝並啟動Nginx服務器( 指南)。然后將sa-frontend/build文件夾的內容移到[nginx安裝目錄]/html中。

這樣,生成的index.html文件可以在[nginx安裝目錄]/html/index.html(這是Nginx的默認訪問文件)中訪問到。

默認情況下,Nginx服務器會監聽80端口。可通過修改[nginx安裝目錄]/conf/nginx.conf文件中的server.listen參數來指定其他端口。

使用瀏覽器打開localhost:80,ReactJS應用程序將會出現。
fig3.png

圖3. Nginx提供的React應用程序頁面

在“Type your sentence.”字段中進行輸入,然后按下Send按鈕,將返回一個404錯誤(可在瀏覽器控制台中查看)。但為什么會這樣呢?我們來看一下代碼。

查看代碼

在App.js文件中我們可以看到,按下Send按鈕會觸發analyzeSentence方法。該方法的代碼如下所示(使用#注釋的各行在腳本下方進行說明):
analyzeSentence() {
fetch('http://localhost:8080/sentiment', {  // #1
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
                   sentence: this.textField.getValue()})// #2
})
    .then(response => response.json())
    .then(data => this.setState(data));  // #3
} 

#1:進行POST調用的URL(應用程序會監聽該URL的調用)。

#2:發送給應用程序的請求主體,如下所示:
{
sentence: “I like yogobella!”
} 

#3:使用響應來更新組件狀態。這會觸發組件的重新渲染。在收到的數據(即包含輸入句子及其情感值的JSON對象)后,如下定義的polarityComponent組件的條件得到了滿足,從而進行了顯示:
const polarityComponent = this.state.polarity !== undefined ?
<Polarity sentence={this.state.sentence} 
          polarity={this.state.polarity}/> :
null;

一切看起來都是對的。那么我們遺漏什么了?如果你猜是因為我們沒有設置任何東西來監聽loc​​alhost:8080,那么恭喜你,答對了!我們需要啟動Spring Web應用程序來監聽該端口!
fig4.png

圖4. Spring WebApp微服務缺失

設置Spring Web應用程序

要啟動該Spring應用程序,你需要安裝JDK8和Maven(還需要設置其環境變量)。安裝完成后,我們繼續進行下一步。

將應用程序打成Jar包

在終端中定位到sa-webapp目錄並輸入以下命令:
maven install

這將在sa-webapp目錄中生成一個名為target的文件夾。我們的Java應用程序會被打包成'sentiment-analysis-web-0.0.1-SNAPSHOT.jar'放在target文件夾中。

啟動我們的Java應用程序

定位到target目錄並使用以下命令啟動該應用程序:
java -jar sentiment-analysis-web-0.0.1-SNAPSHOT.jar

糟糕!我們碰到了一個錯誤。應用程序啟動失敗了,唯一的線索是堆棧跟蹤中的異常信息:
Error creating bean with name 'sentimentController': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'sa.logic.api.url' in value "${sa.logic.api.url}"

這里面的重點信息是SentimentController中的 sa.logic.api.url占位符。我們來看一下!

查看代碼

@CrossOrigin(origins = "*")
@RestController
public class SentimentController {
@Value("${sa.logic.api.url}")    // #1
private String saLogicApiUrl;
@PostMapping("/sentiment")
public SentimentDto sentimentAnalysis(
                        @RequestBody SentenceDto sentenceDto) {
    RestTemplate restTemplate = new RestTemplate();
return restTemplate.postForEntity(
            saLogicApiUrl   "/analyse/sentiment",    // #2
            sentenceDto, SentimentDto.class)
            .getBody();
}
} 

  1. SentimentController有一個名為saLogicApiUrl的字段。字段值由sa.logic.api.url參數定義。
  2. 字符串saLogicApiUrl與“/analyse/sentiment”拼接形成發送情緒分析請求的URL。

定義該參數

Spring默認的參數源是application.properties(位於 sa-webapp/src/main/resources中)。但是,它不是定義參數的唯一方法,也可以使用前面的命令來實現:
java -jar sentiment-analysis-web-0.0.1-SNAPSHOT.jar 
 --sa.logic.api.url=SA.LOGIC.API的路徑

這個參數要使用我們的Python應用程序運行的地址進行初始化,這樣就可以讓Spring Web應用程序知道在運行時要將消息轉發到哪里。

為了簡單起見,我們將在localhost:5000上運行Python應用程序。請記住這一點!

運行如下命令,我們就可以轉移到最后一個服務——Python應用程序。
java -jar sentiment-analysis-web-0.0.1-SNAPSHOT.jar 
 --sa.logic.api.url=http://localhost:5000

fig5.png

設置Python應用程序

要啟動Python應用程序,我們需要安裝Python3和Pip(還需要設置其環境變量)。

安裝依賴項

在終端中定位到sa-logic/sa目錄( 倉庫)並輸入以下命令:
python -m pip install -r requirements.txt
python -m textblob.download_corpora

啟動應用

使用Pip安裝依賴項后,我們即可通過執行以下命令來啟動應用程序:
python sentiment_analysis.py
* Running on http://localhost:5000/ (Press CTRL+C to quit)

現在,我們的應用程序已經啟動並在localhost的5000端口上監聽HTTP請求。

查看代碼

我們來檢查一下代碼以了解SA Logic這個Python應用程序中發生了什么。
from textblob import TextBlob
from flask import Flask, request, jsonify
app = Flask(__name__)                                   #1
@app.route("/analyse/sentiment", methods=['POST'])      #2
def analyse_sentiment():
sentence = request.get_json()['sentence']           #3
polarity = TextBlob(sentence).sentences[0].polarity #4
return jsonify(                                     #5
    sentence=sentence,
    polarity=polarity
)
if __name__ == '__main__':
app.run(host='localhost', port=5000)                #6

  1. 實例化一個Flask對象。
  2. 定義POST請求的路徑。
  3. 從請求主體中提取“sentence”參數。
  4. 實例化一個匿名TextBlob對象並獲取第一個句子(這里只有一個)的polarity。
  5. 將包含sentence和polarity內容的響應返回給調用者。
  6. 運行flask對象應用以監聽localhost:5000上的請求。

這些服務被設置為相互通信。在繼續前請重新打開前端localhost:80,並嘗試輸入句子!
fig6.png

圖6. 微服務架構完成了

在下一節中,我們將繼續介紹如何在Docker容器中啟動服務,因為這是在Kubernetes集群中運行服務的先決條件。

2. 為每個服務構建容器鏡像

Kubernetes是一個容器編排器。很顯然,我們需要容器以便對其進行編排。那么什么是容器?這從Docker的文檔中可以得到最好的回答。

容器鏡像是一個輕量級、獨立的可執行軟件包,它包含了軟件運行所需的全部內容:代碼、運行時、系統工具、系統庫、設置等等。不論是基於Linux或是Windows的應用,容器化的軟件在任何環境中都能同樣運行。
這意味着容器可以在包括生產服務器的任何計算機上無差別地運行。

為了便於說明,我們來比較一下如何使用虛擬機與容器來提供我們的React應用程序服務。

通過VM來提供React靜態文件訪問

使用虛擬機的缺點:
  1. 資源效率低下,每個虛擬機都有一個完整的操作系統的額外開銷。
  2. 它依賴於平台。在你的計算機上正常工作的東西,在生產服務器上有可能無法正常工作。
  3. 與容器相比,屬於重量級產品,且擴展較慢。

fig7.png

圖7. 虛擬機上的提供靜態文件訪問的Nginx Web服務器

通過容器來提供React靜態文件訪問

  1. 在Docker的協助下使用宿主機操作系統,資源利用率較高。
  2. 平台無關。在你的計算機上正常運行的容器在任何地方也都可以正常工作。
  3. 使用鏡像層實現輕量化。

fig8.png

圖8. 容器中提供靜態文件訪問的Nginx Web服務器

這些是使用容器最顯着的特點和好處。有關容器的更多信息,可閱讀 Docker文檔

構建React應用的容器鏡像(Docker簡介)

Docker容器的基礎構建塊是Dockerfile。Dockerfile開頭是一個基礎容器鏡像,后面是有關構建滿足應用程序需求的新容器鏡像的一系列指令。

在開始定義Dockerfile之前,回憶一下使用Nginx提供靜態文件訪問的步驟:
  1. 構建靜態文件(npm run build)
  2. 啟動Nginx服務器
  3. 將sa-frontend項目中build目錄的內容復制到nginx/html中。

在下一節中,你會發現創建一個容器與在本地React設置中所做的極其類似。

定義SA-Frontend的Dockerfile

SA-Frontend的Dockerfile是執行兩個步驟的指令。Nginx團隊已經提供了一個Nginx 基礎鏡像,我們只要在它的基礎上進行構建即可。這兩個步驟是:
  1. 從基礎的Nginx鏡像開始。
  2. 將sa-frontend/build目錄復制到容器的nginx/html目錄。

轉換成Dockerfile看起來是這樣的:
FROM nginx
COPY build /usr/share/nginx/html

是不是很酷?這個文件具有很好的可讀性,復述起來就是:

從Nginx鏡像開始(不管那些家伙在里面做了什么),將build目錄復制到鏡像中的nginx/html目錄。完成!

你可能會問,怎么知道build文件要復制到哪里?也就是 /usr/share/nginx/html。很簡單,查看Docker Hub上Nginx 鏡像的文檔。

構建並推送容器

在推送鏡像之前,我們需要一個容器Registry來托管容器。Docker Hub是一個免費的雲容器服務,我們將用其進行演示。在繼續之前,你有三項任務:
  1. 安裝Docker CE
  2. 在Docker Hub上注冊。
  3. 在終端中執行以下命令進行登錄:

docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"

完成上述任務后,定位到sa-frontend目錄。然后執行以下命令(請把$DOCKER_ID_USER替換成你的Docker Hub用戶名,比如rinormaloku/sentiment-analysis-frontend):
docker build -f Dockerfile -t $DOCKER_ID_USER/sentiment-analysis-frontend .

-f Dockerfile可以省略,因為我們正處在包含Dockerfile的目錄中。

要推送鏡像,請使用docker push命令:
docker push $DOCKER_USER_ID/sentiment-analysis-frontend

在你的Docker Hub倉庫中驗證一下鏡像是否已推送成功。

運行容器

現在,任何人都可以拉取並運行 $DOCKER_USER_ID/sentiment-analysis-frontend中的鏡像:
docker pull $DOCKER_USER_ID/sentiment-analysis-frontend
docker run -d -p 80:80 $DOCKER_ID_USER/sentiment-analysis-frontend

我們的Docker容器運行起來了!

在繼續之前,需要說明一下易發生混淆的80:80:
  • 第一個80是宿主機的端口(即我的電腦)。
  • 第二個80表示請求所要轉發的容器端口。

fig9.png

圖9. 宿主機到容器的端口映射

它將<宿主機端口>映射到<容器端口>上。這意味着對宿主機80端口的請求將被映射到容器的80端口中,如圖9所示。

由於這個端口運行在宿主機(你的計算機)的80端口上,因此可以通過localhost:80進行訪問。如果沒有原生的Docker支持,你可以通過<docker-machine ip>:80打開該應用程序。執行 docker-machine ip可獲得docker-machine的IP地址。

動手試試!你應該能夠通過它訪問到我們的React應用程序了。

Dockerignore文件

正如我們之前所見,構建SA-Frontend的鏡像速度很慢,非常非常慢。這是因為構建時需要將構建的上下文發送給Docker守護進程。更具體地說,構建上下文表示的是Dockerfile所在目錄中構建鏡像所需的所有數據。在我們的示例中,SA前端包含以下文件夾:
sa-frontend:
|   .dockerignore
|   Dockerfile
|   package.json
|   README.md
---build
---node_modules
---public
\---src

但我們需要的唯一數據都在build文件夾中,上傳其他內容是在浪費時間。我們可以通過忽略其他目錄來減少構建時間。這就是 .dockerignore的作用。對你而言這個文件應該不陌生,因為它與 .gitignore類似,只要將所有想忽略的目錄都添加到 .dockerignore文件中即可,其內容如下所示:
node_modules
src
public

.dockerignore文件應與Dockerfile位於同一文件夾中。現在構建鏡像只需要幾秒鍾。

接下來繼續說明Java應用程序。

為Java應用程序構建容器鏡像

你猜怎么了!你幾乎已經學會了創建容器鏡像的所有內容!因此這個部分非常短。

打開sa-webapp中Dockerfile,你會發現有兩個新的關鍵字:
ENV SA_LOGIC_API_URL http://localhost:5000
…
EXPOSE 8080

ENV關鍵字在Docker容器中聲明了一個環境變量。這讓我們可以在啟動容器時指定情緒分析API的URL。

其次, EXPOSE關鍵字暴露了我們稍后要訪問的端口。但是,我們在SA-Frontend的Dockerfile中並沒有這么做。好問題!原因是它僅用於文檔目的,換句話說,它僅僅是為了給閱讀Dockerfile的人提供相關信息用的。

你對容器鏡像的構建和推送應該比較熟悉了。如果遇到任何困難,請閱讀sa-webapp目錄中的README.md文件。

為Python應用程序構建容器鏡像

sa-logic的Dockerfile中沒有新的關鍵字。現在你可以稱自己為Docker專家了。

要構建和推送容器鏡像,請閱讀sa-logic目錄中的README.md。

測試容器化的應用程序

你會信任沒有經過測試的東西嗎?我是不會。接下來讓我們對這些容器進行一輪測試。
  1. 運行sa-logic容器並配置其監聽5050端口:
    docker run -d -p 5050:5000 $DOCKER_ID_USER/sentiment-analysis-logic
    
  2. 運行sa-webapp容器並配置其監聽8080端口(因為我們修改了Python應用監聽的端口,我們需要覆蓋SA_LOGIC_API_URL環境變量):
    docker run -d -p 8080:8080 $DOCKER_USER_ID/sentiment-analysis-web-app -e SA_LOGIC_API_URL='http://localhost:5050'
    
  3. 運行sa-frontend容器:
    docker run -d -p 80:80 $DOCKER_ID_USER/sentiment-analysis-frontend
    

完成!打開你的瀏覽器訪問localhost:80吧。

注意:如果你修改了sa-webapp的端口,或者使用的是docker-machine ip,則需要修改sa-frontend中App.js文件的analyzeSentence方法,以便從新的IP或端口獲取數據。然后你需要重新構建,並使用更新后的鏡像。
fig10.png

圖10. 運行在容器中的微服務

思考題:為什么要用Kubernetes?

在本節中,我們了解了Dockerfile、如何使用它來構建鏡像以及將其推送到Docker Registry的命令。此外,我們還學會了如何通過忽略無用文件以減少發送給構建上下文的文件數量。最后,我們在容器中將應用程序運行了起來。那么為什么要使用Kubernetes?這一點將在下一節中深入探討,你不妨先思考一下:
  • 我們的情緒分析Web應用成了世界熱點,突然每分鍾有一百萬個分析情緒的請求,sa-webapp和sa-logic遭遇了巨大的負載。我們要如何擴展容器?

Kubernetes介紹

我可以保證且毫不誇張地說,到文章結束時,你會問自己:“為何我們不稱它為Supernetes(譯者注:借用Superman)?”
fig11.png

圖11. Supernetes

本文從開始到現在,已經覆蓋了如此多的背景和知識。你可能擔心現在會是最難的部分,但實際上它最簡單。學習Kubernetes之所以令人生畏,唯一原因是因為“其他的一切”,而我們已經很好地學完了這些東西。

Kubernetes是什么

在從容器啟動我們的微服務之后,我們碰到了一個問題,下面我們以問答形式進一步闡述它:

問:如何進行容器擴展?
答:再啟動一個。
問:如何在它們之間分擔負載?如果服務器已經使用到極限,並且我們的容器需要另一台服務器,該怎么辦?如何計算最佳的硬件利用率?
答:啊……呃……(我Google一下)。
問:如何在不影響任何內容的情況下進行更新?如果需要,該如何回退到可工作的版本?

Kubernetes解決了所有這些問題(以及其他更多問題!)。我可以用一句話來介紹Kubernetes:“Kubernetes是一個容器編排器,它對底層基礎設施(容器運行的地方)進行了抽象。”

我們對容器編排器有一個模糊的概念。后文的實踐中會看到它,但這是我們第一次說到“對底層基礎設施進行抽象”,這點需要做個詳細說明。

對底層基礎設施進行抽象

Kubernetes提供了一套簡單的用於發送請求的API,對底層基礎設施進行抽象。Kubernetes會盡其最大能力來滿足這些請求。比如說,可以簡單地請求“Kubernetes啟動4個x鏡像的容器”,然后Kubernetes會找出利用率不足的節點,在這些節點中啟動新的容器(參見圖12)。
fig12.png

圖12. 發送到API服務器的請求

對開發者這意味着什么?意味着他無須關心節點的數量、容器在哪啟動以及它們之間如何通信。他無須處理硬件優化,也無須擔心節點會宕機(根據墨菲定律,節點一定會宕機),因為他可以將新節點添加到Kubernetes集群中。Kubernetes會在正常運轉的節點上啟動容器。它會盡其最大能力來做到這一點。

在圖12中,我們可以看到一些新的東西:
  • API服務器:與集群交互的唯一途徑。用於啟動或停止容器(誤,實為Pod),檢查當前狀態、日志等。
  • Kubelet:監控節點內的容器(誤,實為Pod),並與主節點進行通信。
  • Pod:一開始可以將Pod當作容器看待。

對Kubernetes的介紹到此為止,再深入的話會分散我們的關注點,如有需要有大量有用的資源可供大家學習,比如官方文件(困難模式)或是Marko Lukša編寫的《Kubernetes in Action》。

雲服務提供商標准化

Kubernetes強力推動的另一個項是,將雲服務提供商(CSP)標准化。這是一個大膽的聲明,我們用一個例子來說明:

– 某個Azure、Google雲平台或其他CSP的專家在一個全新的CSP中開展項目,但他對此毫無經驗。這會造成很多后果,比如可能會錯過最后期限;公司可能需要購買更多資源等等。

但這對Kubernetes根本不是問題。因為不論是哪個CSP,發送給API服務器的執行命令都是相同的。你以聲明方式從API服務器請求所需要的東西,Kubernetes抽象並實現了CSP的對該請求的動作。

很顯然,這是個非常強大的功能。對於公司來說,這意味着不需要與CSP捆綁在一起。只需要計算出在另一個CSP上的支出,然后就可以進行遷移。專業知識、資源都還在,而且可以更便宜!

說了這么多,下一節我們將把Kubernetes付諸實踐。

Kubernetes實踐——Pod

把微服務運行在容器中,雖然行得通,但是設置過程相當繁瑣。我們還提到這個解決方案不具有可擴展性和彈性,而Kubernetes則可解決這些問題。本文后續部分,我們會把服務遷移成圖13所示的最終結果,由Kubernetes來編排容器。
fig13.png

圖13. 運行在Kubernetes管理的集群中的微服務

本文將使用Minikube進行本地調試,不過所有東西在Azure和Google雲平台中都可正常工作。

安裝並啟動Minikube

請按照官方文檔來安裝 Minikube。在Minikube安裝過程中,還將安裝Kubectl。這是向Kubernetes API服務器發出請求的客戶端。

執行命令 minikube start來啟動Minikube,完成后執行 kubectl get nodes可獲得如下輸出:
kubectl get nodes
NAME       STATUS    ROLES     AGE       VERSION
minikube   Ready     <none>    11m       v1.9.0

Minikube提供了一個只有一個節點的Kubernetes集群,但別忘了我們並不關心節點的數量,Kubernetes已經將其抽象掉了,並且這對學習Kubernetes並不重要。在下一節中,我們將從我們的第一個Kubernetes資源——Pod開始。

Pod

我喜歡容器,現在你也喜歡容器。那么為什么Kubernetes決定把Pod當作最小的可部署計算單元呢?Pod是做什么的?Pod可以由一個甚至是一組共享相同運行環境的容器組成。

有需要在一個Pod中運行兩個容器么?一般會像本示例所做的這樣,一個Pod中只運行一個容器。但是,如果兩個容器需要共享數據卷,或者它們需要進行進程間通信,或者以其他方式緊密耦合,用Pod就能做到。另外一點,Pod可以讓我們不受Docker容器的限制,如果需要的話,我們可以使用其他技術來實現,比如 Rkt
fig14.png

圖14. Pod屬性

總結來說,Pod的主要屬性是(如圖14所示):
  1. 每個Pod在Kubernetes集群中都有一個唯一的IP地址。
  2. Pod可以包含多個容器。這些容器共享相同的端口空間,因此它們可以通過localhost進行通信(由此可見,它們不能使用相同的端口),與其他Pod的容器進行通信必須使用其Pod的IP地址。
  3. Pod中的容器共享相同的數據卷*、相同的IP地址、端口空間、IPC命名空間。

*容器擁有獨立的文件系統,不過它們可以使用Kubernetes資源卷來共享數據。

對於我們來說這些信息已經足夠了,如果你想了解更多,請查看 官方文檔

Pod定義

以下是第一個Pod sa-front的清單(manifest)文件,后面是對各個要點的解釋。
apiVersion: v1
kind: Pod                                            # 1
metadata:
name: sa-frontend                                  # 2
spec:                                                # 3
containers:
- image: rinormaloku/sentiment-analysis-frontend # 4
  name: sa-frontend                              # 5
  ports:
    - containerPort: 80                          # 6

  1. Kind指定我們想要創建的Kubernetes資源的種類。在這個例子中是Pod。
  2. Name:定義資源的名稱。我們將它命名為sa-front。
  3. Spec是用於定義資源的期望狀態的對象。Pod Spec最重要的參數是容器的數組。
  4. Image是要在此Pod中啟動的容器鏡像。
  5. Name是Pod中容器的唯一名稱。
  6. ContainerPort:容器所要監聽的端口。這只是面向讀者的一個指示信息(刪除該端口並不會限制訪問)。

創建SA前端Pod

上述Pod定義在 resource-manifests/sa-frontend-pod.yaml文件中。你可以在終端中定位其所在目錄​​,或者在命令行中提供完整路徑。然后執行以下命令:
kubectl create pod -f sa-frontned-pod.yaml
pod "sa-frontend" created

要檢查Pod是否正在運行,請執行以下命令:
kubectl get pods
NAME                          READY     STATUS    RESTARTS   AGE
sa-frontend                   1/1       Running   0          7s

如果其狀態仍是ContainerCreating,則可以使用 --watch參數執行上述命令,以便在Pod處於Running狀態時獲得更新信息。

從外部訪問應用程序

為了從外部訪問應用程序,正常來說,我們需要創建一個Service類型的Kubernetes資源(這是后文的內容),但為了快速調試,我們使用另一個方法,也就是端口轉發:
kubectl port-forward sa-frontend-pod 88:80
Forwarding from 127.0.0.1:88 -> 80

在瀏覽器中打開127.0.0.1:88即可訪問我們的React應用程序。

向上擴展的錯誤方法

我們說過Kubernetes的主要功能之一是可擴展性,為了說明這一點,我們來運行另一個Pod。為此,創建另一個Pod資源,其定義如下:
apiVersion: v1
kind: Pod                                            
metadata:
name: sa-frontend2      # The only change
spec:                                                
containers:
- image: rinormaloku/sentiment-analysis-frontend 
  name: sa-frontend                              
  ports:
    - containerPort: 80

通過執行以下命令來創建新的Pod:
kubectl create pod -f sa-frontned-pod2.yaml
pod "sa-frontend2" created

通過執行以下命令驗證第二個Pod是否正在運行:
kubectl get pods
NAME                          READY     STATUS    RESTARTS   AGE
sa-frontend                   1/1       Running   0          7s
sa-frontend2                  1/1       Running   0          7s

現在有兩個Pod在運行了!

注意:這不是最終的解決方案,它的問題很多。我們將在Kubernetes的Deployment資源環節對此進行改進。

Pod總結

提供靜態文件服務的Nginx Web服務器運行在兩個不同的Pod中。現在有兩個問題:
  • 如何將其暴露給外界,以便通過URL進行訪問?
  • 如何在它們之間進行負載平衡?

fig15.png

圖15. 服務之間的負載平衡

Kubernetes為此提供了Service資源。我們在下一節對其進行說明。

Kubernetes實踐——Service

Kubernetes的Service資源為提供相同功能服務的一組Pod充當入口。如圖16所示,此類資源負責發現服務和負載平衡,任務繁重。
fig16.png

圖16. Kubernetes Service維護着IP地址

我們的Kubernetes集群是由多個具有不同功能性服務的Pod組成的(前端、Spring WebApp和Flask Python應用程序)。那么問題來了,Service如何知道要定位到哪些Pod?也就是說,它如何生成Pod的端點列表?

答案是,通過標簽(Labels)來實現的,這是一個兩步過程:
  1. 將標簽應用於所有我們希望Service定位的Pod上
  2. 為我們的Service應用一個“篩選器(selector)”,以定義要定位哪個標記的Pod。

使用圖片更易於理解:
fig17.png

圖17. 帶有標簽的Pod及其清單

可以看到,Pod被打上了“app:sa-frontend”標簽,而Service也使用同一標簽來定位Pod。

標簽

標簽為組織Kubernetes資源提供了一種簡單的方法。它們的表現形式為鍵值對,可以應用於所有資源。修改Pod的清單文件以匹配此前圖17中所示的示例。

完成修改后保存文件,並使用以下命令來應用:
kubectl apply -f sa-frontend-pod.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
pod "sa-frontend" configured
kubectl apply -f sa-frontend-pod2.yaml 
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
pod "sa-frontend2" configured

這里出現了一個警告(apply與create的不同)。下一行可以看到“sa-frontend”和“sa-frontend2”Pod配置好了。我們可以通過過濾要顯示的Pod來驗證Pod是否已打上標簽:
kubectl get pod -l app=sa-frontend
NAME           READY     STATUS    RESTARTS   AGE
sa-frontend    1/1       Running   0          2h
sa-frontend2   1/1       Running   0          2h

驗證Pod被打上標簽的另一種方法是在上述命令中附加 --show-labels參數。這將顯示每個Pod的所有標簽。

好極了!我們的Pod都被打上標簽了,接下來可以用我們的Service來定位它們了。下面開始定義如圖18所示的LoadBalancer類型的Service。
fig18.gif

圖18. 使用LoadBalancer Service實現負載平衡

Service定義

Loadbalancer Service的YAML定義如下所示:
apiVersion: v1
kind: Service              # 1
metadata:
name: sa-frontend-lb
spec:
type: LoadBalancer       # 2
ports:
- port: 80               # 3
protocol: TCP          # 4
targetPort: 80         # 5
selector:                # 6
app: sa-frontend       # 7

  1. Kind:一個Service。
  2. Type:規格類型,我們選擇LoadBalancer是因為我們要實現Pod之間的負載均衡。
  3. Port:指定Service接收請求的端口。
  4. Protocol:定義通信協議。
  5. TargetPort:請求轉發的端口。
  6. Selector:包含選擇Pod的參數的對象。
  7. App:sa-frontend定義了要定位的是打了“app:sa-frontend”標簽的Pod。

請執行以下命令創建該Service:
kubectl create -f service-sa-frontend-lb.yaml
service "sa-frontend-lb" created

可通過執行以下命令來檢查Service的狀態:
kubectl get svc
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
sa-frontend-lb   LoadBalancer   10.101.244.40   <pending>     80:30708/TCP   7m

外部IP處於pending狀態(不用等了,它不會變的)。這是因為我們使用的是Minikube。如果我們在Azure或GCP這樣的雲提供商中執行該操作,將獲得一個公網IP,以便在全球范圍內訪問我們的服務。

盡管如此,我們不會因此受阻,Minikube為本地調試提供了一個有用的命令,執行以下命令:
minikube service sa-frontend-lb
Opening kubernetes service default/sa-frontend-lb in default browser...

這會在你的瀏覽器中打開Service的IP地址。在Service收到請求后,它會將其轉發給其中一個Pod(哪一個無關緊要)。這種抽象讓我們可以使用Service作為入口將多個Pod作為一個單元來看待和交互。

Service總結

本節我們介紹了標簽資源,將它們用於Service的篩選器,同時我們定義並創建了一個LoadBalancer Service。這滿足了我們擴展應用程序的需求(只需添加新的打上標簽的Pod)並使用Service作用入口實現Pod之間的負載均衡。

Kubernetes實踐——Deployment

應用程序是在不斷變化的,Kubernetes的Deployment負責保證這些應用的一致。唯有那些死掉的應用程序才不會改變,否則新的需求會出現,更多的代碼會被發布、打包並部署。在這個過程的每一步中,都可能犯錯誤。

Deployment資源可以自動遷移應用程序版本,實現零停機,並且可以在失敗時快速回滾到前一版本。

Deployment實踐

目前,我們有兩個Pod和一個用於暴露這兩個Pod並在它們之間做負載均衡的Service(參見圖19)。我們之前說過,單獨部署這些Pod並非理想的方案。它要求每個Pod進行單獨管理(創建、更新、刪除及監控其健康狀況)。快速更新和回滾更是不可能!這是無法接受的,而Kubernetes的Deployment資源則解決了這些問題。

fig19.png

圖19. 當前狀態

在繼續之前,我說明我們想要實現的目標,因為這將為我們提供一個總覽,讓我們能夠理解Deployment資源的清單定義。我們想要的是:
  1. 運行rinormaloku/sentiment-analysis-frontend鏡像的兩個Pod
  2. 零停機時間部署
  3. 為Pod打上app: sa-frontend標簽,以便sa-frontend-lb Service能發現這些服務

在下一節中,我們將把這些需求轉化成一個Deployment定義。

Deployment定義

實現上述所有需求的YAML資源定義:

apiVersion: extensions/v1beta1
kind: Deployment                                          # 1
metadata:
name: sa-frontend
spec:
replicas: 2                                             # 2
minReadySeconds: 15
strategy:
type: RollingUpdate                                   # 3
rollingUpdate: 
  maxUnavailable: 1                                   # 4
  maxSurge: 1                                         # 5
template:                                               # 6
metadata:
  labels:
    app: sa-frontend                                  # 7
spec:
  containers:
    - image: rinormaloku/sentiment-analysis-frontend
      imagePullPolicy: Always                         # 8
      name: sa-frontend
      ports:
        - containerPort: 80

  1. Kind:一個Deployment。
  2. Replicas是Deployment Spec對象的一個屬性,用於定義我們需要運行幾個Pod。這里是2個。
  3. Type指定了這個Deployment在遷移版本時使用的策略。RollingUpdate策略將保證實現零當機時間部署。
  4. MaxUnavailable是RollingUpdate對象的一個​​屬性,用於指定執行滾動更新時不可用的Pod的最大數(與預期狀態相比)。我們的部署具有2個副本,這意味着在終止一個Pod后,仍然有一個Pod在運行,這樣可以保持應用程序的可訪問性。
  5. MaxSurge是RollingUpdate對象的另一個屬性,用於定義可以添加到部署中的最大Pod數量(與預期狀態相比)。在我們的部署是,這意味着在遷移到新版本時,我們可以添加一個Pod,也就是同時有3個Pod。
  6. Template:指定Deployment創建新Pod所用的Pod模板。你馬上就發現它與Pod的定義相似。
  7. app: sa-frontend是使用該模板創建出來的Pod所使用的標簽。
  8. ImagePullPolicy設置為Always表示,每次重新部署時都會拉取容器鏡像。

老實說,即便是我也會被這堆文字弄糊塗,我們直接用這個示例入手:
kubectl apply -f sa-frontend-deployment.yaml
deployment "sa-frontend" created


跟之前一樣,我們來驗證一下一切是否正常:
kubectl get pods
NAME                           READY     STATUS    RESTARTS   AGE
sa-frontend                    1/1       Running   0          2d
sa-frontend-5d5987746c-ml6m4   1/1       Running   0          1m
sa-frontend-5d5987746c-mzsgg   1/1       Running   0          1m
sa-frontend2                   1/1       Running   0          2d


現在運行了4個Pod,有兩個是由Deployment部署的,另外兩個是我們手動創建的。可使用命令 kubectl delete pod &lt;Pod名>來刪除手動創建的那兩個。

練習:刪除Deployment部署的一個Pod,看看會發生什么。並在閱讀下面的解釋之前思考一下原因。

說明:刪除一個Pod后Deployment將發現當前狀態(運行着1個Pod)與預期狀態不同(運行着2個Pod),因此它會再啟動一個Pod。

除了保證預期狀態之外,Deployment還有什么好處?下面我們一一來看下它的優點:

優點#1:零停機時間滾動部署

產品經理提出了一項新的需求:客戶希望在前端有一個綠色按鈕。開發人員發布完代碼,然后提供了我們唯一需要的東西,即容器鏡像 rinormaloku/sentiment-analysis-frontend:green。現在輪到我們了,作為DevOps,我們必須實現零停機時間部署,前面的努力值得么?讓我們拭目以待!

編輯 deploy-frontend-pods.yaml文件,修改容器鏡像來引用新的鏡像: rinormaloku/sentiment-analysis-frontend:green。保存並執行以下命令:
kubectl apply -f deploy-frontend-green-pods.yaml --record
deployment "sa-frontend" configured


我們可以使用以下命令檢查滾動部署的狀態:
kubectl rollout status deployment sa-frontend
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 of 2 updated replicas are available...
deployment "sa-frontend" successfully rolled out


從輸出可知,部署工作已經完成。它完成的方式是這樣的,副本被逐一替換。這意味着應用程序始終處於運行狀態。在繼續之前,我們確認一下更新是否生效。

驗證部署

在瀏覽器上查看更新的內容。執行與之前使用過的同一命令 minikube service sa-frontend-lb打開瀏覽器,我們可以看到該按鈕已更新。

fig20.png

圖20. 綠色按鈕

“滾動更新”的幕后

在應用新的Deployment后,Kubernetes會對新舊狀態進行比較。在我們的示例中,新狀態請求兩個使用 rinormaloku/sentiment-analysis-frontend:green的Pod。這與當前運行狀態不同,因此它會啟用RollingUpdate。

fig21.png

圖21. RollingUpdate替換Pod

RollingUpdate會根據我們指定的規則進行操作,即“maxUnavailable: 1”和“maxSurge: 1”。這意味着部署時只能終止一個Pod,並且只能啟動一個新的Pod。該過程會不斷重復直到所有的Pod都被更換(見圖21)。

接下來看看優點#2。

聲明:下一部分以小說形式寫成,僅供娛樂。

優點#2:回滾到之前的狀態

產品經理跑進你的辦公室,說他有大麻煩了!

“生產環境的應用程序里有一個嚴重錯誤!立即恢復到前一個版本!”,產品經理大叫道。

你內心毫無波瀾,眼睛眨都沒眨一下。你切換到終端應用,輸入:
kubectl rollout history deployment sa-frontend
deployments "sa-frontend"
REVISION  CHANGE-CAUSE
1         <none>         
2         kubectl.exe apply --filename=sa-frontend-deployment-green.yaml --record=true


你看了一眼上述Deployment,然后問產品經理:“最新版本有問題,而前一個版本工作正常?”

“是的,你在聽我說話嗎?!”產品經理尖叫起來。

你無視他的存在,心里很清楚要做什么,然后開始輸入:
kubectl rollout undo deployment sa-frontend --to-revision=1
deployment "sa-frontend" rolled back


當你刷新頁面后,最近的修改被撤消了!

產品經理驚得下巴都掉到了地上。

你成了今天的英雄!

劇終!

沒錯……好無聊的小說。在Kubernetes出現之前,現實要精彩得多,更富戲劇性、強度也更高,持續的時間也更長。一段美好的舊時光!

大部分命令都是一目了然的,但有個細節需要你自己解讀。為什么第一個版本的 CHANGE-CAUSE是<none>,而第二個版本的 CHANGE-CAUSE是“ kubectl.exe apply –filename=sa-frontend-deployment-green.yaml –record=true”?

如果你的答案是:我們在應用新鏡像時使用了 --record標示,那么恭喜你,回答正確!

在下一節中,我們將使用到目前為止學到的概念來完成整個架構。

Kubernetes和其他實踐

我們已經學習了完成架構所需的全部資源,因此這部分會很快。在圖22中,我們將所有仍然需要做的事情灰化了。讓我們從最下面開始:部署sa-logic Deployment。

fig22.png

圖22. 當前應用程序狀態

部署SA-Logic

在終端中定位到resource-manifests目錄並執行以下命令:
kubectl apply -f sa-logic-deployment.yaml --record
deployment "sa-logic" created


SA-Logic Deployment創建了三個Pod(運行着Python應用程序容器),並給它們打上了 app: sa-logic標簽。該標簽讓我們能夠使用SA-Logic Service中的篩選器來定位它們。請花點時間打開文件 sa-logic-deployment.yaml查看其內容。

由於使用的概念相同,因此無需多言來看看下一項:Service SA-Logic。

Service SA-Logic

這里需要說明一下為什么我們需要這項Service。我們的Java應用程序(運行在SA-WebApp Deployment的Pod中)依賴於Python應用程序完成的情緒分析。但是,與之前全部在本地運行不同,現在我們不再是使用單一一個Python應用程序監聽一個端口,而是兩個甚至更多。

這就是為什么我們需要一個Service“作為提供相同功能服務的一組Pod的入口”。這意味着我們可以使用Service SA-Logic作為所有SA-Logic Pod的入口。

執行以下命令:
kubectl apply -f service-sa-logic.yaml --record
service "sa-logic" created


更新后的應用程序狀態:我們運行了2個Pod(包含Python應用程序),並且有一個SA-Logic Service作為即將在SA-WebApp Pod中使用的入口。

fig23.png

圖23. 更新后的應用程序狀態

現在我們需要使用Deployment資源來部署SA-WebApp Pod。

部署SA-WebApp

我們對Deployment已經非常熟悉,不過此處還是有一個新功能。如果你打開 sa-web-app-deployment.yaml文件,你會發現這部分是新的:
- image: rinormaloku/sentiment-analysis-web-app
imagePullPolicy: Always
name: sa-web-app
env:
- name: SA_LOGIC_API_URL
  value: "http://sa-logic"
ports:
- containerPort: 8080


我們感興趣的是env屬性是做什么的?我們推測它是在Pod中聲明環境變量SA_LOGIC_API_URL的值為“ http://sa-logic”。但為什么我們將它初始化為 http://sa-logic,什么是sa-logic?

這里需要介紹一下kube-dns。

Kube-dns

Kubernetes有一個特殊的Pod kube-dns。默認情況下,所有Pod都會將其作為DNS服務器。kube-dns一個重要特性是它會為每個新建的Service創建一條DNS記錄。

這意味着當我們創建Service sa-logic時,它獲得了一個IP地址。它的名字(與IP一起)會被添加到kube-dns記錄中。這使得所有的Pod能夠將sa-logic轉換為SA-Logic Service的IP地址。

好的,我們繼續:

部署SA-WebApp(續)

執行以下命令:
kubectl apply -f sa-web-app-deployment.yaml --record
deployment "sa-web-app" created


完成。剩下的是使用LoadBalancer Service對外暴露SA-WebApp Pod。以便讓我們的React應用程序可以向作為SA-WebApp Pod入口的Service發送HTTP請求。

Service SA-WebApp

打開 service-sa-web-app-lb.yaml文件,可以看到一切都很熟悉。

無須多想,執行以下命令:
kubectl apply -f sa-web-app-deployment.yaml
deployment "sa-web-app" created


整個架構完成了。不過還有一點沒完善。在部署SA-Frontend Pod時,容器鏡像將SA-WebApp指向了 http://localhost:8080/sentiment。但是現在我們需要將其更新為指向SA-WebApp LoadBalancer(充當SA-WebApp Pod的入口)的IP地址。

解決這個問題讓我們有機會再次快速地把從代碼到部署的所有內容過一遍(如果你不是遵循以下指南,而是單獨做這件事,可能會更有效)。讓我們開始吧:
  1. 執行以下命令獲取SA-WebApp Loadbalancer的IP地址:

minikube service list
|-------------|----------------------|-----------------------------|
|  NAMESPACE  |         NAME         |             URL             |
|-------------|----------------------|-----------------------------|
| default     | kubernetes           | No node port                |
| default     | sa-frontend-lb       | http://192.168.99.100:30708 |
| default     | sa-logic             | No node port                |
| default     | sa-web-app-lb        | http://192.168.99.100:31691 |
| kube-system | kube-dns             | No node port                |
| kube-system | kubernetes-dashboard | http://192.168.99.100:30000 |
|-------------|----------------------|-----------------------------|

  1. 如下所示,在sa-frontend/src/App.js文件中使用SA-WebApp Loadbalancer的IP地址:

analyzeSentence() {
    fetch('http://192.168.99.100:31691/sentiment', { /* shortened for brevity */})
        .then(response => response.json())
        .then(data => this.setState(data));
}

  1. 運行npm build構建靜態文件(需要定位到sa-frontend目錄)
  2. 構建容器鏡像:

docker build -f Dockerfile -t $DOCKER_USER_ID/sentiment-analysis-frontend:minikube .

  1. 將鏡像推送到Docker Hub:

docker push $DOCKER_USER_ID/sentiment-analysis-frontend:minikube

  1. 編輯sa-frontend-deployment.yaml文件以使用新的鏡像。
  2. 執行命令kubectl apply -f sa-frontend-deployment.yaml

刷新一下瀏覽器,或者再次執行 minikube service sa-frontend-lb。現在輸入一個句子試試!

fig24.png


文章總結

Kubernetes對團隊和項目都非常有益,它簡化了部署、可擴展性和彈性,讓我們能夠使用任意的底層基礎設施。從現在開始,我要稱它為Supernetes!你覺得如何?

我們在這個系列中介紹的內容:
  • 構建/打包/運行ReactJS、Java和Python應用程序
  • Docker容器;如何使用Dockerfiles來定義和構建容器
  • 容器Registry;我們使用Docker Hub作為容器的倉庫
  • 我們介紹了Kubernetes最重要的部分
  • Pod
  • Service
  • Deployment
  • 一些新概念,比如零停機時間部署
  • 創建可伸縮的應用程序
  • 在這個過程中,我們將整個微服務應用程序遷移到Kubernetes集群上

原文鏈接: Learn Kubernetes in Under 3 Hours: A Detailed Guide to Orchestrating Containers (翻譯: 梁曉勇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM