一切都好!
我叫Nikita,是Cian工程团队的组长。 我在公司的职责之一是将与生产基础设施相关的事故数量减少到零。
下面将要讨论的内容给我们带来了很大的痛苦,而本文的目的是防止其他人重复我们的错误或至少尽量减少其影响。
前言
很久以前,当 Cian 由单体组成时,还没有微服务的迹象,我们通过检查 3-5 个页面来衡量资源的可用性。
他们回答 - 一切都很好,如果他们很长时间不回答 - 警报。 他们需要下班多长时间才能被视为事件,这是由人们在会议上决定的。 工程师团队始终参与事件调查。 调查完成后,他们写了一份事后分析——一种通过电子邮件发送的报告,格式如下:发生了什么、持续了多长时间、我们当时做了什么、将来会做什么。
网站的主要页面或我们如何理解我们已经触底
为了以某种方式了解错误的优先级,我们已经确定了业务功能最关键的网站页面。 使用它们,我们可以计算成功/不成功的请求和超时的数量。 这就是我们衡量正常运行时间的方式。
假设我们发现该网站有许多非常重要的部分负责主要服务 - 搜索和提交广告。 如果失败的请求数量超过 1%,则属于严重事件。 如果在黄金时段15分钟内错误率超过0,1%,那么这也被视为严重事件。 这些标准涵盖了大多数事件;其余事件超出了本文的范围。
最佳最佳事件 青色
所以,我们肯定已经学会了确定事件发生的事实。
现在,每个事件都被详细描述并反映在 Jira 史诗中。 顺便说一句:为此我们启动了一个单独的项目,称之为“失败”——只能在其中创建史诗。
如果你收集过去几年的所有失败,领导者是:
- mssql相关事件;
- 由外部因素引起的事件;
- 管理错误。
让我们更详细地看看管理员的错误,以及其他一些有趣的失败。
第五名——“在 DNS 中把事情整理好”
那是一个风雨交加的星期二。 我们决定恢复 DNS 集群中的秩序。
我想将内部 DNS 服务器从 bind 转移到 powerdns,为此分配完全独立的服务器,除了 DNS 之外什么都没有。
我们在 DC 的每个位置放置了一台 DNS 服务器,现在是将区域从 Bind 移动到 powerdns 并将基础设施切换到新服务器的时刻到来了。
在迁移过程中,在所有服务器上的本地缓存绑定中指定的所有服务器中,只剩下一台位于圣彼得堡的数据中心。 该 DC 最初被声明为对我们来说不重要,但突然变成了单点故障。
正是在搬迁期间,莫斯科和圣彼得堡之间的运河垮塌了。 实际上,我们有五分钟没有 DNS 服务,当托管服务商解决了问题后,我们又恢复了正常。
结论:
如果说以前我们在准备工作时忽略了外部因素,那么现在它们也被纳入了我们正在准备的清单中。 现在我们努力确保所有组件都保留n-2,并且在工作过程中我们可以将这个级别降低到n-1。
- 在制定行动计划时,标记出服务可能失败的点,并提前考虑一切“每况愈下”的场景。
- 将内部 DNS 服务器分布在不同的地理位置/数据中心/机架/交换机/输入上。
- 在每台服务器上,安装本地缓存 DNS 服务器,该服务器将请求重定向到主 DNS 服务器,如果不可用,则会从缓存中进行响应。
第四名——“在 Nginx 中把事情整理好”
有一天,我们的团队决定“我们已经受够了”,重构 nginx 配置的过程开始了。 主要目标是将配置引入直观的结构。 以前一切都是“历史既定”,没有任何逻辑。 现在,每个 server_name 已移至同名文件,并且所有配置已分发到文件夹中。 顺便说一句,该配置包含 253949 行或 7836520 个字符,占用近 7 MB 的空间。 顶层结构:
Nginx结构
├── access
│ ├── allow.list
...
│ └── whitelist.conf
├── geobase
│ ├── exclude.conf
...
│ └── geo_ip_to_region_id.conf
├── geodb
│ ├── GeoIP.dat
│ ├── GeoIP2-Country.mmdb
│ └── GeoLiteCity.dat
├── inc
│ ├── error.inc
...
│ └── proxy.inc
├── lists.d
│ ├── bot.conf
...
│ ├── dynamic
│ └── geo.conf
├── lua
│ ├── cookie.lua
│ ├── log
│ │ └── log.lua
│ ├── logics
│ │ ├── include.lua
│ │ ├── ...
│ │ └── utils.lua
│ └── prom
│ ├── stats.lua
│ └── stats_prometheus.lua
├── map.d
│ ├── access.conf
│ ├── ..
│ └── zones.conf
├── nginx.conf
├── robots.txt
├── server.d
│ ├── cian.ru
│ │ ├── cian.ru.conf
│ │ ├── ...
│ │ └── my.cian.ru.conf
├── service.d
│ ├── ...
│ └── status.conf
└── upstream.d
├── cian-mcs.conf
├── ...
└── wafserver.conf
它变得好多了,但在重命名和分发配置的过程中,其中一些配置的扩展名错误,并且没有包含在 include *.conf 指令中。 导致部分主机不可用,返回301返回主页。 由于响应代码不是 5xx/4xx,所以没有立即注意到这一点,而是在早上才注意到。 之后,我们开始编写测试来检查基础设施组件。
结论:
- 正确构建您的配置(不仅仅是 nginx)并在项目的早期阶段仔细考虑结构。 通过这种方式,您将使团队更容易理解它们,从而减少 TTM。
- 为一些基础设施组件编写测试。 例如:检查所有关键服务器名称是否给出正确的状态+响应正文。 手头只要有几个脚本来检查组件的基本功能就足够了,这样就不用在凌晨 3 点疯狂地记住还需要检查什么。
第三名——“卡桑德拉突然空间不足”
数据稳步增长,一切都很好,直到 Cassandra 集群中大型案例空间的修复开始失败的那一刻,因为压缩无法对它们起作用。
在一个暴风雨天,该簇几乎变成了一个南瓜,即:
- 集群中大约还剩下总空间的 20%;
- 无法完全添加节点,因为添加节点后由于分区空间不足而无法进行清理;
- 由于压实不起作用,生产率逐渐下降;
- 集群处于紧急模式。
退出 - 我们在没有清理的情况下又添加了 5 个节点,之后我们开始系统地从集群中删除它们并重新进入它们,就像空间不足的空节点一样。 花费的时间比我们想要的要多得多。 存在集群部分或完全不可用的风险。
结论:
- 在所有 cassandra 服务器上,每个分区上的空间占用不应超过 60%。
- 它们的负载不应超过 50% cpu。
- 您不应忘记容量规划,并且需要根据每个组件的具体情况对其进行仔细考虑。
- 集群中的节点越多越好。 包含少量数据的服务器过载速度更快,这样的集群更容易复活。
第二名——“数据从consul键值存储中消失”
对于服务发现,我们像许多人一样使用 consul。 但我们也将其键值用于整体的蓝绿布局。 它存储有关活动和非活动上游的信息,这些信息在部署期间会改变位置。 为此,编写了与 KV 交互的部署服务。 某个时刻,KV 的数据消失了。 从记忆中恢复,但有一些错误。 因此,在上传过程中,上游的负载分布不均,并且由于后端 CPU 过载,我们收到了很多 502 错误。 因此,我们从 consul KV 迁移到 postgres,从那里删除它们不再那么容易。
结论:
- 未经任何授权的服务不应包含对网站运营至关重要的数据。 例如,如果您在 ES 中没有授权,那么最好在不需要的地方拒绝网络级别的访问,只保留必要的,并设置 action.delta_requires_name: true。
- 提前练习您的备份和恢复机制。 比如提前制作一个可以备份和恢复的脚本(比如python)。
第一名——《不明显的船长》
在某些时候,我们注意到在后端有 10 多台服务器的情况下,nginx 上游的负载分布不均匀。 由于轮询方式是按顺序从第一个上游发送请求到最后一个上游,并且每次 nginx 重新加载都会重新开始,所以第一个上游总是比其他上游收到更多的请求,导致它们的工作速度变慢,整个站点受到影响。 随着流量的增加,这一点变得越来越明显。 简单地更新 nginx 来启用随机是行不通的——我们需要重做一堆在 1 版本上没有成功的 lua 代码(当时)。 我们必须修补 nginx 1.15,引入随机支持。 这解决了问题。 该错误赢得了“非显而易见性队长”类别。
结论:
探索这个错误是非常有趣和令人兴奋的)。
- 组织您的监控,以便帮助您快速发现此类波动。 例如,您可以使用ELK来监控每个upstream的每个后端的rps,从nginx的角度监控它们的响应时间。 在本例中,这帮助我们识别了问题。
因此,通过更加谨慎地对待所做的事情,大多数失败都是可以避免的。 我们必须永远记住墨菲定律: 任何可能出错的事情都会出错 并基于它构建组件。
来源: habr.com