非侵入式的微服务

教学目标

  1. 了解非侵入式以及侵入式微服务的概念
  2. 了解k8s相关的知识
  3. 了解istio的相关知识

预习内容

上课准备

git地址: http://120.92.88.48/luanmengjun/weifuwuday4

3.非侵入式的微服务

3.1 代码的侵入式跟非侵入式的介绍

​ 侵入式和非侵入式是两种不同的设计思想,通常用来描述软件框架或者组件对应用程序的影响程度。

​ 从代码的角度来说,侵入性强指的是耦合太强了,大多数代码表现为对框架类的继承,这样固然会可以利用框架的便利带来便利的功能,但是,重构就会带来非常痛苦的体验。非侵入式,主要是指未引入框架,这样的好处是可以提现高内聚,低耦合,但是在框架类提供的便利方法也无法实现。以下我们来具体看看两者在代码方面的区别。

img

  • 侵入式的设计

​ 侵入式的设计,就是设计者将框架或者组件的功能“推”给应用程序,要求应用程序必须遵循框架或者组件的规范,修改或者依赖应用程序的代码,才能使用框架或者组件的功能。例如,Java 中的继承,必须显示地表明要继承哪个接口,才能拥有接口的一些功能。

​ Struts1代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 //该RegisterAction是由Struts1来调用,Struts1会调用它的execute方法处理请求
public class RegisterAction extends Action {

@Override
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception {
RegisterFormBean registerForm = (RegisterFormBean) request.getAttribute("registerForm");
String username = registerForm.getUsername();
String password = registerForm.getPassword();
String email = registerForm.getEmail();
try {
System.out.println("向数据库中注册" + username + "用户");
request.setAttribute("message", "注册成功!!");
} catch (Exception e) {
request.setAttribute("message", "注册失败!!");
}

return mapping.findForward("message");
}

}

参考资料:https://struts.apache.org/getting-started/

  • 非侵入式的设计

​ 非侵入式的设计,就是设计者将应用程序的功能“拿”到框架或者组件中用,不要求应用程序修改或者依赖框架或者组件的代码,而是通过反射、代理、注解等技术,实现对应用程序的增强或者隔离。例如,Go 中的接口,不需要显示地继承接口,只需要实现接口的所有方法就叫实现了该接口,即便该接口删掉了,也不会影响应用程序。

​ 我们可以对比一下java跟go的接口的区别:

1
2
3
4
5
6
7
8
9
10
func (k *KceOpenapi) CreateCluster(ctx context.Context, req interface{}) (res *dto.CreateClusterRes, err error) {
resp, err := k.doRequest(ctx, consts.CreateCluster, req)
if err != nil {
return nil, err
}
if err = gconv.Struct(resp.ReadAll(), &res); err != nil {
return nil, err
}
return
}

go的interface参考资料:https://blog.csdn.net/yuqiang870/article/details/124746693

  • 侵入式的优缺点

优点:通过侵入代码与你的代码结合可以更好的利用侵入代码提供给的功能。 缺点:框架外代码就不能使用了,不利于代码复用。依赖太多重构代码太痛苦了。

  • 非侵入式的优缺点

优点:代码可复用,方便移植。非侵入式也体现了代码的设计原则:高内聚,低耦合。 缺点:无法复用框架提供的代码和功能。

微服务从侵入式到非侵入式

​ 在前面我们从代码的角度的去探究了侵入式与非侵入式,现在我们从微服务的角度来看看,什么是侵入式的微服务,什么是非侵入式的。

​ 目前主流的微服务框架如springCloud,基本都要求开发者在微服务框架本身的代码上进行开发,代码非常依赖框架,导致如果更换框架,就需要修改代码,并且,微服务之间的耦合度较高,这种就属于侵入式的框架,导致其可移植性以及可维护性都很低。随着云原生技术的发展,服务网格的概念兴起了,这是一种侵入式的框架,开发者在这个框架下不需要再引入框架代码,同时只需进行简单的配置就可以实现微服务的注册、发现、通信和治理等基本功能。大大提高了代码的可移植性,并与云原生技术紧密结合,可以轻松的在云上进行部署,是未来微服务的发展方向。

3.2 主流侵入式微服务框架的介绍

​ 侵入式设计,就是设计者将框架功能“推”给客户端,要求客户端的代码“知道”框架的代码,表现为客户端代码需要继承或引用框架提供的类或接口。目前主流的侵入式框架主要有springCloud、Dobbo以及Apache ServiceComb,以下为一一对其进行介绍:

3.2.1 Spring Cloud

​ Spring Cloud是一个基于Spring Boot的微服务开发框架,提供了一系列的工具和组件,用于快速构建分布式系统中的常见模式,例如配置管理、服务发现、断路器、智能路由、微代理、控制总线等。

​ Spring Cloud的核心特点是提供了统一的配置中心、注册中心和网关等基础设施,使得微服务之间可以轻松地进行通信和协作,同时也提供了分布式锁、领导选举、分布式会话、集群状态等高级功能。

​ Spring Cloud支持多种主流的微服务框架,例如Spring Cloud Netflix、Spring Cloud Alibaba、Spring Cloud Kubernetes等。其优势在于提供了成熟和完善的微服务开发和运维方案,支持多语言和多运行时,有丰富的社区和文档支持。

img

img

bootAdmin:https://blog.csdn.net/zouliping123456/article/details/121977792

turbine:https://blog.csdn.net/weixin_30783629/article/details/95431381

sleuth:https://www.cnblogs.com/qdhxhz/p/14781887.html

Cloud stream:https://blog.csdn.net/Dhjie_king/article/details/117416359

zipkin:https://blog.csdn.net/qq924862077/article/details/80285536

cloudTask:https://blog.51cto.com/u_15326439/5911294

ELK:Elasticsearch, Logstash, Kibana

​ Spring Cloud的主要特点有如下几个方面:

  • Spring Cloud拥有强大的社区支持和丰富的教程资源。
  • Spring Cloud采用了 Spring Boot 的约定优于配置的理念,简化了开发和配置的复杂度,提高了开发效率和可维护性。
  • Spring Cloud集成了多种 Netflix OSS 和 Alibaba Cloud 的开源组件,提供了服务注册与发现、配置中心、服务网关、负载均衡、熔断降级、分布式事务、分布式消息等功能,覆盖了微服务架构的各个方面。
  • Spring Cloud支持多种编程语言和存储技术,可以实现跨平台的服务通信和数据交互。
  • Spring Cloud适应了互联网时代需求,可以实现快速迭代、持续交付、动态扩缩容等能力,提高了系统的可用性和可靠性。

3.2.2 spring cloud 侵入性

​ 在介绍了spring cloud之后,我们以一个非常简单的微服务为例来看看,springCloud的侵入性主要体现在那些方面:

img

consumer代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {

@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}

@RestController
public class TestController {

private final RestTemplate restTemplate;

@Autowired
public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}

@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
public String echo(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);
}
}
}

provide代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@EnableDiscoveryClient
@Slf4j
public class ProviderApplication {

public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}

@RestController
class EchoController {
@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
public String echo(@PathVariable String string) {
log.info("receive msg: {}", string);
return "Hello Discovery " + string;
}
}
}
  • 侵入性的体现

​ 1.spring Cloud 是通过maven依赖的方式引入的,在打包时,会依赖对应的库,需要程序员考虑maven包之间的依赖以及版本匹配问题。

​ 2.业务代码与非业务代码在项目里时融合在一起的,很难进行完整的区分,导致业务可能因为非业务代码的原因出现问题。

​ 3.使用不同语言进行微服务开发时,需要针对不同的情况进行适配以及修改。

  • 侵入式引发的问题

​ 1.语言的问题:springCloud 主要使用java语言,当微服务中其他服务使用其他语言时,会提高整体微服务的学习成本,如何将其他服务融入当前框架中。

​ 2.组件的使用:虽然springCloud提供了非常多很好用的组件,但是这些组件都是通过maven仓库依赖的方式引入的,这些组件的升级以及跟微服务的版本的匹配问题会同时引入到项目中,成为程序员在业务代码逻辑之外需要考虑的额外问题。

