三小时学会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 (翻译: 梁晓勇
相关文章
相关标签/搜索