Kuberhealthy Check — проверьте соответствие меток NodeGroup

Это довольно специфический сценарий, который я написал для целей мониторинга Kubernetes Cluster (в частности, узлов). По сути, мне нужно убедиться, что все узлы в NodeGroup имеют одинаковые метки и значения меток, иначе они не будут равномерно масштабироваться для масштабирования узлов в версии Kubernetes, которую мы используем (Cluster Auto Scaler хочет, чтобы значения были равными. быть одинаковыми, чтобы одинаково обращаться с узлами).

Используется env var IN_CLUSTER, поэтому я могу указать, запускается ли сценарий с моего локального компьютера (который может читать конфигурацию kubectl) или он работает как контейнер в кластере (использовать разрешения RBAC).

Сценарий, который я написал, работает и делает то, что мне нужно — получить список узлов в кластере, выполнить итерацию по каждой группе узлов (есть четыре группы узлов — основная, общая, наблюдаемость, pci). Мы группируем узлы в соответствующие им группы узлов. Затем мы проверяем каждый узел в NodeGroup и сравниваем, чтобы убедиться, что метки совпадают.

Скрипт реализует клиент Kubernetes для получения списка узлов. Скрипт также реализует Kuberhealthy-клиент, который просто сообщает о результатах проверки (успех или неудача) мастеру Kuberhealthy.

Мне не нравится тот факт, что четыре группы NodeGroup жестко запрограммированы в скрипте, но я не могу придумать, как добиться того, чего я хочу, с массивом, хранящимся как env var.

Сценарий предназначен для простого запуска сверху вниз и прост. Я не уверен, что имеет смысл иметь if __name__ == '__main__' директиву, так как она никогда не будет импортирована как модуль.

from kubernetes import client, config
from kh_client import *
import os

# requires cluster role with permissions list, get nodes!
# needs refactoring, for time being have kept it as a 'top to bottom' script

def main():
    if os.getenv('IN_CLUSTER') == "TRUE":
        config.load_incluster_config()
    else:
        config.load_kube_config()
    
    try:
        api_instance = client.CoreV1Api()
        node_list = api_instance.list_node()
    except client.exceptions.ApiException:
        print("401 Unauthorised. Please check you are authenticated for the target cluster / have set the IN_CLUSTER env var.")
        exit(2)

    node_group_core = []
    node_group_general = []
    node_group_observability = []
    node_group_pci = []

    # print("%stt%s" % ("NAME", "LABELS"))
    # this needs changing but difficult to do with an env_var
    for node in node_list.items:
        if node.metadata.labels.get('nodegroup-name') == "core":
            node_group_core.append(node)
        if node.metadata.labels.get('nodegroup-name') == "general":
            node_group_general.append(node)
        if node.metadata.labels.get('nodegroup-name') == "observability":
            node_group_observability.append(node)
        if node.metadata.labels.get('nodegroup-name') == "pci":
            node_group_pci.append(node)

    check_node_group_labels(node_group_core)
    check_node_group_labels(node_group_general)
    check_node_group_labels(node_group_observability)
    check_node_group_labels(node_group_pci)

    # everything has checked successfully, report success. 
    print("Reporting Success.")
    try:
        report_success()
    except Exception as e:
        print(f"Error when reporting success: {e}")

def check_node_group_labels(node_group):
    # ignored labels taken from https://github.com/kubernetes/autoscaler/blob/3a69f118d95cd653bf101aecc0ea5e00bf7ba370/cluster-autoscaler/processors/nodegroupset/aws_nodegroups.go#L26
    # this can be refactored
    ignored_labels = [ "alpha.eksctl.io/instance-id", 
                       "alpha.eksctl.io/nodegroup-name", 
                       "eks.amazonaws.com/nodegroup", 
                       "k8s.amazonaws.com/eniConfig",
                       "lifecycle",
                       # labels i've added
                       "topology.kubernetes.io/zone",
                       "kubernetes.io/hostname",
                       "failure-domain.beta.kubernetes.io/zone" ]

    node_group_labels = []
    for l in node_group[0].metadata.labels:
        if l not in ignored_labels:
            node_group_labels.append(l)

    print(f"There are {len(node_group)} nodes in {node_group[0].metadata.labels.get('nodegroup-name')}")

    for label in node_group_labels:
        # compare against the 'benchmark' label, any difference means a mismatch as far as CAS is concerned
        # print(label)
        benchmark_label = node_group[0].metadata.labels.get(label)
        # print("benchmark label: ", benchmark_label)
        for node in node_group[1:]:
            # print("node label", node.metadata.labels.get(label))
            if node.metadata.labels.get(label) != benchmark_label:
                print("Reporting Failure.")
                try:
                    report_failure([f"Warning! label mismatch detected, for nodegroup and node {node.metadata.name}, benchmark value: {benchmark_label}, this node value: {node.metadata.labels.get(label)}"])
                except Exception as e:
                    print(f"Error when reporting failure: {e}")

if __name__ == '__main__':
    main()
