243,19
Рейтинг
Southbridge
Обеспечиваем стабильную работу highload-проектов

Внедрение CI/CD: в чем основная задача пайплайна и как сделать лучше жизнь разработчиков

Блог компании SouthbridgeСистемное администрированиеПрограммированиеIT-инфраструктураDevOps


О своём опыте построения пайплайнов, правильных и неправильных подходах к CI/CD, здоровых профессиональных конфликтах и реализации GitOps в неидеальном мире рассказывают спикеры курса Слёрма по CI/CD Тимофей Ларкин и Александр Швалов.


Кто говорит


Тимофей Ларкин
Руководил направлением автоматизации в дирекции BigData компании X5 Retail Group, строил платформу для разработки и хостинга продуктов. Сейчас работает платформенным инженером Тинькофф.


Александр Швалов
Инженер Southbridge, настраивает и сопровождает проекты на Kubernetes. Помогал настраивать CI/CD как для небольших, так и для крупных компаний. Certified Kubernetes Administrator.


Почему тема настройки CI/CD до сих пор остаётся актуальна? Почему до сих пор не все научились это делать?


Т: Я думаю, по той же причине, по которой уже одиннадцать лет существует слово DevOps, но некоторые компании и коллективы только-только запускают так называемую DevOps-трансформацию. По той же причине, что всегда есть отстающие, которые пока в группе Low Performеrs. Не все ещё научились. Не всем это вдруг стало нужно, не все так быстро разрабатывали внутри себя технологическую экспертизу.


То есть дело не в том, что тема сама по себе невозможна для реализации, а в том, что просто кто-то ещё последовательно до неё не дошёл?


Т: Да, всё так.


А: Я добавлю, что есть некоторые компании, стартапы, где два человека, допустим, сидят, и им пока не нужен CI/CD. Может быть, они о нём знают, но пока без него обходятся. И внедрение это должно быть на каком-то этапе, когда проект вырос до точки, где это необходимо. Тогда это принесёт больше плюсов, чем минусов. Когда есть только один талантливый программист, ему намного быстрее будет разрабатывать без CI/CD. Ну, и да, отсталость некоторых проектов. Я сам работаю с клиентами, и у них есть сайты с большой посещаемостью, куда они заходят и в середине рабочего дня вешают заглушку и начинают править код. Прямо на продакшене.


Какие это проекты?


А: Да просто интернет-магазины. Небольшие, естественно. В больших очень критичны простои. Там используют серьёзные технологии. А небольшие… может, не слышали, может, им это неинтересно. Мы иногда рассказываем, что и как можно настроить, но без особого энтузиазма воспринимают. Мол, старое работает, и ладно.


До вопроса внедрения мы ещё дойдём. Расскажите о своём опыте построения CI/CD, что это были за проекты?


Т: Я около двух лет назад пришёл в X5 Retail Group как, на тот момент, единственный сотрудник нового подразделения, и на мне висела задача — построить платформу для разработки, чтобы разработчикам дирекции больших данных было, где собирать свой код и где его запускать. Там достаточно много разных проектов. Какие-то более будничные: прогнозирование оптимальных цен, оптимальных промо-акций (вроде скучное, но приносит прибыль). И что-то более хайповое, вплоть до проектов по компьютерному зрению.


Технологии были разные. В основном для бэкендеров — это Java, для фронтендеров — это React. Ну, и для дата-сайентистов — Python (Anaconda, Jupyter Notebook и тому подобное). Я должен был создать для них эту самую платформу разработки. То есть, CI/CD-серверы (у нас был GitLab), Kubernetes — заставить всё это работать в связке и помочь продуктовым командам начать этим эффективно пользоваться.


Два года назад это началось — и? Процесс завершился?


Т: На сегодняшний день я уже в X5 не работаю, но там ребята уже активно развились, очень хорошо освоились. Естественно, дирекция больших данных сейчас и то, что было два года назад — это совершенно разные вещи. По количеству рост сотрудников, наверное, раз в 5-6. Ну, это была очень молодая дирекция на тот момент. Она очень быстро росла. И по компетенции, по парку серверов на этой рантайм платформе, по всему прочему рост был на порядки.


