company_banner

Puppet+Hiera. Выжимаем максимум


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


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


    Для кого была бы полезна данная статья


    Если:


    • Вы знаете, что такое Puppet и Hiera, но особо не использует их в связке, потому что непонятно, как это делать и зачем
    • У вас в компании есть множество команд и вам надо как-то разграничивать конфигурацию серверов на уровне команд
    • Вы используете паппет, и нодные файлы у вас разрослись до неимоверных размеров
    • Вам нравится читать конфигурацию серверов в божественном yaml формате :)
    • Вы в принципе интересуетесь темой Configuration Management и системным администрированием

    Эта статья для вас.


    Прежде, чем начать


    Предупрежу сразу, статья получилась длинной, но, я надеюсь, полезной. Кроме того, подразумевается, что у вас уже подключена hiera в puppet, и вы хоть как-то, но знакомы с puppet. Если hiera не подключена, это не сложно сделать.



    Вводные данные


    • У нас в SEMrush есть около 30 команд разработки, каждая из которых имеет свои сервера
    • Каждая команда работает со своим набором технологий (ЯП, СУБД и т.д.)
    • Команды могут и должны (в идеале) использовать общую конфигурацию для каких-то конкретных проектов (code reuse)
    • Команды сами управляют деплоем приложений на свои сервера (это делается не через паппет)

    Немного истории


    Изначально у нас все было в паппете версии 3, потом решили внедрить 4-й паппет, и все новые сервера начали размещать в нем, а старые потихоньку портировать в 4й.


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


    Потом начали стараться так не делать и не использовать локальные модули, а помещать ссылки на нужные модули и их репозитории в Puppetfile. Почему? Потому что те модули постоянно поддерживаются и улучшаются (ну, в идеале) сообществом и разработчиками, а наши локальные — нет. Позже внедрили hiera и перешли полностью на нее, а нодные файлы (типа nodes.pp) канули в лету.


    В четвертом паппете мы постарались полностью отказаться от локальных модулей и использовать только remote модули. К сожалению, тут снова следует вставить оговорку, так как “полностью” не получилось, иногда все равно приходится что-то склонировать и доделать самим. Конечно, здесь только hiera и никаких нодных файлов.


    Когда у вас 30 команд с зоопарком технологий, проблема того, как содержать этот зверинец с >1000 серверов становится особенно остро. Дальше я расскажу, как в этом нам помогает hiera.


    Иерархия


    У hiera (собственно, от чего она и получила свое название) настраивается иерархия. У нас она выглядит следующим образом:


    ---
    :hierarchy:
     - "nodes/%{::fqdn}"
     - "teams/%{::team}_team/nodes/%{::fqdn}"
     - "teams/%{::team}_team/projects/%{::project}/tiers/%{::tier}"
     - "teams/%{::team}_team/projects/%{::project}/%{::role}"
     - "teams/%{::team}_team/projects/%{::project}"
     - "teams/%{::team}_team/roles/%{::role}"
     - "teams/%{::team}_team/%{::team}"
     - "projects/%{::project}/tiers/%{::tier}/%{::role}"
     - "projects/%{::project}/tiers/%{::tier}"
     - "projects/%{::project}/%{::role}"
     - "projects/%{::project}"
     - "tiers/%{::tier}"
     - "virtual/%{::virtual}"
     - "os/%{::operatingsystem}/%{::operatingsystemmajrelease}"
     - "os/%{::operatingsystem}"
     - users
     - common

    Сначала разберемся с непонятными переменными (фактами).


    Каждый сервер в SEMrush в идеале должен иметь 4 выставленных, специальных факта, описывающих его принадлежность:


    • факт team — к какой команде он относится
    • факт project — к какому проекту он относится
    • факт role — какую роль в этом проекте имеет
    • факт tier — какой у него стейджинг (prod, test, dev)

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


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


    Например, есть у нас проект special. В этом проекте может быть какой-то фронтенд-сервер с nginx, backend-сервер с python, db-кластер с mysql, redis-сервера для кэширования. Все эти сервера следует поместить в один проект под названием special, а дальше назначать серверам роли.


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


    В роли для каждого сервера обычно описывается и кастомизируется конкретно сервис — для чего данный сервер предназначен (nginx, python, mysql и т.д.) Tier в данном случае нам обязательно понадобится, если нам также нужно разворачивать копию продакшн окружения на dev-площадке, но что-то в ней изменить (пароли, например). В таком случае, dev-сервера и prod-сервера будут лишь отличаться выставленным фактом tier в нужное “положение” (prod или dev). А дальше немного магии и hiera сделает свое дело.


    Если же нам нужно развернуть два идентичных сервера в одинаковой роли, но что-то в них должно отличаться, например, какие-то строки в конфигурации, то на помощь придет другая часть иерархии. Помещаем файлы с названием формата {fqdn сервера}.yaml в нужное место (например, nodes/myserver.domain.net), выставляем нужные значения переменных на уровне конкретного сервера, и паппет применит на оба сервера одинаковую конфигурацию для роли, и уникальную для каждого из серверов.


    Пример: два бэкэнда с php-кодом находятся в одной роли и полностью идентичные. Понятно, что мы не хотим бэкапить оба сервера — нет смысла. Мы можем создать роль, в которой описать одинаковую конфигурацию для обоих серверов, а потом создать еще файл nodes/backend1.semrush.net, в который поместить конфигурацию для бэкапа.


    В командном файле teams/team-name.yaml указывается конфигурация для всех серверов, принадлежащих команде. Чаще всего там описываются пользователи, которые могут взаимодействовать с этими серверами, а также их права доступа.


    На основе этих переменных у нас построена данная иерархия. Чем выше найденный файл по иерархии, тем выше приоритет конфигурации, указанной в нем.


    Отсюда следует, что переменные могут оверрайдиться исходя из данной иерархии. То есть переменная в файле роли "projects/%{::project}/%{::role}" имеет больший приоритет, чем переменная в файле проекта "projects/%{::project}". Также переменные могут мерджиться на всех уровнях иерархии, если у вас модуль и/или профиль/роль написаны так, что позволяют это делать. Указав общую часть конфига mysql для всех серверов проекта, можно добавить в ту же переменную на других уровнях иерархии специальные части, имеющие вес для данной роли (для слейва будет дополнительная секция в конфиге).


    Получается, самый высокий приоритет имеет файл конкретной ноды, расположенный по пути — "hieradata/nodes/%{::fqdn}". Дальше следует файл ноды, но уже на уровне команды. Ниже всего находится блок, описывающий другие, более общие факты:


     - "virtual/%{::virtual}"
     - "os/%{::operatingsystem}/%{::operatingsystemmajrelease}"
     - "os/%{::operatingsystem}"
     - users
     - common

    Соответственно, в файле common.yaml у нас содержится конфигурация, которая точно должна приезжать на все сервера, в файле users.yaml описаны все юзеры (но не все они создаются на серверах, конечно же), в os/%{::operatingsystem} общая конфигурация, свойственная серверам с определенной ОС (используется факт ::operatingsystem) и так далее.


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


    Профили


    Важным моментом в конфигурировании серверов с помощью модулей является использование профилей. Они располагаются по пути site/profiles и являются точками входа в модули. Благодаря им можно более тонко настраивать навешиваемые модули на сервера и создавать нужные ресурсы.


    Рассмотрим простой пример. Есть модуль, устанавливающий и настраивающий redis. И мы хотим также при подключении данного модуля установить sysctl параметр vm.overcommit_memory в значение 1, потому что вот. Тогда пишем небольшой профиль, обеспечивающий этот функционал:


    # standalone redis server 
    class profiles::db::redis (
      Hash $config = {}, 
      String $output_buffer_limit_slave = '256mb 64mb 60',
    ) {
      # https://redis.io/topics/faq#background-saving-fails-with-a-fork-error-under-linux-even-if-i-have-a-lot-of-free-ram
      sysctl { 'vm.overcommit_memory':
        ensure => present,
        value  => '1',
      }
    
      class { '::redis':
        * => $config,
      }
    }

    Как было сказано выше, профили являются инструментом, позволяющим менять/улучшать поведение модуля, а также уменьшать количество конфигурации в hiera. Если вы используете удаленные модули, то часто можете столкнуться с проблемой, что "одобренные" модули часто не обладают нужным вам функционалом, или имеют какие-то баги/недоработки. Тогда в принципе можно склонировать этот модуль и поправить/добавить функционал. Но правильным решением будет, если это возможно, написать хороший профиль, который способен “приготовить” модуль нужным вам образом. Ниже будут представлены несколько примеров профилей, и можно будет лучше понять, для чего они нужны.


    Сокрытие секретов в hiera


    Одним из важных преимуществ hiera по сравнению с “голым” паппетом является ее способность хранить sensitive data в конфигурационных файлах в зашифрованном виде в репозитории. Ваши пароли будут в безопасности.


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


    На клиенте (рабочем компьютере) тулза ставится просто, можно через gem install hiera-eyaml. Дальше с помощью команды вида eyaml encrypt --pkcs7-public-key=/path/to/public_key.pkcs7.pem -s 'hello' можно шифровать данные и вставлять в файл с расширением eyaml или просто yaml в зависимости от того, как вы настроите, а дальше паппет сам разберется. Получится что-то вроде:


    roles::postrgresql::password: 'ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAbIz1ihQlThMWa9T+Lq194Y6QdElMD1XTev5y+VPSHtkPTu6Al6TJaSrXF+7phJIjue+NF4ZVtJCLkHxUR6nJJqks0fcGS1vF2+6mmM9cy69sIU1A3HqpOHZLuqHAc7jUqljYxpwWSIGOK6I2FygdAp5FfOTewqfcVVmXj97EJdcv3DKrbAlSrIMO2iZRYwQvyv+qnptnZ7pilR2veOCPW2UMm6zagDLutX9Ft5vERbdaiCiEfTOpVa9Qx0GqveNRVJLV/5lfcL5ajdNBJXkvKqDbx8d3ZBtEVAAqeKlw0LqzScgmCbWQx2kUzukX5LSxbTpT0Th984Vp1sl7iPk7UTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCp5GcwidcEMA+0wjAMblkKgBCR/f9KGXUgLh3/Ok60OIT5]'

    Или многострочной строкой:


    roles::postgresql::password: >
        ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEw
        DQYJKoZIhvcNAQEBBQAEggEAbIz1ihQlThMWa9T+Lq194Y6QdElMD1XTev5y
        +VPSHtkPTu6Al6TJaSrXF+7phJIjue+NF4ZVtJCLkHxUR6nJJqks0fcGS1vF
        2+6mmM9cy69sIU1A3HqpOHZLuqHAc7jUqljYxpwWSIGOK6I2FygdAp5FfOTe
        wqfcVVmXj97EJdcv3DKrbAlSrIMO2iZRYwQvyv+qnptnZ7pilR2veOCPW2UM
        m6zagDLutX9Ft5vERbdaiCiEfTOpVa9Qx0GqveNRVJLV/5lfcL5ajdNBJXkv
        KqDbx8d3ZBtEVAAqeKlw0LqzScgmCbWQx2kUzukX5LSxbTpT0Th984Vp1sl7
        iPk7UTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCp5GcwidcEMA+0wjAM
        blkKgBCR/f9KGXUgLh3/Ok60OIT5]

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


    Пример на пальцах


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


    Давайте теперь рассмотрим пример, как можно конфигурировать сервера с помощью hiera в puppet4. Я не буду публиковать код всех профилей, потому что иначе пост получится довольно больших размеров. Основной акцент я сделаю на иерархии и конфигурации hiera.


    Задача такая: нам нужно развернуть:


    • Два идентичных бд-сервера, на которых развернут postgresql
    • Еще два сервера — frontend с nginx
    • Пятый и шестой сервера — бэкэнды на python в докере
    • Все то же самое и на dev-окружении, за исключением некоторой конфигурации серверов

    Будем создавать нашу иерархию по порядку и начнем мы с проектного файла.


    Проект


    Создаем файл проекта projects/kicker.yaml. Поместим в него то, что общее у всех серверов: нам нужны некоторые репозитории и папки для деплоя, а также сам пользователь deploy.


    ---
    classes:
      - apt::debian::semrush
    
    files:
      "/srv/data":
        ensure: 'directory'
        owner:  'deploy'
        group:  'www-data'
        mode:   '0755'
      '/srv/data/shared_temp':
        ensure: 'directory'
        owner:  'deploy'
        group:  'www-data'
        mode:   '0775'
    
    user_management::present:
      - deploy

    Роль db


    Создаем файл роли для БД-серверов projects/kicker/db.yaml. Пока что обойдемся без разделения серверов на окружения:


    ---
    classes:
      - profiles::db::postgresql
    
    profiles::db::postgresql::globals:
      manage_package_repo: true
      version: '10'
    profiles::db::postgresql::db_configs:
      'listen_addresses':
        value: '*' 
    
    profiles::db::postgresql::databases:
      kicker: {}
    
    profiles::db::postgresql::hba_rules:
      'local connect to kicker':
        type: 'local'
        database: 'kicker'
        user: 'kicker'
        auth_method: 'md5'
        order: '001'
      'allow connect from 192.168.1.100':
        type: 'host'
        database: 'kicker'
        user: 'kicker'
        auth_method: 'md5'
        address: '192.168.1.100/32'
        order: '002'

    Здесь мы подключаем один профиль, написанный для общего использования всеми, кто захочет установить postgres себе на сервера. Профиль конфигурируем и позволяет гибко настроить модуль перед его применением.


    Для самых любопытных ниже под катом код этого профиля:


    Профиль profiles::db::postgresql
    class profiles::db::postgresql (
      Hash $globals = {},
      Hash $params = {},
      Hash $recovery = {},
      Hash[String, Hash[String, Variant[String, Boolean, Integer]]] $roles = {},
      Hash[String, Hash[String, Variant[String, Boolean]]] $db_configs = {},
      Hash[String, Hash[String, Variant[String, Boolean]]] $databases = {},
      Hash[String, String] $db_grants = {},
      Hash[String, Hash[String, String]] $extensions = {},
      Hash[String, String] $table_grants = {},
      Hash[String, Hash[String, String]] $hba_rules = {},
      Hash[String, String] $indent_rules = {},
      Optional[String] $role = undef, # 'master', 'slave'
      Optional[String] $master_host = undef,
      Optional[String] $replication_password = undef,
      Integer $master_port = 5432,
      String $replication_user = 'repl',
      String $trigger_file = '/tmp/pg_trigger.file',
    ){
    
      case $role {
        'slave': {
          $_params = {
            manage_recovery_conf => true,
          }
    
          if $globals['datadir'] {
            file { "${globals['datadir']}/recovery.done":
              ensure => absent,
            }
          }
    
          $_recovery = {
            'recovery config' => {
              standby_mode     => 'on',
              primary_conninfo => "host=${master_host} port=${master_port} user=${replication_user} password=${replication_password}",
              trigger_file     => $trigger_file,
            }
          }
    
          $_conf = {
            'hot_standby' => {
              value => 'on',
            },
          }
    
          file { $trigger_file:
            ensure => absent,
          }
    
        }
        'master': {
          $_conf = {
            'wal_level' => {
              value => 'replica',
            },
            'max_wal_senders' => {
              value => 5,
            },
            'wal_keep_segments' => {
              value => 32,
            },
          }
    
          file { $trigger_file:
            ensure => present,
          }
        }
        default: {
          $_params = {}
          $_recovery = {}
          $_conf = {}
        }
      }
    
      class { '::postgresql::globals':
        * => $globals,
      }
    
      class { '::postgresql::server':
        * => deep_merge($_params, $params),
      }
    
      create_resources('::postgresql::server::config_entry', deep_merge($_conf, $db_configs))
      create_resources('::postgresql::server::role', $roles)
      create_resources('::postgresql::server::database', $databases)
      create_resources('::postgresql::server::database_grant', $db_grants)
      create_resources('::postgresql::server::extension', $extensions)
      create_resources('::postgresql::server::table_grant', $table_grants)
      create_resources('::postgresql::server::pg_hba_rule', $hba_rules)
      create_resources('::postgresql::server::pg_indent_rule', $indent_rules)
      create_resources('::postgresql::server::recovery', deep_merge($_recovery, $recovery))
    }

    Таким образом, мы одним махом устанавливаем Postgresql 10, настраиваем конфиг (listen), создаем БД kicker, а также записываем в pg_hba.conf два правила для доступа к этой базе. Круто!


    Роль frontend


    Беремся за frontend. Создаем файл projects/kicker/frontend.yaml следующего содержания:


    ---
    classes:
      - profiles::webserver::nginx
    
    profiles::webserver::nginx::servers:
      'kicker.semrush.com':
        use_default_location: false
        listen_port: 80
        server_name:
          - 'kicker.semrush.com'
    
    profiles::webserver::nginx::locations:
      'kicker-root':
        location: '/' 
        server: 'kicker.semrush.com'
        proxy: 'http://kicker-backend.semrush.com:8080'
        proxy_set_header:
          - 'X-Real-IP $remote_addr'
          - 'X-Forwarded-for $remote_addr'
          - 'Host kicker.semrush.com'
        location_cfg_append:
          'proxy_next_upstream': 'error timeout invalid_header http_500 http_502 http_503 http_504'
        proxy_connect_timeout: '5' 

    Здесь все просто. Мы подключаем профиль profiles::webserver::nginx, который подготавливает вход в модуль nginx, а также определяем переменные, а конкретно server и location для этого сайта.


    Внимательный читатель заметит, что правильнее будет поместить описание сайта выше по иерархии, ведь у нас будет еще dev-окружение, и там будут использоваться другие переменные (server_name, proxy), но это не слишком важно. Описывая роль таким образом, мы сможем увидеть, как переопределяются эти переменные только лишь иерархией.


    Роль docker


    Осталась роль docker projects/kicker/docker.yaml:


    ---
    classes:
      - profiles::docker
    
    profiles::docker::params:
      version: '17.05.0~ce-0~debian-stretch'
    
    packages:
      'python3-pip':
        provider: apt
      'Fabric3':
        provider: pip3
        ensure:   1.12.post1
    
    user_management::users:
      deploy:
        groups:
          - docker

    Профиль profiles/docker.pp очень прост и изящен. Приведу его код:


    Профиль profiles::docker
    class profiles::docker (
      Hash $params = {}, 
      Boolean $install_kernel = false,
    ){
    
      class { 'docker':
        * => $params,
      }
    
      if ($install_kernel) {
        include profiles::docker::kernel
      }
    }

    Все готово. Этого уже достаточно, чтобы развернуть нужный нам продукт на множестве серверов, просто назначив им определенные проект и роль (например, положив файлик в нужном формате в директорию facts.d, местоположение которой зависит от способа установки puppet).


    Сейчас мы имеем следующую структуру файлов:


    .
    ├── kicker
    │   ├── db.yaml
    │   ├── docker.yaml
    │   └── frontend.yaml
    └── kicker.yaml
    
    1 directory, 4 files

    Разберемся теперь с окружениями и определением конфигурации, уникальной для роли на конкретной площадке.


    Окружения и override


    Создадим общую конфигурацию для всего прода. Файл projects/kicker/tiers/prod.yaml содержит указание, что нам нужно подключить класс с файрволом на это окружение (ну, прод же все-таки), а также некий класс, обеспечивающий повышенный уровень безопасности:


    ---
    classes:
      - semrush_firewall
      - strict_security_level

    Для dev-окружения, если нам нужно описать что-то специфичное, создается такой же файл, и в него вносится нужная конфигурация.


    Далее нужно все-таки переопределить переменные для nginx-конфига роли frontend в dev-окружении. Для этого понадобится создать файл projects/kicker/tiers/dev/frontend.yaml. Обратите внимание на новый уровень иерархии.


    ---
    profiles::webserver::nginx::servers:
      'kicker-dev.semrush.com':
        use_default_location: false
        listen_port: 80
        server_name:
          - 'kicker-dev.semrush.com'
    
    profiles::webserver::nginx::locations:
      'kicker-root':
        location: '/' 
        server: ‘kicker-dev.semrush.com'
        proxy: 'http://kicker-backend-dev.semrush.com:8080'
        proxy_set_header:
          - 'X-Real-IP $remote_addr'
          - 'X-Forwarded-for $remote_addr'
          - 'Host kicker-dev.semrush.com'
        location_cfg_append:
          'proxy_next_upstream': 'error timeout invalid_header http_500 http_502 http_503 http_504'
        proxy_connect_timeout: '5' 

    Класс указывать уже не надо, он наследуется с предыдущих уровней иерархии. Здесь мы изменили server_name и proxy_pass. Сервер, у которого будут факты role=frontend и tier=dev, сначала найдет для себя файл projects/kicker/frontend.yaml, но потом переменные из этого файла будут переопределены файлом с бОльшим приоритетом projects/kicker/tiers/dev/frontend.yaml.


    Сокрытие пароля для PostgreSQL


    И таким образом у нас остается последний пункт на повестке дня — это задать пароли для PostgreSQL.


    Пароли должны различаться в окружениях. Будем использовать eyaml для безопасного хранения паролей. Создадим пароли:


    eyaml encrypt -s 'verysecretpassword'
    eyaml encrypt -s 'testpassword'

    Вставляем полученные строки в файлы **projects/kicker/tiers/prod/db.yaml** и **projects/kicker/tiers/dev/db.yaml** (или можно использовать расширение eyaml, это настраиваемо) соответственно. Вот пример:


    ---
    profiles::db::postgresql::roles:
      'kicker':
        password_hash: >  'ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAsdpb2P0axUJzyWr2duRKAjh0WooGYUmoQ5gw0nO9Ym5ftv6uZXv25DRMKh7vsbzrrOR5/lLesx/pAVmcs2qbhd/y0Vr1oc2ohHlZBBKtCSEYwem5VN+kTMhWPvlt93x/S9ERoBp8LrrsIvicSYZByNfpS2DXCFbogSXCfEPxTTmCOtlOnxdjidIc9Q1vfAXv7FRQanYIspr2UytScm56H/ueeAc/8RYK51/nXDMtdPOiAP5VARioUKyTDSk8FqNvdUZRqA3cl+hA+xD5PiBHn5T09pnH8HyE/39q09gE0pXRe5+mOnU/4qfqFPc/EvAgAq5mVawlCR6c/cCKln5wJTA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDNKijGHBLPCth0sfwAjfl/gBAaPsfvzZQ/Umgjy1n+im0s]'

    Далее пароль для роли kicker приедет, расшифруется и применится на БД-сервере в PostgreSQL.


    На этом, собственно, и все. Да, пример получился массивным, но, надеюсь, функциональным, не оставляющим вопросов, понятным и полезным. Итоговая иерархия в hiera получилась такая:


    .
    ├── db.yaml
    ├── docker.yaml
    ├── frontend.yaml
    └── tiers
        ├── dev
        │   ├── db.yaml
        │   └── frontend.yaml
        ├── prod
        │   └── db.yaml
        └── prod.yaml
    
    3 directories, 7 files

    Вы можете посмотреть эти файлы "вживую", склонировав специально созданный репозиторий


    Заключение


    Puppet хорош и удобен в связке с hiera. Я бы не назвал его идеальным инструментом конфигурации в современном мире, вовсе нет, но он заслуживает внимания. С некоторыми задачами он справляется очень хорошо, а его “философия” поддержки постоянно одинакового состояния ресурсов и конфигурации может играть немаловажную роль в обеспечении безопасности и единообразия конфигураций.


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


    Наша цель как админов в итоге — ничего не конфигурировать самим. Все это в идеале должны делать сами команды. А мы им должны дать инструмент или продукт, позволяющий это делать безопасно, легко и, главное, с точным результатом. Ну, и помогать решать архитектурные и более серьезные задачи, чем "Нужно установить PostgreSQL на сервер и создать пользователя". Камон, 2018-й год на дворе! Поэтому выкидывайте puppet и ansible и двигайтесь в serverless future.


    C развитием облаков, контейнеризации и систем оркестраций контейнеров, системы управления конфигураций потихоньку отступают и отходят на задний план для пользователей и клиентов. Можно же в облаке поднять отказоустойчивый кластер контейнеров и держать свои приложения в контейнерах с авто-скелингом, бэкапированием, реплицированием, авто-дискавери и т.д, не написав ни строчки для ansible, puppet, chef etc. Ни о чем заботиться не надо (ну почти). С другой стороны, железных серверов из-за облаков меньше не стало. Просто вам их больше не нужно конфигурировать, это действие находится в ответственности облачного провайдера. Но вряд ли они пользуются теми же системами, что и простые смертные.


    Credits


    Спасибо:


    • Дмитрию Тупицину, Дмитрию Логинову, Степану Федорову и всей команде системных администраторов за помощь в подготовке этой статьи
    • Владимиру Легкоступову за картинку
    • Яне Табаковой за организацию этого всего и помощь в прохождении всех pre-publishing этапов
    • Никите Захарову за помощь в делах лицензирования
    SEMrush
    98,01
    Компания
    Поделиться публикацией

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

      –2
      Хиера — это жалка попытка сделать папет хоть сколько нибудь удобным и похожим на ansible, где все это из коробки. Ну и расскажите лучше про боль миграции с 3 на 4, что бы никто и когда не вздумал использовать это днище в реальных проектах.
        +1
        Простите, но говорить про миграции между версиями, сравнивая с ансиблом, где совместимость в модулях часто теряется даже при изменении минорной версии…
          –1
          Это мелочи по сравнению с тем что вас ждет в папете.
          В ansible 99% апгрейдов все же проходит без проблем. А когда они есть, время на их решение редко превышает 30 минут. В папете переехать с 3 на 4 — это переписать все и сразу. DSL и так не отличается строгостью, а с двумя версиями это все превращается в бесконечный фарш. Модули в папете даже которые идут в поставке очень низкого качества. В ansible же опять же почти всегда все работает из коробки.

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

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

          0

          А можете выложить весь /etc/puppet?

            0
            Что вы имеете ввиду?
              0
              С настройками сервера и структурой коталогов на уровень выше для большей наглядности. Куда это ложить? Просто в каталог code? Где-то дополнительно надо что-то настраивать? Пользуюсь puppet без hiera, структура просто описана в site.pp — модули, ноды, манифесты. Читая статьи про хиеру чувствую себя безнадежно отставшим, хочу попробовать.)
                +1
                Вам лучше почитать гайд о том, как подключить и настроить хиеру к паппету. Поверьте, там нет ничего сложного. Самое главное — это то, как вы придумаете свою хиерархию
            0

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

              +1
              У нас похожая система, puppet + git + r10k + hiera + hiera-file, последние позволяют вынести все секреты из кодовой базы puppet и проводит ревью кода без «секретности».
              Отказ от собственных велосипедов и переход на upstream модуля одно из лучших решений (по опыту). НО… удаленные модули работают, пока работают.
              Перестают работать в самый не подходящий момент.
              Напоролись на это. Сейчас все модули коммитятся локально и обновляются периодически после тестовой прогонкой новых версий.

              Вопрос в массы… Подскажите рабочий вариант сдружить r10k с gerrit reviews для возможности дальнейший puppet apply --environment=gerrit_review_42 (сейчас приходится пушить ветки на git для r10k).

              P.S. настоятельно рекомендую github.com/github/octocatalog-diff (сразу видно что и где зацепит изменение прямо в ревью перед сабмитом).
                0
                Спасибо за инфу, про hiera-file почитаю, как и про octocatalog-diff.

                У нас проблема с модулями решается тем, что в Puppetfile для всех удаленных модулях мы указываем требуемую версию модуля. Это несколько нивелирует мои высокопарные фразы про то, что удаленные модули круто использовать, потому что они обновляются, но такова жизнь) Просто если что-то сломалось (обновили ОС на сервере), можно удобно глянуть в проект модуля и понять, был ли пофикшен какой-то баг. И если да, обновить версию модуля (проверив перед этим все конечно).
                  0
                  >… в Puppetfile для всех удаленных модулях мы указываем требуемую версию модуля…

                  Проблема не в версии, а в недоступности удаленного ресурса на момент восстановления локально утерянной ноды. Вся инфраструктура у нас покрыта «disaster recovery plan». Все узлы восстанавливаются каждую ночь в автоматическом режиме в тестовом окружении (по одному инстансу каждого типа). В этом случае все внешние зависимости являются точкой потенциального отказа, что происходит как минимум раз в квартал по разным причинам. Слава богу у нас нету роскомпозора с блокиратором, но github/puppet-lab бывают недоступны, последний раз из-за DDOS. Ну и habr.com/post/280039 никто не отменял.
                    0
                    Я писала костыль на Python, перегенерирующий PuppetFile с репозиториев удалённых и локальных, на только локальные (в которые предварительно синкает код)
                      0
                      Звучит как оверкил, но я не знаю вашу религию.

                      У нас модули появляются/удаляются в среднем раз в две недели (причем это среднее с учетом рефакторингов и месячного затишья). Так как задействование нового модуля это коммит, то предшествующий коммит является импортом модуля и выполняется в ручном режиме (с указанием URL откуда модуль пришел, версии, commit id, ...). В этом коммите мы удаляем ненужные _нам_ запчасти (примеры, demo, тесты...). Это позволяет легко в дальнейшем обновлять модуля (так как есть все детали откуда он пришел и как менялся нами, если требовал фиксов, костылей) и также делает общий размер puppet envorinment меньше процентов на 50% что ускоряет r10k. Многие puppet модули содержат очень много ненужного в ежедневном использовании «мусора».

                      Побочным моментом тут является core review для нового модуля, которое хотя-бы поверхностно, позволяет понять что же мы там подключаем и пару раз завернуло некоторые модули. Один был слишком страшный и опасный внутри, во втором случае вопрос решался и без модуля.

                      Так же периодически проводятся апдейты закоммиченных модулей, octocatalog-diff показывает будут ли изменения после апдейта модуля, если не булет — все легко и просто, если будут — нужно смотреть что же там поменяется.
                0
                Как вы решаете вопрос с последовательностью выполнения инструкций в манифестах?
                  0
                  require например? ->, before, notify, subscribe. Но вообще стоит смотреть конкретный случай.
                    0
                    Можно использовать коллекторы ресурсов , или, как сказал выше tamaki, require, before, after и т.д.
                    Но с коллекторами надо быть аккуратным, благодаря ним можно попасть в dependency loop.

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

                    Мы не используем паппет в виде демона, а запускаем его по крону. Грубо говоря, реальные ошибки по неправильным последовательностям применения ресурсов может быть только при первоначальном сетапе сервера и последующего первого запуска паппета. Сервера деплоятся автоматически, паппет на них первый раз запускается тоже «сам». В течение 20 минут он гарантированно разрулит все фейлы и применит-таки конфигурацию, запустившись пару раз по крону.
                    0
                    А каким образом факты team, project, tier, role — попадают на сервер? Используете кастомные факты? Если да, то на основе каких данных вы формируете указанные вами факты?
                    Мне очень интересно узнать, так как я тоже формирую кастомные факты group, subgroup на основе hostname. Например если имя сервера php10-1.example.com, то group = php, subgroup = 10.
                      0
                      Кастомные файлы можно создавать как при сетапе сервера (мы используем Maas), так и руками.
                      Да, мы используем кастомные факты. В папку /opt/puppetlabs/facter/facts.d/ кладутся файлы типа /opt/puppetlabs/facter/facts.d/team.txt с содержимым:
                      team=admin

                      Кроме того, факты можно задать непосредственно через запуск паппет агента:
                      FACTER_team=admin puppet agent -t

                      Тогда агент при прогоне сам создаст нужный файл факта и применит соотвествующую конфигурацию.

                      Данные факты мы выставляем не на основе fqdn, как вы, а в соответствии с:
                      — какой команде делаем сервер?
                      — что это за проект? (если есть)
                      — какая роль у сервера? (если есть)
                      — какой стейджинг? (если нужно и есть)

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

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

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