Логотип

Ansible. Принятие решений в Ansible

Начало работы с Ansible

В этой статье вы узнаете, как добавить навыки принятия решений в свои сценарии Ansible.

Вы научитесь:

  • Используйте операторы when для условного выполнения задач.
  • Используйте инструкции блока для реализации обработки исключений.
  • Используйте обработчики Ansible для запуска задач при изменении.

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

В этой статье используется та же настройка, которая была упомянута в первой статье: 1 элемент управления Red Hat, 3 узла CentOS и 1 узел Ubuntu.

 

Выбор того, когда запускать задачи

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

 

Использование когда с фактами

Вы можете использовать условные выражения when для запуска задачи, только если выполняется определенное условие. Для демонстрации создайте новую книгу воспроизведения с именем ubuntu-server.yml со следующим содержимым:

[destroyer@andreyex]$ cat ubuntu-server.yml 
---
- name: Using when with facts 
  hosts: all
  tasks:
    - name: Detect Ubuntu Servers
      debug:
        msg: "This is an Ubuntu Server."
      when:  ansible_facts['distribution'] == "Ubuntu"

 

Теперь запустите playbook:

[destroyer@andreyex]$ ansible-playbook ubuntu-servers.yml 

PLAY [Using when with facts] *******************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node1]
ok: [node3]
ok: [node2]

TASK [Detect Ubuntu Servers] ***************************************************
skipping: [node1]
skipping: [node2]
skipping: [node3]
ok: [node4] => {
    "msg": "Это сервер Ubuntu."
}

PLAY RECAP *********************************************************************
node1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    

node2                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    
node3                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    
node4                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0

 

Обратите внимание, как мы использовали факт Ansible ansible_facts[‘distribution’] в условии when, чтобы проверить, на каких узлах работает Ubuntu. Также обратите внимание, что вам не нужно заключать переменные в фигурные скобки при использовании условных выражений when.

В выходных данных playbook обратите внимание, как TASK [Detect Ubuntu Servers] пропущены первые три узла, поскольку все они работают под CentOS и работают только на node4, поскольку он работает под управлением Ubuntu.

 

Использование when с регистрами

Вы также можете использовать условные выражения с зарегистрированными переменными. Например, следующая книга centos-servers.yml покажет, на каких узлах работает CentOS:

[destroyer@andreyex]$ cat centos-servers.yml 
---
- name: Using when with registers
  hosts: all
  tasks:
    - name: Save the contents of /etc/os-release
      command: cat /etc/os-release
      register: os_release

    - name: Detect CentOS Servers
      debug:
        msg: "Запуск CentOS..."
      when: os_release.stdout.find('CentOS') != -1

 

Плейбук сначала начинается с сохранения содержимого файла /etc/os-release в регистр os_release . Затем вторая задача отображает сообщение «Запуск CentOS…», только если слово «CentOS» встречается в   стандартном выводе os_release .

Идите вперед и запустите playbook:

[destroyer@andreyex]$ ansible-playbook centos-servers.yml 

PLAY [Using when with registers] ***********************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node1]
ok: [node3]
ok: [node2]

TASK [Save the contents of /etc/os-release] ************************************
changed: [node4]
changed: [node1]
changed: [node2]
changed: [node3]

TASK [Detect CentOS Servers] ***************************************************
ok: [node1] => {
    "msg": "Запуск CentOS..."
}
ok: [node2] => {
    "msg": "Запуск CentOS..."
}
ok: [node3] => {
    "msg": "Запуск CentOS..."
}
skipping: [node4]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node4                      : ok=2    changed=1    unreachable=0    failed=0    skipped=1

 

Обратите внимание, как работает TASK [Detect CentOS Servers] только на первых трех узлах и пропускается node4 (Ubuntu).

 

Тестирование нескольких условий с помощью when

Вы также можете проверить несколько условий одновременно, используя логические операторы. Например, следующая перезагрузка-centos8.yml Playbook использует логический и оператора для перезагрузки серверов, работающих под управлением CentOS версии 8:

[destroyer@andreyex]$ cat reboot-centos8.yml 
---
- name: Reboot Servers
  hosts: all
  tasks:
    - name: Reboot CentOS 8 servers
      reboot: 
        msg: "Сервер перезагружается..."
      when: ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "8"

 

Вы также можете использовать логический оператор или для запуска задачи, если выполняется какое-либо из условий. Например, следующая задача будет перезагружать серверы , на которых работает CentOS или RedHat :

  tasks:
    - name: Reboot CentOS and RedHat Servers 
      reboot: 
        msg: "Сервер перезагружается..."
      when: ansible_facts['distribution'] == "CentOS" or ansible_facts['distribution'] == "RedHat"

 

Использование when с петлями

Если вы объедините условный оператор when с циклом, Ansible проверит условие для каждого элемента в цикле отдельно.

Например, следующий сценарий print-even.yml напечатает все четные числа в диапазоне (1,11):

[destroyer@andreyex]$ cat print-even.yml
---
- name: Print Some Numbers
  hosts: node1
  tasks:
    - name: Print Even Numbers
      debug:
        msg: Number {{ item }} is Even.
      loop: "{{ range(1,11) | list }}"
      when: item % 2 == 0

 

Продолжайте и запустите playbook, чтобы увидеть список всех четных чисел в диапазоне (1,11):

[destroyer@andreyex]$ ansible-playbook print-even.yml

PLAY [Print Some Numbers] **********************************

TASK [Gathering Facts] ****************************
ok: [node1]

TASK [Print Even Numbers] ******************************
skipping: [node1] => (item=1) 
ok: [node1] => (item=2) => {
    "msg": "Число 2 четное."
}
skipping: [node1] => (item=3) 
ok: [node1] => (item=4) => {
    "msg": "Число 4 четное."
}
skipping: [node1] => (item=5) 
ok: [node1] => (item=6) => {
    "msg": "Число 6 четное."
}
skipping: [node1] => (item=7) 
ok: [node1] => (item=8) => {
    "msg": "Число 8 четное."
}
skipping: [node1] => (item=9) 
ok: [node1] => (item=10) => {
    "msg": "Число 10 четное."
}

PLAY RECAP ***********************************
node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0

 

Читать  Ansible. Циклы

Использование when с переменными

Вы также можете использовать условные операторы when с вашими собственными определенными переменными. Помните, что условные выражения требуют логических входных данных; то есть, для срабатывания условия тест должен быть истинным, поэтому вам нужно использовать фильтр bool с небулевыми переменными.

Чтобы продемонстрировать это, взгляните на следующий сборник сценариев isfree.yml :

[destroyer@andreyex]$ cat isfree.yml 
---
- name: 
  hosts: node1
  vars:
    weekend: true
    on_call: "no"
  tasks:
    - name: Run if "weekend" is true and "on_call" is false
      debug:
        msg: "Вы свободны!"
      when: weekend and not on_call | bool

 

Обратите внимание, что здесь я использовал фильтр bool для преобразования значения on_call в его логический эквивалент (no -> false).

Кроме того, вы должны знать, что not false — это истина, и поэтому в этом случае все условие будет оцениваться как true; ты свободен!

Вы также можете проверить, установлена ли переменная или нет; например, следующая задача будет выполняться, только если определена переменная car :

tasks:
    - name: Run only if you got a car
      debug:
        msg: "Давай отправимся в путешествие..."
      when: car is defined

 

Следующая задача использует сбой модуля потерпеть неудачу , если ключи переменная не определена:

tasks:
    - name: Fail if you got no keys
      fail:
        msg: "Эта игра требует некоторых ключей"
      when: keys is undefined

 

Обработка исключений с помощью блоков

Поговорим об обработке исключений.

 

Группировка задач с блоками

Вы можете использовать блоки для группировки связанных задач. Для демонстрации взгляните на следующую книгу install-apache.yml :

[destroyer@andreyex]$ cat install-apache.yml 
---
- name: Install and start Apache Play
  hosts: webservers
  tasks:
    - name: Install and start Apache
      block:
         - name: Install httpd
           yum:
             name: httpd
             state: latest

         - name: Start and enable httpd
           service:
             name: httpd
             state: started
             enabled: yes

    - name: This task is outside the block
      debug:
        msg: "Сейчас я нахожусь за пределами блока..."

 

Playbook запускается на хостах группы веб-серверов и имеет один блок с именем Установить и запустить Apache, который включает в себя две задачи:

  1. Установить httpd
  2. Запустить и включить httpd

Первая задача Install httpd использует модуль yum для установки пакета httpd apache. Вторая задача использует служебный модуль для запуска и разрешает запуск httpd при загрузке.

Обратите внимание, что в playbook есть третья задача, которая не принадлежит блоку Install and start Apache.

Теперь запустите playbook, чтобы установить и запустить httpd на узлах веб-серверов:

[destroyer@andreyex]$ ansible-playbook install-apache.yml 

PLAY [Install and start Apache Play] *******************************************

TASK [Gathering Facts] *********************************************************
ok: [node3]
ok: [node2]

TASK [Install httpd] ***********************************************************
changed: [node2]
changed: [node3]

TASK [Start and enable httpd] **************************************************
changed: [node3]
changed: [node2]

TASK [This task is outside the block] ******************************************
ok: [node2] => {
    "msg": "Сейчас я нахожусь за пределами квартала..."
}
ok: [node3] => {
    "msg": "Сейчас я нахожусь за пределами квартала..."
}

PLAY RECAP *********************************************************************
node2                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0     
node3                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0

 

Вы также можете выполнить специальную команду, чтобы убедиться, что httpd действительно запущен и работает:

[destroyer@andreyex]$ ansible webservers -m command -a "systemctl status httpd"
node3 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2020-11-03 19:35:13 UTC; 1min 37s ago
     Docs: man:httpd.service(8)
 Main PID: 47122 (httpd)
   Status: "Running, listening on: port 80"
    Tasks: 213 (limit: 11935)
   Memory: 25.1M
   CGroup: /system.slice/httpd.service
           ├─47122 /usr/sbin/httpd -DFOREGROUND
           ├─47123 /usr/sbin/httpd -DFOREGROUND
           ├─47124 /usr/sbin/httpd -DFOREGROUND
           ├─47125 /usr/sbin/httpd -DFOREGROUND
           └─47126 /usr/sbin/httpd -DFOREGROUND

Nov 03 19:35:13 node3 systemd[1]: Starting The Apache HTTP Server...
Nov 03 19:35:13 node3 systemd[1]: Started The Apache HTTP Server.
Nov 03 19:35:13 node3 httpd[47122]: Server configured, listening on: port 80
node2 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2020-11-03 19:35:13 UTC; 1min 37s ago
     Docs: man:httpd.service(8)
 Main PID: 43695 (httpd)
   Status: "Running, listening on: port 80"
    Tasks: 213 (limit: 11935)
   Memory: 25.1M
   CGroup: /system.slice/httpd.service
           ├─43695 /usr/sbin/httpd -DFOREGROUND
           ├─43696 /usr/sbin/httpd -DFOREGROUND
           ├─43697 /usr/sbin/httpd -DFOREGROUND
           ├─43698 /usr/sbin/httpd -DFOREGROUND
           └─43699 /usr/sbin/httpd -DFOREGROUND

Nov 03 19:35:13 node2 systemd[1]: Starting The Apache HTTP Server...
Nov 03 19:35:13 node2 systemd[1]: Started The Apache HTTP Server.
Nov 03 19:35:13 node2 httpd[43695]: Server configured, listening on: port 80

 

Обработка сбоев с помощью блоков

Вы также можете использовать блоки для обработки ошибок задач, используя разделы rescue и always. Это очень похоже на обработку исключений в языках программирования, таких как try-catch в Java или try-except в Python.

Читать  Как бесплатно сжать PDF-файл в Linux

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

Для демонстрации давайте взглянем на следующий пример:

tasks:
  - name: Handling error example
    block:
      - name: run a command
        command: uptime

      - name: run a bad command
        command: blabla

      - name: This task will not run
        debug:
          msg: "Я не запускаю потому что вышеприведенная задача провалилась."

    rescue: 
      - name: Runs when the block failed
        debug:
          msg: "Блок не удался; давайте попробуем исправить это здесь..."

 

Обратите внимание, как вторая задача в блоке run a bad command генерирует ошибку, и, в свою очередь, третья задача в блоке никогда не получает возможности запустить. Задачи внутри спасательной секции будут работать , потому что вторая задача в блоке не удалась.

Вы также можете использовать ignore_errors: yes, чтобы гарантировать, что Ansible продолжит выполнение задач в playbook, даже если задача не удалась:

tasks:
  - name: Handling error example
    block:
      - name: run a command
        command: uptime

      - name: run a bad command
        command: blabla
        ignore_errors: yes

      - name: This task will run
        debug:
          msg: "Запуск потому что вышеуказанные ошибки задачи были проигнорированы."

    rescue: 
      - name: This will not run
        debug:
          msg: "Ошибки были проигнорированы! ... не собирается запускаться."

 

Обратите внимание, что в этом примере вы проигнорировали ошибки во второй задаче в блоке run a bad command, и поэтому третья задача была запущена. Кроме того, спасательный раздел не будет работать , как вы игнорировали ошибку во второй задаче в блоке.

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

Чтобы продемонстрировать это, взгляните на следующую книгу воспроизведения handle-errors.yml, в которой есть все три раздела блока (block, rescue, always):

[destroyer@andreyex]$ cat handle-errors.yml 
---
- name: Handling Errors with Blocks
  hosts: node1
  tasks:
    - name: Handling Errors Example
      block:
        - name: run a command
          command: uptime

        - name: run a bad command
          command: blabla

        - name: This task will not run
          debug:
            msg: "Я не запускаюсь потому что вышеприведенная задача провалилась!"

      rescue:
        - name: Runs when the block fails
          debug:
            msg: "Блок провалился! давайте попробуем исправить это здесь..."

      always:
        - name: This will always run
          debug:
            msg: "Независимо от того, провалился блок или нет ... Я всегда буду запускаться.!"

 

Идите вперед и запустите playbook:

[destroyer@andreyex]$ ansible-playbook handle-errors.yml 

PLAY [Handling Errors with Blocks] *********************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [run a command] ***********************************************************
changed: [node1]

TASK [run a bad command] *******************************************************
fatal: [node1]: FAILED! => {"changed": false, "cmd": "blabla", "msg": "[Errno 2] No such file or directory: b'blabla': b'blabla'", "rc": 2}

TASK [Runs when the block fails] ***********************************************
ok: [node1] => {
    "msg": "Block failed! let's try to fix it here ..."
}

TASK [This will always run] ****************************************************
ok: [node1] => {
    "msg": "Whether the block has failed or not ... I will always run!"
}

PLAY RECAP *********************************************************************
node1                      : ok=4    changed=1    unreachable=0    failed=0    skipped=0

 

Как вы видете; спасательный раздел ли работать как 2 — й задача в блоке произошел сбой, и вы не игнорировать ошибки. Кроме того, всегда выполнялся (и будет всегда) раздел всегда.

 

Запуск задач при изменении с помощью обработчиков

Посмотрим, как менять обработчики и запускать задачи.

 

Запуск вашего первого обработчика

Вы можете использовать обработчики для запуска задач при изменении на ваших управляемых узлах. Чтобы продемонстрировать это, взгляните на следующий сборник handler-example.yml:

[destroyer@andreyex]$ cat handler-example.yml 
---
- name: Simple Handler Example
  hosts: node1
  tasks:
    - name: Create engineers group
      group:
        name: engineers
      notify: add destroyer

    - name: Another task in the play
      debug:
        msg: "I am just another task."

  handlers: 
    - name: add destroyer
      user:
        name: destroyer
        groups: engineers
        append: yes

 

Первая задача Create engineers groupсоздает группу инженеров, а также уведомляет обработчика add destroyer.

Давайте запустим сценарий, чтобы увидеть, что произойдет:

[destroyer@andreyex]$ ansible-playbook handler-example.yml 

PLAY [Simple Handler Example] **************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Create engineers group] **************************************************
changed: [node1]

TASK [Another task in the play] ************************************************
ok: [node1] => {
    "msg": "I am just another task."
}

RUNNING HANDLER [add destroyer] ***************************************************
changed: [node1]

PLAY RECAP *********************************************************************
node1                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0

 

Обратите внимание, что создание инженеров вызвало изменение на node1 и в результате запустило add destroyerобработчик.

Читать  Автоматизировать удаленные команды Linux из терминала Windows, используя Plink.

Вы также можете запустить быструю специальную команду, чтобы убедиться, что пользователь destroyer действительно является членом группы инженеров:

[destroyer@andreyex]$ ansible node1 -m command -a "id destroyer"
node1 | CHANGED | rc=0 >>
uid=1000(destroyer) gid=1000(destroyer) groups=1000(destroyer),4(adm),190(systemd-journal),1004(engineers)

 

Плейбуки и модули Ansible являются идемпотентными, что означает, что если на управляемых узлах произошло изменение конфигурации; он больше не повторится!

Чтобы полностью понять концепцию идемпотентности Ansible; запустите playbook handler-example.yml еще раз:

[destroyer@andreyex]$ ansible-playbook handler-example.yml 

PLAY [Simple Handler Example] **************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Create engineers group] **************************************************
ok: [node1]

TASK [Another task in the play] ************************************************
ok: [node1] => {
    "msg": "I am just another task."
}

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0

 

Как вы видете; на этот раз Create engineers group задача не вызвала и не сообщила об изменении, потому что группа инженеров уже существует на node1 и, как результат; обработчик add destroyer не запустился.

 

Контроль, когда сообщать об изменении

Вы можете использовать ключевое слово changed_when, чтобы указать, когда задача должна сообщать об изменении. Чтобы продемонстрировать это, взгляните на следующий сценарий control-change.yml:

