6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Qua nhiều năm sử dụng Kubernetes trong sản xuất, chúng tôi đã tích lũy được nhiều câu chuyện thú vị về cách lỗi trong các thành phần hệ thống khác nhau dẫn đến những hậu quả khó chịu và/hoặc khó hiểu ảnh hưởng đến hoạt động của container và pod. Trong bài viết này, chúng tôi đã lựa chọn một số cái phổ biến nhất hoặc thú vị nhất. Kể cả khi bạn không bao giờ may mắn gặp phải những tình huống như vậy thì việc đọc những truyện trinh thám ngắn như vậy - đặc biệt là “tận mắt” - vẫn luôn thú vị phải không?..

Câu chuyện 1. Supercronic và Docker bị treo

Trên một trong các cụm, chúng tôi định kỳ nhận được một Docker bị đóng băng, điều này cản trở hoạt động bình thường của cụm. Đồng thời, những điều sau đây đã được quan sát thấy trong nhật ký Docker:

level=error msg="containerd: start init process" error="exit status 2: "runtime/cgo: pthread_create failed: No space left on device
SIGABRT: abort
PC=0x7f31b811a428 m=0

goroutine 0 [idle]:

goroutine 1 [running]:
runtime.systemstack_switch() /usr/local/go/src/runtime/asm_amd64.s:252 fp=0xc420026768 sp=0xc420026760
runtime.main() /usr/local/go/src/runtime/proc.go:127 +0x6c fp=0xc4200267c0 sp=0xc420026768
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1 fp=0xc4200267c8 sp=0xc4200267c0

goroutine 17 [syscall, locked to thread]:
runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2086 +0x1

…

Điều khiến chúng tôi quan tâm nhất về lỗi này là thông báo: pthread_create failed: No space left on device. Học nhanh tài liệu giải thích rằng Docker không thể phân nhánh một quy trình, đó là lý do tại sao nó bị đóng băng định kỳ.

Trong quá trình giám sát, hình ảnh sau đây tương ứng với những gì đang xảy ra:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Một tình huống tương tự được quan sát thấy trên các nút khác:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Tại cùng một nút, chúng ta thấy:

root@kube-node-1 ~ # ps auxfww | grep curl -c
19782
root@kube-node-1 ~ # ps auxfww | grep curl | head
root     16688  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root     17398  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root     16852  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root      9473  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root      4664  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root     30571  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root     24113  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root     16475  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root      7176  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>
root      1090  0.0  0.0      0     0 ?        Z    Feb06   0:00      |       _ [curl] <defunct>

Hóa ra hành vi này là hậu quả của việc nhóm làm việc với siêu kinh niên (một tiện ích Go mà chúng tôi sử dụng để chạy các công việc định kỳ trong nhóm):

 _ docker-containerd-shim 833b60bb9ff4c669bb413b898a5fd142a57a21695e5dc42684235df907825567 /var/run/docker/libcontainerd/833b60bb9ff4c669bb413b898a5fd142a57a21695e5dc42684235df907825567 docker-runc
|   _ /usr/local/bin/supercronic -json /crontabs/cron
|       _ /usr/bin/newrelic-daemon --agent --pidfile /var/run/newrelic-daemon.pid --logfile /dev/stderr --port /run/newrelic.sock --tls --define utilization.detect_aws=true --define utilization.detect_azure=true --define utilization.detect_gcp=true --define utilization.detect_pcf=true --define utilization.detect_docker=true
|       |   _ /usr/bin/newrelic-daemon --agent --pidfile /var/run/newrelic-daemon.pid --logfile /dev/stderr --port /run/newrelic.sock --tls --define utilization.detect_aws=true --define utilization.detect_azure=true --define utilization.detect_gcp=true --define utilization.detect_pcf=true --define utilization.detect_docker=true -no-pidfile
|       _ [newrelic-daemon] <defunct>
|       _ [curl] <defunct>
|       _ [curl] <defunct>
|       _ [curl] <defunct>
…

Vấn đề là thế này: khi một tác vụ được chạy ở chế độ siêu thời gian, quy trình được tạo ra bởi nó không thể chấm dứt chính xác, trở thành thây ma.

