مراقبة الأصوات بين مضيفي Kubernetes هي وصفتنا



عند تشخيص المشكلات في كتلة Kubernetes ، نلاحظ غالبًا أن إحدى العقد العنقودية تتلاشى * وبطبيعة الحال ، هذا أمر نادر الحدوث. لذلك توصلنا إلى الحاجة إلى أداة من شأنها إجراء اختبار ping من كل عقدة إلى كل عقدة وتقديم نتائج عملها في شكل مقاييس Prometheus . سيتعين علينا فقط رسم الرسوم البيانية في Grafana وتوطين العقدة الفاشلة بسرعة (وإذا لزم الأمر ، قم بإزالة جميع القرون منه ، ثم القيام بالعمل المقابل **) ...

* بواسطة "رذاذ" أفهم أن العقدة يمكن أن تذهب إلى حالة NotReady والعودة فجأة إلى العمل. أو ، على سبيل المثال ، قد لا يصل جزء من حركة المرور في pods إلى pods على العقد المجاورة.

** لماذا تنشأ مثل هذه الحالات؟ قد يكون أحد مشكلات الشبكة على التبديل في مركز البيانات أحد الأسباب الشائعة. على سبيل المثال ، بمجرد تكوين Hetzner ، قمنا بتكوين vswitch ، ولكن في لحظة رائعة توقفت عن الوصول إلى إحدى العقد على هذا المنفذ vswitch: ولهذا السبب ، تبين أن العقدة كانت غير قابلة للوصول تمامًا على الشبكة المحلية.

بالإضافة إلى ذلك ، نود أن نطلق هذه الخدمة مباشرة في Kubernetes ، بحيث يحدث النشر الكامل باستخدام تثبيت مخطط Helm. (توقع الأسئلة - في حالة استخدام Ansible نفسه ، سيتعين علينا كتابة الأدوار في بيئات مختلفة: AWS و GCE والمعادن العارية ...) وبعد قليل من البحث في الإنترنت عن أدوات جاهزة للمهمة ، لم نجد أي شيء مناسب. لذلك ، جعلوا خاصة بهم.

النصي والتكوينات


لذلك ، فإن المكون الرئيسي .status.addresses هو البرنامج النصي الذي يراقب التغيير في أي عقد في حقل .status.addresses ، وإذا كانت العقدة قد غيرت هذا الحقل (على سبيل المثال ، تمت إضافة عقدة جديدة) ، فإنها ترسل قيم Helm إلى المخطط باستخدام هذه قائمة العقد في شكل ConfigMap:

 --- apiVersion: v1 kind: ConfigMap metadata: name: node-ping-config namespace: kube-prometheus data: nodes.json: > {{ .Values.nodePing.nodes | toJson }} 

بيثون النصي نفسه:
 #!/usr/bin/env python3 import subprocess import prometheus_client import re import statistics import os import json import glob import better_exchook import datetime better_exchook.install() FPING_CMDLINE = "/usr/sbin/fping -p 1000 -A -C 30 -B 1 -q -r 1".split(" ") FPING_REGEX = re.compile(r"^(\S*)\s*: (.*)$", re.MULTILINE) CONFIG_PATH = "/config/nodes.json" registry = prometheus_client.CollectorRegistry() prometheus_exceptions_counter = \ prometheus_client.Counter('kube_node_ping_exceptions', 'Total number of exceptions', [], registry=registry) prom_metrics = {"sent": prometheus_client.Counter('kube_node_ping_packets_sent_total', 'ICMP packets sent', ['destination_node', 'destination_node_ip_address'], registry=registry), "received": prometheus_client.Counter( 'kube_node_ping_packets_received_total', 'ICMP packets received', ['destination_node', 'destination_node_ip_address'], registry=registry), "rtt": prometheus_client.Counter( 'kube_node_ping_rtt_milliseconds_total', 'round-trip time', ['destination_node', 'destination_node_ip_address'], registry=registry), "min": prometheus_client.Gauge('kube_node_ping_rtt_min', 'minimum round-trip time', ['destination_node', 'destination_node_ip_address'], registry=registry), "max": prometheus_client.Gauge('kube_node_ping_rtt_max', 'maximum round-trip time', ['destination_node', 'destination_node_ip_address'], registry=registry), "mdev": prometheus_client.Gauge('kube_node_ping_rtt_mdev', 'mean deviation of round-trip times', ['destination_node', 'destination_node_ip_address'], registry=registry)} def validate_envs(): envs = {"MY_NODE_NAME": os.getenv("MY_NODE_NAME"), "PROMETHEUS_TEXTFILE_DIR": os.getenv("PROMETHEUS_TEXTFILE_DIR"), "PROMETHEUS_TEXTFILE_PREFIX": os.getenv("PROMETHEUS_TEXTFILE_PREFIX")} for k, v in envs.items(): if not v: raise ValueError("{} environment variable is empty".format(k)) return envs @prometheus_exceptions_counter.count_exceptions() def compute_results(results): computed = {} matches = FPING_REGEX.finditer(results) for match in matches: ip = match.group(1) ping_results = match.group(2) if "duplicate" in ping_results: continue splitted = ping_results.split(" ") if len(splitted) != 30: raise ValueError("ping returned wrong number of results: \"{}\"".format(splitted)) positive_results = [float(x) for x in splitted if x != "-"] if len(positive_results) > 0: computed[ip] = {"sent": 30, "received": len(positive_results), "rtt": sum(positive_results), "max": max(positive_results), "min": min(positive_results), "mdev": statistics.pstdev(positive_results)} else: computed[ip] = {"sent": 30, "received": len(positive_results), "rtt": 0, "max": 0, "min": 0, "mdev": 0} if not len(computed): raise ValueError("regex match\"{}\" found nothing in fping output \"{}\"".format(FPING_REGEX, results)) return computed @prometheus_exceptions_counter.count_exceptions() def call_fping(ips): cmdline = FPING_CMDLINE + ips process = subprocess.run(cmdline, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) if process.returncode == 3: raise ValueError("invalid arguments: {}".format(cmdline)) if process.returncode == 4: raise OSError("fping reported syscall error: {}".format(process.stderr)) return process.stdout envs = validate_envs() files = glob.glob(envs["PROMETHEUS_TEXTFILE_DIR"] + "*") for f in files: os.remove(f) labeled_prom_metrics = [] while True: with open("/config/nodes.json", "r") as f: config = json.loads(f.read()) if labeled_prom_metrics: for node in config: if (node["name"], node["ipAddress"]) not in [(metric["node_name"], metric["ip"]) for metric in labeled_prom_metrics]: for k, v in prom_metrics.items(): v.remove(node["name"], node["ipAddress"]) labeled_prom_metrics = [] for node in config: metrics = {"node_name": node["name"], "ip": node["ipAddress"], "prom_metrics": {}} for k, v in prom_metrics.items(): metrics["prom_metrics"][k] = v.labels(node["name"], node["ipAddress"]) labeled_prom_metrics.append(metrics) out = call_fping([prom_metric["ip"] for prom_metric in labeled_prom_metrics]) computed = compute_results(out) for dimension in labeled_prom_metrics: result = computed[dimension["ip"]] dimension["prom_metrics"]["sent"].inc(computed[dimension["ip"]]["sent"]) dimension["prom_metrics"]["received"].inc(computed[dimension["ip"]]["received"]) dimension["prom_metrics"]["rtt"].inc(computed[dimension["ip"]]["rtt"]) dimension["prom_metrics"]["min"].set(computed[dimension["ip"]]["min"]) dimension["prom_metrics"]["max"].set(computed[dimension["ip"]]["max"]) dimension["prom_metrics"]["mdev"].set(computed[dimension["ip"]]["mdev"]) prometheus_client.write_to_textfile( envs["PROMETHEUS_TEXTFILE_DIR"] + envs["PROMETHEUS_TEXTFILE_PREFIX"] + envs["MY_NODE_NAME"] + ".prom", registry) 

يتم تشغيله على كل عقدة ويرسل حزم ICMP إلى جميع مثيلات نظام Kubernetes الأخرى مرتين في الثانية ، ويتم كتابة النتائج في الملفات النصية.