[destroyer@andreyex]$ cat control-change.yml 
---
- name: Control Change
  hosts: node1
  tasks:
    - name: Run the date command
      command: date
      notify: handler1

    - name: Run the uptime command
      command: uptime

  handlers:
     - name: handler1
       debug:
         msg: "I can handle dates"

 

Обратите внимание, как первая задача Run the date commandзапускает handler1. А теперь запустите playbook:

[destroyer@andreyex]$ ansible-playbook control-change.yml 

PLAY [Control Change] **********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Run the date command] ****************************************************
changed: [node1]

TASK [Run the uptime command] **************************************************
changed: [node1]

RUNNING HANDLER [handler1] *****************************************************
ok: [node1] => {
    "msg": "I can handle dates"
}

PLAY RECAP *********************************************************************
node1                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0

 

Обе задачи Run the date command и Run the uptime command зарегистрированные изменения, и обработчик handler1 был запущен. Вы можете утверждать, что выполнение команд date и uptime на самом деле ничего не меняет на управляемом узле, и вы совершенно правы!

Теперь давайте отредактируем playbook, чтобы задача не сообщала об изменениях:

[destroyer@andreyex]$ cat control-change.yml 
---
- name: Control Change
  hosts: node1
  tasks:
    - name: Run the date command
      command: date
      notify: handler1
      changed_when: false

    - name: Run the uptime command
      command: uptime

  handlers:
     - name: handler1
       debug:
         msg: "I can handle dates"

 

Теперь снова запустите playbook:

[destroyer@andreyex]$ ansible-playbook control-change.yml 

PLAY [Control Change] **********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Run the date command] ****************************************************
ok: [node1]

TASK [Run the uptime command] **************************************************
changed: [node1]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0

 

Как видите, на этот раз задача не сообщила об изменении, и в результате handler1 не был запущен.

 

Настройка сервисов с обработчиками

Обработчики особенно полезны, когда вы редактируете конфигурации служб с помощью Ansible. Это потому, что вы хотите перезапустить службу только при изменении ее конфигурации службы.

Чтобы продемонстрировать это, взгляните на следующую инструкцию configure-ssh.yml :

[destroyer@andreyex]$ cat configure-ssh.yml 
---
- name: Configure SSH 
  hosts: all
  tasks:
     - name: Edit SSH Configuration
       blockinfile:
         path: /etc/ssh/sshd_config
         block: |
            MaxAuthTries 4
            Banner /etc/motd
            X11Forwarding no
       notify: restart ssh

  handlers: 
    - name: restart ssh
      service:
        name: sshd
        state: restarted

 

Обратите внимание, что мы использовали модуль blockinfile для вставки нескольких строк текста в файл конфигурации /etc/ssh/sshd_config. Задача также вызывает обработчик restart ssh по изменению.

Идите вперед и запустите playbook:

[destroyer@andreyex]$ ansible-playbook configure-ssh.yml 

PLAY [Configure SSH] ***********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node3]
ok: [node1]
ok: [node2]

TASK [Edit SSH Configuration] **************************************************
changed: [node4]
changed: [node2]
changed: [node3]
changed: [node1]

RUNNING HANDLER [restart ssh] **************************************************
changed: [node4]
changed: [node3]
changed: [node2]
changed: [node1]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    
node2                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    
node4                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0

 

Все хорошо! Теперь давайте быстро взглянем на последние несколько строк в файле/etc/ssh/sshd_config:

[destroyer@andreyex]$ ansible node1 -m command -a "tail -5 /etc/ssh/sshd_config"
node1 | CHANGED | rc=0 >>
# BEGIN ANSIBLE MANAGED BLOCK
MaxAuthTries 4
Banner /etc/motd
X11Forwarding no
# END ANSIBLE MANAGED BLOCK

 

Удивительно! Точно так, как вы ожидали. Имейте в виду, что если вы повторно запустите playbook configure-ssh.yml, Ansible не будет редактировать (или добавлять) файл /etc/ssh/sshd_config. Вы можете попробовать это сами.

Мы также рекомендуем вам взглянуть на страницы документации blockinfile и lineinfile, чтобы понять различия и использование каждого модуля:

[destroyer@andreyex]$ ansible-doc blockinfile
[destroyer@andreyex]$ ansible-doc lineinfile

 

Хорошо! На этом мы подошли к концу нашей статьи по принятию решений в Ansible.

Редактор: AndreyEx

Рейтинг: 4.5 (116 голосов)
Если статья понравилась, то поделитесь ей в социальных сетях:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

Это может быть вам интересно


Загрузка...

Спасибо!

Теперь редакторы в курсе.

Прокрутить страницу до начала