Ghi: Nói chính xác hơn, các tiến trình được sinh ra bởi các tác vụ cron, nhưng supercronic không phải là một hệ thống init và không thể “áp dụng” các tiến trình mà các tiến trình con của nó sinh ra. Khi tín hiệu SIGHUP hoặc SIGTERM được nâng lên, chúng không được chuyển sang các tiến trình con, dẫn đến các tiến trình con không kết thúc và vẫn ở trạng thái zombie. Bạn có thể đọc thêm về tất cả điều này, ví dụ, trong một bài viết như vậy.

Có một số cách để giải quyết vấn đề:

  1. Là giải pháp tạm thời - tăng số lượng PID trong hệ thống tại một thời điểm:
           /proc/sys/kernel/pid_max (since Linux 2.5.34)
                  This file specifies the value at which PIDs wrap around (i.e., the value in this file is one greater than the maximum PID).  PIDs greater than this  value  are  not  allo‐
                  cated;  thus, the value in this file also acts as a system-wide limit on the total number of processes and threads.  The default value for this file, 32768, results in the
                  same range of PIDs as on earlier kernels
  2. Hoặc khởi chạy các tác vụ trong supercronic không trực tiếp mà sử dụng cùng một tini, có thể chấm dứt các tiến trình một cách chính xác và không sinh ra zombie.

Câu chuyện 2. “Zombies” khi xóa cgroup

Kubelet bắt đầu tiêu tốn rất nhiều CPU:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Sẽ không ai thích điều này nên chúng tôi tự trang bị vũ khí cho mình perf và bắt đầu giải quyết vấn đề. Kết quả điều tra như sau:

  • Kubelet dành hơn một phần ba thời gian CPU để lấy dữ liệu bộ nhớ từ tất cả các nhóm:

    6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

  • Trong danh sách gửi thư của nhà phát triển kernel, bạn có thể tìm thấy thảo luận về vấn đề. Nói tóm lại, vấn đề nằm ở chỗ này: nhiều tệp tmpfs khác nhau và những thứ tương tự khác không bị xóa hoàn toàn khỏi hệ thống khi xóa một cgroup, cái gọi là memcg zombie. Sớm hay muộn chúng sẽ bị xóa khỏi bộ đệm của trang, nhưng có rất nhiều bộ nhớ trên máy chủ và kernel không thấy việc lãng phí thời gian để xóa chúng là có ý nghĩa. Đó là lý do tại sao họ cứ chồng chất lên nhau. Tại sao điều này lại xảy ra? Đây là một máy chủ có các công việc định kỳ liên tục tạo ra các công việc mới và cùng với đó là các nhóm mới. Do đó, các nhóm mới được tạo cho các vùng chứa trong đó và sẽ sớm bị xóa.
  • Tại sao cAdvisor trong kubelet lại lãng phí nhiều thời gian như vậy? Điều này dễ dàng nhận thấy với cách thực hiện đơn giản nhất time cat /sys/fs/cgroup/memory/memory.stat. Nếu trên một máy khỏe, thao tác mất 0,01 giây thì trên cron02 có vấn đề, thao tác này mất 1,2 giây. Vấn đề là cAdvisor, đọc dữ liệu từ sysfs rất chậm, cố gắng tính đến bộ nhớ được sử dụng trong các nhóm zombie.
  • Để loại bỏ zombie một cách mạnh mẽ, chúng tôi đã thử xóa bộ nhớ đệm như được đề xuất trong LKML: sync; echo 3 > /proc/sys/vm/drop_caches, - nhưng kernel hóa ra phức tạp hơn và làm hỏng xe.

Phải làm gì? Sự cố đang được khắc phục (làmvà để biết mô tả, hãy xem tin nhắn phát hành) cập nhật nhân Linux lên phiên bản 4.16.

Lịch sử 3. Systemd và mount của nó

Một lần nữa, kubelet đang tiêu tốn quá nhiều tài nguyên trên một số nút, nhưng lần này nó lại tiêu tốn quá nhiều bộ nhớ:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Hóa ra có một vấn đề trong systemd được sử dụng trong Ubuntu 16.04 và nó xảy ra khi quản lý các mount được tạo để kết nối subPath từ ConfigMap hoặc bí mật. Sau khi nhóm đã hoàn thành công việc của mình dịch vụ systemd và dịch vụ gắn kết của nó vẫn còn trong hệ thống. Theo thời gian, một số lượng lớn trong số họ tích lũy. Thậm chí có những vấn đề về chủ đề này:

  1. #5916;
  2. kubernetes #57345.

