diff --git a/materials/06-failures/06-failures.pdf b/materials/06-failures/06-failures.pdf
new file mode 100644
index 0000000..700f70d
Binary files /dev/null and b/materials/06-failures/06-failures.pdf differ
diff --git a/materials/06-failures/readme.md b/materials/06-failures/readme.md
new file mode 100644
index 0000000..53c0b12
--- /dev/null
+++ b/materials/06-failures/readme.md
@@ -0,0 +1,12 @@
+## 6. Отказы, их обнаружение, group membership.
+
+### Лекция
+
+- [Презентация](06-failures.pdf)
+- См. также материалы в конце
+
+### Семинар
+
+- [Материалы семинара](seminar/readme.md)
+- [Демо Consul](seminar/consul-demo)
+- [Outlier detection в Envoy](seminar/envoy-outlier-detection)
\ No newline at end of file
diff --git a/materials/06-failures/seminar/consul-demo/.gitignore b/materials/06-failures/seminar/consul-demo/.gitignore
new file mode 100644
index 0000000..f82933f
--- /dev/null
+++ b/materials/06-failures/seminar/consul-demo/.gitignore
@@ -0,0 +1 @@
+consul-data-*
\ No newline at end of file
diff --git a/materials/06-failures/seminar/consul-demo/app/Dockerfile b/materials/06-failures/seminar/consul-demo/app/Dockerfile
new file mode 100644
index 0000000..163bc2d
--- /dev/null
+++ b/materials/06-failures/seminar/consul-demo/app/Dockerfile
@@ -0,0 +1,10 @@
+FROM python:3.9-slim-buster
+
+WORKDIR /app
+
+COPY requirements.txt requirements.txt
+RUN pip install -r requirements.txt
+
+COPY . .
+
+CMD ["python", "./main.py"]
diff --git a/materials/06-failures/seminar/consul-demo/app/main.py b/materials/06-failures/seminar/consul-demo/app/main.py
new file mode 100644
index 0000000..8f77142
--- /dev/null
+++ b/materials/06-failures/seminar/consul-demo/app/main.py
@@ -0,0 +1,33 @@
+import os
+from flask import Flask
+from consul import Consul, Check
+
+app = Flask(__name__)
+
+consul_host = os.environ.get('CONSUL_HOST', 'localhost')
+consul_port = os.environ.get('CONSUL_PORT', 8500)
+my_host = os.environ.get('MY_HOST', 'localhost')
+my_port = os.environ.get('MY_PORT', 5000)
+
+# Initialize Consul client
+consul = Consul(host=consul_host, port=consul_port)
+
+# Register the Flask app in Consul
+service_name = 'flask-hello-world'
+consul.agent.service.register(
+ name=service_name,
+ service_id=f'{service_name}-{my_host}',
+ port=my_port,
+ check=Check.http(f'http://{my_host}:{my_port}/health', interval='10s', deregister='30s')
+)
+
+@app.route('/')
+def hello():
+ return 'Hello, World!'
+
+@app.route('/health')
+def health():
+ return 'OK'
+
+if __name__ == '__main__':
+ app.run(host='0.0.0.0', port=my_port)
\ No newline at end of file
diff --git a/materials/06-failures/seminar/consul-demo/app/requirements.txt b/materials/06-failures/seminar/consul-demo/app/requirements.txt
new file mode 100644
index 0000000..fe5dd65
--- /dev/null
+++ b/materials/06-failures/seminar/consul-demo/app/requirements.txt
@@ -0,0 +1,3 @@
+python-consul==1.1.0
+Flask==2.2.2
+Werkzeug==2.2.2
diff --git a/materials/06-failures/seminar/consul-demo/docker-compose.yaml b/materials/06-failures/seminar/consul-demo/docker-compose.yaml
new file mode 100644
index 0000000..e179362
--- /dev/null
+++ b/materials/06-failures/seminar/consul-demo/docker-compose.yaml
@@ -0,0 +1,62 @@
+version: '3'
+services:
+ consul-server-1:
+ image: consul:1.15
+ command: |
+ consul agent -ui -server -bootstrap-expect=3 -data-dir=/consul/data -client=0.0.0.0
+ volumes:
+ - ./consul-data-1:/consul/data
+ ports:
+ - 8500:8500
+ - 8600:8600/udp
+
+ consul-server-2:
+ image: consul:1.15
+ command: |
+ consul agent -server -retry-join=consul-server-1 -data-dir=/consul/data -client=0.0.0.0
+ volumes:
+ - ./consul-data-2:/consul/data
+ ports:
+ - 8501:8500
+ - 8601:8600/udp
+ depends_on:
+ - consul-server-1
+
+ consul-server-3:
+ image: consul:1.15
+ command: |
+ consul agent -server -retry-join=consul-server-1 -data-dir=/consul/data -client=0.0.0.0
+ volumes:
+ - ./consul-data-3:/consul/data
+ ports:
+ - 8502:8500
+ - 8602:8600/udp
+ depends_on:
+ - consul-server-1
+
+ app-1:
+ build:
+ context: ./app
+ ports:
+ - 5001:5000
+ environment:
+ CONSUL_HOST: consul-server-1
+ MY_HOST: app-1
+
+ app-2:
+ build:
+ context: ./app
+ ports:
+ - 5002:5000
+ environment:
+ CONSUL_HOST: consul-server-2
+ MY_HOST: app-2
+
+ app-3:
+ build:
+ context: ./app
+ ports:
+ - 5003:5000
+ environment:
+ CONSUL_HOST: consul-server-3
+ MY_HOST: app-3
diff --git a/materials/06-failures/seminar/consul-demo/readme.md b/materials/06-failures/seminar/consul-demo/readme.md
new file mode 100644
index 0000000..9913dd1
--- /dev/null
+++ b/materials/06-failures/seminar/consul-demo/readme.md
@@ -0,0 +1,57 @@
+# Consul: членство агентов и здоровье сервисов
+
+[К материалам семинара](../readme.md)
+
+В стенде работают три сервера Consul и три экземпляра Flask-приложения. Каждый экземпляр регистрирует себя в Consul вместе с HTTP-проверкой `/health`, выполняемой раз в 10 секунд. Проверка приложения и обнаружение недоступности агентов Consul — разные механизмы.
+
+## Запуск и наблюдение
+
+Нужны Docker и Docker Compose. Команды выполняются из этой директории. В PowerShell вместо `curl` используйте `curl.exe`.
+
+Сначала запустите серверы:
+
+```shell
+docker compose up -d consul-server-1 consul-server-2 consul-server-3
+```
+
+Дождитесь появления трёх узлов в [интерфейсе Consul](http://localhost:8500), затем запустите приложения. Такой порядок нужен, потому что регистрация приложения выполняется один раз при старте и требует доступного Consul.
+
+```shell
+docker compose up --build -d app-1 app-2 app-3
+docker compose exec consul-server-1 consul members
+curl http://localhost:8500/v1/catalog/services
+curl http://localhost:8500/v1/health/checks/flask-hello-world
+curl "http://localhost:8500/v1/health/service/flask-hello-world?passing=true"
+```
+
+Здесь `consul members` показывает агентов, каталог — зарегистрированные сервисы, а health API — проверки и экземпляры, прошедшие их. Дождитесь успешных проверок всех трёх приложений. Если приложение завершилось при регистрации, причину можно увидеть через `docker compose logs app-1 app-2 app-3`.
+
+## Остановка приложения
+
+```shell
+docker compose stop app-2
+```
+
+Повторите запросы к health API и `consul members`. После очередной проверки экземпляр `app-2` перестанет попадать в выдачу с `passing=true`, хотя агент `consul-server-2` останется участником группы. Сервис может ещё быть зарегистрирован в каталоге: каталог и список здоровых экземпляров имеют разный смысл. При длительном провале проверки настроена автоматическая отмена регистрации.
+
+Верните приложение и после проверки снова сравните результаты:
+
+```shell
+docker compose start app-2
+```
+
+Обработчик `/health` в [коде приложения](app/main.py) просто возвращает `OK`. Он позволяет заметить недоступность HTTP-сервера, но не проверяет выполнение полезной работы или доступность зависимостей. Этот опыт показывает проверку сервиса; для изучения отказов самих агентов потребовался бы другой сценарий.
+
+## Связь с маршрутизацией
+
+Клиент или балансировщик может использовать сведения о здоровых экземплярах для выбора адреса. В этом стенде балансировщик не настроен, и с [демо Envoy](../envoy-outlier-detection/readme.md) он не соединён.
+
+После работы остановите стенд:
+
+```shell
+docker compose down
+```
+
+Данные Consul сохраняются в каталогах `consul-data-*`, поэтому следующий запуск не обязательно начинается с пустого каталога.
+
+Документация: [Health API](https://developer.hashicorp.com/consul/api-docs/health), [gossip и Lifeguard в Consul](https://developer.hashicorp.com/consul/docs/concept/gossip).
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/docker-compose.yaml b/materials/06-failures/seminar/envoy-outlier-detection/docker-compose.yaml
new file mode 100644
index 0000000..e040880
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/docker-compose.yaml
@@ -0,0 +1,30 @@
+version: '3'
+
+services:
+ server1:
+ build:
+ context: ./kittens
+ environment:
+ APP_VERSION: 'v1'
+ BIND_PORT: 5001
+ ports:
+ - 9001:5001
+
+ server2:
+ build:
+ context: ./kittens
+ environment:
+ APP_VERSION: 'v2'
+ BIND_PORT: 5002
+ FAILURE_PROBABILITY: '0.8'
+ ports:
+ - 9002:5002
+
+ envoy:
+ image: docker.io/envoyproxy/envoy:v1.27-latest
+ environment:
+ loglevel: debug
+ volumes:
+ - ./envoy.yaml:/etc/envoy/envoy.yaml
+ ports:
+ - 8080:8080
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/envoy.yaml b/materials/06-failures/seminar/envoy-outlier-detection/envoy.yaml
new file mode 100644
index 0000000..543c312
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/envoy.yaml
@@ -0,0 +1,59 @@
+admin:
+ address:
+ socket_address:
+ address: 127.0.0.1
+ port_value: 15000
+
+static_resources:
+ listeners:
+ - address:
+ socket_address:
+ address: 0.0.0.0
+ port_value: 8080
+ filter_chains:
+ - filters:
+ - name: envoy.filters.network.http_connection_manager
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+ stat_prefix: http
+ route_config:
+ name: default
+ virtual_hosts:
+ - name: default
+ domains:
+ - "*"
+ routes:
+ - match:
+ prefix: /
+ route:
+ cluster: kittens
+ http_filters:
+ - name: envoy.filters.http.router
+ typed_config:
+ "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+ clusters:
+ - name: kittens
+ connect_timeout: 10s
+ type: STRICT_DNS
+ lb_policy: ROUND_ROBIN
+ outlier_detection:
+ consecutive_5xx: 2
+ interval: "5s"
+ base_ejection_time: "10s"
+ max_ejection_percent: 100
+ load_assignment:
+ cluster_name: kittens
+ endpoints:
+ - lb_endpoints:
+ - endpoint:
+ address:
+ socket_address:
+ address: server1
+ port_value: 5001
+ - endpoint:
+ address:
+ socket_address:
+ address: server2
+ port_value: 5002
+
+cluster_manager: {}
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/kittens/.dockerignore b/materials/06-failures/seminar/envoy-outlier-detection/kittens/.dockerignore
new file mode 100644
index 0000000..cb57075
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/kittens/.dockerignore
@@ -0,0 +1,3 @@
+docker-compose.yaml
+Dockerfile
+__pycache__
\ No newline at end of file
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/kittens/Dockerfile b/materials/06-failures/seminar/envoy-outlier-detection/kittens/Dockerfile
new file mode 100644
index 0000000..0e1c6bc
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/kittens/Dockerfile
@@ -0,0 +1,12 @@
+FROM python:3.11.5-slim-bullseye
+
+WORKDIR /application
+
+# Copy the requirements file and download the dependencies.
+COPY requirements.txt requirements.txt
+RUN pip3 install -r requirements.txt
+
+# Copy other data.
+COPY . .
+
+CMD [ "python3", "app.py"]
\ No newline at end of file
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/kittens/app.py b/materials/06-failures/seminar/envoy-outlier-detection/kittens/app.py
new file mode 100644
index 0000000..55e3f09
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/kittens/app.py
@@ -0,0 +1,41 @@
+from flask import Flask, render_template
+
+import os
+import random
+import requests
+
+app = Flask(__name__)
+
+APP_VERSION = os.getenv('APP_VERSION')
+BIND_HOST = os.getenv('BIND_HOST') or '0.0.0.0'
+BIND_PORT = os.getenv('BIND_PORT') or '8000'
+
+FAILURE_PROBABILITY = float(os.getenv('FAILURE_PROBABILITY') or '0.0')
+
+
+@app.route('/')
+def home():
+ if random.random() <= FAILURE_PROBABILITY:
+ return f'[{APP_VERSION}] You are unlucky :( Server failed [x]', 500
+
+ return f'Hello from app {APP_VERSION}'
+
+
+@app.route('/kittens')
+def kittens():
+ # Get a URL to a random kitten photo.
+ response = requests.get('https://api.thecatapi.com/v1/images/search?api_')
+
+ try:
+ # Response structure is as follows:
+ # [{"id":"bL3lrUi1A","url":"ex.com/bL3lrUi1A.jpg","width":1280,"height":720}]
+ data = response.json()
+ kitten_url = data[0]['url']
+
+ return render_template('index.html', kitten_url=kitten_url)
+ except Exception as e:
+ return f'Failed to fetch a kitten image :(\n {e}'
+
+
+if __name__ == '__main__':
+ app.run(host=BIND_HOST, port=BIND_PORT)
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/kittens/requirements.txt b/materials/06-failures/seminar/envoy-outlier-detection/kittens/requirements.txt
new file mode 100644
index 0000000..a4884d4
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/kittens/requirements.txt
@@ -0,0 +1,2 @@
+Flask==2.3.3
+requests==2.28.2
\ No newline at end of file
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/kittens/templates/index.html b/materials/06-failures/seminar/envoy-outlier-detection/kittens/templates/index.html
new file mode 100644
index 0000000..f42173f
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/kittens/templates/index.html
@@ -0,0 +1,8 @@
+
+
+ DistSys kittens
+
+
+
+
+
\ No newline at end of file
diff --git a/materials/06-failures/seminar/envoy-outlier-detection/readme.md b/materials/06-failures/seminar/envoy-outlier-detection/readme.md
new file mode 100644
index 0000000..8bc4327
--- /dev/null
+++ b/materials/06-failures/seminar/envoy-outlier-detection/readme.md
@@ -0,0 +1,54 @@
+# Envoy: outlier detection
+
+[К материалам семинара](../readme.md)
+
+В стенде работают Envoy и два бэкенда. Первый возвращает успешный ответ на `/`, второй с вероятностью 0.8 возвращает HTTP 500. Envoy наблюдает результаты обычных запросов и временно исключает проблемный бэкенд из балансировки. Это пассивное обнаружение: отдельные запросы проверки здоровья здесь не настроены.
+
+## Запуск и наблюдение
+
+Нужны Docker и Docker Compose. Выполните из этой директории:
+
+```shell
+docker compose up --build -d
+```
+
+После запуска приложений несколько раз отправьте запрос через прокси и сравните с прямыми запросами:
+
+```shell
+curl -i http://localhost:8080/
+curl -i http://localhost:9001/
+curl -i http://localhost:9002/
+```
+
+В PowerShell вместо `curl` используйте `curl.exe`. Ответы содержат версию бэкенда (`v1` или `v2`). Для опыта достаточно пути `/`; путь `/kittens` дополнительно обращается к внешнему API.
+
+При повторных запросах через Envoy проблемный бэкенд временно исключается, а затем снова допускается к трафику. Прямые запросы к порту 9002 позволяют видеть, что процесс продолжает работать и возвращать ответы, в том числе ошибки. Последовательность случайна: по одному успешному ответу или короткой серии нельзя заключить, что бэкенд исключён.
+
+## Что означают настройки
+
+В [конфигурации Envoy](envoy.yaml) задано:
+
+```yaml
+outlier_detection:
+ consecutive_5xx: 2
+ interval: "5s"
+ base_ejection_time: "10s"
+ max_ejection_percent: 100
+```
+
+- `consecutive_5xx`: исключение после двух последовательных ответов 5xx **от одного бэкенда**, наблюдаемых Envoy. Это не любые две ошибки во всём потоке запросов.
+- `interval`: период фонового анализа и проверки возврата исключённых бэкендов. Достижение порога последовательных ошибок обрабатывается при запросах, без ожидания следующего такого интервала.
+- `base_ejection_time`: базовая длительность исключения. При повторных исключениях срок может расти; возврат не гарантирован ровно через 10 секунд.
+- `max_ejection_percent`: разрешённая доля исключённых бэкендов. Значение 100 позволяет исключить все; это настройка данного учебного примера.
+
+Исключение меняет маршрутизацию в данном прокси. Оно не останавливает бэкенд и не доказывает, что тот аварийно завершился. Возврат по таймеру тоже не подтверждает исправление приложения: новый запрос может снова завершиться ошибкой.
+
+Адреса бэкендов заданы в конфигурации. Интеграция с Consul в этом стенде отсутствует.
+
+После работы:
+
+```shell
+docker compose down
+```
+
+Подробнее: [outlier detection в документации Envoy](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/outlier), [справочник параметров](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/outlier_detection.proto).
diff --git a/materials/06-failures/seminar/readme.md b/materials/06-failures/seminar/readme.md
new file mode 100644
index 0000000..46e7473
--- /dev/null
+++ b/materials/06-failures/seminar/readme.md
@@ -0,0 +1,103 @@
+# Семинар 6. Обнаружение отказов и group membership
+
+На семинаре разбираем, как детекторы отказов используются в готовых системах: какие сигналы они получают, почему могут ошибаться и что происходит после обнаружения проблемы. Эти заметки дополняют [лекцию 6](../06-failures.pdf) и запись семинара. Команды для запуска примеров вынесены в README [Envoy](envoy-outlier-detection/readme.md) и [Consul](consul-demo/readme.md).
+
+## Наблюдение и реакция
+
+Отсутствие ответа может быть вызвано остановкой процесса, потерей связи или задержкой обработки — в том числе у самого наблюдателя. Например, перегруженный процесс может не успевать читать пришедшие ответы и начать подозревать исправных соседей.
+
+Детектор сообщает о подозрении, а приложение решает, как на него реагировать. В примерах ниже это приводит к разным действиям: исключению бэкенда из балансировки, изменению статуса участника или обновлению состояния узла в управляющей системе.
+
+## Envoy: ошибки при обработке запросов
+
+В [демо Envoy](envoy-outlier-detection/readme.md) один из двух бэкендов случайно возвращает HTTP 500. Прокси учитывает ошибки и временно исключает этот бэкенд из балансировки. Это пассивная проверка: наблюдения появляются только при поступлении обычных запросов.
+
+Прямой запрос к исключённому бэкенду по-прежнему может получить ответ. Envoy меняет маршрутизацию, а процесс продолжает работать. Возврат в балансировку по таймеру также не означает, что причина ошибок устранена.
+
+## Consul и Serf
+
+В [демо Consul](consul-demo/readme.md) можно сопоставить три вида сведений:
+
+| Источник | Что показывает |
+| --- | --- |
+| `consul members` | Известных данному агенту участников группы и их статусы |
+| Каталог сервисов | Зарегистрированные сервисы |
+| Проверки здоровья | Результаты проверок экземпляров приложения |
+
+При остановке контейнера приложения его HTTP-проверка перестаёт проходить, хотя агент Consul остаётся доступным. При этом регистрация сервиса ещё может сохраняться в каталоге. Поэтому список зарегистрированных экземпляров и список экземпляров с успешными проверками могут различаться.
+
+Есть и ограничение самой проверки. Обработчик `/health` в этом примере просто возвращает `OK`. Он позволяет заметить недоступность HTTP-сервера, но если бы приложение работало с базой данных, потеря доступа к ней осталась бы незамеченной.
+
+За членство самих агентов отвечает другой механизм. Consul использует **Serf**, который опирается на библиотеку **memberlist**. Её протокол основан на SWIM с расширениями, включая Lifeguard. Подробнее: [gossip в Consul](https://developer.hashicorp.com/consul/docs/concept/gossip), [протокол memberlist](https://github.com/hashicorp/memberlist#protocol).
+
+### SWIM и его реализация в Serf
+
+В [SWIM](https://www.cs.cornell.edu/projects/quicksilver/public_pdfs/SWIM.pdf) каждый участник проверяет других и распространяет изменения состава группы. Узел A отправляет выбранному B сообщение `ping`. Если ответ не пришёл вовремя, A просит нескольких посредников проверить B и передать ответ. Это помогает, когда прямой обмен A и B нарушен, но через других участников связь ещё работает.
+
+После неудачных проверок B сначала подозревают. У него есть время опровергнуть подозрение, прежде чем наблюдатель пометит его как отказавшего. Такое ожидание уменьшает риск ложного срабатывания, но откладывает обнаружение настоящего отказа.
+
+Сведения об изменениях состава группы передаются вместе с проверочными сообщениями — этот приём называется piggybacking. В сообщение добавляют ограниченное число обновлений, а не всю таблицу участников. Номера версий состояния помогают отличать новые сведения от запоздавших. Пока обновления доходят до остальных, представления участников могут различаться.
+
+В [Serf](https://github.com/hashicorp/serf/blob/master/docs/internals/gossip.html.markdown) к этому добавлены отдельные gossip-сообщения и периодическая полная синхронизация состояния по TCP. Частоту распространения изменений можно настраивать независимо от частоты проверок, а полная синхронизация помогает восстановить согласованность списков после разделения сети. Эти обмены тоже нужно учитывать при оценке трафика.
+
+Для знакомства с агентами есть [Quick Start Serf](https://github.com/hashicorp/serf#quick-start). Команда [`serf members`](https://github.com/hashicorp/serf/blob/master/docs/commands/members.html.markdown) показывает `alive`, `left`, `failed`: доступность, штатный выход и обнаруженную недоступность. Запись с `failed` некоторое время сохраняется в списке. Промежуточное подозрение можно увидеть в логах.
+
+### Lifeguard: задержки у наблюдателя
+
+Если A перегружен, он может не успевать обрабатывать ответы здорового B. Ожидание опровержения не всегда спасает от ошибки: пришедшее опровержение тоже может задержаться в обработке. Lifeguard учитывает такие ситуации с помощью трёх расширений SWIM.
+
+| Механизм | Как работает |
+| --- | --- |
+| **Local Health Aware Probe** | По результатам обмена сообщениями узел оценивает признаки собственных проблем. При их накоплении увеличивает период проверок и таймаут ответа; успешные проверки постепенно уменьшают их. |
+| **Local Health Aware Suspicion** | Начальный таймаут подозрения увеличен. Подтверждения того же подозрения от разных участников сокращают его; их поступление также свидетельствует, что наблюдатель успевает получать и обрабатывать сообщения. |
+| **Buddy System** | При отправке `ping` подозреваемому в сообщение приоритетно добавляется подозрение о нём. Так он раньше узнаёт о необходимости опровержения. |
+
+Lifeguard уже используется в Serf/Consul. В [статье](https://arxiv.org/html/1707.00788v2) его оценивают при задержках обработки сообщений: измеряют ложные срабатывания, время обнаружения настоящих отказов и дополнительный трафик. Увеличение ожидания может замедлить обнаружение, поэтому уменьшение числа ошибок нужно рассматривать вместе с задержками.
+
+### Consul и балансировщик
+
+Список здоровых экземпляров нужен, чтобы направлять к ним запросы. Балансировщик или клиентская библиотека может получать его из Consul и обновлять набор доступных адресов. Для этого требуется настроить интеграцию между компонентами. В наших стендах её нет: адреса бэкендов Envoy заданы в конфигурации.
+
+Между проверкой и запросом экземпляр может стать недоступен. Поэтому сведения из Consul дополняют таймауты и обработку ошибок на стороне клиента или прокси.
+
+## Примеры из документации и кода
+
+### Cassandra: пауза самого наблюдателя
+
+В Cassandra сведения о heartbeat распространяются через gossip, а решение `UP`/`DOWN` каждый узел принимает локально. Если процесс надолго потерял процессорное время, после возобновления работы он увидит большие интервалы без heartbeat сразу от многих соседей.
+
+В [`FailureDetector.interpret()`](https://github.com/apache/cassandra/blob/9d3283a80ab7d8e3c2b290b222c794e8d41b2f70/src/java/org/apache/cassandra/gms/FailureDetector.java#L335-L367) перед вычислением подозрения проверяется время с предыдущего вызова. После долгой локальной паузы детектор временно воздерживается от признания соседей недоступными. [Документация Cassandra](https://cassandra.apache.org/doc/latest/cassandra/architecture/dynamo.html#ring-membership-and-failure-detection).
+
+### Akka: какие интервалы включать в историю
+
+φ-детектор использует историю интервалов между heartbeat. Если узел долго отсутствовал и вернулся, включение этой паузы в историю изменило бы представление детектора о нормальных задержках.
+
+В [`heartbeat()`](https://github.com/akka/akka/blob/164b3c6615b0ac1957c4ef13b0cff6e2a43a79c0/akka-remote/src/main/scala/akka/remote/PhiAccrualFailureDetector.scala#L141-L155) φ-детектора новый интервал добавляется в историю только в том случае, если к моменту получения heartbeat узел ещё считается доступным. Так длительный перерыв не искажает статистику обычных интервалов.
+
+Статус `unreachable` может смениться на `reachable`, когда связь восстановится. Решение об окончательном исключении участника принимает отдельный механизм. Подробнее: [φ-детектор](https://doc.akka.io/libraries/akka-core/current/typed/failure-detector.html), [Split Brain Resolver](https://doc.akka.io/libraries/akka-core/current/split-brain-resolver.html).
+
+### Kubernetes: heartbeat через API
+
+Kubelet сообщает о себе обновлениями статуса узла и объекта `Lease`. Здесь heartbeat имеет форму обновления ресурса в API Kubernetes. [Контроллер](https://github.com/kubernetes/kubernetes/blob/f28b4c9efbca5c5c0af716d9f2d5702667ee8a45/pkg/controller/nodelifecycle/node_lifecycle_controller.go#L919-L927) при новом `Lease` запоминает локальное время наблюдения.
+
+Если обновления долго не поступают, условию `Ready` присваивается значение `Unknown`: у контроллера нет свежих сведений об узле. Например, связь с управляющей частью могла пропасть, хотя приложения на узле продолжают работать.
+
+Этот механизм следит за узлом в целом. Проверки [readiness и liveness](https://kubernetes.io/docs/concepts/workloads/pods/probes/) относятся к контейнерам приложения: они позволяют отдельно оценивать готовность принимать трафик и необходимость перезапуска. [Документация heartbeat узлов](https://kubernetes.io/docs/concepts/architecture/nodes/#heartbeats).
+
+## Цена обнаружения и распространения сведений
+
+В дополнение к оценкам на слайдах 21–22 и 29 [лекции](../06-failures.pdf) посчитаем расходы для условной группы из 1000 процессов. Запись занимает 32 байта, служебная часть сообщения — 64 байта, период отправки — одну секунду.
+
+| Во всей группе за секунду | All-to-all: своя запись каждому | Gossip: полная таблица одному соседу |
+| --- | ---: | ---: |
+| Сообщений | 999 000 | 1000 |
+| Объём отправок | ≈95,9 МБ | ≈32,1 МБ |
+
+МБ здесь — миллион байт; транспортные заголовки, фрагментация и повторные передачи не учтены. Сообщений стало почти в тысячу раз меньше, а объём сократился лишь втрое: обе схемы передают порядка миллиона записей. Получатель обрабатывает либо много маленьких сообщений, либо большие таблицы.
+
+Одинаковый период не обеспечивает одинаковую задержку распространения сведений. Расходы сравнивают при сопоставимых требованиях к задержке и ложным подозрениям. Помимо сообщений и байтов, измеряют CPU и нагрузку на отдельных участников. Нужны и замеры при отказах: например, в SWIM отсутствие прямого ответа вызывает дополнительные проверки через посредников.
+
+## Дополнительное чтение
+
+- [Why Does the Cloud Stop Computing?](https://ucare.cs.uchicago.edu/pdf/socc16-cos.pdf) — причины реальных аварий облачных сервисов.
+- [Gray Failure: The Achilles’ Heel of Cloud-Scale Systems](https://www.microsoft.com/en-us/research/wp-content/uploads/2017/06/paper-1.pdf) — случаи, когда разные наблюдатели по-разному видят работоспособность системы.