若是誰均可以在三個小時內學會Kubernetes,銀行爲什麼要爲這麼簡單的東西付一大筆錢?
若是你心存疑慮,我建議你不妨跟着我試一試!在完成本文的學習後,你就能在Kubernetes集羣上運行基於微服務的應用程序。我之因此能保證這一點,是由於我就是這麼向客戶介紹Kubernetes的。
這份指南與其餘文章有何不一樣之處?
至關多!大多數指南是從Kubernetes概念和kubectl命令這類簡單的東西開始的。它們假定讀者熟悉應用程序開發、微服務和Docker容器。
而在咱們這篇文章中,步驟是:
- 在你的計算機上運行基於微服務的應用程序。
- 爲微服務應用程序的每一個服務構建容器鏡像。
- 介紹Kubernetes。將基於微服務的應用程序部署到Kubernetes管理的集羣中。
按部就班的過程爲普通人掌握Kubernetes的
簡易性提供了必要的深度。沒錯,當你瞭解使用Kubernetes的上下文時,一切就變得很簡單了。廢話很少說,接下來看看咱們所要構建的東西。
應用程序演示
該應用程序只有一個功能:它以一個句子做爲輸入,使用文本分析計算出句子的情感值。
圖1. 情緒分析Web應用程序
從技術角度來看,這個應用程序由三個微服務組成。每一個微服務都具備一個特定功能:
- SA-Frontend:提供ReactJS靜態文件訪問的Nginx Web服務器。
- SA-WebApp:處理來自前端請求的Java Web應用程序。
- SA-Logic:執行情緒分析的Python應用程序。
微服務不是孤立存在的,它們能夠實現「關注點分離」,但仍是須要互相交互,理解這一點很是重要。
圖2. 情緒分析WebApp中的數據流
經過展現數據在它們之間的流動方式是對這種交互最好的說明:
- 客戶端應用程序請求index.html(繼而請求ReactJS應用程序的捆綁腳本)
- 用戶與應用程序交互會觸發對Spring WebApp的請求。
- Spring WebApp將情緒分析請求轉發給Python應用程序。
- Python應用程序計算情感值並將結果做爲響應返回。
- 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應用程序將會出現。
圖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 Web應用程序來監聽該端口!
圖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();
}
}
- SentimentController有一個名爲saLogicApiUrl的字段。字段值由
sa.logic.api.url
參數定義。
- 字符串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
設置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
- 實例化一個Flask對象。
- 定義POST請求的路徑。
- 從請求主體中提取「sentence」參數。
- 實例化一個匿名TextBlob對象並獲取第一個句子(這裏只有一個)的polarity。
- 將包含sentence和polarity內容的響應返回給調用者。
- 運行flask對象應用以監聽localhost:5000上的請求。
這些服務被設置爲相互通訊。在繼續前請從新打開前端localhost:80,並嘗試輸入句子!
圖6. 微服務架構完成了
在下一節中,咱們將繼續介紹如何在Docker容器中啓動服務,由於這是在Kubernetes集羣中運行服務的先決條件。
2. 爲每一個服務構建容器鏡像
Kubernetes是一個容器編排器。很顯然,咱們須要容器以便對其進行編排。那麼什麼是容器?這從Docker的文檔中能夠獲得最好的回答。
容器鏡像是一個輕量級、獨立的可執行軟件包,它包含了軟件運行所需的所有內容:代碼、運行時、系統工具、系統庫、設置等等。不管是基於Linux或是Windows的應用,容器化的軟件在任何環境中都能一樣運行。
這意味着容器能夠在包括生產服務器的任何計算機上無差異地運行。
爲了便於說明,咱們來比較一下如何使用虛擬機與容器來提供咱們的React應用程序服務。
經過VM來提供React靜態文件訪問
使用虛擬機的缺點:
- 資源效率低下,每一個虛擬機都有一個完整的操做系統的額外開銷。
- 它依賴於平臺。在你的計算機上正常工做的東西,在生產服務器上有可能沒法正常工做。
- 與容器相比,屬於重量級產品,且擴展較慢。
圖7. 虛擬機上的提供靜態文件訪問的Nginx Web服務器
經過容器來提供React靜態文件訪問
- 在Docker的協助下使用宿主機操做系統,資源利用率較高。
- 平臺無關。在你的計算機上正常運行的容器在任何地方也均可以正常工做。
- 使用鏡像層實現輕量化。
圖8. 容器中提供靜態文件訪問的Nginx Web服務器
這些是使用容器最顯着的特色和好處。有關容器的更多信息,可閱讀
Docker文檔。
構建React應用的容器鏡像(Docker簡介)
Docker容器的基礎構建塊是Dockerfile。Dockerfile開頭是一個基礎容器鏡像,後面是有關構建知足應用程序需求的新容器鏡像的一系列指令。
在開始定義Dockerfile以前,回憶一下使用Nginx提供靜態文件訪問的步驟:
- 構建靜態文件(npm run build)
- 啓動Nginx服務器
- 將sa-frontend項目中build目錄的內容複製到nginx/html中。
在下一節中,你會發現建立一個容器與在本地React設置中所作的極其相似。
定義SA-Frontend的Dockerfile
SA-Frontend的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
鏡像的文檔。
構建並推送容器
在推送鏡像以前,咱們須要一個容器Registry來託管容器。Docker Hub是一個免費的雲容器服務,咱們將用其進行演示。在繼續以前,你有三項任務:
- 安裝Docker CE。
- 在Docker Hub上註冊。
- 在終端中執行如下命令進行登陸:
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表示請求所要轉發的容器端口。
圖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。
測試容器化的應用程序
你會信任沒有通過測試的東西嗎?我是不會。接下來讓咱們對這些容器進行一輪測試。
- 運行sa-logic容器並配置其監聽5050端口:
docker run -d -p 5050:5000 $DOCKER_ID_USER/sentiment-analysis-logic
- 運行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'
- 運行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或端口獲取數據。而後你須要從新構建,並使用更新後的鏡像。
圖10. 運行在容器中的微服務
思考題:爲何要用Kubernetes?
在本節中,咱們瞭解了Dockerfile、如何使用它來構建鏡像以及將其推送到Docker Registry的命令。此外,咱們還學會了如何經過忽略無用文件以減小發送給構建上下文的文件數量。最後,咱們在容器中將應用程序運行了起來。那麼爲何要使用Kubernetes?這一點將在下一節中深刻探討,你不妨先思考一下:
- 咱們的情緒分析Web應用成了世界熱點,忽然每分鐘有一百萬個分析情緒的請求,sa-webapp和sa-logic遭遇了巨大的負載。咱們要如何擴展容器?
Kubernetes介紹
我能夠保證且絕不誇張地說,到文章結束時,你會問本身:「爲什麼咱們不稱它爲Supernetes(譯者注:借用Superman)?」
圖11. Supernetes
本文從開始到如今,已經覆蓋瞭如此多的背景和知識。你可能擔憂如今會是最難的部分,但實際上它最簡單。學習Kubernetes之因此使人生畏,惟一緣由是由於「其餘的一切」,而咱們已經很好地學完了這些東西。
Kubernetes是什麼
在從容器啓動咱們的微服務以後,咱們碰到了一個問題,下面咱們以問答形式進一步闡述它:
問:如何進行容器擴展?
答:再啓動一個。
問:如何在它們之間分擔負載?若是服務器已經使用到極限,而且咱們的容器須要另外一臺服務器,該怎麼辦?如何計算最佳的硬件利用率?
答:啊……呃……(我Google一下)。
問:如何在不影響任何內容的狀況下進行更新?若是須要,該如何回退到可工做的版本?
Kubernetes解決了全部這些問題(以及其餘更多問題!)。我能夠用一句話來介紹Kubernetes:「Kubernetes是一個容器編排器,它對底層基礎設施(容器運行的地方)進行了抽象。」
咱們對容器編排器有一個模糊的概念。後文的實踐中會看到它,但這是咱們第一次說到「對底層基礎設施進行抽象」,這點須要作個詳細說明。
對底層基礎設施進行抽象
Kubernetes提供了一套簡單的用於發送請求的API,對底層基礎設施進行抽象。Kubernetes會盡其最大能力來知足這些請求。好比說,能夠簡單地請求「Kubernetes啓動4個x鏡像的容器」,而後Kubernetes會找出利用率不足的節點,在這些節點中啓動新的容器(參見圖12)。
圖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來編排容器。
圖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。
圖14. Pod屬性
總結來講,Pod的主要屬性是(如圖14所示):
- 每一個Pod在Kubernetes集羣中都有一個惟一的IP地址。
- Pod能夠包含多個容器。這些容器共享相同的端口空間,所以它們能夠經過localhost進行通訊(因而可知,它們不能使用相同的端口),與其餘Pod的容器進行通訊必須使用其Pod的IP地址。
- 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
- Kind指定咱們想要建立的Kubernetes資源的種類。在這個例子中是Pod。
- Name:定義資源的名稱。咱們將它命名爲sa-front。
- Spec是用於定義資源的指望狀態的對象。Pod Spec最重要的參數是容器的數組。
- Image是要在此Pod中啓動的容器鏡像。
- Name是Pod中容器的惟一名稱。
- 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進行訪問?
- 如何在它們之間進行負載平衡?
圖15. 服務之間的負載平衡
Kubernetes爲此提供了Service資源。咱們在下一節對其進行說明。
Kubernetes實踐——Service
Kubernetes的Service資源爲提供相同功能服務的一組Pod充當入口。如圖16所示,此類資源負責發現服務和負載平衡,任務繁重。
圖16. Kubernetes Service維護着IP地址
咱們的Kubernetes集羣是由多個具備不一樣功能性服務的Pod組成的(前端、Spring WebApp和Flask Python應用程序)。那麼問題來了,Service如何知道要定位到哪些Pod?也就是說,它如何生成Pod的端點列表?
答案是,經過標籤(Labels)來實現的,這是一個兩步過程:
- 將標籤應用於全部咱們但願Service定位的Pod上
- 爲咱們的Service應用一個「篩選器(selector)」,以定義要定位哪一個標記的Pod。
使用圖片更易於理解:
圖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。
圖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
- Kind:一個Service。
- Type:規格類型,咱們選擇LoadBalancer是由於咱們要實現Pod之間的負載均衡。
- Port:指定Service接收請求的端口。
- Protocol:定義通訊協議。
- TargetPort:請求轉發的端口。
- Selector:包含選擇Pod的參數的對象。
- 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資源則解決了這些問題。
圖19. 當前狀態
在繼續以前,我說明咱們想要實現的目標,由於這將爲咱們提供一個總覽,讓咱們可以理解Deployment資源的清單定義。咱們想要的是:
- 運行rinormaloku/sentiment-analysis-frontend鏡像的兩個Pod
- 零停機時間部署
- 爲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
- Kind:一個Deployment。
- Replicas是Deployment Spec對象的一個屬性,用於定義咱們須要運行幾個Pod。這裏是2個。
- Type指定了這個Deployment在遷移版本時使用的策略。RollingUpdate策略將保證明現零當機時間部署。
- MaxUnavailable是RollingUpdate對象的一個屬性,用於指定執行滾動更新時不可用的Pod的最大數(與預期狀態相比)。咱們的部署具備2個副本,這意味着在終止一個Pod後,仍然有一個Pod在運行,這樣能夠保持應用程序的可訪問性。
- MaxSurge是RollingUpdate對象的另外一個屬性,用於定義能夠添加到部署中的最大Pod數量(與預期狀態相比)。在咱們的部署是,這意味着在遷移到新版本時,咱們能夠添加一個Pod,也就是同時有3個Pod。
- Template:指定Deployment建立新Pod所用的Pod模板。你立刻就發現它與Pod的定義類似。
app: sa-frontend
是使用該模板建立出來的Pod所使用的標籤。
- 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 <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
打開瀏覽器,咱們能夠看到該按鈕已更新。
圖20. 綠色按鈕
「滾動更新」的幕後
在應用新的Deployment後,Kubernetes會對新舊狀態進行比較。在咱們的示例中,新狀態請求兩個使用
rinormaloku/sentiment-analysis-frontend:green
的Pod。這與當前運行狀態不一樣,所以它會啓用RollingUpdate。
圖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。
圖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中使用的入口。
圖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地址。
解決這個問題讓咱們有機會再次快速地把從代碼到部署的全部內容過一遍(若是你不是遵循如下指南,而是單獨作這件事,可能會更有效)。讓咱們開始吧:
- 執行如下命令獲取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-frontend目錄)
- 構建容器鏡像:
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容器;如何使用Dockerfiles來定義和構建容器
- 容器Registry;咱們使用Docker Hub做爲容器的倉庫
- 咱們介紹了Kubernetes最重要的部分
- Pod
- Service
- Deployment
- 一些新概念,好比零停機時間部署
- 建立可伸縮的應用程序
- 在這個過程當中,咱們將整個微服務應用程序遷移到Kubernetes集羣上
原文連接:
Learn Kubernetes in Under 3 Hours: A Detailed Guide to Orchestrating Containers (翻譯:
梁曉勇)