... cái cuối cùng đề cập đến PR trong systemd: #7811 (vấn đề trong systemd - #7798).

Vấn đề không còn tồn tại trong Ubuntu 18.04 nhưng nếu bạn muốn tiếp tục sử dụng Ubuntu 16.04, bạn có thể thấy cách giải quyết của chúng tôi về chủ đề này hữu ích.

Vì vậy, chúng tôi đã tạo DaemonSet sau:

---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  labels:
    app: systemd-slices-cleaner
  name: systemd-slices-cleaner
  namespace: kube-system
spec:
  updateStrategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: systemd-slices-cleaner
  template:
    metadata:
      labels:
        app: systemd-slices-cleaner
    spec:
      containers:
      - command:
        - /usr/local/bin/supercronic
        - -json
        - /app/crontab
        Image: private-registry.org/systemd-slices-cleaner/systemd-slices-cleaner:v0.1.0
        imagePullPolicy: Always
        name: systemd-slices-cleaner
        resources: {}
        securityContext:
          privileged: true
        volumeMounts:
        - name: systemd
          mountPath: /run/systemd/private
        - name: docker
          mountPath: /run/docker.sock
        - name: systemd-etc
          mountPath: /etc/systemd
        - name: systemd-run
          mountPath: /run/systemd/system/
        - name: lsb-release
          mountPath: /etc/lsb-release-host
      imagePullSecrets:
      - name: antiopa-registry
      priorityClassName: cluster-low
      tolerations:
      - operator: Exists
      volumes:
      - name: systemd
        hostPath:
          path: /run/systemd/private
      - name: docker
        hostPath:
          path: /run/docker.sock
      - name: systemd-etc
        hostPath:
          path: /etc/systemd
      - name: systemd-run
        hostPath:
          path: /run/systemd/system/
      - name: lsb-release
        hostPath:
          path: /etc/lsb-release

... và nó sử dụng đoạn script sau:

#!/bin/bash

# we will work only on xenial
hostrelease="/etc/lsb-release-host"
test -f ${hostrelease} && grep xenial ${hostrelease} > /dev/null || exit 0

# sleeping max 30 minutes to dispense load on kube-nodes
sleep $((RANDOM % 1800))

stoppedCount=0
# counting actual subpath units in systemd
countBefore=$(systemctl list-units | grep subpath | grep "run-" | wc -l)
# let's go check each unit
for unit in $(systemctl list-units | grep subpath | grep "run-" | awk '{print $1}'); do
  # finding description file for unit (to find out docker container, who born this unit)
  DropFile=$(systemctl status ${unit} | grep Drop | awk -F': ' '{print $2}')
  # reading uuid for docker container from description file
  DockerContainerId=$(cat ${DropFile}/50-Description.conf | awk '{print $5}' | cut -d/ -f6)
  # checking container status (running or not)
  checkFlag=$(docker ps | grep -c ${DockerContainerId})
  # if container not running, we will stop unit
  if [[ ${checkFlag} -eq 0 ]]; then
    echo "Stopping unit ${unit}"
    # stoping unit in action
    systemctl stop $unit
    # just counter for logs
    ((stoppedCount++))
    # logging current progress
    echo "Stopped ${stoppedCount} systemd units out of ${countBefore}"
  fi
done

... và nó chạy cứ 5 phút một lần bằng cách sử dụng supercronic đã đề cập trước đó. Dockerfile của nó trông như thế này:

FROM ubuntu:16.04
COPY rootfs /
WORKDIR /app
RUN apt-get update && 
    apt-get upgrade -y && 
    apt-get install -y gnupg curl apt-transport-https software-properties-common wget
RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable" && 
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && 
    apt-get update && 
    apt-get install -y docker-ce=17.03.0*
RUN wget https://github.com/aptible/supercronic/releases/download/v0.1.6/supercronic-linux-amd64 -O 
    /usr/local/bin/supercronic && chmod +x /usr/local/bin/supercronic
ENTRYPOINT ["/bin/bash", "-c", "/usr/local/bin/supercronic -json /app/crontab"]

