cat mindflow.txt > /dev/null – Telegram
cat mindflow.txt > /dev/null
180 subscribers
14 photos
87 links
Поток сознания о программировании, технологиях, жизни и вообще
Download Telegram
Об отказоустойчиости и cloud-native

Что делать, если что-то перестало работать? Первое правило инженера - выключить и снова включить (aka перезагрузить). А теперь посмотрите, во что превращается серверная разработка. Сплошные микросервисы, состоящие из большого числа эфемерных контейнеров. Если раньше мы мерились, у кого uptime сервера больше (потому что ребут очень часто был болью), то сейчас мы соревнуемся, кому проще прибить и перезапустить контейнер. Да еще и chaos monkey выпускаем, которые только тем и занимаются, что киляют процессы, ожидая, что сервис сам себя восстановит. Получается, что индустрия начала перманентно применять правило "выключи/включи" и это должно привести к качественному увеличению отказоустойчивости наших систем. Причинно-следственная связь правда не ясна. То ли Docker с Kubernetes-ом и AWS ECS нам позволили осуществить этот переход, то ли их для того и сделали, чтобы этот переход свершился...
conman - [the] container manager

В рамках моего pet project по созданию Kubernetes CRI-совместимого менеджера контейнеров (читай, клона containerd или cri-o) я наконец-то добрался до первых интерактивных результатов. Использовали docker run -i? Это примерно о том, как реализовать такую же фичу самостоятельно. На подходе docker run -i -t 🤓

https://twitter.com/iximiuz/status/1219011765943533570?s=20

О предыдущих этапах можно прочитать тут:

- conman - [the] container manager: inception https://iximiuz.com/en/posts/conman-the-container-manager-inception/
- Implementing Container Runtime Shim: runc https://iximiuz.com/en/posts/implementing-container-runtime-shim/
- Implementing Container Runtime Shim: First Code https://iximiuz.com/en/posts/implementing-container-runtime-shim-2/
В продолжение темы интерактивных контейнеров, описал свои приключения с реализацией этой функции в conman:

https://iximiuz.com/en/posts/implementing-container-runtime-shim-3/

#Go #Rust #Containers
И действительно, полностью согласен с результатами этого исследования. Книг про разработку или около-разработку вокруг очень много, но лишь малая часть из них по-настоящему стоящая и неустаревающая классика. Приятно осознавать, что в Top-5 выборки из исследования попали 4 книги из моего личного Top-5. А вот Clean Code by Robert Martin я, к своему стыду, все еще не прочитал.

https://threadreaderapp.com/thread/1229731043332231169.html
С этим вашим карантином работы почему-то стало больше, а свободного времени - заметно меньше. Так что создание собственного контента пришлось пока поставить на паузу. К счастью, кому-то все еще удается писать статьи. И вот одна новая и действительно классная:

"Surprising Things About Working at Well-Known Tech Unicorns"

https://blog.pragmaticengineer.com/surprising-things-about-working-at-tech-unicorns/
Все знают, что [Docker] контейнеры - это не виртуальные машины, что они нужны для упакови приложения и его зависимостей для последующего совместного запуска тьмы таких же коробочек на одном Linux сервере и т.д. и т.п. В то же время, чуть ли не каждый первый пример работы с Docker упоминает имя какого-нибудь стандартного дистрибутива Linux (debian, centos, ubuntu, alpine, busybox, etc) и бесцеремонно пользуется (GNU?) утилитами из него. В итоге у вновь прибывших случается некоторый когнитивный диссонанс. Если контейнер - это не виртуальная машина, то почему он ходит и крякает как утка выглядит как что-то, внутри чего работает полноценная операционная система? В общем, попробовал разложить все по полочкам в этой короткой заметке:

https://iximiuz.com/en/posts/not-every-container-has-an-operating-system-inside/

Важно: для сохранения душевного равновесия во время прочтения, нужно четко отличать Linux kernel (голое ядро) от операцинной системы (ядро + user-land обвязка в виде либ, утилит и конфигов) и дистрибутива (конкретная комбинация ядра и обвязки).

