我保證本文是最詳盡的 Kubernetes 技術文檔,從我在后台排版了這么漫長的時間就能看出來。廢話不多說——牢牢占據容器技術統治地位的 Kubernetes,其重要性想必不言而喻。
以下為譯文:
為什么銀行肯花大價錢雇我做 Kubernetes 如此簡單的工作?——我一直很奇怪這一點,因為我覺得任何人都可以在 3 個小時內學會這項技術。
如果你懷疑我說的,就來挑戰一下吧!讀完本文,你絕對可以學會如何在 Kubernetes 集群上運行基於微服務的應用程序。我保證你可以做到,因為我就是這樣向我的客戶介紹 Kubernetes 的。
本文的教程與其他資源有何不同?有很大不同。大多數的教程都從最簡單的內容講起:Kubernetes 的概念和 kubectl 的命令。本文則是基於讀者了解應用程序的開發、微服務和 Docker 容器等基礎之上。
本文中,我們會涉及的內容包括:
在計算機上運行基於微服務的應用程序;
為微服務應用程序的每個服務建立容器映像;
Kubernetes 的基本介紹;
在 Kubernetes 管理的集群內部署基於微服務的應用程序。
通過一步步深入學習,讓大家能夠領會 Kubernetes 的簡單易用。只有你了解它的使用環境時,才能輕松掌握 Kubernetes。廢話不多說,我們開始吧。
應用程序示范
如下應用程序有一個功能:每次輸入接受一個句子;使用文本分析,計算句子所表達的情緒。
圖1:情感分析網絡應用
從技術的角度看來,這個應用程序包含 3 個微服務,每個都包含特定功能:
-
SA-Frontend:前端, Nginx 網絡服務器,提供 ReactJS 靜態文件;
-
SA-WebAp:網絡應用, Java 網絡應用程序,處理來自前端的請求;
-
SA-Logic:邏輯處理, Python 應用程序,執行情感分析。
你需要知道微服務是無法獨立工作的,它們引入了“關注點分離”(separation of concerns),但是它們之間依然需要交互。
圖2:情感分析網絡應用中的數據流
我們可以通過微服務之間的數據流來描述這種交互:
-
客戶端應用程序請求初始頁面 index.html(index.html 頁面會反過來加載 ReactJS 應用程序的腳本);
-
用戶與應用程序的交互觸發到 Spring 網絡應用的請求;
-
Spring 網絡應用將請求發送給 Python 應用做情感分析;
-
Python 應用計算情感值,並返回結果;
-
Spring 網絡應用將結果返回給 React 應用(然后由 React 應用將結果顯示給用戶)。
點擊此處下載這些應用程序的代碼:https://github.com/rinormaloku/k8s-mastery。現在就可以克隆這個代碼倉庫,接下來我們要做更加精彩的東西。
在計算機上運行基於微服務的應用程序
我們需要啟動所需的 3 個服務。讓我們從最有意思的部分——前端應用程序開始。
設置 React 的本地部署
為了運行 React 應用程序,首先你需要在計算機上安裝 NodeJS 和 NPM。安裝好這些后,在終端中進入目錄 sa-frontend。然后運行如下命令:
npm install
該命令會將 React 應用程序的所有 Javascript 依賴都下載到文件夾 node_modules 中(package.json 文件中定義了所有依賴)。在所有依賴都解決后,運行如下命令:
npm start
這樣就可以了!我們運行了 React 應用程序,現在可以通過默認端口 localhost:3000 訪問該應用程序了。你可以自由修改代碼,並從瀏覽器中觀察即時效果。這里用到了熱模塊替換(即在運行時用替換模塊來減少頁面刷新次數)技術,可以減輕前端開發的工作。
准備好 React 應用的產品環境
為了搭建產品環境,我們需要建立應用程序的靜態網頁,並通過網絡服務器提供服務。
為了搭建 React 應用程序,首先在終端中進入目錄 sa-frontend。然后運行如下命令:
npm run build
該命令會在項目的文件目錄中生成一個名叫“build”的文件夾。該文件夾內包含了 ReactJS 應用程序所需的所有靜態文件。
利用 Nginx 提供靜態文件
首先安裝並啟動 Nginx 網絡服務器。然后將 sa-frontend/build 目錄內的文件移動到 [nginx安裝目錄]/html。
如此一來,我們就可以通過 [nginx安裝目錄]/html/index.html 來訪問 index.html 文件了,而它是 Nginx 服務的默認文件。
默認情況下,Nginx 網絡服務器會監聽端口 80。你可以通過修改 [nginx安裝目錄]/conf/nginx.conf 文件中的 server.listen 字段來指定不同的端口。
打開瀏覽器,並訪問端口 80,可以看到 ReactJS 應用程序加載成功。
圖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;
一切看起來都很好。但是我們還差什么呢?你可能已經發現我們沒有在 localhost:8080 上設置任何東西!我們必須啟動 Spring 網絡應用程序監聽這個端口!
圖4:缺失的 Spring 網絡應用程序微服務
建立 Spring 網絡應用程序
為了設置 Spring 網絡應用程序,你必須安裝 JDK8 和 Maven,並設置它們的環境變量。設置好后,我們繼續下個部分。
將應用程序打包成 Jar 文件
在終端中進入 sa-webapp 目錄,並運行如下命令:
mvn install
該命令會在目錄 sa-webapp 中生成一個名叫 target 的文件夾。target 文件夾內有打包好的 Java 應用程序包:’sentiment-analysis-web-0.0.1-SNAPSHOT.jar’。
啟動應用程序
進入 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”連接在一起,共同構成了 Sentiment Analysis 請求的 URL。
定義屬性
在 Spring 中默認的屬性資源是 application.properties(具體位置在 sa-webapp/src/main/resources 中)。但是這不是定義屬性的唯一方式,我們可以通過之前的命令完成屬性定義:
java -jar sentiment-analysis-web-0.0.1-SNAPSHOT.jar
--sa.logic.api.url=WHAT.IS.THE.SA.LOGIC.API.URL
應該由 Python 應用程序運行時定義的值初始化該屬性,如此一來 Spring 網絡應用程序就可以知道在運行時把信息傳遞到哪里了。
為了簡單起見,我們假設在 localhost:5000 上運行 Python 應用程序。請記得哦!
運行如下命令,然后我們來看看最后一個服務:Python 應用程序。
java -jar sentiment-analysis-web-0.0.1-SNAPSHOT.jar
--sa.logic.api.url=http://localhost:5000
建立 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://0.0.0.0:5000/ (Press CTRL+C to quit)
這意味着應用程序已經啟動,並在 localhost 的端口 5000 上監聽 HTTP 請求了。
檢查代碼
讓我們檢查代碼,看看處理邏輯部分的 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='0.0.0.0', port=5000) #6
#1:實例化一個 Flask 對象;
#2:定義 POST 請求訪問的路徑;
#3:從請求主體內抽出“sentence”屬性;
#4:初始化匿名 TextBlob 對象,並從第一個句子(我們只有一個)中獲取極性;
#5:在相應體內返回句子和極性;
#6:運行 flask 對象應用來監聽 localhost:5000 上的請求。
所有的服務都設置好,可以互相交流了。試試看重開前端的 localhost:80。
圖6:微服務架構完成
在下面一節中,我們將介紹如何在 Docker 容器內啟動這些服務,因為這是在 Kubernetes 集群內運行這些服務的前提條件。
為每個服務創建容器映像
Kubernetes 是容器管理平台。可想而知我們需要容器去管理它們。但是容器是什么?Docker 官方文檔的最佳答案如下:
容器映像是輕量級的、獨立的、可執行軟件包,包含所有可運行的東西:代碼、運行時、系統工具、系統庫、設置。對於基於 Linux 和 Windows 的應用,不論環境如何,容器化的軟件都可以照常運行。
這意味着容器可以在任何計算機上運行,甚至是在產品服務器上,都沒有任何差別。
為了更形象地描述,讓我們來對比一下 React 應用程序在虛擬機上和容器內運行的情況。
通過虛擬機提供 React 靜態文件
使用虛擬機的缺點包括:
-
資源效率低下,每個虛擬機都需要一個完全成熟的操作系統;
-
對平台有依賴性。本地機器上運行得很好的功能未必能在產品服務器上正常工作;
-
與容器相比,更重而且規模伸縮較慢。
圖7:虛擬機上提供靜態文件的 Nginx 網絡服務器
通過容器提供 React 靜態文件
使用容器的優點包括:
-
資源效率很高,在 Docker 的幫助下使用主機操作系統;
-
對平台沒有依賴性。可以在本地機器上運行的容器可以在任何機器上正常工作;
-
通過映像層提供輕量級服務。
圖8:在容器內提供靜態文件的 Nginx 網絡服務器
以上是使用容器最突出的特色和優勢。更多信息,請參閱 Docker 官方文檔:https://www.docker.com/what-container。
為 React 應用建立容器映像(Docker 簡介)
Docker 容器最基本的組件是 .dockerfile。該 Dockerfile 文件最基本的組成是容器鏡像,我們將通過下列一系列說明,介紹如何創建一個符合應用程序需求的容器鏡像。
在開始定義 Dockerfile 之前,讓我們先回想一下使用 Nginx 服務 React 靜態文件的步驟:
-
創建靜態文件(npm run build);
-
啟動 Nginx 服務器;
-
將前端項目的 build 文件夾的內容復制到 nginx/html 目錄中。
在下一節中,你會注意到創建容器與建立本地 React 的過程非常相似。
為前端定義 Dockerfile
前端 Dockerfile 的建立只有兩個步驟。這是因為 Nginx 團隊為我們提供了基本的 Nginx 映像,我們可以直接利用。這兩個步驟如下:
-
啟動基本的 Nginx 映像;
-
將 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 映像文檔中有記載。
建立並推送容器
在推送映像之前,我們需要一個容器注冊來托管映像。Docker Hub 是一個免費的雲容器服務,我們將使用它來做演示。接下來有 3 個任務需要完成:
-
安裝 Docker CE;
-
注冊 Docker Hub;
-
在終端中運行如下命令登錄:
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
在完成上述任何后,請進入目錄 sa-frontend。然后運行如下命令(請用你的 docker hub 用戶名替換 $DOCKER 用戶名,例如:rinormaloku/sentiment-analysis-frontend)。
docker build -f Dockerfile -t $DOCKER_USER_ID/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_USER_ID/sentiment-analysis-frontend
Docker 容器已經處於運行狀態了!
在進行下一步之前,讓我們先來講解一下 80:80,很多人對此比較不解:
-
第一個 80 是主機的端口號(例如:我的計算機);
-
第二個 80 是容器的端口號,請求都會被轉送到這里。
圖9:從主機到容器的端口匹配
這是從<主機端口>匹配到<容器端口>的。也就是說每個發往主機 80 端口的請求都會被匹配到容器的 80 端口,如圖 9 所示。
因為在主機上(你的計算機)80 端口上運行的端口可以訪問 localhost:80。如果本地不支持 Docker,那么你可以在 <docker機器ip>:80 上打開應用程序。運行 docker-machine ip 命令可以找到 Docker 機器的 IP。
試試看!你現在應該可以訪問 React 應用程序了。
Dockerignore 文件
剛才我們看到建立 SA-Frontend 的映像非常慢,不好意思,應該是超級慢。這是因為我們必須將建立過程中的環境文件發送給 Docker 服務。更具體地來說,建立過程中的環境文件指的是在建立映像的時候,所有會用到的 Dockerfile 目錄中的數據。
以我們的例子來說,SA-Frontend 文件包括如下文件夾:
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 的人提供信息的。
你應該已經掌握了創建和推送容器映像。如果遇到任何困難,可以閱讀 sa-webapp中的README.md 文件。
為 Python 應用程序創建容器映像
sa-logic 的 Dockerfile 中沒有新的關鍵字。現在你已經是 Docker 達人了。
關於如何建立和推送容器映像,請閱讀 sa-logic 目錄中的 README.md 文件。
測試容器化的應用程序
你能相信沒有測試過的東西嗎?我也不信。所以我們來測試一下這些容器吧。
1.運行 sa-logic 容器,並配置監聽端口 5050:
docker run -d -p 5050:5000 $DOCKER_USER_ID/sentiment-analysis-logic
2.運行 sa-webapp 容器,並配置監聽端口 8080(因為我們改變了 Python 應用監聽的端口,所以我們需要重寫環境變量 SA_LOGIC_API_URL):
$ docker run -d -p 8080:8080 -e SA_LOGIC_API_URL='http://<container_ip or docker machine ip>:5000' $DOCKER_USER_ID/sentiment-analysis-web-app
3.運行 sa-frontend 容器:
docker run -d -p 80:80 $DOCKER_USER_ID/sentiment-analysis-frontend
然后就可以了。在瀏覽器中打開 localhost:80。
請注意:如果你改變 sa-webapp 的端口,或使用 Docker 機器的 IP,那么你需要更新 sa-frontend 中的 App.js,讓 analyzeSentence 從新的 IP 或端口獲取 URL。然后你需要建立並使用更新后的映像。
圖10:在容器內運行微服務
智力問答題——為什么使用 Kubernetes?
本節中,我們學習了 Dockerfile,如何使用它創建映像,以及推送映像到 Docker注冊目錄的命令。另外,我們探討了如何通過忽略沒用的文件,減少需要發送的建立過程中的環境文件。最后我們從容器上運行了應用程序。
接下來,我們介紹為什么要使用 Kubernetes?我們將在下面深入介紹 Kubernetes,這里我想給你留個智力問答題。
如果我們的情感分析網絡應用完成得很好,突然間訪問量暴漲到每分鍾數百萬的請求,那么我們的 sa-webapp 和 sa-logic 將面臨巨大的負荷壓力。請問,我們如何才能擴大容器的規模?
Kubernetes 簡介
我向你保證我沒有誇大其詞,讀完本文你會問“為什么我們不稱它為 Supernetes?”
圖11:Supernetes
Kubernetes 是什么?
從容器啟動微服務后,我們有一個問題,讓我們通過如下問答的形式具體描述這個問題:
問:我們怎么擴大或縮小容器?
答:我們啟動另外一個容器。
問:我們如何在容器間分攤負荷?如果當前服務器的負荷達到最大,那我們是否需要另外一個服務器?我們如何最大化硬件使用率?
答:唔......呃......(讓我搜一下)
問:如果在打更新補丁的時候,不影響到所有的服務?如果服務出了問題,如何才能返回之前能正常工作的版本?
Kubernetes 可以解決以上所有問題(以及更多問題!)。我可以用一句話總結 Kubernetes:“Kubernetes 是容器控制平台,可以抽象所有的底層基礎設施(容器運行用到的基礎設施)。”
我們對容器控制平台有個模糊的概念。在本文后續部分,我們將看看它的實際應用,但是這是第一次我們提到“底層基礎設施的抽象”,所以我們來詳細看看這個概念。
底層基礎設施的抽象
Kubernetes 通過一個簡單的 API 提供底層基礎設施的抽象,我們可以向該 API 發送請求。這些請求可以讓 Kubernetes 盡最大能力應對。例如,可以簡單地要求“Kubernetes 添加映像 x 的 4 個容器。”然后 Kubernetes 會找出使用中的節點,並在內添加新的容器(如圖 12 所示)。
圖12:向 API 發送請求
這對開發人員來說意味着什么?意味着開發人員不需要在意節點的數目,也不需要在意從哪里運行容器以及如何與它們交流。開發人員不需要管理硬件優化,或擔心節點關閉(它們將遵循墨菲法則),因為新的節點會添加到 Kubernetes 集群。同時 Kubernetes 會在其他運行的節點中添加容器。Kubernetes 會發揮最大的作用。
在圖 2 中我們看到了一些新東西:
-
API服務器:與集群交互的唯一方式。負責啟動或停止另外一個容器,或檢查當前狀態,日志等;
-
Kubelet:監視節點內的容器,並與主節點交流;
-
Pod:初始階段我們可以把 pod 當成容器。
就介紹這么多,跟深入的介紹會導致我們分心,我們可以等到后面一點再介紹,有一些有用的資源,比如官方文檔,或者閱讀 Marko Lukša 的著作《Kubernetes in Action》,以及 Sébastien Goasguen & Michael Hausenblas 的《Kubernetes Cookbook》。
標准化的雲服務提供商
Kubernetes 另外一個深入人心的點是:它標准化了雲服務提供商。這是一個很大膽的宣言,我們通過如下例子來具體看一看:
比如,有一個 Azure、Google 雲平台或其他雲服務提供商的專家,他擔任了一個搭建在全新的雲服務提供商的項目。這可能引起很多后果,比如說:他可能無法在截止期限內完成;公司可能需要招聘更多相關的人員,等等。
相對的,Kubernetes 就沒有這個問題。因為不論是哪家雲服務提供商,你都可以在上面運行相同的命令。你可以以既定的方式向 API 服務器發送請求。Kubernetes 會負責抽象,並實裝這家雲服務商。
停一秒鍾仔細想一下,這是極其強有力的功能。對公司來說,這意味着他們不需要綁定到一家雲服務商。他們可以計算別家雲服務商的開銷,然后轉移到別家。他們依舊可以保留原來的專家,保留原來的人員,他們還可以花更少的錢。
說了這么多,在下一節中讓我們來實際使用 Kubernetes。
Kubernetes 實踐——Pod
我們建立了微服務在容器上運行,雖然頗為坎坷,但還是可以工作的。我們還提到這種解決方案不具有伸縮性和彈性,而 Kubernetes 可以解決這些問題。在本文的后續章節,我們會將各個服務轉移到由 Kubernetes 管理的容器中,如圖 13 所示。
圖13:在 Kubernetes 管理的集群中運行微服務
在本文中,我們將使用 Minikube 進行本地調試,盡管所有東西都是運行在 Azure 和 Google 雲平台中的。
安裝和啟動 Minikube
請參閱安裝 Minikube 的官方文檔:
-
https://kubernetes.io/docs/tasks/tools/install-minikube/
在安裝 Minikube 的同時,你可以捎帶着安裝 Kubectl。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 的另一個特征是:如果我們希望使用其他 Rke 等技術的話,我們可以做到不依賴 Docker 容器。
圖14:Pod 屬性
總的來說,Pod 的主要屬性包括(如圖 14 所示):
-
每個 Pod 可以在 Kubernetes 集群內擁有唯一的 IP 地址;
-
Pod 可以擁有多個容器。這些容器共享同一個端口空間,所以他們可以通過 localhost 交流(可想而知它們無法使用相同的端口),與其他 Pod 內容器的交流可以通過結合 Pod 的 IP 完成;
-
一個 Pod 內的容器共享同一個卷、同一個 IP、端口空間、IPC 命名空間。
注:容器有個自己獨立的文件系統,盡管他們可以通過 Kubernetes 的資源卷共享數據。
更多詳細內容,請參閱相關的官方文檔:
https://kubernetes.io/docs/concepts/workloads/pods/pod/
Pod 的定義
如下是我們的第一個 pod sa-frontend 的清單文件,我們會對文件內容進行逐一解釋。
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-frontend。
#3 spec:該對象定義了資源應有的狀態。Pod Spec 中最重要的屬性是容器的數組。
#4 image:是指我們希望在本 Pod 中啟動的容器的映像。
#5 name:Pod 中容器中唯一的名字。
#6 containerPort:是指容器監聽的端口號。這只是為了提供文檔信息(即便沒有這個端口也不會影響訪問)。
創建 SA Frontend 的 Pod
你可以在 resource-manifests/sa-frontend-pod.yaml 中找到上述 Pod 的定義。你可以在終端中進入該文件夾,或在命令行輸入完整的路徑。然后執行如下命令:
kubectl create -f sa-frontend-pod.yaml
pod "sa-frontend" created
可以通過如下命令確認 Pod:
kubectl get pods
NAME READY STATUS RESTARTS AGE
sa-frontend 1/1 Running 0 7s
如果該 Pod 還處於容器生成中的狀態的話,你可以在運行命令的時候加入參數 --watch,當 Pod 進入運行狀態的時候,終端會顯示信息。
從外部訪問應用程序
為了從外部訪問應用程序,我們需要創建服務類型的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 -f sa-frontend-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 資源的部署一節中改善這個方案。
總結 Pod
提供靜態文件的 Nginx 網絡服務器在另個不同的 Pod 內運行。現在我們有兩個問題:
-
怎樣對外開放這些服務,讓用戶通過 URL 來訪問它們?
-
怎樣平衡 Pod 之間的負荷?
圖15:服務之間的負荷平衡
Kubernetes 提供了服務類型的資源。在下一節中我們將詳細介紹。
Kubernetes 實踐——服務
Kubernetes 服務資源可以作為一組提供相同服務的 Pod 的入口。這個資源肩負發現服務和平衡 Pod 之間負荷的重任,如圖 16 所示。
圖16:Kubernetes 服務維護 ID 地址
在 Kubernetes 集群內,我們擁有提供不同服務的 Pod(前端、Spring 網絡應用和 Flask Python 應用程序)。所以這里的問題是:服務如何知道該處理哪個 Pod?例如:它如何生成這些 Pod 的終端列表?
這個問題可以用標簽來解決,具體分兩個步驟:
-
給所有服務處理的對象 Pod 貼上標簽;
-
在服務中使用一個選擇器,該選擇器定義了所有貼有標簽的對象 Pod。
下列視圖看起來更清晰:
圖17:帶有標簽的 Pod 和它們的清單文件
我們可以看到 Pod 都貼着標簽“app: sa-frontend”,服務用這個標簽找到目標 Pod。
標簽
標簽提供了一種簡單的方法用於管理Kubernetes資源。它們有一對鍵值表示,且可以用於所有資源。按照圖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
我們看到了一個警告(在應用的時候,而非創建,明白了)。在第二行我們看到部署了 pod “sa-frontend”和 “sa-frontend2”。我們可以過濾想要查看的 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 已經貼上了標簽,我們准備好通過服務找到它們了。讓我們定義 LoadBalancer 類型的服務,如圖 18 所示。
圖18:用 LoadBalancer 服務平衡負荷
服務的定義
LoadBalancer 服務的 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:服務;
#2 type:指定類型,我們選擇 LoadBalancer,因為我們想平衡 Pod 之間的負荷;
#3 ports:指定服務獲取請求的端口;
#4 protocol:定義交流;
#5 targetPort:可以將來訪的請求轉發到這個端口;
#6 selector:包含選擇pod屬性的對象;
#7 app:sa-frontend定義了哪個是目標 Pod,只有擁有標簽“app: sa-frontend”的才是目標 Pod。
通過運行如下命令創建服務:
kubectl create -f service-sa-frontend-lb.yaml
service "sa-frontend-lb" created
可以通過運行如下命令檢查的服務的狀態:
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
External-IP 處於 pending 狀態(不用再等了,這個狀態不會變的)。這是因為我們使用的是 Minikube。如果我們在 Azure 或 Google 雲服務上運行,那么我們可以得到一個公開的 IP,那么全世界都可以訪問我們的服務了。
盡管如此,Minikube 也不會置我們於不顧,它提供一個非常有用的本地調試命令,如下所示:
minikube service sa-frontend-lb
Opening kubernetes service default/sa-frontend-lb in default browser...
這可以在瀏覽器中打開指向該服務的 IP。服務受到請求后,會將請求轉發給其中一個 Pod(不用理會是哪個)。通過利用服務作為訪問入口,這種抽象可以讓我們看到並將多個 Pod 當成一個來交互。
服務的總結
在本節中,我們介紹了給資源貼標簽,在服務中使用標簽作為選擇器,我們還定義並創建了一個 LoadBalancer 的服務。這滿足了我們希望伸縮應用程序規模的需求(只需加入新的貼了標簽的 Pod),並通過將服務作為訪問入口在 Pod 之間做負載均衡。
Kubernetes 實踐——部署
Kubernetes 部署可以幫助每一個應用程序的生命都保持相同的一點:那就是變化。此外,只有掛掉的應用程序才會一塵不變,否則,新的需求會源源不斷地涌現,更多代碼會被開發出來、打包以及部署。這個過程中的每一步都有可能出錯。
部署資源可以自動化應用程序從一版本升遷到另一版本的過程,並保證服務不間斷,如果有意外發生,它可以讓我們迅速回滾到前一個版本。
部署實踐
現在我們有兩個 Pod 和一個服務開放,而且它們之間有負載均衡(如圖 19 所示)。我們提到過現有的 Pod 還遠遠不夠完美。需要分開管理每一個 Pod(創建、更新、刪除和監視他們的情況)。快速更新和迅速回滾根本不可能!這樣是不行的,部署 Kubernetes 資源可以解決這里的每個問題。
圖19:現狀
在繼續下面的內容之前,讓我們復述下我們的目標,通過概述可以讓我們更好的理解部署資源的清單文件的定義。我們想要的是:
-
映像 rinormaloku/sentiment-analysis-frontend 的兩個 Pod;
-
部署期間服務不間斷;
-
Pod 貼有標簽 app: sa-frontend,所以我們可以通過 sa-frontend-lb 服務找到各個服務。
在下一節中,我們可以將這些需求反映到部署的定義中。
部署的定義
如下資源定義的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:部署;
#2 replicas:是部署 Spec 對象的一個屬性,定義了我們想運行多少的 Pod。所以是 2;
#3 type:指定從當前版本升遷到下個版本的時候,部署使用的策略。此處的策略 RollingUpdate 可以保證部署期間服務不間斷;
#4 maxUnavailable:是 RollingUpdate 對象的一個屬性,定義了在升級的時候,最大允許停止的 Pod 數量(與希望的狀態相比)。對我們的部署來說,我們有 2 個副本,這意味着在一個 Pod 停止后,我們還會有另外一個 Pod 運行,所以可以保證應用程序可訪問;
#5 maxSurge:是 RollingUpdate 對象的另一個屬性,定義了添加到部署的最大 Pod 數量(與希望的狀態相比)。對我們的部署來說,這意味着在向新版本遷移的時候,我們可以加一個 Pod,那么我們可以同時擁有個 3 個 Pod;
#6 template:指定 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,兩個是由部署創建的,而另外兩個是我們手動創建的。通過 kubectl delete pod <pod-name> 命令刪除其中一個手動創建的 Pod。
練習:刪除其中一個部署創建的 Pod,看看結果怎樣。在閱讀如下的解釋前,請先想想原因。
解釋:刪除一個 Pod 后,部署注意到當前的狀態(只有 1 個 Pod 在運行)與希望的狀態(2 個 Pod 處於運行狀態),所以它會再啟動一個 Pod。
那么,除了保持希望的狀態外,使用部署還有什么好處?讓我們先來看看好處。
好處 1:采用零停機時間部署(Zero-downtime)
產品經理帶着新的需求來找我們,說客戶想要在前端加一個綠色的按鈕。開發者寫好了代碼后,只需提供給我們一樣必須的東西,容器映像 rinormaloku/sentiment-analysis-frontend:green。然后就該我們了,我們需要采用零停機時間部署,這項工作很難嗎?讓我們試試看!
編輯 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,它會打開瀏覽器。我們可以看到按鈕 SEND 已更新了。
圖20:綠色按鈕
“RollingUpdate”背后的情況
在我們應用了新的部署后,Kubernetes 會將新狀態與舊的相比。在我們的例子中,新狀態需要兩個 rinormaloku/sentiment-analysis-frontend:green 映像的 Pod。這與當前的運行狀態不同,所以 Kubernetes 會執行 RollingUpdate。
圖21:RollingUpdate 替換 Pod
這里的 RollingUpdate 會根據我們指定的規格執行,也就是“maxUnavailable: 1″和“maxSurge: 1″。這意味着部署需要終止一個 Pod,並且僅可以運行一個新的 Pod。這個過程會不斷重復,一直到所有的 Pod被替換(如圖 21 所示)。
我們繼續介紹第二個好處。
聲明:出於娛樂的目的,下面的部分我按照小說的形式來書寫。
好處2:回滾到前一個狀態
產品經理跑進辦公室說,他遇到一個大麻煩!
產品經理大喊道:“產品環境中的應用程序有一個很關鍵的 bug!!需要馬上回滾到前一個版本”。
你冷靜地看着他,眼睛都沒有眨一下,就轉向了心愛的終端,然后開始敲:
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
你看了一眼前一個部署,然后問產品經理:“上個版本很多 bug,那前一個版本運行得很完美嗎?”
產品經理吼道:“是啊,你沒聽見我說嘛?!”
你沒理他,你知道該如何處理,於是你開始敲:
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 的部署。
圖 22:當前應用程序狀態
部署 SA-Logic
在終端中進入資源清單文件所在的目錄,然后運行如下命令:
kubectl apply -f sa-logic-deployment.yaml --record
deployment "sa-logic" created
SA-Logic 的部署會創建三個 Pod(Pod 上運行着我們的 Python 應用)。該命令還會給Pod 貼上 app: sa-logic 的標簽。有了這個標簽,我們就能從 SA-Logic 服務中利用選擇器來選擇這些 Pod。請花點時間打開 sa-logic-deployment.yaml,查看其內容。
這里的概念都是一樣的,因此我們可以直接講解下一個資源:SA-Logic 服務。
SA Logic 服務
首先來解釋下為什么需要該服務。我們的 Java 應用(在 SA-WebApp 部署的 Pod 中運行)依賴於 Python 應用提供的情感分析。但現在,與我們在本地運行一切服務時的狀況不同,我們並沒有一個單一的 Python 應用監聽着某個端口,我們只有兩個 Pod,如果需要,我們可以有更多的 Pod。
這就是為什么需要“服務”為一組提供相同功能的 Pod 提供訪問入口。這就是說,我們可以利用 SA-Logic 服務作為所有 SA-Logic Pod 的訪問入口。
運行如下命令:
kubectl apply -f service-sa-logic.yaml
service "sa-logic" created
更新后的應用程序狀態:現在我們有兩個 Pod 在運行(包含 Python 應用程序),並且 SA-Logic 服務提供了訪問入口,該訪問入口將在 SA-WebApp 的 Pod 中使用。
圖23:更新后的應用程序狀態
現在需要部署 SA-WebApp Pod,我們需要用到部署資源。
SA-WebApp 部署
我們已經學過了部署,盡管這個部署會用到更多的特性。打開 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 屬性。我們猜測它定義了環境變量 SA_LOGIC_API_URl,值為在 Pod 內的值為 http://sa-logic。但為什么要初始化成 http://sa-logic,sa-logic 究竟是什么?
我們先來介紹下 kube-dns。
KUBE-DNS
Kubernetes 有個特殊的 Pod 叫做 kube-dns。默認情況下,所有 Pod 都用它作為 DNS 服務器。kube-dns 的一個重要屬性就是它為每個建立的訪問都創建一條 DNS 記錄。
這就是說當我們創建 sa-logic 服務時,它會獲得一個 IP 地址。它的名字會加入到 kube-dns 中(和它的 IP 地址一起)。這樣所有 Pod 都能夠把 sa-logic 翻譯成 SA-Logic 服務的 IP 地址。
好,現在可以繼續了:
SA WebApp 部署(續)
運行以下命令:
kubectl apply -f sa-web-app-deployment.yaml --record
deployment "sa-web-app" created
完了。剩下的工作就是通過 LoadBalancer 服務將 SA-WebApp Pod 暴露到外部。LoadBalancer 服務提供了 SA-WebApp Pod 的訪問入口,這樣 React 應用程序就能發送 HTTP 請求了。
SA-WebApp 服務
打開 service-sa-web-app-lb.yaml 文件,可以看到內容還是挺熟悉的。
所以我們可以運行如下命令:
kubectl apply -f service-sa-web-app-lb.yaml
service "sa-web-app-lb" created
這樣架構就完成了。但還有一點不完美的地方。在部署 SA-Frontend Pod 之后,容器映像指向了 http://localhost:8080/sentiment 處的 SA-WebApp。但現在我們需要將其更新為 SA-WebApp LoadBalancer 的 IP 地址(其作用是 SA-WebApp Pod 的訪問入口)。
修補該不完美是個快速復習一切的絕佳機會(如果能不參照以下的指南獨立完成更好)。下面我們開始:
-
執行下列命令獲取 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 |
|-------------|----------------------|-----------------------------|
-
在 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));
}
構建靜態文件 npm build (需要先切換到 sa-front-end 目錄);
構建容器映像:
docker build -f Dockerfile -t $DOCKER_USER_ID/sentiment-analysis-frontend:minikube .
-
將映像推送到 Docker hub:
docker push $DOCKER_USER_ID/sentiment-analysis-frontend:minikube
-
編輯 sa-frontend-deployment.yaml 並使用新的映像;
-
執行 kubectl apply -f sa-frontend-deployment.yaml 命令。
刷新瀏覽器(如果你關閉了瀏覽器,則執行 minikube service sa-frontend-lb)。敲個句子試試看!
全文總結
Kubernetes 對團隊、項目都很有好處,它能簡化部署,提供伸縮性、靈活性,可以讓我們使用任何底層基礎設施。以后我們叫它 Supernetes 吧!
本文中覆蓋的內容:
-
構建/打包/運行 ReactJS、Java 和 Python 應用程序;
-
Docker容器,如何利用 Dockerfile 定義並構建容器;
-
容器注冊目錄,我們采用 Docker Hub 作為容器的代碼庫;
-
介紹了 Kubernetes 的最重要的內容;
-
Pod;
-
服務;
-
部署;
-
新概念,如零停機時間部署;
-
創建可伸縮的應用;
-
流程上,我們將整個微服務應用程序轉成了 Kubernetes 集群。
本文為你提供了堅實的基礎供你在實際的項目中使用,並且幫你更容易地學習更多新概念。