Rơi xuống hang thỏ: Câu chuyện nạp lại Varnish thất bại - Phần 1

manushanka, sau khi bấm nút suốt 20 phút trước đó như thể mạng sống của anh ấy phụ thuộc vào nó, quay sang tôi với ánh mắt nửa hoang dã và nụ cười ranh mãnh - “Anh bạn, tôi nghĩ tôi hiểu rồi.”

“Nhìn đây,” anh ấy nói và chỉ vào một trong những biểu tượng trên màn hình, “Tôi cá chiếc mũ đỏ của mình rằng nếu chúng tôi thêm vào đây những gì tôi vừa gửi cho bạn,” chỉ vào một đoạn mã khác, “lỗi sẽ không còn nữa sẽ được hiển thị."

Hơi bối rối và mệt mỏi, tôi sửa đổi biểu thức sed mà chúng tôi đã làm được một thời gian, lưu tệp và chạy systemctl varnish reload. Thông báo lỗi đã biến mất...

“Những email tôi đã trao đổi với ứng viên,” đồng nghiệp của tôi tiếp tục, khi nụ cười toe toét của anh ấy chuyển thành nụ cười vui vẻ thực sự, “Tôi chợt nhận ra rằng đây chính xác là cùng một vấn đề!”

Mọi việc đã bắt đầu thế nào

Bài viết giả định sự hiểu biết về cách hoạt động của bash, awk, sed và systemd. Ưu tiên biết về sơn bóng nhưng không bắt buộc.
Dấu thời gian trong đoạn trích đã được thay đổi.
Được viết bằng manushanka.
Văn bản này là bản dịch của bản gốc được xuất bản bằng tiếng Anh hai tuần trước; dịch boikoden.

Mặt trời chiếu qua cửa sổ nhìn toàn cảnh vào một buổi sáng mùa thu ấm áp khác, một tách đồ uống giàu caffeine mới pha chế nằm cách xa bàn phím, bản giao hưởng âm thanh yêu thích của bạn vang lên trong tai nghe, át đi tiếng xào xạc của bàn phím cơ và mục nhập đầu tiên trong danh sách phiếu tồn đọng trên bảng Kanban sáng lên một cách tinh nghịch với tiêu đề định mệnh “Điều tra véc nireload” sh: echo: Lỗi I/O trong dàn dựng” (Điều tra “varnishreload sh: echo: Lỗi I/O” trong dàn dựng). Khi nói đến sơn bóng, không thể có chỗ cho sai sót, ngay cả khi chúng không gây ra bất kỳ vấn đề nào như trong trường hợp này.

Đối với những người không quen thuộc với tải lại vecni, đây là tập lệnh shell đơn giản được sử dụng để tải lại cấu hình Sơn dầu - còn gọi là VCL.

Như tiêu đề của vé cho thấy, lỗi đã xảy ra trên một trong các máy chủ trên sân khấu và vì tôi chắc chắn rằng định tuyến sơn bóng trên sân khấu đang hoạt động bình thường nên tôi cho rằng đây sẽ là một lỗi nhỏ. Vì vậy, chỉ có một tin nhắn kết thúc ở luồng đầu ra đã đóng. Tôi tự mình lấy vé, hoàn toàn tin tưởng rằng tôi sẽ đánh dấu nó sẵn sàng trong vòng chưa đầy 30 phút, vỗ nhẹ vào lưng mình vì đã dọn sạch bảng thêm một thứ rác rưởi nữa và quay lại với những vấn đề quan trọng hơn.

Đâm vào tường ở tốc độ 200 km/h

Mở tập tin varnishreload, trên một trong các máy chủ chạy Debian Stretch, tôi thấy một tập lệnh shell dài chưa đến 200 dòng.

Sau khi xem qua tập lệnh, tôi không nhận thấy bất kỳ điều gì có thể dẫn đến sự cố khi chạy nó nhiều lần trực tiếp từ thiết bị đầu cuối.

