K8s — 云原生之路

上周,抽空听了两场云栖大会(一场关于k8s 云原生的发展趋势,一场是关于k8s安全的话题), 这几年, 容器领域的技术,发展太快了,工具层出不穷 , 也越来越容易使用了, 逐渐替代了一些硬件的功能; 传统的运维正面临前所未有的挑战, 如果不了解这个领域的技术,未来可想而知

K8s -- 云原生之路

451 研究说未来 76%的企业将上云, 你觉得呢 ?

Auther: Makr.wei
当年Twitter飞速增长期,服务器的扩容常常跟不上用户的爆炸式增长,所以经常会出现宕机,这时候会出现一只小鸟拉着鲸鱼的图片, 就是著名的失败之鲸, 08/09年的Twitter,那绝对是Twitter的一段暗黑历史,可爱的失败鲸因此得以频频出镜。它不仅仅是宕机的标识,也象征了Twitter的程序猿在修复程序之时,这些粉丝真诚的相信Twitter会越来越好。

K8s -- 云原生之路

Twitter 急需一个资源管理系统来帮助他们摆脱可怕的“失败之鲸”,09年,参考Google Borg的开源工具Mesos,进入了Twitter的视野,被用于解决宕机问题。同时,几位大牛开始游说Google全球数据中心网络的负责人开放Borg,对于“是否要把运营Goolgle的秘密武器作为开源技术拱手让人?”这个问题让Google管理层十分纠结,一直犹豫不决, 但最终,谷歌批准发起开源项目Kubernetes(开源版的Borg),把这一重要工具贡献给世界,从此,Kubernetes和Docker成为黄金搭档,迅速传播开来。正是因为k8s社区的高度活跃,以及功能快速迭代, 在2019年5月,Twitter宣布放弃已经管理300000台主机,使用了十年之久的Mesos, 转投Kubernetes社区。

基于Cloud-native的Kubernetes是当今最流行的应用架构,相关技术通过模块化和复杂度转移,来减轻研发的痛苦。
从头开始创建微服务,或者把大系统拆解成微服务,可参考很多重要的理论文章和实践经验。这些实践都基于 Eric Evans 的 Domain-Drive Design, 和有界上下文(Bounded Context)和聚合的理论文章。 Bounded Context 为我们把大模型分解为各种小组件提供依据, 通过聚合有界内容组成模块,并定义事务边界。无论是否是分布式微服务系统,都围绕组织架构,体系结构和运行时状态提供解决方案,除了领域模型之外,还有一系列复杂的问题需要我们考虑。

Docker和Kubernetes,为我们提供了很多新的元素和抽象工具解决分布式系统的问题,为分布式系统带来了大量的便利,不过,遵循Garbage in ,Garbage out 定律,我们放进容器的是垃圾,那么,我们也将得到一个超级垃圾体。

K8s -- 云原生之路

代码层面: 每一个参数,方法,实例化对象都在将来的长期运维中起到非常重要的影响,无论使用什么容器工具,开发团队交付物都非常重要。程序员要尽可能的写出逻辑清晰的代码,完成足够的自动化测试,持续重构提升代码质量,这是一个具备工匠精神的程序员的本分。
领域设计: 领域驱动的设计是让软件实现尽可能贴近真实世界的一种思想。这个技术是OOP理论的延展,准确的业务模型,清晰的事务边界,易用的接口和丰富的API,是容器自动化的基础。
微服务架构, 为设计持续变化的分布式系统,提供了有价值的规范和实践指导,这些原则可以帮助实现常规的系统需求,比如动态扩展、弹性伸缩和快速跌代
容器可以帮助我们快速的搭建一套标准的分布式系统, 模块构建和容器复用是实现Cloud-native 应用的一个先决条件,当容器数量不断增长,维护系统正常运行,管理各种资源就需要一个强大的编排工具。
Cloud native用于描述自动化,集成化,微系统化,模块化和工具化的大规模服务平台。
Distributed Primitives
JVM在本地操作系统进程中编译运行,而Kubernetes提供分布式系统的基本单元和运行工具,可分布运行在多个Node的进程中,增加了一种高维视角。
我们仍然需要使用OOP构建系统的组件,但是我们可用Kubernetes的基本元素完成一些系统间的行为
基本元素和分布式基本元素有些共性,但不能直接替换,他们工作在不同系统的层面,比如,容器化,我们仍然需要使用Java编写程序,但我们可以用 K8s 中的Cron Job 来替代Java中的ExecutorService的实现

Container
容器是Cloud-native的一个基本构建单元,相对于OOP,容器的Image可以类比Class,而Container则类似于Object,我们可以继承Class,同样的的Image也可以继承其它的Image; Object可以组装功能,同理可以将相互协作的Container放入Pod来进行容器组合。k8s可以像Jvm一样实现多主机交互,并负责资源的运行和管理。Container初始化像Java的构造函数, DaemonSets 类似于java后台线程(Daemon thread)。Pod有时类似于IOC框架,当多个容器共享生命周期,可以直接相互访问。

