SegmentFault 基于 Kubernetes 的容器化与持续交付实践

云服务
这篇文章很早就写了,起初只是我一次对外分享的总结,现在把它公开出来。

SegmentFault 是一家综合性技术社区,由于它的内容跟编程技术紧密相关,因此访问量的波动也和这一群体的作息时间深度绑定。我们 web 页面的请求量峰值在 800 QPS 左右,因为现在还做了前后端分离,所以 API 网关的峰值 QPS 会数倍于它。

架构历史

SegmentFault 作为一个技术社区的系统架构变化,里面有些东西还是很有意思的。

遇到的挑战

当然促使我们往K8S架构上迁移是因为我们遇到了不少挑战。

首先虽然我们是一家小公司,但是业务线却非常复杂,我们整个公司只有30人左右,技术可能只占其中三分之一,所以承载这么大的业务量还是负担很重的。而且对于创业公司来讲业务线的调整非常频繁,临时性工作也比较多,传统的系统架构在应对这种伸缩性要求比较高的场景比较吃力。

其次这些复杂的场景就带来了配置管理任务上的艰巨,不同的业务要用到不同的服务,不同的版本,即使用自动化脚本效率也不高。

还有一点比较重要,因为我们人员比较少,所以没有设专职运维这个职位,现在OPS的这个工作是由后端开发人员轮值的。而后端开发者还有自己本职工作要做,所以对我们最理想的场景是能把运维工作全部自动化。

而最重要的一点就是我们要控制成本,这是高情商的说法,低情商就是一个字“穷”(笑)。其实你会发现有钱的话,以上的问题都不是问题,但是对于创业公司(特别是像我们这种访问量比较大,但是又不像电商,金融那些挣钱的公司)来说,我们必将处于和长期处于这个阶段。因此能否控制好成本,其实是一个非常重要的问题。

前后端分离

未命名文件 6.png

在2020年以前,我们的网站还是非常传统的后端渲染页面的方法,所以服务端的架构也非常简单。浏览器通过http请求到后端的php服务器,我们在php里面渲染好页面后返回给它。这种架构用原有的部署方法还能支撑,也就是在各个实例上部署php服务器,了不起再做一个负载均衡就ok了。

未命名文件 5.png

但是随着我们业务的发展,这种后端渲染的方式已经不适合我们的项目规模了,因此我们在2020年做了架构调整,准备上前后端分离。前后端分离的技术特点我在这里就不赘述了,但它给我们带来了系统架构上的考验。一个是入口增多,因为前后端分离不仅涉及到客户端渲染(CSR),还涉及到服务端渲染(SSR),所以响应请求的服务就从单一的变成了两类,一类是以node.js的react server服务(用来做服务端渲染),另一类是php写API服务(用来给客户端渲染提供数据)。而服务端渲染本身还要调用API,而我们为了优化服务端渲染的连接和请求响应速度,还专门启用了使用另一类协议专有通讯协议的内部API服务。

因此在我们的WEB SERVER这一块实际有三类服务器,它们的服务器环境不同,所需的资源不同,协议不同,各自之间可能还有相互连接的关系,另外这些服务要保证高可用的话,也是要做负载均衡的。而在快速迭代的开发节奏下,使用传统的系统架构很难再去适应这样的结构。

我们迫切需要一种能够快速应用的,方便部署各种异构服务的成熟解决方案。

Kubernetes 带来了什么?

开箱即用

截屏2021-05-27 下午4.44.47.png

首先是开箱即用,理论上来说这应该是 kubesphere 的优点,我们直接点一点鼠标就可以打造一个高可用的K8S集群。这一点对我们这种没有专职运维的中小团队来说很重要,根据我的亲身经历,要从零开始搭建一个高可用的k8s集群还是有点门槛的,没有接触过这个的运维人员,一时半会还搞不定,其中的坑也挺多的。

所以如果云厂商能提供这种服务是最好的,我们不用在服务搭建与系统优化上花费太多时间,可以把更多的精力放到业务上去。所以之前我们还自己搭建数据库,缓存,搜索集群,后来全部都使用云服务了。这也让我们的观念有了些转变,云时代这些基础服务你应该把它视为基础设施的一部分加以利用。

用代码管理部署

