云计算课程设计

云计算课程设计 #

背景 #

当今时代是云原生的时代。

容器化带来的优势 #

在之前的《容器与Docker》的实验中,我们从“隔离不同的应用程序”入手,介绍了引入容器的必然性。容器之间这种相互隔离的特性为使它非常便于进行弹性伸缩和迁移部署。

各位试想一下自己之前部署一个应用的过程,通常分为这么几个步骤:

  1. 编译源代码,得到编译产物(你可能需要根据不同的部署目标编译多个不同平台的编译产物,比如在macOS和Windows上开发,却需要编译Linux目标平台的产物)
  2. 在目标机器上安装运行时环境(对于部分类型的应用程序来说,这一步可以省略,如大部分Go、Rust等编写的应用)
  3. 将编译产物传送到目标机器
  4. 设置程序运行时需要的环境变量、配置文件等(大多数情况下至少需要维护开发时和运行时两种不同的配置文件和环境变量)
  5. 启动应用程序
  6. 如果是需要长期稳定运行的程序,还需要配置程序作为守护进程启动(可能需要编写systemd配置文件等)

而对于以容器形式部署的应用来说,只需要:

  1. 编写Dockerfile(这里会包含进程在运行时需要使用的环境变量和配置文件等)
  2. 根据Dockerfile构建镜像
  3. 推送镜像到目标机器,然后使用docker run启动容器

整个过程流畅而优雅,并且因为需要操作的步骤很少,所以会有更低的出错的可能(在传统的部署过程中,敲错一行命令导致部署失败是常有的事)。而且,更进一步地:

  1. 有些时候,我们将机器A上的应用迁移到机器B上时,如果使用传统部署方式,则很可能需要将1至6这几个步骤重复走一遍;而如果使用容器部署方式,只需要在机器B上拉取机器A中使用的镜像,重新启动容器即可。
  2. 有些时候,我们可能需要在同一台机器上部署和管理一个应用的多个不同的实例(如对于单进程应用,出于负载均衡,充分榨干机器性能的需要),如果使用传统部署方式,很可能需要为每个需要部署的实例修改不同的配置,并手动开启或关闭这多个实例;而如果使用容器部署的方式,则只需修改docker run的参数,对于启动的多个实例,可以轻松地使用docker本身提供的工具进行管理。
  3. 大多数情况下,一个应用程序往往会依赖若干个不同的外部服务。比如一个Java编写的后端服务,可能需要依赖数据库、对象存储等服务。这些外部服务的部署和配置在部署应用时也是一个很让人头疼的事情。而对于容器的部署方式,这些外部服务,诸如数据库、对象存储等,完全也可以实现容器化,它们的部署也可以使用基于容器的方式进行(相信各位在上次实验中已经体会到了使用容器的方式部署MySQL服务的便利性)。

微服务与容器 #

相信很多同学都接触或者实践过微服务架构(比如Spring Cloud之类的)。微服务,简单来讲就是说将原来的整个软件系统,拆分成不同的模块,每个模块对外都表现为一个独立的系统(它们可以独立进行开发、测试和部署),每个模块对外暴露规划好的接口,不同模块之间通过某种协议(一般是原生的HTTP或各种各样的RPC协议)相互调用这些接口,从而组织成整个完整的软件系统。

微服务带来了很多好处:

  1. 整个业务系统“高内聚,低耦合”。不同模块只需要维护好对外的接口即可,对内完全是自治的。这给整个软件系统的迭代更新带来了极大的便利。比如,可以根据需要很方便地增减不同模块等等。
  2. 不同模块相互独立,其之间的沟通交流一般使用的是基于HTTP的与编程语言和编程框架无关的协议。这就使得同一个软件系统内部的不同微服务模块可以交给不同团队开发,不同团队可以根据自身的技术积累,或者当前模块的特点,使用不同的编程语言和框架来实现。
  3. 不同微服务模块的部署是相互独立的。这就意味着,可以根据需要调整不同模块部署的实例个数,而无需调整整个软件系统的部署情况,从而可以充分利用硬件资源。
  4. 微服务架构的上述特点很适合互联网业务敏捷开发、快速迭代的工作方式。

不难发现,微服务的特点和容器的优势有很大的重合点,容器非常适合用来作为微服务的实现方式。即,对每个模块构建一个或多个镜像,然后以部署容器的方式部署每个微服务模块。事实上,我们当前使用的各个大厂的主要业务都是跑在容器里面的。“微服务化”和“容器化”也基本成为了同义词。

容器管理与云 #

当前大部分互联网公司的业务的部署都是以容器的方式进行的。整个软件系统被拆成一个个微服务模块,每个微服务的实例都是一个容器。这些成千上万的容器翱翔在以数以万计的服务器组成的大规模集群的资源池中。容器定义了新时代的云。而所谓的“上云”,也基本意味着“将应用以容器方式进行部署和管理”。

