Главная/Справочники/Справочник

Каталог антипаттернов

62 записей в восьми группах. Учебная версия с разбором — урок 19.4; здесь тот же каталог в виде справочника для ревью.

Как устроена запись

ПолеЗачем
АнтипаттернЧто именно сделано
Почему выглядит разумнымГлавное поле: люди не делают заведомо глупого
Чем оборачиваетсяПоследствие, а не нарушенное правило
Как правильноДействие, а не пожелание

Второе поле важнее остальных. Антипаттерн живёт не потому, что о нём не знают, а потому, что он решает настоящую проблему негодным способом. Пока не назван способ лучше, совет «не делайте так» не работает.

Как пользоваться при ревью

  1. Пройти механически проверяемое линтером — см. security checklist и production checklist.
  2. Пройти глазами то, что линтер не находит.
  3. Записать находки с последствием, а не с нарушенным правилом.
  4. Приоритизировать: риск, делённый на стоимость исправления.
  5. Назвать не менее двух вещей, сделанных хорошо.

Пятый пункт не вежливость: отчёт из одних находок читается как обвинение и приводит к спору вместо исправлений.


Сборка образов

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
1FROM ubuntu:latestВсегда свежий базовый образСборка невоспроизводима: вчера и сегодня разные образыКонкретный тег, а лучше digest (14.3)
2COPY . . до установки зависимостейОдна инструкция вместо двухЛюбое изменение кода сбрасывает кэш установкиСначала файл зависимостей, потом код (5.7)
3RUN apt-get update отдельно от installЛогическое разделение шаговКэшированный update даёт установку из устаревшего индексаОдна инструкция update && install
4Кэш пакетного менеджера не удалён в том же слоеУдалили следующей строкойФайлы остались в предыдущем слое; образ большеУдаление в той же RUN (3.2)
5COPY secret и RUN rm secretСекрет удалёнОн остаётся в слое и извлекаетсяRUN --mount=type=secret (12.6)
6Отладочные инструменты в финальном образеПригодятся при разбореПлощадь атаки растёт; образ тяжелееОтдельная стадия или временный container (13.5)
7ADD для локальных файловДелает то же и большеРаспаковывает архивы и качает URL неявноCOPY, а загрузку — явной командой
8Нет .dockerignoreНе выглядит обязательнымКонтекст раздувается; .git и секреты попадают в образ.dockerignore с первого дня (5.4)
9pip install без фиксации версийВсегда последние версииДве сборки подряд дают разные образыФайл блокировки (6.4)
10Сборка в финальном образеПроще один DockerfileКомпиляторы и заголовки едут в эксплуатациюMulti-stage (5.9)
11RUN pip install --user от root, затем USERКажется безопаснееПакеты в домашнем каталоге root, недоступныУстановка после смены пользователя или в общий префикс
12Каждая команда — своя RUNЧитается лучшеСлоёв больше, кэш дробитсяГруппировать связанные операции

Запуск и жизненный цикл

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
13CMD python app.py (shell-форма)Короче и привычнееPID 1 — оболочка; SIGTERM не доходит до приложенияExec-форма (4.6)
14Запуск от rootТак работает по умолчаниюUID 0 внутри равен UID 0 снаружи (19.2)USER с числовым идентификатором
15USER app вместо USER 10001Читается понятнееKubernetes не может проверить runAsNonRootЧисловой UID (18.2)
16Несколько процессов под супервизоромПохоже на привычный серверОтказ одного не виден снаружи; масштабирование невозможноОдин процесс на container (2.4)
17sleep infinity как точка входаContainer перестаёт «падать»Скрывает отказ; сервис мёртв, container живУстранить причину выхода
18Нет обработки SIGTERMПриложение и так завершаетсяЧерез 10 секунд SIGKILL; незавершённые операцииОбработчик и мягкое завершение (6.7)
19restart: always вместо диагностикиСервис «сам поднимается»Цикл падений маскирует причинуРазобрать причину, затем политика перезапуска
20tail -f /dev/null рядом с сервисомУдобно заходить внутрьТо же, что выше: container живёт без сервисаОтдельный container для отладки
21Долгая инициализация без startup-пробыРаботает жеLiveness убивает приложение до готовностиStartup-проба (11.3)