​ 3.学习成本的问题:springCloud全家桶的功能虽然十分全面,但是,如果想具体用好每一个功能,都需要进行学习以及配置,对于应该专注与业务的程序员来说,学习成本还是偏高。

3.3 侵入式微服务框架的优缺点

​ 传统的侵入式微服务框架是指那些需要在业务代码中引入相关依赖或注解,或者需要遵循特定的编程模型或规范的微服务框架。例如,Spring Cloud、Dubbo、brpc等都属于侵入式微服务框架。它们的特点和优缺点如下:

  • 特点:
    • 提供了丰富的功能和组件,如服务发现、配置中心、负载均衡、熔断器、网关等
    • 有成熟的社区和文档支持,易于学习和使用
    • 有较高的性能和稳定性,适合高并发场景
  • 优点:
    • 降低了微服务开发的复杂度,提高了开发效率
    • 规范和统一了微服务间的调用方式,方便了服务治理
    • 有利于代码重用和复用,减少了冗余代码
  • 缺点:
    • 增加了业务代码的耦合度,降低了灵活性和可移植性
    • 需要适应框架的约束和限制,不能随意更换或升级框架
    • 难以适应云原生技术的变化和发展,需要额外的适配和对接

3.4 非侵入式微服务框架的演化以及概念

​ 非侵入的微服务框架是指不需要对现有的服务代码做任何修改,就可以实现微服务的注册、发现、调用等功能的框架。比如,将服务注册和服务调用从现有服务中抽离出来,形成一个服务代理。这样可以降低微服务的开发和维护成本,提高微服务的可扩展性和可靠性。

3.4.1 非侵入式框架的设计原理

非侵入式设计是一种编程方式,它采用组合或实现的方式使类和类之间弱耦合,从而提高灵活性。非侵入式设计,就是设计者将客户端的功能“拿”到框架中用,不需要客户端的代码引入或继承框架的代码,表现为客户端代码只需要实现框架提供的接口。

3.4.2 微服务框架的演变

3.4.2.1 小型机时代

第一个计算机网络诞生于1969年,也就是美军的阿帕网,阿帕网能够实现与其它计算机进行联机操作,但是早期仅仅是为了军事目的而服务

2000年初,中国的网民大约890万,很多人都不知道互联网为何物,因此大多数服务业务单一且简单,采用典型的单机+数据库模式,所有的功能都写在一个应用里并进行集中部署

img

​ 最古早的架构,简单的单机+数据库模型,所有功能全部在一个应用里面。

3.4.2.2 垂直拆分

随着应用的日益复杂与多样化,开发者对系统的容灾,伸缩以及业务响应能力有了更高的要求,如果小型机和数据库中任何一个出现故障,整个系统都会崩溃,若某个板块的功能需要更新,那么整个系统都需要重新发布,显然,对于业务迅速发展的万物互联网时代是不允许的。

如何保障可用性的同时快速响应业务的变化,需要将系统进行拆分,将上面的应用拆分出多个子应用。

img

优点:应用跟应用解耦,系统容错提高了,也解决了独立应用发布的问题

应用垂直拆分解决了应用发布的问题,但是随着用户数量的增加,单机的计算能力依旧是杯水车薪、

3.4.2.3 集群化负载均衡架构

用户量越来越大,就意味着需要更多的小型机,但是小型机价格昂贵,操作维护成本高。

此时更优的选择是采用多台PC机部署同一个应用的方案,但是此时就需要对这些应用做负载均衡,因为客户端不知道请求会落到哪一个后端PC应用上的。

img

负载均衡的思想:对外暴露一个统一的接口,根据用户的请求进行对应规则转发,同时负载均衡还可以做限流等等

有了负载均衡之后,后端的应用可以根据流量的大小进行动态扩容,我们称为”水平扩展”

优点:应用跟应用解耦,系统容错提高了,也解决了独立应用发布的问题,同时可以水平扩展来提供应用的并发量

3.4.2.4 服务化改造架构

虽然系统经过了垂直拆分,但是拆分之后发现在论坛和聊天室中有重复的功能,比如,用户注册、发邮件等等,一旦项目大了,集群部署多了,这些重复的功能无疑会造成资源浪费,所以会把重复功能抽取出来,名字叫”XX服务(Service)”

img

为了解决服务跟服务如何相互调用,需要一个程序之间的通信协议,所以就有了远程过程调用(RPC),作用就是让服务之间的程序调用变得像本地调用一样的简单

优点:在前面的架构之上解决了业务重用的问题

3.4.2.5 服务治理与微服务架构

服务治理要求:

1、当我们服务节点数几十上百的时候,需要对服务有动态的感知,引入了注册中心

2、当服务链路调用很长的时候如何实现链路的监控

3、单个服务的异常,如何能避免整条链路的异常(雪崩),需要考虑熔断、降级、限流

4、服务高可用:负载均衡

  • 微服务时代:

img

​ 微服务是在2012年提出的概念,微服务的希望的重点是一个服务只负责一个独立的功能。

拆分原则,任何一个需求不会因为发布或者维护而影响到不相关的服务,一切可以做到独立部署运维。

比如传统的“用户中心”服务,对于微服务来说,需要根据业务再次拆分,可能需要拆分成“买家服务”、“卖家服务”、“商家服务”等。

​ 典型代表:Spring Cloud,相对于传统分布式架构,SpringCloud使用的是HTTP作为RPC远程调用,配合上注册中心Eureka和API网关Zuul,可以做到细分内部服务的同时又可以对外暴露统一的接口,让外部对系统内部架构无感,此外Spring Cloud的config组件还可以把配置统一管理。

  • 微服务时代的问题

(1)最初是为了业务而写代码,比如登录功能、支付功能等,到后面会发现要解决网络通信的问题,虽然有 Spring Cloud里面的组件帮我们解决了,但是细想一下它是怎么解决的?在业务代码里面加上spring cloud maven依赖,加上spring cloud组件注解,写配置,打成jar的时候还必须要把非业务的代码也要融合在一起,称为“侵入式框架”;

(2)微服务中的服务支持不同语言开发,也需要维护不同语言和非业务代码的成本;

(3)业务代码开发者应该把更多的精力投入到业务熟悉度上,而不应该是非业务上,Spring Cloud虽然能解决微服务领域的很多问题,但是学习成本还是较大的;

(4)互联网公司产品的版本升级是非常频繁的,为了维护各个版本的兼容性、权限、流量等,因为Spring

Cloud是“代码侵入式的框架”,这时候版本的升级就注定要让非业务代码一起,一旦出现问题,再加上多语言之间的调用,工程师会非常痛苦;

(5)其实我们到目前为止应该感觉到了,服务拆分的越细,只是感觉上轻量级解耦了,但是维护成本却越高了,那么怎么 办呢?

3.4.2.6 新时代的非侵入式服务网格架构

​ 服务网格:指的是微服务网络应用之间的交互,随着规模和复杂性增加,服务跟服务调用错综复杂

img

​ 特点:

基础设施:服务网格是一种处理服务之间通信的基础设施层。

支撑云原生:服务网格尤其适用于在云原生场景下帮助应用程序在复杂的服务间可靠地传递请求。

网络代理:在实际使用中,服务网格一般是通过一组轻量级网络代理来执行治理逻辑的。

对应用透明:轻量网络代理与应用程序部署在一起,但应用感知不到代理的存在,还是使用原来的方式工作。

3.4.3 非侵入式微服务框架的优缺点

​ 非侵入式微服务架构是指那些不需要在业务代码中引入相关依赖或注解,或者不需要遵循特定的编程模型或规范的微服务架构。例如,Service Mesh、Cilium等都属于非侵入式微服务架构。它们的特点和优缺点如下:

  • 特点:
    • 提供了代码无侵入、代理透明、开发运维分离等优势
    • 有利于提高微服务的灵活性和可移植性
    • 有利于适应云原生技术的变化和发展
  • 优点:
    • 降低了微服务开发的复杂度,减少了代码耦合
    • 规范和统一了微服务间的调用方式,方便了服务治理
    • 有利于提高微服务的性能和稳定性,减少资源消耗
  • 缺点:
    • 增加了微服务部署和管理的难度,需要额外的工具和平台支持
    • 需要适应新的技术栈和架构模式,有一定的学习成本
    • 难以保证微服务间的数据一致性和事务性