Câu chuyện 4. Tính cạnh tranh khi lập lịch nhóm

Người ta nhận thấy rằng: nếu chúng ta có một nhóm được đặt trên một nút và hình ảnh của nó được bơm ra trong một thời gian rất dài, thì một nhóm khác “đánh” vào cùng một nút sẽ chỉ đơn giản là không bắt đầu kéo hình ảnh của nhóm mới. Thay vào đó, nó đợi cho đến khi hình ảnh của nhóm trước đó được kéo. Do đó, một nhóm đã được lên lịch và hình ảnh của nó có thể được tải xuống chỉ sau một phút sẽ chuyển sang trạng thái containerCreating.

Các sự kiện sẽ trông giống như thế này:

Normal  Pulling    8m    kubelet, ip-10-241-44-128.ap-northeast-1.compute.internal  pulling image "registry.example.com/infra/openvpn/openvpn:master"

Nó chỉ ra rằng một hình ảnh từ sổ đăng ký chậm có thể chặn việc triển khai mỗi nút.

Thật không may, không có nhiều cách để thoát khỏi tình huống này:

  1. Cố gắng sử dụng Docker Đăng ký trực tiếp trong cụm hoặc trực tiếp với cụm (ví dụ: GitLab Đăng ký, Nexus, v.v.);
  2. Sử dụng các tiện ích như Kraken.

Câu chuyện 5. Các nút bị treo do thiếu bộ nhớ

Trong quá trình vận hành các ứng dụng khác nhau, chúng tôi cũng gặp phải tình huống một nút hoàn toàn không thể truy cập được: SSH không phản hồi, tất cả các trình nền giám sát đều ngừng hoạt động và sau đó không có (hoặc gần như không có gì) bất thường trong nhật ký.

Tôi sẽ cho bạn biết bằng hình ảnh bằng ví dụ về một nút nơi MongoDB hoạt động.

Trên đỉnh trông như thế này để tai nạn:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Và như thế này - sau khi tai nạn:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Trong quá trình giám sát, cũng có một bước nhảy vọt, tại đó nút không còn khả dụng:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

Vì vậy, từ ảnh chụp màn hình có thể thấy rõ rằng:

  1. RAM trên máy gần hết;
  2. Mức tiêu thụ RAM tăng vọt, sau đó quyền truy cập vào toàn bộ máy bị vô hiệu hóa đột ngột;
  3. Một tác vụ lớn xuất hiện trên Mongo, buộc quy trình DBMS sử dụng nhiều bộ nhớ hơn và chủ động đọc từ đĩa.

Hóa ra là nếu Linux hết bộ nhớ trống (áp lực bộ nhớ tăng lên) và không có trao đổi nào, thì để Khi kẻ diệt OOM xuất hiện, hành động cân bằng có thể nảy sinh giữa việc ném các trang vào bộ đệm trang và ghi chúng trở lại đĩa. Việc này được thực hiện bởi kswapd, giải phóng càng nhiều trang bộ nhớ càng tốt cho lần phân phối tiếp theo.

Thật không may, với tải I/O lớn cùng với lượng bộ nhớ trống nhỏ, kswapd trở thành nút cổ chai của toàn bộ hệ thống, bởi vì họ bị ràng buộc với nó tất cả phân bổ (lỗi trang) của các trang bộ nhớ trong hệ thống. Điều này có thể tiếp diễn trong một thời gian rất dài nếu các tiến trình không muốn sử dụng bộ nhớ nữa mà được cố định ở rìa vực thẳm của kẻ giết người OOM.

Câu hỏi đương nhiên là: tại sao sát thủ OOM lại đến muộn như vậy? Trong lần lặp lại hiện tại, trình diệt OOM cực kỳ ngu ngốc: nó sẽ chỉ giết tiến trình khi nỗ lực phân bổ trang bộ nhớ không thành công, tức là. nếu lỗi trang không thành công. Điều này không xảy ra trong một thời gian khá dài, bởi vì kswapd đã dũng cảm giải phóng các trang bộ nhớ, chuyển bộ đệm trang (trên thực tế là toàn bộ I/O đĩa trong hệ thống) trở lại đĩa. Chi tiết hơn, với phần mô tả các bước cần thiết để loại bỏ những vấn đề như vậy trong kernel, bạn có thể đọc đây.

