Управляя Github-ом: через Terraform к самописному решению на Ansible

    У нас 350+ человек и 400+ репозиториев на Github-е. В каждой репе может быть несколько админов, и они творят, что считают нужным, — естественно, случается так, что один человек не знает, что делает другой. Когда нам в инфре надоело смотреть на мучения других и добавлять/удалять людей вручную, мы решили, что перейдем централизованное управление, Infrastructure as Code.


    image


    И в качестве платформы выбрали Terraform.


    «У меня есть кубики с буквами О, П, А…»


    На бумаге все выглядело гладко. Terraform популярен, будет нетрудно найти знающих его людей. У него есть состояние, и TF приводит ресурсы к соответствию — мы всегда сможем быть уверены, что реальная конфигурация именно такая, как её описали. И не надо больше лазить по Web UI — посмотрел в конфиг и всё увидел.


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


    Чтобы решить проблемы с ограничениями, мы поделили всё управление на шесть частей:


    1. Члены организации.
    2. Репозитории.
    3. Команды.
    4. Состав команд.
    5. Репозитории команд.
    6. Коллабораторы.

    Теперь типичные операции стали выполнять в два захода.  Чтобы добавить нового разработчика, запускаем Terraform с разными параметрами: 1 и 4. Чтобы добавить новый репозиторий, выполняем 2 и 5. Это стало занимать довольно много времени: запустить TF один раз, вернуться через несколько минут, запустить повторно. Вернуться ещё раз — ответить автору запроса, что всё сделано. Или не сделано, если где-то в конфиге или пулл-реквесте закралась ошибка  Однажды принесли PR, где в нескольких местах вместо английской c была написана русская с. Отлавливать пришлось долго…


    Да и синтаксис не зашел. Описание чего угодно довольно многословное. Вот пример:


    resource "github_membership" "membership_for_юзер" {
        username = "юзер"
        role     = "member"
    }
    
    resource "github_team" "team_команда" {
        name           = "команда"
        description    = ""
        privacy        = "closed"
        parent_team_id = "123456"
    }
    
    resource "github_team_membership" "team_команда_юзер_membership" {
        team_id  = "${data.terraform_remote_state.teams.team_команда_id}"
        username = "юзер"
        role     = "member"
    }
    
    resource "github_repository" "репа" {
        name          = "репа"
        description   = ""
        homepage_url  = ""
        has_projects  = false
        has_wiki      = true
        has_issues    = true
        has_downloads = true
        private       = true
        archived      = false
        topics        = ["yii", "school", "mobile"]
    }
    
    resource "github_team_repository" "team_команда_repo_репа" {
        team_id    = "${data.terraform_remote_state.teams.team_команда_id}"
        repository = "${data.terraform_remote_state.repos.repo_репа_name}"
        permission = "push"
    }
    
    resource "github_repository_collaborator" "репа_юзер_collaborator" {
        repository = "репа"
        username   = "юзер"
        permission = "admin"
    }

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


    Ссылаться на ресурсы по именам нельзя, можно только по id.  Ресурсы — репозитории — описываются в одном файле, а переменные с их id в другом.  Юзеры и команды аналогично.  Также лежат в разных местах параметры самого репозитория и список команд с доступом к нему. А коллабораторы где-то в третьем месте.  Типичный вопрос — у кого есть доступ к этой репе?  Попробуй собери всё в кучу.


    Подход «посмотрел в конфиг и всё увидел» не сработал.  Репозитории команд — это отношение «многие ко многим».  Всё в одном файле размером в тысячи строк.  Как отсортировать такой список?  По репозиториям?  По командам?  Никак.  Новые записи добавляют то в конец, то в середину.  Собрать полный список, у кого есть доступ в конкретную репу — это отдельная задача.


    Спустя месяцы после внедрения TF, особенно весело было узнавать, что какой-то репозиторий был сделан втихую вручную. И когда теперь понадобилось кому-то дать права, мы не можем это сделать. Ведь Terraform про него ничего не знает!  Разумеется, эту проблему тоже можно решить: удалить репу и сделать её снова средствами TF, или же как-то переинициализировать сам TF.  Но...


    Ёлки-иголки, что ж так сложно-то!


    image


    Добавить человека в организацию — это всего одно обращение к API.  Дать права команде на репозиторий — аналогично. Наконец, когда Terraform просто стал падать на управлении составом команд со словами, что он хочет удалить 800 ресурсов, добавить 801 и почему-то не может это сделать, мы сели прикидывать, как оно могло бы быть в идеальном мире.


    • Изменения применяются точечно.
    • Простой синтаксис, понятный без чтения документации.  Без лишних слов вроде resource, value и без идентификаторов типа 123456, которые непонятно, откуда брать.
    • Все параметры какой-то сущности — например, репозитория --  описаны в одном месте.
    • Одна репа / группа / организация — один файл.

    Перевели на YAML


    Организация


    skyeng:
      name: Skyeng
      admin:
        - aleksandr.sergeich
    
      member:
        - andrey.vadimych
        - denis.andreich
        - mikhail.leonidych
        - vladimir.nickolaich

    Группа


    qa-team:
      privacy: secret
    
      maintainer:
        - denis.andreich
    
      member:
        - andrey.vadimych
        - mikhail.leonidych
        - vladimir.nickolaich

    Репозиторий


    alerta:
      description: >-
        Alerta monitoring system
      homepage: https://alerta.io
    
      teams:
        admin:
          - admin-team
    
        push:
          - dev-team
          - qa-team
    
      collaborators:
        direct:
          - denis.andreich
    
        outside:
          - william.shakespeare

    Хотелось заюзать готовое решение, но не нашли — и написали своё


    Наверно, TF — это клёвая штука, но для нас оказался прокрустовым ложем…  И мы пошли реализовывать собственное видение на Ansible, который активно используем для управления инфраструктурой.


    Оно отрабатывает за считанные секунды: типичные изменения это всего несколько вызовов, для больших проектов — несколько десятков.  Легко делается CI/CD.  Параметры чего-либо собраны в один файл: изменения локальные, их просто отследить.  И теперь действительно получается заглянуть в файл и всё увидеть.  Ссылка на код в конце, а пока пара примеров.


    Теперь сделать новый репозиторий или обновить существующий можно так:


    ansible-playbook gitwand.yml
        -e github_repos__state=present
        -e github_repos__include=my_repo

    Сделать что-то с группой — вот так:


    ansible-playbook gitwand.yml
        -e github_teams__state=present
        -e github_teams__include=my_team

    Если надо обновить все группы, то просто не указываем параметр github_teams__include.


    И ещё небольшой побочный эффект.  У нас есть LDAP, и везде, где можно, мы берём юзеров и группы юзеров из него.  Логин человека состоит из имени и фамилии, и это лучше, чем ник, придуманный кем-то в бурной молодости и понятный только его владельцу.  Теперь у нас появилась возможность использовать эти же имена вместо ников на Github-е.


    Если хотите попробовать наше решение


    Оно лежит здесь.

    Skyeng
    Крупнейшая онлайн-школа Европы. Удаленная работа

    Комментарии 12

      0

      Уже не первый раз, когда я слышу от людей, юзающих TF, что 90% проходит в лёт, а оставшиеся (обязательные) 10% — кровь и смертный бой с TF'ом.

        +1
        Ой, как больно (

        Ссылаться на ресурсы по именам нельзя, можно только по id

        Это зависит от сервиса (гитхаб, aws и тд), как у них в апи обьект идентифицируется — так провайдер терраформа его использует. Концепция id вида vpc-123456789 лучше чем Main-vpc или как вы его назовете при создании, так как при изменении использования ресурса вам достаточно обновить теги/дескрипшн а не пересоздавать его.

        Спустя месяцы после внедрения TF, особенно весело было узнавать, что какой-то репозиторий был сделан втихую вручную. И когда теперь понадобилось кому-то дать права, мы не можем это сделать. Ведь Terraform про него ничего не знает!


        не представляю как вы могли пройти мимо импорта — www.terraform.io/docs/providers/github/r/repository.html#import

        Так же есть `-target=resource` который позволяет лимитировать apply на некоторый ресурс или несколько.

        Преимущество терраформа перед ансиблом, шефом и тп для инфраструктуры — состояние, что позволяет управлять полным жизненным циклом обьекта, а не только созданием.
          +1
          Преимущество терраформа перед ансиблом, шефом и тп для инфраструктуры — состояние, что позволяет управлять полным жизненным циклом обьекта, а не только созданием.

          В моём случае другой подход: управляем тем, что записано в конфиге.


          • Репа есть в конфиге? Она управляется.
          • Нет в конфиге? С репой гарантированно ничего не случится.

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


          А если сархивировать репу, то её и Terraform не сможет разархивировать обратно.

            0
            Например, удалить репу невозможно.

            Это все же недостаток который для такого специфичного юзкейса можно проигнориовать. В терраформе есть концепция lifecycle которая позволяет влиять на на жизненный цикл ресурса www.terraform.io/docs/configuration/resources.html#lifecycle-lifecycle-customizations, например запретить удаление можно так:
            lifecycle = {
            prevent_destroy = true
            }


          +1
          Знакома боль автора.
          Сам пришел некогда в организацию подобного размера, с 300+ репами. Тоже было организовано с помощью Terraform в одной папке и с одним гигантским стейтом, terraform plan занимал пол часа плюс иногда обрывался из-за лимитов GitHub API. Также при добавлении/удалении юзера из команды(github_team_membership) из-за используемого count сначала все члены команды удалялись, потом добавлялись, что очень раздражало.

          Решилось путем разделения Terraform кода в кучу подпапок — по командам с разными state.
          CI детектит в каких папках произошли изменения, и там запускает terraform plan.
          Если есть перекрестные связи между командами — решается с помощью чтения внешнего data.terraform_remote_state.
          Проблему с github_team_membership + count помог решить terraform 0.12 for_each, теперь при добавления юзера в команду terraform делает атомарное изменение и не нужно всю команду каждый раз передобавлять.

          Теперь изменения в даже в самой большой команде занимают не больше 1 минуты.
          Удаление/добавление репосов и юзеров у нас запрещено на уровне GitHub организации и старые репосы Terraform просто архивирует.
          Вполне рабочая схема.
            0
            Всегда приятно читать взгляды людей на работу с прекрасным инструментом.

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

            Модули и Terragrunt спасает от копипасты чуть менее, чем полностью.

            Поставили минус вместо подчерка — и никто не заметит. Цена ошибки — минуты ожидания.

            pre-commit-terraform спасает от большинства неприятностей, связанных с ошибками в синтаксисе и от некоторых ошибок при работе с провайдерами. Человеческие ошибки (_ вместо -) могут быть совершены при работе с любым инструментом.

            Представьте себе дебаг с минутами между итерациями...

            Здесь может помочь опция -target, довольно удобная при ручном дебаге, но категорически вредная при запуске в CI.

            У меня была проблема HTTP 429 при работе с DigitalOcean — 300+ дроплетов и лимит на 2000 обращений к API в час. Решилась общением с саппортом и повышением до 10к + отдельный аккаунт для Terraform. Посему, могу понять Вашу боль.
            Тем не менее, я считаю, что Вы недостаточно разобрались в нюансах использования Terraform и в том, как эти нюансы решаются.
              0

              На тему rate limits: внутри AWS провайдера, как мне кажется, видел что-то похожее на встроенный exponential backoff, то есть даже если вдруг рейт лимит выстрелит, инструмент попытается восстановиться. Видимо другие провайдеры могут быть не настолько стабильны.

                –3
                При наших размерах на это уходило около 20 минут, а до следующего изменения нужно было выждать час — мы упирались в лимиты Github-а на количество обращений к API.

                Возможно перед написанием своего велосипеда следовало бы почитать доки и разобраться со стейтом? Что это такое, как его разделить, например.


                Да и синтаксис не зашел. Описание чего угодно довольно многословное.

                Возможно перед написанием своего велосипеда следовало бы почитать доки и разобраться с модулями? Как их писать и сделать из них нужный формат ресурсов, например.


                И когда теперь понадобилось кому-то дать права, мы не можем это сделать. Ведь Terraform про него ничего не знает!

                Возможно перед написанием своего велосипеда следовало бы почитать доки и разобраться с импортом ресурсов. Как добавить созданный руками ресурс в терраформ 1 командой, например.

                  +2
                  Какой смысл усиленно превозмогать, если велосипед оказался проще и удобнее для автора? Превозмогание ради превозмогания?
                    –1
                    1. Когда ты нанимаешь людей новых людей, они уже скорее всего знают терраформ. Твой велосипед они не знают. А следовательно быстрее станут приносить пользу.
                    2. На своем велосипеде ты собираешь все грабли, которые в стандартном инструменте уже собраны и обработаны.
                    3. В случае изменения АПИ, добавления новых ресурсов и т.д. тебе не нужно ничего допиливать, т.к. огромное комьнити (в т.ч. сам амазон) делают стандартный инструмент.
                    4. Если для вас превознемогать это прочесть документацию к стандартному инструменту это превознеможение, то вы никчемный инженер.
                      0
                      Превозмогание ради превозмогания?

                      нет, не нужно конечно, но человек вроде infra as a code делал, а получил нечто, что может только создавать ресурсы, то есть цель не достигнута. На документацию за месяц (или месяцы) пока они ели кактус, времени в команде ни у кого нашлось? Первая же ссылка в гугле `terraform how to import resource` рассказывает что это и зачем. Такие статьи искажают действительность, terraform — номер один в мире как infra as a code не просто так, при том что конкурирует с компаниями у которых ресурсов сильно больше.
                        +1
                        • TF мои потребности не покрывает. Подробности в статье.
                        • Поставленные цели я достиг. :-) Какие они были — читайте там же.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое