Make. Build. Break. Reflect. – Telegram
Make. Build. Break. Reflect.
917 subscribers
116 photos
1 video
122 links
Полезные советы, всратые истории, странные шутки и заметки на полях от @kruchkov_alexandr
Download Telegram
#kubernetes #devops

Последнюю неделю на работе игрался с KRO, AWS ACK и CrossPlane.
- https://github.com/crossplane/crossplane
- https://github.com/aws-controllers-k8s/community
- https://github.com/kubernetes-sigs/kro

Ну что я могу сказать.
Мне одновременно и очень понравилось - инструменты хороши.
Это потрясающие инструменты, выводящие инфраструктурную часть и операционную на совершенно иной уровень.
Не плохой, не хороший, он новый. Не буду приводить аналогий, это просто иной уровень для меня.
Да охрененный, если честно, буду честен.
И одновременно не понравилось - самостоятельно, в одиночку, в одно лицо, без помощи AI ассистентов я разобрался бы в лучшем случае через недели 3-4. Возможно бы и через месяц.

Сложность в том, что эти инструменты создают десятки уровней абстракций - от CRD в Kubernetes до специфических API провайдеров (AWS, GCP). Чтобы отладить ошибку, нужно пройти путь от kubectl describe до логов ACK controller, а потом до настоящего события в облаке. Рисуешь сперва диаграммы, зависимости, порядки деплоя - это колоссальная работа.

Вообще прикольно, конечно, один манифест и фигак, у тебя целые регионы, кластера, роли, рут таблицы, натгейтвеи и миллион сущностей в кубернетисах. Миллионы объектов и сущностей на сотнях уровней абстракций.
Круто. Мне понравилось.

Про KRO/ACK/CrossPlane я ещё напишу не одну глубокую техническую заметку/отзыв, а пока лишь общий отзыв - они хороши.
👍151👏1
Работа работа работа.

Я так и не успел в суете работы купить оперативную память до всего этого хайпа и уж тем более нехватки оперативной памяти на рынке.
Теперь то, что я хочу, даже в наличии нет. Или под заказ на март. Возможно.

Ладно, буду, как и раньше со старым ПК от 2021 года ещё лет 10.😭
Please open Telegram to view this post
VIEW IN TELEGRAM
😭10🔥2
Зачем? Зачем в наше время вы ещё держитесь за свои ноды и эхи?
😁9
Вот так незаметно и тихо прошёл год с основания моего авторского телеграм канала. Поздравляю сам себя и всех, кому это интересно читать.
142👍8🎉7🔥4
Приветствую всех.

Поскольку все читатели здесь ради контента, а не моей биографии, сразу перейду к сути.
Этот блог - мои заметки на полях.
Почти не делаю репосты, пишу для души и лишь когда на это есть время/желание.
Обычно это 2-4 поста в неделю.

В основном делюсь:
- информацией, которую узнал только что (даже если она пятилетней давности, но я узнал её сейчас)
- лонгридами, байками или всратыми историями, без указания срока давности
- последовательным описанием моего процесса мышления на работе при решении задач

Интересные, на мой взгляд, сообщения я публикую с тегами:
- пример основных тем канала:
#aws #azure  #kubernetes  #troubleshooting  #costoptimization  #longread  #devops
- пример второстепенных категорий:
#terragrunt  #victoriametrics  #git #docker  #одинденьизжизни  #helm
- для того, чтобы на работе не поехать кукухой, у меня есть:
#пятница  #всратость  #байки

Сообщения без тегов это просто шутка-минутка или мысль, которая была актуальна лишь на момент написания.

Все заметки не имеют строгой последовательности, читать их можно как угодно:
- начать с самого основания канала (за год постов около 230)
- использовать интересующие теги/поиск
- ну или просто начать с новых постов, пропустив всё ранее написанное 😭
Каждый решает, как ему удобно.

Буду рад, если мои заметки помогут кому-то узнать что-то новое, избежать повтора чужих ошибок или просто улыбнуться.
На крайний случай, самоутвердиться за счёт моих факапов или незнания 🐒
Всем привет и желаю приятного чтения.
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍31👨‍💻1
Make. Build. Break. Reflect. pinned «Приветствую всех. Поскольку все читатели здесь ради контента, а не моей биографии, сразу перейду к сути. Этот блог - мои заметки на полях. Почти не делаю репосты, пишу для души и лишь когда на это есть время/желание. Обычно это 2-4 поста в неделю. В основном…»
#costoptimization #aws #cloudwatch #одинденьизжизни

"Алекс, у нас снова траты по амазон, глянь по биллингу, чего можно скостить".

Иду в billing, выбираю последний месяц.
Дааа, много всего опять зачарджило. Чего можно убрать?
О, в топ 10 есть клаудвоч. Начну с него. Почему так много? Почти $2000+.
Смотрю что не так.
Внезапно много идет за графу
$0.01 per 1,000 metrics requested using GetMetricData API - US East (Northern Virginia)

200.000.000+ реквестов.
Читаю что это.
https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html

Ага. Какой-то сторонний механизм/утилита, которая при помощи кредов/роли подключается к AWS API и вытягивает данные по метрикам из клаудвоч.

Из известного мне есть такие два инструмента:
- https://github.com/prometheus/cloudwatch_exporter
- https://github.com/prometheus-community/yet-another-cloudwatch-exporter

Оба эти категории инструмента подключаются к AWS API, дергают метрики, экспортируют в удобный формат, чтобы можно было в victoria metrics/prometheus/grafana видеть эти новые метрики.
Почему новые? Они берут оригинальное название метрики, добавляют префикс и получается новая метрика в нашем хранилище.

Далее поиском по всем репозиториям(гит, арго, хелм) внутри компании ищу - есть ли они у нас.
Да, есть, у нас YACE.

Как оптимизировать? Мне ничего в голову не приходит, как сделать следующее:
- подключаюсь к service через порт форвард
kubectl port-forward svc/yet-another-cloudwatch-exporter 5000:5000