Dù sao đây cũng là một sân khấu, dù có vỡ cũng không có ai phàn nàn, à... cũng không quá đáng. Tôi chạy tập lệnh và xem nội dung nào sẽ được ghi vào thiết bị đầu cuối, nhưng các lỗi không còn hiển thị nữa.

Một vài lần chạy nữa để đảm bảo rằng tôi không thể tái tạo lỗi mà không cần nỗ lực thêm và tôi bắt đầu tìm cách thay đổi tập lệnh này và làm cho nó vẫn báo lỗi.

Tập lệnh có thể ghi đè STDOUT không (sử dụng > &-)? Hoặc STDERR? Cuối cùng thì cả hai đều không có tác dụng.

Rõ ràng systemd bằng cách nào đó sửa đổi môi trường khởi động, nhưng bằng cách nào và tại sao?
Tôi mở vim và chỉnh sửa varnishreload, thêm set -x ngay dưới Shebang, hy vọng rằng kết quả gỡ lỗi của tập lệnh sẽ làm sáng tỏ phần nào.

Tập tin đã được sửa nên tôi tải lại vecni và thấy rằng sự thay đổi đã phá vỡ hoàn toàn mọi thứ... Ống xả hoàn toàn là một mớ hỗn độn, trong đó có hàng tấn mã giống C. Ngay cả việc cuộn trong terminal cũng không đủ để tìm nơi nó bắt đầu. Tôi hoàn toàn bối rối. Chế độ gỡ lỗi có thể ảnh hưởng đến hoạt động của các chương trình được khởi chạy trong tập lệnh không? Không, thật vớ vẩn. Lỗi trong vỏ? Một số tình huống có thể xảy ra đang chạy đua trong đầu tôi như những con gián theo nhiều hướng khác nhau. Cốc đồ uống có chứa caffein ngay lập tức được uống cạn, bạn nhanh chóng vào bếp để bổ sung lượng dự trữ và… chúng ta bắt đầu. Tôi mở tập lệnh và xem xét kỹ hơn về shebang: #!/bin/sh.

/bin/sh - đây chỉ là một liên kết tượng trưng đến bash, vì vậy tập lệnh được diễn giải ở chế độ tương thích POSIX, phải không? Không phải vậy! Shell mặc định trên Debian là dấu gạch ngang và nó trông giống hệt như vậy. đề cập đến /bin/sh.

# ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 24  2017 /bin/sh -> dash

Để thử nghiệm, tôi đã thay đổi shebang thành #!/bin/bash, đã xóa set -x và thử lại. Cuối cùng, khi khởi động lại véc ni sau đó, một lỗi có thể chấp nhận được đã xuất hiện ở đầu ra:

Jan 01 12:00:00 hostname varnishreload[32604]: /usr/sbin/varnishreload: line 124: echo: write error: Broken pipe
Jan 01 12:00:00 hostname varnishreload[32604]: VCL 'reload_20190101_120000_32604' compiled

Dòng 124, đây rồi!

114 find_vcl_file() {
115         VCL_SHOW=$(varnishadm vcl.show -v "$VCL_NAME" 2>&1) || :
116         VCL_FILE=$(
117                 echo "$VCL_SHOW" |
118                 awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}' | {
119                         # all this ceremony to handle blanks in FILE
120                         read -r DELIM VCL_SHOW INDEX SIZE FILE
121                         echo "$FILE"
122                 }
123         ) || :
124
125         if [ -z "$VCL_FILE" ]
126         then
127                 echo "$VCL_SHOW" >&2
128                 fail "failed to get the VCL file name"
129         fi
130
131         echo "$VCL_FILE"
132 }

Nhưng hóa ra, dòng 124 khá trống rỗng và không có gì đáng quan tâm. Tôi chỉ có thể cho rằng lỗi xảy ra như một phần của chuỗi nhiều dòng bắt đầu từ dòng 116.
Điều gì cuối cùng được ghi vào biến? VCL_FILE là kết quả của việc thực thi shell con ở trên?

