55、构建可运维的软件系统:关键策略与实践

构建可运维的软件系统:关键策略与实践

在软件开发过程中,构建一个可运维的系统至关重要。它不仅能确保软件在生产环境中的稳定运行,还能提高团队的工作效率,减少因故障带来的损失。以下将详细介绍构建可运维软件系统的关键策略和实践。

1. 配置管理
  • 代码与配置分离 :团队成员不应随意访问机密信息。在部署时,要将源代码和环境配置清晰分离,例如将源代码放在源目录,环境配置放在环境目录。部署时,通过设置环境变量、复制文件等方式将配置注入到部署环境,具体操作取决于部署机制。
  • 避免过度配置 :避免让软件具有无限的可配置性,复杂的配置最终会变成一种代码,而且是用糟糕的编程语言编写的,缺乏抽象和测试。如果需要复杂的可配置性,可以使用功能开关有选择地开启或关闭某些行为;若需要复杂的客户控制行为,可考虑使用插件架构,这两种方法都能让你用真正的编程语言编写细节代码。
  • 机密信息管理 :机密信息(如密码、API 密钥等)是一种特殊的配置,绝不能包含在源代码中,大多数团队成员不应有权限访问。要定义一个安全的流程来生成、存储、轮换和审计机密信息,对于复杂系统,通常需要使用机密管理服务或工具。
    • 单独仓库管理 :如果将环境配置放在单独的仓库中,可以通过严格限制对该仓库的访问来控制对机密信息的访问。
    • 代码仓库加密 :如果将环境配置放在代码仓库中,需要对包含机密信息的文件进行“静态加密”,并在部署脚本中编写解密逻辑,在注入到部署环境之前解密。
    • 避免日志记录 :绝不要将机密信息写入日志。为防止意外记录,可编写日志包装器来查找类似机密的数据(如名为“password”或“secret”的字段)并进行编辑,发现时触发警报让团队修复。
2. 偏执遥测

无论代码编写得多么仔细,在生产环境中仍可能出现故障。外部服务可能返回意外数据或响应缓慢,文件系统可能空间不足,故障转移也可能失败。因此,每次代码与外部世界交互时,都要假设外部世界会对其造成威胁。
- 错误检查 :检查每个错误代码,验证每个响应,对无响应的系统设置超时。
- 重试逻辑 :编写重试逻辑,使用指数退避算法。
- 故障处理 :能安全绕过故障系统时就绕过,无法绕过时要以可控和安全的方式失败,并记录问题,以便监控系统发送警报。

3. 日志记录

日志记录是排查生产问题的重要手段,要采取系统和深思熟虑的方法进行日志记录。
- 有针对性记录 :不要在代码中随意添加日志语句,要思考可能出现的问题以及人们需要了解的信息,创建用户故事来解决这些问题。例如,发现安全漏洞时,如何确定受影响的用户和数据;性能下降时,如何确定要修复的问题。前瞻性分析和威胁模型有助于识别和优先处理这些故事。
- 结构化日志 :使用结构化日志,并将其路由到集中的数据存储,以便于搜索和过滤。结构化日志以机器可读的格式(如 JSON)输出数据,编写日志包装器支持记录任意对象,方便包含提供重要上下文的变量。
- 示例代码