- вытягиваю все метрики в файл
curl localhost:5000/metrics > metrics.txt

- пишу баш скрипт, который считает количество уникальных метрик и всякое такое

Ага, у нас 54.000+ метрик. Это много.
Иду в конфиг в гите, там нечто типа
config: |-
apiVersion: v1alpha1
discovery:
exportedTagsOnMetrics:
AWS/NetworkELB:
- tenant-id
- type
- cluster
jobs:
- type: AWS/NetworkELB
regions:
- us-east-1
...
- eu-central-1
- eu-central-2
period: 300
length: 300
metrics:
- name: ActiveFlowCount
statistics: [Average, Minimum, Maximum]
- name: ActiveFlowCount_TLS
statistics: [Average, Minimum, Maximum]
...
- name: UnHealthyHostCount
statistics: [Average, Minimum, Maximum]
- name: PortAllocationErrorCount
statistics: [Sum]
...
- type: AWS/TransitGateway
regions:
- us-east-1
...
period: 300
length: 300
metrics:
- name: BytesIn
statistics: [Average, Minimum, Maximum, Sum]
- name: BytesOut
statistics: [Average, Minimum, Maximum, Sum]
- name: PacketsIn
statistics: [Average, Minimum, Maximum, Sum]
...
- type: AWS/NATGateway
regions:
- us-east-1
...
- eu-central-2
period: 300
length: 300
metrics:
- name: ActiveConnectionCount
statistics: [Maximum, Sum]
- name: BytesInFromDestination
statistics: [Sum]
...

И такого там много.

По частоте опроса вроде ок - 300 секунд, около бест практис для клаудвоч экспортёров.
А чего подрезать-то тогда?
Нужен ли max? Min? Avg?
Следим ли мы за натгейтвеем?

А всё просто.
- при том же bash скрипте расписываем все уникальные метрики и их итоговое название.
- идём все репозитории с алертингом и мониторингом (alertmanager, grafana irm, vmalert, grafana dashboards etc)
- если метрика нигде в observability не используется - мы её просто убираем из конфига. Где-то только min, где-то max, где-то полностью всю метрику со всеми значениями

Даже если удастся убрать хотя бы 50% неиспользуемых метрик - это минус 50% от биллинга, а это $1000+
Пулл реквест, ревью, аппрув, раскатываем.
Всё, в следующем месяце ждём снижения костов на клаудвоч на 50%+
👍13👏5🎄2🤷‍♂11❤‍🔥1
Итог:
я всего лишь проанализировал куда уходят деньги, нашёл конфиг приложения, который чарджит амазон, собрал все метрики, проверил есть ли они в обсервабилити, удалил неиспользуемое (зачем нам собирать то, что мы не используем?).
Ничего сложного, а у нас экономия на пустом месте.
👍14👏5
#aws #aurora #rds #troubleshooting

Не уверен, что материал будет простым, но публикую как есть, как всё знал на момент этой стори.


Ко мне снова обратились с привычной проблемой: AWS, есть Aurora MySQL 3.*, большая нагрузка по кверям, увеличили инстанс, добавили CPU и памяти, даже меняли IOPS - но тормоза не ушли.
Особенно заметно на пиковых нагрузках, когда в систему летят десятки тысяч инсертов в секунду.

Знакомая история, которая всегда начинается с "мы уже всё перепробовали" и заканчивается у моего терминала.
Таска говно - впарить лоху (мне).
Не, ну ладно, зарплата сама себя не отработает, поехали разбираться.

Делаю привычное SHOW PROCESSLIST - вижу волну wait/io/redo_log_flush.
Коммиты висят по 50-100 мс, хотя дисковая подсистема вроде бы не жалуется.
Первое, что приходит в голову: а чо там, а чо, давайте смотреть на sync_binlog.
SHOW VARIABLES LIKE 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+

SHOW VARIABLES LIKE 'sync_binlog';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 1 |
+---------------+-------+

Сука.

Бинлог включен. Синхронный.
Каждый коммит ждёт фсинка на диск.
На Aurora MySQL 3.x, где кто-то раньше включил Enhanced Binlog (по дефолту он выключен, но у нас был исторически включён).

Вспоминаю, что на этом проекте был Дебезиум. Вроде бы.
Ладно, зацепка, иду в поиск.
Поднимаю историю по слаку, страницам RFC/PoC в Confluence.

Года три назад у нас был активный конвейер CDC на Debezium - реплицировали данные в Snowflake для аналитики.
Потом проект закрылся, команда разошлась, а инфраструктура осталась.
Кто-то выключил Debezium, кто-то отключил коннекторы, но binlog почему-то остался включённым.
"На всякий случай", "вдруг понадобится", "не трогай, оно работает".
Ну или девопс забыл 🤡 (а девопс там был я, фить-ха! )

Только вот работает оно медленно. Каждый INSERT ... VALUES (...), (...), (...) из условных 5000 строк теперь не просто пишет в таблицу, а ещё и в binlog, синхронно, с фсинком.
На пике нагрузки это добавляет 20-30 мс к каждому батчу.
А когда таких батчей 100 в секунду - получаем как раз наш bottleneck.

Перед тем как вырубать, нужно убедиться, что никто не читает этот binlog.
Вдруг там не только дебезум был.
Сперва в консоли нечто типа:
aws dms describe-replication-tasks --region us-east-1 --query "ReplicationTasks[?Status!='stopped'].ReplicationTaskIdentifier"

aws iam list-roles --region us-east-1 --query "Roles[?contains(RoleName, 'DMS')].RoleName"

aws rds describe-db-parameters --db-parameter-group-name aurora-mysql3-params --query "Parameters[?ParameterName=='binlog_format'].ParameterValue"


Затем аудит самой MySQL:
SHOW BINARY LOGS;
+---------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000123 | 1073741824 | No |
| binlog.000124 | 1073741824 | No |
| binlog.000125 | 536870912 | No |
+---------------+-----------+-----------+

