【p1】Spring:通过介入bean生命周期以及自定义注解来简化代码结构

(转载请注明作者和出处‘https://fourthringroad.com/’,请勿用于任何商业用途)

过去几年在不少项目里也用到了spring框架,主要使用的还是IoC和mvc框架的相关能力。使用的动力就是简单,快速:写几个restful的controller和业务逻辑处理器再套个ORM框架,就能够让服务跑起来。尤其对于一些非关键路径上的服务后端,能够在非常短时间内让服务上线。

我同意SpringMVC引入的一系列组件大大简化了我们对Servlet的开发,同时spring的生态系统确实功能完善,但是单独讲对于Bean的管理这块我是认为spring有些臃肿的,相比之下我更偏向使用一些更轻量级的DI框架,比如像Google的Guice。Spring对bean的生命周期管理太冗长了,它有很多的hook,有很强的定制化能力,但是伴随的是学习曲线高,同时代码复杂度也有可能变高,可读性差。这样带来的一个问题是,使用它可能违背我‘简单,快速’的初衷。我的代码可能需要一个不了解spring的人评审或者debug或者在未来接手,而我使用了一些不太好理解的hook,那么对于后者来说就是一个头疼的事情。

总结来讲,我认为对于偏业务的开发人员而言,他们需要的就是工具,如果需要他们去阅读源码,无法纯粹把工具当作黑盒使用,那么这个工具的顶层设计就有一些问题。所以我在做业务的时候基本上只使用spring常用的注解和基本的bean管理功能,用此将初始化过程与其他代码模块解耦。

但是如果我是给业务方服务的开发者呢,则情况会有所不同。我最近接手的一个项目是一个负责托管配置的中间件服务,公司内很多第三方服务都会调用,利用这个服务存储/获取自己的配置。类似的‘配置中心’在很多公司都存在,相比于hardcoded的配置(存在配置文件或者代码),显然这种集中化管理方式更好管理更易修改;同时相比Spring Cloud Config那种在应用启动阶段拉取一次配置的机制,多数配置中心也具备动态更新配置的能力。我之前在Amazon工作也接触过一个叫SDC的动态配置管理服务。当配置发生改变的时候,相关客户端可以观察到变动,具体可以是配置中心发布一个事件或者客户端周期性的检查拉取变动。假设采用后者,时序逻辑非常简单:

不考虑可靠性和性能的话,业务逻辑基本上可以抽象成简单的CRUD。如果服务只是提供REST接口,或者更进一步,一个会周期向Config Server发检查拉取请求的简单Client的话,我们来想想用户还额外需要做些什么?

假设用户以某一种格式在Config Server托管了一份配置文件(xml,properties,json,yaml或者其他常见格式),用户希望将它map到运行时的一个bean对象:

{"foo":"xxxx", "bar":123}
<=>
ConfigBean {
    String foo;
    Long bar; 
}

这个ConfigBean的配置内容在业务处理流程中会被使用到,比如foo可能是某个商品的title,bar代表某个商品的优惠策略。那么用户侧应用的启动过程则应该是:

同时,因为ConfigClient会后台周期同步最新的配置,所以当新配置被拉取后,需要注册回调函数去对ConfigBean实例的属性进行修改。

我们大部分用户都在使用spring/springboot构建业务应用,对于我们提供的client,上述是大多数用户都需要重复编写的逻辑,如何在将用户侧的代码进行简化?我们回到用户需求分析,用户的需求只是希望能够将ConfigBean和一份配置文件进行映射,至于是否采用C/S模式,http还是tcp,rest还是rpc都不重要,只要用小的开销给他同步就配置数据就ok了。

那么最简单的配置方案是否可以简化成这样:

1. 用户在spring config里面配置服务信息:

<配置中心标签 服务地址=“xx”应用ID=“xx” 配置仓库=“xx”.../>

2. 在代码里面通过注解建立ConfigBean与具体配置的映射

@配置专用Annotation(配置ID=“xx” 解析格式=“json/xml/yml...”)
@Component
ConfigBean {
    String foo;
    Long bar;
}

隐藏上面提到的流程中的所有细节,用户只是根据自己直接需求进行非常简单的配置。显然用户体验会得到了很大的优化。

这就需要利用spring预留的hook,介入相关bean的生命周期。

发表在 spring相关 | 6条评论

ES性能调优思路

写性能调优

ES官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html

  1. 【index级别】增大refresh的间隔(以及增大buffer),副作用是会降低读取的实时性
  2. 【index级别】增大translog落盘的间隔,副作用是会有数据丢失的风险,容灾能力下降(https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html
  3. 【index级别】减少flush的频率:
    1. 增大translog触发flush的上限:index.translog.flush_threshold_size
    1. 固定时间间隔:index.translog.flush_threshold_period (在高版本已经不支持了)
  4. 增大队列
  5. 增大线程池
  6. 使用专用节点
  7. 清洗掉无用字段,避免无用字段的分词过程
  8. 关闭动态mapping
  9. 【index级别】横向扩容:增加primary shard数量; shard在node上分布更均匀(避免单个节点成为瓶颈 ),可以设定shard_per_node;
  10. 减少副本数量,可以短时间降为零
  11. 纵向扩容,增加硬件规格

关于shard数量多说几点:

  • 可以提前估计shard的大小,不要超过几十个G
  • 可以根据需求压测一个primary shard的承载能力,再根据实际TPS计算shard数量
  • 压测是估计shard的最好方式

读性能调优

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html

  1. 合理的数据模型(建模)非常重要,既要能满足业务需求,同时尽可能以合理结构进行数据存储,避免复杂查询过程 
  2. 增加副本数量,副作用写性能下降,
  3. 增大队列
  4. 增大线程池
  5. 使用专用节点
  6. 使用Filter过滤初始数据
  7. 尽量不使用script
  8. 多层嵌套的agg也会拖慢读性能
  9. Profile,explain等都是调优利器
发表在 ElasticSearch | 247条评论

手记-如何用profiles区分不同环境的配置

(转载请注明作者和出处‘https://fourthringroad.com/’,请勿用于任何商业用途)

常用的两种方式:

依赖SpringBoot

springboot 的配置文件设计成application-{profile}.yml(properties),其中profile可根据不同的环境而不同,dev, beta, gamma, stag(uat), prod 等等。

并在application.yml中指定生效的profile即可

spring:
  profiles:
    active: prod

如上,则生效的配置文件为applicaiton-prod.yml

如何动态指定:

  1. jvm参数:-Dspring.profiles.active=prod
  2. @profile注解,加在bean上则只有指定profile生效时才会被初始化
  3. 配置文件设定成${profiles.active},通过mvn clean package -P prod命令来替换

依赖maven

在pom文件中可以有profiles的配置

<profiles>
    <profile>
        <id>local</id>
        <properties>
            <foo>123</foo>
            <bar>456</bar>
        </properties>
    </profile>
</profiles>

这样${foo},${bar}变量会根据profile的不同而变化,实现灵活的配置。

同时还可以在<resource>标签中指定打包哪个资源文件 – src/main/resource/${profiles.active}

或者在<build>中指定filter,include哪些文件- src/…/application-${profiles.active}.properties

如何动态指定:

  1. mvn clean package -P prod
  2. 可以根据os,env,文件是否存在等条件设定自动生效的profile
  3. maven的settings.xml里面可以指定生效的profile是哪个-activeProfile

整合SpringBoot和Maven

用maven pom中profiles下的变量去代替application.yml, application-stag.yml, bootstrap.yml 等文件中的值

发表在 软件工程 | 一条评论

【p2】谈一谈ElasticSearch的灾备

(转载请注明作者和出处‘https://fourthringroad.com/’,请勿用于任何商业用途)

接着上一篇来讲一讲ES的灾备。

首先谈谈在资源充沛的的情况下,ES的灾备。

支持多Region多AZ部署的ES集群

云上的ES集群,倘若支持节点分布在多个AZ,乃至多个Region,且这个因素会影响ES数据分片分配时,这个集群自身就能具备很高的可用性。这个很好理解,当一个机房发生宕机,ES自己的机制会保证数据的迁移恢复。

在做云服务的跨AZ的设计时,有两个比较重要的点需要考虑:

当节点分布在多个AZ时,数据也应该合理分布

这个很好理解,如果三个节点分布在两个AZ,集群存在一个1分片,1副本的索引,那么这个主分片和副本分片应该分别在不同的AZ,这样才能保证高可用。如下图所示:

如何实现呢?ES官方就支持一个特性:Shard Allocation Awareness。这个特性使得ES在分配分片时会将无力硬件配置纳入考虑范围内。

以上面ES集群为例,我如果给Node-1和Node-2打上一个tag:

node.attr.az:AZ-1

给Node-3打上一个tag:

node.attr.az:AZ-2

在主节点上配置:

cluster.routing.allocation.awareness.attributes: az

那么在创建索引的时候,ES会将一个shard的副本分布在打有不同az tag的机器上。

tag的含义是灵活的,可以是机架,az,region,都ok。

脑裂问题

通过配置discovery.zen.minimum_master_nodes=N/2+1去防脑裂是一个常用的手段,我就不多说了;但是在跨az高可用的场景下有个问题

倘若AZ-2的机房挂了,还剩两个M节点,集群依然可以对外提供服务;倘若挂的是AZ-1,那么因为master-eligible小于2,所以集群无法对外提供服务;也就是不具备单机房故障高可用。

如何解决呢?引入第三个AZ,让Master分布在三个AZ上,这样才能支持挂掉一个机房,集群仍然可用。

 

非云上服务,或者云上不支持跨AZ/Region

假如不具备云上托管ES直接支持多机房,那应该怎么去实现ES高可用?

根据前一文‘聊一聊灾备’所讲,实现容灾,无非就是主备或者多活。我们先聊聊主备方案。

主备方案

双写

双写就是对主备集群分别执行写入操作。操作的发起是应用。从双写发生的实时性,可以有实时双写和异步双写两种方案。

实时双写

应用向主备集群发起两次独立的写入请求,增加了一次额外的写入,需要解决的难点:

  • 一致性问题:
    • 如果一个client的写入成功,另外一个失败多次,是中断等待人工介入还是回滚?
    • ES集群不限制接入client数量,很难保证两个集群数据百分百一致。
    • 增量数据可以被处理,历史数据很难同步
  • 延迟问题:在两个写完全并发的情况下,总延迟变为两个集群写入延迟较大者

异步双写

异步意味着需要用消息中间件进行解耦,譬如用一个消息队列,应用向消息队列里面写,再由消息队列的消费者向两个ES集群进行写入。但是同样有自己的问题:

  • 一致性问题跟实时双写一样
  • 消息消费的顺序必须严格保证
  • 引入额外组件,复杂度更高
  • 同样增量数据可以被处理,历史数据很难同步

但是优点是写入效率会非常高。更进一步的话可以对中间的一层进行抽象,单独抽出一层写入服务,可以赋予更多能力。

当然也可以把两者结合起来,应用直接向主集群写入数据,然后将数据丢到消息队列,由下游服务进行异步同步到备集群;不赘述。

ES底层同步

除了双写之外,很多数据库支持底层的复制,ES也不例外。ES在6.7版本中引入了跨集群复制(CCR)功能,从索引级别支持将数据从一个ES集群复制到一个或者多个ES集群。

我不能把我在做的内部项目设计文档贴过来,但是我写的一段Background还是可以复制一下的:

“ElasticSearch最早在6.5版本推出了跨集群复制功能cross-cluster replication(CCR), 直到6.7才成为正式版本(非beta)。

为了实现CCR,es也做了一些技术准备,其中最重要的是在6.0版本引入了sequence number。在6.0之前, es在不同shard之间做数据同步还是通过在网络中传输lucene segments,但是在6.0之后就可以支持追踪单个操作,并通过在另一个节点上回放该操作来实现一致性;与sequence number同时推出的还有primary term和checkpoint两个概念,primary term用于解决换主后的数据冲突,checkpoint(分为local和global)是指向sequence number的指针,用于记录从哪个点开始做数据同步从而避免全量的对比。

但是在早期有一个问题处理得不好:如何防止删除操作在被复制之前因为merge事件被清除掉?6.0引入sequence number之后主副shard之间同步是通过translog retention来实现的,即在translog中记录这些操作,副shard获取操作进行回放,通过配置参数可以设置translog文件保留的时间以及最大占用磁盘空间(index.translog.retention.size & index.translog.retention.age); 但是translog的功能也变得愈加复杂,不同的功能耦合在translog上,也束缚了es未来的可拓展性;所以实际上在ccr实现之前,在Lucene的7.3版本中又开发了原生的软删除soft-delete的功能,这个功能可以按需持久化记录删除操作,避免它们在merge操作中丢失。开启这个功能也会增加一些merge过程中的开销。

鉴于soft-delete比translog retention在保留历史操作方面更有优势,故前者在逐步取代后者;在7.4.0开始副本恢复数据已经不再从translog获取数据,对translog retention的配置实际上已经会被忽视(index.translog.retention.size & index.translog.retention.age),并且会在将来的版本中移除掉;7.6.0版本中已经不允许控制soft-delete的关闭(index.soft_deletes.enabled)。

需要指出的是目前版本的es(7.15.0)的文档上依然有以下描述与其他文档冲突的地方,我暂且理解为文档没有更新及时,但是无论如何,软删一定会在未来取代translog成为实际上记录delete operation的地方。”

CCR的实现目前有AWS开源版本和ES Xpack的版本,CCR我之后可能会单独拿篇文章来讲,这里就不赘述了。

听上去底层复制的方案更完美;当然,但是如果去看官方源码会发现复杂度也是很高的,主备集群之间连接的建立保持,如何全量同步,如何增量同步,如何开始/停止/暂停/恢复 等等;官方的功能也是有限的,定制代价比较大,同时目前也不是所有的ES版本都支持。

双活方案

ES双活方案复杂度更高,因为涉及到了双向的数据实时同步。CCR插件目前也不能很好的支持。

 

总结

显然,对于业务方来讲,自己去实现ES主备集群代价是较大的,即使采用ES的底层复制方案;业务方也不应该有过多的精力放在这上面,这就要求公司的中间件团队,云服务团队承担起更多的责任,将高可用的复杂度隐藏在托管服务之下,甚至做到serverless。AWS在向这个方向发展,相信国内的大厂也会朝这个方向发展。

累死我了,灾备的两篇文章就写到这人吧。

发表在 ElasticSearch | 留下评论

【p1】谈一谈灾备

(转载请注明作者和出处‘https://fourthringroad.com/’,请勿用于任何商业用途)

对于非关键服务我们通常不太考虑这个问题,之前在Amazon工作的时候,有时候将服务的高可用建立在底层设施/托管服务的高可用上。但是离开了这些环境,这就是一个我们自己需要关注的点了。京东云发展之前,业务部门更多的使用的是自己搭建的中间件,这边就需要不少的灾备工作,来应对大大小小的促销节点。

即使在云上,对于关键服务,我们也要关注灾备的问题。

首先谈谈高可用。

高可用HA

截了个wikipedia的描述。高可用是现代IT系统极为关注的点,作为微服务链路中的一个重要环节,哪怕你的SLA实现了四个9,一年也难以避免会有一个小时的宕机。你无法想象S3发生几个小时的宕机的后果是什么(事实上也发生过)。

如果灾难一定会发生,那么我们要做的就是准备好后续的措施,这个就是灾备要做的事情。

灾备Disaster Recovery

灾备DR其实就是如何从灾难中恢复,早期可能主要关注数据层面,现在对我们来说如何快速让业务层面恢复正常也同样重要。我最近听的一个讲座这样拆分灾备:

灾备= 容灾 + 备份

容灾侧重于设置主备中心,保证服务的不间断;而备份从数据层面保证安全。

从我的理解上讲这两个概念有重复的地方,主备中心必然牵扯到数据的备份。单独说备份可能更多的是强调这是最基础,也是最后一道防线。倒也是合理

容灾

如前所述容灾通常采用主备部署方式,最理想的方式是两地三中心

本地数据/服务中心 + 本地灾备中心 + 异地灾备中心

在云上,本地的两中心通常同Region,跨AZ(不同机房);异地的中心则跨Region部署。

这种部署方式从最大程度保证了安全。当主服务出现了问题,可以灵活切换到备用服务。理想状态是备份中心和主中心可以互相感知健康状态并自动切换,当然也可以人工干预。

这种主备方式有一个问题是资源的使用率,备用机房只是进行数据的同步,并不承接业务,存在资源的浪费。所以有些业务会在备用服务使用更少的资源或者服务降级或者处在睡眠状态,只要保证服务能够在可接受范围内启动即可。这就引出了热备和冷备,其实这只是根据业务能承受中断恢复时间进行的一个选择。还有一些业务会在备用服务上提供只读服务进行数据挖掘分析,或者将其改造成跟沙盒来进行测试,这些都是为了提高资源的使用率。

上述部署方式,涉及到底层数据的同步问题,通常同步过程可拆解为:全量复制(快照)和增量复制,不同的服务这两个阶段的复制具体实现方案不同。快照的机制包括COW,ROW等,增量复制的机制对数据库来说就是事务日志。

除了主备部署,另一个常见的方案:双活/多活(也可分本地和异地)。其实就是备用服务也承接业务流量,变成了主-主或者主-主-主架构。它的技术难点是需要进行数据的双向同步;并且要保证数据的一致性,有一些数据库是支持的。

备份

解决的问题不是服务高可用不中断,而是以某个Recovery Point恢复点为准,对服务,数据进行备份,用于应对故障发生后的恢复。

常见的衡量指标是RPO(Recovery Point Objective)和RTO(Recovery Time Objective),前者衡量能接受的数据丢失量,后者衡量能接受的恢复时间。

类似的,备份方式也分为全量备份(快照)和增量备份,差异备份等

各个数据库服务基本上都有快照或者镜像功能,elasticsearch有提供snapshot的接口

我这个博客使用的lightsail服务本身也有备份功能,同时可以自动调整备份周期:从客户(容忍程度)和成本进行综合考虑。

小结

除了很多服务自身有灾备的功能,现在专用支持灾备的云上独立服务也很多,比如阿里的HDR-为企业级应用提供低至秒级RPO和分钟级RTO的容灾服务。类似还有DRaaS。

这里面有很多复杂的底层原理,不赘述。

下一篇文章详细的讲讲ES的灾备方案。

发表在 ElasticSearch | 7条评论

闲扯淡 – 企业的项目规划

(转载请注明作者和出处‘https://fourthringroad.com/’,请勿用于任何商业用途)

最近做了一些灾备的工作,也听了个讲座,想写写这方面的事情。

不过在那之前有一些闲淡想扯扯。

五月初花了两周在做部门服务破坏性演练的相关事情。云内部有个团队做了一个故障演练平台,可以做一些基本的故障注入以及故障场景编排, 譬如模拟节点故障,CPU/内存问题,Pod故障,容器故障等场景,可能在在将来还可以支持线程枯竭,流量激增等场景。用于测试我们中间件服务的容错性和故障恢复能力。这种平台/服务在混沌工程流行的今天不少公司都在搭建,比如AWS的FIS服务(Fault Injection Simulator)。

初衷非常好,云原生时代的云服务提供商,关注一个重点就是容错,可靠性,服务自愈能力;借助混沌工程的理论和工具,能够进一步优化我们服务的这个方面。

但实际上这个平台使用体验非常糟糕。

  • 首先我只能说它是一个半成品,我是第一批用户;抛开功能层面,平台后端资源有限导致测试环境不稳定,大量的功能刚刚上线,交互几乎没有用户体验可言,卡顿严重,我记忆深刻的是连基本的tab切换功能都做的很糟糕;几百行的日志没有换行,堆在一起;UI美观更不用提。使用过程中我的感受就是:我不知道是在用这个平台测试我们的服务,还是用我们的服务测试这个的平台;
  • 对方开发人员短缺,每一通电话联系都必然会提到这个点,这个正在开发/修复过程中,最近正在做xx/yy/zz这几个功能,刚刚/很快就能上线。。等等
  • 显然因为人力短缺,系统自然没有任何说明文档,没有任何的self-service,取而代之的是一通一通电话和聊天软件联系。负责人和对接工程师倒是很nice。
  • 在我完成onboard后,我开始在内部分享这个平台,帮助其他几个中间件上线这个平台进行测试,仿佛我成为了这个平台的义务社区工作者。

光看我罗列的缺点感觉它是一个一无是处的产品,其实并不是,它能够推出至少表明它基本是过关了的。不过客观的讲现阶段它是一个非常不成熟的产品,它无法靠自身去吸引用户使用它。我给我的wordpress也装了很多插件,没有人告诉我你必须安装它们中的任何一个。

前前后后我没有花很多时间在这个事情上,不过它不禁让我思考下面的问题:

  • 公司内部的重要基础设施,应该是由一帮经验丰富的工程师进行有详细规划开发,因为涉及到所有上层服务的稳定性和可靠性;尤其是一个故障测试平台,如果它本身都无法做到稳定可靠,如何用它来测试别的服务的稳定可靠呢?
  • 一个不成熟的产品被在公司内部推广,并要求多个部门的产品上线;这不是敏捷开发;用违背软件工程的方式去做事,结果就是为了测试而测试,为了上线而上线。“我们没法等到‘成熟’再上线,我们需要快速迭代,我们的资源只允许做到这个程度”-这真是一个烂透了的说辞,只能彰显项目规划把控的无能。
  • 当完成一件事情对所有人来说就是为了在给上级的周报,季报,年报上的一个check box划勾,为了在promotion doc上划勾,而忘记这个是为了一个更宏大的目标服务的时候,这个事情就偏离了最初的轨道。

当出现问题的时候,需要有人掷地有声的把问题说出来。

望以此为鉴。

发表在 闲扯淡 | 28条评论

手记 – 关于Kubernetes(P2)

接上文

(转载请注明作者和出处‘https://fourthringroad.com/’,请勿用于任何商业用途)

服务service

服务是一种给一组pod提供一个统一接入点的资源。

service通过标签收集到一组pod,client可以通过service的IP+port访问到底层的pod;这其中还包括了负载均衡的策略在里面。

service一旦被分配IP,可以保证这个IP不会变化。

Service提供四层lb能力,不能提供7层。

服务可以配置会话的亲和性:

sessionAffinity:ClientIP

这样同一个IP的请求会被转发到同一个后端pod

服务发现

实现服务发现的两层含义:

  • 一个vip/域名后挂在多个动态的后端
  • 服务之间可以通过一个服务来发现彼此的vip/域名(实际上是通过CoreDNS实现的)

如何通过DNS发现服务:

通过FQDN(fully qualified domain name )可以直接访问一个服务:

Service-name.default.svc.cluster.local

需要注意:

  1. 端口是默认信息,如果用httpclient访问,默认就是80/443;有必要需要自己指定
  2. 如果在同一个namespace下面,可以直接同service-name访问

集群内部pod之间通过服务访问:

ClusterIP模式:最简单的模式;直接创建一个service;直接IP/FQDN 就可以访问

集群内部pod访问外部服务

  • Service资源关联了一个Endpoint资源,里面是后端的ip 列表;如果暴露的是内部pods,那么是selector关联到的所有pod ip;如果手动配置外部服务的ip,那么service等同于暴露外部服务的访问了。
  • 更简单的方式:通过type: ExternalName直接指向一个外部资源FQDN: www.xxx.com

将服务暴露给外部客户端

1. Service type = nodeport:

会将node上开启一个port,并将这个port的流量重定向到service本身

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30123 #如果省略会随机分配
  selector:
    run: my-nginx

上述配置可以通过node_ip:30123 访问到service暴露的服务

2. service type = LoadBalancer

以nodeport为基础;会在云基础设施中创建一个独立LB资源,这个LB拥有可公开访问的独立IP地址;LB会将流量转发到node port,剩下的流程跟nodeport相同。

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    run: my-nginx

创建完成之后,一个通过 get svc 查看external-ip。

3. Ingress

LB模式,给每一个服务都生成一个负载均衡器以及独立IP。如果想做到七层(http)负载均衡呢?如果想提供基于cookie的会话亲和性呢?

 

Headless Service-无头服务

如果希望连接到所有pod,则需要获得所有pod的IP;如果创建Service的时候不生成IP,则DNS查询服务时(nslookup xx.default.svc.cluster.local),会返回所有pod的IPs;

配置: clusterIP:none

STS必须使用无头服务

负载均衡:

通常使用RoundRobin

 

底层实现

每个 Node 运行一个 kube-proxy 进程。 kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,

1. Userspace    

这种模式,kube-proxy 会监视 Kubernetes 控制平面对 Service 对象和 Endpoints 对象的添加和移除操作。 对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被proxy转发到 Service 的后端 Pods 中的某个上面(如 Endpoints 所报告的一样)。 使用哪个后端 Pod,是 kube-proxy 基于 SessionAffinity 来确定的。

最后,它配置 iptables 规则,捕获到达该 Service 的 clusterIP(是虚拟 IP) 和 Port 的请求,并重定向到代理端口,代理端口再代理请求到后端Pod。

默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。

2. Iptables

iptables是基于包过滤的防火墙工具

关于iptables原理:https://cloud.tencent.com/developer/article/1632776

这种模式,kube-proxy 会监视 Kubernetes 控制节点对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会配置 iptables 规则,从而捕获到达该 Service 的 clusterIP 和端口的请求,进而将请求重定向到 Service 的一组后端中的某个 Pod 上面(RR也是iptables实现的)。对于每个 Endpoints 对象,它也会配置 iptables 规则,这个规则会选择一个后端组合。

默认的策略是,kube-proxy 在 iptables 模式下随机选择一个后端。

使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理, 而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。

如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应, 则连接失败。 这与用户空间模式不同:在这种情况下,kube-proxy 将检测到与第一个 Pod 的连接已失败, 并会自动使用其他后端 Pod 重试。

你可以使用 Pod 就绪探测器 验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端。 这样做意味着你避免将流量通过 kube-proxy 发送到已知已失败的 Pod。

3. Ipvs:1.14版本默认使用

在 ipvs 模式下,kube-proxy 监视 Kubernetes 服务和端点,调用 netlink 接口相应地创建 IPVS 规则, 并定期将 IPVS 规则与 Kubernetes 服务和端点同步。 该控制循环可确保IPVS 状态与所需状态匹配。访问服务时,IPVS 将流量定向到后端Pod之一。

IPVS代理模式基于类似于 iptables 模式的 netfilter 挂钩函数, 但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。

IPVS 提供了更多选项来平衡后端 Pod 的流量。 这些是:

  • rr:轮替(Round-Robin)
  • lc:最少链接(Least Connection),即打开链接数量最少者优先
  • dh:目标地址哈希(Destination Hashing)
  • sh:源地址哈希(Source Hashing)
  • sed:最短预期延迟(Shortest Expected Delay)
  • nq:从不排队(Never Queue)

ConfigMap

A ConfigMap is an API object used to store non-confidential data in key-value pairs. Pods can consume ConfigMaps as environment variables, command-line arguments, or as configuration files in a volume.

A ConfigMap allows you to decouple environment-specific configuration from your container images, so that your applications are easily portable.

例子:

apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # property-like keys; each key maps to a simple value
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"

  # file-like keys
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true   

如何消费:

There are four different ways that you can use a ConfigMap to configure a container inside a Pod:

  1. Inside a container command and args
  2. Environment variables for a container
  3. Add a file in read-only volume, for the application to read
  4. Write code to run inside the Pod that uses the Kubernetes API to read a ConfigMap
apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo-pod
spec:
  containers:
    - name: demo
      image: alpine
      command: ["sleep", "3600"]
      env:
        # Define the environment variable
        - name: PLAYER_INITIAL_LIVES # Notice that the case is different here
                                     # from the key name in the ConfigMap.
          valueFrom:
            configMapKeyRef:
              name: game-demo           # The ConfigMap this value comes from.
              key: player_initial_lives # The key to fetch.
        - name: UI_PROPERTIES_FILE_NAME
          valueFrom:
            configMapKeyRef:
              name: game-demo
              key: ui_properties_file_name
      volumeMounts:
      - name: config
        mountPath: "/config"
        readOnly: true
  volumes:
    # You set volumes at the Pod level, then mount them into containers inside that Pod
    - name: config
      configMap:
        # Provide the name of the ConfigMap you want to mount.
        name: game-demo
        # An array of keys from the ConfigMap to create as files
        items:
        - key: "game.properties"
          path: "game.properties"
        - key: "user-interface.properties"
          path: "user-interface.properties"

ConfigMap的更新

  1. 如果是挂载volume支持热更新
  2. 如果是env则不支持热更新 

Secret

A Secret is an object that contains a small amount of sensitive data such as a password, a token, or a key. Such information might otherwise be put in a Pod specification or in a container image. Using a Secret means that you don’t need to include confidential data in your application code.

我个人认为应该尽量避免使用ConfigMap和Secret而是使用一些第三方微服务在k8s环境中提供相应的功能这样能够保证与k8s底层解耦方便未来的迁移

用途

  1. As files in a volume mounted on one or more of its containers.
  2. As container environment variable.

存储 kubelet从docker registry 拉取镜像时使用的密钥;(imagePullSecrets)

apiVersion: v1
data:
  username: YWRtaW4=
  password: MWYyZDFlM  mU2N2Rm
kind: Secret
metadata:
  annotations:
....

用法很简单:创建一个secret资源,里面有加密过的strings(base64加密),但在pod中使用时可以直接使用解密后的数据。

疑问:base64真的是一个安全的加密方案吗?

存储

Volumes

容器中的文件的存在是短暂的,一旦容器被kubelet重启,所有的数据都会被丢掉(以一个clean state重启);另外一个问题是一个pod中的容器如何共享文件的问题;这两个问题都可以通过卷来解决。

Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁持久卷。 对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。

卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。

容器中的进程看到的文件系统视图是由它们的 容器镜像 的初始内容以及挂载在容器中的卷(如果定义了的话)所组成的。卷挂载在镜像中的指定路径下。 Pod 配置中的每个容器必须独立指定各个卷的挂载位置。

几个常用的卷类型:

Secret

Configmap

这两个不赘述,都可以挂载到pod上;

emptyDir

当 Pod 分派到某个 Node 上时,emptyDir 卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。 就像其名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,这些容器都可以读写 emptyDir 卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会被永久删除。

注意:容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir 卷中的数据是安全的

取决于你的环境,emptyDir 卷存储在该节点所使用的介质上;这里的介质可以是磁盘或 SSD 或网络存储。但是,你可以将 emptyDir.medium 字段设置为 “Memory”,以告诉 Kubernetes 为你挂载 tmpfs(基于 RAM 的文件系统)

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

hostPath

hostPath 卷能将主机节点文件系统上的文件或目录挂载到你的 Pod 中

这是一共比较通用的解决方案,譬如你在主机上挂载了aws 的ebs,当然可以通过hostPath的形式将ebs中的一个目录挂载到pod中

不过最新的文档已经不推荐使用hostpath了:

HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。 当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # 宿主上目录位置
      path: /data
      # 此字段为可选
      type: Directory

其他还有一些类型,例如:

awsElasticBlockStore

awsElasticBlockStore 卷将 Amazon Web服务(AWS)EBS 卷 挂载到你的 Pod 中。与 emptyDir 在 Pod 被删除时也被删除不同,EBS 卷的内容在删除 Pod 时会被保留,卷只是被卸载掉了。 这意味着 EBS 卷可以预先填充数据,并且该数据可以在 Pod 之间共享。

持久卷:PV-PVC

原理非常简单:

  1. 把一个存储映射到一个PV(持久卷)资源;它们拥有独立于任何使用 PV 的 Pod 的生命周期;
  2. 在pod中申明一个PVC:持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求;
  3. 系统会根据pod的pvc去匹配一个核实的pv资源并挂载到pod中的容器中。

pvc的命名规则:(volumeClaimTemplates.name)-(pod name);每一个pod都会有一个自己特有的pvc;可以通过get pvc查看

如何释放pv

使用pv-pvc后,即使删除相关pods(STS);再次创建之后,存储数据依然存在并且能够正常访问。pvc在选择了retain模式之后,只有手动删除才会真的被删除。一旦删除pvc,相关的pv就会变成released状态,还需要手动删除pv(kubectl edit pv xxx)中的绑定信息才能变成available状态并可供其他的pod挂载。

调度抢占preemption和驱逐(Eviction)

调度

调度 是指将 Pod 放置到合适的 Node 上,然后对应 Node 上的 Kubelet 才能够运行这些 pod。

模块:kube-scheduler;用户也可以自己实现调度器来取代这个原有的组件。可以通过对schedulerName: default-scheduler 进行修改,使用自己定义的调度器。

满足一个 Pod 调度请求的所有 Node 称之为 可调度节点。 如果没有任何一个 Node 能满足 Pod 的资源请求,那么这个 Pod 将一直停留在 未调度状态直到调度器能够找到合适的 Node。

调度的流程:过滤&打分

调度器先在集群中找到一个 Pod 的所有可调度节点(过滤),然后根据一系列函数对这些可调度节点打分(打分), 选出其中得分最高的 Node 来运行 Pod。之后,调度器将这个调度决定通知给 kube-apiserver,这个过程叫做 绑定。

也可以称这两个过程为预选和优选

There are two supported ways to configure the filtering and scoring behavior of the scheduler:

  • Scheduling Policies allow you to configure Predicates(断言) for filtering and Priorities for scoring.
  • Scheduling Profiles(配置) 允许你配置实现不同调度阶段的插件, including: QueueSort, Filter, Score, Bind, Reserve, Permit, and others. You can also configure the kube-scheduler to run different profiles.

Predicate有一系列的算法可以使用:

  • 节点上剩余资源是否满足需求
  • 节点上已经使用的port是否冲突
  • 节点的label是否满足需求
  • 等等

predicate无法得到满足,pod会一直处在pending状态

预选过程之后获得一系列节点后,就会进行优选:

  • CPU和Memory使用率越低,权重越高
  • CPU和Mem使用率越接近,权重越高
  • 已经有镜像(不需要重复拉取),权重越高
  • 等等

可以通过以下方式控制一个pod落在哪个node上面:

  1. 通过nodeSelector来选择node标签
  2. 指定亲和性与反亲和性(Affinity and anti-affinity)
  3. 直接指定nodeName(PodSpec.nodeName)

Scheduler在bind一个pod到相关node之后,对应节点上的kubelet会监听(watch)到这个事件,并在node上创建相关容器。

1. 通过nodeSelector来选择node标签

Kubernetes 只会将 Pod 调度到拥有你所指定的每个标签的节点上。这个标签通常是云服务提供商打在node上的。

2. 指定亲和性与反亲和性(Affinity and anti-affinity)

1. (pod亲和)你可以使用节点上(或其他拓扑域中)运行的其他 Pod 的标签来实施调度约束, 而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起。

2. (节点亲和)可以指定具备某些标签的节点,也可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该 Pod。当然也可以要求严格匹配,否则不调度。

对于例子1:


apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
image: k8s.gcr.io/pause:2.0
  • 调度器必须将 Pod 调度到具有 topology.kubernetes.io/zone=V 标签的节点上,并且集群中至少有一个位于该可用区的节点上运行着带有 security=S1 标签的 Pod。
  • 如果同一可用区中存在其他运行着带有 security=S2 标签的 Pod 节点, 并且节点具有标签 topology.kubernetes.io/zone=R,Pod 不能被调度到该节点上。

topologyKey可以是服务器,机架,机房,az,region等等,其本质只是node的一个标签而已。

对于例子2:

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0
  • 节点必须包含键名为 kubernetes.io/os 的标签,并且其取值为 linux。
  • 节点 最好 具有键名为 another-node-label-key 且取值为 another-node-label-value 的标签。

如果operator使用了NotIn 和 DoesNotExist 可用来实现节点反亲和性行为。

污点taint和容忍度toleration

节点亲和性 是 Pod 的一种属性,它使 Pod 被吸引到一类特定的节点 (这可能出于一种偏好,也可能是硬性要求)。 污点(Taint)则相反——它使节点能够排斥一类特定的 Pod。

容忍度(Toleration)是应用于 Pod 上的,允许(但并不要求)Pod 调度到带有与之匹配的污点的节点上。

污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod,是不会被该节点接受的。

应用场景:

  • 如果您想将某些节点专门分配给特定的一组用户使用,您可以给这些节点添加一个污点
  • 在部分节点配备了特殊硬件(比如 GPU)的集群中, 我们希望不需要这类硬件的 Pod 不要被分配到这些特殊节点,以便为后继需要这类硬件的 Pod 保留资源。
  • 基于污点的驱逐: 这是在每个 Pod 中配置的在节点出现问题时的驱逐行为,污点的 effect 值 NoExecute会影响已经在节点上运行的 Pod,能够驱逐pod;比如,一个使用了很多本地状态的应用程序在网络断开时,仍然希望停留在当前节点上运行一段较长的时间, 愿意等待网络恢复以避免被驱逐。

effect的三个选项:

  • Noschedule
  • preferNoSchedule
  • noExecute:不调度,且会驱逐已经存在的pod

例子:

kubectl taint nodes node1 key1=value1:NoSchedule

给节点 node1 增加一个污点,它的键名是 key1,键值是 value1,效果是 NoSchedule。 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到 node1 这个节点。

如果一个pod在spec中配置如下容忍度,则可以被分配到node1

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"

一个容忍度和一个污点相“匹配”是指它们有一样的键名和效果

PreferNoSchedule。 这是“优化”或“软”版本的 NoSchedule —— 系统会 尽量 避免将 Pod 调度到存在其不能容忍污点的节点上, 但这不是强制的。

master节点天生就被打了污点,所以普通的pod不会被调度到master集群上;如果将其污点改成preferNoSchedule,那么在资源不足时,也可以将pod部署到master上了。

 

抢占

Pod 可以有 优先级。 优先级表示一个 Pod 相对于其他 Pod 的重要性。 如果一个 Pod 无法被调度,调度程序会尝试抢占(驱逐)较低优先级的 Pod, 以使悬决 Pod 可以被调度。

PriorityClass 是一个无名称空间对象,它定义了从优先级类名称到优先级整数值的映射。将它跟pod绑定就可以指定pod的优先级。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "此优先级类应仅用于 XYZ 服务 Pod。"

---

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority
  • 当启用 Pod 优先级时,调度程序会按优先级对悬决 Pod 进行排序, 并且每个悬决的 Pod 会被放置在调度队列中其他优先级较低的悬决 Pod 之前。 因此,如果满足调度要求,较高优先级的 Pod 可能会比具有较低优先级的 Pod 更早调度。 如果无法调度此类 Pod,调度程序将继续并尝试调度其他较低优先级的 Pod。
  • Pod 被创建后会进入队列等待调度。 调度器从队列中挑选一个 Pod 并尝试将它调度到某个节点上。 如果没有找到满足 Pod 的所指定的所有要求的节点,则触发对悬决 Pod 的抢占逻辑。 让我们将悬决 Pod 称为 P。抢占逻辑试图找到一个节点, 在该节点中删除一个或多个优先级低于 P 的 Pod,则可以将 P 调度到该节点上。 如果找到这样的节点,一个或多个优先级较低的 Pod 会被从节点中驱逐。 被驱逐的 Pod 消失后,P 可以被调度到该节点上。

驱逐

节点压力驱逐

节点压力驱逐是 kubelet 主动终止 Pod 以回收节点上资源的过程。

kubelet 监控集群节点的 CPU、内存、磁盘空间和文件系统的 inode 等资源。 当这些资源中的一个或者多个达到特定的消耗水平, kubelet 可以主动地使节点上一个或者多个 Pod 失效,以回收资源防止饥饿。

API 发起的驱逐

API 发起的驱逐是一个先调用 Eviction API 创建 Eviction 对象,再由该对象体面地中止 Pod 的过程。

你可以通过直接调用 Eviction API 发起驱逐,也可以通过编程的方式使用 API 服务器的客户端来发起驱逐, 比如 kubectl drain 命令。 此操作创建一个 Eviction 对象,该对象再驱动 API 服务器终止选定的 Pod。

 

K8s operator的二次开发

https://kubernetes.io/docs/concepts/extend-kubernetes/#extension-patterns

https://zhuanlan.zhihu.com/p/246550722

https://www.bilibili.com/video/BV1oL411F7hN

People who run workloads on Kubernetes often like to use automation to take care of repeatable tasks. The Operator pattern captures how you can write code to automate a task beyond what Kubernetes itself provides.

operator核心就是将运维操作自动化;

譬如运维人员在k8s创建一个ES集群时,有很多需要依次执行的步骤:

  • 创建管理容器
  • 创建master容器
  • 创建协调节点容器
  • 创建数据节点
  • 创建kibana节点
  • 等等等等

这些个步骤的自动化可以放在k8s外当然也可以通过k8s的operator模式来实现-“operator 是一种 kubernetes 的扩展形式可以帮助用户以 Kubernetes 的声明式 API 风格自定义来管理应用及服务,operator已经成为分布式应用在k8s集群部署的事实标准了,在云原生时代系统想迁移到k8s集群上编写operator应用是必不可少的能力

使用operator之后,在管理云资源时,可以通过声明式API,而不是命令式的方式。

下面画了一个我理解的的大概原理:

Operator:其实就是定义一个新的控制回路(controller),这个控制回路会创建,监控并控制一个自定义资源(CR)的状态;

CR是什么:

在 Kubernetes 中我们使用的 Deployment, DamenSet,StatefulSet, Service,Ingress, ConfigMap, Secret 这些都是resource资源;当我们在使用中发现现有的这些资源不能满足我们的需求的时候,Kubernetes 提供了自定义资源(Custom Resource)和 operator为应用程序提供基于 kuberntes 扩展。

如何部署Operator

其实就是将CRD(definition) 和 controller 添加到集群中。(使支持)

这样可以用kube create/get/edit直接对这个资源进行操作;与内建controller不同的是,这个控制器会在控制平面之外运行。

一个postgres-operator的CRD:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: postgresqls.acid.zalan.do
  labels:
    app.kubernetes.io/name: postgres-operator
  annotations:
    "helm.sh/hook": crd-install
spec:
  group: acid.zalan.do
  names:
    kind: postgresql
    listKind: postgresqlList
    plural: postgresqls
    singular: postgresql
    shortNames:
    - pg  additionalPrinterColumns:
  - name: Team
    type: string
    description: Team responsible for Postgres CLuster
    JSONPath: .spec.teamId
  - name: Version
    type: string
    description: PostgreSQL version
    JSONPath: .spec.postgresql.version
  - name: Pods
    type: integer
    description: Number of Pods per Postgres cluster
    JSONPath: .spec.numberOfInstances
  - name: Volume
    type: string
    description: Size of the bound volume
    JSONPath: .spec.volume.size
...

为了方便Operator开发,有一些工具会为我们搭建脚手架,例如:

  • operator SDK —— operator framework,是 CoreOS 公司开发和维护的用于快速创建 operator 的工具,可以帮助我们快速构建 operator 应用,
  • KUDO (Kubernetes 通用声明式 Operator)
  • kubebuilder,kubernetes SIG 在维护的一个项目
  • Metacontroller,可与 Webhook 结合使用,以实现自己的功能。

重要函数入口:reconcile()

Helm

以chart包的形式管理部署k8s的程序;

一个chart包包括一系列的文件资源,并支持模版形式方便变量的注入。

https://www.jianshu.com/p/4bd853a8068b

一些常用kubectl命令

kubectl apply -f xx.yml

kubectl create -f xx.yml

kubectl cluster-info

kubectl get nodes

Kubectl describe node (NODE_ID)

Kubectl get pod

kubectl get pods –show-lables

kubectl get pods xxx -o yaml

Kubectl get pod -o wide 查看详细信息

kubectl lable pod xxx foo=bar

kubectl describe pod xxx

kubectl delete pod xxx

kubectl log xxx

kubectl log xxx -c 指定容器

Kubectl get services

kubectl exec xxx -it — /bin/sh 进入容器

kubectl delete deployment —all

kubectl explain rs -》 查看rs的文档

kubectl exec xx-pod — curl -s http://xxx   在pod内部执行一个Linux命令  

kubectl exec -it xx /bin/bash -》 进入容器

如何指定namespace: -n xxxx

kubectl apply -f xx.yml

kubectl create -f xx.yml

kubectl cluster-info

kubectl get nodes

Kubectl describe node (NODE_ID)

Kubectl get pod

kubectl get pods –show-lables

kubectl get pods xxx -o yaml

Kubectl get pod -o wide 查看详细信息

kubectl lable pod xxx foo=bar

kubectl describe pod xxx

kubectl delete pod xxx

kubectl log xxx

kubectl log xxx -c 指定容器

Kubectl get services

kubectl exec xxx -it — /bin/sh 进入容器

kubectl delete deployment —all

kubectl explain rs -》 查看rs的文档

kubectl exec xx-pod — curl -s http://xxx   在pod内部执行一个Linux命令  

kubectl exec -it xx /bin/bash -》 进入容器

如何指定namespace: -n xxxx

发表在 云原生 | 4条评论

ES文件结构

es有node级别state信息,index级别state信息,shard级别state信息,都分别保存在各自独立的目录下,其中index级别的state信息在7.x版本中被合并到node级别state同一个目录下。

发表在 ElasticSearch | 5条评论

ES内存溢出/CPU使用率高Debug

最近值班帮助用户诊断了不少内存溢出的case,这里做个笔记,顺便回顾一下java 内存溢出问题排查思路:

ES内存/CPU使用率高问题排查

用free -m查看Linux系统的内存使用情况

top命令也可以看内存使用情况,linux直接M排序,mac需要输入o再输入mem

主要思路

ES堆内存分析

jmap -histo:live $pid >> heap.log

打印活对象的信息,查看当前占用内存多的ES对象是什么

造成CPU压力的hot thread分析

抓出cpu使用率大于某一个阈值的Thread的tid;然后用jstack打出es进程所有的线程堆栈信息,用前面的tid在es线程栈中查出详细信息并进行分析。可以自己基于top和jstack来写脚本,也可以用网上一些现成的工具包;

ES熔断机制

https://elasticsearch.cn/article/698

堆栈分析

Step1:查看java进程id

jps

Step2:通过top找到cpu和内存使用率高的线程ID

top -H -p PID

Step3: 打印java进程的堆栈信息

jstack PID >> log.txt

Step4: 在堆栈信息里面查找相关线程的堆栈

echo “obase=16; tid” | bc   (转化线程号为16进制,可用于在log.txt里面查询)

Step5:分析负载高的线程栈都是什么业务操作。优化程序并处理问题。

可视化分析

VisualVM

eclipse的Memory Analyzer

查看ES导致GC的频率

jstat可以用于查看查看虚拟机statistics,包括Heap size和垃圾回收状况的监控

查看jvm GC的情况

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量

jstat -gcutil PID  总结垃圾回收统计

jstat -gc  27836  10000    每10秒打印一次

字段说明:
字段	说明
S0C	年轻代第一个Survivor区的大小(字节)
S1C	年轻代第二个Survivor区的大小(字节)
S0U	年轻代第一个Survivor区的使用大小(字节)
S1U	年轻代第二个Survivor区的使用大小(字节)
EC	年轻代中Eden区的大小(字节)
EU	年轻代中Eden区的使用大小(字节)
OC	老年代大小(字节)
OU	老年代使用大小(字节)
MC	方法区大小(字节)
MU	方法区使用大小(字节)
CCSC	压缩类空间大小(字节)
CCSU	压缩类空间使用大小(字节)
YGC	年轻代垃圾回收次数
YGCT	年轻代垃圾回收消耗时间
FGC	老年代垃圾回收次数
FGCT	老年代垃圾回收消耗时间
GCT	垃圾回收消耗总时间

查看堆内存的使用细节

JVM管理堆内存(heap)和非堆内存(non-heap);堆内存可以直接用xms,xmx设定;非堆内存可以用XX:MetaspaceSize -XX:MaxMetaspaceSize 进行配置

更多细节:https://coder-programming.blog.csdn.net/article/details/105213285

有多种方式可以导出处内存文件:

  • 设定-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath,这样在java进程内存溢出退出时就会保存一份heap dump供离线分析;(hprof文件)
  • 使用jmap手动导出堆/非堆内存:
    • jmap -dump:format=b,file=文件名 [pid]
    • jmap -heap pid 展示pid的整体堆信息
    • jmap -histo pid 展示class的内存情况
    • jmap -histo:live pid>a.log 可以观察heap中所有对象的情况(heap中所有生存的对象的情况)
    • jmap -histo pid|head -n 10 查看前10位
    • jmap -histo pid | sort -k 2 -g -r 查看对象数最多的对象,按降序输出
    • jmap -histo pid | sort -k 3 -g -r 查看内存的对象,按降序输出
    • -histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.

死锁排查

同样使用jstack命令,看到多个Thread之间,各自拥有的锁和等待的锁是否有冲突

随便扒个网上的例子

发表在 ElasticSearch | 623条评论

手记 – 关于Kubernetes(P1)

(转载请注明作者和出处‘https://fourthringroad.com/’,请勿用于任何商业用途)

资料

 

基础概念

什么是云原生:

先看看官方定义

Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.

These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, they allow engineers to make high-impact changes frequently and predictably with minimal toil.

The Cloud Native Computing Foundation seeks to drive adoption of this paradigm by fostering and sustaining an ecosystem of open source, vendor-neutral projects. We democratize state-of-the-art patterns to make these innovations accessible for everyone.

简而言之,云原生就是在多种云环境中,构建高可用,弹性伸缩,方便管理和监控的应用。

CNCF(Cloud Native Computing Foundation):

云原生计算基金会,Linux旗下非盈利基金会。谷歌容器编排技术Borg(k8s前身)开源后与Linux共同成立了CNCF。致力于推动云原生相关技术的发展。

云原生的技术:

参考CNCF的路线图(trail map),主要和代表技术为:

  1. 容器化:docker
  2. CI/CD(Continuous Integration / Delivery):Argo
  3. 编排:k8s+helm
  4. 监控&分析:Prometheus for monitoring, Fluentd for logging, Jaeger for tracing
  5. 服务代理(proxy),发现(discovery),治理(mesh):CoreDNS, Envoy, Linkerd
  6. 网络和安全:Calico, Flannel, OPA, Falco
  7. 分布式数据库和存储:Vitess(可分片的mysql),etcd, TiKV
  8. 流处理&消息处理
  9. 容器镜像库和运行环境:Harbor(私有镜像仓库)
  10. 软件发布

参考CNCF的全景图(landscape),可以了解更多的项目,包括已经毕业的,孵化中的,CNCF成员自己的项目。。。等等。

云原生应用的特性(什么是云原生应用)

TBD

 

回顾容器化发展:

物理机部署的缺点是:资源不存在边界,隔离性差,扩展费用昂贵。

虚拟机部署:解决物理机部署的缺点

容器化部署:共享OS,相比VM更轻量级

k8s发展背景:

Kubernetes(k8s): 希腊语舵手(helmsman)所以logo是一个舵哈哈

可以看出K8s只是云原生版图中的一部分,是一个容器编排系统(Container Ochestration):

无论是基于云架构,还是基于企业内部的物理机/虚机资源池,容器化已经成为广泛采纳的部署技术,但是容器资源的管理,调度,编排是复杂的问题,所以出现了一些解决方案,譬如Mesos,K8s,Swarm。

例如我司中间件云服务的就是基于就是在IaaS上的一个k8s集群,用户的请求会通过我们的控制层传达到k8s集群,进行相关资源的管理调度和编排。

K8s特性:

  1. Automated rollouts and rollbacks
  2. Service discovery and load balancing
  3. Storage orchestration
  4. Secret and configuration management
  5. Resource Management for Pods and Containers
  6. Batch Execution(基于jobs)
  7. IPv4 & IPv6 dual-stack
  8. Horizontal Scaling
  9. Self-healing
  10. Designed for Extensibility

K8s架构:

K8s采用了Master-Worker架构,master就是指图中的control plane控制平面,为了高可用通常也是一个集群。负责管理worker节点和上面的pods。Worker指上图的node节点,pod会部署在上面。

Control-plane的组件:

  1. Kube-api-server:暴露Kubernetes API,是对外的frontend;支持水平扩展。只有它与控制平面的各个组件通信。
  2. Etcd:高可用分布式键值数据库,存储集群信息;基于http+raft协议。
  3. Kube-Scheduler:负责调度pod到某个node上面;考虑的因素包括: individual and collective resource requirements, hardware/software/policy constraints, affinity and anti-affinity specifications, data locality, inter-workload interference, and deadlines
  4. Kube-Controller-manager: 用来执行controller协程;后者是一个控制回路,通过api-server监控pod状态,并使其向期望状态发展
  5. Cloud-controller-manager:The cloud-controller-manager only runs controllers that are specific to your cloud provider;通过上图的 cloud provider’s API进行控制。

Node上的组件:

  1. Kubelet: 负责管理node上的pod(a set of containers)
  2. Kube-proxy: 负责网络请求转发;譬如,一系列的pod是通过service向外界提供服务,kube-proxy就是实现service的基础。

还有一些组件可以借助上述组件和k8s的机制进行安装,并且属于kube-system的名称空间下,为集群提供服务,例如CoreDNS。完整可询 https://kubernetes.io/docs/concepts/cluster-administration/addons/

K8s vs istio

TBD

GKE (Google Kubernetes Engine) vs K8S :

GKE是托管的k8s服务,out-of-box,多租户,类似的产品包括AWS的 Amazon Elastic Kubernetes Service (EKS)

K8s vs Mesos vs Swarm

TBD

Why etcd?

  • 数据一致性
  • 支持持久化
  • 高可用
  • 高性能

相关核心概念:

  • Pod:k8s管理的最小单元,可以包括一至多个容器。
  • 资源清单: 定义资源的yaml文件
  • Controller:类似一个控制回路Control Loop,监控资源状态并向理想状态方向调整,例如ReplicaSet, StatefulSet, DaemonSet等等controllers。
  • Operator: k8s的扩展性的体现,自定义资源类型(CRD)及开发对应的自定义controller。
  • Service & Ingress:如何将一组pod以服务形式对外暴露
  • Volume & PV:存储
  • ConfigMap & Secret: 配置和密钥
  • Scheduler: 如何进行pod的调度,故障域,亲和性,污点等概念
  • Authentication & Authorization: 认证 与 鉴权。

工具:

  • Helm:
  • K9s:

关于API Server:

API Server暴露的HTTP接口,使用户与k8s集群,组件与组件之间得以相互通信。

用户有几个常用工具管理集群:restful api, kubectl,kubeadm,k9s。

Restful api是一切的基础,例如我们使用kubectl + yml时,实际上kubectl底层将信息转化为api+json的形式与api server进行通信。

其中restful api是基于OpenAPI规范设计的后者是一个基于Yaml/json的Restful API设计规范同时方便用户/机器理解遵循OpenAPI规范设计的API可以利用工具校验生成文档生成多语言客户端等等

关于k8s中的对象(object,也可以叫资源):

K8s中的一切资源都是对象,包括:pod, rs, deployment, job, service, ingress, volume, configmap, secrete,Namespace, node, role, clusterRole等等

有两个东西用来描述k8s中的实体:spec(资源清单)和status(状态);The Kubernetes control plane continually and actively manages every object’s actual state to match the desired state you supplied.

Namespace-名称空间:

保证对象(object)的隔离性:In Kubernetes, namespaces provides a mechanism for isolating groups of resources within a single cluster. 

但是有些资源是跨namespace的,譬如role。

GVK vs GVR

GVK = GroupVersionKind,GVR = GroupVersionResource。

apiVersion:这个就是 GV 。

kind:这个就是 K。根据 GVK K8s 就能找到你到底要创建什么类型的资源,根据你定义的 Spec 创建好资源之后就成为了 Resource,也就是 GVR。GVK/GVR 就是 K8s 资源的坐标,是我们创建/删除/修改/读取资源的基础。

关于pods

pod是最小部署单元,实际上是一组容器的集合,共享存储和网络资源。可以将pod理解成一个逻辑主机(logical host);pod中的容器共享存储(fs volumes)、网络和命名空间(namespaces)

如何通过kubectl启动一个pod:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
- containerPort: 80
kubectl apply -f xxx.yaml

通常不会直接创建一个pod(创建之后无人管理),而是通过controller,譬如deployment, ReplicaSet, statefulSet, job 等等。

一个pod可以包括多个containers,这些container可以共享资源(网络,存储),相互通信,例如sidecar pattern;当然必须要有合理的理由将这些containers结合在一起;否则就应该将他们部署在不同的pod中。

注:一个container内应当尽量只有一个进程。

pod中的共享资源:

Pause container:

之所以pod中的容器能够共享网络,是因为在创建pod之后,首先有一个隐藏的容器被创建了,pause container,之后的容器都会复用它的网络栈。

The ‘pause’ container is a container which holds the network namespace for the pod.

pod之间通信:

通过IP可以直接访问;每个pod(pause 容器)都有全局唯一的IP地址,并且构成一个扁平化的虚拟网络。所有的pod的IP地址资源和真实物理主机的路由关系是存储在etcd中的。

同一个node上pod间通信:走内部docker网桥

不同node上pod间通信有所不同:走物理网卡(src pod ip + src node ip -> dst pod ip + dst node ip)通过flannel实现

容器探针:

pod中的容器可以定义探针,可以周期性的被kubelet进行健康诊断,支持的探针类型:

  1. 容器操作
  2. Tcp socket请求
  3. http get 请求

静态pod:

Static Pods are always bound to one Kubelet on a specific node。kubelet直接管理的pod(而不是controller)。

容器&pod的生命周期:

pod的phase阶段:

  1. Pending:还在schedule,下载image等等
  2. Running:至少一个容器启动
  3. Succeeded:所有容器都成功停止
  4. Failed:所有容器都停止,而且至少一个是以失败状态停止的
  5. Unknown:For some reason the state of the Pod could not be obtained. This phase typically occurs due to an error in communicating with the node where the Pod should be running.

一个pod一旦被创建,就一定遵循上述几个阶段,不会出现类似reschedule到另一个node的状态。

Container的阶段:

Waiting, Running, and Terminated

Status

Pod启动之后更详尽的状态记录在它spec中的status下面,里面有资源在运行时的一些信息;

有下面一些内置的status:

  • PodScheduled: the Pod has been scheduled to a node.
  • ContainersReady: all containers in the Pod are ready.
  • Initialized: all init containers have completed successfully.
  • Ready: the Pod is able to serve requests and should be added to the load balancing pools of all matching Services.

Pod readiness:

在spec中设置readinessGates;允许用户向 Pod Status 中注入额外的自定义反馈或者信号;pod 中的所有 container 都ready并且所有 readinessGates 中定义的状态都是 ‘True’ 之后 Pod 才会被标记为 ready.

kind: Pod
...
spec:
  readinessGates:
    - conditionType: "www.example.com/feature-1"
status:
  conditions:
    - type: Ready                              # 内置的 Pod 状况
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
    - type: "www.example.com/feature-1"        # 额外的 Pod 状况
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
  containerStatuses:
    - containerID: docker://abcd...
      ready: true

When a Pod’s containers are Ready but at least one custom condition is missing or False, the kubelet sets the Pod’s condition to ContainersReady.

探针机制:

A probe is a diagnostic performed periodically by the kubelet on a container

探针的形式:

  • exec
  • grpc
  • httpGet
  • tcpSocket

Readiness Probe -就绪探针

检测是否ready,只有ready才会被驾到Service后面的pod列表里并提供对外服务

Liveness Probe-生存探针

检测容器是否存活

Startup Probe – 启动探针

其他探针不会在startup探针成功之前启动。

Init 容器

A Pod can have multiple containers running apps within it, but it can also have one or more init containers, which are run before the app containers are started.

Init containers are exactly like regular containers, except:

  • Init containers always run to completion.
  • Each init container must complete successfully before the next one starts.

Init 容器有一些牛逼的地方,譬如:

  • 包含一些运行时镜像中没有的依赖
  • 权限可以与应用程序容器不同

例如,可以在init容器中循环检查一个外部依赖是否ready,ready才启动pod;如果超时则exit 1;

Container Lifecycle Hooks

容器在启动后和结束前都有一个hook:PostStart/PreStop

 

控制器

所谓控制器就是一个control loop-控制回路,官方给了一个调温器的例子来说明:

When you set the temperature, that’s telling the thermostat about your desired state. The actual room temperature is the current state. The thermostat acts to bring the current state closer to the desired state, by turning equipment on or off.

contollers都运行在kube-controller-manager上。

A Kubernetes Controller is a routine;一个控制器本质上是一个go 协程;

控制器获取信息,发送指令都是通过API Server间接完成。

CRI: Container Runtime Interface

一个非常重要的模块。Container Runtime是从容器系统中剥离出来专门运行容器的软件功能。K8s为了兼容多种container runtime,抽象出了一层CRI;kubelet 通过cri直接管理容器的运行。

标签和注解

pod上的label标签用于selector进行选择;

pod上的annotation注解用于为pod提供更多的信息。

Owner reference

Owner reference 告诉控制平面某一个对象依赖另外的哪一个对象。Kubernetes uses owner references to give the control plane, and other API clients, the opportunity to clean up related resources before deleting an object. In most cases, Kubernetes manages owner references automatically.

例如,一个RS创建的pod,如果用yaml格式查看:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2020-02-12T07:06:16Z"
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-b2zdv
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: f391f6db-bb9b-4c09-ae74-6a1f77f3d5cf
...

RC-ReplicationController(deprecated)

A ReplicationController ensures that a specified number of pod replicas are running at any one time

Example:
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

保证label selector匹配的pod数量和replicas中的一样

RS-ReplicaSet

增强版本的RC,selector 的表达能力更起强,譬如支持:匹配缺少某个label的pod,包含某个特定label的pod而不管其值如何。

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

Deployment

不直接管理pod,而是管理RS,方便滚动更新/部署,回滚,扩缩容。滚动更新时,实际上是创建一个新的RS来间接控制新的pod的创建。

实现零停机升级的手段包括蓝绿部署,滚动部署;

早期的滚动部署可以通过kubectl来直接执行

Kubectl rolling-update xx-pod ….

但是这种由客户端发起的包含多条命令的流程有明显的缺点,及一旦中间client和master之间的网络出现问题,集群相关的对象可能会陷入一个中间状态;所以更理想的方式是‘声明’一个状态,交给k8s集群,让集群自己去完成工作。

声明式/命令式

声明式,告诉系统,系统会自主去实现;隐藏了底层的复杂逻辑。

例子
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

HPA:horizontal pod autoscaling

支持按照cpu,内存等使用率来自动扩缩容。底层通过RS间接控制Pod的数量

StatefulSet:

与Deployment和RS不同;它是针对有状态服务的pod管理。譬如一个pod要挂PVC则需要使用StatefulSet;支持按照顺序的扩缩容(pod按照0,1,2,3…的顺序扩容,相反顺序缩容);Pod重新调度后网络标志不变(主机域名不会变换,ip会变)。

STS中的pod的名称为 “container名称+序号”;例如:xx-0, xx-1, xx-3

STS中的每个pod都有自己的域名,可通过DNS解析,不需要直接通过IP地址调用;地址格式为’pod_name.headless_svc_name’;pod飘移时IP会变,但是域名不会变;

STS必须配合无头服务来使用;直接访问无头服务(dig),可以拿到所有pod的IP地址;

典型的例子,ES的集群就需要用Sts进行管理。

DaemonSet:

在node上运行一个守护pod。比如每个node上都要收集日志,就可以通过这个pod来执行。

Job/Cron Job

批处理/定时任务

 

发表在 云原生 | 一条评论