И люди тоже очень сильно компетенций набрались. Как я говорил, тогда я был единственным сотрудником отдела, а сейчас там примерно 10 человек. И были какие-то измеряемые успехи. Я воспроизводил исследование «Ускоряйся», которое лежит в основе методики ежегодных отчётов State of DevOps, и по результатам этого исследования в масштабах одной только дирекции успехи были.


Александр, какой у вас опыт?


А: У меня опыт построения CI/CD первый был на третьем или на четвёртом потоке «Слёрма», где я участвовал в качестве студента. Ведь «Слёрм» изначально задумывался как средство обучения сотрудников Southbridge, а я работал в Southbridge и как раз на одном из потоков плотно познакомился с CI/CD. До этого у нас было несколько клиентов с не совсем правильными подходами к CI/CD, а на обучении я увидел такой конкретный, цельный пример, и потом он мне очень пригодился.


У нас пришел клиент, ему нужно было мигрировать из Docker Swarm в Kubernetes некий стек и плюс распилить монолит. Там были уже несколько контейнеризованных микросервисов и плюс был монолит, который разработчики пилили и добавляли микросервисы, и вот это все мы заворачивали в Kubernetes. Поэтому туда я взял пример нашего CI/CD из «Слёрма». Он простенький, но вполне себе рабочий. И в итоге мы дошли до того, что последние микросервисы разработчики деплоили уже сами, без нашего участия. Мы всё построили и отладили, а дальше они уже сами всё по шаблонам делали.


Если по этапам описать проделанную вами работу, в чем она заключалась? Как вы дошли от нулевой точки до момента, когда команда уже сама могла что-то делать?


А: Сначала мы адаптировали практически вручную то, что уже было в Docker Swarm — микросервисы. Потребовался небольшой допил конфигурации. Плюс, helm-чарты написали первые. Использовался Kubernetes. Helm 2 на тот момент ещё был популярен. Ну и GitLab CI. После этого разработчики задавали множество вопросов, иногда делали не так, как мы задумывали. Мы поправляли их. У нас не было прямо тесной связи, мы иногда приходили и советовали, где как лучше сделать, чтобы работало. Таким путем проб и ошибок пришли к тому, что они теперь без нас отлично справляются.


До этого вы упомянули неправильные подходы. Что это было?


А: В целом неправильный подход для мелких проектов, наверное, оправдан. Потому что там не было CI-инструмента. У нас в курсе будут описаны некоторые уже устоявшиеся инструменты (online, self-hosted-решения). А там было проще некуда: задание по расписанию, git pull из репозитория с кодом. Буквально каждые две минуты он делает git pull. И когда разработчик в нужную ветку пушит изменения, он понимает, что это попадёт на продакшн. Т.е. через вот этот промежуток времени 1-2 минуты скрипт сработал, и всё попало на продакшн. Разработчику не нужно было самому ходить. Это такой небольшой пример CI/CD. Естественно, о тестах говорить не приходилось, всё было на совести разработчика.


Какие проблемы могут возникнуть при таком подходе? Ошибки?


А: Да, плюс откат. Если мы из этой ветки пушим постоянно, то чтобы откатиться, нужно будет в ту же ветку запушить уже более поздним коммитом нужную версию, уже исправленную. А не вернуться к предыдущему коммиту или предыдущему релизу/тегу.


Т: Тут еще, понимаете, это очень здорово работает, пока там действительно стартап из двух человек. И один только финансами занимается, а кодит только второй. Зачем ему тогда сильно следить за качеством своего кода? Зачем ему выстроенный CI/CD процесс? Он и так отлично знает, что у него где лежит. Проблемы начинаются, когда такую модель работы переносят на командную разработку, и там есть 2-3-4 сеньора, которые всё отлично знают, но никто не может начать с ними работать, потому что они не столь ответственно относились к качеству кода, не запускали тесты. Вроде и так всё понятно, но всегда тяжело учесть, что придёт человек со стороны — а в больших компаниях это постоянно случается — которому будет сложно объяснить, что тут вообще происходит. Цель CI/CD — не просто автоматизировано допихать код до продакшена, но и следить за его качеством.