Логи растут, но кто их читает?
Бегу проверять:
SHOW REPLICAS;
Empty set (0.00 sec)

SELECT * FROM mysql.ro_replica_status;
Empty set (0.00 sec)

Никаких реплик.

Проверяю в AWS Console - DMS tasks нет.
Проверяю во всех Kubernetes - нет подов с Debezium, нет коннекторов.
Проверяем в IAM - нет ролей для DMS.
Проверяем в CloudWatch - нет метрик от коннекторов.

Теперь самое главное: спрашиваем бизнес. 😁
Собираем три команды: дата-инженеров, аналитиков, девопсов.
Вопрос простой: "Кто-нибудь реплицирует данные из Aurora куда-то ещё?"

Ответы:
- "Нет, мы теперь всё через Kinesis делаем"
- "Нет, мы используем S3 snapshots"
- "Нет, у нас только internal реплики на Aurora"

Сука.

Ок, Убедился, что binlog никому не нужен.
Теперь можно всё кильнуть.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥4🥰1
#aws #aurora #rds #troubleshooting

Делаем бэкап параметр группы (привычка), потом выключаем в правильном порядке:
Сначала проверяем, включён ли Enhanced Binlog:
SHOW VARIABLES LIKE 'aurora_enhanced_binlog';

Если 1 - значит, он был активен.
Тогда выключаем его правильно:

Выключить Enhanced Binlog:
aurora_enhanced_binlog = 0
binlog_backup = 1 (возвращаем обычный бэкап)


Перезагружаем. Ждём перезагрузки.

Выключить сам binlog:
binlog_format = OFF

Ещё одна перезагрузка.

Важно (тут переуточнить в документации, если сами будете делать):
В Aurora MySQL 3.x binlog_backup = 0 используется только когда Enhanced Binlog включён. Если вы просто выставите binlog_backup = 0 без aurora_enhanced_binlog = 1 - это неправильная конфигурация. Правильный порядок: сначала гасим Enhanced Binlog через aurora_enhanced_binlog = 0, потом уже можно вырубать binlog_format = OFF.

Уже через 10-20 минут после перезапуска смотрим на графики.
А там у нас победа!!!!
Чо там чо там:
- CommitLatency снизился с 45ms до 12ms (99 перцентиль)
- WriteThroughput остался тем же, но WriteIOPS упал на 23%!!!!!!!! (только для этого проекта)
- wait/io/redo_log_flush в Performance Schema перестал быть топ-1 контрибьютором latency
- Инсерты, которые раньше тормозили на 30-50ms, теперь выполняются за 8-15ms

Профиты повсюду:
- Команда разработки радуется.
- Мониторинг перестал краснеть.
- Яблоки вторым урожаем пошли.
- AWS Bill на IOPS снизился на $??? в месяц (не осталось в записях точная сумма).

Итоги:
- Всегда аудируйте binlog.
Даже если вы думаете, что он выключен - проверьте. SHOW VARIABLES LIKE 'log_bin' - ваш лучший друг.
В Aurora MySQL 3.x по дефолту Enhanced Binlog выключен, но могут остаться включёнными legacy-настройки.
- CDC - это не навсегда.
Когда выключаете Debezium, DMS или любой другой CDC инструмент - проверьте, что выключили всё.
Включая aurora_enhanced_binlog.
Особенно на Aurora, где Enhanced Binlog не нужен для internal репликации.
- синхронный binlog на write-heavy workload - убийца performance.
sync_binlog = 1 + инсерты = гарантированная latency.
Если вам не нужен binlog - выключайте через правильную последовательность: сначала aurora_enhanced_binlog = 0, потом binlog_format = OFF.
Если нужен - используйте Enhanced Binlog, он асинхронный и влияет на производительность меньше.
- документируйте зависимости
Если три года назад кто-то включил binlog для CDC - должна быть документация.
Если её нет - придётся расследовать, как детектив.
Используйте aws rds describe-db-parameters для аудита.
- не бойтесь выключать то, что не используете.
База не обидится. Зато станет быстрее.
Но делайте это в правильном порядке и в maintenance window.
- наймите уже DBA-шника, хватит мучать девопсов 😀
Please open Telegram to view this post
VIEW IN TELEGRAM
11🔥6😁2
Заметка ненависти.

Бесят три типа переменных:
1) double negative (Двойное/обратное отрицание).
Примеры:
- https://github.com/VictoriaMetrics/helm-charts/blob/master/charts/victoria-metrics-k8s-stack/values.yaml#L39
operator:
disable_prometheus_converter: false

- https://github.com/VictoriaMetrics/helm-charts/blob/master/charts/victoria-metrics-operator/values.yaml#L224
operator:
enable_converter_ownership: true

Так сказать лучи негодования команде VM @VictoriaMetrics_ru1.

2) unintuitive / non-denoscriptive names (Неочевидные имена)
Пример:
- https://github.com/Terraform-VMWare-Modules/terraform-vsphere-vm/blob/master/examples/example-vmname.tf#L65
module "example-server-fqdnvmname" {
...
fqdnvmname = true

По имени ожидаешь строку с FQDN, а это флаг "создавать имя как FQDN" 🐒
https://github.com/Terraform-VMWare-Modules/terraform-vsphere-vm/blob/master/variables.tf#L144-L148
variable "fqdnvmname" {
denoscription = "If true, the vm will be created using domain variable appended"
type = bool
default = false
}


3) cryptic names (Криптические имена)
Примеры:
- https://github.com/krateoplatformops/eventrouter/blob/main/main.go#L60-L66
var cfg *rest.Config
var err error
if len(*kubeconfig) > 0 {
cfg, err = clientcmd.BuildConfigFromFlags("", *kubeconfig)
} else {
cfg, err = rest.InClusterConfig()
}