3.5 主流非侵入式微服务框架简介

非侵入式的微服务框架跟云原生结合比较紧密,由于无论什么项目,都需要落地在云原生上,因此,为了更好的适合云原生,很多基于云原生的非侵入式框架都发展出来了,以下会为大家介绍一些目前主流的非侵入式的框架

3.5.1 Service Mesh 的背景

​ Service Mesh的思想是将服务间通信的处理从应用层下沉到基础设施层,通过一组轻量级的网络代理(Sidecar)来实现服务间的可靠、安全、可观测的通信,而不需要应用感知。Service Mesh可以为微服务架构提供流量控制、策略、网络安全和可观测性等功能,并解决传统微服务框架的侵入性、多语言支持和老旧系统维护等问题。Service Mesh是一种适应云原生应用的新一代微服务架构。

​ 想要了解Service mesh,必须了解其是如何演化的,这里参考Phil Calçado的文章《Pattern: Service Mesh》让大家可以更好的理解,下面我们将从服务间通讯的角度来说明,服务网格为何需要出现:

​ 太初之时:不同的服务之间互相通信,抽象如下

img

原始时代:然而现实远比想象的复杂,在实际情况中,通信需要底层能够传输字节码和电子信号的物理层来完成,在TCP协议出现之前,服务需要自己处理网络通信所面临的丢包、乱序、重试等一系列流控问题,因此服务实现中,除了业务逻辑外,还夹杂着对网络传输问题的处理逻辑。

img

Tcp时代:为了避免每个服务都需要自己实现一套相似的网络传输处理逻辑,TCP协议出现了,它解决了网络传输中通用的流量控制问题,将技术栈下移,从服务的实现中抽离出来,成为操作系统网络层的一部分。

img

网络七层模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

第一代微服务时代:在TCP出现之后,机器之间的网络通信不再是一个难题,这时分布式系统的微服务架构出现了。同时,为了解决分布式特有的通信问题,如熔断策略、负载均衡、服务发现、认证和授权、quota限制、trace和监控等等,微服务需要统一一套自己的通信语义,这样就产生了面向服务的微服务开发框架,如springcloud,这些框架实现了分布式系统通信需要的各种通用语义功能:如负载均衡和服务发现等,因此一定程度上屏蔽了这些通信细节,使得开发人员使用较少的框架代码就能开发出健壮的分布式系统。

img

第二代微服务时代:为了避免每个服务都需要自己实现一套分布式系统通信的语义功能,随着技术的发展,一些面向微服务架构的开发框架出现了,如Twitter的Finagle、Facebook的Proxygen以及Spring Cloud等等,这些框架实现了分布式系统通信需要的各种通用语义功能:如负载均衡和服务发现等,因此一定程度上屏蔽了这些通信细节,使得开发人员使用较少的框架代码就能开发出健壮的分布式系统。

img

第一代服务网格时代:微服务模式看似完美,但开发人员很快又发现,它也存在一些本质问题:

  • 其一,虽然框架本身屏蔽了分布式系统通信的一些通用功能实现细节,但开发者却要花更多精力去掌握和管理复杂的框架本身,在实际应用中,去追踪和解决框架出现的问题也绝非易事;
  • 其二,开发框架通常只支持一种或几种特定的语言,回过头来看文章最开始对微服务的定义,一个重要的特性就是语言无关,但那些没有框架支持的语言编写的服务,很难融入面向微服务的架构体系,想因地制宜的用多种语言实现架构体系中的不同模块也很难做到;
  • 其三,框架以lib库的形式和服务联编,复杂项目依赖时的库版本兼容问题非常棘手,同时,框架库的升级也无法对服务透明,服务会因为和业务无关的lib库升级而被迫升级;

​ 因此以Linkerd,Envoy,NginxMesh为代表的代理模式(边车模式)应运而生,这就是第一代Service Mesh,它将分布式服务的通信抽象为单独一层,在这一层中实现负载均衡、服务发现、认证授权、监控追踪、流量控制等分布式系统所需要的功能,作为一个和服务对等的代理服务,和服务部署在一起,接管服务的流量,通过代理之间的通信间接完成服务之间的通信请求,这样上边所说的三个问题也迎刃而解。

img

如果我们从一个全局视角来看,就会得到如下部署图:

img

  • 边车模式设计思想

​ 边车(Sidecar)模式设计思想的核心是将控制和逻辑分离,常用于我们在分布式架构中的逻辑和控制分离设计。迁移到我们的分布式架构中就是:我们不需要在服务中实现控制面上的东西,如监视、日志记录、限流、熔断、服务注册、协议适配转换等这些属于控制面上的东西,而只需要专注地做好和业务逻辑相关的代码,然后,由“边车”来实现这些与业务逻辑没有关系的控制功能。

img

img

​ 同样,sidecar独立运行的单机代理服务彼此建相对独立,缺乏一个对其进行统一管理的平台,也无法准确的监控不同sidecar的运行情况,因此,为了解决以上问题,出现了集中化的控制面板,所有的单机代理组件通过和控制面板交互进行网络拓扑策略的更新和单机数据的汇报。

Linkerd设计思想

img

​ 跟我们前面sidecar很类似,以前的调用方式都是服务来调用服务,在Linkerd思想要求所有的流量都走sidecar,Linkerd帮业务人员屏蔽了通信细节,通信不需要侵入到业务代码内部了,这样业务开发者就专注于业务开发的本身。Linkerd在面世之后,迅速获得用户的关注,并在多个用户的生产环境上成功部署、运行。2017年,Linkerd加入CNCF,随后宣布完成对千亿次生产环境请求的处理,紧接着发布了1.0版本,并且具备一定数量的商业用户,一时间风光无限,一直持续到Istio横空出世。

问题: 在早期的时候又要部署服务,又要部署sidecar,对于运维人员来说比较困难的,所以没有得到很好的发展,其实主要的 问题是Linkerd只是实现了数据层面的问题,但没有对其进行很好的管理。

  • 第二代Service Mesh

​ 第一代Service Mesh由一系列独立运行的单机代理服务构成,为了提供统一的上层运维入口,演化出了集中式的控制面板,所有的单机代理组件通过和控制面板交互进行网络拓扑策略的更新和单机数据的汇报。这就是以Istio为代表的第二代Service Mesh。

​ 引入数据层面管理:

img

​ 只看单机代理组件(数据面板)和控制面板的Service Mesh全局部署视图如下:

img

​ 相信大家看过上面的技术演变后,一定能对服务网格(service mesh)有一个比较全面的了解,我们可以以其发明者Buoyant的CEO William Morgan对服务网格的概念做一个定义,也即是服务网格是一个基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,服务网格保证请求在这些拓扑中可靠地穿梭。在实际应用当中,服务网格通常是由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但对应用程序透明。

​ 总结一下,Service Mesh具有如下优点:

  • 屏蔽分布式系统通信的复杂性(负载均衡、服务发现、认证授权、监控追踪、流量控制等等),服务只用关注业务逻辑;
  • 真正的语言无关,服务可以用任何语言编写,只需和Service Mesh通信即可;
  • 对应用透明,Service Mesh组件可以单独升级;

当然,Service Mesh目前也面临一些挑战:

  • Service Mesh组件以代理模式计算并转发请求,一定程度上会降低通信系统性能,并增加系统资源开销;
  • Service Mesh组件接管了网络流量,因此服务的整体稳定性依赖于Service Mesh,同时额外引入的大量Service Mesh服务实例的运维和管理也是一个挑战;

参考资料:https://zhuanlan.zhihu.com/p/61901608

CNCF云原生组织发展跟介绍

img

  • CNCF