Lúc đầu, nó gửi nội dung của biến VLC_SHOW, được tạo trên dòng 115, theo lệnh thông qua đường ống. Và sau đó điều gì xảy ra ở đó?

Thứ nhất, nó được sử dụng ở đó varnishadm, là một phần của gói cài đặt véc ni, để thiết lập véc ni mà không cần khởi động lại.

Đội phụ vcl.show -v được sử dụng để xuất toàn bộ cấu hình VCL được chỉ định trong ${VCL_NAME}, tới STDOUT.

Để hiển thị cấu hình VCL đang hoạt động hiện tại, cũng như một số phiên bản trước đó của cấu hình định tuyến véc ni vẫn còn trong bộ nhớ, bạn có thể sử dụng lệnh varnishadm vcl.list, đầu ra của nó sẽ tương tự như bên dưới:

discarded   cold/busy       1 reload_20190101_120000_11903
discarded   cold/busy       2 reload_20190101_120000_12068
discarded   cold/busy       16 reload_20190101_120000_12259
discarded   cold/busy       16 reload_20190101_120000_12299
discarded   cold/busy       28 reload_20190101_120000_12357
active      auto/warm       32 reload_20190101_120000_12397
available   auto/warm       0 reload_20190101_120000_12587

Giá trị biến ${VCL_NAME} được cài đặt trong một phần khác của tập lệnh varnishreload vào tên của VCL hiện đang hoạt động, nếu có. Trong trường hợp này nó sẽ là “reload_20190101_120000_12397”.

Tuyệt vời, có thể thay đổi ${VCL_SHOW} chứa cấu hình hoàn chỉnh cho sơn bóng, hiện tại đã rõ ràng. Bây giờ tôi cuối cùng đã hiểu tại sao đầu ra dấu gạch ngang là set -x hóa ra lại bị hỏng - nó bao gồm nội dung của cấu hình kết quả.

Điều quan trọng là phải hiểu rằng cấu hình VCL hoàn chỉnh thường có thể được ghép lại với nhau từ nhiều tệp. Nhận xét kiểu C được sử dụng để xác định vị trí các tệp cấu hình nhất định đã được đưa vào các tệp khác và đó chính là nội dung của dòng mã sau đây.
Cú pháp nhận xét mô tả các tệp được bao gồm có định dạng sau:

// VCL.SHOW <NUM> <NUM> <FILENAME>

Các con số không quan trọng trong bối cảnh này, chúng tôi quan tâm đến tên tệp.

