Всем привет! В предыдущей статье я рассказал, как можно перевести TeamCity на работу по HTTPS. Сегодня я расскажу, с какими проблемами мы столкнулись и как их решали. Поехали!

Немного предыстории

TeamCity мы используем уже больше 7 лет, и в самом начале его использования мы даже не подозревали про Docker, поэтому сервер и агенты у нас крутились на какой-то виртуалке через скрипты. Но были проблемы: скрипты периодически глючили, виртуалка тоже, переезд виртуалки на другой компьютер вызывал кучу проблем, всевозможные непонятки с окружением при сборке новых версий приложения.... И при каждой такой проблеме процесс сборки вставал на полдня-день. Со временем мы стали собирать наше приложение в докере - это избавило нас от проблем окружения. А потом мы решили и TeamCity тоже засунуть в Docker для удобства администрирования.

Проблема 1: Выбор правильного образа для агента

С образом для сервера ошибиться невозможно, а вот с образом для агента очень даже. Тем более, что когда мы внедряли это решение, то документация была гораздо скуднее.

Если вы хотите использовать Docker для сборки своего приложения на агенте, то обязательно надо брать образ jetbrains/teamcity-agent:XXX-linux-sudo. Собственно, это и была наша первая проблема и потеря нескольких дней для "дебага". Потому что на версии без/с sudo это выглядит так

Образ без sudo и несоответствие требований для сборки
Образ без sudo и несоответствие требований для сборки
Правильный образ
Правильный образ

С версии 2020.1.1 JetBrains предоставляет уже готовые sudo образы, но когда переходили мы, готовых образов не было и нам пришлось собирать их самим по документации.

Проблема 2: Неправильный запуск

С запуском сервера ошибиться практически невозможно. Единственная проблема - это права на папки.

docker run -it --name teamcity-server-instance  \
    -v <path-to-data-directory>:/data/teamcity_server/datadir \
    -v <path-to-logs-directory>:/opt/teamcity/logs  \
    -p <port-on-host>:8111 \
    jetbrains/teamcity-server

Собственно, папкам <path-to-data-directory> и <path-to-logs-directory> надо выдать права sudo chown -R 1000:1000 <path>.

С агентами сложнее - нужно правильно "стартануть" докер внутри агента. Документация предлагает 2 опции на выбор (я опущу некоторые параметры):

  1. docker run -it -u 0 -v /var/run/docker.sock:/var/run/docker.sock jetbrains/teamcity-agent

  2. docker run -it --privileged -e DOCKER_IN_DOCKER=start jetbrains/teamcity-agent

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

Проблема 3: Постепенное уменьшение свободного места

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

Причина оказалась довольно "банальной". Один из наших проектов запускает автотесты через docker-compose: поднимается контейнер приложения и контейнер с тестами, запросы летят к приложению, на выходе формируется отчёт. Отчёт получается довольно жирным и нам показалось удобным монтировать хостовую папку (папка в докер контейнере агента) в контейнер с тестами, чтобы отчёт сразу был доступен на хосте - это и была наша "ошибка". Оказалось, что вольюмы монтируются с правами root пользователя, а сервис билд агента TeamCity запускается от пользователя buildagent, поэтому при чистке он просто не мог удалить эти отчёты.

Решения три:

  1. Запускать контейнер билд агента с параметром -u, сервис внутри запускался из под root пользователя. Но это не безопасно.

  2. Не монтировать вольюм напрямую при запуске, а копировать нужные файлы из контейнера после его остановки.

  3. Внутри контейнера с отчётами поменять права у файлов.

Как быстрое временное решение, мы выбрали первый вариант.

Проблема 4: Недоступность сетевых ресурсов

Некоторые наши сборки используют пакеты, которые тянутся из внешних ресурсов. Например, различные npm и NuGet пакеты. И периодически были проблемы с сетевой доступностью - все запросы падали с 503 ошибкой. Эта проблема возникала спонтанно и уходила аналогично, но доставляла массу проблем, когда надо было срочно что-то собрать.

В итоге, вылечилось добавлением опции --network host при запуске билд агента.

Проблема 5: Недостаток предустановленных библиотек

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

Что получилось?

Если собрать всё воедино, то получится что-то такое

docker run -dt \
  -u 0 \
  -e SERVER_URL="XXX" \
  --name teamcity-agent-instance \
  -v <conf_path>:/data/teamcity_agent/conf \
  --privileged -e DOCKER_IN_DOCKER=start \
  --restart=always \
  --log-driver=none \
  --network host \
  custom-agent-image:2021.2-linux-sudo

PS

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

А с какими проблемами сталкивались вы? Пишите решения в комментариях.