​ CNCF 是一个开源软件基金会,致力于使云原生计算具有普遍性和可持续性。 云原生计算使用开源软件技术栈将应用程序部署为微服务,将每个部分打包到自己的容器中,并动态编排这些容器以优化资源利用率。 云原生技术使软件开发人员能够更快地构建出色的产品。

  • Kubernetes

​ 从2014年6月由Google宣布开源,到2015年7月发布1.0这个正式版本并进入CNCF基金会,再到2018年3月从CNCF基金会正式毕业,迅速成为容器编排领域的标准,是开源历史上发展最快的项目之一。也是世界上最受欢迎的容器编排平台也是第一个 CNCF 项目。

  • Linkerd

Scala语言编写,运行在JVM中,Service Mesh名词的创造者

2016年01月15号,0.0.7发布

2017年01月23号,加入CNCF组织

2017年04月25号,1.0版本发布

  • Istio

Google、IBM、Lyft发布0.1版本

Istio是开源的微服务管理、保护和监控框架。Istio为希腊语,意思是”起航“。

  • Prometheus

Prometheus 为云原生应用程序提供实时监控、警报包括强大的查询和可视化能力,并与许多流行的开源数据导入、导出工具集成。

  • Jaeger

Jaeger 是由 Uber 开发的分布式追踪系统,用于监控其大型微服务环境。 Jaeger 被设计为具有高度可扩展性和可用性,它具有现代 UI,旨在与云原生系统(如 OpenTracing、Kubernetes 和 Prometheus)集成。

参考资料:https://www.cncf.io/

3.5.2 K8s

​ istio依托于k8s这个基础平台,因此在讲解istio之前,让我们一起简单了解一下什么是k8s。

​ k8s 通常被描述为一个容器编排(container orchestration)平台。

  • 容器

​ 容器提供了一个轻量级的机制来隔离应用程序的环境。对于一个给定的应用程序,我们可以指定其配置和所需要安装的依赖,而不用担心其与同一台物理机上其他的应用程序发生冲突。我们将每个应用程序封装在容器镜像(container image)中,容器镜像可以可靠地运行在任何机器(只要机器有能力运行容器镜像)上,能够提供可移植的能力,即支持应用开发到部署的平滑过渡。此外,因为每个应用是独立的,不用担心环境冲突,所以在同一台物理机上可以部署多个容器,实现更高的资源(内存和 CPU)利用率,最终降低成本。

img

img

img

参考资料:https://blog.csdn.net/baidu_28340727/article/details/128430351

  • 容器中的问题

​ 比如,你的容器挂掉了会发生什么?或者更糟糕,容器发生错误,又或者机器运行容器失败会发生什么?要知道,容器是没有容错(fault tolerance)的能力。或者你有多个容器需要通信,如何在容器之间建立通信网络?当你启动、终止单个容器时,这种情况会发生什么变化?容器网络很容易变成一团混乱。最后,假设你的生产环境有多个机器,你将如何抉择使用哪个机器运行你的容器?

​ k8s 管理着每个容器的全生命周期,如根据需要启动或停止容器。如果一个容器意外关闭,k8s 将会运行另一个容器快速代替它。除此之外,k8s 提供一种机制,可以让应用程序相互通信,即使在底层单个容器正在被创建或正在被销毁的时候。最后,给定要运行的一组容器和集群上的一组机器,k8s 检查每个容器并调度应用程序到最佳机器上。

  • k8s的基本架构

img

  • 控制节点Master

​ K8S中的Master是集群控制节点,负责整个集群的管理和控制

ApiServer : 资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API注册和发现等机制,其他模块通过API Server查询或修改数据,只有API Server才直接和etcd进行交互;

Scheduler : 负责集群资源调度,通过API Server的Watch接口监听新建Pod副本信息,按照预定的调度策略将Pod调度到相应的node节点上;

ControllerManager : K8S里所有资源对象的自动化控制中心,通过 api-server 提供的 restful 接口实时监控集群内每个资源对象的状态,发生故障时,导致资源对象的工作状态发生变化,就进行干预,尝试将资源对象从当前状态恢复为预期的工作状态,常见的 controller 有 Namespace Controller、Node Controller、Service Controller、ServiceAccount Controller、Token Controller、ResourceQuote Controller、Replication Controller等;

Etcd : 是Kubernetes的存储状态的数据库(所有master的持续状态都存在etcd的一个实例中)

  • 工作节点Node

​ Node是K8S集群中的工作负载节点,每个Node都会被Master分配一些工作负载,当某个Node宕机时,其上的工作负载会被Master自动转移到其他节点上

Kubelet : 负责维护容器的生命周期,即通过控制docker,控制Pod 的创建、启动、监控、重启、销毁等工作,处理Master节点下发到本节点的任务;

KubeProxy : 负责制定数据包的转发策略,并以守护进程的模式对各个节点的pod信息实时监控并更新转发规则,service收到请求后会根据kube-proxy制定好的策略来进行请求的转发,从而实现负载均衡,总的来说,负责为Service提供cluster内部的服务发现和负载均衡;

Docker : 负责节点上容器的各种操作;

3.5.3 K8s的核心组成

学习kubernetes的核心,就是学习如何对集群上的Pod、Pod控制器、Service、存储等各种资源进行操作

Pod: kubernetes的最小控制单元,容器都是运行在pod中的,一个pod中可以有1个或者多个容器

Controller: 控制器,通过它来实现对pod的管理,比如启动pod、停止pod、伸缩pod的数量等等

Service: pod对外服务的统一入口,下面可以维护者同一类的多个pod

Label: 标签,用于对pod进行分类,同一类pod会拥有相同的标签

NameSpace: 命名空间,用来隔离pod的运行环境

img

  • K8s基本对象

Pod

Pod 对象是 k8s 中基础对象,由一个或多个(紧密相关)容器组成,并且这些容器共享网络和文件系统。类似于容器,Pod 被设计成拥有短暂的生命周期,不会期望特定的 Pod 长期存在。

img

通常你不会显示地通过清单文件创建 Pod 对象,因为使用更高级的对象管理 Pod 对象通常更简单。

Deployment

Deployment 对象包含由模板和副本数(要运行的模板数量)定义的 Pod 集合。你可以设置具体的副本数,也可以使用单独的 k8s 资源(如 HPA),基于系统性能指标如 CPU 使用率来控制副本数。

注意,Deployment 对象的控制器实际上会在背后创建 ReplicaSet 对象。然而,这对作为用户的你来说是抽象的。

img

虽然你不能依赖任何单一 Pod 永远的保持运行,但你可以依赖这样一个事实:集群总是能保证 n 个 Pod 运行中(其中 n 是你定义的副本数)。如果我们有一个 Deployment,其副本数为 10,并且其中 3 个 Pod 由于机器故障而崩溃,这 3 个 Pod 将会在集群中的其他机器上被调度运行起来。因此,Deployment 是最适合无状态应用程序使用,Pod 能够随时被替换而不破坏其他东西。

Deployment 还允许我们指定在拥有新的容器镜像时,我们希望如何进行更新。这篇文章很好地概述了不同操作选项。如果我们想覆盖默认值,我们可以在 Deployment 对象 spec 下附加一个 strategy 字段。 k8s 将确保优雅地关闭正在运行中的旧容器镜像的 Pod 并启动运行新容器镜像的新 Pod。

img

下面 的 YAML 文件提供了一个带有注释的示例,展示了如何定义 Deployment 对象。

为了让 k8s 知道应用程序大概的负载,我们还应该在 Pod 模板清单中提供资源限制参数。

  • deployment文件详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