Данные

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
22Данные в записываемом слоеРаботает без настройкиИсчезают при пересоздании container'аТом (7.2)
23Bind mount каталога хоста в эксплуатацииФайлы видно снаружиПрава, владельцы, привязка к машинеИменованный том
24Нет резервного копирования тома«Данные в Docker»Docker копированием не занимается (19.1)Внешняя процедура копирования и проверка восстановления
25chmod 777 на томеРешает проблему прав немедленноЛюбой процесс может всёСовпадающие UID и GID (7.5)
26Логи в файл внутри container'аКак на обычном сервереНе видны сборщику; растут в слоеstdout и stderr (13.1)
27База данных в эксплуатации без ответа «как восстановим»Compose поднимает её одной строкойПотеря данных при первом же отказеОтветить на шесть вопросов (19.1)
28Том для кэша, который не жаль потерятьЕдинообразиеЛишняя сущность в эксплуатацииtmpfs или без тома

Сеть

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
29--network host для производительностиУбирает NAT и vethИзоляция снята целиком (19.1)Измерить, действительно ли сеть узкое место
30Обращение по IP-адресуАдрес известен и стабиленАдреса меняются при пересозданииОбращение по имени сервиса (8.4)
31Публикация портов «на всякий случай»Пригодится для отладкиСервис доступен снаружи без надобностиПубликовать только нужное
32links вместо сетейВстречается в старых примерахУстаревший механизмПользовательские сети (8.3)
33Все сервисы в одной сетиПроще настроитьСервис базы доступен всемРаздельные сети (9.4)
34Публикация на 0.0.0.0 вместо 127.0.0.1Так по умолчаниюПорт открыт наружу, а не только локальноЯвный адрес привязки

Конфигурация и секреты

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
35ENV DB_PASSWORD=... в DockerfileПросто и работаетВиден в docker history и в inspectСекрет при запуске (12.6)
36Секрет через ARG при сборкеНе попадает в ENVОстаётся в метаданных сборкиRUN --mount=type=secret
37.env внутри образаКонфигурация «едет с приложением»Секреты в образе; один образ на окружениеФайл монтируется или задаётся окружением
38Отдельный образ на каждое окружениеКажется надёжнееПроверенный образ и запущенный — разныеОдин образ, разная конфигурация (11.2)
39Секреты в compose.yaml в репозиторииУдобно и всё в одном местеУтечка при первом же клонированииВнешний файл вне репозитория
40Конфигурация правится docker execБыстрее, чем пересобиратьИзменение исчезнет при пересозданииИзменить источник конфигурации
41Один общий секрет на все сервисыМеньше сущностейКомпрометация одного равна компрометации всехОтдельный секрет на сервис

Compose

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
42depends_on без conditionВыглядит как порядок запускаЖдёт запуска, а не готовностиcondition: service_healthy и повторы в приложении (9.3)
43image: myapp:latestВсегда актуальная версияНепонятно, что именно запущеноКонкретный тег или digest
44Один файл на все окруженияМеньше файловОтладочные настройки попадают в эксплуатациюБазовый файл плюс override (9.5)
45container_name у масштабируемого сервисаУдобно обращаться по имени--scale перестаёт работать: имя занятоОбращение по имени сервиса
46build: в файле для эксплуатацииОдин файл на всёВ эксплуатации собирается вместо запуска готовогоРаздельные файлы: сборка и запуск
47version: в начале файлаТак во всех старых примерахПоле устарело и игнорируетсяУбрать (9.2)
48Тома объявлены, но не в volumes:Работает и такСоздаётся анонимный том; данные теряютсяОбъявить именованный том