log.action({
  code: "L16",
  message: "ExampleService API has been deprecated.",
  endpoint,
  headers: response.headers,
});
- **日志输出示例**:
{
  "timestamp": 16206894860033,
  "date": "Mon May 10 2021 23:31:26 GMT",
  "alert": "action",
  "component": "ExampleApp",
  "node": "web.2",
  "correlationId": "b7398565-3b2b-4d80-b8e3-4f41fdb21a98",
  "code": "L16",
  "message": "ExampleService API has been deprecated.",
  "endpoint": "/v2/accounts/:account_id",
  "headers": {
    ⋮
    "x-api-version": "2.22",
    "x-deprecated": true,
    "x-sunset-date": "Wed November 10 2021 00:00:00 GMT"
  }
}
  • 异常上下文 :抛出异常时也要提供上下文信息。例如,对于不应执行的默认 case,不要只抛出“unreachable code executed”,而要抛出详细的异常信息,方便团队排查问题。
  • 标准日志字段 :日志中可考虑添加以下标准字段:
    | 字段 | 说明 |
    | ---- | ---- |
    | Timestamp | 日志发生的机器可读时间 |
    | Date | 时间戳的人类可读版本 |
    | Alert | 要发送的警报类型,通常也称为“级别” |
    | Component | 生成错误的代码库 |
    | Node | 生成错误的具体机器 |
    | Correlation ID | 唯一 ID,用于关联相关日志,通常跨多个服务 |
    | Code | 日志消息的任意唯一代码,用于搜索日志和查找文档 |
    | Message | 代码的人类可读版本,可变化 |

  • 日志文档 :为日志配备文档,简要解释每个警报以及应对方法,这通常是运行手册的一部分。例如:

    • L16 :ExampleService API 已被弃用。
      • 解释 :我们的计费提供商 ExampleService 计划移除我们使用的一个 API。
      • 警报 :行动。当他们移除该 API 时,我们的应用将停止工作,因此必须解决,但他们提供了宽限期,所以不需要立即处理。
      • 行动
        • 代码可能需要升级到新的 API。如果需要,包含 ExampleService 返回的头信息的 headers 对象应包含 x-deprecated 头和 x-sunset-date 头。
        • endpoint 字段显示了导致警报的具体 API,但我们使用的其他端点也可能受影响。
        • 升级的紧迫性取决于 API 何时退役,由 x-sunset-date 头显示。可通过查看 ExampleService 的文档(example.com)进行验证。
        • 在 API 升级之前,可能需要禁用此警报。注意不要意外禁用其他 API 的警报,并考虑让警报自动重新启用,以免忘记升级。
4. 指标与可观测性

除了日志记录,代码还应测量感兴趣的项目,即指标。大多数指标是技术性的,如应用接收的请求数量、响应请求的时间、内存使用情况等;也有一些是业务导向的,如客户购买的数量和价值。
指标通常会在一段时间内进行累积,然后进行报告。可以在应用内部通过日志消息报告,也可以通过向指标聚合工具发送事件来完成。
日志和指标共同构成了可观测性,即从技术和业务角度理解系统行为的能力。要像对待日志一样,深思熟虑地进行可观测性规划,与利益相关者沟通,确定从运营、安全、业务和支持等角度所需的可观测性,创建用户故事来满足这些需求。

5. 监控与警报

监控用于检测日志和指标何时需要关注,当需要时,监控工具会发送警报(如电子邮件、聊天通知、短信或电话),以便有人处理。
- 警报配置 :监控工具的警报决策可能很复杂,有些监控工具可以通过合适的编程语言进行配置。如果是这样,要像对待真正的程序一样对待配置,将其存储在版本控制中,关注设计,并尽可能编写测试。
- 警报类型 :监控工具发送的警报通常分为四类:
| 警报类型 | 说明 |
| ---- | ---- |
| 紧急 | 情况危急,需要有人立即醒来修复 |
| 行动 | 重要事项需要关注,但不紧急到需要唤醒任何人 |
| 监控 | 出现异常情况,需要人工二次检查(谨慎使用) |
| 信息 | 不需要任何人关注,但对可观测性有用 |
- 日志与警报映射 :配置监控工具根据日志进行警报时,大多数日志库使用 FATAL、ERROR、WARN、INFO 和 DEBUG,可将其直接映射到上述级别:FATAL 对应紧急,ERROR 对应行动,WARN 对应监控,INFO 对应信息,不要使用 DEBUG,它只会增加噪音。开发时可以使用 DEBUG 日志,但不要提交,有些团队会在持续集成脚本中检测到 DEBUG 日志时自动使构建失败。
- 指标警报 :其他警报可能基于指标触发,通常由监控工具生成,而非应用代码。要像对待日志消息一样,为每个警报提供查找代码、人类可读的消息和运行手册中的文档。
- 避免警报疲劳 :要避免警报疲劳,每个警报都要得到处理,要么解决问题,要么防止误报再次发生。如果某个警报的级别与实际情况不符,如紧急警报可以明天处理,或监控警报从未指示真正的问题,可考虑降低其级别或使其更智能。对于那些总是需要机械响应、无需思考的警报,可以考虑将其自动化,特别是监控警报,避免其成为琐碎信息的垃圾场。创建监控警报帮助理解系统行为后,可通过使其更具选择性将其升级为行动或紧急警报。
- 程序员参与 :为控制警报,要让程序员参与轮班值班,这有助于他们了解哪些警报重要,从而编写更好的警报决策代码。

6. 团队协作与资源协调

在实际工作中,团队协作和资源协调对于构建可运维的软件系统至关重要。以下通过一个场景来说明可能遇到的问题及解决方法。