apiVersion: apps/v1  # 指定api版本,此值必须在kubectl api-versions中  
kind: Deployment # 指定创建资源的角色/类型
metadata: # 资源的元数据/属性
name: demo # 资源的名字,在同一个namespace中必须唯一
namespace: default # 部署在哪个namespace中
labels: # 设定资源的标签
app: demo
version: stable
spec: # 资源规范字段
replicas: 1 # 声明副本数目
revisionHistoryLimit: 3 # 保留历史版本
selector: # 选择器
matchLabels: # 匹配标签
app: demo
version: stable
strategy: # 策略
rollingUpdate: # 滚动更新
maxSurge: 30% # 最大额外可以存在的副本数,可以为百分比,也可以为整数
maxUnavailable: 30% # 示在更新过程中能够进入不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
type: RollingUpdate # 滚动更新策略
template: # 模版
metadata: # 资源的元数据/属性
annotations: # 自定义注解列表
sidecar.istio.io/inject: "false" # 自定义注解名字
labels: # 设定资源的标签
app: demo
version: stable
spec: # 资源规范字段
containers:
- name: demo # 容器的名字
image: demo:v1 # 容器使用的镜像地址
imagePullPolicy: IfNotPresent # 每次Pod启动拉取镜像策略,三个选择 Always、Never、IfNotPresent
# Always,每次都检查;Never,每次都不检查(不管本地是否有);IfNotPresent,如果本地有就不检查,如果没有就拉取
resources: # 资源管理
limits: # 最大使用
cpu: 300m # CPU,1核心 = 1000m
memory: 500Mi # 内存,1G = 1024Mi
requests: # 容器运行时,最低资源需求,也就是说最少需要多少资源容器才能正常运行
cpu: 100m
memory: 100Mi
livenessProbe: # pod 内部健康检查的设置
httpGet: # 通过httpget检查健康,返回200-399之间,则认为容器正常
path: /healthCheck # URI地址
port: 8080 # 端口
scheme: HTTP # 协议
# host: 127.0.0.1 # 主机地址
initialDelaySeconds: 30 # 表明第一次检测在容器启动后多长时间后开始
timeoutSeconds: 5 # 检测的超时时间
periodSeconds: 30 # 检查间隔时间
successThreshold: 1 # 成功门槛
failureThreshold: 5 # 失败门槛,连接失败5次,pod杀掉,重启一个新的pod
readinessProbe: # Pod 准备服务健康检查设置
httpGet:
path: /healthCheck
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
successThreshold: 1
failureThreshold: 5
#也可以用这种方法
#exec: 执行命令的方法进行监测,如果其退出码不为0,则认为容器正常
# command:
# - cat
# - /tmp/health
#也可以用这种方法
#tcpSocket: # 通过tcpSocket检查健康
# port: number
ports:
- name: http # 名称
containerPort: 8080 # 容器开发对外的端口
protocol: TCP # 协议
imagePullSecrets: # 镜像仓库拉取**
- name: harbor-certification
affinity: # 亲和性调试
nodeAffinity: # 节点亲和力
requiredDuringSchedulingIgnoredDuringExecution: # pod 必须部署到满足条件的节点上
nodeSelectorTerms: # 节点满足任何一个条件就可以
- matchExpressions: # 有多个选项,则只有同时满足这些逻辑选项的节点才能运行 pod
- key: beta.kubernetes.io/arch
operator: In
values:
- amd64

Service

k8s 为每个 Pod 都分配了一个唯一的 IP 地址,可以使用这个 IP 来通信。但是,由于 Pod 的生命周期是短暂的,所以很难把流量发送到指定的容器上。例如,让我们考虑上面提到的 Deployment,有 10 个 Pod 运行,为机器学习模型提供 REST 接口服务。作为 Deployment 的一部分,这些运行的 Pod 可以随时更改,我们如何与服务器可靠地通信?这时 Service 对象映入眼帘。k8s 的 Service 提供了一个稳定的端点(endpoint),它可以用来将流量定向到所需的 Pod 上,即使底层 Pod 由于更新、扩展和故障而发生变化。Service 基于我们在 Pod 清单文件中元数据(metadata)中定义的标签(是键值对)知道应该向哪些 Pod 发送流量。

img

上图展示了 Service 通过 app=ml-model 标签将流量打到所有健康的 Pod 上。

以下 YAML 文件提供了一个示例,展示了我们如何根据早期的 Deployment 示例包装成 Service。

  • service的详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1 # 指定api版本,此值必须在kubectl api-versions中 
kind: Service # 指定创建资源的角色/类型
metadata: # 资源的元数据/属性
name: demo # 资源的名字,在同一个namespace中必须唯一
namespace: default # 部署在哪个namespace中
labels: # 设定资源的标签
app: demo
spec: # 资源规范字段
type: ClusterIP # ClusterIP 类型
ports:
- port: 8080 # service 端口
targetPort: http # 容器暴露的端口
protocol: TCP # 协议
name: http # 端口名称
selector: # 选择器
app: demo

参考资料:https://kubernetes.io/zh-cn/docs/home/

​ 简单部署一个Nginx

​ 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-test
name: nginx-test
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-test
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
generation: 4
labels:
app: nginx-test
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Tiller
app.kubernetes.io/name: nginx-test
helm.sh/chart: nginx-0.1.0
version: 0.1.0
name: nginx-test
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx-test
app.kubernetes.io/name: nginx-test
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
kmse.jekins.build: "1"
creationTimestamp: null
labels:
app: nginx-test
app.kubernetes.io/instance: release-name
app.kubernetes.io/name: nginx-test
version: 0.1.0
spec:
containers:
- image: hub.kce.ksyun.com/ksyun-public/nginx:latest
imagePullPolicy: Always
name: kce-nginx
resources: {}
securityContext:
privileged: false
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: ksyunregistrykey
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
  • pod的创建流程

img

1、客户端提交创建请求,可以通过API Server的Restful API,也可以使用kubectl命令行工具。支持的数据类型包括JSON和YAML。

2、API Server处理用户请求,存储Pod数据到etcd。

3、调度器通过API Server查看未绑定的Pod。尝试为Pod分配主机。

4、过滤主机 (调度预选):调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。

5、主机打分(调度优选):对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。

6、选择主机:选择打分最高的主机,进行binding操作,结果存储到etcd中。

7、kubelet根据调度结果执行Pod创建操作: 绑定成功后,scheduler会调用APIServer的API在etcd中创建一个boundpod对象,描述在一个工作节点上绑定运行的所有pod信息。运行在每个工作节点上的kubelet也会定期与etcd同步boundpod信息,一旦发现应该在该工作节点上运行的boundpod对象没有更新,则调用Docker API创建并启动pod内的容器。

​ 在整个生命周期中,Pod会出现5种状态(相位),分别如下:

挂起(Pending): apiserver已经创建了pod资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中

运行中(Running): pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成

成功(Succeeded): pod中的所有容器都已经成功终止并且不会被重启

失败(Failed): 所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态

未知(Unknown): apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致

  • 应用持久化存储(pv 以及 pvc)

​ 而Persistent Volume则是一个K8S资源对象,它是独立于Pod的,能单独创建。Persistent Volume 不与Pod发生直接关系,而是通过 Persistent Volume Claim(PVC) 来与Pod绑定关系。在定义Pod时,为Pod指定一个PVC,Pod在创建时会根据PVC要求,从现有集群的PV中,选择一个合适的PV绑定,或动态建立一个新的PV,再与其进行绑定。

Persistent Volume(PV):用于定义各种存储资源的配置信息,一个PV对应一个volume,定义一个PV内容包括了 存储类型、存储大小和访问模式等。

Persistent Volume Claim(PVC):描述对PV的一个请求。请求信息包含存储大小、访问模式等。PVC只会选择符合自己要求的PV进行绑定,然后在定义pod时指定使用哪个PVC就可以了。

  • pvc以及pv的绑定原理

​ PVC和PV的设计,其实跟面向对象的思想完全一致,PVC是面向对象编程中的接口,PV是接口具体的实现。

用户只需通过PVC来声明自己的存储需求,比如存储大小、可读写权限等,类似于调用接口函数并传入属性参数,而不用关心后端存储实现细节,这些都交由运维人员统一管理即可。

​ Pod是直接与PVC绑定关系,再根据PVC存储需求,去找到对应PV。PVC只有绑定了PV之后才能被Pod使用。

PersistentVolume Controller 会不断地查看当前每一个PVC,是不是已经处于Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的PV,并尝试将其与这个PVC进行绑定。这样,Kubernetes就可以保证用户提交的每一个PVC,只要有合适的PV出现,它就能够很快进入绑定状态。

