Перенос Подов На Первичную Ноду Патрони Или Как Пользоваться Тейнтами, Метками В K8S

Низкая задержка в сети между прикладом и базой может стать головной болью для финтех-компаний, если вы обслуживаете Kubernetes и Patori исключительно на «голом железе».

Нарушение сетевого подключения и последующая смена патронов первичного узла — частый случай, который может случиться в жизни ОП, например, когда горит крупный дата-центр или уборщица случайно задевает шваброй силовой кабель.

И тогда они приходят на помощь портит , этикетки из Кубернетеса.

Итак, что мы имеем:

  1. геораспределенный кластер Kubernetes
  2. несколько подов с нашим приложением, которое отказывается Верно работать с задержкой сети около 20 мс
  3. геораспределенный кластер патрони, который работает как часы, но кто знает, что может случиться на этот раз.

К сожалению, внимательно прочитав документацию, руководства и stackoverflow, я пришел к выводу, что taint работает только при планировании подов, выселения не происходит, если менять taint и метку на лету.

И было решено написать свой сервис-костыль.

Так как у меня есть некоторый опыт написания на питоне, я выбрал его, потому что есть возможность быстро и без боли реализовать свой велосипед. Логика такая, давайте сделаем это набор демонов , Где ливнес-зонд это ручка, измеряющая задержку до мастер-ноды (с окном в 10 icmp запросов), пишем значения в redis, а потом по значениям вешаем теги и отправляем блестящий перезапуск выката

Откройте для себя основной узел

Это не должно вызывать каких-либо недоразумений, поскольку патрони реализует службу на порту 8008, которая выдает статус 200 если это мастер и 503 если это реплика.

  
  
  
   

import requests from flask import Blueprint, Response from json import dumps, loads from app.constants import * load_dotenv(find_dotenv()) main = Blueprint('main', name) redis_conn = REDIS_CONN @main.route('/primary') def discover_primary(): res = RESPONSE for node in PATRONI_HOSTS: r = requests.get(f'{getenv("PATRONI_SCHEMA")}{node}:{getenv("PATRONI_PORT")}') if r.status_code == 200: res['data']['node'] = node res['message'] = 'successful' redis_conn.set('primary', node) return Response(dumps(res), status=200, mimetype='application/json') res['error'] = 1 res['message'] = 'primary not found' return Response(dumps(res), status=500, mimetype='application/json')



Зонд живости

Проверка работоспособности сервиса, а также измерение пингов до мастер-ноды картриджа

#.

from ping3 import ping @main.route('/liveness-probe') def liveness_probe(): res = RESPONSE res['data']['node'] = NODE res['data']['round-trip'] = 0 result_pings = [] for _ in range(COUNT_PING): single_ping = ping(loads(discover_master().

data)['data']['node'], unit='ms') if single_ping: result_pings.append(single_ping) try: res['data']['round-trip'] = str(sum(result_pings) / len(result_pings)) res['message'] = 'successful' except ZeroDivisionError: res['message'] = 'something was going on (ping)' redis_conn.set(NODE, res['data']['round-trip']) return Response(dumps(res), status=200, mimetype='application/json')

Не забываем про первоначальную инициализацию:

@main.route('/') def init(): primary = redis_conn.get('primary_host') for node in KUBE_NODES: if redis_conn.get(node): pass else: redis_conn.set(node, 0) if primary is None: redis_conn.set('primary_host', 'localhost') return "It's a Switchover for patroni and kubernetes"

Cronjob или как модно называть оператор : Нам нужен cronjob, который по сути будет следить за состоянием наших танов и меток и устанавливать необходимые теги и метки в зависимости от расстояния [далеко, промежуточно, близко]

import subprocess from app.constants import * from app.main.views import init redis_conn = REDIS_CONN check = dict() min_ping = MIN_PING max_ping = MAX_PING try: current_db_master = redis_conn.get('primary').

decode() pre_current_db_master = redis_conn.get('primary_host').

decode() except BaseException as error: init() current_db_master = redis_conn.get('primary').

decode() pre_current_db_master = redis_conn.get('primary_host').

decode() def subprocess_wrapper(command): return subprocess.run( command, text=True, check=False, capture_output=True ) for node in KUBE_NODES: value = redis_conn.get(node) if value: check[node] = int(float(value.decode())) for _, value in check.items(): max_ping = value if max_ping < value else max_ping min_ping = value if min_ping > value else min_ping for node in KUBE_NODES: if check.get(node) in range(0, min_ping + WINDOW_PING): subprocess_wrapper( ['kubectl', 'taint', 'nodes', node, 'switchover:NoExecute', '--overwrite'] ) subprocess_wrapper( ['kubectl', 'label', 'nodes', node, 'switchover=closely', '--overwrite'] ) elif check.get(node) in range(max_ping - WINDOW_PING, max_ping + WINDOW_PING): subprocess_wrapper( ['kubectl', 'taint', 'nodes', node, 'switchover:NoExecute-'] ) subprocess_wrapper( ['kubectl', 'label', 'nodes', node, 'switchover=far', '--overwrite'] ) else: subprocess_wrapper( ['kubectl', 'taint', 'nodes', node, 'switchover:NoExecute-'] ) subprocess_wrapper( ['kubectl', 'label', 'nodes', node, 'switchover=intermediate', '--overwrite'] ) if pre_current_db_master != current_db_master: for app in APPS: subprocess_wrapper( ['kubectl', 'rollout', 'restart', app] ) redis_conn.set('primary_host', current_db_master)

Я не претендую на открытие чего-то нового; Меня мучает вопрос, как оппы решают эту проблему в наше время.

Возможно, здесь есть люди, готовые поделиться своей болью или даже лучшим решением.

Интересно, как люди решают это дело.

Теги: #python #Kubernetes #postgresql #patroni #operator

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.