cfg без какого‑либо намёка, что это именно Kubernetes REST‑конфиг, а не, скажем, конфиг логгера или чего‑нибудь ещё.
Пока не откроешь импорты и десяток файлов вокруг ни черта не понять.
- https://github.com/vmware-tanzu/vm-operator/blob/main/main.go
func initRateLimiting() {
if rateLimiterQPS == 0 && rateLimiterBurst == 0 {
return
}
cfg := ctrl.GetConfigOrDie()
...
managerOpts.KubeConfig = cfg
}

cfg тут - это kubernetes REST конфиг, через который оператор лезет ко всему API кластера, но по имени это просто "какой‑то конфиг" без намёка, что именно kubeconfig, а не, например, конфиг логгера или самого приложения. Чтобы это понять, нужно либо знать паттерн ctrl.GetConfigOrDie, либо докручивать в голове по managerOpts.KubeConfig = cfg и типам. Бред.

Голанг комьюнити допускает короткие имена вроде cfg для переменных узкой области видимости, но здесь это используется на уровне файла! Алё! Тут сокращение несёт неочевидный смысл.

Бесит короч иногда.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🥰1🙏1👌1
Адвент-календарь декабря 2025 ❤️

После шикарных подарков от CloudFlare и GitHub, красочно подхватывает эстафету и выдаёт DockerHub.
https://www.dockerstatus.com/
3🔥14
#пятница #всратость

На работе защита везде и всюду:
- рабочий ноутбук без прав администратора, с целым зоопарком антивирусов и систем контроля доступа.
Сам Евгений по утрам приезжает и лично проверяет твою авторизацию.

- вход в кубер через SSO, OIDC, VPN со временем сессии 2 часа, ролевая модель строже и острее, чем шутки в 1937 году, девелоперы могут быть только в UI ArgoCD с read-only, а каждый чих логирует SIEM, и если ты случайно нажал F5 чаще, чем раз в 5 секунд, срабатывает правило "Potential Brute Force" и тебя блочат.

- никакого интернета, только внутренние ресурсы. Везде distroless или SBOM, имаджи сканирует Trivy, депенданси бот обновляет версии. Вайтлист на реджистри, OPA. Три контура container registry, с ручной перекладкой имаджей и immutable тегами. Три контура CICD с разделением доступов по командам и подпись образа через Cosign.

- внутри нетворк полиси, ингрессы с сертами, самообновляющиеся каждые 7 дней, свой собственный удостоверяющий центр, мтлс и даже егрессы, чтобы чётко знать куда и чего. Инженерный комитет еженедельно составляет список обязательных сертификатов и ключей, которые обязаны быть в контейнерах.

- инфобеза угорает по файрволам между immutable Talos нодами и натягивает Istio везде, даже на глобусы. В системе логируется буквально всё: ивенты, ивенты ивентов, даже команды от дебаг контейнеров типа ls. Собственно и kubectl debug не работает, когда ты пытаешься запустить busybox, ведь OPA его блокирует, потому что не в вайтлисте. Все логи не просто собираются, а дублируются в три разных S3-бакета, каждый из которых зашифрован своим KMS-ключом, который ротируется каждые 2 часа

- всё обвешано киверно политиками, от замены пути к имаджам, до принудительного обмазывания капабилитис. Уже оператор Trivy сканирует в рантайме имаджи, пофиг, что в CI/CD уже сканировалось.

- повсеместно селинукс, секьюрити контекст, рутлес, капабилитис.
Всем запрещено монтирование секрета куба, доступ к нему только по заявке и аппрувом Его Самого.

- все коммиты в git проходят через 4 уровня ревью: обычный code review, security review, compliance review и финальный ревью от инфобеза, где они проверяют, не закодировал ли ты в YAML отступах какие-нибудь вредоносные секреты.
Каждый мердж в мастер требует подписи руководителя департамента

- пароль от БД не знает даже дба - оператор сам создает пароль и хранит в волте, доступ только у оператора, пароль инжектится напрямую в апп, даже в секретах не узнать. Все проходят PCI DSS v4, SOC2 и даже DRP в случае нападения казаков.

- всюду SAST, DAST, DU HAST, CIS, SCA, хоть и не все понимают что это и зачем, но покорно используют

- админы опеншифта и кубера общаются между собой исключительно через вебхуки и admission-контроллеры

- каждый месяц проходишь тесты на полиграфе, доказывая, что никаких закладок не делал, ты всего-лишь пилил пайплайны. Вокруг датацентра ров с акулами, колючей проволокой в башкирском мёде, вертолёты и собственная армия с малой авиацией. При выдаче USB-токена при старте работы проходишь квест убегания от служебных собак, тренировка на скорость и выносливость, ведь слабаки нам не нужны.
.
..
...
А в это время в Перми, весело щебеча, словно воробушек, операционистка Валентина делает "клац-клац" фотокамерой с телефона, сливая все данные клиентов на экране монитора её панели оператора, за сумасшедшие 3500 рублей, которые ей пообещали анонимы в телеграме, наплевав на DLP, ведь она об этом даже и не знала.
.
..
...
Инфобеза тратит ещё миллиард денег, чтобы провести расследование и внезапно доказывает, что виноваты девопсы, опять всё сделали не так. Ведь так оно и есть. Во всём везде и всегда виноваты девопсы.
15🤣31🔥6😁6💯61
#мысли #kubernetes

Выходные закончились, новый год успешно встречен, яма 100+ уровня и 240+ парагон взяты, а все данжи и боссы зачищены в 11 сезоне Diablo 4, а это значит пора готовиться к работе. И что у нас впереди? А впереди у нас очередная версия кубера.

Чем больше читаю про изменения куба в 1.35, тем больше жалею всех людей, кто работает в экосистеме Kubernetes.
Это просто невероятно, как кубер развивается ежеквартально/ежегодно.
Как всё это помнить, знать, отвечать на собеседовании, траблшутить, когда так часто и многое меняется (в рамках одного года - четырёх релизов).

Заморочился и подцепил через MCP/context7/etc всё, что связано с кубом, но даже последние версии LLM не всегда корректно отвечают на вопросы по кубернетису, особенно всё, что связано с lifecycle и security.
Пришлось написать собственный MCP для работы с компонентами k8s в git и пару tools/noscripts для аналитики.

Чтобы всё знать и понимать на уже глубоком уровне требуется не только читать официальную документацию, но так же и сторонние ресурсы, где авторы детально разбирают многие механизмы, рисуют диаграммы, приводят примеры манифестов.
Ну типа
- https://www.redhat.com/en/blog/kubernetes-deep-dive-api-server-part-1
- https://www.ivinco.com/blog/a-deep-dive-into-kubernetes-networking
- https://learnkube.com/kubernetes-long-lived-connections
- https://www.opcito.com/blogs/advanced-kubernetes-security-posture-management-deep-dive
- https://blog.devops.dev/mastering-kubernetes-architecture-a-deep-dive-for-devops-and-platform-engineers-f0376c319d45
Все не буду перечислять, это лишь пример.

С таким подходом и быстрым развитием уже через 3-4 года я смогу и не узнать тот кубер, что был в 2019, когда я впервые в него потыкал. Впрочем, может оно и к лучшему.

- - -
* спасибо славным ребятам из Фланта, которые квартал за кварталом и год за годом отлично пишут заметки про изменения новых версий ❤️
👍17🔥42
#devops #security #nginx #tls #privacy

Тихо и незаметно (для меня) NGINX 1.29.4 официально добавил поддержку Encrypted Client Hello (ECH).
Для инженеров, которые следят за приватностью трафика, это важная новость.

Что такое ECH и почему это важно.
ECH - расширение TLS 1.3, которое шифрует полностью весь ClientHello, включая Server Name Indication (SNI).
Раньше SNI передавался открытым текстом: любой наблюдатель на сети видел, к какому домену вы коннектитесь, даже если содержимое соединения зашифровано. ECH устраняет этот пробел.


ECH использует два ClientHello:
- ClientHelloOuter - видимый, с "dummy" SNI
- ClientHelloInner - зашифрованный, с реальным SNI и другими параметрами

Шифрование происходит через HPKE (Hybrid Public Key Encryption).
Если сервер не может расшифровать inner, он завершает handshake с outer и сообщает клиенту актуальный ключ для повторной попытки.

Важные требования:
- DNS-over-HTTPS обязателен - без него публикация ключа в DNS небезопасна
- Частая ротация - рекомендуется ротировать ключи каждый час (как Cloudflare)
- HTTPS-записи -должны быть корректно настроены с параметром ech=

Для инженера это значит:
- провайдер или DPI-бокс не увидит, куда идёт трафик из вашей инфраструктуры
- метаданные перестают быть источником утечек
- комплаенс с требованиями privacy-by-design становится проще

Что уже работает:

- NGINX принимает директиву ssl_ech_file /path/to/ech-keys.pem
В контексте http/server - она указывает PEM‑файл с ECHConfig и ключами, необходимыми для включения TLS 1.3 ECH в shared‑режиме.
​- поддержка HTTP и Stream модулей
​- ротация ключей через несколько файлов и nginx -s reload - директива ssl_ech_file может указываться несколько раз для разных файлов, что позволяет операционно ротировать ECH‑ключи.
​- мониторинг через переменные $ssl_ech_status и $ssl_ech_outer_server_name - они дают статус обработки ECH

Чего мне пока не хватает для продакшена:
- стабильная версия OpenSSL с ECH
ECH поддерживается только в экспериментальном бранче feature/ech OpenSSL.
Хотя OpenSSL 3.5 LTS вышел в апреле 2025, ECH не вошел в основной релиз и требует сборки из специального бранча.
- отсутствие интеграции с инфраструктурой
Let’s Encrypt и другие CA пока не выдают ECH‑ключи через ACME‑протокол - механизм не стандартизирован. Обычные TLS‑сертификаты работают с ECH без изменений, но получить и автоматически обновить ECHConfig‑ключи придётся вручную.
Публикация ключей в DNS (через HTTPS/SVCB‑записи) требует ручного управления или кастомных скриптов.
- ограниченная поддержка клиентами
Хотя Chrome и Firefox поддерживают ECH, некоторые корпоративные библиотеки еще не обновлены.

Как же это плюсы для для девопс-команд?
1) Privacy-focused сервисы🔥🔥🔥
server {
listen 443 ssl http2;
ssl_ech_file /etc/nginx/ech-keys.pem;
# Теперь SNI не виден провайдеру!
}


2) Мультитенант платформы
- скрываете имена клиентских доменов от внешних наблюдателей
- полезно при shared infrastructure с чувствительными данными

3) Корпоративные VPN-шлюзы 🔥🔥🔥
- скрываете конечные точки внутри корпоративной сети
- провайдер видит только коннект к VPN-шлюзу, не к конкретным сервисам

Что делал я для домашней лаборатории:
git clone -b feature/ech https://github.com/openssl/openssl.git
./config --prefix=/opt/openssl-ech && make && sudo make install

./configure --with-openssl=/opt/openssl-ech \
--with-http_ssl_module \
--with-stream_ssl_module
make && sudo make install

/opt/openssl-ech/bin/openssl ech_keygen -out /etc/nginx/ech-keys.pem

+ крайне важно не забыть добавить DNS публикацию ECH-ключей.
Ключи ECH не работают изолированно - их обязательно нужно опубликовать в DNS через HTTPS-записи (SVCB/HTTPS-тип).
Без этого клиенты не узнают, что сервер поддерживает ECH.
_443._https.example.com. IN HTTPS 1 . ech=AEj+DQBE..
313🆒2👍1🤯1
Для продакшена предполагаю будет так:
- подождать OpenSSL 4.0 (ориентировочно апрель 2026)
- дождаться стабильного релиза NGINX с ECH (сейчас, как я понял, это не стейбл, а мейнлайн)
- подготовить скрипты ротации ключей
- протестировать с реальными клиентами (A/B)
- мониторить логи через $ssl_ech_status для выявления проблем

