mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 09:41:36 +00:00
[ansible/en] Fix build error and styling (#3399)
This commit is contained in:
parent
4918e01479
commit
3b70e25a03
@ -4,17 +4,78 @@ tool: ansible
|
|||||||
contributors:
|
contributors:
|
||||||
- ["Jakub Muszynski" , "http://github.com/sirkubax"]
|
- ["Jakub Muszynski" , "http://github.com/sirkubax"]
|
||||||
- ["Pat Myron" , "https://github.com/patmyron"]
|
- ["Pat Myron" , "https://github.com/patmyron"]
|
||||||
|
- ["Divay Prakash", "https://github.com/divayprakash"]
|
||||||
filename: LearnAnsible.txt
|
filename: LearnAnsible.txt
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
"{{ Ansible }}" is an orchestration tool written in Python.
|
"{{ Ansible }}" is an orchestration tool written in Python.
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Ansible is (one of many) orchestration tools. It allows you to control your
|
||||||
|
environment (infrastructure and code) and automate the manual tasks.
|
||||||
|
'You can think as simple as writing in bash with python API,
|
||||||
|
Of course the rabbit hole is way deeper.'
|
||||||
|
|
||||||
|
Ansible has great integration with multiple operating systems (even Windows)
|
||||||
|
and some hardware (switches, Firewalls, etc). It has multiple tools that
|
||||||
|
integrate with the cloud providers. Almost every noteworthy cloud provider is
|
||||||
|
present in the ecosystem (AWS, Azure, Google, DigitalOcean, OVH, etc...).
|
||||||
|
|
||||||
|
But ansible is way more! It provides an execution plans, an API, library,
|
||||||
|
callbacks, not forget to mention - COMMUNITY! and great support by developers!
|
||||||
|
|
||||||
|
### Main pros and cons
|
||||||
|
|
||||||
|
#### Pros
|
||||||
|
|
||||||
|
* It is an agent-less tools In most scenarios, it use ssh as a transport layer.
|
||||||
|
In some way you can use it as 'bash on steroids'.
|
||||||
|
* It is very easy to start. If you are familiar with ssh concept - you already
|
||||||
|
know Ansible (ALMOST).
|
||||||
|
* It executes 'as is' - other tools (salt, puppet, chef - might execute in
|
||||||
|
different scenario than you would expect)
|
||||||
|
* Documentation is at the world-class standard!
|
||||||
|
* The community (github, stackOverflow) would help you very fast.
|
||||||
|
* Writing your own modules and extensions is fairly easy.
|
||||||
|
* Ansible AWX is the open source version of Ansible Tower we have been waiting
|
||||||
|
for, which provides an excellent UI.
|
||||||
|
|
||||||
|
#### Cons
|
||||||
|
|
||||||
|
* It is an agent-less tool - every agent consumes up to 16MB ram - in some
|
||||||
|
environments, it may be noticable amount.
|
||||||
|
* It is agent-less - you have to verify your environment consistency
|
||||||
|
'on-demand' - there is no built-in mechanism that would warn you about some
|
||||||
|
change automatically (this can be achieved with reasonable effort)
|
||||||
|
* Official GUI Tool (web inferface) - Ansible Tower - is great, but it is
|
||||||
|
expensive.
|
||||||
|
* There is no 'small enterprice' payment plan, however Ansible AWX is the free
|
||||||
|
open source version we were all waiting for.
|
||||||
|
|
||||||
|
#### Neutral
|
||||||
|
|
||||||
|
Migration - Ansible <-> Salt is fairly easy - so if you would need an
|
||||||
|
event-driven agent environment - it would be a good choice to start quick with
|
||||||
|
Ansible, and convert to Salt when needed.
|
||||||
|
|
||||||
|
#### Some concepts
|
||||||
|
|
||||||
|
Ansible uses ssh or paramiko as a transport layer. In a way you can imagine
|
||||||
|
that you are using a ssh with API to perform your action. The simplest way is
|
||||||
|
to execute remote command in more controlled way (still using ssh).
|
||||||
|
On the other hand - in advanced scope - you can wrap Ansible (use python Ansible
|
||||||
|
code as a library) with your own Python scrips! This is awesome! It would act a
|
||||||
|
bit like Fabric then.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
An example playbook to install apache and configure log level
|
An example playbook to install apache and configure log level
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
- hosts: apache
|
- hosts: apache
|
||||||
@ -24,39 +85,39 @@ An example playbook to install apache and configure log level
|
|||||||
|
|
||||||
handlers:
|
handlers:
|
||||||
- name: restart apache
|
- name: restart apache
|
||||||
service:
|
service:
|
||||||
name: apache2
|
name: apache2
|
||||||
state: restarted
|
state: restarted
|
||||||
enabled: True
|
enabled: True
|
||||||
notify:
|
notify:
|
||||||
- Wait for instances to listen on port 80
|
- Wait for instances to listen on port 80
|
||||||
become: True
|
become: True
|
||||||
|
|
||||||
- name: reload apache
|
- name: reload apache
|
||||||
service:
|
service:
|
||||||
name: apache2
|
name: apache2
|
||||||
state: reloaded
|
state: reloaded
|
||||||
notify:
|
notify:
|
||||||
- Wait for instances to listen on port 80
|
- Wait for instances to listen on port 80
|
||||||
become: True
|
become: True
|
||||||
|
|
||||||
- name: Wait for instances to listen on port 80
|
- name: Wait for instances to listen on port 80
|
||||||
wait_for:
|
wait_for:
|
||||||
state: started
|
state: started
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 80
|
port: 80
|
||||||
timeout: 15
|
timeout: 15
|
||||||
delay: 5
|
delay: 5
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Update cache
|
- name: Update cache
|
||||||
apt:
|
apt:
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
cache_valid_time: 7200
|
cache_valid_time: 7200
|
||||||
become: True
|
become: True
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
apt:
|
apt:
|
||||||
name={{ item }}
|
name={{ item }}
|
||||||
with_items:
|
with_items:
|
||||||
- apache2
|
- apache2
|
||||||
@ -66,40 +127,43 @@ An example playbook to install apache and configure log level
|
|||||||
become: True
|
become: True
|
||||||
|
|
||||||
- name: Configure apache2 log level
|
- name: Configure apache2 log level
|
||||||
lineinfile:
|
lineinfile:
|
||||||
dest: /etc/apache2/apache2.conf
|
dest: /etc/apache2/apache2.conf
|
||||||
line: "LogLevel {{ apache2_log_level }}"
|
line: "LogLevel {{ apache2_log_level }}"
|
||||||
regexp: "^LogLevel"
|
regexp: "^LogLevel"
|
||||||
notify:
|
notify:
|
||||||
- reload apache
|
- reload apache
|
||||||
become: True
|
become: True
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Universal way
|
# Universal way
|
||||||
$ pip install ansible
|
$ pip install ansible
|
||||||
|
|
||||||
# Debian, Ubuntu
|
# Debian, Ubuntu
|
||||||
$ apt-get install ansible
|
$ apt-get install ansible
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* [Appendix A - How do I install ansible](#infrastructure-as-a-code)
|
* [Appendix A - How do I install ansible](#infrastructure-as-a-code)
|
||||||
* [Additional Reading.](http://docs.ansible.com/ansible/latest/intro_installation.html)
|
* [Additional Reading.](http://docs.ansible.com/ansible/latest/intro_installation.html)
|
||||||
|
|
||||||
### Your first ansible command (shell execution)
|
### Your first ansible command (shell execution)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# This command ping the localhost (defined in default inventory: /etc/ansible/hosts)
|
# Command pings localhost (defined in default inventory: /etc/ansible/hosts)
|
||||||
$ ansible -m ping localhost
|
$ ansible -m ping localhost
|
||||||
# you should see this output
|
# You should see this output
|
||||||
localhost | SUCCESS => {
|
localhost | SUCCESS => {
|
||||||
"changed": false,
|
"changed": false,
|
||||||
"ping": "pong"
|
"ping": "pong"
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shell Commands
|
### Shell Commands
|
||||||
|
|
||||||
There are few commands you should know about
|
There are few commands you should know about
|
||||||
|
|
||||||
* `ansible` (to run modules in CLI)
|
* `ansible` (to run modules in CLI)
|
||||||
@ -109,11 +173,11 @@ There are few commands you should know about
|
|||||||
* and other!
|
* and other!
|
||||||
|
|
||||||
### Module
|
### Module
|
||||||
A program (usually python) that executes, does some work and returns proper JSON output
|
|
||||||
|
|
||||||
This program performs specialized task/action (like manage instances in the cloud, execute shell command).
|
A program (usually python) that executes, does some work and returns proper
|
||||||
|
JSON output. This program performs specialized task/action (like manage
|
||||||
The simplest module is called `ping` - it just returns a JSON with `pong` message.
|
instances in the cloud, execute shell command). The simplest module is called
|
||||||
|
`ping` - it just returns a JSON with `pong` message.
|
||||||
|
|
||||||
Example of modules:
|
Example of modules:
|
||||||
* Module: `ping` - the simplest module that is useful to verify host connectivity
|
* Module: `ping` - the simplest module that is useful to verify host connectivity
|
||||||
@ -126,67 +190,74 @@ $ ansible -m ping all
|
|||||||
$ ansible -m shell -a 'date; whoami' localhost #hostname_or_a_group_name
|
$ ansible -m shell -a 'date; whoami' localhost #hostname_or_a_group_name
|
||||||
```
|
```
|
||||||
|
|
||||||
* Module: `command` - executes a single command that will not be processed through the shell, so variables like $HOME or operands like `|` `;` will not work. The command module is more secure, because it will not be affected by the user’s environment. For more complex command - use shell module.
|
* Module: `command` - executes a single command that will not be processed
|
||||||
|
through the shell, so variables like `$HOME` or operands like ``|` `;`` will not
|
||||||
|
work. The command module is more secure, because it will not be affected by the
|
||||||
|
user’s environment. For more complex commands - use shell module.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ansible -m command -a 'date; whoami' # FAILURE
|
$ ansible -m command -a 'date; whoami' # FAILURE
|
||||||
|
|
||||||
$ ansible -m command -a 'date' all
|
$ ansible -m command -a 'date' all
|
||||||
$ ansible -m command -a 'whoami' all
|
$ ansible -m command -a 'whoami' all
|
||||||
```
|
```
|
||||||
|
|
||||||
* Module: `file` - performs file operations (stat, link, dir, ...)
|
* Module: `file` - performs file operations (stat, link, dir, ...)
|
||||||
* Module: `raw` - executes a low-down and dirty SSH command, not going through the module subsystem (useful to install python2.7)
|
* Module: `raw` - executes a low-down and dirty SSH command, not going through
|
||||||
|
the module subsystem (useful to install python2.7)
|
||||||
|
|
||||||
### Task
|
### Task
|
||||||
Execution of a single Ansible **module** is called a **task**
|
|
||||||
|
|
||||||
The simplest module is called `ping` as you could see above
|
Execution of a single Ansible **module** is called a **task**. The simplest
|
||||||
|
module is called `ping` as you could see above.
|
||||||
Another example of the module that allow you to execute command remotly on multiple resources is called `shell`. See above how you were using them already.
|
|
||||||
|
|
||||||
|
Another example of the module that allow you to execute command remotly on
|
||||||
|
multiple resources is called `shell`. See above how you were using them already.
|
||||||
|
|
||||||
### Playbook
|
### Playbook
|
||||||
|
|
||||||
**Execution plan** written in a form of script file(s) is called **playbook**.
|
**Execution plan** written in a form of script file(s) is called **playbook**.
|
||||||
Playbook consist of multiple elements
|
Playbook consist of multiple elements -
|
||||||
* a list (or group) of hosts that 'the play' is executed against
|
* a list (or group) of hosts that 'the play' is executed against
|
||||||
* `task(s)` or `role(s)` that are going to be executed
|
* `task(s)` or `role(s)` that are going to be executed
|
||||||
* multiple optional settings (like default variables, and way more)
|
* multiple optional settings (like default variables, and way more)
|
||||||
|
|
||||||
Playbook script language is YAML.
|
Playbook script language is YAML. You can think that playbook is very advanced
|
||||||
|
CLI script that you are executing.
|
||||||
|
|
||||||
You can think that playbook is very advanced CLI script that you are executing.
|
#### Example of the playbook
|
||||||
|
|
||||||
|
This example-playbook would execute (on all hosts defined in inventory) two tasks:
|
||||||
#### Example of the playbook:
|
|
||||||
This example-playbook would execute (on all hosts defined in the inventory) two tasks:
|
|
||||||
* `ping` that would return message *pong*
|
* `ping` that would return message *pong*
|
||||||
* `shell` that execute three commands and return the output to our terminal
|
* `shell` that execute three commands and return the output to our terminal
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- hosts: all
|
- hosts: all
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: "ping all"
|
- name: "ping all"
|
||||||
ping:
|
ping:
|
||||||
|
|
||||||
- name: "execute a shell command"
|
- name: "execute a shell command"
|
||||||
shell: "date; whoami; df -h;"
|
shell: "date; whoami; df -h;"
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the playbook with the command:
|
Run the playbook with the command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ansible-playbook path/name_of_the_playbook.yml
|
$ ansible-playbook path/name_of_the_playbook.yml
|
||||||
```
|
```
|
||||||
_Note: Example playbook is explained in the next chapter: 'Roles'
|
|
||||||
|
Note: Example playbook is explained in the next chapter: 'Roles'
|
||||||
|
|
||||||
### More on ansible concept
|
### More on ansible concept
|
||||||
|
|
||||||
### Inventory
|
### Inventory
|
||||||
Inventory is a set of objects or hosts, against which we are executing our playbooks or single tasks via shell commands
|
|
||||||
For these few minutes, let's assume that we are using the default ansible inventory (which in Debian based system is placed in /etc/ansible/hosts)
|
|
||||||
|
|
||||||
`/etc/ansible/hosts`
|
Inventory is a set of objects or hosts, against which we are executing our
|
||||||
|
playbooks or single tasks via shell commands. For these few minutes, let's
|
||||||
|
assume that we are using the default ansible inventory (which in Debian based
|
||||||
|
system is placed in `/etc/ansible/hosts`).
|
||||||
|
|
||||||
```
|
```
|
||||||
localhost
|
localhost
|
||||||
|
|
||||||
@ -198,18 +269,23 @@ hostB.localdomain
|
|||||||
[a_group_of_a_groups:children]
|
[a_group_of_a_groups:children]
|
||||||
some_group
|
some_group
|
||||||
some_other_group
|
some_other_group
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* [Additional Reading.](http://docs.ansible.com/ansible/latest/intro_inventory.html)
|
* [Additional Reading.](http://docs.ansible.com/ansible/latest/intro_inventory.html)
|
||||||
|
|
||||||
### ansible-roles (a 'template-playbooks' with right structure)
|
### ansible-roles (a 'template-playbooks' with right structure)
|
||||||
|
|
||||||
You already know that the tasks (modules) can be run via CLI. You also know the playbooks - the execution plans of multiple tasks (with variables and logic).
|
You already know that the tasks (modules) can be run via CLI. You also know the
|
||||||
|
playbooks - the execution plans of multiple tasks (with variables and logic).
|
||||||
|
|
||||||
A concept called `role` was introduced for parts of the code (playbooks) that should be reusable.
|
A concept called `role` was introduced for parts of the code (playbooks) that
|
||||||
|
should be reusable.
|
||||||
|
|
||||||
**Role** is a structured way to manage your set of tasks, variables, handlers, default settings, and way more (meta, files, templates).
|
**Role** is a structured way to manage your set of tasks, variables, handlers,
|
||||||
Roles allow reusing the same parts of code in multiple playbooks (you can parametrize the role 'further' during its execution).
|
default settings, and way more (meta, files, templates). Roles allow reusing
|
||||||
It is a great way to introduce `object oriented` management for your applications.
|
the same parts of code in multiple playbooks (you can parametrize the role
|
||||||
|
'further' during its execution). Its a great way to introduce `object oriented`
|
||||||
|
management for your applications.
|
||||||
|
|
||||||
Role can be included in your playbook (executed via your playbook).
|
Role can be included in your playbook (executed via your playbook).
|
||||||
|
|
||||||
@ -222,40 +298,43 @@ Role can be included in your playbook (executed via your playbook).
|
|||||||
ping:
|
ping:
|
||||||
- name: "execute a shell command"
|
- name: "execute a shell command"
|
||||||
shell: "date; whoami; df -h;"
|
shell: "date; whoami; df -h;"
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- some_role
|
- some_role
|
||||||
- { role: another_role, some_variable: 'learnxiny', tags: ['my_tag'] }
|
- { role: another_role, some_variable: 'learnxiny', tags: ['my_tag'] }
|
||||||
|
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
- name: some pre-task
|
- name: some pre-task
|
||||||
shell: echo 'this task is the last, but would be executed before roles, and before tasks'
|
shell: echo 'this task is the last, but would be executed before roles, and before tasks'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### For remaining examples we would use additional repository
|
#### For remaining examples we would use additional repository
|
||||||
This example install ansible in `virtualenv` so it is independend from a system. You need to initialize it into your shell-context with `source environment.sh` command.
|
This example install ansible in `virtualenv` so it is independend from a system.
|
||||||
|
You need to initialize it into your shell-context with `source environment.sh`
|
||||||
|
command.
|
||||||
|
|
||||||
We are going to use this repository with examples: https://github.com/sirkubax/ansible-for-learnXinYminutes
|
We are going to use this repository with examples: [https://github.com/sirkubax/ansible-for-learnXinYminutes]()
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ # The following example contains a shell-prompt to indicate the venv and relative path
|
$ # The following example contains a shell-prompt to indicate the venv and relative path
|
||||||
$ git clone git@github.com:sirkubax/ansible-for-learnXinYminutes.git
|
$ git clone git@github.com:sirkubax/ansible-for-learnXinYminutes.git
|
||||||
user@host:~/$ cd ansible-for-learnXinYminutes
|
user@host:~/$ cd ansible-for-learnXinYminutes
|
||||||
user@host:~/ansible-for-learnXinYminutes$ source environment.sh
|
user@host:~/ansible-for-learnXinYminutes$ source environment.sh
|
||||||
$
|
$
|
||||||
$ # First lets execute the simple_playbook.yml
|
$ # First lets execute the simple_playbook.yml
|
||||||
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/simple_playbook.yml
|
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/simple_playbook.yml
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the playbook with roles example
|
Run the playbook with roles example
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ source environment.sh
|
$ source environment.sh
|
||||||
$ # Now we would run the above playbook with roles
|
$ # Now we would run the above playbook with roles
|
||||||
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/simple_role.yml
|
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/simple_role.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Role directory structure:
|
#### Role directory structure
|
||||||
|
|
||||||
```
|
```
|
||||||
roles/
|
roles/
|
||||||
some_role/
|
some_role/
|
||||||
@ -269,10 +348,13 @@ roles/
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Role Handlers
|
#### Role Handlers
|
||||||
Handlers are tasks that can be triggered (notified) during execution of a playbook, but they execute at the very end of a playbook.
|
Handlers are tasks that can be triggered (notified) during execution of a
|
||||||
It is the best way to restart a service, check if the application port is active (successful deployment criteria), etc.
|
playbook, but they execute at the very end of a playbook. It is the best way to
|
||||||
|
restart a service, check if the application port is active (successful
|
||||||
|
deployment criteria), etc.
|
||||||
|
|
||||||
Get familiar with how you can use roles in the simple_apache_role example
|
Get familiar with how you can use roles in the simple_apache_role example
|
||||||
|
|
||||||
```
|
```
|
||||||
playbooks/roles/simple_apache_role/
|
playbooks/roles/simple_apache_role/
|
||||||
├── tasks
|
├── tasks
|
||||||
@ -283,18 +365,14 @@ playbooks/roles/simple_apache_role/
|
|||||||
|
|
||||||
### ansible - variables
|
### ansible - variables
|
||||||
|
|
||||||
Ansible is flexible - it has 21 levels of variable precedence
|
Ansible is flexible - it has 21 levels of variable precedence.
|
||||||
|
|
||||||
[read more](http://docs.ansible.com/ansible/latest/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)
|
[read more](http://docs.ansible.com/ansible/latest/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)
|
||||||
|
|
||||||
For now you should know that CLI variables have the top priority.
|
For now you should know that CLI variables have the top priority.
|
||||||
|
|
||||||
You should also know, that a nice way to pool some data is a **lookup**
|
You should also know, that a nice way to pool some data is a **lookup**
|
||||||
|
|
||||||
### Lookups
|
### Lookups
|
||||||
Awesome tool to query data from various sources!!! Awesome!
|
Awesome tool to query data from various sources!!! Awesome!
|
||||||
query from:
|
query from:
|
||||||
|
|
||||||
* pipe (load shell command output into variable!)
|
* pipe (load shell command output into variable!)
|
||||||
* file
|
* file
|
||||||
* stream
|
* stream
|
||||||
@ -309,6 +387,7 @@ query from:
|
|||||||
```
|
```
|
||||||
|
|
||||||
You can use them in CLI too
|
You can use them in CLI too
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ansible -m shell -a 'echo "{{ my_variable }}"' -e 'my_variable="{{ lookup("pipe", "date") }}"' localhost
|
ansible -m shell -a 'echo "{{ my_variable }}"' -e 'my_variable="{{ lookup("pipe", "date") }}"' localhost
|
||||||
ansible -m shell -a 'echo "{{ my_variable }}"' -e 'my_variable="{{ lookup("pipe", "hostname") }}"' all
|
ansible -m shell -a 'echo "{{ my_variable }}"' -e 'my_variable="{{ lookup("pipe", "hostname") }}"' all
|
||||||
@ -316,15 +395,16 @@ ansible -m shell -a 'echo "{{ my_variable }}"' -e 'my_variable="{{ lookup("pipe"
|
|||||||
# Or use in playbook
|
# Or use in playbook
|
||||||
|
|
||||||
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/lookup.yml
|
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/lookup.yml
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Register and Conditional
|
### Register and Conditional
|
||||||
|
|
||||||
#### Register
|
#### Register
|
||||||
|
|
||||||
Another way to dynamically generate the variable content is the `register` command.
|
Another way to dynamically generate the variable content is the `register` command.
|
||||||
`Register` is also useful to store an output of a task and use its value
|
`Register` is also useful to store an output of a task and use its value
|
||||||
for executing further tasks.
|
for executing further tasks.
|
||||||
|
|
||||||
```
|
```
|
||||||
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/register_and_when.yml
|
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/register_and_when.yml
|
||||||
```
|
```
|
||||||
@ -340,22 +420,25 @@ for executing further tasks.
|
|||||||
- name: debug root_size
|
- name: debug root_size
|
||||||
debug:
|
debug:
|
||||||
msg: "{{ root_size }}"
|
msg: "{{ root_size }}"
|
||||||
|
|
||||||
- name: debug root_size return code
|
- name: debug root_size return code
|
||||||
debug:
|
debug:
|
||||||
msg: "{{ root_size.rc }}"
|
msg: "{{ root_size.rc }}"
|
||||||
|
|
||||||
# when: example
|
# when: example
|
||||||
|
|
||||||
- name: Print this message when return code of 'check the system capacity' was ok
|
- name: Print this message when return code of 'check the system capacity' was ok
|
||||||
debug:
|
debug:
|
||||||
msg: "{{ root_size.rc }}"
|
msg: "{{ root_size.rc }}"
|
||||||
when: root_size.rc == 0
|
when: root_size.rc == 0
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Conditionals - when:
|
#### Conditionals - when:
|
||||||
|
|
||||||
You can define complex logic with Ansible and Jinja functions. Most common is usage of `when:`, with some variable (often dynamically generated in previous playbook steps with `register` or `lookup`)
|
You can define complex logic with Ansible and Jinja functions. Most common is
|
||||||
|
usage of `when:`, with some variable (often dynamically generated in previous
|
||||||
|
playbook steps with `register` or `lookup`)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
@ -366,53 +449,63 @@ You can define complex logic with Ansible and Jinja functions. Most common is us
|
|||||||
when: some_variable in 'a string'
|
when: some_variable in 'a string'
|
||||||
roles:
|
roles:
|
||||||
- { role: mid_nagios_probe, when: allow_nagios_probes }
|
- { role: mid_nagios_probe, when: allow_nagios_probes }
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### ansible - tags, limit
|
### ansible - tags, limit
|
||||||
|
|
||||||
You should know about a way to increase efficiency by this simple functionality
|
You should know about a way to increase efficiency by this simple functionality
|
||||||
|
|
||||||
#### TAGS
|
#### TAGS
|
||||||
You can tag a task, role (and its tasks), include, etc, and then run only the tagged resources
|
|
||||||
|
|
||||||
ansible-playbook playbooks/simple_playbook.yml --tags=tagA,tag_other
|
You can tag a task, role (and its tasks), include, etc, and then run only the
|
||||||
ansible-playbook playbooks/simple_playbook.yml -t tagA,tag_other
|
tagged resources
|
||||||
|
|
||||||
There are special tags:
|
```
|
||||||
always
|
ansible-playbook playbooks/simple_playbook.yml --tags=tagA,tag_other
|
||||||
|
ansible-playbook playbooks/simple_playbook.yml -t tagA,tag_other
|
||||||
--skip-tags can be used to exclude a block of code
|
|
||||||
--list-tags to list available tags
|
There are special tags:
|
||||||
|
always
|
||||||
|
|
||||||
|
--skip-tags can be used to exclude a block of code
|
||||||
|
--list-tags to list available tags
|
||||||
|
```
|
||||||
|
|
||||||
[Read more](http://docs.ansible.com/ansible/latest/playbooks_tags.html)
|
[Read more](http://docs.ansible.com/ansible/latest/playbooks_tags.html)
|
||||||
|
|
||||||
#### LIMIT
|
#### LIMIT
|
||||||
You can limit an execution of your tasks to defined hosts
|
|
||||||
|
|
||||||
ansible-playbook playbooks/simple_playbook.yml --limmit localhost
|
You can limit an execution of your tasks to defined hosts
|
||||||
|
|
||||||
--limit my_hostname
|
```
|
||||||
--limit groupname
|
ansible-playbook playbooks/simple_playbook.yml --limit localhost
|
||||||
--limit some_prefix*
|
|
||||||
--limit hostname:group #JM
|
--limit my_hostname
|
||||||
|
--limit groupname
|
||||||
|
--limit some_prefix*
|
||||||
|
--limit hostname:group #JM
|
||||||
|
```
|
||||||
|
|
||||||
### Templates
|
### Templates
|
||||||
|
|
||||||
Templates are a powerful way to deliver some (partially) dynamic content. Ansible uses **Jinja2** language to describe the template.
|
Templates are a powerful way to deliver some (partially) dynamic content.
|
||||||
|
Ansible uses **Jinja2** language to describe the template.
|
||||||
|
|
||||||
```jinja2
|
```
|
||||||
Some static content
|
Some static content
|
||||||
|
|
||||||
{{ a_variable }}
|
{{ a_variable }}
|
||||||
|
|
||||||
{% for item in loop_items %}
|
{% for item in loop_items %}
|
||||||
this line item is {{ item }}
|
this line item is {{ item }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
```
|
```
|
||||||
|
|
||||||
Jinja may have some limitations, but it is a powerful tool that you might like.
|
Jinja may have some limitations, but it is a powerful tool that you might like.
|
||||||
|
|
||||||
Please examine this simple example that installs apache2 and generates index.html from the template
|
Please examine this simple example that installs apache2 and generates
|
||||||
|
index.html from the template
|
||||||
"playbooks/roles/simple_apache_role/templates/index.html"
|
"playbooks/roles/simple_apache_role/templates/index.html"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -421,15 +514,18 @@ $ # Now we would run the above playbook with roles
|
|||||||
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/simple_role.yml --tags apache2
|
(venv) user@host:~/ansible-for-learnXinYminutes$ ansible-playbook playbooks/simple_role.yml --tags apache2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Jinja2 CLI
|
#### Jinja2 CLI
|
||||||
|
|
||||||
You can use the jinja in the CLI too
|
You can use the jinja in the CLI too
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible -m shell -a 'echo {{ my_variable }}` -e 'my_variable=something, playbook_parameter=twentytwo" localhost
|
ansible -m shell -a 'echo {{ my_variable }}` -e 'my_variable=something, playbook_parameter=twentytwo" localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
In fact - jinja is used to template parts of the playbooks too
|
In fact - jinja is used to template parts of the playbooks too
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
#check part of this playbook: playbooks/roles/sys_debug/tasks/debug_time.yml
|
# check part of this playbook: playbooks/roles/sys_debug/tasks/debug_time.yml
|
||||||
- local_action: shell date +'%F %T'
|
- local_action: shell date +'%F %T'
|
||||||
register: ts
|
register: ts
|
||||||
become: False
|
become: False
|
||||||
@ -439,24 +535,29 @@ In fact - jinja is used to template parts of the playbooks too
|
|||||||
debug: msg="{{ ts.stdout }}"
|
debug: msg="{{ ts.stdout }}"
|
||||||
when: ts is defined and ts.stdout is defined
|
when: ts is defined and ts.stdout is defined
|
||||||
become: False
|
become: False
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Jinja2 filters
|
#### Jinja2 filters
|
||||||
|
|
||||||
Jinja is powerful. It has many built-in useful functions.
|
Jinja is powerful. It has many built-in useful functions.
|
||||||
```jinja
|
|
||||||
|
```
|
||||||
# get first item of the list
|
# get first item of the list
|
||||||
{{ some_list | first() }}
|
{{ some_list | first() }}
|
||||||
# if variable is undefined - use default value
|
# if variable is undefined - use default value
|
||||||
{{ some_variable | default('default_value') }}
|
{{ some_variable | default('default_value') }}
|
||||||
```
|
```
|
||||||
|
|
||||||
[Read More](http://docs.ansible.com/ansible/latest/playbooks_filters.html)
|
[Read More](http://docs.ansible.com/ansible/latest/playbooks_filters.html)
|
||||||
|
|
||||||
### ansible-vault
|
### ansible-vault
|
||||||
To maintain **infrastructure as code** you need to store secrets.
|
|
||||||
Ansible provides a way to encrypt confidential files so you can store them in the repository, yet the files are decrypted on-the-fly during ansible execution.
|
|
||||||
|
|
||||||
The best way to use it is to store the secret in some secure location, and configure ansible to use during runtime.
|
To maintain **infrastructure as code** you need to store secrets. Ansible
|
||||||
|
provides a way to encrypt confidential files so you can store them in the
|
||||||
|
repository, yet the files are decrypted on-the-fly during ansible execution.
|
||||||
|
|
||||||
|
The best way to use it is to store the secret in some secure location, and
|
||||||
|
configure ansible to use during runtime.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Try (this would fail)
|
# Try (this would fail)
|
||||||
@ -487,36 +588,41 @@ $ ansible-vault decrypt path/somefile
|
|||||||
```
|
```
|
||||||
|
|
||||||
### dynamic inventory
|
### dynamic inventory
|
||||||
|
|
||||||
You might like to know, that you can build your inventory dynamically.
|
You might like to know, that you can build your inventory dynamically.
|
||||||
|
(For Ansible) inventory is just JSON with proper structure - if you can
|
||||||
|
deliver that to ansible - anything is possible.
|
||||||
|
|
||||||
(For Ansible) inventory is just JSON with proper structure - if you can deliver that to ansible - anything is possible.
|
You do not need to reinvent the wheel - there are plenty of ready to use
|
||||||
|
inventory scripts for most popular Cloud providers and a lot of in-house
|
||||||
You do not need to reinvent the wheel - there are plenty of ready to use inventory scripts for most popular Cloud providers and a lot of in-house popular usecases.
|
popular usecases.
|
||||||
|
|
||||||
[AWS example](http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html#example-aws-ec2-external-inventory-script)
|
[AWS example](http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html#example-aws-ec2-external-inventory-script)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ etc/inv/ec2.py --refresh
|
$ etc/inv/ec2.py --refresh
|
||||||
|
|
||||||
$ ansible -m ping all -i etc/inv/ec2.py
|
$ ansible -m ping all -i etc/inv/ec2.py
|
||||||
```
|
```
|
||||||
|
|
||||||
[Read more](http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html)
|
[Read more](http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html)
|
||||||
|
|
||||||
### ansible profiling - callback
|
### ansible profiling - callback
|
||||||
Playbook execution takes some time. It is OK. First make it run, then you may like to speed things up
|
|
||||||
|
|
||||||
Since ansible 2.x there is built-in callback for task execution profiling
|
Playbook execution takes some time. It is OK. First make it run, then you may
|
||||||
|
like to speed things up. Since ansible 2.x there is built-in callback for task
|
||||||
|
execution profiling.
|
||||||
|
|
||||||
```
|
```
|
||||||
vi ansible.cfg
|
vi ansible.cfg
|
||||||
#set this to:
|
# set this to:
|
||||||
callback_whitelist = profile_tasks
|
callback_whitelist = profile_tasks
|
||||||
```
|
```
|
||||||
|
|
||||||
### facts-cache and ansible-cmdb
|
### facts-cache and ansible-cmdb
|
||||||
|
|
||||||
You can pull some information about your environment from another hosts.
|
You can pull some information about your environment from another hosts.
|
||||||
If the information does not change - you may consider using a facts_cache to speed things up.
|
If the information does not change - you may consider using a facts_cache
|
||||||
|
to speed things up.
|
||||||
|
|
||||||
```
|
```
|
||||||
vi ansible.cfg
|
vi ansible.cfg
|
||||||
@ -532,41 +638,55 @@ fact_caching_timeout = 86400
|
|||||||
```
|
```
|
||||||
|
|
||||||
I like to use `jsonfile` as my backend. It allows to use another project
|
I like to use `jsonfile` as my backend. It allows to use another project
|
||||||
`ansible-cmdb` [(project on github)](https://github.com/fboender/ansible-cmdb) that generates a HTML page of your inventory resources. A nice 'free' addition!
|
`ansible-cmdb` [(project on github)](https://github.com/fboender/ansible-cmdb) that generates a HTML page of your inventory
|
||||||
|
resources. A nice 'free' addition!
|
||||||
|
|
||||||
|
### Debugging ansible [chapter in progress]
|
||||||
|
|
||||||
### debugging ansible [chapter in progress]
|
|
||||||
When your job fails - it is good to be effective with debugging.
|
When your job fails - it is good to be effective with debugging.
|
||||||
|
|
||||||
1. Increase verbosity by using multiple -v **[ -vvvvv]**
|
1. Increase verbosity by using multiple -v **[ -vvvvv]**
|
||||||
2. If variable is undefined
|
2. If variable is undefined -
|
||||||
- grep -R path_of_your_inventory -e missing_variable
|
`grep -R path_of_your_inventory -e missing_variable`
|
||||||
3. If variable (dictionary or a list) is undefined
|
3. If variable (dictionary or a list) is undefined -
|
||||||
- grep -R path_of_your_inventory -e missing_variable
|
`grep -R path_of_your_inventory -e missing_variable`
|
||||||
4. Jinja template debug
|
4. Jinja template debug
|
||||||
5. Strange behaviour - try to run the code 'at the destination'
|
5. Strange behaviour - try to run the code 'at the destination'
|
||||||
|
|
||||||
### Infrastructure as code
|
### Infrastructure as code
|
||||||
You already know, that ansible-vault allows you to store your confidential data along with your code (in repository). You can go further - and define your ansible installation and configuration as-a-code.
|
|
||||||
See `environment.sh` to learn how to install the ansible itself inside a `virtualenv` that is not attached to your operating system (can be changed by non-privileged user), and as additional benefit - upgrading version of ansible is as easy as installing new version in new virtualenv. What is more, you can have multiple versions of Ansible present at the same time.
|
You already know, that ansible-vault allows you to store your confidential data
|
||||||
|
along with your code (in repository). You can go further - and define your
|
||||||
|
ansible installation and configuration as-a-code.
|
||||||
|
See `environment.sh` to learn how to install the ansible itself inside a
|
||||||
|
`virtualenv` that is not attached to your operating system (can be changed by
|
||||||
|
non-privileged user), and as additional benefit - upgrading version of ansible
|
||||||
|
is as easy as installing new version in new virtualenv. What is more, you can
|
||||||
|
have multiple versions of Ansible present at the same time.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# recreate ansible 2.x venv
|
# recreate ansible 2.x venv
|
||||||
$ rm -rf venv2
|
$ rm -rf venv2
|
||||||
$ source environment2.sh
|
$ source environment2.sh
|
||||||
# execute playbook
|
|
||||||
|
# execute playbook
|
||||||
(venv2)$ ansible-playbook playbooks/ansible1.9_playbook.yml # would fail - deprecated syntax
|
(venv2)$ ansible-playbook playbooks/ansible1.9_playbook.yml # would fail - deprecated syntax
|
||||||
|
|
||||||
# now lets install ansible 1.9.x next to ansible 2.x
|
# now lets install ansible 1.9.x next to ansible 2.x
|
||||||
(venv2)$ deactivate
|
(venv2)$ deactivate
|
||||||
$ source environment.1.9.sh
|
$ source environment.1.9.sh
|
||||||
# execute playbook
|
|
||||||
|
# execute playbook
|
||||||
(venv1.9)$ ansible-playbook playbooks/ansible1.9_playbook.yml # works!
|
(venv1.9)$ ansible-playbook playbooks/ansible1.9_playbook.yml # works!
|
||||||
|
|
||||||
# please note that you have both venv1.9 and venv2 present - you need to (de)activate one - that is all
|
# please note that you have both venv1.9 and venv2 present - you need to (de)activate one - that is all
|
||||||
```
|
```
|
||||||
|
|
||||||
#### become-user, become
|
#### become-user, become
|
||||||
In Ansible - to become `sudo` - use the `become` parameter. Use `become_user` to specify the username.
|
|
||||||
|
In Ansible - to become `sudo` - use the `become` parameter. Use `become_user`
|
||||||
|
to specify the username.
|
||||||
|
|
||||||
```
|
```
|
||||||
- name: Ensure the httpd service is running
|
- name: Ensure the httpd service is running
|
||||||
service:
|
service:
|
||||||
@ -574,93 +694,65 @@ In Ansible - to become `sudo` - use the `become` parameter. Use `become_user` to
|
|||||||
state: started
|
state: started
|
||||||
become: true
|
become: true
|
||||||
```
|
```
|
||||||
Note: You may like to execute Ansible with `--ask-sudo-pass` or add the user to sudoers file in order to allow non-supervised execution if you require 'admin' privilages.
|
|
||||||
|
Note: You may like to execute Ansible with `--ask-sudo-pass` or add the user to
|
||||||
|
sudoers file in order to allow non-supervised execution if you require 'admin'
|
||||||
|
privilages.
|
||||||
|
|
||||||
[Read more](http://docs.ansible.com/ansible/latest/become.html)
|
[Read more](http://docs.ansible.com/ansible/latest/become.html)
|
||||||
|
|
||||||
## Tips and tricks
|
## Tips and tricks
|
||||||
|
|
||||||
#### --check -C
|
#### --check -C
|
||||||
Always make sure that your playbook can execute in 'dry run' mode (--check), and its execution is not declaring 'Changed' objects.
|
|
||||||
|
Always make sure that your playbook can execute in 'dry run' mode (--check),
|
||||||
|
and its execution is not declaring 'Changed' objects.
|
||||||
|
|
||||||
#### --diff -D
|
#### --diff -D
|
||||||
Diff is useful to see nice detail of the files changed
|
|
||||||
|
|
||||||
It compare 'in memory' the files like `diff -BbruN fileA fileB`
|
Diff is useful to see nice detail of the files changed.
|
||||||
|
It compare 'in memory' the files like `diff -BbruN fileA fileB`.
|
||||||
|
|
||||||
|
|
||||||
#### Execute hosts with 'regex'
|
#### Execute hosts with 'regex'
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible -m ping web*
|
ansible -m ping web*
|
||||||
```
|
```
|
||||||
|
|
||||||
####
|
#### Host groups can be joined, negated, etc
|
||||||
Host groups can be joined, negated, etc
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible -m ping web*:!backend:monitoring:&allow_change
|
ansible -m ping web*:!backend:monitoring:&allow_change
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Tagging
|
#### Tagging
|
||||||
You should tag some (not all) objects - a task in a playbook, all tasks included form a role, etc.
|
|
||||||
It allows you to execute the chosen parts of the playbook.
|
You should tag some (not all) objects - a task in a playbook, all tasks
|
||||||
|
included form a role, etc. It allows you to execute the chosen parts of the
|
||||||
|
playbook.
|
||||||
|
|
||||||
#### no_logs: True
|
#### no_logs: True
|
||||||
You may see, that some roles print a lot of output in verbose mode. There is also a debug module.
|
|
||||||
This is the place where credentials may leak. Use `no_log` to hide the output.
|
You may see, that some roles print a lot of output in verbose mode. There is
|
||||||
|
also a debug module. This is the place where credentials may leak. Use `no_log`
|
||||||
|
to hide the output.
|
||||||
|
|
||||||
#### Debug module
|
#### Debug module
|
||||||
|
|
||||||
allows to print a value to the screen - use it!
|
allows to print a value to the screen - use it!
|
||||||
|
|
||||||
#### Register the output of a task
|
#### Register the output of a task
|
||||||
You can register the output (stdout), rc (return code), stderr of a task with the `register` command.
|
|
||||||
|
|
||||||
#### Conditionals: when:
|
You can register the output (stdout), rc (return code), stderr of a task with
|
||||||
|
the `register` command.
|
||||||
|
|
||||||
#### Loop: with, with_items, with_dict, with_together
|
#### Conditionals: when:
|
||||||
|
|
||||||
|
#### Loop: with, with\_items, with\_dict, with\_together
|
||||||
|
|
||||||
[Read more](http://docs.ansible.com/ansible/latest/playbooks_conditionals.html)
|
[Read more](http://docs.ansible.com/ansible/latest/playbooks_conditionals.html)
|
||||||
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
Ansible is (one of the many) orchestration tools. It allows you to control your environment (infrastructure and code) and automate the manual tasks.
|
|
||||||
'You can think as simple as writing in bash with python API
|
|
||||||
Of course the rabbit hole is way deeper.'
|
|
||||||
|
|
||||||
Ansible has great integration with multiple operating systems (even Windows) and some hardware (switches, Firewalls, etc). It has multiple tools that integrate with the cloud providers. Almost every noteworthy cloud provider is present in the ecosystem (AWS, Azure, Google, DigitalOcean, OVH, etc...)
|
|
||||||
|
|
||||||
|
|
||||||
But ansible is way more! It provides an execution plans, an API, library, callbacks, not forget to mention - COMMUNITY! and great support by developers!
|
|
||||||
|
|
||||||
|
|
||||||
### Main cons and pros
|
|
||||||
|
|
||||||
#### Cons
|
|
||||||
|
|
||||||
It is an agent-less tool - every agent consumes up to 16MB ram - in some environments, it may be noticable amount.
|
|
||||||
It is agent-less - you have to verify your environment consistency 'on-demand' - there is no built-in mechanism that would warn you about some change automatically (this can be achieved with reasonable effort)
|
|
||||||
Official GUI Tool (web inferface) - Ansible Tower - is great, but it is expensive. There is no 'small enterprice' payment plan, however Ansible AWX is the free open source version we were all waiting for.
|
|
||||||
|
|
||||||
#### Pros
|
|
||||||
|
|
||||||
It is an agent-less tools In most scenarios, it use ssh as a transport layer.
|
|
||||||
In some way you can use it as 'bash on steroids'.
|
|
||||||
It is very easy to start. If you are familiar with ssh concept - you already know Ansible (ALMOST).
|
|
||||||
It executes 'as is' - other tools (salt, puppet, chef - might execute in different scenario than you would expect)
|
|
||||||
Documentation is at the world-class standard!
|
|
||||||
The community (github, stackOverflow) would help you very fast.
|
|
||||||
Writing your own modules and extensions is fairly easy.
|
|
||||||
Ansible AWX is the open source version of Ansible Tower we have been waiting for, which provides an excellent UI.
|
|
||||||
|
|
||||||
#### Neutral
|
|
||||||
Migration Ansible<->Salt is fairly easy - so if you would need an event-driven agent environment - it would be a good choice to start quick with Ansible, and convert to salt when needed.
|
|
||||||
|
|
||||||
#### Some concepts
|
|
||||||
|
|
||||||
Ansible uses ssh or paramiko as a transport layer. In a way you can imagine that you are using a ssh with API to perform your action.
|
|
||||||
The simplest way is to execute remote command in more controlled way (still using ssh).
|
|
||||||
On the other hand - in advanced scope - you can wrap Ansible (use python Ansible code as a library) with your own Python scrips! This is awesome! It would act a bit like Fabric then.
|
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
|
|
||||||
* [Servers For Hackers: An Ansible Tutorial](https://serversforhackers.com/c/an-ansible-tutorial)
|
* [Servers For Hackers: An Ansible Tutorial](https://serversforhackers.com/c/an-ansible-tutorial)
|
||||||
|
Loading…
Reference in New Issue
Block a user