#Docker #Linux #Containers
И снова про контейнеры....

Термин container довольно сильно перегружен. Для разных людей и в зависимости от контекста он может означать разные вещи (systemd-nspawn, lxc/lxd, docker, rkt, OCI-complaint runtime, kata containers, etc). Но основная цель контейнеризации остается неизменной - это эффективная упаковка и изолированное выполнение пользовательских приложений, реализуемая на уровне операционной системы (а по факту - Linux). Эта идея и технология существует довольно давно, но по-настоящему популярной ей удалось стать именно благодаря удобной реализации от Docker.

Так хорошо всем знакомые docker build|push|pull|run позволили обычным смертными программистами воспользоваться всеми преимуществами контейнеризации без выполнения сложных последовательностей команд и тонкой конфигурации. Но, у такого коробочного решения оказалась и обратная сторона. Повседневное использование docker run -it ubuntu bash (или написание Dockerfile-ов, начинающихся с `FROM centos`) может привести к тому, что контейнер начнет казаться чем-то мало отличимым от полноценной операционной системы, спрятанной внутри некоторой коробочки виртуализации, созданной докером на вашем хосте.

На самом же деле, почти всегда контейнер - это лишь изолированный (namespaces) и ограниченный с точки зрения потребляемых ресурсов (cgroups) и доступных действий (capabilities, seccomp, AppArmor) процесс (или группа процессов). Такой же, как и все остальные процессы на вашем Linux хосте. [1] Но если контейнер - это обычный процесс, то для его запуска нам нужен всего лишь один единственный выполняемый файл! Т.е. можно создать контейнер с нуля FROM scratch (а не FROM debian|alpine|centos|etc) и добавить в пустую директорию (bundle) лишь один бинарник, который затем будет выполнен в изолированном окружении. Вполне себе валидный вариант использования, особенно для статических сборок, как в Go.

Если же программа внутри контейнера хочет иметь полноценную файловую структуру, что-то вроде того, что мы видим, выполняя ls /, то мы добавляем в bundle директорию все необходимые файлы. Заметили, что до сих пор мы ни разу не упомянули images? Только директории и исполняемые файлы, только хардкор!

Так для чего же нужны все эти _images_? Оказывается, основная задача, решаемая с помощью images - это эффективная сборка и распространение контейнеров, а не их запуск. [2] Images - суть tar архивы с файловой системой внутри. Перед запуском контейнера такой архив распаковывается во временную директорию, которая затем становится bundle директорией контейнера. Но если у вас на хосте есть 10 кастомных образов, каждый из которых основывается на базовом образе debian (100+ MB), будет ли это означать, что они займут как минимум 1 GB на диске? К счастью - нет. Образы сохраняются слоями (layers). Базовый образ debian формирует один такой слой и этот слой - неизменяемый. На этот слой затем ссылаются все остальные кастомные образы. Отлично экономит место на диске нужное, для хранения образов. Что, если нам нужно запустить 100 экземпляров одного и того же контейнера? Нужно ли нам создать 100 копий bundle директории? Да! Займут ли они x100 от размера исходного образа? К счастью - нет! Опять же, спасибо слоям, мы можем создавать директории bundle монтирую слои один на другой с помощью какой-либо из реализаций union mount, например overlayfs. Все слои, кроме самого верхнего временного слоя, окажутся неизменяемыми и поэтому могут быть безопасно использованы совместно разными контейнерами. Все изменения файловой системы, сделанные любым из экземпляров контейнера будут сохранены в самом верхнем временном слое, который после остановки контейнера будет просто удален. Отлично экономит место на диске, нужное для запуска контейнеров.
Итак, мы знаем, что images на самом деле не являются необходимым компонентом для контейнеров. Но на самом деле ситуация еще более "вывернутая" по сравнению с привычным нам вариантом использования docker build -> docker run. Для того, чтобы собрать образ, необходимо запускать контейнеры! [3] Обычно, мы их не видим, потому что docker (podman, buildah и т.п.) делает всю грязную работу за нас. Но, если в Dockerfile-е есть инструкция RUN это означает, что в процессе сборки образа будет происходить запуск промежуточных контейнеров. Задача таких контейнеров - выполнить команды из RUN в изолированном окружении. Как обычно, для создания таких контейнеров все промежуточные слои будут смонтированы для создания временной bundle директории. Все изменения файловой системы сделанные командами из инструкции RUN будут сохранены во временном слое, который затем будет сохранен, как один из слоев собираемого нами образа. Отличное и далеко не всегда явное использование технологии контейнеров!

Make code, not war!

[1] https://iximiuz.com/en/posts/not-every-container-has-an-operating-system-inside/
[2] https://iximiuz.com/en/posts/you-dont-need-an-image-to-run-a-container/
[3] https://iximiuz.com/en/posts/you-need-containers-to-build-an-image/
Крутая интерактивная визуализация Cloud-native проектов landscape.cncf.io. С разделением на уровни наконец-то стало понятно, что CNCF пытается под одной крышей собрать проекты, охватывающие полный цикл разработки, начиная от Provisioning и затем через Runtime (containers, stroage, and network) и Orchestration до Application Definition and Development.

Все элементы на карте кликабельны!
TIL: если вы вдруг оказались на Linux хосте или внутри контейнера, где нет ни ip, ни ifconfig утилит, а очень хочется узнать сколько там сетевых интерфейсов и как они называются - смело делайте ls /sys/class/net.

#Linux
TIL: pushd and popd - это как комбинация cd и cd -, только с полноценной историей переходов.

#Linux
Откуда есть пошел YAML заморский...

На самом деле, понятия не имею. Но воюя в очередной раз с хитрым синтаксисом какого-нибудь условного nginx.conf у меня всегда возникает вопрос - ну почему бы просто не описать конфигурацию в JSON? Та же история, когда пишешь какой-нибудь приложение, и наступает пора сделать его конфигурируем. Если все можно описать как композицию объектов, то зачем выдумывать кастомные форматы? Положим все конфиги в JSON! Но когда дело доходит до редактирования JSON, все вот эти скобочки, кавычки, отсутствие комментариев - это прямо ух... И так мы приходим к YAML. Который по сути JSON на стероидах и без лишнего шума (кавычек и скобочек). By the way, JSON - это валидный YAML, но не наоборот.
Так, я потратил суммарно уже целых 6 минут (за два дня), чтобы активировать здесь комментарии к постам... А что, если комментарии можно писать только к новому контенту?!
Не знаю как дела обстоят сейчас, но лет 5-7 назад на собеседованиях было ультра-модно спрашивать про архитектурные шаблоны. Абстрактные фабрики, синглтоны, вот это все. Возможно это и хорошая разминка для ума, но ведь архитектурные шаблоны - не самоцель. Они - средство. А цель - это адекватная архитектура приложения. Не супер-сложная (чтобы коллеги не приходили в ужас от количества реверансов и условностей при чтении/написании кода), не полное ее отсутствие (никто не любит big ball of mud катающийся в тарелке со спагетти), а сбалансированная (т.е. с умеренным использованием абстракций и инкапсуляции), симметричная (все компоненты примерно одного размера, нет god objects и армии анемичных недо-компонентов) и обладающая слабым зацеплением (aka loose coupling, обожаю российскую терминологию) структура программы.

Достигнуть такой адекватной архитектуры помогает следование некоторым принципам. Например, мы должны стараться производить код с loose coupling и high cohesion (нет, даже не буду стараться перевести на русский эти термины). Еще один замечательный свод принципов - это SOLID. Четыре из пяти его постулатов применимы и за пределами объектно-ориентированной парадигмы. Любой компонент системы должен стараться не брать на себя слишком много (single-responsibility principle), быть легко расширяемым без необходимости копаться по локоть в его кишках (open–closed principle), обладать лаконичным и недвусмысленным интерфейсом (interface segregation principle) и не зависеть от конкретных реализаций соседних компонентов (dependency inversion principle). Заметьте, я умышленно не употреблял слово код в прошлом предложении, потому что более высокоуровневые компоненты (микросервисы, очереди задач, API и пр.) также должны удовлетворять этим принципам, чтобы результирующая система оказалась адекватной.

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

Навеяно недавней статьей Кента Бека https://medium.com/@kentbeck_7670/monolith-services-theory-practice-617e4546a879.
Минутка занимательного языкове́дения

Знание латинских (и в чуть меньшей степени - греческих) префиксов - рулит! В частности, при изучении английского языка. Мой любимый пример - prefix + duce, где duce - это производное от (опять же) латинского ducere - вести (to lead).

- produce - производить (pro - это что-то вроде вперед)
- introduce - вводить (intro - это что-то вроде внутрь)
- reduce - сводить [в плане уменьшать] (re - это назад, со временем reduce эволюционировало от простого "возвращать", до "возвращать то, что осталось", т.е. в меньшей степени)
- deduce - делать вывод, выводить [теорему] (de - это вниз, то есть проводить, прослеживать связь до самого низа)
- induce - побуждать (in - внутрь, т.е. приводить кого-то к чему-то)
- conduce - проводить, способствовать (con - вместе, т.е. идти самому вместе с ведомым, вести).

Что самое интересное, связь, похоже, можно прослеживать и дальше. Так, conduct - это производное от conduce - проводить. А кондуктор - это проводник. А если добавить еще один латинский префикс semi-, то получится semiconductor, он же - полупроводник.

Другие полезные примеры:

prefix + voke, где voke - это что-то вроде "звать"

- invoke - призывать
- evoke - вызывать (эмоции, воспоминания)
- provoke - вызывать (на конфронтацию)

prefix + clude, где clude - это что-то вроде "закрывать"

- include - включать
- exclude - исключать
- conclude - заключать, т.е. сводить факты вместе
- preclude - предотвращать

prefix + ject, где ject - это "бросать"

- inject - вводить
- eject - выбрасывать (e- то же, что ex-)
- reject - отбрасывать

А также ingress/egress/progress/egress/congress, revolve/convolve/evolve/involve, и т.д. и т.п... Быстрое гугление привело вот к такой ссылке с достаточно хорошим списком префиксов https://www.englishhints.com/latin-prefixes.html.
Статья Haste Makes Waste вообще о финансах, но начинается она с очень интересного исследования из мира биологии. Ученые брали две одинаковые группы мальков и помещали одну - в слишком холодную, а другую - в слишком теплую воду. В результате, мальки из холодной группы росли немного медленнее среднего для такого вида рыб, а мальки из теплой группы - немного быстрее. Затем мальков из обоих групп помещали в стандартную для них среду и со временем скорость их роста уравнивалась. Но на этом эксперимент не заканчивается! Оказывается, рыбы из холодной группы в итоге жили на 30% дольше среднего, а рыбы из теплой - на 15% меньше. А объясняется такая разница в продолжительности жизни тем, что ускоренный рост на ранних этапах формирования организма отнимает ресурсы у других не менее важных процессов - поддержки и восстановления существующих систем. И наоборот - замедленный рост позволяет больше ресурсов направить на поддержку и восстановление органов. Это все здорово и интересно, но причем здесь software development, спросите вы? А при том, что вероятно эти выводы можно экстраполировать на разработку сложных систем, особенно - разработку с нуля. Если допустить, что у нас есть две примерно одинаковые группы разработчиков делающих два примерно одинаковых проекта, и одна их групп топит за скорость поставки фич (экономя на рефакторинге, тестах, code style, observability, CI/CD и прочих вторичных вещах), а вторая - за качество процессов, то вероятно на первых порах первая группа будет продвигаться в продуктовой разработке намного быстрее, а вторая - намного медленнее среднего по больнице темпа. Но как показывает эксперимент с мальками, при помещении их в одинаковую среду со временем скорость роста уравнивалась в обоих группах. В случае разработки же зачастую определяющих фактором является степень сложности и успешность управление ей. Так как умственные ресурсы разработчиков в обоих группах конечны, а менеджмент всегда топит за скорость заработки фич, то рано или поздно оба проекта окажутся в нормальной среде с примерно одинаковым балансом фиче-делания и рефакторинга & co. Но моя гипотеза состоит в том, что проект первой группы загнется под весом неконтролируемой сложности намного раньше, чем проект из второй группы. В то время, как выработанные медленной группой на начальных этапах подходы и практики позволят второму проекту гармонично расти без саморазрушения из-за детских травм. Т.е. в долгосрочной перспективе неторопливый старт выигрывает. Если конечно у проекта планируется долгая жизнь, но это уже совсем другая история... 😉
TIL (not really): Network Service Mesh - это никакой не service mesh, в традиционном, если уже можно так говорить, его понимании. Istio, linkerd, consul - это оригинальные service mesh-и, оперирурющие на уровнях L4/L7. А Network Service Mesh - это какой-то новый зверь, пытающийся идеологию service mesh притащить на уровни L2/L3 сети. Похоже это нужно всяким telcos и ISP, или в продвинутых ынтерпрайз сетях. Самая адекватная информация по проекту на его же сайте, а пятиминутный поиск в Google показывает, что большинство статей в Интернет об NSM - сплошная вода.
Немного занимательных аналогий из мира Kubernetes
...для тех, кто программировал сервера еще до контейнеров