Мне кажется, это даже не основная цель пайплайна — собрать какой-то артефакт, Docker-образ и задеплоить. В любом случае, пайплайн должен уметь делать это безупречно. Потому что это такая типовая задача, которую мы должны легко делать каждый день с нашим кодом. Но вообще, в процессе разработки продукта полезно иметь здоровый профессиональный конфликт. Это конфликт между разработчиками и инженерами по обеспечению качества. И пайплайн тоже должен стоять на страже качества кода. Чтобы тыкать разработчика носом и говорить: «Так, вот тут у тебя код не по стандартам отформатирован. Вот тут линтер ругается, тут юнит-тесты не проходят. Давай откатывай и переделывай».

Если пайплайн просто собирает как-нибудь код и потом деплоит его, мы не знаем, что, собственно, задеплоили. Нормально или фигня. Может, там десять релизов уже, и каждый всё больше ломал наш код.


А как построить такую систему? Нужно, чтобы разработчики договорились о стандартах: мы все согласны, что вот так делаем, а вот так не делаем?


Т: Да, нужно прийти к соглашению, что мы считаем плохим/хорошим, как мы будем делать. Но в целом, это спор ни о чем. Не так важно, до чего вы договоритесь — у вас будут табы или пробелы. Это все второстепенно. Выберите уже что-нибудь. Это лучше, чем ничего. В этом плане Golang — такой интересный пример. Он не стал давать разработчикам волю самим выбирать, какое будет соглашение о качестве. Он просто сказал: «Ок, у нас отступы табами, табы шириной 8 символов». И всё, не колышет. Это встроено прямо в утилиты самого Golang. Есть такое высказывание: «Все его ненавидят, но при этом он самый лучший». Потому что хоть какой-то стандарт есть. Встроить проверку соответствия кода выбранным стандартам в пайплайн — это буквально две строчки.


Как обеспечить контроль выполнения стандартов?


Т: Мне не приходит на ум ни один язык, где нельзя какие-то юнит-тесты сделать. Как правило, они делаются обычно чуть ли не средствами самого компилятора. Это одна строчка. Если нет юнит-тестов, когда первый будет написан, он автоматически будет выполняться. Потом, если это компилируемый код, тогда сам факт, что компиляция прошла успешно, тоже о чём-то да говорит.


Значит, есть несколько таких ступенек?


Т: Да. Всегда принцип 20/80. 20% усилий дают 80% успеха.


А: Тут упомянули проверку кода. Это достаточно важно, и многие инструменты содержат в своём названии как раз CI. Travis CI, GitLab CI. И это очень важно — проверить и программиста носом натыкать, это экономия труда тестировщиков. Может, потом код скомпилируется, запустится, даже будет первое время выглядеть нормально, но потом тестировщик найдёт ошибку. А если это сделает автоматика, это намного быстрее и экономия труда, половины рабочего дня тестировщика.


Вы сказали «программиста носом натыкать». Это тот самый конфликт между разработкой и QA, о котором говорил Тимофей?


А: Могу привести свой пример, поскольку работаю с CI/CD каждый день, но не с кодом, а с конфигурациями. У нас есть линтеры — лишний пробел поставил, он уже ругается. Иногда злишься, ведь это просто формальность, ни на что не повлияет, но это приучает к качеству. И стандарты эти нужны для обеспечения командной работы в том числе. Чтобы новый человек пришел и быстро разобрался.


Получается, весь процесс настройки CI/CD как бы против программиста. Разработчик раньше был этаким свободным художником, а теперь приходит DevOps-инженер и говорит ему, как работать.


А: Можно провести аналогию с конвейером. Если норма у рабочего на конвейере — пять деталек в час, а он такой стахановец и делает десять. Это уже может быть непредсказуемость, если мы всегда будем считать, что он делает десять, хотя норма пять. Поэтому лучше пусть стабильно делает пять, но качественно. Можно фигачить код без стандартов, он будет работать, но новый человек не «въедет» быстро. Поэтому в командной работе есть ограничения, чтобы ото всех можно было получить одинаково стабильную работу. А не так, что один вкалывает, а другой сидит.


Т: А если сеньор проверяет код джуна и говорит: «Слушай, а тут вот у тебя функция может вернуть исключение, а ты его не проверяешь». Это сеньор тогда против джуна? А что меняется, если то же самое делает автоматика? Что сеньор, что автоматика — они против плохого кода, а не против разработчика.


Давайте мы теперь чуть отступим и поговорим про внедрение CI/CD. Как компании и команды приходят к тому, что пора что-то менять? Ошибки слишком частые, порядка нет, что-то ещё?