因为我们没有专职的运维,但是我们有程序员。如果我们能把运维工作全部用代码来管理,那就再理想不过了。而目前k8s确实给我们提供了这样一个能力,现在我们每个项目都有一个docker目录,里面放置了不同环境下的Dockerfile,k8s配置文件等等。不同的项目,不同的环境,不同的部署,一切都可以在代码中描述出来加以管理。

比如我们之前提到的同样的API服务,使用两种协议,变成了两个服务。在这现在的架构下,就可以实现后端代码一次书写,分开部署。其实这些文件就代替了我们之前的很多部署操作,我们需要做的只是定义好以后执行命令把它们推送到集群里去。

而一旦将这些运维工作代码化以后,我们就可以利用现有的代码管理工具,像写代码一样来调整线上服务。更关键的一点是,代码化之后就无形中又增加了版本管理功能,这无疑离我们理想中的全自动化运维又更近了一步。

持续集成,快速迭代

截屏2021-05-23 下午2.25.16.png

持续集成标准化了我们的代码发布流程,如果能将持续集成和k8s的部署能力结合起来,无疑能大大加快我们的项目迭代速度。而在使用k8s之前我们就一直用GitLab作为版本管理工具, 它的持续集成功能对我们来说也比较好用。在做了一些脚本改造之后,我们发现它也能很好地服务于现有的k8s架构,所以也没有使用k8s上诸如jenkins这样的服务来做持续集成。

步骤其实也很简单,做好安全配置就没什么问题。我们本地跑完单元测试之后,会自动上线到本地的测试环境供测试。在代码合并到上线分支后,由管理员点击确认进行上线步骤。我们会在本地build一个镜像推送到镜像服务器中,然后通知k8s集群去拉取这个镜像执行上线,最后执行一个脚本来检查上线结果。整个流程都是可视化可追踪的,而且在代码管理界面就可以完成,方便开发者查看上线进度。

一些经验

有一些实际运用中总结的经验。

管理好基础镜像

目前我们用一个专门的仓库来管理这些基础镜像,统一管理的基础镜像可以让我们的开发人员拥有与线上一致的开发环境,而且后续的版本升级也可以在基础镜像中统一完成。

除了将Dockerfile文件统一管理以外,我们还将镜像build服务与持续集成结合起来。可以看到每个Dockerfile文件都有一个所属的VERSION文件,每次修改里面的版本号并提交,系统都会自动build一个相应的镜像并推送到仓库里去,这样我们就把基础镜像的管理工作完全自动化了,大大减少了人为操作带来的错误与混乱。

KubeSphere使用

  1. 别把日志服务放到集群里,当然这在kubesphere文档里也说了,但是我当时还是把它给放到集群里了。为什么?因为第一,安装的时候我把组件全部默认安装了,第二,当时我是个小白,无法判断这些组件是不是需要安装。所以也希望kubesphere在文档里优化一下,把这些组件的必要程度给用户一个解释,要不然像我这样装了一堆没用的服务,又要一个个去删除。具体到日志服务,其实主要就是一个Elastic搜索服务,不放到集群里放到外面也很简单,自己建一个Elastic集群就可以了。因为日志服务本身负载比较大,而且对硬盘的持续性需求较大,有时候你会发现日志服务本身就占据了集群里相当大的资源,这就得不偿失了。
  2. 如果你在生产环境保证高可用,还是要部署3个或以上的,从我们使用的情况来看,主节点还是会偶尔出问题的。特别是如果遇到节点机器要维护或者升级的时候,多个主节点可以保证业务的正常运行。
  3. 如果你本身不是专门提供数据库或缓存的服务商,这类高可用服务就不要上K8S,因为要保证这类服务高可用本身就要耗费你大量的精力。我建议还是尽量用云厂商的服务。
  4. 副本的规模和集群的规模要匹配,如果你的容器就几个节点,但一个服务里面就扩展了上百个副本出来,系统的调度会过于频繁从而把资源耗尽。所以这两者要相匹配,在系统设计的时候就要考虑到。

最后是一点感想,就是你做完容器化再去看自己的应用运行在集群里的时候,会发现原来它们原来不需要占用那么多台服务器。这是因为你把资源的粒度降低了,所以可以做更多的精细化规划,因此使用效率也提高了。

暂无评论