```

1 ответ
1

Касательно:

Мне не нравится тот факт, что четыре группы NodeGroup жестко запрограммированы в скрипте, но я не могу придумать, как добиться того, чего я хочу, с массивом, хранящимся как env var.

Мы можем начать сначала с создания словаря со всеми node_groups а затем приступим к рефакторингу нашего кода:

NODE_GROUPS = {
    'core': [],
    'general': [],
    'observability': [],
    'pci': [],
}

Сделав это, мы удалим часть повторяющегося кода, который есть в main функция:

def main():
    # ...
    for node in node_list.items:
        nodegroup_name = node.metadata.labels.get('nodegroup-name')

        for node_group_name, nodes in NODE_GROUPS.items():
            if nodegroup_name == node_group_name:
                nodes.append(node)

    for node_group in NODE_GROUPS.values():
        check_node_group_labels(node_group)
    
    # ...

Теперь, сохраняя NODE_GROUPS в файл конфигурации — не лучшая идея, потому что новый формат нам не поможет. Если вы действительно хотите удалить группы узлов, я бы посоветовал вам использовать другой метод.


Я не уверен, что имеет смысл иметь if __name__ == '__main__'
директиву, так как она никогда не будет импортирована как модуль.

Эта защита представляет собой шаблонный код, который защищает пользователей от случайного вызова сценария, когда они не собирались этого делать. Вот некоторые типичные проблемы, возникающие при отсутствии защиты в скрипте:

  • Если вы импортируете скрипт без защиты в другой скрипт (например, import my_script_without_a_name_eq_main_guard), то второй сценарий вызовет запуск первого во время импорта с использованием аргументов командной строки второго сценария. Это почти всегда ошибка.

  • Если у вас есть собственный класс в сценарии без защиты и вы сохранили его в файле pickle, то его извлечение в другом сценарии вызовет импорт сценария без защиты с теми же проблемами, что описаны в предыдущем пункте.

Я бы сказал, что это обычно хорошая практика, но не обязательно. Чтобы узнать больше об этом проверьте этот ответ

Вот как бы я стилизовал весь сценарий:

import os
import sys

from kh_client import *
from kubernetes import client, config


NODE_GROUPS = {
    'core': [],
    'general': [],
    'observability': [],
    'pci': [],
}
IGNORED_LABELS = (
    "alpha.eksctl.io/instance-id",
    "alpha.eksctl.io/nodegroup-name",
    "eks.amazonaws.com/nodegroup",
    "k8s.amazonaws.com/eniConfig",
    "lifecycle",
    
    # custom labels
    "topology.kubernetes.io/zone",
    "kubernetes.io/hostname",
    "failure-domain.beta.kubernetes.io/zone"
)


def load_config():
    if os.getenv('IN_CLUSTER') == "TRUE":
        config.load_incluster_config()
    else:
        config.load_kube_config()


def get_nodes():
    try:
        api_instance = client.CoreV1Api()
        return api_instance.list_node()
    except client.exceptions.ApiException:
        print("401 Unauthorised. Please check you are authenticated "
              "for the target cluster / have set the IN_CLUSTER env "
              "var.")
        sys.exit(2)
        
        
def get_group_labels(node_group):
    return [
        node_group_label for node_group_label in node_group[0].metadata.labels
        if node_group_label not in IGNORED_LABELS
    ]


def check_node_group_labels(node_group):
    node_group_labels = get_group_labels(node_group)

    print(f"There are {len(node_group)} nodes in "
          f"{node_group[0].metadata.labels.get('nodegroup-name')}")

    for label in node_group_labels:
        # compare against the 'benchmark' label, any difference means 
        # a mismatch as far as CAS is concerned
        benchmark_label = node_group[0].metadata.labels.get(label)
        
        for node in node_group[1:]:
            label = node.metadata.labels.get('nodegroup-name')
            if label != benchmark_label:
                print("Reporting Failure.")
                
                try:
                    report_failure([
                        f"Warning! label mismatch detected, for nodegroup and "
                        f"node {node.metadata.name}, benchmark value: {benchmark_label}, "
                        f"this node value: {label}"
                    ])
                except Exception as e:
                    print(f"Error when reporting failure: {e}")


def main():
    load_config()
    nodes = get_nodes()

    for node in nodes.items:
        nodegroup_name = node.metadata.labels.get('nodegroup-name')

        for node_group_name, group_nodes in NODE_GROUPS.items():
            if nodegroup_name == node_group_name:
                group_nodes.append(node)

    for node_group in NODE_GROUPS.values():
        check_node_group_labels(node_group)

    print("Reporting Success.")
    try:
        report_success()
    except Exception as e:
        print(f"Error when reporting success: {e}")
        
        
if __name__ == '__main__':
    main()

Я не тестировал это, так как у меня нет кластера кубов, но вот некоторые из вещей, которые я улучшил:

  • создавать меньшие функции, чтобы упростить написание модульных тестов / проверку правильности вашего кода
  • улучшены некоторые имена переменных
  • переупорядочил импорт
  • exit является помощником для интерактивной оболочки — sys.exit предназначен для использования в программах. Воспользуйтесь вторым.

Домашнее задание для ОП:

  • попробуйте добавить в свой код строки документации;
  • избегайте использования простых исключений, если это необходимо

  • Замечательно, большое спасибо. Мы рассмотрим ваши предложения сейчас и свяжемся с вами в должное время.

    — кафка

  • этот код здесь (строка 65 в вашем) приводит к сбою моего скрипта … я не думаю, что он делает то, как выглядит (не уверен, конфликтует ли он с ‘label’, определенным выше) label = node.metadata.labels.get(label)

    — кафка

  • Упси, это должно быть на самом деле: label = node.metadata.labels.get('nodegroup-name')

    — Грайдяну Алекс

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *