# 软件运维

  • 当一个软件部署、交付之后,会正式交给用户使用。此时进入软件生命周期的最后一个阶段,名为"运行与维护"(operation and maintenance),简称为"运维"(ops)。
    • 为什么命名为运维?因为软件运行时,可能发生故障,需要实施一些维护措施。

# 运维技术

软件行业经过多年发展,运维技术可分为几大类:

  • 手工运维
    • :传统的运维工作,通常是人工手动执行。
    • 缺点:
      • 工作效率低。
      • 人工操作容易出错。
  • 自动化运维
    • :编写程序(通常是 shell 脚本)来自动执行一些重复性的运维工作。
    • 缺点:
      • 编写程序需要一定人力成本。
  • AIOps
    • :基于人工智能来自动化运维。
    • 优点:
      • 自动化程度更高。shell 脚本只能执行固定的运维规则,而 AIOps 能通过机器学习,从海量运维数据中,总结出新的运维规则。
    • 缺点:
      • 人工智能可能出错。

# 部署环境

假设某公司研发了一个 Web 网站,通常存在多个部署环境:

  • 开发环境(dev)

    • :允许开发工程师自由部署某些服务模块,自由调试,不必让测试工程师检查。
    • 为了安全,通常不暴露到公网。
  • 测试环境(test)

    • :模拟生产环境,部署了全部模块,用于测试。
    • 为了安全,通常不暴露到公网。
  • 生产环境(prod)

    • :供正式用户使用,通常暴露到公网。
    • 为了稳定,新版本的服务模块,应该先在测试环境检查之后,才部署到生产环境。
  • 预发布环境(pre)

    • :一个严格的测试环境。各个服务模块的软件版本、配置文件、数据尽量与生产环境一致。
    • 对于复杂的 Web 网站,普通的测试环境可能难以模拟生产环境,比如配置文件随意、编造假数据。这样可能无法发现一些 bug ,因此建议增加一个预发布环境。

# 发版方式

假设某公司研发了一个 Web 网站,需要从版本 v1 更新部署到 v2 ,常见的几种发版方式如下:

  • 直接发布

    • 流程:
      1. 先停止旧版本的服务。
      2. 再部署新版本的服务。
    • 优点:
      • 发布过程简单。
    • 缺点:
      • 中途没有正常运行的服务,导致网站的服务中断一段时间,严重影响用户的使用。
      • 如果新服务有问题,需要重启旧服务,又导致服务中断一段时间。
  • 蓝绿发布(blue-green release)

    • 前提条件:
      • 用户的访问流量不直接发送到服务,而是先发送到网关(比如 Nginx ),然后网关将流量反向代理到服务。
    • 流程:
      1. 先部署一组新版本的服务(称为蓝组)。
      2. 修改网关的配置,原本将用户的访问流量转发到旧服务,现在改为转发到新服务。该过程称为流量迁移。
      3. 等新服务正常运行之后,再停止旧服务(称为绿组)。
    • 优点:
      • 发布过程简单。
      • 如果新服务有问题,可以立即将访问流量转发到旧服务,实现一键回滚。
    • 缺点:
      • 中途同时部署两组服务,因此需要两倍的服务器资源,成本较大。
    • 迁移 HTTP 类型的流量时,用户新发送的 HTTP 请求会转发给新服务。但用户可能之前发送了 HTTP 请求,已经转发给旧服务处理,尚未返回 HTTP 响应。为了避免中断旧 HTTP 请求,需要增加以下措施:
      • 保持旧 HTTP 请求的网络链路(比如保持旧的 TCP 连接),使得旧服务返回 HTTP 响应时,网关能正常转发给用户。
      • 等旧 HTTP 请求全部结束,才停止旧服务。
  • 滚动发布

    • :分阶段更新服务,而不是一次更新全部。
    • 比如一个服务部署了多个实例,先更新 20% 的实例,等更新部署成功了,才更新下一批 20% 的实例。
    • 优点:
      • 类似蓝绿发布,但一次只更新部分服务,因此不需要两倍的服务器资源,成本较低。
    • 缺点:
      • 如果发现异常,可以恢复到上一阶段,称为回滚(rollback)。但不能直接恢复到最初状态。
  • 金丝雀发布(canary release)

    • :在生产环境更新服务,先给少部分用户试用。试用正常之后,再给全部用户使用。
    • 在中国又称为灰度发布。
    • 优点:
      • 测试环境不能完全模拟生产环境,因此新版本的服务可能在测试环境正常工作,在生产环境却发现问题。而灰度发布可减轻这种问题,实现更可靠的测试。
    • 缺点:
      • 可能导致少部分用户不满。