​ 在提交PVC后,是如何找到对应的PV:先根据PVC的accessModes匹配出PV列表,再根据PVC的Capacity、StorageClassName、Label Selector进一步筛选PV。如果满足条件的PV有多个,选择PV的size最小的,accessmodes列表最短的PV,也即最小适合原则。

pvc跟pv的绑定规则:

VolumeMode:被消费PV的VolumeMode需要和PVC一致。

AccessMode:被消费PV的AccessMode需要和PVC一致。

StorageClassName:如果PVC定义了此字段,则PV也必须有对应字段才能进行绑定。

LabelSelector:通过标签(labels)匹配的方式从PV列表中选择合适的PV绑定。

Size:被消费PV的capacity必须大于或等于PVC的存储容量需求才能被绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
nfs: # 存储类型,与底层真正存储对应
capacity: # 存储能力,目前只支持存储空间的设置
storage: 2Gi
accessModes: # 访问模式
storageClassName: # 存储类别
persistentVolumeReclaimPolicy: # 回收策略

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
namespace: dev
spec:
accessModes: # 访问模式
selector: # 采用标签对PV选择
storageClassName: # 存储类别
resources: # 请求空间
requests:
storage: 5Gi

参考资料:https://blog.csdn.net/qq_19676401/article/details/121145651

  • kubect相关命令
1
2
3
4
sudo yum remove -y  kubectl #卸载kubectl
传入 kubectl 文件
chmod +x ./kubectl
mv ./kubectl /usr/local/bin/kubectl

kubectl的安装:https://www.kancloud.cn/zatko/kubernetes_k8s/2290707

kubectl 命令的语法如下:

kubectl [command] [TYPE] [NAME] [flags]

kubectl命令中,指定执行什么操作(command,如create,delete,describe ,get ,apply 等)指定什么类型资源对象(type)指定此类型的资源对象名称(name)指定可选参数(flags)

查看pod,nodee,service,endpoints,secret等信息

1
kubectl get 组件名      #例如kubectl get pod

查看资源状态,比如有一组deployment内的pod没起来,一般用于pod调度过程出现的问题排查

1
kubectl describe pod pod名    #先用kubectl get pod查看

查看node节点或者是pod资源(cpu,内存资源)使用情况

1
kubectl top 组件名     #例如kubectl top node  kubectl top pod

进入pod内部

1
kubectl exec -ti pod名 /bin/bash   #先用kubectl get pod查看

删除pod

1
kubectl delete pod -n

查看集群健康状态

1
kubectl get cs

基于 pod.yaml 定义的名称删除指定资源

1
kubectl delete -f pod.yaml

查看容器的日志

1
kubectl logs -f <pod-name> # 实时查看日志

创建资源

1
kubectl apply -f ./my-manifest.yaml 

批量删除异常pod

1
kubectl get pods -n  namespace | grep OutOfcpu | awk '{print $1}' | xargs kubectl delete pod -n  namespace

参考资料:https://blog.csdn.net/langzi6/article/details/125139698

  • 演示挂载nginx的index文件

使用docker直接挂载

1
docker ps # 找到对应的容器

img

1
2
docker cp index.html 178888c944e8:/root/index.html #docker cp 命令将文件传入
kubectl exec -it nginx-test-57df77cb76-pg4db /bin/bash #进入pod中查看是否挂载到对应目录

直接挂载本地节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-test
name: nginx-test
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-test
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
generation: 4
labels:
app: nginx-test
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Tiller
app.kubernetes.io/name: nginx-test
helm.sh/chart: nginx-0.1.0
version: 0.1.0
name: nginx-test
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx-test
app.kubernetes.io/name: nginx-test
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
kmse.jekins.build: "1"
creationTimestamp: null
labels:
app: nginx-test
app.kubernetes.io/instance: release-name
app.kubernetes.io/name: nginx-test
version: 0.1.0
spec:
volumes:
- name: index
hostPath:
path: /root/campus/index/index.html
nodeName: 10.0.0.188
containers:
- image: hub.kce.ksyun.com/ksyun-public/nginx:latest
imagePullPolicy: Always
name: kce-nginx
resources: {}
securityContext:
privileged: false
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- name: index
mountPath: /usr/share/nginx/html/index.html
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: ksyunregistrykey
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30

使用pvc、pv进行挂载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
apiVersion: v1
kind: PersistentVolume
metadata:
finalizers:
- kubernetes.io/pv-protection
labels:
type: local
name: nginx-pv
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Gi
hostPath:
path: /root/campus/index
type: ""
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: usedFor
operator: In
values:
- nginx
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
volumeMode: Filesystem


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
finalizers:
- kubernetes.io/pvc-protection
labels:
app.kubernetes.io/managed-by: Helm
name: nginx-pvc
namespace: nginx
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
storageClassName: manual
volumeMode: Filesystem
volumeName: nginx-pv

apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-test
name: nginx-test
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-test
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
generation: 4
labels:
app: nginx-test
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Tiller
app.kubernetes.io/name: nginx-test
helm.sh/chart: nginx-0.1.0
version: 0.1.0
name: nginx-test
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx-test
app.kubernetes.io/name: nginx-test
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
kmse.jekins.build: "1"
creationTimestamp: null
labels:
app: nginx-test
app.kubernetes.io/instance: release-name
app.kubernetes.io/name: nginx-test
version: 0.1.0
spec:
volumes:
- name: index
persistentVolumeClaim:
claimName: nginx-pvc
containers:
- image: hub.kce.ksyun.com/ksyun-public/nginx:latest
imagePullPolicy: Always
name: kce-nginx
resources: {}
securityContext:
privileged: false
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /root
name: index
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: ksyunregistrykey
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30

3.5.4 k8s与Istio

  • Istio是Kubernetes的好帮手

img

​ 从场景来看,Kubernetes已经提供了非常强大的应用负载的部署、升级、扩容等运行管理能力。Kubernetes 中的 Service 机制也已经可以做服务注册、服务发现和负载均衡,支持通过服务名访问到服务实例。

​ 从微服务的工具集观点来看,Kubernetes本身是支持微服务的架构,在Pod中部署微服务很合适,也已经解决了微服务的互访互通问题,但对服务间访问的管理如服务的熔断、限流、动态路由、调用链追踪等都不在Kubernetes的能力范围内。因此,istio横空出世,完成了这个任务

img

  • k8s为istio提供地基

​ 1.提供统一的数据面

​ 数据面Sidecar运行在Kubernetes的Pod里,作为一个Proxy和业务容器部署在一起。在服务网格的定义中要求应用程序在运行的时感知不到Sidecar的存在。而基于Kubernetes的一个 Pod 多个容器的优秀设计使得部署运维。对用户透明,用户甚至感知不到部署 Sidecar的过程。用户还是用原有的方式创建负载,通过 Istio 的自动注入服务,可以自动给指定的负载注入Proxy。如果在另一种环境下部署和使用Proxy,则不会有这样的便利。

img

​ 2.提供域名访问机制,省去类似Eureka的注册中心

img

3.基于Kubernetes CRD描述规则

Istio的所有路由规则和控制策略都是通过 Kubernetes CRD实现的,因此各种规则策略对应的数据也被存储在 Kube-apiserver 中,不需要另外一个单独的 APIServer 和后端的配置管理。所以,可以说Istio的APIServer就是Kubernetes的APIServer,数据也自然地被存在了对应Kubernetes的etcd中。

Kubernetes CRD:CRD 本身是一种 Kubernetes 内置的资源类型,即自定义资源的定义,用于描述用户定义的资源是什么样子。

3.5.5 Istio

​ Istio是一个提供连接、保护、控制以及观测功能的开放平台,通过提供完整的非侵入式的微服务治理解决方案,能够很好的解决云原生服务的管理、网络连接以及安全管理等服务网络治理问题。Istio 的架构从逻辑上分为数据平面和控制平面。数据平面由一组智能代理(Envoy)组成,被部署为 Sidecar。这些代理负责协调和控制微服务之间的所有网络通信。它们还收集和报告所有网格流量的遥测数据。控制平面管理并配置代理来进行流量路由。