Т: Это очень по-разному случается. Вот мой опыт в X5 был такой, что была полная поддержка со стороны менеджмента, но при этом есть менеджмент всей дирекции, а есть отдельные продуктовые команды, у которых некое двоевластие. С одной стороны, есть руководитель отдела разработки, который, наверное, ратует за поставку качественного кода, но, с другой стороны, это продуктовая команда, у них есть дедлайны для поставки фич, и не всегда ясно, что поставить в приоритет. Конечно, нет откровенно глупых людей, которые говорят, что весь этот ваш DevOps не важен, нам нужно фичи пилить. Но при этом в разговорах проскакивает моментами. Мол, ну, я не знаю, у меня сейчас есть бюджет на одну ставку, и для меня вывод очевиден, кого я возьму — DevOps или ещё одного разработчика. Мне нужно разрабатывать, так что я возьму разработчика.


Как сделать хорошую мину при плохой игре? Поскольку у нас есть такой инфраструктурный отдел, который ещё в какой-то степени DevOps-евангелисты, то есть отдел, которым я руководил в X5, лучшее, что можно сделать, — это облегчить командам внедрение этих пайплайнов, этого CI/CD. Обучать людей до тех пор, пока это возможно, но лучше всего что-то сделать за них. Зашаблонизировать типовые действия. Например, даже банально собрать докер-образ и запушить его в репозиторий. Это нужно авторизоваться в docker registry. Рассчитать, с каким тегом мы соберем докер-образ. Это docker build, потом пуш несколько раз. Возможно, если мы хотим кэши, то нужно сначала скачать старый образ, который уже был, использовать его как кэш, потом закачать новый. Это куча рутины, которая может растянуться строчек на пятнадцать, но это всё однотипно.


Если инфраструктурная команда понимает немного в DevOps, она осознаёт, что это её задача. Сделайте Developer Experience получше. Зашаблонизируйте это, чтобы умещалось в три строчки, а не в пятнадцать. Тогда разработчики будут мыслить не категориями: надо спулить образ, собрать, туда-сюда. А категориями: а вот здесь Docker. Просто один блок, модульный такой. И им будет легче. Они тогда смогут абстрагироваться от деталей и больше заниматься своей непосредственной работой. А именно — писать код. В этом плане DevOps — он в том, чтобы фасилитировать, предоставлять разработчикам возможность лучше делать свою работу.
И таким образом снижать сопротивление.

А: Отвечу на тот же вопрос со своей стороны. У меня пример внедрения не из мира разработки, больше из мира администрирования. У нас в один прекрасный момент сказали: «Мы вот подошли к тому, что откладывать нельзя. Базис мы подготовили для вас. Теперь все новые проекты вы будете создавать вместо старого пути по CI/CD. Вот у вас есть шаблончик, создаете в GitLab и работаете с ним. Каждая команда будет вольна его улучшать». Так и внедряли. Достаточно императивно. В некоторых проектах, когда этот путь нёс больше вреда, чем пользы, мы откатывались на старую версию, но сейчас половина проектов работают через CI/CD. Мы управляем конфигурациями серверов через GitLab CI. Точно так же, как у разработчиков, там есть проверки, линтеры.


Раз уж заговорили про администрирование, поговорим про GitOps. Что это такое, и какое значение имеет: это всё-таки хайп или полезность?


А: Мало что могу сказать про GitOps. Мое мнение такое, что это достаточно хайповое слово. В последнее время я много его слышу. Три года назад так хайповали на DevOps, так что GitOps для меня одно из слов, примазавшихся к DevOps. Для меня это больше хайп.


Это вообще о том, что вся правда хранится в Git. Там и управление конфигурациями серверов, Infrastructure-as-Code, и код проекта, который потом на эти сервера поедет, и описание процесса, как мы с исходного кода получаем приложение на серверах. Так это я понимаю. Возможно, Тимофей меня дополнит, исправит.


Т: GitOps, мне кажется, чуть более честное словечко, чем все эти DevOps, DevSecOps и так далее. Потому что оно значит почти буквально то, что и говорит. Это именно ведение своих действий по эксплуатации своего софта, своей инфраструктуры через Git. Вся правда хранится в Git.


Что это на практике значит? Вроде как мы запушили какой-то код в наш репозиторий, автоматически запустился пайплайн. Что-то произошло, допустим, какие-то манифесты отправились в Kubernetes. И вот у нас то, что мы запушили в Git, это теперь то, что находится в Kubernetes. В принципе, это тоже GitOps, хотя не всегда его так называют. В частности, это так называемая push-модель, когда мы информацию из репозитория толкаем на продакшн.


Еще бывает так называемая pull-модель. Когда, если речь идет о Kubernetes, то у нас там какой-то агент стоит, который мониторит изменения в репозиториях, и если видит, что там что-то закомитили, что-то изменилось, то он стягивает эти изменения. Он умеет и Helm, и Kustomize делать, и просто манифесты ямликами подтягивать. И опять же отправляет их в Kubernetes.


Некоторые пропоненты GitOps вовсе закрывают любое редактирование в Kubernetes и позволяют изменять состояние кластера, только подтягивая изменения из Git. Если этого не делать, всегда есть риск, что из-за чьих-то ручных действий состояние кластера и то, что описано в репозитории — разъедется. Кто-то там ручками что-то поменял, а в Git ничего не поменялось. Поэтому частенько говорят про GitOps именно в контексте pull-модели, когда, ну, поменял ты что-то в Kubernetes, ничего страшного, потому что автоматика в ближайшие полчаса все равно вернет все, как описано в Git.


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


К примеру, смежный отдел, который управлял Data Lake в X5, быстро пришёл к тому, что у них вся конфигурация из двух сотен машин управлялась через Ansible, а он запускался каждые 30 минут, как пайплайн в GitLab. Это пример более-менее правильного применения GitOps.


Ну, и те инструменты, которые для GitOps используются, они ещё в какой-то мере нам подсвечивают: а у нас задеплоено именно то, что мы хотим? Иногда они не возвращают автоматически состояния, а просто зажигается желтый или красный светофор: «Оп, ребята, у вас рассинхронизировалось состояние тут и тут».


Александр, когда вы говорили о том, как строите процесс работы с клиентами в Southbridge, вы упоминали что-то похожее на GitOps. Настройка конфигурации через описание в Git, это не то же самое?


А: В идеальном мире — да. Но у софта зачастую очень много настроек. И чтобы в каждой ручечку/переключатель/ползунок поменять, роль управления конфигурацией должна быть очень развесистая, раскидистая. Либо шаблонизированная. Иногда бывает, что настройки просто нет, и мы идем вручную поправляем. Разработка не успевает за эксплуатацией. Потому что иногда задачи бывают критичные. А чтобы всё разработать, у нас просто нет времени. Но в идеальном случае мы делаем именно то, что описал Тимофей.


Тимофей, а подход GitOps учитывает это обстоятельство неидеального мира?


Т: Это тяжело, конечно. Потому что нам надо бизнес делать, бабки зарабатывать. Казалось бы, вот она, ручка, сходи и поменяй, надо же. Есть потребность, а тут какой-то скрипт ещё писать, дорабатывать helm-чарты. Поэтому да, есть соблазн всё быстренько сделать руками.
Чтобы и бизнес-потребности учесть и себе codebase не испортить, можно хотя бы завести тикет, что вот тут вручную настраиваемое значение, но это баг. Мы положим тикет в бэклог, потом обязательно вернемся и допилим тут автоматизацию. Главное — не потерять места, где мы руками настраивали.


В чём ключевые преимущества подхода GitOps?


Т: Основное — мы достоверно знаем, что у нас запущено в продакшене. Что все четко описано в репозитории, и не возникает сомнений, что кто-то руками поменял.


А: Когда большая команда, это очень важно, что все могут в любой момент посмотреть и увидеть, какая сейчас конфигурация работает на нужном сервере. Потому что если там 1-2 человека, они могут всегда договориться, а когда 15 человек, GitOps сильно экономит время. Не надо ходить, опрашивать, выяснять, откуда ноги растут у несоответствий.


Чем GitOps отличается от подхода Infrastructure-as-Code?