随着容器数量的扩大,容器管理问题也逐渐凸显出来。试想,在容器数量很少时,你可以在自己的机器上手动使用docker命令开启或关闭容器。但当容器数量数以万计时,手动操作的成本就会迅速增加,因为:

  1. 即使每个容器因为各种各样的原因崩溃的概率很低,但乘以一个很大的容器总数,就会使“每时每刻都会有若干容器挂掉”变成大概率事件。为了保证业务的正常运行,必须能够即使发现这些崩溃的容器,并将它们重新启动。
  2. 在业务迭代时,经常需要更新容器的镜像。这意味着,我们必须能够在数以万计的容器中,找到先前版本的所有容器实例,并将它们关闭移除,然后启动新版本镜像的容器。
  3. 集群中的各个机器的状态可能不同(集群可能是异构的),为了充分发挥硬件资源的能力,必须能够即使根据不同机器的状态(空闲与否)和容器占用资源的情况,将容器调度到不同机器上运行。

不仅如此,随着单纯的容器化本身只是一种“理想的愿景”,有很多实际问题需要考虑:

  1. 前面我们提到,微服务的各个模块之间需要相互调用,这也意味着,不同容器之间需要相互交换信息,那么如何定位不同的容器、容器之间以怎样的方式发送和接收消息就成为一个非常重要的问题。
  2. 容器本身是“一次性”的,它的存储相关工作一般都交给数据卷来保存。不仅如此,数据对任何业务来讲都是最宝贵的资源,为了保证高可用,数据卷的管理必须考虑备份、容灾等。当容器数量增大时,数据卷的管理将变得非常复杂。

围绕这上述这些实际生产中的问题,很多科技公司都积极尝试并给出了自己的解决方案,但最终还是Google开源的Kubernetes笑到了最后,并已经在当下成为了分布式容器管理和容器编排的事实标准。而当下繁荣的云原生生态,也正是以容器和Kubernetes为基石,围绕它们构建起来的。

如果同学们对云计算感兴趣,可以去关注一下 CNCF(云原生计算基金会),它是Linux基金会的一部分,收录了大量云计算方面的开源项目,很多国内外大厂都是它的会员,同时也会给它捐献自己的云计算方面的开源项目,在 这里你可以看到CNCF的全景图。CNCF每年都会举办很多面向学生的开源项目活动,是一个接触并深入云计算领域的非常不错的窗口,感兴趣的同学可以多留意相关信息(比如各个大厂的云原生公众号,CNCF在国内的公众号等)。

课程设计内容 #

相信大家都上过软件学院的软件工程这门课。课程的大作业一般都需要完成做一次完整的软件开发,最终一般都需要提交源代码和相关文档等。但这门课的作业检查是非常痛苦的。不同小组使用的编程语言、编程框架及其相应的版本都不一而同。助教或教师如果想实际检查源代码的正确性(即能正确编译并在部署后能呈现出你文档中描述的那种效果)是非常困难的。

因此,请以软件学院为样本,结合容器技术,设计这样一个Web应用,教师或助教部署作业任务后,学生提交源代码和必要的编译部署选项(比如编译脚本、部署参数等);教师或助教在学生提交完成后,只需要在系统中点击“编译”和“部署”按钮,就可以完成学生作品的部署流程,并观察到学生作品的运行效果。

以上只是对系统的笼统描述,有很多细节需要考虑:

  1. 软院云平台现有课程管理、实验管理、提交作业功能,如何将这些现有功能与新系统联动起来?
  2. 学生提交作业时使用什么方式?直接提交源代码压缩包,还是给出一个代码托管地址?有自己的代码托管平台BuGit,如果将新系统与其联动起来?
  3. 学生在完成作业后,提交代码前,很可能也需要自己在系统上尝试编译部署进行自测,以保证自己代码的运行效果与本地测试的效果相同。
  4. (可选)大多数情况下,学生在完成作业时,都使用版本控制系统(如Git等)来管理代码。学生很可能需要在每次提交代码时能验证本次所提交的代码没有破坏之前的功能。即,系统应该可以在每次学生提交新代码(如果使用Git进行管理的话,就是在每次提交新commit时)时自动拉取学生代码,或者运行代码库的单元测试,或者进行编译和部署,并将结果展示出来。熟悉 GitHub ActionsCI/CD的同学应该很容易理解这里在说什么。

本次课程设计并不需要大家真正实现这一系统,而是要编写文档,描述:

  1. 对上述场景进行需求分析,可以使用用例图等形式描述系统中包含哪些角色,每个角色应该有怎样的功能。请大家重视这一部分的工作,这是你接下来设计系统的重要前提。
  2. 描述系统的设计结构,可以使用原型图、架构图、流程图等形式,说明系统从教师或助教部署作业,到教师或助教看到学生的作业效果这一端到端的流程是怎样工作的。并请结合一个典型的作业类型(例如,一个包含了前端、后端和数据库的课程作业),来举例描述系统的工作流程。
  3. 描述技术选型,描述计划使用哪些关键技术,并且这些技术是怎样为最终系统功能服务的。