Container是单个业务的功能单元
Image属于团队,拥有自己的发布周期
Image有自己的运行时依赖和资源需求
Image是自包含,自定义的,承载运行时依赖
Image是不可变的,一旦创建,不可更改,且已完成配置
Image已完成运行时依赖和资源需求
Image拥有明确的定义的Api来暴露功能
Container是一次性的,随时可以安全的扩容和缩减
除了上面这些特点之外,Image业务模块创建是基于参数化的,以便于在不同环境复用。并且Image也必须实现参数化以适应各种应用场景。 原子化,模块化, 可复用化的Image,类似于编程语言中的类库
Pods
容器的Image提供单一功能单元,属于单个团队,具有独立的发布周期,并提供部署和运行时资源隔离。 大多数情况下,一个微服务对应于一个Image。
但是,Cloud-Native 提供了一个新元素来管理Kubernetes中一组容器的生命周期,它被称为Pod。 Pod是一组容器调度,部署和运行的原子单元。 一个Pod里的所有容器始终安排在同一Node上,无论是扩展还是Node迁移,都可以一起部署,还可以共享文件系统,网络和NameSpace。 这个联合体生命周期内允许Pod中的容器通过文件系统相互交互或者网络交互。
在开发和创建期,微服务对应于一个开发团队的镜像,在运行时,微服务呈现为一个Pod,Pod是部署,编排,伸缩的单位,无论是扩展还是迁移,运行容器的唯一方法是通过Pod。

K8s -- 云原生之路

Pod的一些特性

Pod是调度任务的最小单元。 调度程序会尝试寻找满足Pod里的容器资源的Node。如果创建包含多个Container的Pod,调度程序会寻找满足所有container资源的Node。
托管容器, 同一Pod中的容器有独特交互方式,最常见的通信方式包括使用共享本地文件系统交换数据,网络层交互,或使用主机进程间通信(IPC)机制进行高性能交互
Pod具有属于它的所有容器共享的IP地址,名称和端口范围。必须仔细配置同一Pod中的容器以避免端口冲突,就像同一主机上并行的Unix进程一样。
Pod是我们的应用程序在Kubernetes系统的原子单元,不能直接访问,那么,我们该如何访问Pod的应用呢? 答案是Service
Service
Pods 可以随时启动或者销毁,比如在对Pod扩容或者缩减,健康心跳失败,或者迁移Node时。Pod的IP的地址只有在Pod启动后才会生效,假设当前运行的Node出问题,Pod也可能会自动迁移到其它Node。这就意味着Pod的生命周期中IP地址可能会发生变化,如果这时另外一个Pod里的资源,通过IP地址访问被迁移的POD里的资源,就会发生异常,而Service就可以作为隔离层隔离这种差异,实现自动的服务发现。
这就是Kubernetes Services发挥作用的地方。Service是一个简单但功能强大的Kubernetes抽象层,它将Service名称永久绑定到IP地址和端口上。 因此,Service代表访问应用程序的入口点。在最常见的场景中,Service充当一组Pod的入口点,但情况可能并非总是如此。 Service是一个通用元素,它也可能指向Kubernetes集群外部的资源。因此,Service可用于服务发现和负载平衡,允许在不影响服务使用者的情况下改变Pod。

Label
通过上面的内容,我们可以知道微服务在创建时时一个容器,运行时是一个Pod,那么,是么是由多个微服务组成的应用系统呢 ? 在这里,kubernetes提供了另外两个元素,可以帮助我们定义应用程序的概念:标签和名称空间。
在微服务出现之前,应用程序是具有版本控制和发布周期的单体部署单元。应用程序的采用.war,.ear或其打包格式。 但随后,应用程序被拆分为微服务,这些微服务是独立开发,发布,运行,设置或扩展的。 使用微服务,应用程序的概念会减少,并且我们不再需要在应用程序级别执行关键构建工作。但是,如果您仍需要一种方法来制定某些独立服务属于某个应用程序,则可以使用标签。让我们假设我们将一个单体应用程序拆分为三个微服务,将另一个应用程序拆分为两个微服务。
现在,我们有五个Pod定义(可能还有更多的Pod实例),这些定义独立于开发和运行时的观点。但是,我们可能仍需要指出前三个Pod代表一个应用程序,而另外两个Pod代表另一个应用程序。甚至Pod也可以是独立的,提供一个业务功能,但他们可能相互依赖。 例如,一个Pod负责前端的容器,另外两个Pod负责提供后端接口。如果这些Pod中的任何停机,那么从业务角度来看,应用程序是不可用的。使用标签选择器给我们能够查询和识别一组Pod,并作为一个逻辑单元进行管理 ,下图显示了如何使用标签对分布式应用程序的各个部分进行分组 。

K8s -- 云原生之路

ReplicaSet使用Label来保持特定Pod的某些实例运行。 这意味着每个Pod定义都需要具有用于调度的唯一标签组合
调度程序也大量使用标签。 调度程序使用标签来共置或传播Pod,以将Pod放置在满足Pods要求的节点上
Label可以指示一组Pod的逻辑分组,并为它们提供应用程序标识。
除了前面的典型用例之外,标签还可用于存储元数据。很难准确的说标签可以用于什么,但最好有足够的标签描述Pod的所有重要特征。 例如,使用标签来指明应用程序的逻辑分组,业务特征和关键性,指定的运行时依赖等等

调度程序可以使用这些标签进行更细粒度的调度,或者可以通过命令行使用相同的标签来大规模地管理匹配的Pod。 但是,我们不应该过分夸大并提前添加太多标签。我们应该根据实际需要创建标签。删除标签风险很大,因为没有很明确的方法可以表明标签的用途,此类操作可能导致的不可预知的影响

对于金融系统,监管对机房内服务器划分,访问权限,安全级别有严格的指导。通过标签定义,我们可以分配哪些Pod在指定节点(或者说指定的物理机,机柜)上创建。

K8s -- 云原生之路

Annotations
Annotation与Label类似,也使用key/value键值对的形式进行定义。Label具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector。Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找。
用Annotation来记录的信息包括:

build信息、release信息、Docker镜像信息等,例如时间戳、release id号、PR号、镜像hash值、docker registry地址等;
日志库、监控库、分析库等资源库的地址信息;
程序调试工具信息,例如工具名称、版本号等;
团队的联系信息,例如电话号码、负责人名称、网址等
Namespaces
Namespaces是Kubernetes系统中的另一个非常重要的概念,通过将系统内部的对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目、小组或用户组,当不同的分组在整个集群中使用共享资源的同时,还能被分别管理。
Kubernetes集群在启动后,会创建一个名为“default”的Namespace,通过Kubectl可以查看到。

Namespaces用于k8s的资源管理
Namespaces为Container,Pod,Service或ReplicaSet等资源提供工作范围。资源名称在Namespaces中必须是唯一的,而且不能跨越它们。
默认情况下,Namespaces为资源提供界限,但不会隔离这些资源,阻止一个资源访问另一个资源。例如,只要Pod IP地址已知,开发Namespaces中的Pod就可以从生产Namespaces访问另一个Pod。但是,如果需要,还有Kubernetes插件可提供网络隔离,以实现跨Namespaces的真正多租户模式。
每个k8s服务必须有Namespaces,有相应的DNS地址,Namespaces的命名格式如. .svc.cluster.local。所有k8s服务的URI都含有NameSpaces的名称地址。这个类似java 里class的package name, 作用也非常类似。
用ResourceQuotas约束Namespaces的资源。集群管理员可以使用ResourceQuotas控制Namespaces中创建的各种对象个数。 例如,开发人员可以限定Namespaces只允许5个ConfigMaps,5个Secrets,5个Services,5个ReplicaSet,5个PersistentVolumeClaims和10个Pod
用ResourceQuotas指定NameSpaces 资源配比。 例如,在容量为32 GB RAM和16个内核的群集中,可以为生产环境NameSpaces分配一半资源(16 GB RAM和8个内核),然后分配8 GB RAM和4个内核,4 GB RAM和2个内核用于开发,测试的NameSpaces。
PersistentVolume & PersistentVolumeClaims
PersistentVolume(PV)在集群中是由管理员配置的网络存储。 PV是容量插件,如Volumes,其生命周期独立于使用PV的任何单个pod。PV在k8s集群中的作用类似我们经常在Linux服务器上挂载的NFS,可以很方便的做mount 映射
PersistentVolumeClaim(PVC)是由用户进行存储的请求。 它类似于pod。 Pod可以请求特定级别的资源(CPU和内存)。 指定可用资源的大小和访问模式(例如,可以一次读/写或多次只读)。
虽然PersistentVolumeClaims允许用户端使用抽象存储资源,对于不同的问题,用户端通常需要使用不同资源(例如性能)。集群管理员要能够提供各种PersistentVolumes不同的方式,不仅仅是大小和访问模式,但用户端不需要了解这些数据卷实现的细节。
PV是群集中的资源。PVC是对这些资源的请求,并且还充当对资源可用性检查。PV和PVC之间的相互作用遵循以下生命周期:Provisioning ——-> Binding ——–>Using——>Releasing——>Recycling

准备Provisioning---通过集群外的存储系统或者云平台来提供存储持久化支持。
静态提供Static:集群管理员创建多个PV。 它们携带可供集群用户使用的真实存储的详细信息。
动态提供Dynamic:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于StorageClasses:PVC必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。
绑定Binding---用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。
使用Using---用户可在pod中像volume一样使用pvc。
释放Releasing---用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。
回收Recycling---pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。
保留策略:允许人工处理保留的数据。
删除策略:将删除pv和外部关联的存储资源,需要插件支持。
回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。
下面这张图是k8s元素相互关系的直观体现

K8s -- 云原生之路

K8S是一个完备的分布式系统支撑平台,具有完备的集群管理能力,多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和发现机制、內建智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制以及多粒度的资源配额管理能力。同时Kubernetes提供完善的管理工具,涵盖了包括开发、部署测试、运维监控在内的各个环节, 减轻了以往运维手工操作的痛苦,为DevOps思想提供了强力的支撑。