# SRE

  • 传统的软件公司,会将大部分人力投入软件的开发过程。等软件部署、交付之后,只投入少量人力来维护,从而降低公司的成本。

    • 公司内一般会划分两个部门:开发部门(dev)、运维部门(ops)
      • 开发部门的工程师,负责开发软件。如果开发速度较慢,赶不上进度,他们的工资绩效就会受损。
      • 运维部门的工程师,负责保证软件正常运行。如果软件发生故障,他们的工资绩效就会受损。
    • 缺点:
      • 两种工程师的工资绩效是矛盾的,导致他们的协作效率低,甚至吵架。
        • 开发工程师希望每次修改了软件,就能立即部署,从而实现工资绩效。
        • 运维工程师希望如果软件能正常运行,就尽量不要修改,以免发生故障。
      • 运维工程师的技术能力通常较低,甚至只懂服务器的开机、关机,因此不能解决故障、优化性能。
        • 开发工程师虽然有能力做这些事,但很少做,因为不会增加工资绩效。
  • 2000 年以后,计算机行业飞速发展。一个软件可能拥有成千上万的用户,任何一个小故障都可能引发用户的不满,导致软件公司损失部分收益。

    • 随着损失越来越大,一些软件公司开始转变思路,愿意将更多人力投入运维工作,从而避免软件故障。
    • 但具体应该怎么做呢?Google 公司的做法是,安排一些开发工程师,负责运维工作。
  • Google 公司是全球最大的互联网公司之一,运行了几百万服务器,因此对大规模服务器的架构设计、运维有丰富经验。

    • 由于服务器数量巨大,任何小的问题都可能被放大很多倍。例如:
      • 修改一台服务器挺简单,但如何修改大量服务器?必须使用自动化措施。
      • 一台服务器故障时,容易排查原因。但如果大量服务器故障,产生海量警报,如何排查原因?必须精简警报的数量,只将必要的警报发给工程师。
    • 2003 年,Google 公司安排一些开发工程师,组成一个新的运维部门,后来称为 SRE(Site Reliability Engineering,站点可靠性工程师)。
      • 工作职责:对软件系统进行运维,保障它 24 小时可用。
      • 招聘要求:拥有熟练的开发能力,同时掌握 Linux、网络等运维工作需要的知识。
  • SRE 的指导思想:

    • 尽量将运维工作自动化,从而节省人力、时间。

      • 首先,将经常发生的工作,自动化。
      • 其次,将偶尔发生的工作,自动化。
      • 理想的情况下,普通的运维工作都实现自动化,软件系统可以无人值守。
    • 最多将 50% 的时间,投入运维工作。从而有足够的时间,构思如何优化运维工作,并进行尝试。

      • 如果运维工作过多,可以分担一些给开发工程师。
    • 搭建可靠的监控系统。

      • 如果软件系统发生了故障,却没有被监控系统发现,则说明需要扩大监控范围。
      • 精简警报的数量,只将必要的警报发给工程师。
        • 发生故障时,如果工程师需要分析大量警报来排查原因,则需要较长时间才能解决故障。
        • 最好能自动推理,找出故障的原因。
    • 向用户告知,软件系统的 SLO(Service Level Agreement,服务质量目标),比如平均延迟的最小值、最大值。

      • SLA 是向用户承诺的服务可用性,如果未达到则给用户一定赔偿。而 SLO 是声明软件系统的性能指标,以免用户的预期过高,容易抱怨。
      • 对软件公司而言,提升软件系统的性能指标,需要增加人力成本、服务器成本,但不一定能明显提升用户的满意度。
    • 根据经验,生产环境的大部分故障,都是因为工程师实施了变更(比如发布新版本、修改配置文件)。因此,变更生产环境之前,应该做好以下准备:

      • 这项变更已经在测试环境实施了吗?是否引发故障?或者发现轻微异常?
      • 准备预案:如果这项变更引发了故障,怎么处理?
        • 立即撤销这项变更?但某些变更不可撤销,导致生产环境不能回滚。
        • 安排工程师修复?但可能花很久时间。
      • 采取渐进式发布:
        • 如果需要对生产环境做出多项变更,则不要同时实施,而是逐一实施。这样发生故障时,能立即确定是哪项变更造成的。
        • 如果软件部署了多个实例,则采用滚动发布、金丝雀发布等方案,先修改部分实例,给部分用户试用。
    • 生产环境发生重大事故时,可能同事们不知所措,或者很多人都想出谋划策。建议按以下流程来处理事故:

      1. 某人发现事故,进行上报。如果认定为重大事故,则在公司内选择一人,担任本次事故的总负责人。
      2. 总负责人,安排几位工程师,处理本次事故。
        • 其他同事未经授权,不要插手,以免添乱。
        • 这些工程师,专注于处理事故,没时间与其他同事沟通。因此,总负责人需要从这些工程师收集信息,传递给其他同事。
      3. 及时沟通:
        • 在公司内部,编写一个在线文档,记录本次事故的处理进度、操作细节,并实时更新,供其他同事了解。(建议不要使用群聊,很多人发言,太混乱)
        • 记得通知公司高管,以免他们急躁。
        • 在公司外部,撰写公关发言,通知所有用户,本次事故的影响范围、处理进度、预计恢复时间,以免他们急躁。
      4. 解决事故之后,需要编写总结报告。
        • 从失败中学习,如何避免这种事故再次发生。

