一.开发具体问题
-
2021.09.26 SpringBoot自带的序列化工具是fasterxml的
Jackson
,今天有三个个问题?
1) 如何搜索任意key
(部分)JsonNode
的findValue
方法
2) 什么时候使用ObjectMapper
的readValue
,什么时候使用readToTree
readvalue-and-readtree-in-jackson-when-to-use-whichreadValue() can be used for any and all types, including JsonNode. readTree() only works for JsonNode (tree model); and is added for convenience.
3) Json String中对应内部静态类字段不存在时反序列化后这个域是否存在
不存在,为null
,也就是说无参构造函数并没有执行,仔细想想,都没有识别到它的存在,为什么要执行构造函数 -
2021.09.23 谁引入了这个jar?
某个类缺少一个方法,毫无疑问是版本不兼容,这个问题又来了(我在2019年8月遇到过一次);
jar:servelet-api-2.3.jar
class:javax.servlet.http.HttpServletResponse
谁引入的这个jar?怎么找到pom依赖三元素?笨!jar文件前面Idea那里显示的就是三元素,输进maven helper
分析即可 -
打包进jar的资源文件读取,
ResourceUtils.getFile
可以读取文件,那文件夹怎么读取?getUrl.toURI()
来new一个File读取文件夹提示文件Path为空【补充具体异常】;原来打进jar的文件其实是不能用File读取,文件系统中只有jar这个文件,只能转成stream读出内容,文件夹怎么转成stream读出文件信息?这太难了,看这个方法:https://stackoverflow.com/a/68179822 -
2021.09.22
@Configuration
类是个什么类,起什么作用?待答 Spring的配置可以可以认为是一个对象,比如一个prefix下映射到一个Bean,一部分属性在application.yaml
一部分在active
的配置中,最终都是可以生效的(经过测试验证了的) -
JPA定义的entity字段有
NOT_NULL
限制,也有defaultValue
(数据库定义字段),为什么写不进去:not-null propertites reference a null or trasient value
因为写入数据库的语句肯定相应字段是null
那么问题就变成了,JPA如何不更新某个字段但是读的时候要把该字段读出来?
配置updatable = false
即不更新这个字段
或者
联合字段注解@UpdatedDate
、entity上加注解@EntityListeners
、任意类上注解@EnableJpaAuditing
自动set日期字段 creation-timestamp-and-last-update-timestamp-with-hibernate-and-mysql注意字段要定义为
解决updatetime字段问题后,新问题是用dao(CrudRepository)
保存这个entity
后立即查询出更新时间却为null
,使用另一http请求访问却可以查出来:
原因是open-in-view Spring Boot中建议关闭Open-EntityManager-in-view
spring-open-session-in-view 建议细读
what-is-this-spring-jpa-open-in-view-true-property-in-spring-boot 建议细读
关于spring的事务管理,需要先理解AOP实现的原理,在一个被@Transaction
注解的方法中(不设置额外的TxType
),会随着方法调用默认在此线程传递,不需要特殊处理,在这个方法结束后则事务终止。
那么,假如在service层不注解,如果在controller层看有事务吗?不会有
事务什么时候开始的,和open session in view是什么关系?事务开始就是@Transaction
注解的方法处开始,和open session in view 没有关系,open session in view只是会在请求进入的时候就申请MySQL连接,一些web接口是不需要访问db的 -
DI 注入方式与
@Autowired
(因为Idea不建议Field反射注入: Field inject is not recommended)
setter
注解在setter方法上
constructor
注解在构造方法上
Field反射
注解在Field
上
配置注入 yaml先看-D jvm参数,再看系统环境变量 -
Jackson序列化与反序列化相关注解 待答
21.10.08补充@JsonValue
注解只会把这个域的值传给序列化器,其他域会被忽略;所以只能使用即带key又带body(另一个Json结构)的类来表示,用@JsonProperties
来弥补,无法通过@JsonValue
一键直达,一个类只能有一个JsonValue
注解 -
3年前的疑问:
Could not autowired: No beans of "kafkaProperties" type found
---->@CompnentsScan
-
自定义的
Aspect
和ControllerAdvice
的顺序问题:自定义的Aspect用于监控Controller中请求处理时延和返回值日志输出,但当处理请求中出现异常时,则不会进行统计监测;尝试进行Order
注解,并不管用,Order
只能在各自同类之间起作用(数字越小优先级越高,ControllerAdvice注解的顺序:https://stackoverflow.com/a/19500823)
搜索得知有两个办法https://stackoverflow.com/a/59697304:1) 用Interceptor
,但是这个对response输出时不会打印body; 2) 拦截ControllerAdvice
中注解了@ExceptionHandlers
的方法,但是这个会导致出现异常时会出现两个log
其他补充:finally代码永远会被执行,在RET指令前; Actuator也是基于Interceptor:org.springframework.boot.actuate.metrics.web.servlet.LongTaskTimingHandlerInterceptor
-
CrudRepository
没有update
方法,怎么update
? how-do-i-update-an-entity-using-spring-data-jpa 中文CSDN博客:工作单元模式:Unit of Work 对某个业务操作,比如银行的进出账的业务对象(进账对象,出账对象)进行统一管理,使它们不需要在自己的repository中提交(commit),放在工作单元中做持久化工作In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.
-
@Lazy
注解解释 与 Spring AOP与 final class(比如String) -
postProcessAfterInitialization postConstruct afterPropertiesSet方法作用时机 适用场景分析:尽量使用afterPropertiesSet,这个时候
@Autowire
、@Value
注入已经完成; BPPA是用来factory创建后修改bean的,使用它的时候不能再使用autowire,并且应该独立起一个类编写。先PostConstruct
后执行afterPropertiesSet
-
如果Controller同时有返回值和传入HttpServletResponse,最终客户端收到的内容怎么定?测试结果显示:
- 如果同时有return和HttpServletResponse则返回是个缝合怪:HttpServletResponse + 一部分return值
- 如果return为null则以HttpServletResponse为准
- 如果return为null但是HttpServletResponse的stream写是异步的, 则因为KeepAlive机制,会出现有的请求是空,后面的积攒了多个stream的回复
是谁调用的controller里的方法?org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
Tomcat容器的调用org.apache.catalina.core.StandardWrapperValve
-
如何做将请求处理异步化,不占用htto-nio线程? 可以采用好多个方法 https://www.w3cschool.cn/article/55411104.html Servlet的AsyncContext,或者SpringBoot的@Async注解;那么问题来了,已有的一个Controller,怎么灵活地让它运行时同步或者异步?可以采用request.startAsync();AsyncContext context =request.getAsyncContext(); 在需要异步时,将结果写入HttpServletResponse,然后return null就行,实测能生效,不过执行线程还是http-nio线程们,且哪块逻辑哪个线程执行很乱
图中的father thread是startAsync
前,current thread是异步中的逻辑,complete 是AsyncListener
的Oncomplete
中输出
最终实践我用@Async
注解来完成,http-nio线程停在future.get
, 实际上是因Object.wait
函数阻塞,处于TIMED_WAITING
状态,搜索一番这个状态下线程是让出了CPU资源的,Thread.sleep
也是TIMED_WAITING
不过线程栈里括号里写的是sleeping
,所以又引到了线程状态
所以实际上这种场景下要实现异步,就要全链路异步,像锁链一样(所以就有了react,js一样响应式),松耦合才有效果,发起源头处总是要同步等待,这是无法避免的,因为客户端是同步的,总有个线程去看守future,不过一两个线程看守最好,我现在还没有实现;我最初认为的异步是完全释放http-nio线程,让它去接收下个请求,不要阻塞在controller中;但是实验一番,即使使用AsyncContext
,也是会占用其他http-nio线程 -
AOP切类内函数调用,有时为了监控函数调用、类内多函数操作数据支持事务中,有调用自身函数也需要被切到的需要,可以: 1.proxy类 2.autowire自己 3.supplier函数封装 stackoverflow self-injection-with-spring
-
Spring 配合yaml文件object格式并不能直接
@Value
来匹配成一个普通的map,必须写成map的花括号String格式 -
Bean中自己调自己方法不能AOP增强。因为调用的是原始对象自己的方法,并不是增强对象的方法。https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-understanding-aop-proxies 使用动态代理活着CGLIB对类进行扩展; https://stackoverflow.com/questions/56614354/why-does-self-invocation-not-work-for-spring-proxies-e-g-with-aop 当然,你也可以切换为AspectJ,加载类时增强(当然也可以使用
if()
来在运行时增强) -
请求参数中有枚举?参数只能大写。用
@JsonValue
轻松解决 -
tomcat无法限制请求body是个潜在的隐患,我经过测试,发送巨大的json (59MB)
Content-Type: application/json
,请求在微服务间传递,会造成pod oom,所以限制入参是非常重要的。另外一个server.tomcat.max-http-post-size
参数只对application/x-www-urlencoded
即url上的参数大小有限制,json body没有限制,StackOverflow & Github SpringBoot issue 进一步可以查看tomcat源码对这个参数的使用,和tomcat文档中对maxPostSize
的描述;可以通过自己实现一个Filter进行过滤: StackOverflow -
多module项目实践,在项目前期,部署资源等需要复用,但是在开发的时候不能不考虑将来的拆分,所以可以采用多module便于将来维护,同样对当前的功能实现进行清晰隔离也有好处
- 怎么从一个项目建一个module?project名上右键即可
- 主module一定要是parent吗?为什么通常主module要作为parent?
因为子module之间避免不了互相引用,所以需要一个父pom,如果要单独起一个project的话,为什么不复用主module呢?利用主module还能保证版本同步,于是主module除了需要<packaging>pom</packaging>
和<modules>
这个主module需要的特征标签外,还有<dependencyManagment>
这个父pom的特征标签,这个就形成了一个循环依赖
一样的东西:子module
也是子project,需要依赖父pom,同时父pom中的modules
、dependencyManagement
中又有子module
, 实际上并没有循环依赖,因为父project只是个空架子,dependencyManagement
只管提供三元组特别是版本信息,并不提供这个jar,jar还是发布到仓库中,由仓库提供
当然也可以把父pom管理依赖版本的功能独立出去成为一个bom(就是实际打包为pom),独立出去更清晰,原项目父pom再以这个为父。 - 从安全性考量的内存隔离?
JVM没有这个本事,这个是OS的事情,多classloader是为了解决特别是底层框架的jar版本隔离,并没有JVM层面的“内存”隔离
- 文件下载,零拷贝,需要注意的是,Spring中设置了
Content-Length
之后,Request就commit了,后续的增加header等就加不上了.
Jackson
- Jackson使用@JsonProperties序列化Enum时,得到的string会带引号
- 希望把map提升时,可以增加一个getMap的方法,并且注解
@JsonAnyGetter
, 这是人家作者在2011年就写了的 SF链接 - 如果希望把一个对象提升铺平,就用
@JsonUnwrapped
注解 - 一个类想同时表示create和update应该用什么单词?
save
- 请求参数
RequestParameter
参数是枚举类型的反序列化问题 需要自定义converter来解决,而由于本人的代码洁癖,枚举类全用的是@JsonProperties
序列化和反序列化,就没有string构造函数,就没有采用这种方式
参数校验或转化
- 需要对所有
RequestParameter
统一集中校验,应该怎么做?怎么把集中定义的注解约束在method参数校验时转移过去呢?validate体系本身就是基于注解的,怎么把注解的元数据改成逻辑呢?看了下源码,深度适配比较麻烦,走在路上突然想到:我定义一个类,定义中包含所有的请求参数,并加上validator限制,拦截器中通过filed名字,propertiesDescriptor
反射设置进去,一切都是自动的。 - 请求参数是string,限制参数范围应该怎么做?1.自定义注解
@ValidateString
2.自定义EnumValidator更好 - 请求参数实际上会是Enum类型,或者需要解密什么的,应该怎么做? 1.
org.springframework.format.Formatter
并注册到webMvcCofigSF链接 2. 另一种方式,converter看起来简单点
二.单元测试问题
-
mockito
与mockBean
配合使用,mockBean
可以mock任意bean的任意方法,即使非直接bean也能mock -
那mockito的的
when
怎么指示为任意参数?答:ArgumentMatchers
类 的any()
方法 -
mock静态方法用
thenAnswer()
来完成 -
Spring的另一种单元测试方法,不是使用
SpringRunner
而是MockitoJunitRunner
,用@InjectMocks
标记受测对象,用ReflectUtils
反射设置要mock的field;实际上当使用MockitoJUnitRunner
配合InjectMocks
注解将会自动完成构造 -
注意不管哪种runner,被标记为
@Mock
@Mockbean
的对象它的真实方法都不会得到执行,默认返回都是空值,所以注意,我遇到用不同runner得到的测结果不一致,就是因为有个bean mock和没被mock调用其方法产生区别 -
mock部分方法,其他方法希望调用真实方法:用spy而不是mock:https://stackoverflow.com/questions/14970516/use-mockito-to-mock-some-methods-but-not-others
-
doAnswer()和thenReturn的区别 https://stackoverflow.com/questions/36615330/mockito-doanswer-vs-thenreturn 大多时候区别不大:很多场景是不需要感知调用者信息而无脑返回一个固定值的时候
-
when-then 和 do-when 都可以用来mock,它们的区别
- when-then更符合语义
- 对于spy的class,当你when-then的时候,真实method会被执行,所以用do-then;但是method不出错的话,返回值当然还是你when-then中指定的返回值
- mock void方法时只能用do-when
若出现报错:Unfinished stubbing detected here:
是因为使用错误了:doReturn(result).when(targetObject).someMethod(); not doReturn(result).when(targetObject.someMethod());
-
要遮蔽一个被spy的对象的一个方法,可以通过doNothing来进行,这是mock对象的默认行为,什么都不做,但是总是抛空指针,原来要遮蔽的方法参数是boolean,而我使用了any(), 应该使用anyBoolean(); 同时注意,和doReturn一样,遮蔽方法是在括号外。
-
在SpringBoot单测中是可以定义一个同名Bean标记为
@Primary
来覆盖正式代码中的bean,可以用来mock外部资源 -
强烈建议在测试
application.yaml
中spring下定义main: lazy-initialization: true
可以加快测试,但是这会导致一些bean,特别是无法加载ApplicationListener
这种通过META-INF
来初始化的类;参见如何获取Context ,setLazy的意思是,在启动引用树中,当这个Bean没有被其他Bean引用到时,它就不会被初始化,而去掉,就会初始化。如果因为代码限制不能通过yaml配置全局加lazy限制,那么可以使用BeanFactoryPostProcessor
, 提前根据Bean名字指定Lazy属性 -
加快测试的第二个方法:在测试代码中写个
Configueration
类,关闭一些中间件的SpringBoot的自动装配,自行初始化并缓存(static))bean,比如Redisson;其中一个重要的自动配置类是GrpcServerFactoryAutoConfiguration
这个可以解决GrpcServer
端口占用问题,可以在相关测试中去掉DirtyContext
;也可考虑把自己代码@Bean
生成的bean缓存;注意缓存实现后,就不能再用DirtyContext
注解了,否则会出现资源被关闭,shutdown -
单元测试可以参考 https://java-self-testing.github.io/java-self-testing-book/05-testing-with-spring/
-
anyString()
和any()
的区别:后者接受null
值,这在校验函数调用次数时非常有用。 -
从头写一个单测时,应该使用哪个注解?SpringBootTest 和 SpringRunner 是什么关系?DirtiesContext又是什么意思?https://www.baeldung.com/springjunit4classrunner-parameterized
1). 从头写时:可以有以下几种组合:- A.
SpringRunner + @SpringBootTest
没有@SpringBootTest
就会找不到bean - B.
SpringRunner + @ContextConfiguration(classes = SimpleBean.class)
ContextConfiguration中指定测试中需要Autowire的bean - C.
普通Runner(默认或者参数化)+ SpringClassRule + SpringMethodRule + @ContextConfiguration(classes = SimpleBean.class)
并且实测,同一个JVM进程中,使用A的类们共享一个context,使用方式B的类们共享一个Context,如果两个类一个使用A,一个使用B,Context不共享;
同一个类里的用例始终共享一个Context
2).
SpringBootTest
使用默认配置机制,SpringRunner
最重要的是提供了TestContextManager
,TestContextManagers
是启动Spring上下文的核心@SpringBootTest
和@ContextConfiguration
的作用大约是配置Context怎么启动。具体可参见SpringBootTest
注解的classes
属性
3).DirtiesContext
注解帮你关闭上下文,经过测试,没有此注解时,当一个bean属性被修改,在另一个测试类中也能感知到。但需注意1)中描述的context共享关系 - A.
-
遇到有时
@SpyBean
居然无法mock,MockBean
却可以 -
mock一个
void
方法的行为,比如抓取监测对象的某个方法入参,虽然doAnswer(..).when(T)
和Mockito.when(T)
参数都是T
,但是实际上参数含义是不一样的
前者是doAnswer(..).when(mockObject).someMetho(any())
后者是Mockito.when(mockObject.someMethod(any())
同时需要注意要在被测函数调用前进行doAnswer
三.运维部署问题
- actuator 要用不同的端口且只能集群内部访问
- 我遇到模块actuator 监控数据不能输出到prometheus是因为模块metric的tag丢失了,tag信息是直接输出在curl的结果中的;metric信息丢失说明是因为配置没起作用, actuator 的配置在哪里被读取然后起作用呢?
PrometheusMetricRegistry
因为这个bean对应的BPP
没有执行postProcessAfterInitialization
方法,业务bean实现该方法导致一些链接到的bean都没有被执行这个方法,进而配置未生效; 注意启动日志中的关键字: 类似的问题还会有事务不生效、AOP不生效 - 部署相关,稍微扩展下部署。部署有滚动部署 蓝绿部署 金丝雀(灰度)发布。滚动部署即一个个替换新模块,服务器数量少的时候还能接受,但是要注意依赖关系;蓝绿部署就是另启一套新环境来部署新模块,然后识别请求导入不同环境,金丝雀也能采用这样的办法。先来扯扯流量识别,现在都是采用istio进行流量管理,它一直能从网络层到应用层,可以通过IP、HTTP header来区分流量,识别请求后,可以采用一定的策略来分配流量,从而实现蓝绿或者灰度发布。扯扯灰度:软件有两种,客户端和服务器,所以客户端也可灰度。客户端有:App、固件、前端页面等;客户端灰度就容易多了,因为客户端是用户向云请求得到的,是
fetch
,这样就可以进行更细粒度的灰度,完全可以是在业务级上,比如22岁的充值超500的LOL男性玩家。
再来梳理下istio的入门过程:
知乎的这篇文章非常不错,讲了微服务的前世今生:[什么是Service Mesh][https://zhuanlan.zhihu.com/p/61901608?]
istio是什么?https://www.ibm.com/cloud/learn/istio
Istio is a configurable, open source service-mesh layer that connects, monitors, and secures the containers in a Kubernetes cluster. At this writing, Istio works natively with Kubernetes only, but its open source nature makes it possible for anyone to write extensions enabling Istio to run on any cluster software. Today, we’ll focus on using Istio with Kubernetes, its most popular use case.
Kubernetes is a container orchestration tool, and one core unit of Kubernetes is a node. A node consists of one or more containers, along with file systems or other components. A microservices architecture might have a dozen different nodes, each representing different microservices. Kubernetes manages availability and resource consumption of nodes, adding pods as demand increases with the pod autoscaler. Istio injects additional containers into the pod to add security, management, and monitoring.
istio
是一个可配置的、开源的service-mesh层的概念,连接、监控并且巩固Kubernates集群中的容器。编写此文章时,istio
只适用于Kubernetes,但是其开源的特性使得任何人可以编写拓展以使其跑在任何集群软件上。今天,我们将主要关注在Kubernetes上使用Istio,那是最常用的。
Kubernetes是一个容器编排工具,其中一个核心单元就是node。一个node包含一个或多个容器,以及文件系统或者其他组件。一个微服务架构也许有十几个不同的node,每个代表不同的微服务。Kubernetes管理了node的可用性和资源消耗,pod autoscaler按需增加pods。Istio向pod注入了额外的容器,以便实现安全、管理和监控。
k8s的service https://kubernetes.io/docs/concepts/services-networking/service/
An abstract way to expose an application running on a set of Pods as a network service.
With Kubernetes you don’t need to modify your application to use an unfamiliar service discovery mechanism. Kubernetes gives Pods their own IP addresses and a single DNS name for a set of Pods, and can load-balance across them
一种抽象的方式去将暴露一个运行在一组Pods上的应用程序作为一个网络服务。
使用Kubernetes你不必修改你的应用程序去使用一个不熟悉的服务发现机制,Kubernetes给予每个Pod它们自己的IP地址,给一组Pods DNS name,并且在Pods间负载均衡。
Service a unit of application behavior bound to a unique name in a service registry. Services consist of multiple network endpoints implemented by workload instances running on pods, containers, VMs etc.
其Service的selector
通过Pod的lable
去匹配Pod,可以参见defining-a-service
k8s的lable 和selector https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/Labels are key/value pairs that are attached to objects, such as pods. Service可以通过Pod的lable选择Pod,Pod可以根据Node的Lable选择Node
Istio的virtualService
注意virtualService是Istio的,不是(隶属)k8s的概念:https://istio.io/latest/docs/reference/config/networking/virtual-service/
A VirtualService defines a set of traffic routing rules to apply when a host is addressed. Each routing rule defines matching criteria for traffic of a specific protocol. If the traffic is matched, then it is sent to a named destination service (or subset/version of it) defined in the registry.
一个VirtualService
定义了主机寻址时的一组流量路由规则。每个路由规则为一个具体的协议流量定义匹配规则。如果流量匹配,则将会被发送到注册里的具有名称的目的Service。
SideCar https://www.nginx.com/resources/glossary/sidecar
A sidecar is a separate container that runs alongside an application container in a Kubernetes pod – a helper application of sorts. Typically, the sidecar is responsible for offloading functions required by all apps within a service mesh – SSL/mTLS, traffic routing, high availability, and so on – from the apps themselves, and implementing deployment testing patterns such as circuit breaker, canary, and blue‑green. Sidecars are sometimes used to aggregate and format log messages from multiple app instances into a single file.
As data‑plane components, sidecars are typically managed by some type of control plane within the service mesh. While the sidecar routes application traffic and provides other data‑plane services, the control plane injects sidecars into a pod when necessary and performs administrative tasks, for example renewing mTLS certificates and pushing them to the appropriate sidecars as needed.
sidercar
是一个单独的容器,k8s中,其和应用容器一起运行-一种辅助应用程序。
K8s Deployment
污点和容忍度 给Node一种灵活的标签方式,且部署Pod时可选择地部署:
污点(Taint) 则相反——它使节点能够排斥一类特定的 Pod。容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。 容忍度允许调度但并不保证调度:作为其功能的一部分, 调度器也会评估其他参数。
NodeSelector 节点(或者Pod)亲和性(affinity)与反亲和性 :
nodeSelector 提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上。 亲和性和反亲和性扩展了你可以定义的约束类型
Deployment replicas和HPA的关系
https://technologyconversations.com/2018/10/10/to-replicas-or-not-to-replicas-in-kubernetes-deployments-and-statefulsets/
一开始是deployment为准,最终会以HPA为准,HPA会修改Deployment,这个行为无法接受。同时此文章建议使用HPA时就不要在deployment中指定副本数了,但是文心一言不这么认为,我们实践时也是两个都设置了但是要记得最小值设置一样。
So far, we observed that HPA modifies our Deployments. No matter how many replicas we defined in a Deployment (or a StatefulSets), HPA will change it to fit its own thresholds and calculations. In other words, when we update a Deployment, the number of replicas will be temporarily changed to whatever we have defined, only to be modified again by HPA a few moments later. That behavior is unacceptable.
Service 和 VirtualService
Service是k8s里的概念,VirtualService是Istio概念,你可以有多个k8s集群,但是Istio mesh 可能只有一个?
我们每个环境的 多个区域的K8S集群 到底是多个还是1个?答:多个
如果是多个,主从是怎么搞的,K8S集群的主从是怎么搞的,像VirtualService都在主区保存,Service保存在各自区?但是也没见从区有复制这些配置啊?答:部署Istiod的时候可选模式,如下面对多cluster的DNS解析的问题分析,Service会复制到多区,实际上我们实践也是复制了,只是我没注意到。
如果是单个,那么为什么又会有某区自己的 service?这些是docker namespace层面的东西?
namespace是docker的概念还是K8s的概念,应该是docker,那么看起来K8s和docker的关系并不是清晰的分层关系,而是有些嵌入渗透的:k8s是编排管理docker的
但是根据我们的配置,不同区是不同的EKS集群,不同的EKS集群打通,通过的是Gateway不同hostname的 + VirtualService来指向某区Service,感觉是通过DNS解析互通实现了网络互通?不同集群只要网络互通+DNS能统一注册,那么就是互通的。这里猜对了一半,因为实际上是Istio模型的选择,参见文档:https://istio.io/latest/docs/ops/deployment/deployment-models/
For example, if two clusters reside on the same underlying network, you can enable cross-cluster communication by simply configuring firewall rules.
- 另一个问题是Gateway下面的格式是什么意思?
- VirtualService下面的格式是什么意思?
回答1. istio Gateway居然和K8s Gateway不是一回事
Gateway表示就是一个云LB或者集群内的proxy
主要看Istio的:ports 表示Gateway会暴露这么些个端口 hosts表示Gateway暴露的域名
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: my-gateway
namespace: some-config-namespace
spec:
selector:
app: my-gateway-controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- uk.bookinfo.com
- eu.bookinfo.com
tls:
httpsRedirect: true # sends 301 redirect for http requests
- port:
number: 443
name: https-443
protocol: HTTPS
hosts:
- uk.bookinfo.com
- eu.bookinfo.com
tls:
mode: SIMPLE # enables HTTPS on this port
serverCertificate: /etc/certs/servercert.pem
privateKey: /etc/certs/privatekey.pem
- port:
number: 2379 # to expose internal service via external port 2379
name: mongo
protocol: MONGO
hosts:
- "*"
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: example-class
listeners:
- name: http
protocol: HTTP
port: 80
然后VirtualService是个处于应用层的配置化交换机
http那里的格式:https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPRoute
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings-route
spec:
hosts:
- ratings.prod.svc.cluster.local
http:
- match:
- headers:
end-user:
exact: jason
uri:
prefix: "/ratings/v2/"
port: externalPort # 比如80?
ignoreUriCase: true
route:
- destination:
host: ratings.prod.svc.cluster.local / serviceName
port: 如果Service暴露一个端口,那就不用设置了
K8S namespace或者istio的namespa和docker的namespace是同一个吗?根据创建K8S集群的示例来看是同一个Istio / Set up a Kubernetes Cluster
Service 的name只能在同namespace下解析,不同namespace要通过这个域名 my-service.default.svc.cluster.local
多集群问题
1.不同区 2.不同域名 3.不同ELB 4.都对外监听443 5.对内采用不同端口 6.Gateway不同域名换不同端口() 7.VirtualService 绑定指定Gateway,根据不同端口 转发到不同区的Service
5 6怎么对接的呢?之前不是说Gateway表示的是监听的不同端口吗?
这个问题很好,istio的这篇文章deployment-models 清晰地说明了部分问题,首先概念要清楚:k8sCluster network controlPlane mesh 他们分别是多个还是一个
我们主要关注多个k8s cluster的情况,所以着重翻译下这块:
一个mesh可以包括多个cluster:可以故障转移,基于地理位置路由,多种控制面板模型:不同级别的可用性,团队、项目隔离
cluster间的通信可以由你的云服务提供商完成,比如简单的配置下防火墙。
Istio uses the virtual IP returned by the DNS lookup to load balance across the list of active endpoints for the requested service 这句话真难翻译。the DNS lookup to load balance
Istio使用DNS查找返回的虚拟IP在所请求服务端点列表间进行负载均衡。Istio使用k8s service/endpoint或者Istio ServiceEntry来配置内部映射:域名到负载IP地址。
在多个集群下,这个两层域名系统使情况变得复杂。Istio天生就是多集群感知的,而k8s不是,所以客户端集群就需要这个service的DNS入口来让DNS查询成功,为了让DNS查询成功,k8s service必须部署到每个消费这个service的集群(我们的确如此)。不管请求是哪里发起的,能确保正确查找并传递给istio来正确路由,这可以用Istio的ServiceEntry实现,而不是k8s Service,但是ServiceEntry并不配置K8S的DNS Server,这意味着DNS需要被手动配置或者Istio DNS proxying的自动分配特性。
有如下几个进行中的DNS方向的改进故事:
DNS sidecar proxy,Istio 1.8增加的预览版,通过sidecar拦截了工作负载的DNS查询,主要参考这篇文章,非常有用。
Istio团队一开始想用Envoy的DNS Proxy,但是它不可靠且其DNS库对C类地址实现不好,于是istio团队自己写了个DNS proxy放在sidecar里, 同时保持了 scale和稳定性。Istiod给其推送域名IP映射(包括要访问的K8s services和ServiceEntry),这个对pod或者VM透明。
If the query is for a service within the mesh, irrespective of the cluster that the service is in, the agent responds directly to the application. If not, it forwards the query to the upstream name servers defined in /etc/resolv.conf.
DNS proxy in the Istio sidecar agent, Your applications can resolve Kubernetes services on any cluster in any namespace, without the need to create stub Kubernetes services in every cluster.再回过头来看模型说明的那篇文章:
This can also be achieved with Istio ServiceEntry, rather than Kubernetes Service. However, a ServiceEntry does not configure the Kubernetes DNS server. This means that DNS will need to be configured either manually or with automated tooling such as the Address auto allocation feature of Istio DNS Proxying.
所以跨集群DNS解析要么复制Service(成为stub Service) + K8s的DNS服务器,要么利用Istio ServiceEntry + Istio DNS proxy
现在感觉在Istio治理下,主要的考虑点是:向谁寻址,谁来如何路由的问题