Hành vi này Nên cải thiện với nhân Linux 4.6+.

Câu chuyện 6. Pod bị kẹt ở trạng thái Chờ xử lý

Ở một số cụm, trong đó thực sự có rất nhiều nhóm đang hoạt động, chúng tôi bắt đầu nhận thấy rằng hầu hết chúng đều “treo” trong trạng thái rất lâu. Pending, mặc dù bản thân các vùng chứa Docker đã chạy trên các nút và có thể được xử lý theo cách thủ công.

Hơn nữa, trong describe không có gì sai:

  Type    Reason                  Age                From                     Message
  ----    ------                  ----               ----                     -------
  Normal  Scheduled               1m                 default-scheduler        Successfully assigned sphinx-0 to ss-dev-kub07
  Normal  SuccessfulAttachVolume  1m                 attachdetach-controller  AttachVolume.Attach succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
  Normal  SuccessfulMountVolume   1m                 kubelet, ss-dev-kub07    MountVolume.SetUp succeeded for volume "sphinx-config"
  Normal  SuccessfulMountVolume   1m                 kubelet, ss-dev-kub07    MountVolume.SetUp succeeded for volume "default-token-fzcsf"
  Normal  SuccessfulMountVolume   49s (x2 over 51s)  kubelet, ss-dev-kub07    MountVolume.SetUp succeeded for volume "pvc-6aaad34f-ad10-11e8-a44c-52540035a73b"
  Normal  Pulled                  43s                kubelet, ss-dev-kub07    Container image "registry.example.com/infra/sphinx-exporter/sphinx-indexer:v1" already present on machine
  Normal  Created                 43s                kubelet, ss-dev-kub07    Created container
  Normal  Started                 43s                kubelet, ss-dev-kub07    Started container
  Normal  Pulled                  43s                kubelet, ss-dev-kub07    Container image "registry.example.com/infra/sphinx/sphinx:v1" already present on machine
  Normal  Created                 42s                kubelet, ss-dev-kub07    Created container
  Normal  Started                 42s                kubelet, ss-dev-kub07    Started container

Sau khi tìm hiểu kỹ, chúng tôi đã đưa ra giả định rằng kubelet đơn giản là không có thời gian để gửi tất cả thông tin về trạng thái của nhóm và các bài kiểm tra mức độ sẵn sàng/hoạt động đến máy chủ API.

Và sau khi nghiên cứu trợ giúp, chúng tôi tìm thấy các thông số sau:

--kube-api-qps - QPS to use while talking with kubernetes apiserver (default 5)
--kube-api-burst  - Burst to use while talking with kubernetes apiserver (default 10) 
--event-qps - If > 0, limit event creations per second to this value. If 0, unlimited. (default 5)
--event-burst - Maximum size of a bursty event records, temporarily allows event records to burst to this number, while still not exceeding event-qps. Only used if --event-qps > 0 (default 10) 
--registry-qps - If > 0, limit registry pull QPS to this value.
--registry-burst - Maximum size of bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry-qps. Only used if --registry-qps > 0 (default 10)

Như đã thấy, giá trị mặc định khá nhỏ, và 90% chúng đáp ứng mọi nhu cầu... Tuy nhiên, trong trường hợp của chúng tôi, điều này là chưa đủ. Vì vậy, chúng tôi đặt các giá trị sau:

--event-qps=30 --event-burst=40 --kube-api-burst=40 --kube-api-qps=30 --registry-qps=30 --registry-burst=40

... và khởi động lại kubelets, sau đó chúng tôi thấy hình ảnh sau trong biểu đồ các lệnh gọi đến máy chủ API:

6 lỗi hệ thống giải trí trong hoạt động của Kubernetes [và giải pháp của chúng]

... và vâng, mọi thứ bắt đầu bay!

PS

Để được họ giúp đỡ trong việc thu thập lỗi và chuẩn bị bài viết này, tôi bày tỏ lòng biết ơn sâu sắc đến rất nhiều kỹ sư của công ty chúng tôi và đặc biệt là đồng nghiệp của tôi từ nhóm R&D Andrey Klimentyev (zuzz).

PPS

Đọc thêm trên blog của chúng tôi:

Nguồn: www.habr.com

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