# DevOps

  • 2009 年,一些程序员开始宣扬 DevOps 的思想,提倡以下职业行为:

    • 重视开发部门(dev)与运维部门(ops)之间的沟通合作,从而提高整体的工作效率。
    • 将软件开发流程中的大部分工作,通过程序自动完成,从而节省人力、时间。比如自动化测试、自动化部署。
    • 如何自动化?
      • 传统模式下,对于重复性工作,工程师通常编写 shell 脚本来完成。
      • DevOps 时代,人们开发了很多专业级别的自动化程序,例如 Jenkins、Docker、k8s 。
  • DevOps 包含以下概念:

    • 持续集成(Continuous Integration,CI)
      • :反复地完成,从开发到集成的流程。
      • 传统模式下,开发工程师可能多次提交代码到 Git 仓库,等写好全部代码,做好准备之后,才手动构建、测试。
      • 持续集成模式下,开发工程师每次提交代码到 Git 仓库,就会自动进行一次构建、测试,以便更早看到效果、发现问题。
      • 一种流行的 CI 方案:将代码推送到 GitLab ,会触发 gitlab-ci.yml 脚本,自动进行构建、测试。
    • 持续交付(Continuous Delivery,CD)
      • :反复地完成,从开发到交付的流程。
      • 这里的交付,不是将软件交给客户,而是交到工件仓库。
      • 一种流行的方案:从 GitLab 拉取源代码,构建成 Docker 镜像,上传到 Harbor 仓库。
    • 持续部署(Continuous Deployment,CD)
      • :反复地完成,从开发到部署的流程。
      • 一种流行的方案:在持续交付之后,从 Harbor 仓库拉取 Docker 镜像,部署到 k8s 中。
    • GitOps
      • :一种配置文件的管理方案,于 2017 年提出。它是将所有软件的配置文件,存储在 Git 仓库中。
      • 优点:
        • 每次修改配置文件,就在 Git 仓库提交成一个新版本,方便看出修改了哪些内容。
        • Git 仓库记录了配置文件的历史版本,可以将配置文件回滚到任意历史时刻的状态。
        • 当用户修改 Git 仓库中的配置文件时,可以触发 CI/CD 脚本,自动进行部署,不必用户手动将配置文件上传到服务器。
        • 多个用户编辑 Git 仓库时,可以通过 Pull Request 的方式,请求修改 Git 主分支,实现审批。
        • 如果 Git 仓库中,记录了每个软件是如何部署的、使用什么配置参数。则根据该 Git 仓库,可以重新部署所有软件,并且可由程序自动完成该部署操作。