А: Это не только инфраструктура, но еще и приложение, как код. Они хранятся в единой точке. Может, в разных репозиториях, но в одном инстансе GitLab. Можно легко посмотреть состояние инфраструктуры и приложения. Плюс, в идеальном мире может быть вообще связана инфраструктура с кодом. Мы создали новый репозиторий, настроили с нуля хитрый пайплайн, запушили код и он сам пошёл, создал себе инфраструктуру где-нибудь в облаке и задеплоил туда приложение, которое мы туда написали. Это идеальный, конечно, случай, но думаю, где-то это работает. Например, в больших компаниях.


Т: Да, там предлагают даже держать отдельно несколько репозиториев. Один — это просто сам код приложения, в котором собирается образ или бинарник. Второй репозиторий — туда генерируются, допустим, манифесты для Kubernetes. Третий — это такой большой репозиторий, который нам рассказывает, что в нашем кластере манифест из этого репозитория за таким-то тегом, из этого такой-то версии. Он уже просто по номерам версий описывает, что вообще в нашем кластере запущено. На это иногда даже автоматику натягивают: изменили приложение, только код, повесили на него новый тег, а там потом автоматически новый манифест запушился в репозиторий с манифестами, и нужная строчка с нужной версией изменилась в том месте репозитория, которое описывает общее состояние кластера.


Это такая идеальная система?


Т: Ну, да.


CI/CD — это такая штука, которую один раз настроил и забыл, или это процесс, который нужно поддерживать?


Т: И да, и нет. Когда он только внедряется, не бывает сразу идеально. Когда в X5 я внедрял, то начальник отдела разработки спрашивал у разработчиков, сколько времени в неделю они тратят на обслуживание их CI/CD. Кто-то отвечал: «А что там обслуживать? Вот мы настроили и забыли». И какое-то время это работает. Если сделать сходу правильно, можно сделать очень модульные блоки. И потом не сильно трогать нижележащий код, который это делает, а просто появился новый проектик/микросервис, подключили к нему типовые модульные блоки и поехали дальше. Но пока все учатся, что-то допиливается. Приходится на первых порах долго и мучительно внедрять новые хорошие практики в трудно обслуживаемый код. Дальше уже полегче.


А в больших компаниях как обычно происходит? Приходит DevOps, помогает всё это настроить и остается в команде, или разработчики перенимают на себя его функции, а DevOps уходит в другую команду? Или есть отдел DevOps?


Т: Все очень по-разному. У меня в X5 были продуктовые команды очень разных способностей. Какие-то хорошо въезжали в процесс, и редко надо было им помогать. Были более слабые. К ним привязывали инженера, который помогал писать пайплайны. Работал, в некоторой степени, приходящим релиз-инженером в их команде. Но в X5 порядка 9-10 DevOps обслуживали потребности где-то 200 техспециалистов.


Сейчас я работаю в «Тинькофф», тоже в платформенной команде, но там масштабы немного другие. Нас 8 человек, а разработчиков тысячи полторы. И невозможно предоставлять каждой команде DevOps как сервис. Поэтому там предполагается, что команда сама добывает эту экспертизу или нанимает себе DevOps. Либо сами разработчики этому обучаются.


А: Моя версия такая, что да, вначале идет активная разработка. Мы программируем пайплайн, что за чем будет идти, потом ситуация может устаканиться. Если в архитектуре проекта нет глобальных изменений, всё может работать год без правок. Но в мире все меняется. Микросервисы могут быть переписаны за две недели. И если их переписали на другом языке программирования, то тесты выкидываем и пишем новые. Поэтому CI/CD тоже требует обслуживания.


Ну и добавлю, что у разработчиков разная компетенция. Кому-то постоянно нужна помощь, а кому-то нет. Вот у нас был пример с одним из клиентов. Мы им настроили базовые CI/CD, всё показали. Через какое-то время они к нам приходят и говорят: «А мы вот переписали всё по-своему». Мы только и ответили: «Ух ты, молодцы». Они просто взяли все в свои руки.


Вопросы задавала Виктория Федосеенко Vika_Fedoseenko

Теги:DevopsCI/CDKubernetesSoftware Developmentcloud computing
Хабы: Блог компании Southbridge Системное администрирование Программирование IT-инфраструктура DevOps
+17
7,2k 67
Комментарии 6

Похожие публикации

Лучшие публикации за сутки

Информация

Дата основания
Местоположение
Россия
Сайт
southbridge.io
Численность
51–100 человек
Дата регистрации

Блог на Хабре