Вывод (для меня):
ECH в NGINX - это важный первый шаг, но не готовое решение для продакшена.
Пока это инструмент для пионеров, которые готовы собирать из сорсов и тестировать в лабораторных условиях.

Рекомендация (для остальных):
Начинайте изучать технологию, готовьте тестовые стенды, но не спешите в прод.
Когда выйдет OpenSSL 4.0 и основные клиенты добавят поддержку - ECH станет must-have для privacy-conscious инфраструктуры.

А сейчас это хорошая возможность изучить что-то новое.

* Дополнительный материал:
- https://blog.nginx.org/blog/nginx-open-source-1-29-3-and-1-29-4
- https://nginx.org/en/docs/http/ngx_http_ssl_module.html
- https://developers.cloudflare.com/ssl/edge-certificates/ech/
- https://blog.cloudflare.com/encrypted-client-hello/
- https://datatracker.ietf.org/doc/draft-ietf-tls-esni/25/
1👍15
#cloudfront #aws #troubleshooting #одинденьизжизни

Однажды меня попросили сделать перенос старого SPA (Single Page Application)-приложения на новый S3 bucket в CloudFront.
Но не просто перенос, а хитрый: часть путей должна идти на новое приложение, а часть - оставаться на старом.
Legacy страницы типа /welcome, /login-start, /login-end пока не переписаны в новом коде, их надо сохранить.
А всё остальное - на свежий app-v2.
По сути частичная миграция на новое приложение, не ломая старое.

Казалось бы, что может пойти не так? 😁

