Автоматизация системы мониторинга на базе Icinga2 и Puppet


Поговорим немного о… Infrastructure as code (IaC).


На Хабре есть несколько очень хороших статей про Icinga2, есть также отличные статьи про Puppet:

Icinga2 простой вариант
Поднимаем микромониторинг на icinga2 с минимальными затратами
Настройка современного Puppet сервера с нуля

Однако тема автоматизации и интеграции этих двух потрясающих систем совсем не раскрыта.
В данном руководстве, я покажу на «живом» примере, как можно, объединив эти две
системы, получить мощный инструмент мониторинга вашей инфраструктуры со всем набором необходимых функций. Статья является своего рода руководством к действию по установке пакета «все в одном флаконе». После выполнения этого руководства у вас в наличии будет полностью рабочее решение мониторинга, которое в дальнейшем можно будет «допиливать» под себя. Давайте попробуем!

Итак:


Мы подняли новый хост. И нам нужно:


1. Чтобы его мониторинг автоматически появился в Icinga2, и создались базовые проверки:


Проверки
Снимки
Пояснения
Host



С заданной периодичностью проверяем командой ping, что хост «живой».
Disk usage



Проверям, что у нас достаточно свободного места на дисках.
Load average



Мониторим нагрузку на сервере динамически. Учитывается количество процессоров на нём.
Free memory



Проверяем, что у нас достаточно свободной памяти на сервере.
Open ports



Сканируем порты на сервере и создаём карту открытых портов. Мониторим, что у нас не появились новые открытые или закрытые порты.
Critical updates



Мониторим наличие критических обновлений на сервере.


2. Добавлять кастомные проверки на различные сервисы в удобном и понятном виде. Что бы потом директору показать какие мы молодцы и премию получить!


Некоторые примеры:
Сервис
YAML-код проверки
Virtual host
Проверяем «живой» хост или нет.




    '%{::fqdn} virtual host' :
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        display_name: '%{::fqdn} virtualhost'
        check_command: 'http'
        vars:
            http_uri: /
            http_ssl: true
            http_vhost: 'hostname'
            http_address: "%{lookup('host_address')}"
    

PostgreSQL
Проверяем, что мы можем соединиться с БД PostgreSQL.




    '%{::fqdn} PostgreSQL':
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        display_name: 'PostgreSQL'
        command_endpoint: '%{::fqdn}'
        check_command: "postgres"
        vars:
            postgres_host: "localhost"
            postgres_action: "connection"
            phone_notifications: true
    

Nginx Status
Мониторим статус Nginx через stub_status.




    '%{::fqdn} nginx status' :
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        command_endpoint: '%{::fqdn}'
        display_name: 'nginx status'
        check_command: 'nginx_status'
        vars:
            nginx_status_host_address: localhost
            nginx_status_servername: server.com
            nginx_status_critical: '1600,60,30'
            nginx_status_warn: '1500,55,25'
    


3. Чтобы всё было аккуратно, надёжно и красиво. И главное, потратить не более 30 минут на возню с первоначальной настройкой.


У вас должен быть опыт работы с Docker'ом, а значит и с Linux — само собой.

Данный сетап описан под Debian/Ubuntu. И, хотя я не вижу причин ему не работать на других Unix-подобных системах, сам я таких гарантий дать не могу. У меня есть пара машин с CentOS, там это работает, но большинство, всё же — это Debian/Ubuntu.

Начнём


Скажу сразу, мне удобно, когда вся конфигурация хоста — сервисы, конфиги, софт, аккаунты и т.п. — описываются одним yaml-файлом, это фактически позволяет избежать документирования инфраструктуры и даёт наглядность конфигурации. Открыл соответствующий проект в git-репозитории, где имена файлов соответствуют имени хоста, затем открыл конфиг нужного хоста. И сразу видно, какие сервисы есть на хосте, что из этого бэкапится, что мониторится и т.д.

Вот так выглядит структура проекта в репозитории:

project_1/hostname1.com.yaml
project_2/hostname2.com.yaml
project_3/hostname3.com.yaml

У себя я использую вот такой шаблон, в котором описывается конфигурация любого из наших серверов:

Полный шаблон

#============================|INPUT DATA|=================================#
#---------------------------------------|VARS|----------------------------#
#---//Information about variables, keys & contacts//----------------------#

host_address: x.x.x.x
my_company: 
my_mail_domain: 
my_ssh_port: 



#---------------------------------------|CLASSES|-------------------------#
#---//Classes are modules installed on the server.//----------------------#
#---//These modules process the arguments typed below.//------------------#
#---//Without classes nothing will work.//--------------------------------#
#---//Class default_role is mandatory. This class will install//----------#
#---//etckeeper, some required perl modules and manages all the logics.//-#

classes:
  - default_role

#---------------------------------------|TIMEZONE|------------------------#
#---//Set timezone, which will be used on the host.//---------------------#

timezone::timezone: Europe/Moscow

#---------------------------------------|FACTS|---------------------------#
#---//Facts are variables which puppet agent uses.//----------------------#

facter::facts_hash:
 role:
   value: 'name'
 company:
   value: 'name of company'
   file: 'location.txt'
#=========================================================================#

#============================|PUPPET|=====================================#
#---//Settings for puppet agent.//----------------------------------------#

puppet::runmode: cron
puppet::ca_server: "%{lookup('puppet_ca')}"

puppet::puppetmaster: "%{lookup('puppet_master')}"
#=========================================================================#
 
#============================|CRON TASKS|=================================#

cron_tasks:
    Name:
        command: ""
        user: 
        minute: ''
        hour: ''   

#=========================================================================#

#============================|SUPERVISOR|=================================#
#---//Supervisor is a process manager//-----------------------------------#

supervisord::install_pip: false
supervisord::install_init: false
supervisord::service_name: supervisor
supervisord::package_provider: apt
supervisord::executable: /usr/bin/supervisord
supervisord::executable_ctl: /usr/bin/supervisorctl
supervisord::config_file: /etc/supervisor/supervisord.conf
supervisord::programs:
  'name':
    ensure: present
    command: 'su - rails -c "/home/name/s2"'
    autostart: no
    autorestart: 'false'
    directory: /home/name/domainName/current

#=========================================================================#
 
#============================|SECURITY|===================================#
#-------------------------------------|FIREWALL|--------------------------#
#---//Iptables rules//----------------------------------------------------#

firewall:
    096 Allow inbound SSH:
      dport: "%{lookup('my_ssh_port')}"
      proto: tcp
      action: accept

#-------------------------------------|FAIL2BAN|--------------------------#


#-------------------------------------|ACCESS|----------------------------#
#--------------------------------------------|ACCOUNTS|-------------------#
#---//Discription of accounts which will be created on server.//----------#

accounts:
    user:
        shell: '/bin/bash'
        password: ''
        locked: false
        purge_sshkeys: true
         groups:
            - docker
        sshkeys:
            - "%{alias('admins_ssh_keys')}"
   
#--------------------------------------------|SUDO|-----------------------#
#---//Appointment permissions for users.//--------------------------------#

sudo::config_file_replace: false
sudo::configs:
    user:
        content: "user ALL=(ALL) NOPASSWD: ALL"
    
#--------------------------------------------|SSH|------------------------#
#--------//Settings for ssh server.//-------------------------------------#

ssh::storeconfigs_enabled: true
ssh::server_options:
    Protocol: '2'
    Port: "%{lookup('my_ssh_port')}"
    PasswordAuthentication: 'yes'
    PermitRootLogin: 'without-password'
    SyslogFacility: 'AUTHPRIV'
    UsePAM: 'yes'
    X11Forwarding: 'no'

#--------------------------------------------|VPN|------------------------#
#---//Settings for vpn server.//------------------------------------------#

openvpn::servers:
  'namehost':
    country: ''
    province: ''
    city: ''
    organization: ''
    email: ''
    server: 'x.x.x.x 255.255.255.0'
    dev: tun
    local: "%{lookup('host_address')}"

openvpn::client_defaults:
  server: 'namehost'

openvpn::clients:
  # Firstname Lastname
  'user': {}

openvpn::client_specific_configs:
  'user':
    server: 'namehost'
    redirect_gateway: true
    route:
    - x.x.x.x 255.255.255.255

#=========================================================================#
 
#============================|OPERATING SYSTEM|===========================#
#---------------------------------------------|SYSCTL|--------------------#
#---//set sysctl parameters//---------------------------------------------#

sysctl::base::values:
  fs.file-max:
    value: '2097152000'
  net.netfilter.nf_conntrack_max:
    value: '1048576'
  net.nf_conntrack_max:
    value: '1048576'
  net.ipv6.conf.all.disable_ipv6:
    value: '1'
  vm.oom_kill_allocating_task:
    value: '1'    
  net.ipv4.ip_forward:
    value: '0'
  net.ipv4.tcp_keepalive_time:
     value: '3'
  net.ipv4.tcp_keepalive_intvl:
    value: '60'
  net.ipv4.tcp_keepalive_probes:
    value: '9'
    
#---------------------------------------------|RCS|-----------------------#
#---//Managment of RC scenario//------------------------------------------#

rcs::tmptime: '-1'

#---------------------------------------------|WEB SERVERS|---------------#
#---------------------------------------------------------|HAPROXY|-------#
#---//HAProxy is software that provides a high availability load//--------#
#---//balancer and proxy server for TCP and HTTP-based applications//-----# 
#---// that spreads requests across multiple servers.//-------------------#

haproxy::merge_options: true
haproxy::defaults_options:
    log: global
    maxconn: 20000
    option: [
        'tcplog',
        'redispatch',
        'dontlognull'
    ]
    retries: 3
    stats: enable
    timeout: [
        'http-request 10s',
        'queue 1m',
        'check 10s',
        'connect 300000000ms',
        'client 300000000ms',
        'server 300000000ms'
    ]

haproxy_server:
  stats:
    ipaddress: "%{lookup('host_address')}"
    ports: '9090'
    options:
      mode: 'http'
      stats: [ 'uri /', 'auth puppet:123123123' ] 
  postgres:
    collect_exported: false
    ipaddress: '0.0.0.0'
    ports: '5432' 
    options:
        option:
            - tcplog
        balance: roundrobin
haproxy_balancemember:
  hostname1:
    listening_service: postgres
    server_names: hostname1
    ipaddresses: "%{lookup('host1_ip')}"
    ports: 6432
    options: check
  hostname2:
    listening_service: postgres
    server_names: hostname2
    ipaddresses: "%{lookup('host2_ip')}"
    ports: 6432
    options:
        - check
        - backup
        
#---------------------------------------------|NGINX|---------------------#
#---//Nginx is a web server which can also be used as a reverse proxy,//--#
#---//load balancer, mail proxy and HTTP cache//--------------------------# 

nginx::nginx_cfg_prepend:
  'load_module':
      - modules/ngx_http_geoip_module.so
nginx::http_raw_append:
  - 'real_ip_header X-Forwarded-For;'
  - 'geoip_country /usr/share/GeoIP/GeoIP.dat;'
  - 'set_real_ip_from 0.0.0.0/0;'
nginx::worker_rlimit_nofile: 16384
nginx::confd: true
nginx::server_purrge: true
nginx::server::maintenance: true  

#---------------------------------------------nginx-|MAPS|----------------#

nginx::string_mappings:
  allowed_country:
    ensure: present
    string: '$geoip_country_code'
    mappings:
      - key: 'default'
        value: 'yes'
      - key: 'US'
        value: 'no'

#---------------------------------------------nginx-|UPSTREAMS|-----------#
nginx::nginx_upstreams:
  "upstreamName":
    ensure: present
    members:
      - "localhost:9999"
#---------------------------------------------nginx-|VHOSTS|--------------#
nginx::nginx_servers:

  'hostname':
    proxy: 'http://'
    location_raw_append:
        - 'if ($allowed_country = no) {return 403;}'
    try_files:
    - ''
    - /index.html
    - =404
    ssl: true
    ssl_cert: "/etc/letsencrypt/live/hostname/fullchain.pem"
    ssl_key: "/etc/letsencrypt/live/hostname/privkey.pem"
    ssl_trusted_cert: "/etc/letsencrypt/live/hostname/chain.pem"
    ssl_redirect: false
    ssl_port: 443
    error_pages:
      '403': /usa-restrict.html

#---------------------------------------------nginx-|HTTPAUTH|------------#

httpauth:
  'admin':
    file: "/etc/nginx/htaccess"
    password: ''
    realm: realm
    mechanism: basic
    ensure: present

#---------------------------------------------nginx-|LOCATIONS|-----------#

nginx::nginx_locations:
  'domain1.com/usa-restricted':
    location: /usa-restrict.html
    www_root: /home/clientName1/clientName1-client-release/current/dist
    server: domain1.com
    ssl: true
  '^~ domain2.com/resources/upload/':
    location: '^~ /resources/upload/'
    server: domain2.com
    location_alias: '/home/clientName2/upload/'
    raw_append:
        - 'if ($allowed_country = no) {return 403;}'
  '/nginx_status-domain3.com':
    location: /nginx_status
    stub_status: on
    raw_append:
        - access_log off;
        - allow 127.0.0.1;
        - deny all;      

#---------------------------------------------nginx-|WELL-KNOWN|----------#
#---//These locations are needed for SSL certificates generating//--------#
#---//with Letsencrypt.//-------------------------------------------------#

  'x.hostname.zz/.well-known':
    location: '/.well-known'
    server: x.hostname.zz
    proxy: 'http://kibana' 
    auth_basic: "ciao"
    auth_basic_user_file: "/etc/nginx/htaccess
    www_root: /var/www/html
    ssl: true
  'hostname.zz/~* \.(?:ico|css|js|html|map|gif|jpg|png|svg|ttf|woff|appcache|pdf)$':
    location: '~* \.(?:ico|css|js|html|map|gif|jpg|png|svg|ttf|woff|appcache|pdf)$'
    www_root: '/home/hostname/hostname-client-release/current/dist'
    expires: max
    raw_append:
    - "add_header Pragma public;"
    - 'add_header Cache-Control "public, must-revalidate, proxy-revalidate";'    

#---------------------------------------------------|NGINS STATUS|--------#

#----------------------------|LETSENCRYPT|--------------------------------#
#---//Let's Encrypt is a certificate authority that provides
#---//free X.509 certificates for Transport Layer Security (TLS)//--------#
#---//encryption via an automated process designed to eliminate//---------#
#---//the hitherto complex process of manual creation, validation,//------#
#---//signing, installation, and renewal of certificates//----------------#
#---//for secure websites.//----------------------------------------------#

letsencrypt::email: client@mail.com
letsencrypt_certonly:
    hostname:
        manage_cron: true
        domains:
            - hostname
        plugin: webroot
        webroot_paths:
            - '/var/www/html'

#----------------------------|FILE SERVERS|-------------------------------#
#-----------------------------------------|FILES|-------------------------#
#---//Creating files & folders on the server.//---------------------------#

file:
    "/home/hostname/hostname-release/shared/ecosystem.config.js":
        ensure: present
        owner: hostname
        content: |
            module.exports = {
              /**
               * Application configuration section
               * http://pm2.keymetrics.io/docs/usage/application-declaration/
               */
              apps : [
                // First application
                {
                  name      : 'api',
                  script    : '/home/hostname/hostname-release/current/dist/index.js',
                  cwd       : '/home/hostname/hostname-release/current/dist/',
                  watch     : false,
                  ignore_watch : ["logs"],
                  "log_type": "format",
                  env: {
                    COMMON_VARIABLE: 'true'
                  },
                  env_production : {
                    NODE_ENV: 'production',
                    PORT: 9999,
                    DEBUG   : '*'
                  },
                  env_development : {
                    NODE_ENV: 'development',
                    PORT: 9999,
                    DEBUG   : '*'
                  }
                },
                // Second application
                {
                  name      : 'WEB',
                  script    : 'web.js'
                }
              ],
              /**
               * Deployment section
               * http://pm2.keymetrics.io/docs/usage/deployment/
               */
              deploy : {
                production : {
                  user : 'node',
                  host : '0.0.0.0',
                  ref  : 'origin/master',
                  repo : 'name@name.com:repo.git',
                  path : '/var/www/production',
                  'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production'
                },
                dev : {
                  user : 'node',
                  host : '0.0.0.0',
                  ref  : 'origin/master',
                  repo : 'name@name.com:repo.git',
                  path : '/var/www/development',
                  'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env dev',
                  env  : {
                    NODE_ENV: 'dev'
                  }
                }
              }
            };

#-----------------------------------------|FTP|----------------------------#
#---//Very Secure FTP Daemon is an FTP server for Unix-like systems.//-----#

vsftpd::ftpd_banner: 'ASCII FTP Server'
vsftpd::anonymous_enable: 'NO'
vsftpd::write_enable: 'YES'
vsftpd::chroot_local_user: 'YES'
vsftpd::allow_writeable_chroot: 'YES'
vsftpd::userlist_enable: 'YES'
vsftpd::userlist_deny: 'NO'

#-----------------------------------------|NFS|---------------------------#
#---//Network File System is a distributed file system protocol,//--------#
#---//allowing a user on a client computer to access files over//---------# 
#---//a computer network much like local storage is accessed.//-----------#

nfs::server_enabled: true
nfs::client_enabled :  false
nfs::nfs_v4:  true
nfs::nfs_v4_idmap_domain:  "%{::domain}"
nfs::nfs_v4_export_root:  '/share'
nfs::nfs_v4_export_root_clients:  "%{lookup('host_address')}/32(rw,fsid=root,insecure,no_subtree_check,async,no_root_squash)"
nfs::nfs_exports_global:
  /var/www: {}
  /var/smb: {}

#----------------------------|PACKAGES|-----------------------------------#
#---//Install of packages to server.//------------------------------------#

packages:
    namePackage:
        ensure: installed

#-------------------------------------|APT|-------------------------------#
#---//Management of repository of packages.//---#

apt::sources:
  'debian_unstable':
    comment: 'This is the iWeb Debian unstable mirror'
    location: 'http://debian.mirror.iweb.ca/debian/'
    release: 'unstable'
    repos: 'main contrib non-free'
    pin: '-10'
    key:
      id: 'IDIDIDIDIDIDIDIDIDIDIDIDIDIDIDID'
      server: 'subkeys.pgp.net'
    include:
      src: true
      deb: true
  'puppetlabs':
    location: 'http://apt.puppetlabs.com'
    repos: 'main'
    key:
      id: 'IDIDIDIDIDIDIDIDIDIDIDIDIDIDIDID'
      server: 'pgp.mit.edu'


#-------------------------------------|PHP|-------------------------------#

php_pool:
  myadmin:
   listen: 'x.x.x.x:x'

#-------------------------------------|RVM|-------------------------------#
#---//Ruby Version Manager  is a unix-like software platform designed//---#
#---//to manage multiple installations of Ruby on the same device.//------#

rvm_ruby:
  user1_rvm:
    user: user1
    version: ruby-2.5.1
white_label_platform: default

#-------------------------------------|PYTHON|----------------------------#

python::version: system
python::dev: present
python::virtualenv: true

#----------------------------|NODEJS|-------------------------------------#
#-----------------------------------|NVM|---------------------------------#
#---//Node Version Manager for Node.js//----------------------------------#
#---//Node.js lets developers use JavaScript to write Command Line//------#
#---//tools and for server-side scripting—running scripts server-side//---#
#---//to produce dynamic web page content before the page is sent//-------#
#---//to the user's web browser.//----------------------------------------#

nodejs::repo_url_suffix: '8.x'
nvm::user: name
nvm::install_node: 8.10.0

#----------------------------|DOCKER|-------------------------------------#
#---//Docker's management//-----------------------------------------------# 

docker::run_instance::instance:
  nats:
    image: 'nats'
    extra_parameters: '-p 8222:8222 -p 4222:4222 -p 6222:6222 --name nats --network admin_default'
docker::compose::ensure: present
docker_compose:
    /etc/admin/gitlab/docker-compose.yaml:
        ensure: present
docker::iptables: false
docker::tcp_bind: tcp://0.0.0.0:2375  
docker::compose::ensure: present
docker_swarm:
    'agent':
        ensure: present
        join: true
        advertise_addr: '%{::ipaddress_enp2s0}'
        listen_addr: '%{::ipaddress_enp2s0}'
        manager_ip: '%{lookup("host_address")}'
        token: ''  
docker_registry:
    'host.com:5000': 
      username: ''
      password: ''
      email: 'mail@mail.com'

#=========================================================================#
 
#============================|BACKUPS UPDATES|============================#
#--------------------------------------------|UPDATES|--------------------#
#---//Unattended upgrades is the linux-like mechanism//-------------------#
#---//automatic updates.//------------------------------------------------#

unattended_upgrades::origins:
    - "o=Debian,n=${distro_codename}"
    - "o=Debian,n=${distro_codename}-security"
    - "o=Debian,n=${distro_codename}-updates"
    - "o=Debian,n=${distro_codename}-proposed"
    - "o=Debian,n=${distro_codename}-backports"
    - "o=debian icinga,n=icinga-${distro_codename}"
    - "o=Zabbix,n=${distro_codename}"

#--------------------------------------------|BACKUPS|--------------------#
#---//The backups are processed by gembackup.//---------------------------#

backup_jobs:
    #Creates full backup
    type_files:
        types: ['archive']
        add:
            - '/path'
        storage_type: 'ftp'
        storage_host: "%{lookup('my_ftp_hostname')}"
        path: '/files/'
        storage_username: "%{lookup('my_backup_ftp_username')}"
        storage_password: "%{lookup('my_backup_ftp_password')}"
        compressor: "%{lookup('my_backup_compressor')}"
        keep: 7
        weekday: [1-7]
        hour: 0
        minute: 0

#--------------------------------------------|MOUNT|----------------------#
#---//Сontrol the mounting of remote & local mount points.//--------------#

mount:
    #Mount folder from other server to keep backups
    "/tmp/mediastagetv_netbynet":
        device: "%{lookup('host_address')}:/"
        ensure: mounted
        fstype: nfs4
        options: defaults
        atboot: false

#--------------------------------------------|S3 AMAZON|------------------#
#---//Simple Storage Service is a cloud computing web service.//----------#

hostname1-archives: '/home/hostname1/hostname1/shared/log_amazon'
hostname2-archives: '/home/hostname2/hostname2/shared/log_amazon'
yas3fs::mounts:


#=========================================================================#
 
#============================|MONITORING|=================================#
#---------------------------------------|ICINGA SERVICES|-----------------#
#---//Managment of monitoring system.//-----------------------------------#

icinga2_service:    
    '%{::fqdn} virtual host' :
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        display_name: '%{::fqdn} virtualhost'
        check_command: 'http'
        vars:
            http_uri: /
            http_ssl: true
            http_vhost: 'hostname'
            http_address: "%{lookup('host_address')}"
    
    '%{::fqdn} nginx status' :
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        command_endpoint: '%{::fqdn}'
        display_name: 'nginx status'
        check_command: 'nginx_status'
        vars:
            nginx_status_host_address: localhost
            nginx_status_servername: server.com
            nginx_status_critical: '1600,60,30'
            nginx_status_warn: '1500,55,25'
    
    '%{::fqdn} redis':
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        display_name: 'Redis'
        command_endpoint: '%{::fqdn}'
        check_command: "redis"
        vars:
            redis_hostname: localhost
            redis_port: 6379
            redis_perfvars: '*'

#=========================================================================#
 
#============================|MAIL & LOGS|================================#
#----------------------------------------|MAIL|---------------------------#
#---------------------------------------------|POSTFIX|-------------------#
#---//Management of mail-server Postfix.//--------------------------------#

postfix::manage_conffiles: false
postfix_config:
    relayhost:
        ensure: present
        value: "%{lookup('host_address')}"
    virtual_maps:
        value: hash:/etc/postfix/virtual
        ensure: present
    sender_canonical_map:
        value: hash:/etc/postfix/canonical_sender
        ensure: present
postfix_hash:
    '/etc/postfix/virtual':
        ensure: present
        content: |
          root %{lookup('hostname_admin_emails')}
          rails %{lookup('hostname_emails')}
    '/etc/postfix/canonical_sender':
        ensure: present
        content: |
            name@%{lookup('my_domain')} name@my_domain.com
            root@%{lookup('my_domain')} root@my_domain.com

#----------------------------------------|LOGS|---------------------------#
#---------------------------------------------|RSYSLOG|-------------------#
#---//Rsyslog is a software utility for forwarding log messages//---------#
#---//in an IP network. It implements the basic syslog protocol,//--------#
#---//extends it with content-based filtering, rich filtering//-----------#
#---//capabilities, flexible configuration options and adds features//----#
#---//such as using TCP for transport.//----------------------------------#

rsyslog::client:
  log_local: true

my_rsyslog_snippet:
  99_everything:
    content: "*.*;auth,authpriv.none /var/log/syslog\n"
  01_mail:
    content: "mail.*  -/var/log/mail.log\n& stop"
  02_auth:
    content: "auth,authpriv.* /var/log/auth\n& stop"
  03_puppetagent:
    content: ":programname,contains,\"puppet-agent\" /var/log/puppetlabs/puppet/puppet-agent.log\n& stop"
  04_iptables:
    content: ":msg,contains,\"IPTABLES INPUT\" /var/log/iptables/iptables.log\n& stop"
  05_pam_unix:
    content: ":msg,regex,\".*session opened for.*(uid=0)\" /var/log/admin/auth.log\n& stop"
  06_sshd:
    content: ":msg,regex,\".*publickey for username.*0.0.0.0\" /var/log/admin/auth.log\n& stop

#---------------------------------------------|LOGWATCH|------------------#
#---//Logwatch is a log-analysator for create short reports.//------------#

logwatch::format: text
logwatch::service:
    # Ignore this servie
    - -http
    - -iptables

#---------------------------------------------|LOGROTATE|-----------------#
#---//Management of rotation of log files.//------------------------------#

my_rclogs_path: '/home/hostname/hostname/shared/log'
my_rclogs_amazon_path: '/home/hostname1/hostname1/shared/log_amazon'
my_ttlogs_path: '/home/hostname2/hostname2/shared/log'
my_ttlogs_amazon_path: '/home/hostname/hostname/shared/log_amazon'
logrotate::ensure: latest
logrotate::config:
  dateext: true
  compress: true
logrotate::rules:
  booking-logs:
    path: '%{lookup("my_rclogs_path")}/booking_com.log'
    size: 2500M
    rotate: 20
    copytruncate: true
    delaycompress: true
    dateext: true
    dateformat: -%Y%m%d-%s
    compress: true
    postrotate: mv %{lookup('my_rclogs_path')}/booking_com.log*.gz %{lookup('my_rclogs_amazon_path')}/

#---------------------------------------------|ATOP|----------------------#
#---//ATOP service displays a new information about CPU//-----------------#
#---//and memory utilization.//-------------------------------------------#

atop::service: true
atop::interval: 30

#----------------------------------------|DNS|----------------------------#
#---//Management of file /etc/resolv.conf//-------------------------------#

resolv_conf::nameservers:
    - 0.0.0.0
    - 0.0.0.0

#=========================================================================#
 
#============================|DATABASES|==================================#
#--------------------------------------|MYSQL|----------------------------#
#---//MySQL is a relational database management system.//-----------------#

mysql::server::package_ensure: 'installed' #·
mysql::server::root_password: "ooy5ieneePahnei"
mysql::server::manage_config_file: true
mysql::server::service_name: 'mysql' # required for puppet module
mysql::server::override_options:
  'mysqld':
    'bind-address': '*'

        "userName": "",
        "password": "",
        "databaseName": "",

mysql::server::db:
  "hostname":
    user: ""
    password: ""
    host: "%"
    grant:
      - "ALL"

#-------------------------------------|ELASTICSEARCH|---------------------#
#---//Elasticsearch is a search engine based on Lucene.//-----------------#
#---//It provides a distributed, multitenant-capable full-text search//---#
#---//engine with an HTTP web interface and schema-free JSON documents.//-#

elasticsearch::version: 5.5.1
elasticsearch::manage_repo: true
elasticsearch::repo_version: 5.x
elasticsearch::java_install: false
elasticsearch::restart_on_change: true
elasticsearch_instance:
    'es-01':
      ensure:  'present'

#-------------------------------------|REDIS|-----------------------------#
#---//Redis is an in-memory database project implementing//---------------#
#---//a distributed, in-memory key-value store with//---------------------#
#---//optional durability.//----------------------------------------------#

redis::bind: 0.0.0.0

#-------------------------------------|ZOOKEPER|--------------------------#
#---//Zooker is a centralized service for distributed systems//-----------#
#---//to a hierarchical key-value store, which is used to provide//-------#
#---//a distributed configuration service, synchronization service,//-----#
#---//and naming registry for large distributed systems.//----------------#

zookeeper::init_limit: '1000'
zookeeper::id: '1'
zookeeper::purge_interval: '1'
zookeeper::servers:
  - "%{lookup('host')}"

#-------------------------------------|POSTGRES|--------------------------#
#---//Postgres, is an object-relational database management system//------#
#---//with an emphasis on extensibility and standards compliance.//-------#
#---//As a database server, its primary functions are to store  data//----#
#---//securely and return that data in response to requests from other//--#
#---//software applications.//--------------------------------------------#

postgresql::server::postgres_password: 
postgresql::server::ip_mask_allow_all_users: '0.0.0.0/0'

postgresql::postgresql::server:
  ip_mask_allow_all_users: '0.0.0.0/32'

postgres_db:
  master:
    user: 
    password: 
  confluence:
    user: 
    password: 
postgres_config:
    'max_connections':
       value: 300

postgres_hba:
    'Allow locals without password':
        order: 1
        description: 'locals postgres no password'
        type: 'host'
        address: '127.0.0.1/32'
        database: 'all'
        user: 'all'
        auth_method: 'trust'  

#----------------------------------------------|PGBOUNCER|----------------#
#---//PgBouncer is a connection pooler for PostgreSQL//-------------------#

pgbouncer::group: postgres
pgbouncer::user: postgres
pgbouncer::userlist:
   - user:  
     password: 

pgbouncer::databases:
   - source_db: recommender
     host: "%{lookup('')}"
     dest_db: recommender
     auth_user: recommender
     pool_size: 200
     auth_pass: 
   - source_db: master
     host: "%{lookup('')}"
     dest_db: recommender
     auth_user: recommender
     pool_size: 50
     auth_pass: 
   - source_db: slave
     host: "%{lookup('')}"
     dest_db: recommender
     auth_user: recommender
     pool_size: 200
     auth_pass: 

#=========================================================================#

#==============================|APPLICATION SERVICES|=====================#
#---------------------------------------------------|CONFLUENCE|----------#
#---------------------------------------------------|JENKINS|-------------#

#=========================================================================#





Пример секции мониторинга:


#============================|MONITORING|=================================#
#---------------------------------------|ICINGA SERVICES|-----------------#
#---//Managment of monitoring system.//-----------------------------------#
  