img

​ 下面我们来看一下istio的架构

img

​ 其主要数据平面以及控制平面组成

  • 数据平面:以一组Sidecar的方式部署的Evnoy组成,所有进入和流出服务的流量都会被Envoy拦截,并与控制面进行交互,根据配置执行相应的通信功能。Envoy之所以称之为智能,是因为Envoy相对于其他代理来说有着更丰富的治理能力和灵活的配置方式,并且支持各种插件可用于扩展流量治理能力,并生成遥测数据。
  • 控制平面:Pilot、Citadel、Galley组成的一个单进程、多模块的istiod组成了istio的控制平面。其中Pilot组件负责提供服务发现、智能路由(如金丝雀发布)和弹性功能(如超时、重试);Citadel负责安全,管理密钥和证书;Galley负责对配置的验证和处理等功能。Istiod作为一种全新的设计,构建了一套标准的控制面规范,主要提供服务发现、规则配置和证书管理等功能,并向数据平面传递这些信息。
  • istio的特征
  • 连接:对网格内部的服务之间的调用所产生的流量进行智能管理,并以此为基础,为微服务的部署、测试和升级等操作提供有力保障。

img

  • 安全:为网格内部的服务之间的调用提供认证、加密和鉴权支持,在不侵入代码的情况下,加固现有服务,提高其安全性。

安全提供网格内部的安全保障,就应具备服务通信加密、服务身份认证和服务访问控制(授权和鉴权)功能。

上述功能通常需要数字证书的支持,这就隐藏了对CA的需求,即需要完成证书的签发、传播和更新业务。

除了上述核心要求,还存在对认证失败的处理、外部证书(统一 CA)的接入等相关支撑内容。

  • 策略:在控制平面定制策略,并在服务中实施。

img

Istio 通过可动态插拔、可扩展的策略实现访问控制、速率限制、配额管理等功能使得资源在消费者之间公平分配

  • 观察:对服务之间的调用进行跟踪和测量,获取服务的状态信息。

动态获取服务运行数据和输出,提供强大的调用链、监控和调用日志收集输出的能力。配合可视化工具,可方便运维人员了解服务的运行状况,发现并解决问题。

3.5.6 Istio架构详解

Istio的架构,分为控制平面和数据面平两部分。

  • 数据平面:由一组智能代理([Envoy])组成,被部署为 sidecar。这些代理通过一个通用的策略和遥测中心传递和控制微服务之间的所有网络通信。
  • 控制平面:管理并配置代理来进行流量路由。此外,控制平面配置 Mixer 来执行策略和收集遥测数据。

img

Pilot:提供服务发现功能和路由规则

Mixer:策略控制,比如:服务调用速率限制

Citadel:起到安全作用,比如:服务跟服务通信的加密

Sidecar/Envoy: 代理,处理服务的流量

(1)自动注入:(由架构图得知前端服务跟后端服务都有envoy,我们这里以前端服务envoy为例说明)指在创建应用程序时自动注入 Sidecar代理。那什么情况下会自动注入你?在 Kubernetes场景下创建 Pod时,Kube-apiserver调用管理平面组件的 Sidecar-Injector服务,然后会自动修改应用程序的描述信息并注入Sidecar。在真正创建Pod时,在创建业务容器的同时在Pod中创建Sidecar容器。

总结:会在pod里面自动生产一个代理,业务服务无感知

(2)流量拦截:在 Pod 初始化时设置 iptables 规则,当有流量到来时,基于配置的iptables规则拦截业务容器的入口流量和出口流量到Sidecar上。但是我们的应用程序感知不到Sidecar的存在,还以原本的方式进行互相访问。在架构图中,流出前端服务的流量会被 前端服务侧的 Envoy拦截,而当流量到达后台服务时,入口流量被后台服务V1/V2版本的Envoy拦截。

总结:Pilot提供了服务发现功能,调用方需要到Pilot组件获取提供者服务信息

img

(3)服务发现:前端服务怎么知道后端服务的服务信息呢?这个时候就需要服务发现了,所以服务发起方的 Envoy 调用控制面组件 Pilot 的服务发现接口获取目标服务的实例列表。在架构图中,前端服务内的 Envoy 通过 控制平面Pilot 的服务发现接口得到后台服务各个实例的地址,为访问做准备。

总结:Pilot提供了服务发现功能,调用方需要到Pilot组件获取提供者服务信息

(4)负载均衡:数据面的各个Envoy从Pilot中获取后台服务的负载均衡衡配置,并执行负载均衡动作,服务发起方的Envoy(前端服务envoy)根据配置的负载均衡策略选择服务实例,并连接对应的实例地址。

总结:Pilot也提供了负载均衡功能,调用方根据配置的负载均衡策略选择服务实例

(5)流量治理:Envoy 从 Pilot 中获取配置的流量规则,在拦截到 入口 流量和出口 流量时执行治理逻辑。比如说,在架构图中,前端服务的 Envoy 从 Pilot 中获取流量治理规则,并根据该流量治理规则将不同特征的流量分发到后台服务的v1或v2版本。当然,这只是Istio流量治理的一个场景,Istio支持更丰富的流量治理能力。

img

总结:Pilot也提供了路由转发规则

(6)访问安全:在服务间访问时通过双方的Envoy进行双向认证和通道加密,并基于服务的身份进行授权管理。在架构图中,Pilot下发安全相关配置,在前端模块服务和后端服务的Envoy上自动加载证书和密钥来实现双向认证,其中的证书和密钥由另一个控制平面组件Citadel维护。

总结:Citadel维护了服务代理通信需要的证书和密钥

(7)服务遥测:在服务间通信时,通信双方的Envoy都会连接控制平面组件Mixer上报访问数据,并通过Mixer将数据转发给对应的监控后端。比如说,在架构图中,前端模块服务对后端服务的访问监控指标、日志和调用链都可以通过Mixer收集到对应的监控后端。

img

总结:Mixer组件可以收集各个服务上的日志,从而可以进行监控

(8)策略执行:在进行服务访问时,通过Mixer连接后端服务来控制服务间的访问,判断对访问是放行还是拒绝。在架构图中,数据面在转发服务的请求前调用Mixer接口检查是否允许访问,Mixer 会做对应检查,给代理(Envoy)返回允许访问还是拒绝, 比如:可以对前端模块服务到后台服务的访问进行速率控制。

img

总结:Mixer组件可以对服务速率进行控制(也就是限流)

(9)外部访问:在架构图中,外部服务通过Gateway访问入口将流量转发到服务前端服务内的Envoy组件,对前端服务的负载均衡和一些流量治理策略都在这个Gateway上执行。

总结:这里总结在以上过程中涉及的动作和动作主体,可以将其中的每个过程都抽象成一句话:服务调用双方的Envoy代理拦截流量,并根据控制平面的相关配置执行相应的服务治理动作,这也是Istio的数据平面和控制平面的配合方式。

3.5.7 Sidecar的注入方式

​ sidecar的注入方式分为手动注入以及自动注入:

​ 手动注入:使用istioctl命令即可进行手动注入

1
istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f -

​ 自动注入:在命名空间上设置istio-injection = enabled标签并且启用了注入Webhook时,在该名称空间中创建的所有新容器都将自动添加一个sidecar。

1
kubectl label ns default istio-injection=enabled

​ 自动注入原理:在 Kubernetes环境下,根据自动注入配置,Kube-apiserver在拦截到 Pod创建的请求时,会调用自动注入服务 istio-sidecar-injector 生成 Sidecar 容器的描述并将其插入原 Pod的定义中,这样,在创建的 Pod 内, 除了包括业务容器,还包括 Sidecar容器。这个注入过程对用户透明,用户使用原方式创建工作负载。

  • Envoy

​ Envoy是Lyft开源的一个C++实现的代理(Proxy),和Nginx及HAProxy类似,可代理L3/L4层和L7层。代理是它最核心和基础的功能,它也是服务网格框架的Sidecar。

img

如上图所示,Envoy 作为 Sidecar 使用时,需要和服务部署在同一台机器或者 Pod 中,用户访问其他服务时,流量会被自动劫持到 Envoy 中。

