Monitorar pings entre hosts Kubernetes é a nossa receita



Ao diagnosticar problemas em um cluster Kubernetes, geralmente observamos que algumas vezes um dos nós do cluster chove * e, é claro, isso é raro e estranho. Portanto, chegamos à necessidade de uma ferramenta que faça ping de cada nó para cada nó e apresente os resultados de seu trabalho na forma de métricas do Prometheus . Teríamos apenas que desenhar gráficos no Grafana e localizar rapidamente o nó com falha (e, se necessário, remover todos os pods dele e, em seguida, fazer o trabalho correspondente **) ...

* Por "chuvisco", entendo que o nó pode entrar no status NotReady e retornar repentinamente ao trabalho. Ou, por exemplo, parte do tráfego nos pods pode não alcançar os pods nos nós vizinhos.

** Por que essas situações surgem? Uma das causas comuns pode ser problemas de rede no comutador no data center. Por exemplo, uma vez na Hetzner, configuramos o vswitch, mas em um momento maravilhoso um dos nós deixou de estar acessível nessa porta vswitch: por causa disso, verificou-se que o nó estava completamente inacessível na rede local.

Além disso, gostaríamos de lançar esse serviço diretamente no Kubernetes , para que toda a implantação ocorra usando a instalação do Helm-chart. (Antecipando perguntas - se usássemos o mesmo Ansible, teríamos que escrever funções para vários ambientes: AWS, GCE, bare metal ...) Depois de pesquisar um pouco na Internet por ferramentas prontas para a tarefa, não encontramos nada adequado. Portanto, eles fizeram os seus próprios.

Script e configurações


Portanto, o principal componente de nossa solução é um script que monitora a alteração em qualquer nó do campo .status.addresses e, se um nó alterou esse campo (ou seja, um novo nó foi adicionado), envia valores Helm para o gráfico usando esta lista de nós no formato do ConfigMap:

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

Script Python em si:
 #!/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) 

Ele é executado em cada nó e envia pacotes ICMP para todas as outras instâncias do cluster Kubernetes 2 vezes por segundo, e os resultados são gravados nos arquivos de texto.

O script está incluído na imagem do 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"] 

Além disso, uma ServiceAccount foi criada e uma função para ela, que permite que apenas uma lista de nós seja recebida (para conhecer seus endereços):

 --- 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 

Por fim, você precisa do DaemonSet , que é executado em todas as instâncias do cluster:

 --- 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 

Resumo de traços em palavras:

  • Resultados do script Python - ou seja, os arquivos de texto colocados na máquina host no diretório /var/run/node-exporter-textfile textfile entram no DaemonSet node-exportador. Os argumentos para executá-lo indicam --collector.textfile.directory /host/textfile , em que /host/textfile é o hostPath em /var/run/node-exporter-textfile . (Você pode ler sobre o coletor de arquivos de texto no nó-exportador aqui .)
  • Como resultado, o nó-exportador lê esses arquivos e o Prometheus coleta todos os dados do nó-exportador.

O que aconteceu?


Agora - para o tão esperado resultado. Quando essas métricas foram criadas, podemos examiná-las e, é claro, desenhar gráficos visuais. É assim que parece.

Primeiramente, existe um bloco geral com a capacidade (usando o seletor) de selecionar uma lista de nós a partir dos quais o ping é executado e em qual. Esta é a tabela de resumo para o ping entre os nós selecionados para o período especificado no painel Grafana:



E aqui estão os gráficos com informações gerais sobre os nós selecionados :



Também temos uma lista de linhas, cada uma das quais é um gráfico para um nó separado do seletor do nó de origem :



Se você expandir essa linha, poderá ver informações sobre pings de um nó específico para todos os outros que foram selecionados no seletor de Nós de destino :



Esta informação está em gráficos:



Finalmente, como serão os gráficos estimados com ping baixo entre os nós?





Se você observar isso em um ambiente real - é hora de descobrir os motivos.

PS


Leia também em nosso blog:

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


All Articles