Điều gì cuối cùng sẽ xảy ra trong đống lệnh bắt đầu từ dòng 116?
Hãy đối mặt với nó.
Đội gồm có bốn phần:

  1. Đơn giản echo, in ra giá trị của biến ${VCL_SHOW}
    echo "$VCL_SHOW"
  2. awk, tìm kiếm một dòng (bản ghi) trong đó trường đầu tiên, sau khi ngắt văn bản, là “//”, và trường thứ hai là “VCL.SHOW”.
    Awk sẽ viết ra dòng đầu tiên khớp với các mẫu này và sau đó dừng xử lý ngay lập tức.

    awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}'
  3. Một khối mã lưu trữ các giá trị trường thành năm biến, cách nhau bằng dấu cách. Biến FILE thứ năm nhận phần còn lại của dòng. Cuối cùng, tiếng vang cuối cùng ghi ra nội dung của biến ${FILE}.
    { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" }
  4. Vì tất cả các bước từ 1 đến 3 được đặt trong một lớp con nên xuất ra giá trị $FILE sẽ được ghi vào một biến VCL_FILE.

Như nhận xét ở dòng 119 gợi ý, điều này phục vụ mục đích duy nhất là xử lý một cách đáng tin cậy các trường hợp trong đó VCL sẽ tham chiếu các tệp có dấu cách trong tên của chúng.

Tôi đã nhận xét logic xử lý ban đầu cho ${VCL_FILE} và cố gắng thay đổi trình tự lệnh, nhưng không dẫn đến điều gì. Mọi thứ đều ổn với tôi, nhưng khi tôi khởi động dịch vụ thì nó báo lỗi.

Có vẻ như lỗi đơn giản là không thể tái tạo khi chạy tập lệnh theo cách thủ công, trong khi 30 phút được cho là đã hết sáu lần và ngoài ra, một nhiệm vụ có mức độ ưu tiên cao hơn đã xuất hiện, đẩy các vấn đề khác sang một bên. Phần còn lại của tuần tràn ngập nhiều nhiệm vụ khác nhau và chỉ bị pha loãng một chút bởi một báo cáo về sed và một cuộc phỏng vấn với một ứng viên. Vấn đề về lỗi trong varnishreload đã bị mất đi một cách không thể cứu vãn được trong cát bụi của thời gian.

Cái gọi là sed-fu của bạn... thực ra là... rác rưởi

Tuần tiếp theo, tôi có một ngày khá rảnh nên quyết định xử lý lại tấm vé này. Tôi hy vọng rằng trong não mình, một quá trình nền tảng nào đó đã luôn tìm kiếm giải pháp cho vấn đề này và lần này tôi chắc chắn sẽ hiểu chuyện gì đang xảy ra.

Vì lần trước chỉ thay đổi mã cũng không giúp ích được gì nên tôi quyết định viết lại nó bắt đầu từ dòng 116. Trong mọi trường hợp, mã hiện tại thật ngu ngốc. Và hoàn toàn không có nhu cầu sử dụng nó read.

Nhìn lại lỗi:
sh: echo: broken pipe — echo xuất hiện ở hai vị trí trong lệnh này, nhưng tôi nghi ngờ rằng vị trí đầu tiên có nhiều khả năng là thủ phạm hơn (hoặc ít nhất là đồng phạm). Awk cũng không truyền cảm hứng cho sự tự tin. Và trong trường hợp nó thực sự là như vậy awk | {read; echo} thiết kế dẫn đến tất cả những vấn đề này, tại sao không thay thế nó? Lệnh một dòng này không sử dụng tất cả các tính năng của awk và thậm chí cả tính năng bổ sung này read Ngoài ra.

Từ tuần trước đã có báo cáo về sed, tôi muốn thử những kỹ năng mới học được của mình và đơn giản hóa echo | awk | { read; echo} thành một cách dễ hiểu hơn echo | sed. Mặc dù đây chắc chắn không phải là cách tiếp cận tốt nhất để xác định lỗi nhưng tôi nghĩ ít nhất tôi nên thử sed-fu của mình và có thể tìm hiểu điều gì đó mới về vấn đề này. Trong quá trình thực hiện, tôi đã nhờ đồng nghiệp của mình, tác giả của bài nói chuyện về sed, giúp tôi nghĩ ra một tập lệnh sed hiệu quả hơn.

Tôi đã bỏ nội dung varnishadm vcl.show -v "$VCL_NAME" vào một tệp, vì vậy tôi có thể tập trung vào việc viết tập lệnh sed mà không gặp bất kỳ rắc rối nào khi khởi động lại dịch vụ.

Một mô tả ngắn về chính xác cách đầu vào của quy trình sed có thể được tìm thấy trong hướng dẫn sử dụng GNU của anh ấy. Trong các nguồn sed, biểu tượng n được chỉ định rõ ràng làm dấu phân cách dòng.

Trong một số lần thực hiện và với sự giới thiệu của đồng nghiệp, chúng tôi đã viết một tập lệnh sed cho kết quả tương tự như toàn bộ dòng 116 ban đầu.

Dưới đây là một tệp mẫu với dữ liệu đầu vào:

> cat vcl-example.vcl
Text
// VCL.SHOW 0 1578 file with 3 spaces.vcl
More text
// VCL.SHOW 0 1578 file.vcl
Even more text
// VCL.SHOW 0 1578 file with TWOspaces.vcl
Final text

Điều này có thể không rõ ràng từ mô tả ở trên, nhưng chúng tôi chỉ quan tâm đến nhận xét đầu tiên // VCL.SHOWvà có thể có một vài trong số chúng trong dữ liệu đầu vào. Đây là lý do tại sao awk ban đầu kết thúc sau trận đấu đầu tiên.

# шаг первый, вывести только строки с комментариями
# используя возможности sed, определяется символ-разделитель с помощью конструкции '#' вместо обычно используемого '/', за счёт этого не придётся экранировать косые в искомом комментарии
# определяется регулярное выражение “// VCL.SHOW”, для поиска строк с определенным шаблоном
# флаг -n позаботится о том, чтобы sed не выводил все входные данные, как он это делает по умолчанию (см. ссылку выше)
# -E позволяет использовать расширенные регулярные выражения
> cat vcl-processor-1.sed
#// VCL.SHOW#p
> sed -En -f vcl-processor-1.sed vcl-example.vcl
// VCL.SHOW 0 1578 file with 3 spaces.vcl
// VCL.SHOW 0 1578 file.vcl
// VCL.SHOW 0 1578 file with TWOspaces.vcl

# шаг второй, вывести только имя файла
# используя команду “substitute”, с группами внутри регулярных выражений, отображается только нужная группa
# и это делается только для совпадений, ранее описанного поиска
> cat vcl-processor-2.sed
#// VCL.SHOW# {
    s#.* [0-9]+ [0-9]+ (.*)$#1#
    p
}
> sed -En -f vcl-processor-2.sed vcl-example.vcl
file with 3 spaces.vcl
file.vcl
file with TWOspaces.vcl