Комментарии (16)


  1. foRz
    13.12.2021 13:29

    Статья полезная, но..... у меня всегда только один вопрос - ЗАЧЕМ? Зачем усложнять себе жизнь докером? Почему не развернуть это все на обычной виртуалке, а новые агенты например разворачивать через Ansible? В чем профит? Любая IaC тулза гарантирует идемпотентность.


    1. Zvava Автор
      13.12.2021 13:39
      +1

      Если отвечать честно, то на момент внедрения не было специалиста с экспертизой в Ansible и аналогов, а компетентности в Docker было достаточно. И казалось, что "условно за день" можно справиться своими силами без изучения Ansible. Задача оказалось разовой, работает вполне годно, удовлетворяет наши потребности.

      Если бы был человек, который знает Ansible, скорее всего тогда бы делали с его помощью.


      1. foRz
        13.12.2021 13:52
        +2

        Спасибо за ответ. С этой точки зрения выглядит логично.


      1. GrgPlus93
        13.12.2021 23:17

        "компетентности в Docker было достаточно" для того, чтобы воткнуть вольюмы, privileged, и host-network. Ах да, еще root в контейнере "как быстрое решение". Ну и как вишенка на торте - отключенный лог-драйвер.

        Ну т.е. вам просто нужен был chroot готовый для агента?


    1. slonopotamus
      13.12.2021 22:51

      Затем что инфраструктуру, которая на каждый билд будет создавать чистую виртуалку (в которой не осталось ошмётков от предыдущих билдов), поднять сложнее чем инфраструктуру, которая на каждый билд будет создавать чистый контейнер.


      1. foRz
        13.12.2021 23:15

        Насколько я понял, такой задачи не стояло. Максимум речь была про переезд билд-агента на новую вм. Да и слабо себе могу представить ситуацию, при которой вам потребуется создавать НОВЫЙ агент на каждый билд. У нас например агенты молотили практически без перерыва, и каждый раз поднятие и остановка образа агента несла бы в себе дополнительные ненужные накладные расходы. Если у вас есть другой опыт - поделитесь, будет интересно. Я не холивара ради вопрос задавал :)


        1. slonopotamus
          13.12.2021 23:42

          См. любой современный CI. GitHub Actions, GitLab CI, CircleCI, Travis, етц. Все они гарантируют запуск билда в девственно чистом окружении.


          1. foRz
            13.12.2021 23:59

            Не, подождите. С этими-то понятно, у них это by design. Я больше про Teamcity.

            В этом есть смысл, но например на нашей практике сборки .NET/Angular кода без постоянного кэша nuget и npm пакетов... это была бы боль. It depends.


            1. slonopotamus
              14.12.2021 00:04

              Кэш - это контролируемые ошмётки от предыдущих билдов, и во всех этих CI оно поддерживается. А вот какие-нибудь зависшие процессы (или ещё хуже, не зависшие, а продолжающие что-то делать после того как билд формально завершился) - это плохие, негодные ошмётки.


              1. Zvava Автор
                14.12.2021 11:08

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

                С зависшими процессами лично мы не сталкивались, все шаги у нас можно разбить на 3 группы:

                1. Подготовка аргументов. Это простейшие bash скрипты, там нечему зависать.

                2. Собственно, сборка Docker образа. Сделано через обёртки TeamCity - при стопах билдов и тому подобному зависаний не заметили.

                3. Пуш в Docker Hub.


  1. slonopotamus
    13.12.2021 22:56

    Но в любом случае документация настоятельно рекомендует помнить о возможных проблемах безопасности (почитать можно здесь и здесь), и действовать на "свой страх и риск", потому что билды смогут получить рутовые права к хостовой машине. Мы поигрались с первым вариантом и решили выбрать второй.

    1. Первый "здесь" не ссылается никуда

    2. Способ через --priveleged точно так же позволяет билд-процессу заовнить хост-машину, это описано в вашей же ссылке "Документация"

    NOTE: both of these options require extra trust to your builds, as a build may get root access to the host where the TeamCity agent container is running.


    1. Zvava Автор
      14.12.2021 10:55

      Спасибо, поправил.


  1. emashev
    14.12.2021 11:50

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

    У gitlab runner'ов первый способ описан в документации для сборок docker-in-docker, вполне себе рабочий. Прибить хостовый докер нужно сильно постараться.
    Во всяком случае у нас проекты так и собираются в докер контейнерах на 3х жирных машинках под раннеры с пробрасыванием docker sock с хоста.


    1. Zvava Автор
      14.12.2021 18:19

      Судя по документации, у docker sock есть особенности при работе двух билд агентов на одном хосте, надо будет дополнительно настраивать.

      И ещё из билда можно грохнуть контейнер билд агента.


      1. emashev
        15.12.2021 01:30

        Так в том то и дело, что хост целиком отдается под один сборочный раннер для docker-in-docker в который пробрасывается docker.sock.
        Опять же - пример был для gitlab, про TeamCity не могу сказать.
        У меня параллельно 5-6 образов в проекте собирается иногда, конфликтов нет.
        При сборке - запускается докер контейнер, в нем собирается проект, причем можно указать в каком именно образе, после сборки контейнер останавливается.
        Грохнуть контейнер с билд агентом можно, если сделать это намеренно, либо параллельно после сборки выполнять prune, тогда да, можно в теории завалить сборку. Но можно "мусор" и по крону подчищать.


        1. Zvava Автор
          15.12.2021 19:05

          Правильно ли я понял?

          Есть хост с докером, в начале билда создаётся контейнер с пробросом sock, в него передаются сорцы (через вольюм или простым копированием), и внутри этого контейнера собирается итоговый образ через docker build... ?

          Или сам контейнер создаётся на основе образа с компилятором и просто запускается башовая команда на компиляцию?