В разрезе: новостной агрегатор на Android с бэкендом. Система контроля конфигураций (Puppet)

    Вводная часть (со ссылками на все статьи)

    В ITIL (v3) среди описанных процессов есть 2 особенно интересных: «Процесс управления конфигурациями» и «Процесс управления изменениями», предназначенных для анализа и управления изменениями конфигураций систем. Для продолжения повествования нужно определиться, что такое «система». В это понятие входит огромное количество составляющих, влияющих (прямо или косвенно) на предоставление услуги:

    • серверы

      • настройки безопасности (пользователи, группы, права, межсетевые экраны);
      • установленные приложения и библиотеки;
      • настройки работы приложений (лимиты по дискрипторам, памяти, времени CPU и т.д.);
      • резервное копирование;

    • системы мониторинга за работой прикладного и системного ПО;
    • конфигурационные файлы самого продукта, его компонентов, вспомогательных системных и прикладных приложений
    • ...

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


    Этот перечень компонентов системы — минимум для большинства проектов, развёрнутых более чем на одном сервере (да даже и на одном). При этом их количество по мере развития проекта меняется и растёт. При отсутствии какого-либо процесса их учёта становится очевидно к чему это приводит: вы имеете тестовый стенд (или стэнд разработки) на котором всё работает идеально, но попытка продублировать его на продуктиве или создать новый стенд приводит к:

    • очень длительному процессу развёртывания/дублирования (все вспоминают, что и как настроено по мере возникновения сбоев и часто сверяют настройки с имеющимся стендом);
    • неожиданно возникают проблемы при эксплуатации развёрнутого продукта (забывают выполнить какие-то настройки, которые на тестовом стенде внесли в последний момент, а на продуктив не перенесли);
    • замедляется процесса тестирования (каждая публикация новой сборки, даже для внутренних тестировщиков, — подвиг как по времени, так и по сложности);
    • и наконец – появляется страх у разработчиков перед выпуском новой версии и как результат – оттягивание выкладки нового релиза (реально серьёзная проблема о которой открыто никто не скажет, но её эффект будет чувствоваться постоянно).

    В итоге — среда для корректной работы вашего продукта выход из-под вашего контроля и имеющихся средств автоматизации и версионности.

    Мотивация


    Вещи вроде очевидные, но в большинстве групп по разработке на них закрываются глаза – многие аргументируют это тем, что сервер у них один, настроек мало, компетентностью работников (аргумент «у нас работают профессионалы и никуда уходить не собираются» меня просто свалил), малыми рисками и т.д. и т.п.

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

    Мотивация для разработчика-одиночки


    С учётом мощной вводной части в предыдущем абзаце преимущества для разработчика-одиночки опишу доступнее и более кратко:

    • т.к. разработка осуществляется не ежедневно, как результат некоторые особенности развёртывания плавно улетучиваются по прошествии пары дней;
    • автоматизация для одиночки – самый надёжный напарник;
    • скорость выполнения работ, обеспечиваемая автоматизацией, позволяет не потерять «запал» и желание развивать проект (это очень важный специфичный момент).

    Важная оговорка про ITIL


    Не старайтесь брать, из описанных в ITIL процессов, все шаги которые там описаны, — ситуация будет хуже, чем до этого! Я знаю, как подобные шаги внедряются в некоторыъ банковских системах и к чему это приводит (особенно без автоматизации) – бюрократическая машина удушит любой динамично развивающийся проект/систему.

    Puppet


    В моём случае был выбран Puppet. При выборе между Chef и Ansible, выбор в пользу него был сделан с учётом хорошей документационной базы, хорошей поддержки, достаточно большого количества модулей (от разработчиков и сообщества), активного развития проекта и реализацией на Ruby (который более-менее мне знаком).

    Откровенно говоря, кривая изучения для Puppet была вовсе не пологой. В связи с тем, что в разрабатываемой системе используется большое количество всяких элементов, каждый из них может быть настроен по-разному и всё это может быть развёрнуто на разных стендах – изучение инструмента потребовалось полное и тщательное. По мере изучения инструмента стали очевидны и некоторые его минусы (в большинстве случаев «by-design») и ограничения (хорошая статья о философии Puppet, поясняющая некоторые архитектурные решения). Так же по мере изучения и уже частичной реализации необходимых скриптов чуть больше узнал про Ansible в котором решены некоторые проблемы, имеющиеся в Puppet (что не отменят возможности наличия своих проблем, отсутствующих в Puppet). Так, что последующее повествование это не реклама Puppet, а описание возможностей и опыта использования.

    Немного о Puppet


    Puppet использует свой собственный конфигурационный язык (DSL), который был разработан что бы быть доступным для системных администраторов. Язык Puppet позиционируется как не требующий формального знания программирования и его синтаксис был сформирован под влияние формата конфигурационных файлов Nagios.

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

    Группы ресурсов могут быть организованы в классы, которые являются более крупными единицами конфигурации. В то время как ресурс может описывать единственный файл/каталог или пакет, класс может описывать всё, что нужно для конфигурирования службы или приложения (включая необходимое количество пакетов, конфигурационных файлов, демонов/служб и задач обслуживания). Более маленькие классы могут объединяться в крупные, которые описывают целую системную роль – «сервер базы данных» или «рабочий узел кластера».

    Пример класса с ресурсами внутри:

    class apache (String $version = 'latest') {
      package {'httpd':
        ensure => $version, # Using the class parameter from above
        before => File['/etc/httpd.conf'],
      }
      file {'/etc/httpd.conf':
        ensure  => file,
        owner   => 'httpd',
        content => template('apache/httpd.conf.erb'), # Template from a module
      }
      service {'httpd':
        ensure    => running,
        enable    => true,
        subscribe => File['/etc/httpd.conf'],
      }
    }
    

    Машины, которые выполняют разные роли должны, в общем случае, получить разный набор классов. Задача конфигурирования того, какие классы будут применены на какие машины – задача узлов Puppet.

    Примеры определения узлов Puppet:

    node 'www1.example.com', 'www2.example.com', 'www3.example.com' {
      include common
      include apache, squid
    }
    
    node /^(foo|bar)\.example\.com$/ {
      include common
    }

    Факты и база данных Hiera. Перед тем как выполнить код Puppet выполняется сборка информации об узле, собранная информация оформляется в виде предопределённых фактов – переменных, которые можно использовать где угодно в коде. Hiera это встроенная «key-value» база данных. По-умолчанию в качестве источника данных используются файлы формата YAML или JSON, хотя возможно расширение для использования любого источника данных. В связи с её иерархичностью и возможностью изменения данных в зависимости от узла – её использование является неотъемлемой частью работы большинства модулей/классов.

    Модули – самодостаточные блоки кода и данных (классы, шаблоны, файлы, и т.д.). Эти повторно используемые, доступные для общего пользования элементы являются основными строительными блоками Puppet.

    Тем, кто планирует использовать Puppet в основном придётся заниматься созданием классов и иногда — модулей.

    Варианты развёртывания


    При внедрении Puppet возможны 2 варианта с централизованным хранением конфигурации и без него:

    • Централизованное хранение конфигурации: преимущества явно прослеживаются при наличии БОЛЬШОГО количества серверов. В этом случае на клиентские машины передаётся информация, касающиеся только самой машины, что так же обеспечивает некоторый уровень безопасности и минимизирует траффик.
    • Децентрализованное хранение конфигурации: обосновано при небольшом количестве серверов, при этом на машин должен быть полный комплект конфигурационных скриптов и файлов и при запуске агентов будет выполнена их компиляция и выполнения части касающейся данной машины. Реализуется обычной cron-задачей, запускаемой каждые 15 минут.

    Мой скрипт выглядит примерно так:

    #!/bin/sh
    
    PUPPET_BIN='/opt/puppetlabs/bin/puppet'
    
    # Ставим необходимые пакеты для старта
    apt-get update &&  apt-get -y install git mc htop apt-transport-https nano wget lsb-release apt-utils curl python
    
    # Первоначально осуществляем установку `puppet-agent`
    if [ ! -d /etc/puppetlabs ]; then
      	rm *.deb.* *.deb # possible trash
    	wget https://apt.puppetlabs.com/puppetlabs-release-pc1-xenial.deb && dpkg -i puppetlabs-release-pc1-xenial.deb
    	apt-get update && apt-get -y install puppet-agent
    fi
    
    # Определяем тип `environment`
    /opt/puppetlabs/bin/puppet config set environment $PUPPET_ENV
    if [ ! -d /etc/puppetlabs/code/environments/$PUPPET_ENV ]; then
      cp -r /etc/puppetlabs/code/environments/production /etc/puppetlabs/code/environments/$PUPPET_ENV
    fi
    
    # Install puppet modules
    $PUPPET_BIN module install puppetlabs-ntp
    $PUPPET_BIN module install aco-oracle_java
    $PUPPET_BIN module install puppetlabs-firewall
    $PUPPET_BIN module install saz-ssh
    $PUPPET_BIN module install saz-sudo
    $PUPPET_BIN module install saz-limits
    $PUPPET_BIN module install thias-sysctl
    $PUPPET_BIN module install yo61-logrotate
    $PUPPET_BIN module install puppetlabs-apt
    $PUPPET_BIN module install puppet-archive
    
    # git pull "deployment" project and go in it only if POVISION_NO_GIT_CLONE set to "true"
    if [ ${POVISION_NO_GIT_CLONE:-"false"} = "true" ];
    then
    	echo "do nothing"
    else
    	LOCAL_REV=""
    	if [ -f local_latest.sha1 ]; then
    	  LOCAL_REV=`cat local_latest.sha1`
    	fi
    	REMOTE_REV=`git ls-remote --tags | grep "latest" | awk '{print $1}'`
    	if [ $LOCAL_REV = $REMOTE_REV ]; then
    	 exit 0
    	fi
    	 git fetch --all --tags --prune
    	 git checkout -f tags/latest
    fi
    
    # replace puppet configs
    cp puppet_config/hiera.yaml  /etc/puppetlabs/code/environments/$PUPPET_ENV/
    
    # replace hiera db
    rm /etc/puppetlabs/code/environments/$PUPPET_ENV/hieradata/*
    cp -r $PUPPET_ENV/hieradata/*  /etc/puppetlabs/code/environments/$PUPPET_ENV/hieradata
    
    # replace storyline_* modules
    rm -r /etc/puppetlabs/code/environments/$PUPPET_ENV/modules/storyline_*
    cp -r modules/*  /etc/puppetlabs/code/environments/$PUPPET_ENV/modules
    
    # copy site.pp
    cp $PUPPET_ENV/site.pp /etc/puppetlabs/code/environments/$PUPPET_ENV/manifests/site.pp
    
    #echo "hostname:"
    #hostname
    $PUPPET_BIN apply /etc/puppetlabs/code/environments/$PUPPET_ENV/manifests/site.pp
    
    echo $REMOTE_REV > local_latest.sha1

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

    При запуске указанного скрипта:

    1. устанавливается сам клиент Puppet и необходимые пакеты
    2. инсталлируются необходимые модули Puppet
    3. сверяется изменение номера коммита для метки «latest» (делается при успешном интеграционном тестировании новой версии)
    4. заменяется конфигурация (hiera.yaml) БД Hiera Puppet в текущем окружении (переменная $PUPPET_ENV);
    5. заменяются YAML-файлы с данными для БД Hiera Puppet;
    6. копируются с заменой описания моих модулей;
    7. копируется конфигурация с узлами (серверами моей системы);
    8. вызывается применение всех тех настроек, что были скопированы/установлены ($PUPPET_BIN apply ….)

    Список задач, которые выполняет клиент Puppet при запуске огромен (проверка и необходимое выполнение руками была бы просто невозможна):

    • выставляются лимиты на открытые файлы, задействованный объем памяти, свапирование и количество соединений;
    • настраивается ротация логов (как для системы, так и для моего приложения и необходимых ему сервисов);
    • создаются необходимые учётные записи для администрирования с необходимыми группами и полномочиями;
    • устанавливается и настраивается NTP-сервер;
    • устанавливается и настраивается SSH-сервер;
    • устанавливается Oracle JDK;
    • настраивается брандмауэр;
    • устанавливается и настраивается большое количество компонентов, необходимых для функционирования проекта или его компонента на данном конкретном узле.


    Примеры из жизни


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

    Приведу несколько примеров кода и настроек.

    Настройка ngnix (не из пакетов, а из родного репозитария) (спрятал в блок спойлер в связи с размерами. Но для заинтересованных — обязателен к просмотру Очень много нюансов видны при его изучении)
    Класс nginx из модуля storyline_infra
    
    class storyline_infra::nginx () {
    	$params = lookup({"name" => "storyline_infra.nginx",
    	    "merge" => {"strategy" => "deep"}})
    	$reverse_port = $params['reverse_port']
    	$reverse_url = $params['reverse_url']
    	$pid_file = $params['pid_file']
    	$init_script = $params['init_script']
    	$dir_data = $params['dir_data']
    	$dir_logs = $params['dir_logs']
    	$version = $params['version']
    	$enabled_startup = $params['enabled_startup']
    	$enabled_running = $params['enabled_running']
    	# topology_configuration
    	$enabled_topology_configuration = $params['enabled_topology_configuration']
    	$topology_configuration_port = $params['topology_configuration_port']
    
    	# создать соотвествующего пользователя  (при необходимости)
    	user { 'nginx':
    		ensure => "present",
    		managehome => true,
    	}
    	# создать необходимые каталоги  (при необходимости)
    	exec { "nginx-mkdir":
    		command => "/bin/mkdir -p /data/db && /bin/mkdir -p /data/logs",
    		cwd => "/",
    		unless => '/usr/bin/test -d /data/db -a -d /data/logs',
    	} ->
    	# working dir
    	file { [ $dir_logs, $dir_data] :
    		ensure => "directory",
    		recurse => "true",
    		owner => "nginx",
    		group=> "nginx",
    		require => Exec['nginx-mkdir'],
    	}
    
    	# добавить необходимые ключи в менеджер пакетов  (при необходимости)	
    	# see by "gpg --verify keyfile"
    	apt::key { 'nginx-key':
    		id => '573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62',
    		source  => 'http://nginx.org/keys/nginx_signing.key',
    	} ->
    	
    	# добавить источник  (при необходимости)
    	# deb http://nginx.org/packages/ubuntu/ xenial nginx
    	apt::source { 'nginx-repo':
    		comment  => 'nginx repo',
    		location => "http://nginx.org/packages/ubuntu/",
    		release => "xenial",
    		repos    => "nginx",
    		include  => {
    	   		'deb' => true,
    			'deb-src' => true,
    		},
    	} ->
    
    	# осуществить установку пакета (при необходимости)
    	package {  'nginx':
    		ensure => $version,
    		# notify => Exec['disable_nginx'],
    	} →
    	# выполнить копирование конфигурационного файла из данных модуля (при необходимости). При этом сам файл шаблона содержит подстановочные знаки, которые заменяются переменными класса. Крайне удобный функционал!!!
    	file { "/etc/nginx/nginx.conf":
    		replace => true,
    		content => epp('storyline_infra/nginx.epp'),
    		owner => "nginx",
    		group=> "nginx",
    		notify => Service['nginx'],
    	}->
    	# выполнить копирование конфигурационного файла из данных модуля (при необходимости)
    	file { "/etc/nginx/conf.d/default.conf":
    		replace => true,
    		content => epp('storyline_infra/nginx_default.epp'),
    		owner => "nginx",
    		group=> "nginx",
    		notify => Service['nginx'],
    	}→
    	# выполнить копирование конфигурационного файла из данных модуля (при необходимости)
    	file { $init_script:
    		replace => true,
    		content => epp('storyline_infra/nginx_startup.epp'),
    		mode=>"ug=rwx,o=r",
    		notify => Service['nginx'],
    	}→
    	#  настроить службу/скрипты запуска (при необходимости)
    	service { 'nginx':
      		ensure => $enabled_running,
    		enable    => $enabled_startup,
    		start 		=> "${init_script} start",
    		stop 		=> "${init_script} stop",
    		status 		=> "${init_script} status",
    		restart 	=> "${init_script} restart",
    		hasrestart => true,
    		hasstatus => true,
    	}
    	#  дополнительные настройки nginx (при необходимости)
    	if $enabled_topology_configuration {
    		file { "/etc/nginx/conf.d/topology.conf":
    			replace => true,
    			content => epp('storyline_infra/nginx_topology.epp'),
    			mode=>"ug=rwx,o=r",
    			notify => Service['nginx'],
    		}
    	}
    	#  отключение автозапуска встроенными средствами (при необходимости)
    	if $enabled_startup != true {
    		exec { "disable_nginx":
    			require => Package['nginx'],
    			command => "/bin/systemctl disable nginx",
    			cwd => "/",
    		}
    	}
    
    }
    


    Как видите в каждом комментарии перед определением ресурса указано — «при необходимости». Puppet никогда не будет выполнять каких-либо операций если состояние ресурса уже соответствует его определению.

    В данном случае видно как с помощью кода «$params = lookup({"name" => "storyline_infra.nginx", "merge" => {"strategy" => "deep"}})» получаются данные из Hiera (примеры её данных я приведу позже), которые в дальнейшем используются для заполнения всех переменных.

    Конфигурационный файл Hiera:
    ---
    version: 5
    defaults:
      datadir: "hieradata"
      data_hash: yaml_data
    
    hierarchy:
      - name: "1"
        path: "nodes/%{trusted.certname}.yaml"
      - name: "2"
        path: "version.yaml"
      - name: "3"
        path: "common.yaml"
    

    В данном случае видна иерархия (ключ «hierarchy») источник, где каждый источник на более высоком уровне переопределяет значения ключей на боле низком. Это позволяет и иметь ключ, например, «www.server.port» со значением «80» в «common.yaml» и со значением «81» в «nodes/webserver1.yaml» — в итоге мы получим значение данного ключа при выполнении Puppet кода: «81» на узле с именем «webserver1» и «80» на всех остальных.

    Hiera's common.yaml
    ---
    limits::entries:
      '*/nofile':
        both: 1048576
      '*/memlock':
        both: unlimited
    logrotate::config:
        su_user: root
        su_group: syslog
        compress: true
    # sysctl
    sysctl::base::purge: false
    sysctl::base::values:
      net.core.somaxconn:
        value: '65536'
      vm.swappiness:
        ensure: absent
      fs.file-max:
        value: '500000'
      vm.max_map_count:
        value: '262144'
    storyline_base:
      oracle_java:
        version: "8u92"
    storyline_infra:
      collectd:
        server_address: "XXX.nlp-project.ru"
        pid_file: '/data/logs/collectd/collectd.pid'
        init_script: '/etc/init.d/collectd'
        dir_data:  '/data/db/collectd'
        dir_logs:  '/data/logs/collectd'
        version: "1.2.0-1"
        enabled_mongodb: false
        mongodb_user: "collectd"
        mongodb_password: "######"
        enabled_storm: false
        enabled_elasticsearch: false
        elasticsearch_port: "####"
        elasticsearch_cluster: "elastic_storyline"
        enabled_startup: false
        enabled_running: true
      influxdb:
        port_http: "####"
        port_rpc: "####"
        pid_file: '/data/logs/influxdb/influxdb.pid'
        init_script: '/etc/init.d/influxdb'
        dir_data:  '/data/db/influxdb'
        dir_logs:  '/data/logs/influxdb'
        version: "present"
        enabled_auth: true
        enabled_startup: false
        enabled_running: true
    ….
    


    site.pp (файл с определением Puppet узлов)
    
    node "XXX.nlp-project.ru"  {
    	include ::limits
    
    	include ::sysctl::base
    	include ::logrotate
    	include storyline_base::ntp
    	include storyline_base::srv_oper
    	include storyline_base::ssh
    	include storyline_base::oracle_java
    …..
    	include storyline_infra::monit
    	include storyline_base::firewall
    }
    
    node "YYYY.nlp-project.ru"  {
    	include ::limits
    	include ::sysctl::base
    	include ::logrotate
    
    	include storyline_base::ntp
    	include storyline_base::srv_oper
    	include storyline_base::ssh
    	include storyline_base::oracle_java
    ….
    	include storyline_infra::zookeeper
    	include storyline_components::server_storm
    
    	include storyline_infra::monit
    	include storyline_base::firewall
    }
    

    Если кого-то заинтересует конкретная реализация какой-либо из задач: пишите – отпишу реализацию в комменте или добавлю в «Tips».

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

    Tips


    • При разработке модуля не забывать писать код не только для добавления функции, но и для её отключения. При отсутствии такого функционала при переносе компонента на другой сервер у вас их будет 2: на новом и на старом месте — на старом потребуется удалять руками, что противоречит основной задаче по автоматизации управления конфигурациями;
    • Хорошие книги по Puppet для начинающих – Learning Puppet и Puppet 4 Essentials;
    • Хороший модуль для получения артефактов из nexus sonatype (https://github.com/cescoffier/puppet-nexus);
    • Максимальное количество параметров выносите в файлы-данные Hiera для легкости конфигурации узлов и достижения универсальности кода самих модулей.

    Спасибо за внимание!
    Share post

    Comments 0

    Only users with full accounts can post comments. Log in, please.