icinga2_service:   
    '%{::fqdn} virtual host' :
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        display_name: '%{::fqdn} virtualhost'
        check_command: 'http'
        vars:
            http_uri: /
            http_ssl: true
            http_vhost: 'hostname'
            http_address: "%{lookup('host_address')}"
      
    '%{::fqdn} nginx status' :
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        command_endpoint: '%{::fqdn}'
        display_name: 'nginx status'
        check_command: 'nginx_status'
        vars:
            nginx_status_host_address: localhost
            nginx_status_servername: server.com
            nginx_status_critical: '1600,60,30'
            nginx_status_warn: '1500,55,25'
    
    '%{::fqdn} redis':
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        display_name: 'Redis'
        command_endpoint: '%{::fqdn}'
        check_command: "redis"
        vars:
            redis_hostname: localhost
            redis_port: 6379
            redis_perfvars: '*'
  
#=========================================================================#



Общая схема работы такой схемы заключается всего лишь в двух простых действиях:


  1. Запускаем puppet на хосте — хост видит проверки, которые ему принадлежат и экспортирует их в puppetDB.

  2. Запускаем puppet в контейнере icinga2 — проверки из puppetdb превращаются в реальные конфиги Icinga2.


Тут многие скажут: «А зачем мне всё это городить, если я могу поднять обычную icinga2 и добавлять проверки руками или через веб-интерфейс?»

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

Обозначим основные плюсы:


Посмотрим на вот такой шаблон:

icinga2_service:
    '%{::fqdn} disk service':
        target: /etc/icinga2/zones.d/master/%{::fqdn}.conf
        apply: true
        assign: [ 'host.name == %{::fqdn}' ]
        display_name: 'Disk usage'
        command_endpoint: '%{::fqdn}'
        check_command: 'disk'
        vars:
          #All disks
          disk_all: true
          disk_exclude_type:
            - aufs
            - tmpfs
          disk_ignore_ereg_path:
            - /run/docker/*
            - /sys/*
            - /var/lib/docker*
            - /var/lib/ureadahead/debugfs/*
            - /run/user/*

+ 1. Этот шаблон встречается в Hiera только один раз и применяется ко всем хостам в таком виде — т.е. он универсальный. Конфиги на сервере Icinga2, для данного сервиса, будут созданы автоматически для каждого хоста в нашей системе. К тому же, произойдёт создание самих конфигов хостов, ключей, зон и прочей радости.

+ 2. Нам не нужно помнить, добавили мы проверки или нет. Если паппет на хосте был запущен — стандартный набор проверок для этого хоста сгененрирован в нашей системе мониторинга.

+ 3. Мы не переживаем о бекапах нашей системы мониторинга, т.к. все проверки у нас генерируются паппет сервером, и даже при полной потере всех конфигов на сервере Icinga2 их можно будет легко восстановить, запустив puppet на сервере Icinga2.

+ 4. Так так все проверки у нас храняться в единой базе puppetDB мы можем создавать довольно мощные сценарии для дальнейшей автоматизации, в которых будем эту информацию использовать.


Итак, поехали


1. Настроим puppet.


Надеюсь у вас есть настроенный docker и docker-compose.
Если нет, то их необходимо установить:
Установка Docker...
Установка Docker-compose...

2. Склонируем репозиторий к себе на сервер:


git clone http://git.comgress64.com/external/puppet-icinga2-how-to.git

3. Откроем docker-compose.yaml любимым редактором и посмотрим на него.


Мы видим, что в данной пачке у нас поднимается сразу несколько контейнеров — PuppetServer, PuppetDb, PostgreSQL сервер и PuppetBoard. Так же монтируются volumes из текущей директории. Не для всех эта конфигурация является оптимальной, поэтому учитывайте свою инфраструктуру. У кого-то уже есть PostgreSQL сервер, кто-то хочет хранить код на другом разделе — тут свобода для творчества. На данном этапе я предлагаю оставить шаблон по умолчанию — к нему всегда можно будет вернуться позже. Поднимем нашу пачку контейнеров и посмотрим, что у нас вышло:

4. Запустим контейнер Puppet, Postgres, Puppetdb и Graphite


#Запустим контейнер Puppet, Postgres и Puppetdb
#Запустим контейнер Puppet, Postgres, Puppetdb и Graphite
cd puppet-icinga2-how-to
docker-compose up -d puppet puppetdb-postgres puppetdb graphite  && docker-compose logs -f

Сейчас у нас загрузилось несколько образов и запустились контейнеры, создались базы данных в PostgreSQL. Дождемся, пока все серверы будут запущены — ошибки можно игнорировать, т.к они через какое-то время должны стабилизироваться. Жмём Ctrl+C, чтобы выйти из режима просмотра логов.

5. Проверим работу нашего puppet master:



docker run --net puppeticinga2howto_default --link puppet:puppet puppet/puppet-agent

6. Если все ок, вы увидите вот такой вывод:



    Notice: Applied catalog in 0.03 seconds
Changes:
            Total: 1
Events:
          Success: 1
            Total: 1
Resources:
          Changed: 1
      Out of sync: 1
            Total: 8
Time:
         Schedule: 0.00
             File: 0.00
   Transaction evaluation: 0.01
   Catalog application: 0.03
   Convert catalog: 0.04
   Config retrieval: 0.45
   Node retrieval: 1.38
         Last run: 1532605377
   Fact generation: 2.24
      Plugin sync: 4.50
       Filebucket: 0.00
            Total: 8.65
Version:
           Config: 1532605376
           Puppet: 5.5.1
    

Настроим cервер Icinga2.

7. Сделаем образ из Dockerfile, выполнив:



docker-compose build icinga

8. Запустим контейнер, в котором у нас будет жить сервер Icinga2.



docker-compose up -d icinga

9. Пока что у нас контейнер пустой. Давайте настроим icinga2 сервер при помощи нашего Puppet сервера, выполнив:



#Сгенерируем модули для puppet запустив librarian-puppet в контейнере puppet
docker-compose exec puppet bash -c 'cd /etc/puppetlabs/code && gem install librarian-puppet && librarian-puppet install --verbose'
#Запускаем Puppet в контейнере Icinga для первичной конфигурации контейнера
docker-compose exec icinga puppet agent --server puppet --waitforcert 60 --test

10. Установится и сконфигурируется Icinga, Icingaweb2 и Apache.


Ваш сервер Icinga2 готов!


Icingaweb2


1. Давайте посмотрим, что у нас получилось.


Удобно смотреть на состояние системы мониторинга в браузере, выполним:
Откроем в браузере http://ip_адресс_вашего_хоста:8081/icingaweb2 и зайдём в Icinga c дефолтным логином: icingaadmin/icinga.

Видим вот такую картинку:



Через несколько минут мы видим, что у нас все проверки отработали:



Графики тоже работают:



У нас есть рабочая веб-среда для Icinga2, но пока ещё нет ни одного хоста подключённого к Icinga, давайте подключим наш первый хост.

2. Подключим новый хост в систему мониторинга


Сейчас у нас к icinga2 подключен только один хост — сама Icinga. Давайте подключим ещё один.
Показываю на примере в докере, но всё тоже самое будет работать и на голом железе:

Для начала нужно подготовить шаблон и дать некоторую информацию о хосте для puppet:


#Переходим в директорию шаблонов
cd puppet-icinga2-how-to/code/environments/production/hieradata/nodes
#Копируем шаблон с новым именем(должен называться именем хоста)
cp template.yaml example.com.yaml
#Открываем новый фаил любимым редактором и меняем переменные:
host_address: 172.18.0.7
my_company: COMGRESS64
my_package_manager: apt
my_ssh_port: 22
#Сохраняем фаил и выходим

Запустим образ докера. Тут самое главное поставить ему hostname такой же как у фаила шаблона, который мы только что создали:


docker run --hostname example.com --rm -t --link puppet:puppet --net puppeticinga2howto_default -i phusion/baseimage:latest /sbin/my_init -- bash -l

Установим puppet на нашем хосте:


apt-get update && apt-get install -y ruby make gcc perl-modules && gem install --no-ri --no-rdoc puppet

Запускаем puppet:


puppet agent --server puppet --waitforcert 60 --test

Сейчас у нас установилась icinga2 на наш новый хост и все проверки для неё выгрузились в базу puppetDB. Давайте их сгенерируем, выполнив puppet в контейнере icinga2:


docker-compose exec icinga puppet agent --server puppet --waitforcert 60 --test

Смотрим через браузер, что у нас получилось:



Проверки добавились и ожидают выполнения.
Через 5 минут:



Таким же образом добавляются все остальные хосты в систему. Хочу обратить ваше внимание, что данное руководство является доказательством концепта и не может быть использовано в среде production без доработок.

Если статья покажется интересной, в следующий раз я покажу, как можно добавить всякие интересные кастомные проверки, поделюсь секретами и расскажу про всякие тонкости. Также, если возникнет желание, расскажу более подробно о том как это всё работает изнутри.

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



Ссылка на эту же статью в Confluence:
/Чуть больше форматирования и наглядности./
docs.comgress64.com/...

Ссылки на материал:
https://github.com/Icinga/puppet-icinga2
https://forge.puppet.com/icinga/icingaweb2
https://docs.docker.com/
https://docs.puppet.com/
https://docs.puppet.com/hiera/

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


  1. Dimonyga
    14.08.2018 22:21

    Предупреждайте людей что у вас модуль под puppet3, и использует depricated функции. (Хотя в статье у вас паппет 5, в логи сервера не заглядывали ?)
    hiera вы используете «божественно». Вдумайтесь в само слово, hiera, hiera, hiera…
    Объясняю: Шаблон под каждую ноду писать не нужно, нужно создать уровень объединяющий хосты. Или вообще вынести в common.


    1. comgress64 Автор
      15.08.2018 10:59

      Спасибо, за комментарий. Сам модуль icinga2 совместим с Puppet >= 3.8.0 < 6.0.0. В логи конечно же смотрим — всё чисто.
      Hiera мы используем по-разному, в том числе и так как вы описали, есть common (http://git.comgress64.com/external/puppet-icinga2-how-to/blob/master/code/environments/production/hieradata/common.yaml) с общими проверками. Шаблон под каждую ноду нужен, что бы описать исключительные случаи.


  1. ilyakruchinin
    16.08.2018 15:02

    Что бы люди не выдумывали, лишь бы не ставить Zabbix.


    1. comgress64 Автор
      16.08.2018 15:09

      Про забикс я ничего не знаю, чем он выгоднее?