Стадия жизненного цикла модуля: Experimental
У модуля есть требования для установки
Данное руководство предназначено для пользователей модуля ansible и описывает порядок создания заданий, которые выполняют Ansible-плейбуки на виртуальных машинах и на хостах, заданных адресом. В руководстве разобраны все блоки манифеста задания, порядок выбора целей, источники плейбука, формат результата и порядок диагностики.
Ресурсы модуля
Модуль добавляет в кластер два кастомных ресурса, приведённых в таблице ниже.
| Ресурс | Назначение |
|---|---|
| AnsibleRun | Задание, которое выполняет плейбук один раз. Задание определяет целевые хосты, выполняет на них плейбук и записывает результат в свой статус. Повторно задание не выполняется, а его блок spec изменять нельзя, поэтому для нового выполнения создаётся новый объект |
| AnsibleRunSchedule | Расписание в формате cron, которое создаёт объекты AnsibleRun так же, как ресурс CronJob создаёт объекты Job |
Далее в тексте под заданием понимается объект AnsibleRun, а под плейбуком — YAML-файл с задачами, который это задание выполняет.
Все объекты, на которые ссылается задание, должны находиться в его неймспейсе. К таким объектам относятся Secret с учётными данными, ConfigMap с текстом плейбука и целевые ресурсы VirtualMachine.
Быстрый старт
Пример настройки виртуальной машины, в котором задание проверяет связь с машиной и записывает результат в свой статус.
-
Подготовьте виртуальную машину. Для подключения потребуются SSH-сервер, пользователь для входа и адрес, известный платформе. Ниже приведён фрагмент cloud-init-сценария, который создаёт такого пользователя:
#cloud-config packages: - openssh-server users: - name: ansible lock_passwd: false sudo: ALL=(ALL) NOPASSWD:ALL ssh_authorized_keys: - ssh-ed25519 <SSH_PUBLIC_KEY> ansible@example runcmd: - systemctl enable --now sshВ параметре
<SSH_PUBLIC_KEY>укажите публичный ключ, приватная часть которого будет помещена в Secret на следующем шаге. -
Создайте Secret с учётными данными SSH в том же неймспейсе, где будет создано задание:
apiVersion: v1 kind: Secret metadata: name: ssh-creds namespace: demo type: Opaque stringData: username: ansible ssh-privatekey: | -----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY----- -
Создайте задание, которое проверит связь с машиной:
apiVersion: ansible.deckhouse.io/v1alpha1 kind: AnsibleRun metadata: name: configure-vm namespace: demo spec: target: type: VirtualMachines virtualMachines: selector: matchLabels: vm: vm-01 connection: secretRef: name: ssh-creds playbook: type: Inline inline: | --- - name: Configure VM hosts: all tasks: - name: Check SSH connectivity ansible.builtin.ping: -
Проверьте результат выполнения задания:
d8 k get ansibleruns -n demoПример вывода:
NAME PHASE REASON HOSTS OK FAILED UNREACHABLE SKIPPED AGE configure-vm PlaybookSucceeded PlaybookSucceeded 1 1 0 0 0 42sПодробный результат, включая счётчики задач по каждому хосту, приведён в статусе задания:
d8 k get ansiblerun configure-vm -n demo -o yaml
Структура задания
Манифест задания состоит из четырёх блоков, которые приведены в таблице ниже. Каждый блок описан в отдельном разделе руководства.
| Блок | Назначение | Обязателен |
|---|---|---|
target |
Определяет, какие хосты настраивает задание | Да |
connection |
Определяет, как задание подключается к хостам | Да |
playbook |
Определяет источник плейбука, теги выполняемой части и переменные | Да |
runner |
Определяет режим выполнения плейбука | Нет |
Ниже приведён манифест, в котором заполнены все блоки задания:
apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
name: configure-db
namespace: demo
spec:
target:
type: VirtualMachines # VirtualMachines | Hosts
virtualMachines:
selector: # selector ИЛИ names, но не оба
matchLabels:
role: db
connection:
secretRef:
name: ssh-creds # учётные данные SSH, тот же namespace
network: # необязательно; по умолчанию основная сеть
type: ClusterNetwork # Main | Network | ClusterNetwork
name: vlan-64
playbook:
type: Inline # Inline | ConfigMap | Git
inline: |
---
- hosts: all
tasks:
- ansible.builtin.ping:
tags: [deploy] # необязательно; выполнить только эти теги
skipTags: [migrations] # необязательно; исключить эти теги
vars: # необязательно; --extra-vars этого задания
- name: app_version
value: "1.4.2" # любой YAML: строка, число, список, отображение
- name: db_password
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
varsFiles: # необязательно; YAML-документ с переменными
- configMapRef:
name: app-config # ключ по умолчанию — vars.yaml
runner:
dryRun: false # режим проверки Ansible
diff: false
verbosity: 0 # 0..4Цели
Блок target определяет хосты, на которых выполняется плейбук. Такими хостами являются либо виртуальные машины платформы, либо хосты, заданные адресом. Одно задание работает с хостами только одного вида, поэтому указать поля virtualMachines и hosts в одном объекте нельзя.
Блок target используется вместо параметра -i командной строки Ansible. На основе этого блока контроллер формирует файл инвентаря, который иначе потребовалось бы создавать и поддерживать вручную. Адреса хостов определяются в момент запуска задания по данным платформы, поэтому виртуальная машина, пересозданная с другим адресом, будет настроена без изменения манифеста задания.
Виртуальные машины
Виртуальные машины выбираются по лейблам или перечисляются по именам. Адреса выбранных машин контроллер определяет самостоятельно в момент запуска задания.
Ниже приведён пример выбора машины по лейблу:
target:
type: VirtualMachines
virtualMachines:
selector:
matchLabels:
vm: vm-01Пример выбора машин по выражению над лейблами:
target:
type: VirtualMachines
virtualMachines:
selector:
matchExpressions:
- key: vm
operator: In
values: [vm-01, vm-02]Пример выбора машин по именам:
target:
type: VirtualMachines
virtualMachines:
names: [demo-vm-01, demo-vm-02]Поля selector и names являются взаимоисключающими, а манифест с пустым селектором кластер отклоняет. Задание выполняется однократно, поэтому цели требуется задавать явно.
Если машина подошла под выбор, но не может быть настроена в момент запуска задания, она попадает в список status.skippedHosts с указанием причины, а плейбук выполняется на остальных машинах. Возможные причины пропуска приведены в разделе Диагностика.
Хосты, заданные адресом
Адреса некоторых хостов платформе неизвестны. К таким хостам относятся машины с адресом, настроенным вручную внутри гостевой ОС или полученным от внешнего DHCP-сервера, а также физические серверы, сетевые устройства и машины из других кластеров. Для настройки таких хостов используется тип целей Hosts, в котором адреса указываются в манифесте задания.
Цели типа Hosts доступны только в коммерческих редакциях. В Community Edition задание работает с виртуальными машинами платформы, а манифест с целями типа Hosts кластер отклоняет при создании.
Пример задания с хостами, заданными адресом:
target:
type: Hosts
hosts:
- address: 192.168.55.10
vars:
- name: app_role
value: frontend
groups: [web]
- address: db.example.comХост, заданный адресом, не является ресурсом VirtualMachine, поэтому задание работает с ним иначе, чем с виртуальной машиной платформы. Различия приведены в таблице ниже.
| Свойство | VirtualMachines |
Hosts |
|---|---|---|
| Адрес | Определяет платформа | Указывается в манифесте задания |
| Фаза цели | Ожидается Running или Migrating |
Не применяется |
| Проверка занятости другим заданием | Выполняется, причина пропуска TargetBusy |
Не выполняется |
| Переменные и группы | Аннотации на машине | Поля vars и groups |
status.hosts[].name |
Имя машины | Адрес хоста |
status.skippedHosts |
Заполняется, если есть пропущенные машины | Всегда пустой |
Поскольку занятость хоста, заданного адресом, не проверяется, два задания могут выполняться на одном адресе одновременно. За разделение таких заданий отвечает пользователь, который их создаёт.
Поле vars такого хоста описывается так же, как переменные хоста задания, включая источники значений valueFrom. Имена групп подчиняются правилам, описанным в разделе Инвентарь Ansible.
Инвентарь Ansible
Файл инвентаря создавать не требуется. Контроллер формирует его самостоятельно на основе выбранных целей и аннотаций виртуальных машин. Переменные ansible_host, ansible_user и пути к учётным данным контроллер задаёт сам, и переопределить их нельзя. Остальные переменные задаются пользователем.
Для виртуальной машины группы и переменные хоста берутся из её аннотаций, которые приведены в таблице ниже.
| Аннотация | Назначение |
|---|---|
ansible.deckhouse.io/groups |
Список групп через запятую. Хост входит в каждую из перечисленных групп дополнительно к группе all |
vars.ansible.deckhouse.io/<VARIABLE_NAME> |
Переменная хоста с именем <VARIABLE_NAME> |
apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachine
metadata:
name: demo-vm-01
namespace: demo
annotations:
ansible.deckhouse.io/groups: "web,production"
vars.ansible.deckhouse.io/app_role: "frontend"Аннотации виртуальной машины выполняют ту же роль, что файл host_vars/<HOST_NAME> в обычном проекте Ansible, но хранятся на самой машине. Для хоста, заданного адресом, те же данные указываются в манифесте задания полями vars и groups.
В обоих случаях контроллер формирует файл инвентаря следующего вида:
all:
hosts:
demo-vm-01:
ansible_host: 10.66.10.2
ansible_user: ansible
ansible_ssh_private_key_file: /home/runner/.ssh/ssh-privatekey
app_role: frontend
web:
hosts:
demo-vm-01: {}
production:
hosts:
demo-vm-01: {}К именам переменных в аннотациях применяются те же правила, что и к запрещённым именам переменных. Дополнительно действует правило, которое применяется только к аннотациям. Аннотация не может задавать переменные с префиксом ansible_, за исключением переменной ansible_port.
Это ограничение связано с тем, что аннотацию может изменить владелец виртуальной машины, который не обязательно является автором задания. Например, переменная ansible_host в аннотации перенаправила бы чужое задание на другую машину и передала бы ей учётные данные SSH, а переменная ansible_ssh_common_args со значением ProxyCommand выполнила бы произвольную команду внутри пода раннера.
Переменная ansible_port является исключением, потому что SSH-порт относится к конкретной машине. Поле spec.playbook.vars задаёт один порт для всего задания, а машины выбираются по лейблам, поэтому нестандартный порт известен только самой машине:
metadata:
annotations:
vars.ansible.deckhouse.io/ansible_port: "2222"Такое исключение не снижает безопасность задания. Адрес хоста остаётся тем, который определил контроллер, поэтому подключение приходит на ту же машину, а владелец машины и без того определяет, какая служба слушает на каждом её порту.
Таким образом, порт задаётся в трёх местах, от менее приоритетного к более приоритетному:
spec.playbook.varsзадаёт порт для всех хостов задания.hosts[].varsзадаёт порт для отдельного хоста, заданного адресом.- Аннотация машины задаёт порт только для этой машины и переопределяет оба предыдущих значения.
Аннотации, имена которых не начинаются с двух перечисленных префиксов, контроллер игнорирует, а пустые элементы в списке групп пропускает. Если правило именования нарушено, задание завершается фазой Error с причиной InvalidInventoryAnnotations до создания пода. В сообщении статуса при этом указаны машина и аннотация, которые нарушили правило.
Подключение
Блок connection определяет, по какому транспорту, с какими учётными данными и через какую сеть задание подключается к хостам.
Транспорт
Транспорт определяет способ подключения задания к хосту и указывается в поле connection.type. В текущей версии модуля доступно единственное значение SSH, которое используется по умолчанию, поэтому поле можно не указывать:
connection:
type: SSH
secretRef:
name: ssh-credsУчётные данные SSH
Учётные данные для подключения задание берёт из Secret, имя которого указано в поле connection.secretRef:
connection:
secretRef:
name: ssh-credsТакой Secret должен находиться в неймспейсе задания и содержать ключи, приведённые в таблице ниже. Обязательным является хотя бы один из ключей ssh-privatekey или password.
| Ключ | Назначение |
|---|---|
username |
Пользователь SSH |
ssh-privatekey |
Приватный ключ SSH |
password |
Пароль SSH |
become-password |
Пароль для повышения привилегий, который используется задачами с параметром become: true |
ansible-vault-password |
Пароль для Ansible Vault. Передаётся утилите ansible-playbook как файл пароля |
Имена ключей соответствуют встроенным типам Secret kubernetes.io/ssh-auth и kubernetes.io/basic-auth.
Ниже приведён пример Secret, в котором вместо приватного ключа используется пароль:
apiVersion: v1
kind: Secret
metadata:
name: ssh-creds-password
namespace: demo
type: Opaque
stringData:
username: ansible
password: ansibleСети
По умолчанию задание выполняется в основной сети. Под раннера запускается в сети подов кластера и подключается к машине по адресу из поля status.ipAddress ресурса VirtualMachine.
Дополнительные сети доступны только в коммерческих редакциях. В Community Edition задание выполняется в основной сети, а манифест с любым другим типом сети кластер отклоняет при создании.
Виртуальная машина может быть подключена также к дополнительным сетям модуля sdn — проектной (Network) или кластерной (ClusterNetwork). Служба SSH в гостевой ОС может слушать только в такой сети.
Дополнительная сеть является отдельным L2-доменом, и маршрута из сети подов в неё нет, поэтому одного адреса машины для подключения недостаточно. Задание должно выполняться в той же сети, в которой находится машина.
Сеть указывается в блоке connection.network. Под раннера подключается к указанной сети, и адрес каждой цели контроллер также берёт в ней:
connection:
secretRef:
name: ssh-creds
network:
type: ClusterNetwork # Main (по умолчанию) | Network | ClusterNetwork
name: vlan-64Блок network повторяет элемент списка spec.networks виртуальной машины, поэтому его значения переносятся из манифеста машины:
# VirtualMachine
spec:
networks:
- type: Main
- type: ClusterNetwork
name: vlan-64 # эту сеть и указываем в задании
- type: Network
name: storage-netПеред настройкой дополнительной сети учитывайте следующие особенности:
- Пул адресов у сети (
spec.ipam.ipAddressPoolRef) — адрес из него получают и машина, и раннер, поэтому сеть без пула работает только на уровне L2. Задание завершается фазойErrorи причинойNetworkWithoutIPAM, а в сообщении указана сеть, которую нужно настроить. Под при этом не создаётся. - Настройка интерфейса в гостевой ОС — адрес доставляется по DHCP, поэтому в гостевой ОС нужен DHCP-клиент на этом интерфейсе. Иначе платформа показывает адрес, по которому машина не отвечает, и хост оказывается недостижимым (
unreachable). Привязывайте интерфейс по MAC-адресу или по предсказуемому имени, а не поethX, потому что подключение сети или изменение порядка вspec.networksможет перенумероватьethXвнутри гостя, и тогда DHCP-клиент запросит адрес на другом интерфейсе. - Одна сеть на задание — машины, которые слушают в разных сетях, разделяются лейблами на несколько заданий. Из каждого манифеста видно, в какой сети оно выполняется.
- Область видимости сети — Network видна только в своём неймспейсе, а ClusterNetwork доступна из любого.
- Размер пула — адресов должно хватать на машины плюс одновременные задания, потому что задание занимает адрес, пока существует его под. Под успешного задания удаляется сразу, а под неуспешного сохраняется до удаления AnsibleRun, поэтому оставленные неуспешные задания продолжают занимать адреса.
- Хосты, заданные адресом — им
networkтоже нужна, потому что сам адрес раннер в эту сеть не помещает. Так в сети управления настраивают то, чего в кластере нет, например физические серверы, сетевые устройства или машины других кластеров.
Под раннера остаётся подключённым и к сети подов кластера, потому что дополнительный интерфейс добавляется к основному, а не заменяет его. Поэтому плейбук из Git-репозитория внутри кластера и содержимое Galaxy из зеркала в кластере загружаются так же, как при выполнении задания без дополнительной сети. Init-контейнерам при этом по-прежнему доступны кластерный DNS и сервисы кластера.
Исключением является пул адресов, который выдаёт маршруты. Такие маршруты прописываются внутри пода, и маршрут по умолчанию (0.0.0.0/0) перенаправляет через дополнительную сеть весь исходящий трафик. Обращения к внутрикластерным адресам продолжат работать, поскольку для них есть отдельные маршруты. Данные, которые задание загружает извне кластера, например из публичного Git-репозитория или Galaxy, будут отправлены через дополнительную сеть и могут оказаться недоступны. Перед выполнением задания в такой сети проверьте список маршрутов в поле spec.pools[].routes пула.
Адрес появляется в списке status.networks[] виртуальной машины в момент, когда интерфейс фактически подключён к её поду, а не в момент выделения адреса. Ресурс IPAddress у машины при этом может уже существовать, пока статус ещё пуст. До подключения интерфейса задание пропускает такую машину с причиной NoAddressInNetwork.
Ещё три особенности дополнительных сетей следуют из их устройства, и администратору кластера важно учитывать их при выдаче прав на создание заданий:
- Сетевые политики — NetworkPolicy проекта ограничивает только сеть подов и не ограничивает действия задания внутри VLAN.
- Доступность ClusterNetwork — она видна из любого неймспейса, поэтому право создавать AnsibleRun равно праву поместить под в любой кластерный L2-домен. Так работает модуль
sdnдля любой нагрузки, и модульansibleздесь ничего не добавляет. Но учитывать это следует до того, как выдать такое право. - MTU — берётся из сети и не должен превышать MTU узловых интерфейсов, на которых она построена. Несовпадение проявляется неочевидно. SSH подключается, а копирование файла или установка пакета зависают.
Плейбук
Блок playbook определяет, откуда взять плейбук и какую его часть выполнить. Источником плейбука может быть текст в манифесте задания, ресурс ConfigMap или Git-репозиторий. В одном задании используется только один источник.
Встроенный плейбук
Текст плейбука указывается непосредственно в манифесте задания:
playbook:
type: Inline
inline: |
---
- name: Configure VM
hosts: all
tasks:
- ansible.builtin.ping:Размер встроенного плейбука ограничен 64 КБ. Для плейбука большего размера используйте ресурс ConfigMap или Git-репозиторий.
Плейбук из ConfigMap
Плейбук хранится в ресурсе ConfigMap в неймспейсе задания, а задание ссылается на ключ этого ресурса:
playbook:
type: ConfigMap
configMapRef:
name: demo-playbook
key: playbook.yaml # значение по умолчаниюПлейбук-проект из Git
Для проектов с ролями, каталогом group_vars и шаблонами контроллер загружает всё дерево репозитория, поэтому роли, расположенные рядом с плейбуком, выполняются без изменений:
playbook:
type: Git
git:
url: https://github.com/example-org/infra
revision: v1.4.0 # ветка, тег или SHA; по умолчанию ветка репозитория
path: playbooks/site.yml # по умолчанию playbook.yaml
secretRef:
name: git-token # приватные репозиторииУказанным настройкам соответствуют следующие команды Ansible:
git clone --recurse-submodules https://github.com/example-org/infra && cd infra
git checkout v1.4.0
ansible-galaxy install -r requirements.yml # если проект их объявляет
ansible-playbook -i inventory.yml playbooks/site.ymlЕсли в загруженном дереве присутствует файл requirements.yml, roles/requirements.yml или collections/requirements.yml, объявленные в нём роли и коллекции Galaxy устанавливаются до запуска плейбука. Подмодули репозитория также загружаются. Коммит, который был фактически выполнен, контроллер записывает в поле status.playbookCommit.
Загрузка репозитория и содержимого Galaxy является единственным трафиком задания, выходящим за пределы кластера, и выполняется через прокси, настроенный для кластера. Отдельных настроек прокси у модуля нет. Задачи плейбука в этот трафик не входят, поскольку выполняются на целевых хостах по SSH. Если задаче требуется прокси, объявите его в параметре environment этой задачи.
Приватные репозитории
Для доступа к приватному репозиторию требуются отдельные учётные данные, не связанные с учётными данными SSH для целевых хостов. Такие данные контроллер берёт из Secret, указанного в поле git.secretRef, и монтирует только на шаг загрузки репозитория, поэтому задачи плейбука их не видят.
Для адресов вида ssh://git@… Secret является обязательным и должен содержать ключи ssh-privatekey и known_hosts, поскольку ключи хоста проверяются строго. Получить содержимое known_hosts можно командой ssh-keyscan github.com.
apiVersion: v1
kind: Secret
metadata:
name: git-deploy-key
namespace: demo
type: kubernetes.io/ssh-auth
stringData:
ssh-privatekey: |
-----BEGIN OPENSSH PRIVATE KEY-----
...
known_hosts: |
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5...Для адресов HTTP(S) с токеном требуются ключи username и password, где паролем является сам токен. В GitLab в качестве имени пользователя используется значение oauth2. Если репозиторий размещён на собственном сервере с сертификатом своего удостоверяющего центра, добавьте в Secret ключ ca.crt:
apiVersion: v1
kind: Secret
metadata:
name: git-token
namespace: demo
type: kubernetes.io/basic-auth
stringData:
username: oauth2
password: glpat-xxxxxxxxxxxxВыборочный запуск по тегам
Теги определяют, какая часть плейбука выполняется, и работают так же, как параметры --tags и --skip-tags командной строки Ansible.
playbook:
type: Git
git:
url: https://github.com/example-org/infra
path: site.yml
tags: [deploy, config] # выполнить только помеченные этими тегами задачи
skipTags: [migrations] # и исключить этиПриведённым настройкам соответствует следующая команда:
ansible-playbook -i inventory.yml site.yml --tags deploy,config --skip-tags migrationsЕсли заданы оба поля, выполняются задачи, выбранные полем tags, за вычетом задач, исключённых полем skipTags. Так же ведёт себя утилита ansible-playbook.
Именем тега может быть любая строка без пробелов и запятой. Запятая используется как разделитель тегов в аргументе, а пробелы Ansible удаляет самостоятельно. Остальные написания, принятые в плейбуках, допускаются, включая role:install и db/migrate.
Служебные теги Ansible сохраняют своё стандартное значение, которое приведено в таблице ниже.
| Тег | Значение |
|---|---|
always |
Выполняется при любом фильтре, пока не назван в skipTags |
never |
Выполняется, только когда назван в tags |
tagged |
Все задачи, у которых есть хоть один тег |
untagged |
Все задачи без тегов |
all |
Все задачи — поведение по умолчанию |
Фильтр без совпадений
Такая ситуация не является ошибкой. Утилита ansible-playbook завершается успешно, не выполнив ни одной задачи, а задание переходит в фазу PlaybookSucceeded с нулевыми счётчиками. Так же выглядит результат при опечатке в имени тега, поэтому задание сообщает об этом в статусе:
$ d8 k get ansiblerun configure-db -o jsonpath='{.status.message}'
No task matched the tags of the run.
Теги не связаны с параметрами раннера. Параметры dryRun и verbosity изменяют режим выполнения и подробность вывода, но не влияют на состав выполняемых задач.
Параметры раннера
Блок runner изменяет режим запуска плейбука и подробность его вывода, не влияя на состав выполняемых задач:
runner:
dryRun: true # режим проверки Ansible
diff: true
verbosity: 2 # 0..4Приведённым настройкам соответствует следующая команда:
ansible-playbook -i inventory.yml site.yml --check --diff -vvС параметром dryRun задание показывает, какие изменения были бы выполнены, а вместе с параметром diff выводит эти изменения подробно. В режиме проверки Ansible пропускает задачи модулей command и shell.
Если Secret подключения содержит ключ ansible-vault-password, контроллер добавляет к запуску параметр --vault-password-file, поэтому проект с зашифрованными файлами выполняется без изменений.
Переменные
Переменные задают значения, с которыми выполняется плейбук, и позволяют не изменять сам плейбук. Благодаря этому один Git-проект обслуживает несколько окружений, а задача получает пароль, который не хранится в манифесте задания.
В простейшем случае переменная содержит значение, которое плейбук читает как {{ app_version }}:
spec:
playbook:
vars:
- name: app_version
value: "1.4.2"Формат записи совпадает с форматом блока env пода. У переменной есть имя, а её значение либо указывается непосредственно в поле value, либо берётся из другого объекта через поле valueFrom. В поле value можно указать строку, число, список или отображение.
Значение переменной задаётся тремя способами, которые приведены в таблице ниже.
| Способ | Назначение |
|---|---|
vars с полем value |
Значение принадлежит этому заданию и указывается непосредственно в манифесте |
vars с полем valueFrom |
Значение уже хранится в ключе Secret или ConfigMap |
varsFiles |
Требуется передать целый набор значений со структурой и типами |
Ниже приведён манифест, в котором использованы все три способа:
spec:
playbook:
vars:
- name: app_version
value: "1.4.2"
- name: packages
value: [nginx, curl, git]
- name: users
value:
- name: deploy
groups: [wheel]
- name: db_password
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
varsFiles:
- configMapRef:
name: app-configПриведённым настройкам соответствует следующая команда:
ansible-playbook -i inventory.yml site.yml \
-e @app-config-vars.yml \
-e '{"app_version":"1.4.2","packages":["nginx","curl","git"],"users":[{"name":"deploy","groups":["wheel"]}]}' \
-e db_password="$DB_PASSWORD"Поле spec.playbook.vars соответствует полю Extra Variables в AWX, параметру -e командной строки и имеет такой же высший приоритет.
Именем переменной является идентификатор Ansible, то есть буква или подчёркивание, за которыми следуют буквы, цифры и подчёркивания. Имя, не соответствующее этому правилу, API-сервер не принимает. Часть имён запрещена дополнительно, и такие имена перечислены в разделе Запрещённые имена переменных.
Значения из Secret и ConfigMap
Пароль или другое уже существующее значение не требуется переносить в манифест задания. Поле valueFrom берёт значение из одного ключа Secret или ConfigMap, при этом плейбук видит переменную под тем именем, которое указано в задании:
spec:
playbook:
vars:
- name: db_password
valueFrom:
secretKeyRef:
name: app-secrets
key: db-passwordЗначение подставляет kubelet при запуске пода раннера. Контроллер обращается к Secret и ConfigMap только для проверки имён ключей, поэтому значение не попадает ни в один объект модуля и не появляется в аргументах процесса, которые видны в выводе ps. В файле переменных остаётся только ссылка на переменную окружения:
db_password: "{{ lookup('env', 'ANSIBLE_VAR_db_password') }}"Из такого способа передачи значений следуют перечисленные ниже особенности.
- Права на Secret — при выполнении они не проверяются, так же как для любого пода, который на Secret ссылается. Значит, права создавать задание достаточно, чтобы прочитать содержимое названного в задании объекта, ведь плейбук пишет владелец задания. Учитывайте это, выдавая такое право в неймспейсе, где содержимое Secret доступно не всем.
- Имя переменной окружения —
ANSIBLE_VAR_плюс имя переменной как написано, с сохранением регистра. Ansible считаетdb_passwordиDB_PASSWORDразными переменными, и в окружении это две разные записи. Поэтому Secret с ключами вUPPER_SNAKEсоседствует с переменной, названной по конвенции Ansible, без переименований. - Одно имя, одно значение — если одно имя получает значения из двух разных ключей (например, два хоста называют каждый свой Secret), задание отклоняется с причиной
InvalidVariables, потому что окружение у пода раннера одно. - Ключ с двоичными данными (
binaryDataу ConfigMap) — переменной стать не может. Если он указан явно, задание завершается ошибкой с соответствующим сообщением.
Тип значения из отдельного ключа
В ключе Secret или ConfigMap хранится строка, и именно она передаётся плейбуку. Поле value и документ, указанный в varsFiles, сохраняют форму значения, а отдельный ключ — нет.
vars:
- name: packages
value: [nginx, curl] # список передаётся списком
- name: extra_packages
valueFrom:
configMapKeyRef: # "[nginx, curl]" — строка
name: app-config
key: extra_packagesЕсли плейбуку требуется список, он разбирает строку самостоятельно:
- hosts: all
tasks:
- ansible.builtin.package:
name: "{{ extra_packages | from_yaml }}"
state: presentИменно этим объясняется ошибка задачи вида «ожидался список, получена строка». Если набор значений имеет структуру, передайте его документом в поле varsFiles, где типы значений уже заданы и разбирать строку не требуется.
Набор переменных в файле
Набор переменных хранится в файле так же, как в файле group_vars/all.yml обычного проекта Ansible. Разместите такой файл в ресурсе ConfigMap или Secret и укажите его в задании. Этот способ соответствует параметру -e @vars.yml командной строки.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
vars.yaml: | # обычный YAML-документ
app_tier: backend
app_version: "1.10" # в кавычках — значит строка
packages: [nginx, curl] # список остаётся списком
limits:
cpu: 2
mem: 4Gispec:
playbook:
varsFiles:
- configMapRef:
name: app-config # ключ по умолчанию — vars.yaml
- secretRef:
name: app-secrets
key: prod.yaml
optional: trueКлючом по умолчанию является vars.yaml, так же как ключом по умолчанию для ConfigMap с плейбуком является playbook.yaml. Указывайте поле key, если в одном объекте хранится несколько наборов переменных, например dev.yaml и prod.yaml.
Основное назначение файла состоит в том, что типы значений в нём сохраняются. Автор файла указывает, какое значение является строкой, а какое числом, тогда как отдельный ключ Secret или ConfigMap такой возможности не даёт. Например, значение 1.10 в отдельном ключе является текстом, и его преобразование в число превратило бы версию в 1.1.
Файл читает утилита ansible-playbook, а не контроллер, поскольку kubelet монтирует ключ в под раннера. Из этого следуют два вывода:
- Значения не проходят ни через один объект модуля и не появляются в аргументах процесса. Их не видно ни в манифесте задания, ни в списке процессов на узле.
- Имена переменных внутри файла не проверяются — имена, которые запрещены в других местах, из файла действуют. Отдельно следует отметить переменную
ansible_connection: local, при которой плейбук выполнится внутри пода раннера и завершится успешно, не затронув ни одного целевого хоста.
Второй вывод является предупреждением о возможной ошибке, а не расширением прав. Файл переменных имеет тот же уровень доверия, что и плейбук, а автор задания и без того выбирает плейбук, адреса хостов и Secret с учётными данными.
Переменные хоста
Переменные хоста описывают саму машину, а не задание, поэтому указываются рядом с тем хостом, которому принадлежат.
Поле hosts[].vars описывается так же, как переменные задания, включая источники значений valueFrom:
target:
type: Hosts
hosts:
- address: 192.168.55.10
vars:
- name: app_role
value: frontend
- name: ansible_port
value: 2222
groups: [web]Такие переменные попадают в файл инвентаря рядом с машиной, которую описывают, и предназначены для фактов о ней, например для значений datacenter, app_role или нестандартного SSH-порта. Для виртуальной машины те же значения задают аннотации.
Переменная с тем же именем из поля spec.playbook.vars имеет приоритет над обоими способами, поскольку аннотация задана заранее, а значения задания применяются в момент выполнения.
Исключением является случай, когда обе стороны получают значение из Secret или ConfigMap и при этом из разных ключей. В такой ситуации приоритет не применяется, и задание отклоняется с причиной InvalidVariables до запуска плейбука, поскольку окружение у пода раннера одно. Чтобы этого избежать, задайте таким значениям разные имена. На два литеральных значения или на литеральное значение вместе с valueFrom ограничение не распространяется.
Приоритет значений
Для значений действует общее правило: переменные задания имеют приоритет над всеми значениями, которые задают плейбук и его проект. Переменная хоста, заданная аннотацией виртуальной машины или полем hosts[].vars, имеет приоритет над каталогом group_vars/ проекта, но уступает блоку vars внутри play.
Все значения из полей vars и varsFiles передаются как --extra-vars, то есть на 22-м, самом высоком уровне приоритета Ansible. Полный порядок приоритетов, от низшего к высшему, приведён в таблице ниже.
| Откуда значение | Уровень Ansible |
|---|---|
defaults роли |
2 |
group_vars/all.yml проекта |
5 |
Переменные хоста: аннотации виртуальной машины, hosts[].vars |
8 |
vars: в play |
12 |
vars_files |
14 |
| Переменные роли | 15 |
set_fact |
19 |
spec.playbook.varsFiles, в порядке списка |
22 |
spec.playbook.vars |
22 |
Play в таблице означает блок вида - hosts: … tasks: …, и в одном плейбуке таких блоков может быть несколько.
Поля spec.playbook.vars и spec.playbook.varsFiles находятся на одном уровне приоритета, а между собой их различает порядок аргументов. Сначала передаются файлы в порядке списка, а поле spec.playbook.vars передаётся последним. Утилита ansible-playbook применяет параметры -e слева направо, поэтому итоговый порядок виден в аргументах пода, где значение правее имеет приоритет.
d8 k -n <NAMESPACE> get pod -l ansible.deckhouse.io/owner=<RUN_NAME> \
-o jsonpath='{.spec.containers[0].args}'
# … -e @/ansible/vars.d/0/vars.yaml -e @/ansible/vars.d/1/prod.yaml -e @/ansible/vars.ymlЗначение с более высоким приоритетом заменяет имя переменной целиком, и отображения не объединяются. Например, значение limits: {cpu: 2, mem: 4Gi} в одном файле и значение limits: {cpu: 4} в следующем дадут плейбуку limits: {cpu: 4}, а значение mem будет потеряно. Поэтому храните одно отображение в одном файле, а отдельное имя переопределяйте через поле spec.playbook.vars, значение которого нельзя перекрыть ни одним файлом.
Из описанного порядка приоритетов следует, что общий Git-проект не может защитить свои имена переменных от задания. Значения, принадлежащие проекту, храните в его каталоге group_vars/, а поле vars используйте для значений, которые различаются между заданиями.
Разрешение конфликта имён
В примере ниже плейбук задаёт переменную app_version самостоятельно, на машине задана аннотация с тем же именем, а задание передаёт собственное значение:
# VirtualMachine
metadata:
annotations:
vars.ansible.deckhouse.io/app_version: "from-annotation" # уровень 8
---
# AnsibleRun
spec:
playbook:
vars:
- name: app_version
value: "from-the-run" # уровень 22
playbook:
type: Inline
inline: |
- hosts: all
vars:
app_version: "from-the-playbook" # уровень 12
tasks:
- ansible.builtin.debug:
msg: "{{ app_version }}"Задача выведет значение from-the-run. Если удалить поле spec.playbook.vars, задача выведет значение from-the-playbook, а не значение аннотации, поскольку уровень 12 выше уровня 8. Значение аннотации применяется только в том случае, если плейбук эту переменную не задаёт.
Типы значений
Тип значения определяется тем, как это значение записано в манифесте. Схема ресурса проверяет только имена переменных, а содержимое значений не проверяется, поэтому опечатка внутри вложенной структуры обнаружится при выполнении плейбука.
Здесь также действует известная особенность YAML, в котором значение 1.10 является числом 1.1, а значение no — логическим false. Версия является строкой, поэтому её следует указывать в кавычках:
vars:
- name: app_version
value: "1.10" # без кавычек превратится в 1.1Целые числа сохраняют свой тип, поэтому значение value: 1000000 передаётся плейбуку целым числом 1 000 000, а большое целое число не теряет точность.
Значение с фигурными скобками
Будет ли выражение Jinja внутри значения вычислено, зависит от источника этого значения. Возможные варианты приведены в таблице ниже.
| Как записано | Что получит плейбук |
|---|---|
value: "{{ 7 * 6 }}" в манифесте |
42 |
| Тот же текст в Secret или ConfigMap | {{ 7 * 6 }}, без вычисления |
Поле value задаёт автор задания, поэтому его значение шаблонизируется так же, как переменная в любом файле переменных. Значение из поля valueFrom передаётся через выражение lookup('env', ...), и его результат Ansible помечает как небезопасный (unsafe). Благодаря этому содержимое чужого объекта не может стать выражением, выполняемым внутри пода раннера.
Чтобы фигурные скобки в поле value остались литеральными, экранируйте их так, как этого ожидает Ansible:
vars:
- name: template_source
value: "{{ '{{' }} not a template {{ '}}' }}" # приедет как {{ not a template }}Отсутствующий источник переменных
Если объявленный источник переменных отсутствует, задание не выполняется, но и не завершается ошибкой сразу. Поведение различается для отсутствующего объекта и отсутствующего ключа так же, как для ConfigMap с плейбуком. Возможные ситуации приведены в таблице ниже.
| Ситуация | Поведение задания |
|---|---|
Объекта нет, optional не задан |
Задание остаётся в Pending с DependenciesReady=False и причиной VarsSourceNotFound, а когда объект появится — продолжается. Виртуальные машины при этом не занимаются |
Объекта нет, optional: true |
Задание выполняется без этих переменных |
Объект есть, но нет ключа, названного в valueFrom |
Задание завершается ошибкой DependencyInvalid |
Объект есть, ключа нет, у селектора optional: true |
Переменная отсутствует |
Для несуществующего ключа ссылка на переменную не формируется, поэтому в плейбуке продолжают работать конструкции when: x is defined и фильтр default().
Поле optional есть у источников переменных и отсутствует у полей connection.secretRef и playbook.configMapRef, поскольку задание без учётных данных и без плейбука выполнить нельзя.
Запрещённые имена переменных
Именем переменной является идентификатор Ansible, соответствующий шаблону [A-Za-z_][A-Za-z0-9_]*. Например, выражение {{ app-role }} Ansible трактует не как обращение к переменной, а как вычитание. Имя, не соответствующее шаблону, отклоняет API-сервер, поэтому ошибка возвращается в ответ на команду d8 k apply и содержит имя поля:
The AnsibleRun "pattern-check" is invalid:
* spec.target.hosts[0].vars[0].name: Invalid value: "app-role": spec.target.hosts[0].vars[0].name in body should match '^[A-Za-z_][A-Za-z0-9_]*$'Имена групп подчиняются тому же правилу и отклоняются таким же образом.
Дополнительно запрещены две группы имён.
Первая группа — имена, которые модуль устанавливает самостоятельно. Такие имена отклоняются во всех полях, где объявляется переменная, поскольку для каждого из них в API уже предусмотрено отдельное поле, а два источника одного значения приводят к ошибкам аутентификации, которые сложно диагностировать. Соответствие имён и полей приведено в таблице ниже.
| Переменная | Поле, в котором задаётся значение |
|---|---|
ansible_host |
hosts[].address или status.ipAddress машины |
ansible_user |
Ключ username Secret подключения |
ansible_password, ansible_ssh_pass |
Ключ password |
ansible_ssh_private_key_file |
Ключ ssh-privatekey |
ansible_become_password, ansible_become_pass |
Ключ become-password |
ansible_connection |
spec.connection.type |
Переменная ansible_connection со значением local выполнила бы плейбук внутри пода раннера и сообщила бы об успехе, хотя на целевых машинах ни одна задача не выполнялась.
Остальные переменные с префиксом ansible_ в манифесте задания задавать можно, например ansible_port, ansible_python_interpreter, ansible_become_user, ansible_become_method, ansible_shell_type и ansible_ssh_common_args.
Вторая группа — magic-переменные Ansible, перечисленные в документации Ansible, например groups, hostvars, inventory_hostname, playbook_dir, role_path и omit. Такие имена отклоняются во всех полях задания. Значения magic-переменных Ansible заполняет самостоятельно, поэтому их присвоение либо не даёт эффекта, либо нарушает вычисление выражений. Отдельным случаем является переменная omit, значение которой означает «не передавать этот параметр». Переменная с таким именем изменила бы поведение каждой задачи, где используется omit, и ошибка при этом не возникла бы.
Места, в которых выполняются проверки имён, приведены в таблице ниже.
| Источник имени | Имя не является идентификатором | Имя входит в одну из двух групп выше |
|---|---|---|
spec.playbook.vars, hosts[].vars |
Команда d8 k apply возвращает ошибку |
Задание завершается с причиной InvalidVariables до создания пода |
Документ varsFiles |
Не проверяется, поскольку контроллер не читает файл | Не проверяется |
| Аннотация машины | Задание завершается с причиной InvalidInventoryAnnotations |
Задание завершается с той же причиной. Дополнительно запрещены все переменные с префиксом ansible_, кроме ansible_port |
Если проверка по третьему столбцу таблицы не пройдена, задание завершается до создания пода, а в сообщении статуса указаны поле и причина отказа:
status:
phase: Error
message: 'The variable "omit" in spec.playbook.vars[0] cannot be used: it shadows an Ansible magic variable, which Ansible fills in itself.'
conditions:
- type: Completed
status: "False"
reason: InvalidVariablesДля имени, которое модуль устанавливает самостоятельно, сообщение указывает на поле, в котором следует задавать значение:
The variable "ansible_user" in spec.playbook.vars[0] cannot be used: the module sets it
itself, from the username key of the connection Secret.Запуск по расписанию
Расписание выполняет плейбук по времени так же, как ресурс CronJob запускает объекты Job. На каждый тик cron создаётся отдельное задание со своим статусом. Расписание описывается ресурсом AnsibleRunSchedule:
apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRunSchedule
metadata:
name: nightly-hardening
namespace: demo
spec:
schedule: "0 4 * * *"
timeZone: "Europe/Moscow" # по умолчанию UTC
concurrencyPolicy: Forbid # Forbid (по умолчанию) | Allow | Replace
successfulRunsHistoryLimit: 3
failedRunsHistoryLimit: 1
startingDeadlineSeconds: 300 # тик, пропущенный дольше этого, отбрасывается
suspend: false
template: # AnsibleRun, создаваемый на каждый тик
spec:
target: { type: VirtualMachines, virtualMachines: { selector: { matchLabels: { role: worker } } } }
connection: { secretRef: { name: ssh-creds } }
playbook: { type: Git, git: { url: https://github.com/example-org/infra, path: playbooks/site.yml } }Работа расписания подчиняется следующим правилам:
- Имя задания складывается из имени расписания и времени тика в формате Unix, например
nightly-hardening-1735660800. Старые задания удаляются по лимитам истории, а вместе с расписанием удаляются все его задания. - Спецификация расписания, включая шаблон задания, редактируется, в отличие от неизменяемой спецификации отдельного AnsibleRun. Изменения действуют со следующего тика, а текущее задание продолжает работу по прежнему шаблону.
- Настройка
concurrencyPolicy: Forbidпропускает тик, пока предыдущее задание активно. Пропущенные тики не выполняются задним числом. - Настройка
suspend: trueприостанавливает создание новых заданий. Уже созданные задания при этом продолжают работу. - Некорректное расписание или часовой пояс отражаются в условии
Readyс причинойInvalidSchedule. Задания по такому расписанию не создаются, пока ошибка не исправлена.
Результат задания
Задание записывает в свой статус фазу выполнения, счётчики задач по каждому хосту и список задач, завершившихся с ошибкой.
Для просмотра результата используются следующие команды:
d8 k get ansibleruns -n demo
d8 k get ansiblerun configure-vm -n demo -o yamlstatus:
phase: PlaybookSucceeded
message: Run completed successfully # при ошибке — ещё и имя пода с полным выводом
hosts: # по записи на хост, как в PLAY RECAP
- name: demo-vm-01
address: 10.66.10.2
ok: 3
changed: 1
rescued: 1 # ошибки, обработанные блоком rescue
ignored: 0 # ошибки под ignore_errors
summary: { total: 1, successful: 1, failed: 0, unreachable: 0, skipped: 0 }
failures: [] # задачи с ошибкой и их вывод, до 20
podRef: { name: d8a-ar-configure-vm-x7k2p, namespace: demo }
playbookCommit: 82b6d90a… # только для источника Git
conditions:
- type: Completed
status: "True"
reason: PlaybookSucceededЗадание может находиться в одной из фаз, которые приведены в таблице ниже.
| Фаза | Значение |
|---|---|
Pending |
Выполняется проверка зависимостей и целей задания |
Running |
Под раннера создан. Загружается образ, выполняются init-контейнеры, затем плейбук |
PlaybookSucceeded |
Плейбук выполнен успешно, под раннера удалён |
PlaybookFailed |
Плейбук выполнялся и сообщил об ошибках задач или о недоступных хостах |
Error |
Выполнение плейбука не началось, поэтому вывод Ansible отсутствует |
Терминальная фаза показывает, выполнялся ли плейбук. От этого зависит, где искать причину ошибки — в задачах плейбука или в подготовке задания.
Ошибки, которые плейбук обработал самостоятельно с помощью блока rescue или параметра ignore_errors, попадают в счётчики rescued и ignored. Плейбук при этом остаётся успешным, а список status.failures — пустым. В логе Ansible такие ошибки по-прежнему выводятся с префиксом fatal:, поэтому отслеживать их следует по перечисленным счётчикам.
Диагностика
Диагностику задания следует начинать с фазы выполнения и причины в условии Completed. Поле status.message содержит тот же текст, а при ошибке дополнительно указывает под раннера, в логах которого находится полный вывод плейбука.
Причины, по которым выполнение задания не началось, приведены в таблице ниже. Пода раннера в этих случаях нет, поэтому исправлять требуется сам манифест задания.
| Причина | Описание | Действия |
|---|---|---|
DependencyMissing / DependencyInvalid |
Нет Secret или ConfigMap, либо в объекте нет названного ключа — учётных данных подключения, доступа к Git, ключа valueFrom переменной |
В сообщении названо, чего именно |
PlaybookConfigMapMissing / PlaybookConfigMapInvalid |
Нет ConfigMap с плейбуком или нужного ключа в нём | Создайте; отсутствующий ConfigMap оставляет задание в Pending |
InvalidSelector |
Селектор целей пуст или некорректен | Задайте лейблы или имена явно |
InvalidInventoryAnnotations |
Недопустимое имя переменной или группы на машине | В сообщении названы машина и аннотация |
InvalidVariables |
Переменную из vars или hosts[].vars использовать нельзя: непригодное имя, запись с обоими или ни одним из value и valueFrom, объект с пустым именем, два имени, требующие одной переменной окружения |
В сообщении названы поле и переменная; правила — в разделе Запрещённые имена переменных |
VarsSourceNotFound |
Secret или ConfigMap с переменными не существует. Не терминальная причина: задание ждёт в Pending |
Создайте объект либо пометьте источник optional: true, как описано в разделе Отсутствующий источник переменных |
NoEligibleTargets |
Ни одна цель не подошла | status.skippedHosts объясняет по каждой |
NetworkNotFound |
Такой Network или ClusterNetwork нет | Проверьте имя и тип; проектная сеть видна только в своём неймспейсе |
NetworkWithoutIPAM |
У сети нет пула адресов | Привяжите пул к сети либо задайте цели типа Hosts |
GitCloneFailed |
Не удалось загрузить репозиторий | d8 k logs <POD_NAME> -c git-clone |
GalaxyInstallFailed |
Не удалось установить содержимое Galaxy | d8 k logs <POD_NAME> -c galaxy-install |
Машины, которые задание пропустило, перечислены в списке status.skippedHosts, а выполнение плейбука продолжается на остальных машинах. Возможные причины пропуска приведены в таблице ниже.
Причина в skippedHosts |
Описание |
|---|---|
TargetNotReady / TargetNotRunning |
Машина запускается (Pending, Starting) или не работает вовсе (Stopped, Terminating, Failed) |
NoAddress |
У машины пока нет адреса в основной сети |
NetworkNotAttached |
Сеть, указанная в задании, к этой машине не подключена |
NoAddressInNetwork |
Сеть подключена, но адреса в ней пока нет |
TargetNotFound |
Имени из names не существует |
TargetBusy |
Машину использует другое активное задание (его имя — в busyBy) |
Причины, по которым завершилось задание, выполнение которого началось, приведены в таблице ниже.
| Причина | Описание | Действия |
|---|---|---|
PlaybookFailed |
Задачи завершились с ошибкой или хосты оказались недоступны | Просмотрите список status.failures, затем логи пода |
AnsibleRunnerPodFailed |
Под раннера завершился, не оставив результата, пригодного для разбора | Просмотрите логи пода. Под при этом сохраняется |
RunFailed |
Задание завершилось с ошибкой без более точной причины | Просмотрите логи пода |
Часть проблем не имеет отдельной причины в статусе задания. Такие симптомы и порядок их разбора приведены в таблице ниже.
| Симптом | Порядок разбора |
|---|---|
| Задача получает строку там, где ожидался список или число | Значение передано через valueFrom, а в отдельном ключе хранится строка, как описано в разделе Тип значения из отдельного ключа. Набор значений со структурой следует передавать файлом |
| Переменная, заданная в задании, не влияет на выполнение | То же имя задано другим значением на 22-м уровне приоритета, либо плейбук читает другое имя. Проверьте приоритет значений |
Хосты в состоянии unreachable |
Проверьте подключение по SSH: пользователя и ключ или пароль в Secret, а также доступность гостевой ОС по этому адресу. Для дополнительной сети проверьте, настроил ли DHCP-клиент интерфейс |
Фаза PlaybookFailed |
Список status.failures указывает хост, задачу и текст ошибки, а полный вывод находится в логах пода |
Задание долго остаётся в фазе Pending |
Условие DependenciesReady указывает объект, которого ожидает задание |
Под раннера остаётся в состоянии ContainerCreating |
Не удалось настроить сеть задания. Команда d8 k describe pod покажет причину, полученную от модуля sdn |
Для просмотра логов пода раннера используется следующая команда:
d8 k logs -n demo -l ansible.deckhouse.io/owner=configure-vmВ список status.failures попадает до 20 ошибок задач. Если ошибок больше, полный вывод остаётся в логах пода, который сохраняется до удаления объекта AnsibleRun. Успешное задание сохраняет весь результат в статусе, поэтому его под удаляется сразу.
Такой порядок очистки следует учитывать в двух случаях:
- Полный вывод Ansible успешного задания исчезает вместе с его подом. Лог, который нужно сохранить, собирает хранилище логов.
- Неуспешное задание в дополнительной сети занимает свой адрес из пула до удаления объекта AnsibleRun. Неудачные задания, оставленные в неймспейсе, постепенно расходуют пул адресов.
Сбор логов в хранилище
Полный вывод утилиты ansible-playbook остаётся в поде раннера, а под успешного задания удаляется через несколько секунд после завершения. Чтобы вывод сохранялся, настройте его сбор модулем log-shipper. В этом случае лог остаётся доступным и после удаления пода.
Поды раннера не являются системными и создаются в неймспейсе задания, поэтому готовые конфигурации сбора логов платформы их не отбирают. Такие конфигурации охватывают только неймспейсы с лейблом heritage: deckhouse, поэтому для сбора логов задания требуется собственная конфигурация.
Настройка сбора в проекте
Ресурс PodLoggingConfig является проектным, поэтому его может создать владелец проекта. Для отбора подов используется лейбл, который контроллер устанавливает на каждый под раннера. Имя пода для отбора не подходит, поскольку складывается из имени задания и хеша и уникально для каждого задания, например d8a-ar-configure-vm-x7k2p.
apiVersion: deckhouse.io/v1alpha1
kind: PodLoggingConfig
metadata:
name: ansible-runners
namespace: demo
spec:
labelSelector:
matchLabels:
app.kubernetes.io/managed-by: ansible-controller
clusterDestinationRefs:
- d8-lokiПоле clusterDestinationRefs ссылается на приёмник логов, уже созданный в кластере. Список доступных приёмников можно получить следующей командой:
d8 k get clusterlogdestinationsНастройка сбора в кластере
Приёмник (ClusterLogDestination) и сбор по всем проектам (ClusterLoggingConfig) являются кластерными ресурсами, поэтому создать их может только администратор кластера. Такая конфигурация настраивается один раз на весь кластер и не требует отдельной конфигурации в каждом проекте:
apiVersion: deckhouse.io/v1alpha2
kind: ClusterLoggingConfig
metadata:
name: ansible-runner-logs
spec:
type: KubernetesPods
kubernetesPods:
labelSelector:
matchLabels:
app.kubernetes.io/managed-by: ansible-controller
destinationRefs:
- d8-lokiПриёмник для длительного хранения логов создаётся администратором кластера. Встроенный в платформу Loki рассчитан на оперативный разбор логов, а не на их архивное хранение. Срок хранения в нём задаётся параметром retentionPeriodHours модуля loki, а место освобождается по мере заполнения диска, поэтому фактическая глубина хранения зависит от объёма записи.
Если логи заданий требуется хранить месяцами, создайте отдельный ресурс ClusterLogDestination для внешнего хранилища. Модуль log-shipper поддерживает приёмники Loki, Elasticsearch, Logstash, Vector, Kafka, Splunk и Socket. Один и тот же сбор логов можно направить сразу в несколько приёмников, перечислив их в поле destinationRefs.
Поиск лога задания в Loki
После настройки сбора вывод задания попадает в поток с лейблами неймспейса, пода и контейнера, где контейнер имеет имя ansible. Ниже приведены запросы LogQL для типовых задач:
# все задания в неймспейсе
{namespace="demo", container="ansible"}
# одно задание
{namespace="demo", pod=~"d8a-ar-configure-vm-.+"}
# все задания, созданные одним расписанием
{namespace="demo", pod=~"d8a-ar-nightly-baseline-.+"}
# только итоги: строка PLAY RECAP несёт ok/changed/failed по каждому хосту
{namespace="demo", container="ansible"} |= "PLAY RECAP"
# задачи с ошибкой и недоступные хосты
{namespace="demo", container="ansible"} |~ "fatal:|UNREACHABLE"
# сколько заданий падало за сутки
sum by (pod) (count_over_time({container="ansible"} |= "fatal:" [24h]))Задание, выполнившееся за несколько секунд, также собирается целиком, вместе со строкой PLAY RECAP, поскольку сбор читает файл лога до конца и после удаления пода. Конец вывода может не попасть в хранилище только в том случае, если приёмник логов недоступен именно в эти секунды.
Числовые пределы
Числовые пределы одного задания приведены в таблице ниже.
| Ограничение | Значение |
|---|---|
| Встроенный плейбук | 64 КБ |
| Хосты, заданные адресом | 1000 на задание; 64 переменные и 32 группы на хост |
| Теги | По 64 записи в spec.playbook.tags и spec.playbook.skipTags |
| Переменные | 64 записи в spec.playbook.vars; 8 файлов в spec.playbook.varsFiles |
status.failures |
До 20 ошибок задач, остальные ошибки остаются в логах пода |
| Дополнительные сети | Только IPv4 |