يتم تضمين البرنامج النصي في صورة Docker :

 FROM python:3.6-alpine3.8 COPY rootfs / WORKDIR /app RUN pip3 install --upgrade pip && pip3 install -r requirements.txt && apk add --no-cache fping ENTRYPOINT ["python3", "/app/node-ping.py"] 

بالإضافة إلى ذلك ، تم إنشاء ServiceAccount ودوره ، مما يسمح فقط باستلام قائمة العقد (لمعرفة عناوينهم):

 --- apiVersion: v1 kind: ServiceAccount metadata: name: node-ping namespace: kube-prometheus --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: kube-prometheus:node-ping rules: - apiGroups: [""] resources: ["nodes"] verbs: ["list"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: kube-prometheus:kube-node-ping subjects: - kind: ServiceAccount name: node-ping namespace: kube-prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kube-prometheus:node-ping 

أخيرًا ، تحتاج إلى DaemonSet ، والذي يعمل على كل مثيلات الكتلة:

 --- apiVersion: extensions/v1beta1 kind: DaemonSet metadata: name: node-ping namespace: kube-prometheus labels: tier: monitoring app: node-ping version: v1 spec: updateStrategy: type: RollingUpdate template: metadata: labels: name: node-ping spec: terminationGracePeriodSeconds: 0 tolerations: - operator: "Exists" serviceAccountName: node-ping priorityClassName: cluster-low containers: - resources: requests: cpu: 0.10 image: private-registry.flant.com/node-ping/node-ping-exporter:v1 imagePullPolicy: Always name: node-ping env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: PROMETHEUS_TEXTFILE_DIR value: /node-exporter-textfile/ - name: PROMETHEUS_TEXTFILE_PREFIX value: node-ping_ volumeMounts: - name: textfile mountPath: /node-exporter-textfile - name: config mountPath: /config volumes: - name: textfile hostPath: path: /var/run/node-exporter-textfile - name: config configMap: name: node-ping-config imagePullSecrets: - name: antiopa-registry 

السكتات الدماغية ملخص في الكلمات:

  • نتائج سيناريو Python - أي الملفات النصية الموضوعة على الجهاز المضيف في الدليل /var/run/node-exporter-textfile source-textfile ، تحصل على DaemonSet-source source. تشير الوسائط لتشغيله إلى --collector.textfile.directory /host/textfile ، حيث يكون /host/textfile هو hostPath على /var/run/node-exporter-textfile source /var/run/node-exporter-textfile . (يمكنك أن تقرأ عن جامع textfile في مصدر العقدة هنا .)
  • نتيجةً لذلك ، يقوم مصدر - العقدة بقراءة هذه الملفات ، ويقوم بروميثيوس بجمع كل البيانات من مصدر العقدة.

ماذا حدث؟


الآن - إلى النتيجة التي طال انتظارها. عندما يتم إنشاء هذه المقاييس ، يمكننا أن ننظر إليها ، وبطبيعة الحال ، رسم الرسوم البيانية البصرية. هذا هو كيف يبدو.

أولاً ، هناك كتلة عامة لها القدرة (باستخدام المحدد) لتحديد قائمة العقد التي يتم تنفيذ اختبار ping منها. هذا هو جدول الملخص لل ping بين العقد المحددة للفترة المحددة في لوحة القيادة Grafana:



وهنا الرسوم البيانية مع معلومات عامة عن العقد المحددة :



لدينا أيضًا قائمة بالأسطر ، كل منها عبارة عن رسم بياني لعقدة واحدة منفصلة عن محدد عقدة المصدر :



إذا قمت بتوسيع مثل هذا الخط ، فيمكنك الاطلاع على معلومات عن الأصوات من عقدة محددة إلى جميع العناصر الأخرى التي تم تحديدها في محدد العقد الوجهة :



هذه المعلومات في الرسوم البيانية:



أخيرًا ، كيف ستبدو الرسوم البيانية العزيزة عند استخدام ping الفقراء بين العقد؟





إذا لاحظت ذلك في بيئة حقيقية - فقد حان الوقت لمعرفة الأسباب.

PS


اقرأ أيضًا في مدونتنا:

Source: https://habr.com/ru/post/ar442798/


All Articles