20 нояб. 2017 г.

yum: a lot of duplicate packages

Пришел сегодня коллега с проблемой: в процессе обновления завис yum, а после рестарта виртуалки случился kernel panic на старте. И если рабочую систему мы получили, просто загрузившись с предыдушей версии ядра, то проблемы с менеджером пакетов решали еще некоторое время. А проблема оказалась такая: при попытке совершить любые действия с пакетами yum находил вагон и маленькую тележку пакетов в состоянии duplicate. Аналогичный результат выдавал и yum check. Выискивать и удалять полторы сотни дубликатов руками - не вариант.
После некоторого количества изысканий пришли к следующему решению:
# package-cleanup --cleandupes --skip-broken
# yum clean all
# yum update


27 окт. 2017 г.

github.com/de1ayer

Дошли руки выложить некоторые наработки за последние полгода, чтобы не забылись.

16 окт. 2017 г.

YUM: установка пакета конкретной версии

Посмотреть доступные версии пакета, причем с выводом в том виде, который скушает yum
[root@git03 gitlab]# repoquery --show-duplicates gitlab-ce | grep 9.5
gitlab-ce-0:8.9.5-ce.0.el7.x86_64
gitlab-ce-0:9.5.0-ce.0.el7.x86_64
gitlab-ce-0:9.5.1-ce.0.el7.x86_64
gitlab-ce-0:9.5.2-ce.0.el7.x86_64
gitlab-ce-0:9.5.3-ce.0.el7.x86_64
gitlab-ce-0:9.5.4-ce.0.el7.x86_64
gitlab-ce-0:9.5.5-ce.0.el7.x86_64
gitlab-ce-0:9.5.6-ce.0.el7.x86_64
gitlab-ce-0:9.5.7-ce.0.el7.x86_64
gitlab-ce-0:9.5.8-ce.0.el7.x86_64
Сам бы ни в жизнь не догадался так записать О_о, тем более что установленный пакет выглядит так:
[root@dpro-git03 gitlab]# yum -q list installed gitlab-ce
Installed Packages
gitlab-ce.x86_64           9.5.5-ce.0.el7           @gitlab_gitlab-ce

12 окт. 2017 г.

cURL + GitHub

Не достает времени заняться пополнением своих заметок. Оставлю пару ссылок на отличные сборники рецептов по использованию API GitHub (и любых других) с помощью cURL. Вот одна, вот вторая. Спасибо авторам, пешыте исчо.

15 сент. 2017 г.

Equivalent of update-grub for RHEL/Fedora/CentOS

Неведомо почему, но в RedHat-based дистрибутивах нет команды grub-update (update-grub). Приходится для регенерации конфигурационного файла загрузчика помнить вот такое:
grub2-mkconfig -o "$(readlink /etc/grub2.cfg)"

7 авг. 2017 г.

clear bash history.

history -c && history -w; exit

5 авг. 2017 г.

vCenter Web Client fails to start