Container - процесс (или несколько связанных процессов).

Pod - "виртуальная машина" как группа процессов (т.е. контейнеров) разделяющих общие ресурсы (disk, RAM, CPU, network, etc) доступная по некоторому IP адресу. Такие "машины" недолговечны, при перезапуске создается новая идентичная машина с новым IP адресом.

Service - логическая группа "виртуальных машин" с постоянным адресом (IP и DNS). В до-kubernetes эре сервисом бы мог считаться виртуальный server какого-нибудь Nginx reverse proxy (или VirtualHost в Apache сервере) прячущий за своим upstream группу виртуалок переменной численности.

Node - в до-kubernetes эре - это железный сервер, на котором запущена куча виртуалок. А теперь это (зачастую, хоть и не всегда) виртуальная машина, на которой запущена куча pod-ов. Т.е. ушли на один уровень виртуализации глубже.

Service discovery - модное название для задачи нахождения по имени (или IP адресу) сервиса IP адреса конкретной машины из группы машин этого сервиса.

Load balancing - сделай свою service discovery так, чтобы ни один из IP адресов машин сервиса не оказался перегружен запросами.

Service proxy - это как reverse proxy, только тонко размазанный по стороне клиента. Клиенты общаются с конкретными машинами сервисов через service proxy. Service proxy работают локально и распределенно. Условно говоря, сколько клиентов, столько и service proxy-ей. Как и reverse proxy, технология решает задачу service discovery и load balancing.

Kube-proxy - процесс, который превращает каждую ноду кластера в L3/L4 service proxy. Что-то вроде envoy, но на уровне операционной системы (iptables или IPVS). Для каждого контейнера нода, на которой он запущен, является его service proxy.
Это свершилось! Kubernetes отказывается от поддержки Docker в качестве container runtime.

И это на самом деле здорово. Docker - это огромная экосистема с прицелом на удобство использования контейнеров именно человеками (а после покупки их Mirantis'ом и отказа от дальнейшей разработки swarm'а, developer experience стало по факту их единственным фокусом). Kubernetes'у же от container runtime'а нужно буквально три команды - запусти контейнер, покажи статус контейнера, да пошли ему сигнал (утрирую конечно). Поэтому по факту использовать полноценный dockerd смысла очень было мало. Но так уж исторически сложилось... К счастью, еще в далеком 2016 требования Kubernetes'а к container runtime формализовали в виде Container Runtime Interface (CRI); и containerd (то, что на самом деле запускает контейнеры позади dockerd) быстренько добавил поддержку CRI, а RedHat запилили свой аналог - cri-o. Но вот код Kubernetes до сих пор был вынужден поддерживать и милый сердцу CRI и legacy интеграцию с Docker'ом. И вот, настала пора большой чистки.

Fear not, с точки зрения разработчиков ничего не поменялось. Все благополучно продолжат писать свои Dockerfile'ы и запускать контейнеры на любимых маках в старом добром Docker. Стандартизация - сила!