Эксплуатация

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
49Исправление через docker execБыстро чинит проблемуИсчезнет при пересоздании; расхождение с образомИсправить образ и выкатить
50Нет ограничений ресурсовПриложение и так не жрётОдна утечка останавливает всю машинуmem_limit и cpus (11.4)
51docker system prune -a в эксплуатацииКончилось местоУдаляет образы, нужные для откатаЦелевая очистка с пониманием, что удаляется
52Нет healthcheckContainer же запущен«Запущен» не значит «работает»Healthcheck (11.3)
53Перезаписываемый тег prodОдно имя, всегда актуальноеОткатиться некуда: старого образа нетНеизменяемые теги плюс digest
54Логи не ограничены по размеруДрайвер по умолчаниюДиск заполняется логамиРотация в настройке драйвера (13.1)
55Монтирование docker.sock в сервисНужно управлять container'амиРавно правам root на хосте (19.2)Отдельный агент с ограниченными правами
56Сборка образа на машине разработчикаБыстрее, чем ждать CIОбраз невоспроизводим; зависит от машиныСборка в CI (16.1)

Организационные

АнтипаттернПочему выглядит разумнымЧем оборачиваетсяКак правильно
57«Завернём всё в Docker»Единообразие как ценностьЧасть задач не решается контейнеризацией (19.3)Вопрос: что перестанет работать без Docker
58Один человек знает, как собирается образТак сложилосьТочка отказа в людях, а не в системеDockerfile в репозитории, сборка в CI
59Базовые образы не обновляютсяРаботает — не трогайИзвестные уязвимости накапливаютсяРегулярная пересборка (12.7)
60Сканирование настроено, результаты не читаютФормально требование выполненоОтчёт без действий равен отсутствию отчётаПороги и остановка сборки (16.4)
61Копирование Dockerfile из статьи без разбораЭкономит времяПереносятся и антипаттерны из статьиПонимать каждую инструкцию
62Kubernetes «потому что так делают»Индустриальная практикаПостоянная стоимость без выгоды (18.4)Чек-лист принятия решения

Что проверяется механически, а что нет

ГруппаЛинтеромТребует разговора
Сборка образовПочти всё«Понимать каждую инструкцию»
Запуск и жизненный циклФорма CMD, USER, супервизорОбработка SIGTERM в коде
ДанныеОтсутствие тома, chmod 777Есть ли процедура восстановления
Сетьnetwork_host, links, публикацияНужен ли порт снаружи
Конфигурация и секретыСекрет в ENV, .env в образеОдин ли секрет на все сервисы
Composelatest, container_name, versionРазделены ли окружения по смыслу
ЭксплуатацияЛимиты, healthcheck, ротацияИсправления через exec
ОрганизационныеНичегоВсё

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

Приоритизация находок

text
риск      = последствие × вероятность
приоритет = риск / стоимость исправления

Сводить всё к одной оценке «серьёзности» — обычная ошибка. Тогда chmod 777 в образе для локальной разработки и секрет в ENV эксплуатационного образа получают одинаковый вес, хотя первый почти безвреден, а второй уже произошёл.

НаходкаПоследствиеВероятностьСтоимостьПриоритет
Секрет в ENV эксплуатацииВысокое1 (уже)НизкаяНаивысший
Нет .dockerignoreСреднееВысокаяПочти нулеваяВысокий
Нет healthcheckСреднееСредняяНизкаяВысокий
latest в ComposeСреднееСредняяНизкаяСредний
chmod 777 в образе разработкиНизкоеНизкаяНизкаяНе исправлять

Последняя строка обязательна в любом отчёте: каталог перечисляет антипаттерны, а не приговоры.

Границы каталога

Чего здесь нетПочему
Качество кода приложенияКурс про контейнеризацию
Архитектурные решенияКонтейнеризация их не исправляет
Уместность самой контейнеризацииОтдельный вопрос: урок 19.3
Исчерпывающая полнота62 записей покрывают частое, не всё

Навигация

Вернуться к справочникам
Учебная версия с разбором
Security checklist
Production checklist
Карта решений
Главное оглавление

Markdown на GitHub ↗