Задача звучит просто:
- /* > новый bucket (app-v2)
- /welcome*, /login-start*, /login-end*, /help-bot* > старый bucket
- /assets/*, /sdk.js > старый bucket

Открываю Terraform, добавляю новый origin для app-v2 bucket.
Меняю default_cache_behavior на новый origin.
Добавляю ordered_cache_behavior для legacy путей на старый bucket.
Terraform plan - всё как задумано.
Apply. Жду. Готово.

Иду проверять.
- https://stage-app.example.com/ - работает, новое приложение грузится.
- https://stage-app.example.com/?appId=xxx - работает, редиректит.
- https://stage-app.example.com/welcome - ...

"Server Error. An unexpected error happened."


Чо. 🤡

Первая мысль - кэш CloudFront. Делаю invalidation. Жду. Проверяю.
Та же херня.

Вторая мысль - может origin неправильный?
Проверяю напрямую S3 bucket:
curl -I "http://stage-app.example.com.s3-website-us-east-1.amazonaws.com/index.html"
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 4755

Bucket работает. index.html есть. 4755 байт. Всё ок.

Третья мысль - может custom_error_response мешает?
В CloudFront есть глобальный обработчик 404 ошибок, который возвращает /index.html.
Думаю: "А, наверное S3 возвращает 404, а CloudFront берёт index.html с дефолтного origin!"
Убираю custom_error_response. Apply. Проверяю.
Та же херня. Даже хуже - теперь вместо "Server Error" просто "Not found".

Возвращаю custom_error_response. Сижу, чешу репу.

Ладно, поехали курлить по-взрослому.
curl -sI "https://stage-app.example.com/welcome"
HTTP/2 200
content-type: text/html
x-cache: Hit from cloudfront

Стоп. 200? Не 404? CloudFront отдаёт 200 и HTML?
Смотрю body:
curl -s "https://stage-app.example.com/welcome" | head -20

Да, это HTML старого приложения. webpackJsonstatham@app/web. Всё верно.
Так почему "Server Error" в браузере?

Если HTML грузится, значит проблема в JavaScript.
Приложение падает после загрузки.
Открываю DevTools > Network.

И тут я вижу ЭТО:
/static/js/main.a1b2c3d4.chunk.js → content-type: text/html 
/static/css/main.e5f6g7h8.chunk.css → content-type: text/html

JS файлы возвращают HTML вместо JavaScript.
Браузер пытается выполнить HTML как JavaScript.
Приложение падает с "Server Error".
Сука. 😁

Проверяю напрямую S3:
curl -sI "http://stage-app.example.com.s3-website-us-east-1.amazonaws.com/static/js/main.a1b2c3d4.chunk.js"
HTTP/1.1 200 OK
Content-Type: text/javanoscript
Content-Length: 1080675

S3 отдаёт правильно! 1MB JavaScript!

А через CloudFront:
curl -sI "https://stage-app.example.com/static/js/main.a1b2c3d4.chunk.js"
HTTP/2 200
content-type: text/html
x-cache: Error from cloudfront

CloudFront отдаёт HTML. И x-cache: Error from cloudfront. Красота.

Теперь понятно что происходит.
Сажусь рисовать на планшете путь запроса (а я всегда рисую).

Проблема:
1. Браузер > /welcome
2. CloudFront матчит /welcome* > origin: OLD bucket
3. S3 не находит файл /welcome (это SPA route, не файл)
4. S3 возвращает error_document = index.html
5. Браузер получает HTML старого приложения

6. HTML содержит: <noscript src="./static/js/main.a1b2c3d4.chunk.js">
7. Браузер резолвит ./static/... относительно /welcome
8. Браузер запрашивает /static/js/main.a1b2c3d4.chunk.js

9. CloudFront матчит /* (default) > origin: app-v2 bucket
10. В app-v2 bucket НЕТ файла main.a1b2c3d4.chunk.js!
11. S3 возвращает 404
12. CloudFront custom_error_response > /index.html
13. Браузер получает index.html (HTML) вместо JavaScript
14. JavaScript парсер: "че за херня, это не JS"
15. Приложение: "Server Error"🐒
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
#cloudfront #aws #troubleshooting #одинденьизжизни

Вот оно.
Два разных SPA с разными static assets в одном CloudFront distribution.
Legacy paths идут на старый bucket, но их ./static/* резолвится как /static/* и летит на default origin - новый bucket.
А там этих файлов нет.

Классика жанра: симптомы в одном месте, причина в другом.
Ошибка "Server Error" от JavaScript, а проблема в CloudFront routing.
Логи чистые, метрики зелёные, status code 200 - всё отлично, только ничего не работает.

Для визуалов:
ДО (работало):
══════════════════════════════════════════
CloudFront


┌─────────┐
│ OLD S3 │ ← всё идёт сюда
│ bucket │
└─────────┘

ПОСЛЕ (сломалось):
══════════════════════════════════════════
CloudFront

┌───────────┴───────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ OLD S3 │ │ NEW S3 │ ← default
│ bucket │ │ bucket │
└─────────┘ └─────────┘
/welcome* /* (всё остальное)
/login-* включая /static/*


Ну или вот так, что не работало (красиво только на ПК):
  Browser                     CloudFront                        S3
| | |
| GET /welcome | |
|------------- req ---------->| |
| | /welcome* --> OLD bucket |
| |------------- req ---------->| OLD bucket
| | | (app-v1)
| |<------------ res -----------| index.html
|<----------- res ------------| HTML + <noscript> |
| | |
| GET /static/js/main.js | |
|------------- req ---------->| |
| | /* (default) --> NEW |
| |------------- req ---------->| NEW bucket
| | | (app-v2)
| | |
| | main.js NOT FOUND! |
| |<------------ 404 -----------|
| | |
| | custom_error_response |
| | 404 --> /index.html |
| |------------- req ---------->| NEW bucket
| |<------------ res -----------| index.html
|<----------- res ------------| HTML (not JS!) WRONG! | (app-v2!)
| | |
| "Server Error" | |
| JS parser: WTF?! | |


Мораль:
Нельзя без дополнительной логики безопасно смешать два SPA с разными static assets в одном CloudFront distribution, если они используют пересекающиеся пути (/static/*) и простой path-based routing.
Относительные пути (./static/...) превращаются в абсолютные (/static/...) и летят на default origin.
И не всегда виноват девопс, что криво пилит редирект или бихейвиор. 😀

Решения:
- отдельные CloudFront distributions для старого и нового приложения
- Cloudflare Workers / Lambda@Edge для умного роутинга
- добавить /static/* behavior на старый bucket (но тогда сломается новое приложение)
- не переключать default, тестировать новое приложение на отдельном домене

Я выбрал вариант 4 (вроде, уже не помню) и пошёл пить кофе.

А RFC переписали, убрав требование "переключить трафик через один CloudFront".
Иногда лучшее решение - не решать задачу в лоб.
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍3
Make. Build. Break. Reflect.
#пятница #байки #troubleshooting Задолго до работы в айти я был простым советским инженером спутниковой связи. Тарелки, антенны, модемы, кабели, бесконечные командировки. В то время случалось масса странных историй. Мы разрабатывали спутниковое оборудование…
#байки #пятница #tcp #networking

Когда-то давно я работал в компании, которая делала железки для спутниковой связи.
Не те, что вы вешаете на дачу для ТВ, а серьёзные штуки - морские терминалы VSAT, Inmarsat-модемы для судов, станции для нефтянки в тайге.
Короче, интернет там, где провода не дотягиваются.

И вот первое, что узнаёшь в этой индустрии:
всё, что ты знал о сетях - забудь. 🙃

⚠️ Важно: всё, что ниже - про геостационарные спутники (GEO).
Это классический VSAT, Inmarsat, морская и промышленная связь.

Starlink, OneWeb и прочие LEO-созвездия - совсем другая история.
Там спутники летают на 550 км вместо 36 000, RTT составляет 20-40 мс, и проблемы с TCP практически отсутствуют.
Но это технология 2020-х, а я работал в эпоху, когда GEO был единственным вариантом.


Геостационарный спутник висит на высоте ~36 000 км.
Сигнал от твоего модема до спутника и обратно - это примерно 72 000 км.
Дальше сигнал летит на наземную станцию (HUB), там обрабатывается, и только потом идёт в интернет.
А ответ - всё в обратном порядке.
                        🛰 СПУТНИК
(36 000 км)
/ \
/ \
↗️ / \ ↘️
/ ~36 000 км \
/ в каждую \
/ сторону \
/ \
┌──────────────┐ ┌──────────────┐
│ ТВОЙ МОДЕМ │ │ HUB │
│ (корабль, │ │ (наземная │
│ платформа) │ │ станция) │
└──────────────┘ └──────┬───────┘

│ ③ оптика

┌──────────────┐
│ ИНТЕРНЕТ │
└──────────────┘


Суммарное расстояние: ~144 000 км (минимум, на экваторе).
Скорость света в вакууме: 299 792 км/с.
RTT = 144000 / 299792 ≈ 480 мс (только свет!)


Добавь обработку на спутнике, на HUB'е, в модеме - получаешь 600-800 мс RTT.
И это не баг. Это физика. Скорость света не обманешь.

Классический звонок от клиента:
- Алло, вы нам поставили модем на 10 мегабит, а у нас файлы качаются со скоростью 500 килобит!
- А какой размер файла?
- 100 мегабайт!
- А пинг какой?
- 700 миллисекунд...
- (вздох) Сейчас объясню.


И начинается лекция по TCP, которую никто не хотел слышать.
Но вам я расскажу.

Bandwidth-Delay Product: труба, которую надо заполнить
TCP работает так: отправил данные > ждёшь ACK > отправляешь ещё.
Сколько данных можно "запихнуть" в сеть до получения первого ACK, ограничено TCP Window.

А сколько данных должно лететь одновременно, чтобы канал был загружен?
Это называется BDP (Bandwidth-Delay Product):
BDP = Bandwidth × RTT

Считаем для нашего случая (все каналы 10 Мбит/с):
LAN (RTT 1 мс):
BDP = 10 000 бит ≈ 1.25 КБ
WAN (RTT 50 мс):
BDP = 500 000 бит ≈ 62.5 КБ
Спутник (RTT 700 мс):
BDP = 7 000 000 бит ≈ 875 КБ

Видишь разницу?

Чтобы полностью загрузить 10 Мбит спутниковый канал, надо держать почти мегабайт данных "в полёте" одновременно.
А дефолтное TCP окно в старой винде - 64 КБ. 😭

Математика боли
Максимальный throughput TCP (без потерь) приблизительно равен:
Throughput ≈ Window Size / RTT

Подставляем:
Window = 64 КБ = 524 288 бит
RTT = 0.7 с
Throughput = 524288 / 0.7 ≈ 749 000 бит/с ≈ 730 Кбит/с

730 Кбит/с на 10 Мбит канале.

Семь процентов утилизации.

Клиент платит за 10 мегабит, а получает 730 килобит.
И это не наш модем плохой. Это TCP.
Хотя клиент думает обратно. 😀

А давайте потюним TCP!
Конечно, никто ж до этого не догадался же, ага.
Ладно, мы ж не дураки. Знаем про sysctl, знаем про window scaling.

На Linux:
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
sysctl -w net.ipv4.tcp_window_scaling=1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥3
Make. Build. Break. Reflect.
#пятница #байки #troubleshooting Задолго до работы в айти я был простым советским инженером спутниковой связи. Тарелки, антенны, модемы, кабели, бесконечные командировки. В то время случалось масса странных историй. Мы разрабатывали спутниковое оборудование…
#байки #пятница #tcp #networking

На Windows (реестр, куда ж без него):
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
TcpWindowSize = 65535 (или больше с window scaling)
Tcp1323Opts = 3 (timestamps + window scaling)


Помогает? Да, частично.
Но есть нюансы:
- Обе стороны должны поддерживать большие окна.
А сервер где-нибудь в Китае - фиг знает как настроен.
- Потери убивают всё.
При 1% потерь и 700 мс RTT скорость падает катастрофически.
- Slow Start
TCP начинает медленно и наращивает окно раз в RTT. При 700 мс это оооочень долго.

Короче, тюнинг помогает, но не спасает. Нужен костыль уровня "бог".

TCP Acceleration: красивый обман
И тут на сцену выходит PEP - Performance Enhancing Proxy.
В народе - TCP Accelerator. Маркетологи любят это слово.

Идея гениальна в своей наглости.
БЕЗ PEP (честный TCP):

Client Satellite (700ms) Server
│ │
│───── DATA ───────────────────────────────>│
│ 700 ms │
│<─────────────────────────────────── ACK ──│
│ 700 ms │
│───── DATA ───────────────────────────────>│
│ Total: очень медленно │


С PEP (хитрый обман):

Client Local PEP Satellite Remote PEP Server
│ │ 700ms │ │
│── DATA ─>│ │ │
│<── ACK ──│ (мгновенно!) │ │
│── DATA ─>│ │ │
│<── ACK ──│ │ │
│── DATA ─>│ │ │
│<── ACK ──│─────── DATA ───────────>│ │
│ │ (много данных сразу!) │── DATA ──>│
│ │ │<── ACK ───│
│ │ │ │
│ Client думает, На самом деле данные │
│ что всё уже ещё летят по спутнику, │
│ доставлено! но клиент уже шлёт ещё! │

Как это работает:
1. На стороне клиента стоит модем с PEP (наша железка).
2. На стороне HUB'а - ответная часть PEP.
3. Клиент отправляет TCP-пакет.
4. Локальный PEP сразу отвечает ACK, не дожидаясь ответа с сервера.
5. Клиент думает "ура, данные доставлены!" и шлёт ещё.
6. Тем временем PEP буферизирует данные и гонит их по спутнику *своим протоколом* (оптимизированным под high latency).
7. Удалённый PEP получает данные и уже по-честному передаёт серверу.

По сути, мы разрываем TCP-сессию на два локальных сегмента с низким RTT, а между ними гоним трафик специальным протоколом.

Клиент видит: "пинг 700 мс, но скорость 10 мегабит!" 🎉
Магия? Нет, честный обман. 😁

Конечно, есть ограничения:
- Память не бесконечна.
Каждая "ускоренная" сессия жрёт буфер. Модем на 100 сессий - это одно, на 10 000 - совсем другие деньги.
- При потере связи - боль.
Если спутник моргнул, а PEP уже насобирал мегабайт данных "в кредит" - это всё надо переслать. А клиент уже уверен, что данные доставлены.
- Шифрование.
HTTPS, VPN, IPsec - PEP не может влезть в сессию, не может подменить ACK. Приходится либо терминировать SSL на PEP (что не всегда возможно), либо PEP работает только на транспортном уровне и не так эффективен.
- Оборудование на обеих сторонах.
Если у тебя PEP только на модеме, а на HUB'е нет - толку мало. Поэтому это работает в контролируемых сетях VSAT.

Вместо морали
Работая со спутниковой связью, начинаешь по-другому смотреть на сети.
Когда твой пинг 700 мс, ты понимаешь, что TCP придумали для LAN.
Твой клиент на корабле посреди Тихого океана жалуется на скорость, ты не можешь сказать "перезагрузите роутер".
Тебе надо найти решение.

Если вы думали, что всё знаете о сетях и TCP просто представьте:
Вы на нефтяной платформе в Северном море.
С ноутбуком 2005 года.
И Windows XP.
И TCP-окном в 64 КБ.
И RTT 800 мс.
А на улице идёт дождь, который дарит помехи.
И надо срочно скачать огромный файл.

Вы знаете о TCP и сетях не всё 😀
Please open Telegram to view this post
VIEW IN TELEGRAM
👍324🔥4