假设你入职新公司第一周,向老板询问团队中的 DevOps 人员,老板表示公司有单独的 DevOps 团队、ProdOps 团队和 DataOps 团队,DevOps 负责测试环境和 CI 工具,ProdOps 处理生产环境,DataOps 负责数据库和架构。你遇到 API 可能抛出“Disk Full”异常的问题,想与 ProdOps 团队协调合适的响应,但老板认为 ProdOps 团队很忙,一切需通过他们的票务系统,且认为“Disk Full”不太可能发生,建议记录异常并返回 null。

这反映出在大型公司中,不同团队之间的协作可能存在沟通障碍和流程繁琐的问题。为解决这些问题,可采取以下措施:
- 提前沟通 :在开发早期主动与运营和安全团队沟通,让他们了解项目的运营需求和警报设置,询问如何建立定期检查机制以获取他们的反馈。
- 明确职责 :清晰定义各团队的职责和权限,避免出现职责不清导致的沟通不畅和工作延误。
- 简化流程 :优化跨团队协作的流程,减少不必要的繁琐步骤,提高工作效率。

7. 时间管理与资源分配

在构建可运维软件系统的过程中,时间管理和资源分配是关键问题。有人可能会问如何为这些工作腾出时间,实际上,不构建可运维的软件通常会浪费大量时间在本可避免的问题上,或者至少需要花费更多时间来诊断和解决问题。
- 故事规划 :可以像规划其他工作一样,通过用户故事来安排运营和安全需求。为确保这些故事得到优先处理,要让具备运营和安全技能的人员参与规划讨论。
- 警报处理 :将警报视为 bug,应是意外发生且需认真对待的。每个警报都要立即处理,对于误报,要改进警报机制,减少误报的可能性。
- 质量改进 :团队的空闲时间可用于进行一些质量改进工作,如调整警报的灵敏度或改进日志消息。

8. 应对人员技能不足

如果团队中没有具备运营或安全技能的人员,可以采取以下措施:
- 主动沟通 :在开发早期主动联系运营部门,表明希望获得他们对运营需求和警报设置的意见,并询问如何建立定期检查机制以获取反馈。通常,运营人员会对开发团队在问题出现前主动寻求帮助感到惊喜,并愿意提供帮助。
- 安全人员参与 :虽然安全人员相对较少,但也可以采用类似的方法,在开发早期与他们沟通,讨论如何将安全融入到开发过程中,而不是在事后添加。

9. 构建可运维系统的前提条件与指标
  • 前提条件 :任何团队都可以构建可运维的系统,但为获得最佳效果,需要一个包含具备运营和安全技能人员的团队,或者与拥有这些技能的人员建立良好关系,同时需要一种重视长期思考的管理文化。
  • 指标 :构建可运维系统时,可参考以下指标来评估系统的可运维性:
    • 团队是否考虑并解决了潜在的安全威胁。
    • 警报是否具有针对性和相关性。
    • 生产问题发生时,组织是否有能力做出响应。
    • 软件是否具有弹性和稳定性。
10. 替代方法与实验

构建可运维系统的核心是认识到“可用的软件”不仅意味着编写代码,还包括在生产环境中运行,并有意地构建软件以满足生产需求。最好的方法是让具备运营和安全专业知识的人员作为团队的一部分参与其中。
但公司的运营和安全人员有限,难以让每个团队都配备这些人员。常见的替代方法是使用清单和自动化执行工具,但效果不佳。更好的方法是为团队成员提供培训,提升他们的技能。在进行实验之前,建议先与采用真正 DevOps 方法的团队合作,这样可以了解实验的效果。

综上所述,构建可运维的软件系统需要从配置管理、日志记录、指标监控、团队协作等多个方面入手,综合运用各种策略和实践,不断优化和改进,才能确保软件在生产环境中稳定运行,为业务发展提供有力支持。

构建可运维的软件系统:关键策略与实践

11. 持续部署与警报处理

在软件系统的运维过程中,持续部署和合理的警报处理是保障系统稳定运行的重要环节。

持续部署能够让软件的更新快速且可靠地到达生产环境。在处理警报方面,当基于指标触发警报时,要确保每个警报都有明确的查找代码、人类可读的消息以及相关文档。例如,在监控工具生成的警报中,我们可以为其赋予类似日志消息的详细信息,方便后续的排查和处理。