# шаг третий, получить только первый из результатов
# как и в случае с awk, добавляется немедленное завершения после печати первого найденного совпадения
> cat vcl-processor-3.sed
#// VCL.SHOW# {
    s#.* [0-9]+ [0-9]+ (.*)$#1#
    p
    q
}
> sed -En -f vcl-processor-3.sed vcl-example.vcl
file with 3 spaces.vcl

# шаг четвертый, схлопнуть всё в однострочник, используя двоеточия для разделения команд
> sed -En -e '#// VCL.SHOW#{s#.* [0-9]+ [0-9]+ (.*)$#1#p;q;}' vcl-example.vcl
file with 3 spaces.vcl

Vì vậy, nội dung của tập lệnh Varnishreload sẽ trông giống như thế này:

VCL_FILE="$(echo "$VCL_SHOW" | sed -En '#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#1#p;q;};')"

Logic trên có thể được diễn đạt ngắn gọn như sau:
Nếu chuỗi khớp với biểu thức chính quy // VCL.SHOW, sau đó tham lam đọc ngấu nghiến văn bản bao gồm cả hai số trong dòng này và lưu lại mọi thứ còn lại sau thao tác này. Phát ra giá trị được lưu trữ và kết thúc chương trình.

Đơn giản phải không?

Chúng tôi hài lòng với tập lệnh sed và thực tế là nó đã thay thế tất cả mã gốc. Tất cả các thử nghiệm của tôi đều cho kết quả như mong muốn, vì vậy tôi đã thay đổi “varnishreload” trên máy chủ và chạy lại systemctl reload varnish. Sai lầm tồi tệ echo: write error: Broken pipe lại cười vào mặt chúng tôi. Con trỏ nhấp nháy đang chờ lệnh mới được nhập vào khoảng trống tối tăm của thiết bị đầu cuối...

Nguồn: www.habr.com

Thêm một lời nhận xét