Эта статья содержит краткую выжимку из моего собственного опыта и опыта моих коллег, с которыми мне днями и ночами доводилось разгребать инциденты. И многих инцидентов не возникло бы никогда, если бы всеми любимые микросервисы были написаны хотя бы немного аккуратнее. К сожалению, некоторые невысокие программисты всерьёз полагают, что Dockerfile с какой-нибудь вообще любой командой внутри — это уже сам по себе микросервис и его можно деплоить хоть сейчас. Докеры крутятся, лавешка мутится. Такой подход оборачивается проблемами начиная с падения производительности, невозможностью отладки и отказами обслуживания и заканчивая кошмарным сном под названием Data Inconsistency. Если вы ощущаете, что пришло время запустить ещё одну аппку в Kubernetes/ECS/whatever, то мне есть чем вам возразить. English version is also available. Я сформировал для себя некий набор критериев оценки готовности приложений к запуску в продакшн. Некоторые пункты этого чек-листа не могут быть применены ко всем приложениям, а только к особенным. Иные в целом применимы ко всему. Уверен, вы сможете добавить свои варианты в комментариях или оспорить какие-то из этих пунктов.  Если ваш микро-сервис не соответствует хотя бы одному из критериев, я не допущу его в свой идеальный кластер, построенный в бункере в 2000 метрах под землёй с подогревом полов и замкнутой самодостаточной системой подачи интернета. Поехали.... Примечание: порядок пунктов не имеет никакого значения. Во всяком случае, для меня. Боже, это кажется так просто. Но как же часто я сталкивался, что репозиторий не содержит ни малейшего объяснения зачем он нужен, какие задачи решает и так далее. Про что-то более сложное вроде вариантов конфигурации и говорить не приходится. Анализ потребления ресурсов, утечек памяти, stacktraces, взаимозависимости сервисов, частота ошибок — без понимания всего этого (и не только) крайне сложно контролировать что происходит в большом распределённом приложении. Метрики это хорошо, но следить за ними никто не будет. Поэтому автоматически получаем звонки/пуши/смс если: Список может сильно отличаться в разных организациях, но хотя бы базовые вещи там должны быть. Когда приложение создаёт файлы с логами — эти логи бесполезны. Вы не будете заходить в 5 запущенных параллельно контейнеров, в надежде поймать нужную ошибку (а вот и будете, плачет...). Перезапуск контейнера приведёт к полной потере этих журналов.  Если приложение пишет логи самостоятельно в стороннюю систему, например в Logstash — это создаёт бесполезную избыточность. Соседний сервис не умеет этого делать, т.к. у него другой фреймворк? Вы получаете зоопарк. Приложение пишет часть логов в файлы, а часть в stdout потому, что разработчику удобно видеть INFO в консоли, а DEBUG в файлах? Это вообще худший вариант. Никому не нужные сложности и совершенно лишний код и конфигурации, которые нужно знать и поддерживать. До сих пор практически все пишут логи в plain text. Это настоящая катастрофа. Я был бы счастлив никогда не знать про Grok Patterns. Они мне снятся иногда и я замерзаю, стараясь не шевелиться, чтобы не привлечь их внимание. Просто попробуйте однажды распарсить исключения Java в логах. Json — это благо, это огонь, подаренный с небес. Просто добавьте туда: Загрузите в любую подходящую систему (правильно настроенный ElasticSearch, например) и наслаждайтесь. Соедините логи множества микросервисов и снова почувствуйте чем были хороши монолитные приложения. (А ещё можно добавить Request-Id и получить tracing...) Желательно, чтобы все сервисы в одной экосистеме поддерживали одну и ту же переменную окружения. Не опция конфига, не опция в командной строке (хотя это оборачиваемо, конечно), а сразу by default из окружения. Вы должны иметь возможность получить как можно больше логов, если что-то идёт не так и как можно меньше логов, если всё хорошо. Об этом много где уже говорилось, конечно. Некоторые фиксируют зависимости на мажорных версиях, надеясь, что в минорных будут только bug fixes и security fixes. Это неправильно.
Каждое изменение каждой зависимости должно быть отражено отдельным коммитом. Чтобы его можно было отменить в случае проблем. Тяжело контролировать руками? Есть полезные роботы, вроде этого, которые проследят за обновлениями и создадут вам Pull Requests на каждое из них. Докер давно стал стандартом для многих компаний. Бывают исключения, но даже если в production у вас не Docker, то любой инженер должен иметь возможность просто выполнить docker-compose up и ни о чём больше не думать, чтобы получить дев-сборку для локальной проверки. А системный администратор должен иметь уже выверенную разработчиками сборку с нужными версиями библиотек, утилит и так далее, в которой приложение хотя бы как-то работает, чтобы адаптировать её под production. Никто и никогда не захочет читать ваши файлы конфигурации и изучать их формат. Просто примите это. Детальнее тут: https://12factor.net/config Если приложение обслуживает HTTP запросы, оно должно по-умолчанию иметь два интерфейса: Для проверки, что приложение живое и не зависло, используется Liveness-проба. Если приложение не отвечает, оно может быть автоматически остановлено оркестраторами вроде Kubernetes, "но это не точно". На самом деле убийство зависшего приложения может вызвать эффект домино и насовсем положить ваш сервис. Но это не проблема разработчика, просто сделайте этот endpoint. Для проверки, что приложение не просто запустилось, но уже готово принимать запросы, выполняется Readiness-проба. Если приложение установило соединение с базой данных, системой очередей и так далее, оно должно ответить статусом от 200 до 400 (для Kubernetes). Конкретная реализация этого пункта будет очень разной в разных организациях и для разных оркестраторов. Однако эти лимиты обязаны быть заданы в едином формате для всех сервисов, быть разными для разных окружений (prod, dev, test, ...) и находиться вне репозитория с кодом приложения. Никогда и ничего не доставляется в production вручную. Как бы ни было сложно автоматизировать сборку и доставку вашего проекта, это должно быть сделано до того, как этот проект попадёт в production. Этот пункт включает сборку и запуск Ansible/Chef cookbooks/Salt/..., сборку приложений для мобильных устройств, сборку форка операционной системы, сборку образов виртуальных машин, что угодно.
Не можете автоматизировать? Значит вам нельзя это запускать в мир. После вас уже никто это не соберёт. Это крайне важный пункт. Docker-процессы становятся осиротевшими и месяцами работают в фоне, где их никто не видит. Нетранзакционные операции обрываются в середине выполнения, создавая несогласованность данных между сервисами и в базах данных. Это приводит к ошибкам, которые невозможно предусмотреть и может стоить очень, очень дорого. Если вы не контролируете какие-то зависимости и не можете гарантировать, что ваш код корректно обработает SIGTERM, воспользуйтесь чем-то вроде dumb-init. Больше информации здесь: Я видел множество случаев (это не просто оборот речи) когда сервисы, созданные для обработки очередей или событий теряли соединение по таймауту и начинали бесконечно сыпать ошибки в логи, возвращая сообщения в очереди, отправляя их в Dead Letter Queue или попросту не выполняя свою работу. Далеко не все приложения могут масштабироваться горизонтально. Ярким примером могут служить Kafka Consumers. Это не обязательно плохо, но если конкретное приложение не может быть запущено дважды, об этом нужно знать заранее всем заинтересованным лицам. Эта информация должна быть бельмом в глазу, висеть в Readme и везде, где только можно. Некоторые приложения вообще нельзя запускать параллельно ни при каком обстоятельстве, что создаёт серьёзные трудности в его поддержке. Гораздо лучше если приложение само контролирует эти ситуации или для него написана обёртка, которая эффективно следит за "конкурентами" и просто не даёт процессу запуститься или начать работу пока другой процесс не завершит свою или пока какая-то внешняя конфигурация не позволит работать N процессам одновременно. Множество раз я видел бесконечно перезапускаемые consumers и очереди, раздувшиеся до таких размеров, что их последующая обработка занимала много дней. Любой слушатель очереди должен быть готовым к изменению формата, к случайным ошибкам в самом сообщении (типизация данных в json, например) или же при его обработке дочерним кодом. Я даже сталкивался с ситуацией, когда стандартная библиотека по работе с RabbitMQ для одного крайне популярного фреймворка вообще не поддерживала retries, счётчики попыток и т.п. Ещё хуже, когда сообщение просто уничтожается в случае неудачи. Всё течёт, всё меняется, особенно память. Непрерывно растущий график потребления памяти и OOM Killed в итоге — это норма жизни современных кубернетических разумов. Имплементация примитивной проверки, которая бы просто избавила вас даже от самой необходимости исследовать все эти утечки памяти сделала бы жизнь легче. Не раз видел, как люди тратят уйму сил и времени (и денег), чтобы приостановить эту текучесть, но нет никаких гарантий, что следующий коммит вашего коллеги не сделает всё ещё хуже. Если приложение может выживать неделю — это прекрасный показатель. Пусть потом оно просто само завершится и будет перезапущено. Это лучше чем SIGKILL (про SIGTERM см. выше) или исключение "out of memory". На пару десятков лет вам этой затычки хватит. Это редкий случай, но крайне неприятный. Очень неудобно когда один крошечный сервис блокирует возможность смены кластера или переезда в другой регион всей инфраструктуре. Если необходимо общаться с кем-то, кто не умеет в oAuth или VPN, заранее настройте reverse proxy. Не реализуйте в своей программе динамическое добавление/удаление подобных внешних интеграций, так как делая это вы гвоздями прибиваете себя к единственной доступной среде выполнения. Лучше сразу автоматизируйте эти процессы для управления конфигами Nginx, а в своём приложении обращайтесь на него. Когда у вас 100 разных приложений общаются друг с другом, можно сойти с ума, видя в логах что-то вроде "Go-http-client/1.1" и динамический IP-адрес контейнера Kubernetes. Всегда идентифицируйте своё приложение и его версию явным образом. Это самоочевидный кейс, но доводилось такое видеть, что даже юрист, писавший NDA, сейчас икает. Если библиотека, которую вы взяли в проект, больше никем не поддерживается — ищите другой способ достичь цели или занимайтесь развитием самой библиотеки. В моём списке есть ещё несколько крайне специфичных проверок для конкретных технологий или ситуаций, а что-то я просто забыл добавить. Уверен, вы тоже найдёте что вспомнить. User