对于警报的处理方式,有两种常见的思路。一种是将警报决策编程到应用代码中,通过日志触发警报。这种方式的好处是团队成员可以使用熟悉的代码来实现警报的“智能”判断,但缺点是更改警报需要重新部署软件,所以更适合采用持续部署的团队。另一种是在监控配置中设置警报,这种方式灵活性较高,但可能会增加配置的复杂性。

为了避免警报疲劳,我们需要对警报进行有效的管理。当一个警报持续不适合其设定的级别时,比如“紧急”警报实际上可以第二天处理,或者“监控”警报从未指示真正的问题,我们应该考虑降级处理或者让警报更加智能。例如,对于那些总是需要机械响应、无需思考的警报,可以将其自动化,把响应逻辑转化为代码。这样不仅可以提高处理效率,还能减少人力的浪费。

12. 可视化规划与团队协作

可视化规划是一种有效的团队协作方式,它可以帮助团队成员更好地理解项目的目标和进度。通过可视化的工具,如流程图、甘特图等,我们可以清晰地展示项目的各个环节和依赖关系。

以下是一个简单的 mermaid 格式流程图,展示了一个软件项目从需求分析到部署的基本流程:

graph LR
    A[需求分析] --> B[设计]
    B --> C[开发]
    C --> D[测试]
    D --> E[部署]

在团队协作中,不同的角色有着不同的职责。例如,开发团队负责编写代码,运维团队负责保障系统的稳定运行,安全团队负责确保系统的安全性。为了实现高效的协作,各个团队之间需要建立良好的沟通机制。可以定期召开会议,分享项目的进展和遇到的问题,共同探讨解决方案。

同时,团队成员之间的相互理解和支持也非常重要。在面对问题时,要避免互相指责,而是应该共同寻找问题的根源,共同解决问题。例如,当出现生产问题时,开发团队和运维团队可以一起分析日志和指标,找出问题所在,并共同制定修复方案。

13. 无缺陷目标与持续改进

追求无缺陷的软件是每个团队的目标,但在实际开发过程中,完全避免缺陷是几乎不可能的。因此,我们需要建立一套持续改进的机制,不断提高软件的质量。

可以通过收集和分析软件的缺陷数据,找出常见的问题和潜在的风险点。例如,我们可以统计不同类型的缺陷出现的频率,分析缺陷产生的原因,如代码逻辑错误、配置问题等。根据分析结果,我们可以制定针对性的改进措施,如加强代码审查、优化配置管理等。

同时,我们还可以建立一个反馈机制,让用户和团队成员能够及时反馈软件的问题和改进建议。通过不断地收集反馈,我们可以了解用户的需求和期望,及时调整开发方向,提高用户满意度。

14. 资源缓冲与弹性应对

在软件系统的运行过程中,可能会遇到各种突发情况,如流量高峰、硬件故障等。为了应对这些情况,我们需要预留一定的资源缓冲,提高系统的弹性。

资源缓冲可以包括计算资源、存储资源等。例如,在服务器配置方面,可以适当增加服务器的数量或者配置更高性能的服务器,以应对流量高峰。同时,我们还可以采用负载均衡技术,将流量均匀地分配到多个服务器上,避免单个服务器过载。

在存储方面,可以采用分布式存储系统,提高数据的可靠性和可用性。当某个存储节点出现故障时,系统可以自动切换到其他节点,保证数据的正常访问。

以下是一个简单的表格,展示了不同类型的资源缓冲及其作用:
| 资源类型 | 缓冲方式 | 作用 |
| ---- | ---- | ---- |
| 计算资源 | 增加服务器数量、提高服务器性能 | 应对流量高峰,避免系统崩溃 |
| 存储资源 | 分布式存储系统 | 提高数据可靠性和可用性,应对硬件故障 |

15. 总结与展望

构建可运维的软件系统是一个复杂的过程,需要从多个方面进行综合考虑。通过合理的配置管理、有效的日志记录、精确的指标监控、高效的团队协作以及持续的改进优化,我们可以提高软件系统的稳定性、可靠性和可维护性。

在未来的发展中,随着技术的不断进步,软件系统的规模和复杂性将不断增加。我们需要不断地学习和掌握新的技术和方法,如人工智能、大数据等,以应对日益复杂的运维挑战。同时,我们还需要加强团队的建设和培养,提高团队成员的技能和素质,打造一支高效、协作的运维团队。

总之,构建可运维的软件系统是一个长期的、持续的过程,需要我们不断地努力和探索。只有这样,我们才能确保软件系统能够为业务的发展提供有力的支持,实现业务的持续增长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值