Chúng tôi đã chứng minh mọi cảnh báo đều có thể kích hoạt. Phần lớn thì không.
Giám sát có một đặc tính khiến nó nguy hiểm theo cách riêng: nó là đoạn mã chỉ chạy trong lúc thảm họa. Mọi phần khác trong hệ thống của bạn chạy liên tục, ngay trước mặt người dùng, ném ra những lỗi bạn nhận thấy. Cơ chế cảnh báo của bạn chạy vào đúng cái ngày cơ sở dữ liệu đầy tràn, chứ không sớm hơn, và nếu nó hỏng thì bạn phát hiện ra đúng vào khoảnh khắc bạn đang trông cậy vào nó, và đúng theo cái cách bảo đảm rằng không ai để mắt tới. Một cảnh báo câm lặng còn tệ hơn không có cảnh báo, vì cảnh báo bạn không có thì bạn biết là mình không có, còn một cảnh báo câm lặng thì bạn tin là mình đang có.
Vì vậy chúng tôi thôi tin rằng các cảnh báo của mình hoạt động chỉ vì chúng tôi đã viết ra chúng, và dựng một bộ khung kiểm thử để chứng minh điều đó. Với mỗi quy tắc cảnh báo mà chúng tôi triển khai, bộ khung đẩy một tín hiệu thật lẽ ra phải làm nó kích hoạt, rồi làm cái điều thực sự quan trọng: nó chờ cho tới khi thông báo tới được một relay thu nhận theo đúng nghĩa vật lý, một dịch vụ nhỏ đóng vai chiếc điện thoại lẽ ra sẽ rung. Việc nó sáng lên trong giao diện của chính công cụ giám sát thì không tính. Việc gửi tới nơi mới tính. Trọn cả chặng đường từ tín hiệu cho đến một tin nhắn nằm trong tay, bằng không thì đó là một thất bại.
Lần chạy thật đầu tiên làm rớt phần lớn các quy tắc. Đây là những gì đang ẩn nấp sau các bảng điều khiển màu xanh.
Cảnh báo chủ lực đã chết
Quy tắc quan trọng nhất mà chúng tôi có là quy tắc kích hoạt khi tỷ lệ lỗi leo thang: quá nhiều HTTP 500, gọi ai đó dậy. Bộ khung kiểm thử đẩy vào nó bốn trăm năm mươi lỗi máy chủ thật, xác nhận rằng ít nhất chín mươi phần trăm trong số đó thực sự trả về 500, rồi chờ. Không có gì tới cả. Cái cảnh báo duy nhất mà chúng tôi muốn tin cậy nhất chưa từng kích hoạt lấy một lần, và sẽ không kích hoạt trong một sự cố thật.
Nguyên nhân là một cái tên. Các số liệu của chúng tôi đến từ OpenTelemetry, nơi tên được đặt theo dạng có dấu chấm, như http.server.request.duration. Ở đâu đó giữa lúc đọc một bài hướng dẫn và lúc viết truy vấn, chúng tôi đã ngỡ rằng backend giám sát sẽ chuẩn hóa các dấu chấm đó thành dấu gạch dưới, theo cách mà rất nhiều công cụ Prometheus vẫn làm, và đã viết truy vấn cảnh báo theo dạng gạch dưới. Backend không hề chuẩn hóa. Nó giữ nguyên các tên có dấu chấm đúng như khi được gửi đi. Thế là cảnh báo truy vấn một số liệu không tồn tại, khớp với con số không, tính ra tỷ lệ lỗi bằng không mãi mãi, và ngồi đó bình thản và xanh mướt, trong khi số liệu thật, dưới cái tên có dấu chấm thật của nó, ghi lại từng lỗi năm trăm một. Truy vấn không sai theo kiểu báo lỗi. Nó sai theo kiểu âm thầm trả về tập rỗng, rồi phép tính biến điều đó thành một con số không hoàn toàn có vẻ hợp lý.
Cái đã kéo mọi cảnh báo log sập theo nó
Đó mới là cái tệ. Cái tiếp theo còn tệ hơn, và bộ khung kiểm thử chỉ bắt được nó bởi vì nó đòi hỏi việc gửi tới nơi chứ không phải việc quy tắc trông có vẻ đúng.
Cả một nhóm cảnh báo của chúng tôi dựa trên log: khớp một dòng trong log, thì kích hoạt. Từng cái một trong số đó đang đánh giá trên hư không, và lý do chẳng liên quan gì tới các quy tắc cảnh báo. Đó là một thiết lập lưu giữ.
Chúng tôi đồng bộ thời gian lưu giữ log qua API thiết lập của backend. API đó đã chuyển sang một phiên bản mới với hình dạng yêu cầu mới, còn chúng tôi thì vẫn gửi hình dạng cũ. Phần thân cũ không bị từ chối. Bộ giải mã ở đầu bên kia âm thầm bỏ qua những trường mà nó không nhận ra, nên nó đọc các trường nay đã lạ của chúng tôi như thể vắng mặt, lấy giá trị mặc định cho thời gian lưu giữ mà nó không tìm thấy, và giá trị mặc định đó là số không. Lời gọi trả về một mã 200 tươi vui. Mà thời gian lưu giữ bằng không ngày nghĩa là hết hạn tức thì: dữ liệu log vừa được ghi bị đóng dấu để hết hạn ngay lập tức, nên trên thực tế kho mà các cảnh báo log truy vấn luôn luôn rỗng. Suốt thời gian đó việc nạp dữ liệu trông hoàn toàn khỏe mạnh, vì log vẫn đang tới. Chúng chỉ già đi và biến mất ngay khoảnh khắc vừa đáp xuống.
Hai hệ thống, mỗi bên đều làm một điều có thể biện hộ được. API thiết lập chấp nhận một yêu cầu mà nó chỉ hiểu một phần thay vì báo lỗi, đó là một lựa chọn phổ biến và thường hợp lý. Đợt triển khai của chúng tôi thì gửi một phần thân đã cũ so với một lần nâng phiên bản. Chẳng bên nào nêu ra điều gì. Kết quả là con số hệ trọng nhất trong toàn bộ đường ống log của chúng tôi, một dòng log sống được bao lâu, đã bị âm thầm đặt về không, và triệu chứng duy nhất nhìn thấy được là mọi cảnh báo log cứ câm lặng một cách vĩnh viễn và có vẻ khỏe mạnh. Nếu chúng tôi kiểm tra xem các quy tắc trông có đúng không, chúng trông hoàn hảo. Chỉ có việc kiểm tra rằng một sự kiện được ghi log thật sinh ra một tin nhắn được gửi tới nơi mới phơi bày được nó, bởi chỉ bài kiểm tra đó mới thực sự đọc ngược lại từ cái kho đã bị dọn sạch.
Những cảnh báo kích hoạt mãi mãi, thứ cũng chẳng khác gì không bao giờ
Một lỗi thứ ba chạy theo hướng ngược lại, và nó là cái tinh vi. Một nhúm cảnh báo kiểm toán cứ kích hoạt liên tục. Nghe thì có vẻ trái ngược với một cảnh báo câm lặng, nhưng hệ quả thì y hệt, và hiểu vì sao thì đáng để vòng qua một chút.
Khi một trong các cảnh báo này kích hoạt, backend giám sát ghi một dòng log về lần đánh giá đó, một vụn bánh mì để lần theo, và dòng ấy chứa chính biểu thức bộ lọc của cảnh báo dưới dạng văn bản. Vụn bánh đó rồi lại bị nạp vào như một dòng log, y như mọi thứ khác. Thế nên lần kế tiếp quy tắc đánh giá, nó tìm thấy một dòng khớp: chính dấu chân của nó từ lần trước. Quy tắc đã trở nên tự nuôi sống chính mình. Nó khớp với cái bóng của chính nó, ở mỗi lần đánh giá, mãi mãi.
Một cảnh báo lúc nào cũng đang kích hoạt thì không bao giờ chuyển từ chưa kích hoạt sang kích hoạt, mà chính bước chuyển ấy mới là thứ gửi đi một thông báo. Vậy nên lớp cảnh báo, khi thấy một điều kiện vốn đã đúng và vẫn cứ đúng, đã chặn những thông báo về sau, đúng như thiết kế, để đỡ cho bạn khỏi bị gọi mỗi phút về một chuyện đã được báo rồi. Hệ quả là một sự kiện thật, một hành động kiểm toán chính đáng đáng để gọi báo, lại rơi vào một quy tắc vốn đã kẹt ở trạng thái bật và vì thế chẳng nói gì. Một cảnh báo bị ghim vĩnh viễn ở trạng thái bật thì câm chẳng kém một cảnh báo bị ghim vĩnh viễn ở trạng thái tắt. Cách sửa là thu hẹp các quy tắc đó vào những dòng log do chính các dịch vụ của chúng tôi phát ra, để chúng thôi khớp với chuyện phiếm mà backend nói về chính nó, với một ngoại lệ có chủ ý mà tín hiệu thật của nó quả thực đến từ bên ngoài các dịch vụ của chúng tôi.
Quy tắc coi hơi thở đầu tiên của một pod là bình thường
Cái cuối cùng là một bài học nhỏ mà sắc bén về việc tính tốc độ. Một cảnh báo dựng trên tốc độ thay đổi của một số liệu không thể tính ra tốc độ từ mẫu đầu tiên nhất của một chuỗi, bởi phía trước nó chẳng có gì để mà so sánh. Thế nên khi một pod vừa mới toanh phun ra loạt lỗi đầu tiên, loạt lỗi đó trở thành đường nền thay vì một cú vọt, và quy tắc lẽ ra phải bắt được nó lại chỉ thấy một điểm khởi đầu. Chúng tôi sửa việc này trong bộ khung kiểm thử bằng cách đẩy những tín hiệu như vậy thành hai đợt, một đợt nhỏ để thiết lập chuỗi và một đợt thật sau đó, mà đây cũng là một cách mô tả xác đáng điều lưu lượng sản xuất tự nó làm và điều mà một bài kiểm thử nhân tạo phải chủ ý bắt chước.
Mỗi lỗi đều ẩn trong một đường ghép mà bạn chỉ tìm ra bằng cách vận hành nó
Từng lỗi một trong số này đều vô hình trước sự soi xét. Các truy vấn đọc lên thì đúng. Các quy tắc đã được triển khai. Các bảng điều khiển đều xanh. Việc nạp dữ liệu thì khỏe mạnh. Mỗi lỗi sống trong một đường ghép giữa hai thứ mà tự thân mỗi thứ đều ổn: một cái tên số liệu và một giả định về việc chuẩn hóa, một phần thân yêu cầu và một lần nâng phiên bản, một cảnh báo và cái log do chính lần kích hoạt của nó sinh ra, một hàm tính tốc độ và một chuỗi vừa mới bắt đầu. Bạn không thể đọc một đường ghép. Bạn phải vận hành nó.
Vậy hãy đối xử với giám sát đúng như bản chất của nó, tức là mã, và cụ thể là thứ mã có lần chạy thật duy nhất chính là lúc khẩn cấp. Thứ mã chỉ chạy vào lúc khẩn cấp cần các bài kiểm thử của nó chạy vào mọi ngày còn lại, bởi chẳng có một lần hỏng nhẹ nhàng đầu tiên nào để cảnh báo bạn cả. Hãy đẩy một tín hiệu thật đi trọn từ đầu tới cuối, và kiểm chứng trên sản phẩm ở ngay cuối chuỗi, tức là thông báo đã được gửi tới nơi, chứ không phải trên việc quy tắc trông có vẻ khỏe mạnh ở khúc giữa. Bất cứ điều gì kém hơn thế đều chỉ là kiểm tra rằng bạn đã viết ra một cảnh báo, điều bạn đã biết rồi, thay vì kiểm tra rằng nó kích hoạt, điều duy nhất mà bạn thực sự muốn.
Nếu bạn muốn nhà cung cấp danh tính của mình đã tự theo dõi chính nó theo cách này, Authagonal cho hệ thống cảnh báo của riêng mình đi qua đúng bộ khung kiểm thử này, để cái ngày có gì đó hỏng, thì lời gọi báo lẽ ra phải kích hoạt sẽ kích hoạt thật.