下面结合一个具体的例子来讲解。下图是 Productpage 服务通过 HTTP 协议,调用 review 服务的过程。

img

​ 通过 Iptables 对流量进行劫持,将 Productpage 访问 Reviews 的流量转发到 Envoy 的出流量 15001 端口上。Envoy 先根据 virtual_hosts 进行匹配,再通过路由匹配,发现路由对应的 Cluster,通过服务发现找到 Cluster 对应的 EndPoint,将流量转发到 10.40.0.15:9080 的 Pod 上。Reviews 的 Pod 通过 Iptables 对流量进行劫持,将流量劫持到 Envoy 的入流量端口 15006 上。Envoy 将本地流量转发到对应的本地地址 127.0.0.1:9080,这里不需要对流量进行识别,因为流量被转发到入流量端口 15006 上,这个端口的配置用于本地流量的转发。

到这里整个 Sidecar 的流量出入过程就结束了。出入流量都经由 Envoy,最终被正确的转发到了 Reviews 的 Pod 上面。

参考资料:https://www.cnblogs.com/muzinan110/p/17060326.html

3.5.8 Istio的Ingress Gateway

​ ingressgateway 就是入口处的 Gateway,从网格外访问网格内的服务就是通过这个Gateway进行的。ingressgateway比较特别,是一个Loadbalancer类型的Service,不同于其他服务组件只有一两个端口,ingressgateway 开放了一组端口,这些就是网格内服务的外部访问端口。

  • istio ingressgateway是通过一个Gateway资源来配置的,它可以指定暴露的端口、协议、主机等信息,但不包含任何流量路由配置。
  • istio ingressgateway的流量路由配置是通过一个VirtualService资源来实现的,它可以指定匹配的条件和目标服务,以及一些高级的功能,如故障注入、重试、超时等。
  • istio ingressgateway可以利用istio的其他功能,如安全、可观察性、流量控制等。 例如,istio ingressgateway可以实现TLS终止、JWT认证、指标收集、分布式追踪等。

img

由于gateway暴露了一个端口,外部的请求就可以根据这个端口把请求发给gateway了然后由gateway把请求分发给网格内部的pod上

ingressgateway的配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# gw和vs配置
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 443 # 使用gw-ingress的443端口
name: https_test_name # 这个名称可以随意写
protocol: HTTPS
hosts:
- "bookinfo.com"
tls:
mode: SIMPLE
serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
privateKey: /etc/istio/ingressgateway-certs/tls.key
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage.istio.svc.cluster.local
port:
number: 9080

3.5.9 Istio网关Gateway概述

​ Istio网关Gateway是一个负责处理南北向流量的组件,它通常会暴露服务网格内部的服务,以便外部的请求能够访问到服务网格中的服务。Istio网关Gateway支持多种协议,包括HTTP、HTTPS和GRPC等。

​ 在Istio网关Gateway中,每个服务器都包含一个或多个端口,每个端口都定义了一种协议和相应的配置。Istio网关Gateway还可以定义多个TLS证书,以便对传输的数据进行加密和解密。

​ 在配置Istio网关Gateway时,我们需要指定其所使用的负载均衡算法和服务发现机制。Istio网关Gateway支持多种服务发现机制,包括Kubernetes服务发现、Consul服务发现和Eureka服务发现等。

​ gateway 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
generation: 3
name: my-gateway
namespace: test
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- my.gateway.com
port:
name: http
number: 9080
protocol: HTTP

3.5.10 Istio 虚拟服务(Virtual service)

​ 在istion中,虚拟服务(Virtual service)和目标规则(destination rule)是流量路由功能的关键组成部分。在Istio所提供的基本连接和发现基础上,通过虚拟服务,能够将请求路由到Istio网格中的特定服务。每个虚拟服务由一组路由规则组成,这些路由规则使Istio能够将虚拟服务的每个给定请求匹配到网格内特定的目标地址。

​ virtual service 配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-virtualService
namespace: test
spec:
gateways:
- my-gateway
hosts:
- my.gateway.com
http:
- match:
- uri:
prefix: /
route:
- destination:
host: nginx-test
port:
number: 80

演示配置流量分发并通过公网访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
generation: 3
name: nginx-gateway
namespace: nginx
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- my.gateway.com
port:
name: http
number: 80
protocol: HTTP


apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nginx-vs
namespace: nginx
spec:
gateways:
- nginx-gateway
hosts:
- my.gateway.com
http:
- match:
- uri:
prefix: /
route:
- destination:
host: nginx-test.default.svc.cluster.local
port:
number: 80

3.5.11 Istio 目的规则(DestinationRule)

​ DestinationRule,根据字面意思即目标规则,我们可以理解VirtualService为流量的路由,那么DestinationRule则为流量路由之后的去处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: bookinfo-ratings
spec:
# 含义同VirtualService中destination的host字段一致。
host: ratings.prod.svc.cluster.local
# 流量策略,包括负载均衡、连接池策略、异常点检查等
trafficPolicy:
# 负载均衡策略,支持随机负载均衡 /按权重负载均衡 /最少请求负载均衡 / hash轮训等
loadBalancer:
simple: LEAST_CONN
# 连接池策略
connectionPool:
# tcp连接池设置
tcp:
maxConnections: 100
connectTimeout: 30ms
tcpKeepalive:
time: 7200s
interval: 75s
http:
http2MaxRequests: 1000
maxRequestsPerConnection: 10
# 异常点检查
outlierDetection:
consecutiveErrors: 7
interval: 5m
baseEjectionTime: 15m
# tls设置
tls:
mode: MUTUAL
clientCertificate: /etc/certs/myclientcert.pem
privateKey: /etc/certs/client_private_key.pem
caCertificates: /etc/certs/rootcacerts.pem
# 服务端点集合
subsets:
# subset名称可以用于路由规则中的流量拆分,与virtualService的subset的引用
- name: testversion
# 使用标签对服务注册表中的服务端点进行筛选
labels:
version: v3
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN

参考资料:https://zhuanlan.zhihu.com/p/360592235?utm_id=0

  • 如何将nginx的端口通过istioEngress转发到公网
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nginx-vs
namespace: nginx
spec:
gateways:
- nginx-gateway
hosts:
- my.gateway.com
http:
- match:
- uri:
prefix: /
route:
- destination:
host: nginx-test.default.svc.cluster.local
port:
number: 80

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
generation: 3
name: nginx-gateway
namespace: nginx
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- my.gateway.com
port:
name: http
number: 80
protocol: HTTP

3.5.12 Istio的总结

img

优点:

  • Istio 是一个开源的服务网格平台,提供了安全、连接和监控微服务的统一方法。
  • Istio 得到了 IBM、Google 和 Lyft 等行业领军者的支持,拥有活跃的社区和众多的落地案例。
  • Istio 是 Kubernetes 的一等公民,被设计成模块化、平台无关的系统。
  • Istio 提供了强大的流量管理、安全性和可观察性功能,可以轻松实现 A/B 测试、金丝雀发布、故障注入等。

缺点:

  • Istio 对非 Kubernetes 环境的支持有限,可能需要根据旧有基础设施进行适配才能正常工作。
  • Istio 只有 HTTP 协议是一等公民,对私有协议的支持不够完善,可能导致一些功能的缺失。
  • Istio 扩展的成本并不低,可能需要修改多个复杂的代码库,并且面临与社区割裂的风险。
  • Istio 在集群规模较大时的性能问题,可能导致 Envoy 的内存开销、Istio 的 CPU 开销、XDS 的下发时效性等问题。
  • Istio 缺乏成熟的产品生态,可能需要研发基于 Istio 的上层产品来满足可视化界面、权限和账号系统对接等需求。
  • Istio 目前解决的问题域还很有限,还有一些分布式系统的复杂语义和功能并未纳入到 Istio 的 Sidecar 运行时之中。
  • Istio 技术架构的成熟度还没有达到预期,经历了多次重大变动,导致升级更新带来不兼容性问题。

参考资料:https://istio.io/latest/zh/