После установки очередного пакета обновлений ОС на одном из vCenter появилась проблема доступа к интерфейсу vSphere. Практически сразу стало ясно, что проблема в незапущенной службе Web Client. Ручной перезапуск службы работоспособность системы восстанавливал, но до первой перезагрузки. 
Оказалось, что "это не бага, это фича"(с). Описание и решение нашлось тут. Если кратко, то служба стартовала слишком рано, поэтому останавливалась с ошибкой. Решение - изменить свойства запуска на Automatic (Delayed Start) и увеличить с помощью ключа в реестре время задержки с умолчальных 120  до 300 секунд.
Create a new key 'AutoStartDelay' (DWORD (32-bit) under HKLM\SYSTEM\CurrentControlSet\services\vspherewebclientsvc and set it to decimal 300. Default delay is 120 seconds. Oh and you should check the key 'DelayedAutostart' that it is set to 1.

21 июн. 2017 г.

Lookaround assertions in Python

Приходится по рабочим задачкам ковыряться в регулярках на питоне. И очень плохо в голову складывалась такая удобная штука как Lookahead\Lookbehind assertions. Ясности сильно добавила вот эта статья, где с примерами и пояснениями разложена как сама концепция, так и особенности практического применения.

15 мая 2017 г.

ansible host_vars

Оказался в моем inventory старый-старый хост, при работе с которым даже ping падал.

˜# ansible -m ping old.example.com -e ansible_user=ansible
 [WARNING]: Module invocation had junk after the JSON data: usage: sudo -e [-S] [-p prompt] [-u username|#uid] file ...
old.example.com | FAILED! => {
    "changed": false,
    "failed": true,
    "module_stderr": "Shared connection to 192.168.0.1 closed.\r\n",
    "module_stdout": "sudo: illegal option `-n'\r\nusage: sudo -h | -K | -k | -L | -l | -V | -v\r\nusage: sudo [-bEHPS] [-p prompt] [-u username|#uid] [VAR=value]\r\n            {-i | -s | }\r\nusage: sudo -e [-S] [-p prompt] [-u username|#uid] file ...\r\n",
    "msg": "MODULE FAILURE",
    "rc": 1
Дело оказалось в том, что со второй версии изменился набор опций для вызова sudo: добавился параметр -n, которого "старые" sudo могут не знать. Так как подобных хостов у меня пара штук, менять ради них настройки по умолчанию неинтересно. Вместо этого воспользуемся функционалом Host Variables. Идея простая, нам нужно чтобы переменная окружения ansible_sudo_flags не содержала в себе -n для хоста old.example.com. Решение:
# cat /data/ansible/inventory/host_vars/old.example.com.yml
---
## fix ansible regression for old distros (no -n option with sudo)
##
ansible_sudo_flags: "-H"
Почитать о том, какая переменная и откуда важнее при выполнении заданий, можно тут.

3 мая 2017 г.

ansible + cmdbuild

Как известно, любые задачи вокруг ansible начинаются с перечня ресурсов, управление которыми требуется. Основным способом инвентаризации является обычный текстовый файл (по умолчанию /hosts) c перечнем имен или адресов серверов. Многие знают, что при указании в качестве пути директории ansible будет использовать все находящиеся внутри оной файлы как источники данных о хостах.
Однако при количестве хостов более пары десятков файлы становится трудно поддерживать в актуальном состоянии, и тогда на помощь приходят различные системы инвентаризации и управления активами. В случае с виртуальными средами такой системой в первом приближении является гипервизор (или вышестоящее ПО, типа  vsphere, proxmox, etc). При наличии подобной базы данных единственно правильным и удобным решением является или автоматизация процесса синхронизации локальных inventory-файлов ansible с данными этой базы, или прямое использование данных базы во время выполнения плейбука - dynamic inventory. Для многих случаев существуют готовые скрипты, реализующие функционал динамического построения списка хостов. Также есть страничка, где описываются основные моменты, необходимые для написания собственного решения.
Но вот с программированием (да, даже на питоне :\) у меня никак, поэтому пришлось обходиться средствами самого ansible, а именно: для взаимодействия с CMDBuild пришлось написать плейбук, который актуализирует локальный inventory-файл.
Далее я хочу сохранить для истории все тонкости и интересные с точки зрения изучения продукта моменты, возникшие в процессе разработки этого решения.
Итак, в качестве средства инвентаризации и хранения данных об информационных ресурсах используется CMDBuild. Для автоматизированного общения с базой данных доступен REST API, познакомиться с которым можно вот тут
Так как неавторизованным пользователям доступ не предоставляется (401 при любом запросе), первая задача: пройти аутентификацию. Замечание: как выяснилось в процессе отладки, сервисные учетные записи (в отличие от учетных записей пользователей) не могут быть использованы в веб-интерфейсе, только через API.
Здесь и далее для работы с http-запросами используется ansible-модуль uri. Как обычно, документация к модулю достаточно полна и снабжена примерами, чтобы разобраться в его использовании.
    - name: Get ID and open session with CMDBuild API
      uri:
        url: "{{cmdb_url}}/sessions"
        validate_certs: no
        method: POST
        body: "{\"username\" : \"{{cmdb_user}}\" , \"password\" : \"{{cmdb_pass}}\"}"
        body_format: json
        status_code: 200
        headers:
          Content-Type: application/json
      register: auth_result
Стоит указать, что значение {{cmdb_url}} стоит подсмотреть в документации, в моем случае он выглядит примерно так: "https://cmdb.example.com/cmdbuild/services/rest/v2" , а учетные данные ({{cmdb_user}} и {{cmdb_pass}}) не следует держать в открытом виде. В случае ansible 2.3+ наиболее удобным способом будет использование in-place vault:
    cmdb_user: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          666239373962396262623031663239613432663366383435393634653436366133326339373735373063306464626631613765326331343364356337313432330a386164303261623339326435613862323433386361343262383432326439653235333665313666623431343532616437376163666561613737343535336533610a6234343263376465626264333630306639626137616165373066643034336262
Для более ранних версий - отдельный vault-файл, который следует подгрузить через include_vars. Подробнее о хранилище ansible-vault и методах работы с ним тут
Итак, задание выше отправляет POST по адресу url c учетными данными в json-формате, считается выполненным при получении http-кода 200 и записывает результат выполнения (тоже json) в переменную {{auth_result}}.
Результатом этого запроса будет открытая на сервере сессия с идентификатором {{auth_result.json.data._id}}, который далее необходимо вкладывать в заголовок каждого запроса. 
Далее, имея доступ к дереву ресурсов (в рамках прав учетной записи), можно запросить требуемые нам данные. В CMDBuild есть свои встроенные фильтры, которые можно передавать через http в виде параметра ?filter=, а также свой sql-подобный язык запросов CQL, передающийся через параметр ?cql=. Мои потребности полностью покрывались фильтрами, поэтому cql-запросы не рассматривались.
Однако с фильтрами тоже все непросто. В текущей версии документации эта тема практически не затронута. Где-то на форумах нашлось описание конструкции фильтра:
'{"filter": {"attribute": "Description","operator":"contain","value":["SomeValue"],"parameterType":"fixed" }}'
Там же поступило предложение настраивать фильтр в веб-интерфейсе и подглядывать за передаваемыми http-параметрами в окне консоли браузера. Иных способов пока, к сожалению, нет. В общем, неким магическим для меня образом было получено несколько фильтров, с каждым из которых был создан запрос. Вот пример одного из них:
    ## filter_prod: Description
    ##  Status: Production,[03]
    filter_prod: "?filter=%7B%22attribute%22%3A%7B%22simple%22%3A%7B%22attribute%22%3A%22Status%22%2C%22operator%22%3A%22equal%22%2C%22value%22%3A%5B03%5D%2C%22parameterType%22%3A%22fixed%22%7D%7D%7D"
     - name: Query hosts with "Production" status (filter_prod)
      uri:
        url: "{{cmdb_url}}/classes/Server/cards{{filter_prod}}"
        validate_certs: no
        method: GET
        status_code: 200
        headers:
          CMDBuild-Authorization: "{{auth_result.json.data._id}}"
          Content-Type: application/json
        when: auth_result.json.data._id is defined
      register: prod_result
      no_log: yes
Большая часть из описания задания совпадает с вышесказанным. Из особенностей: добавлен дополнительный параметр headers, где передается токен авторизации - CMDBuild-Authorization (название которого тоже, как и в случае с фильтрами, не освещено в документации). Результат выполнения GET запроса - JSON с описанием карточек всех ресурсов класса "Server", подходящих под фильтр {{filter_prod}}. Так как вывод идет в stdout, количество выпавшей в консоль информации может удивить, поэтому добавлен параметр  no_log: yes, запрещающий любой вывод результатов этого задания вне зависимости от уровня журналирования.
Также добавлено условие выполнения задания: when: auth_result.json.data._id is defined. В случае неуспешной аутентификации плейбук не будет сыпать ошибками, а просто завершится без выполнения запросов.
После выполнения всех запросов хорошим тоном будет закрыть сессию:
    - name: Close session with CMDBuild API
      uri:
        url: "{{cmdb_url}}/sessions/{{auth_result.json.data._id}}"
        validate_certs: no
        method: DELETE
        status_code: 204
        headers:
          CMDBuild-Authorization: "{{auth_result.json.data._id}}"
Отмечу лишь, что успешный код выполнения этого запроса: 204.
Для удобства дальнейшего использования полученные списки хостов следует прогруппировать, для этого запишем названия необходимых групп в inventory-файл:
    - name: Create ansible groups
      lineinfile:
        state: present
        dest: "{{cmdb_inventory}}"
        regexp: '^{{ item|replace("[", "\[")|replace("]", "\]") }}'
        line:  '{{ item }}'
        insertbefore: BOF
        create: yes
      with_items:
        - '[cmdb-prod]'
Из особенностей здесь описание регулярки: помимо обычного указания элемента здесь последовательно используется два фильтра replace, подставляющих символ экранирования для квадратных скобок. Без этого изменения мы получаем не выражение для поиска, а диапазон символов, и поиск не срабатывает. За подсказку по проблеме спасибо автору этого поста.
Наконец, осталось лишь записать выгруженные хосты в файл.
    - name: Write '[cmdb-prod]' hosts to inventory file
      lineinfile:
        state: present
        dest: "{{cmdb_inventory}}"
        line: "{{item}}"
        insertafter: '^\[cmdb-prod\]'
      with_items: "{{ prod_result.json.data | map(attribute='HostName') | list | sort(reverse = True) }}"
Стоит пояснить происходящее внутри параметра with_items. Если содержимое переменной в Ansible - JSON, то к ее элементам можно получить доступ через var_name.json. В выводе CMDBuild присуствуют массивы meta и data, содержимое последнего нас и интересует. Далее используется фильтр map, который показывает только содержимое атрибута HostName внутри  prod_result.json.data. Однако вывод остается в формате JSON, поэтому используется фильтр list, делающий из него plaintext. Сортировка по убыванию используется потому, что каждый {{item}} вставляется после [cmdb-prod], и таким образом мы получаем в итоге сортированный в алфавитном порядке список.

В процессе разработки плейбука запросы приходилось делать множество раз, поэтому очень полезным оказался проект resty. Это обертка над curl, которая встраивается в текущий шелл и позволяет более просто делать различные http-запросы.
        
:

27 апр. 2017 г.

dnsmasq on macos

Потихоньку превращаю сайтик в link-блог, ну да нестрашно.
Потребовалось на скорую руку поднять локальный dnsmasq на макбуке. Если с конфигом все ясно: brew подсказывает, где его взять и куда положить, а что писать внутри, мы и сами с усами, то как запускать и автостартовать демона, сходу неясно. Вот тут поясняют, спасибо.
Краткая выжимка  про запуск:
  • положить скрипт инициализации в ожидаемое ОС место для автозагрузки:
sudo cp -v $(brew --prefix dnsmasq)/homebrew.mxcl.dnsmasq.plist /Library/LaunchDaemons

  • запустить вот прямо сейчас:

sudo launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist

25 апр. 2017 г.

Bash. Подводные камни

Прилетела в одном из чатиков ранее не замеченная статья про bash. Очень рекомендуется к прочтению.

13 апр. 2017 г.

Ansible -vvv reveals sensitive data

В процессе дебага плейбуков заметил очень неприятную особенность: при увеличении уровня verbose в лог (на экран и в файл) попадают те данные в открытом виде, что скрыты в vault-файле. В том случае, когда это критично (компрометация в логе закрытых ключей, паролей, и так далее), следует добавлять к заданиям параметр no_log. При его включении весь вывод команды заменяется на подавляется вне зависимости от количества -v.
- name: create user accounts
  user:
    state: present
    name: "{{ item.username }}"
    shell: "{{ item.shell | default(default_shell) }}"
    groups: "{{ item.groups | default(omit) }}"
    comment: "{{ item.name }} ({{ item.comment | default(omit) }})"
    password: "{{ item.password | default(dis_default_pass) }}"
    append: yes
    update_password: on_create
  with_items: "{{regular_users}} + {{vip_users}}"
  no_log: yes
Выглядит это так:
TASK [user-mgmt : create user accounts] ****************************************
ok: [01.example.com] => (item=(censored due to no_log)) => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result"} 
По этому поводу есть тикет в github, в котором также рекомендуется использовать no_log.  

10 апр. 2017 г.

ansible-playbook: exclude host

Если нужно исключить из списка хостов только один-два-несколько позиций, а на остальных плейбук проиграть, то делай так:
ansible-playbook --limit 'all:!bad_host' playbook.yml
Взято отсюда

6 апр. 2017 г.

yum + linux kernel

В CentOS (и, полагаю, во всех его "родственниках") довольно удобно удалять старые ядра:
package-cleanup --oldkernels --count=2
В результате останется текущее ядро и предыдущее.
Если команда не найдена, нужно доустановить пакет yum-utils. 

28 февр. 2017 г.

ansible: запрос значения переменных

В некоторых случаях требуется ручной ввод переменных. Это можно сделать с помощью параметра vars_prompt.
- name: get host info
  hosts: localhost
  vars_prompt:
    - name: remote_host
      prompt: "Where to go? Enter DNS/IP"
      private: no
      default: localhost
    - name: remote_user
      prompt: "Enter remote username"
      private: no
      default: root
    - name: remote_user_pass
      prompt: "Enter password"
      private: yes 
Описание довольно очевидно, из интересностей: private: yes скрывает вводимые символы в консоли (аналогично поведению при вводе пароля при логине); значение default подставляется в случае, если ввод был пустым (также оно отображается в скобках при запросе значения). 
В качестве примера использования можно предложить такое задание:
tasks:
    - name: create dynamic inventory
      add_host:
        name: "{{ remote_host }}"
        groups: temp_hosts
        ansible_user: "{{ remote_user }}"
        ansible_become: yes
        ansible_ssh_pass: "{{ remote_user_pass }}"
        ansible_sudo_pass: "{{ remote_user_pass }}"

21 февр. 2017 г.

ansible: list of facts to file

В последнее время активно работаю с системой управления конфигурациями Ansible, поэтому есть что записать. 
Одна из последних задач: получить список хостов, удовлетворяющий заданному условию. В моем случае нужно было отобрать серверы, у которых в перечне используемых файловых систем есть устаревшая ext2. Информация о смонтированных файловых системах есть в автоматически собираемых фактах (gather_facts: yes в ansible.cfg или описании плейбука), поэтому каких-то дополнительных действий на хосте собирать не нужно.
Чуть усложнило ситуацию то, что элемент ansible_mounts представляет собой не словарь, как большинство элементов, возвращаемых модулем setup, а массив. Таким образом, для получения, к примеру, точки монтирования, требуется использовать не ansible_mounts.fstype, а ansible_mounts[0].fstype, причем количество элементов этого массива будет уникальным для каждого хоста. 
Решением такой задачи будет плейбук следующего вида:
- name: Check hosts for ext2fs available
  hosts: all
  gather_facts: yes
  tasks:
    - name: create list of files with ext2fs partitions
      shell: "echo {{ansible_fqdn|quote}},{{item.mount|quote}},{{item.fstype|quote}}, >> ./ext2fs_list_`date +%s`.csv"
      args:
        chdir: /tmp/
      delegate_to: localhost
      with_items: "{{ ansible_mounts }}"
      when: item.fstype == "ext2"
Если исключить атрибут when: , в файл будут записаны все найденные точки монтирования.

17 февр. 2017 г.

usermod -p

Для тестовых целей может потребоваться образ linux-а с предустановленным паролем root. При создании такого образа (через Docker, Vagrant, установочные скрипты) можно воспользоваться командой 
# usemod -p encrypted_password root
Чтобы получить шифрованное значение пароля
# openssl passwd my_pass

26 янв. 2017 г.

Создание (регистрация) службы Windows

Если нужно сделать из исполняемого файла службу Windows, которая будет доступна в оснастке services.msc и вести себя как другие службы, следует обратиться к странице документации тут.