Kubernetes 有状态集群服务部署与管理

lpzgf 7年前
   <p>在容器化时代,除了无状态的容器服务,比如Web服务器,用户也越来越多地使用容器部署有状态的应用,比如MySQL、Redis、Cassandra等。这些Pets(运行有状态服务的容器,需要特殊处理)就带来了新的需求,包括更长的生命周期,配置依赖,有状态的故障转移等。</p>    <p>本次分享将深入介绍Kubernetes如何满足有状态集群服务对容器编排系统提出的新需求,包括如何使用Kubernetes的动态存储请求与分配机制来实现服务状态的持久化存储,以及与高效部署和运行有状态集群服务相关的Kubernetes新特性,如Init Container、PetSet (StatefulSet)等。最后通过一个MySQL集群实例详解在Kubernetes中如何轻松部署一个高可用的有状态集群服务并实现自动化管理。</p>    <h3>大纲:</h3>    <p>- Kubernetes简介和运行有状态集群服务的挑战</p>    <p>- Kubernetes 存储系统</p>    <p>- Kubernetes 有状态集群服务相关特性</p>    <p>- 实战:在Kubernetes上部署和管理MySQL集群</p>    <h3>正文:</h3>    <p>在容器化时代,除了无状态的容器服务,比如Web服务器,用户也越来越多地使用容器部署有状态的应用,这就对容器编排系统提出了新的需求。</p>    <p>我今天要和大家分享的主题就是如何在目前主流的容器云平台Kubernetes 上部署和管理有状态集群服务。</p>    <p>这次分享的关键词有两个: <strong>一个是Kubernetes, 另一个是有状态集群服务。</strong></p>    <p>我们会在第一部分了解一下什么是 Kubernetes,以及运行有状态集群服务面临的一些挑战。</p>    <p>接下来的两部分我们会重点介绍 Kubernetes 是如何应对这些挑战,以及通过哪些特性来解决有状态集群服务所特有的一些问题。</p>    <p>最后一部分是实战,通过一个MySQL集群的例子来展示如何在Kubernetes上轻松地部署和管理一个有状态集群服务。</p>    <p>首先来看什么是Kubernetes?</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e926ad885b102d357d1bfcda09be5c17.png"></p>    <p>简单一句话来说,Kubernetes是一个运行和管理容器的平台。它在Docker、rkt等容器运行时之上,实现了容器的集群化和高可用。</p>    <p>Kubernetes简称K8S, 来自Google,支持多种云计算环境,并且100% 开源,是云原生计算基金会的一部分,用Go语言开发的。</p>    <p>这里是Kubernetes的一些基本概念。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/0f56c80ca937038d62b6a42d7efd568e.png"></p>    <p>其中最核心的一个概念是Pod,它是Kubernetes对容器进行的封装,是Kubernetes管理的最小单位。</p>    <p>Pod通过Deployment来部署,Deployment会创建一个Replica Set 来保证Pod的个数始终是一个指定的值 。</p>    <p>Pod一般不直接对外提供服务,而是通过Service对外提供一个稳定的访问接口,一个Service后面可以挂多个Pod实例 。</p>    <p>Service是如何找到它匹配的Pod呢?靠的是Label。Label是联系各个K8S资源的纽带。Replica Set 和它管理的Pod之间也是通过 Label 来关联的。</p>    <p>如果Pod里的容器运行的是有状态服务,如数据库与缓存等,还需要挂载存储卷,用于存储服务状态。</p>    <p>讲完原理,我们来看一个实例。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a83d3ea2d26a58a6d96fd8e7101b651f.png"></p>    <p>这是一个在K8S集群里运行的容器化应用案例,这个应用有自己的Web 客户端,同时还从推ter采集数据,处理完后存储到自己的DB。</p>    <p>可以看到容器里跑的服务有两类,无状态和有状态。像Web服务器,流处理器等无状态服务出现问题后,直接杀掉,新建一个,管理起来非常简单。</p>    <p>但是对有状态服务,像数据库,它要求有更长的生命周期。在一个集群的情况下,集群成员之间如何能保持稳定的成员关系?这都对容器编排系统提出了新的挑战。</p>    <p>那么K8S是如何应对这些挑战的呢?</p>    <p>K8S运行的服务,从简单到复杂可以分成三类:无状态服务、普通有状态服务和有状态集群服务。下面分别来看K8S是如何运行这三类服务的。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/afb56ad57f6b584fbf426ca0d75f471f.png"></p>    <p>首先无状态服务,K8S使用RC(或更新的Replica Set)来保证一个服务的实例数量。通过Service来对外提供一个稳定的访问接口。</p>    <p>然后是普通有状态服务,它多了状态保存的需求。Kubernetes提供了以Volume和Persistent Volume为基础的存储系统,可以实现服务的状态保存。</p>    <p>最后是有状态集群服务,它又多了集群管理的需求。K8S为此开发了一套以Pet Set为核心的全新特性,方便了有状态集群服务在K8S上的部署和管理。</p>    <p>下面我们首先来看Kubernetes如何满足“状态保存”的需求。</p>    <p>K8S的存储系统大致分为三个层次:普通Volume,Persistent Volume 和动态存储供应。</p>    <p>对普通Volume,最简单的一种是“单节点存储卷”。它和Docker的存储卷类似,使用的是Pod所在K8S节点的本地目录。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/340d7f9171e4fdd1b9bf3c64246d4d59.png"></p>    <p>具体有两种,一种是 emptyDir,是一个匿名的空目录,由Kubernetes在创建Pod时创建,删除Pod时删除。</p>    <p>另外一种是 hostPath,与emptyDir的区别是,它在Pod之外独立存在,由用户指定路径名。</p>    <p>这类和节点绑定的存储卷在Pod迁移到其它节点后数据就会丢失,所以只能用于存储临时数据或用于在同一个Pod里的容器之间共享数据。</p>    <p>普通Volume的第二种类型是“跨节点存储卷”。这种存储卷不和某个具体的K8S节点绑定,而是独立于K8S节点存在的 。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/65bca9aac3ed84efc02da8bb0bb3df61.png"></p>    <p>跨节点存储卷由于可以在任何一个Kubernetes 节点上都能够被访问到,比较灵活,所以应用比较广泛。</p>    <p>Kubernetes上的Volume是通过插件方式来实现的,所以可扩展性很强。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/f28491694b1e3d57139e83282af12920.png"></p>    <p>目前来说几乎所有主流的存储在Kubernetes上都有相应的插件来支持。如果已有的存储不能满足要求,还可以开发自己的volume插件 。</p>    <p>K8S存储系统的第二种存储方式叫persistent volume。它和普通volume的区别是什么呢?</p>    <p>普通Volume和使用它的Pod之间是一种静态绑定关系,我们无法单独创建一个普通volume,因为它不是一个独立的K8S资源对象。</p>    <p>而Persistent Volume 简称PV是一个K8S资源对象,所以我们可以单独创建。它不和Pod直接发生关系,而是通过Persistent Volume Claim,简称PVC来实现动态绑定。</p>    <p>接下来我们看一下这个动态绑定过程是怎样的?</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/30d4b5c29577e542c4798e530ff3430f.png"></p>    <p>这是PV的生命周期,首先是Provision,即创建PV,这里创建PV有两种方式,静态和动态。</p>    <p>所谓静态,是管理员手动创建一堆PV,组成一个PV池,供PVC来绑定。动态方式是通过一个叫 storage class的对象由存储系统根据PVC的要求自动创建。</p>    <p>一个PV创建完后状态会变成Available,等待被PVC绑定。一旦被PVC邦定,PV的状态会变成Bound,就可以被相应的Pod使用。Pod使用完后会释放PV,PV的状态变成Released。</p>    <p>变成Released的PV会根据定义的回收策略做相应的回收工作。有三种回收策略,Retain、Delete 和 Recycle。</p>    <p>Retain就是保留现场,K8S什么也不做。Delete 策略,K8S会自动删除该PV及里面的数据。Recycle方式,K8S会将PV里的数据删除,然后把PV的状态变成Available,又可以被新的PVC绑定使用。</p>    <p>刚才提到PV的供给有两种方式,静态和动态。其中动态方式是通过StorageClass来完成的,这是一种新的存储供应方式。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/9ee1a88e58209b9d301f1f6fade3bb4f.png"></p>    <p>使用StorageClass有什么好处呢?除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以封装不同类型的存储供PVC选用。</p>    <p>比如这里就有两个StorageClass,它们都是用谷歌的存储系统,但是一个使用的是普通磁盘,名字为slow。另一个使用的是SSD,名字为fast。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c98960781e64e311448031677a5863d3.png"></p>    <p>在PVC里通过annotation指定了storage class的名字为fast,这样这个PVC就会绑定一个SSD,而不会绑定一个普通的磁盘。</p>    <p>好,到这里Kubernetes的整个存储系统就都介绍完了。</p>    <p>下面进入Kubernetes与有状态集群服务相关的两个新特性。Init Container 和 Pet Set。</p>    <p>什么是Init Container?</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/fa88bc4df453fd4ddf322887b0cf8f6f.png"></p>    <p>从名字来看就是做初始化工作的容器。可以有一个或多个,这些 Init Container 按照定义的顺序依次执行,只有所有的Init Container 执行完后,主容器才启动。</p>    <p>由于一个Pod里的存储卷是共享的,所以 Init Container 里产生的数据可以被主容器使用到。</p>    <p>这是Init Container的一个使用样例。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a791d4569a682616632e8f42f5bff617.png"></p>    <p>这个例子创建一个Pod,这个Pod里跑的是一个nginx容器,Pod里有一个叫workdir的存储卷,访问nginx容器服务的时候,就会显示这个存储卷里的index.html 文件。</p>    <p>而这个index.html 文件就是通过一个 busybox的初始化容器获得的。</p>    <p>介绍完Init Container,千呼万唤始出来,该今天的主角Pet Set出场了。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c3e61c455a99741ad809c006f10bcc13.png"></p>    <p>什么是Pet Set?顾名思义是Pet的集合,那什么是Pet呢?它是一种需要特殊照顾的Pod。它有状态、有身份、当然也比普通的Pod要复杂一些。</p>    <p>具体来说,一个Pet有三个特征</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/63ab90ef074588f930d016486dcc4f89.png"></p>    <p>一是有稳定的存储,这是通过我们前面介绍的PV/PVC 来实现的。</p>    <p>二是稳定的网络身份,这是通过一种叫 Headless Service 的特殊Service来实现的。 和普通Service相比,Headless Service没有Cluster IP,用于为一个集群内部的每个成员提供一个唯一的DNS名字,用于集群内部成员之间 通信 。</p>    <p>Pet的第三个特征是序号命名规则。 比如 Pet Set 的名字叫 mysql,那么第一个启起来的Pet就叫mysql-0,第二个叫mysql-1,如此下去。</p>    <p>当一个Pet down 掉后,新创建的Pet 会被赋予跟原来Pet一样的名字,通过这个名字就能匹配到原来的存储,实现状态保存。</p>    <p>好,与有状态服集群服务相关的K8S特性就介绍到这里。</p>    <p>理论讲完了,下面进入实战,以Galera MySQL集群为例子,介绍如何在 Kubernetes如何上部署和管理一个有状态集群服务。</p>    <p>首先大致了解一下Galera MySQL。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/490ca23ca2737b79cf0833d0133c2526.png"></p>    <p>它不是那种主从式的集群,而是多Master集群,通过 Galera Replication 把多个MySQL实例关联起来组成一个集群。由Galera Replication 负责节点间的数据同步。</p>    <p>用户访问时可以连接到任何一个节点进行读写操作。每次写入的数据会被Galera Replication同步到整个集群,才算写入成功。</p>    <p>节点之间没有数据延迟,在某个节点失效后,直接退出集群即可,无需失效转移。</p>    <p>对Galera MySQL集群有了基本了解后,我们来看看如何在Kubernetes上部署和运行它。这是整体结构图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/9c70ed173d35fa1e553809cf5505a885.png"></p>    <p>左边的Headless Service用于为每个MySQL Pet实例提供一个DNS名字,右边的PV池为MySQL提供存储 。</p>    <p>这里有两个初始化容器,第一个用于安装需要的文件,第二个做MySQL的初始化工作 。</p>    <p>一个Pet Set里有多个Pet,每个Pet对应MySQL集群里的一个节点。通过Pet Set可以管理整个MySQL集群。</p>    <p>这是部署MySQL集群具体的YAML文件。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/8dea9fa019dfdfab720d28d2024c4117.png"></p>    <p>右边是一个Headless Service,名字是galera。</p>    <p>左边是Pet Set,它用到了右边的Headless service。Replicas的数目为3,会创建3个Pet。</p>    <p>在 Pet Set 的annotation里定义了两个初始化容器。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/5e58f3858b1ab13a0f9f2aae37cb345a.png"></p>    <p>Install容器安装的文件可以被bootstrap容器使用到;同时bootstrap容器生成的MySQL配置文件会放到config存储卷里,供后面的MySQL 容器使用。</p>    <p>这是主容器 Galera MySQL 的定义:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/1c94f14f458d6e2c76aea818fdb487ce.png"></p>    <p>除了常规的3306端口外,它还暴露了其它一些端口,用于集群内部的数据同步和状态转移等操作。</p>    <p>这里MySQL启动参数里用到的文件,是在初始化容器里生成的,通过共享存储卷传递过来。</p>    <p>最后是数据存储卷的定义。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c5585ca3f4c049a93af6fc25feaa1375.png"></p>    <p>这里定义了三个存储卷,其中config、workdir就是简单的本地目录,而 datadir是一个PVC,它可以去绑定PV来存储MySQL数据库的数据 。</p>    <p>所以部署一个集群总共就需要两个YAML文件就可以了,一个Headless Service,一个 Pet Set。其中Pet Set里定义了初始化容器和存储卷。</p>    <p>用上面的方式部署完MySQL集群后,后面的运维工作是比较简单的。</p>    <p>假如某个集群节点由于某种原因Crash掉了,Kubernetes 会自动创建一个新的Pet来替代,实现自动恢复。</p>    <p>如果要扩容或缩容,也是一条命令、指定一下这个Pet Set 的Replicas的数目就行了。</p>    <p>如果要升级,只需要修改Pet Set 定义里 podTemplate 的image值,然后把老的Pet删除,新创建的Pet,就是最新版本的了。</p>    <p>对于在Kubernetes上部署有状态集群服务,我们补充两点:</p>    <p>第一点是在最新发布的 Kubernetes 1.5 里 PetSet 重新命名为StatefulSet。所以根据你使用的K8S版本不同,可能看到的名字也不一样。</p>    <p>第二点是简单介绍一下时速云提供的有状态集群服务:数据库与缓存 。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/5e2af9708865c6718241694c3a0e5659.png"></p>    <p>如上图所示,这项服务最大程度的简化了有状态集群服务的创建工作,用户不再需要了解我们前面介绍的所有技术术语,只需要指定一下副本数目,存储的大小就可以了。</p>    <p>最后以下面这张图做为今天分享内容的总结。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/8ac6171eaa35a115e595a93949efb73e.png"></p>    <p>有状态集群服务的两个需求,一个是存储需求。另一个是集群需求 。</p>    <p>对存储需求,Kubernetes的解决方案是Volume、Persistent Volume 。对PV,除了手动创建PV池外,还可以通过Storage Class来让存储系统自动创建。</p>    <p>对集群需求,Kubernetes的解决方案是Pet Set。Pet Set 又通过Init Container来做集群初始化,通过Headless Service来为集群成员提供稳定 的网络身份。</p>    <p>最后我们以MySQL集群为例,说明了如何在Kubernetes上部署和运行一个有状态集群服务。</p>    <p>目前有状态集群服务在Kubernetes上的部署还不是正式版,但完全可用。如果您的项目中有容器化的需求,可以尝试。</p>    <p>今天的分享到此结束,谢谢大家!</p>    <p>Q: 前面提到init container,k8s里pod初始化是基于gcr的pause,这个初始化镜像是自定义的吗?</p>    <p>A:init container 和 gcr的pause 是不同的概念,一个是初始化容器(运行完就结束),一个是基础容器(一直运行)</p>    <p>Q: 北京Q1:你介绍的k8s存储技术都是比较新的,能否适应企业生产大规模使用,有没有什么性能和稳定性问题?</p>    <p>A: 性能和稳定性上我们也在不断尝试,先使用起来看看效果,目前创建过几百个集群,暂时没有碰到太多稳定性问题。</p>    <p>Q:能否提供一下k8s部署mysql的文档,以供交流[呲牙]</p>    <p>A:这个在后面的整理文档里会有部署样例提供。</p>    <p>Q:请问是时速云mysql集群的存储是用什么?cephfs 还是glusterfs,或者其他</p>    <p>A:目前出于效率考虑,主要是分布式块存储,没有用glusterfs这些</p>    <p>Q:存储系统如何动态创建StorageClass,如果 Headless Service没有Cluster IP,服务如何调用?</p>    <p>A:K8S 通过StorageClass 让存储系统动态创建PV,不是动态创建StorageClass。Headless Service 用于集群内部通信,外部调用,再建普通Service,二者并存。</p>    <p>Q: 我想问个k8s比较基础的问题哈,是关于service type的,有三种,第一种clusterIP我不是很明白,不是k8s默认所有pod之间是互通的么,那还需要这个clusterIP干啥呢。比如我两个pod,一个跑redis,一个跑web, 那web那个pod肯定是可以访问到redis的呀,为啥还需要为redis create 一个service呢</p>    <p>A:Pod重建或迁移后IP可能变,用Service可以提供一个稳定的访问接口。</p>    <p>Q:有状态集群还有其他的实现方式吗?</p>    <p>A: 在容器云里比较好的方式是用PetSet,当然也能自己做,相当于自己实现PetSet的一些功能。</p>    <p>Q:同步到整个集群才算写入成功,是不是意味着不适合高负载的项目使用?有可能增加其它策略供选择吗?</p>    <p>A:由于采用多主方式,对外只写入一个,内部扩散同步可以并行,而且每个节点都能对外提供服务,相当于增加了服务带宽,所以性能不是问题。</p>    <p>杭州Q:您好,你们是采用什么分布式存储的,io性能如何?好像一些开源分布式的存储写io的性能普遍比较低,能撑得住一些io高性能的应用吗?</p>    <p>A: 性能上要等到支持host 模式后,才能满足一些IO要求比较高的场景</p>    <p>Q:时速云的mysql集群是RDS吗</p>    <p>A:目标就是基于容器的 RDS 服务</p>    <p> </p>    <p>来自:http://dockone.io/article/2016</p>    <p> </p>