diff --git a/Content/ansible/01-intro.md b/Content/ansible/01-intro.md new file mode 100644 index 0000000..23b3b9c --- /dev/null +++ b/Content/ansible/01-intro.md @@ -0,0 +1,51 @@ + +# Introduction to Ansible + +- [What is Ansible?](#what-is-ansible) +- [Core Concepts](#core-concepts) +- [Architecture](#architecture) +- [Installation](#installation) + + + +## What is Ansible? + +Ansible is an open-source software provisioning, configuration management, and application-deployment tool enabling infrastructure as code. It runs on many Unix-like systems, and can configure both Unix-like systems as well as Microsoft Windows. + +Ansible is agentless, temporarily connecting remotely via SSH or Windows Remote Management (allowing remote PowerShell execution) to do its tasks. + +## Core Concepts + +### Inventory + +Ansible works against multiple managed nodes or "hosts" in your infrastructure at the same time, using a list or group of lists known as inventory. You can pass inventory once at the command line, but most Ansible users create a file and keep it in their repo. + +### Modules + +Ansible works by connecting to your nodes and pushing out small programs, called "modules" to them. These programs are written to be resource models of the desired state of the system. Ansible executes these modules (over SSH by default), and removes them when finished. + +### Playbooks + +Playbooks are Ansible’s configuration, deployment, and orchestration language. They can describe a policy you want your remote systems to enforce, or a set of steps in a general IT process. + +## Architecture + +Ansible's architecture is simple and effective. It relies on: +- **Control Node**: The machine where Ansible is installed and from which you run commands. +- **Managed Nodes**: The devices (servers, network devices, etc.) that you manage with Ansible. +- **Plugins**: Code that expands Ansible's core functionality. +- **Inventory**: A list of managed nodes. +- **Playbooks**: YAML files containing the definition of automation. + +## Installation + +To install Ansible, you generally need Python installed on your control node. + +```bash +sudo apt update +sudo apt install ansible +``` + +## Resources + +- [Official Ansible Documentation](https://docs.ansible.com/) diff --git a/Content/ansible/02-inventory.md b/Content/ansible/02-inventory.md new file mode 100644 index 0000000..c4672da --- /dev/null +++ b/Content/ansible/02-inventory.md @@ -0,0 +1,84 @@ + +# Ansible Inventory + +- [What is Inventory?](#what-is-inventory) +- [Inventory Formats](#inventory-formats) + - [INI Format](#ini-format) + - [YAML Format](#yaml-format) +- [Groups and Variables](#groups-and-variables) +- [Default Location](#default-location) + +## What is Inventory? + +The Ansible inventory is a file (or multiple files) that contains a list of the servers or nodes you want to manage. It allows you to organize your managed nodes into groups, which makes it easier to run automation against specific sets of servers (e.g., "webservers", "dbservers"). + +## Inventory Formats + +Ansible supports multiple formats for inventory, but the most common are **INI** and **YAML**. + +### INI Format + +The INI format is simple and easy to read. + +```ini +mail.example.com + +[webservers] +foo.example.com +bar.example.com + +[dbservers] +one.example.com +two.example.com +``` + +### YAML Format + +The YAML format is more structured and consistent with Playbooks. + +```yaml +all: + hosts: + mail.example.com: + children: + webservers: + hosts: + foo.example.com: + bar.example.com: + dbservers: + hosts: + one.example.com: + two.example.com: +``` + +## Groups and Variables + +You can assign variables to hosts or groups in your inventory. + +### Host Variables + +```ini +[webservers] +web1 http_port=80 maxRequestsPerChild=808 +web2 http_port=303 maxRequestsPerChild=909 +``` + +### Group Variables + +```ini +[webservers] +web1 +web2 + +[webservers:vars] +ntp_server=ntp.atlanta.example.com +proxy=proxy.atlanta.example.com +``` + +## Default Location + +The default location for the inventory file is `/etc/ansible/hosts`, but you can specify a different inventory file using the `-i` flag when running Ansible commands. + +```bash +ansible all -i my_inventory_file -m ping +``` diff --git a/Content/ansible/03-ad-hoc-commands.md b/Content/ansible/03-ad-hoc-commands.md new file mode 100644 index 0000000..34417bc --- /dev/null +++ b/Content/ansible/03-ad-hoc-commands.md @@ -0,0 +1,80 @@ + +# Ansible Ad-Hoc Commands + +- [What are Ad-Hoc Commands?](#what-are-ad-hoc-commands) +- [Syntax](#syntax) +- [Common Modules](#common-modules) + - [Ping](#ping) + - [Shell / Command](#shell--command) + - [Package Management (apt/yum)](#package-management-aptyum) + - [Service Management](#service-management) + +## What are Ad-Hoc Commands? + +Ad-hoc commands are quick, one-off tasks that you run without writing a playbook. They are useful for quick tests, checking system status, or performing simple operations across your inventory. + +## Syntax + +The basic syntax for an ad-hoc command is: + +```bash +ansible [pattern] -m [module] -a "[module options]" +``` + +- **pattern**: The host or group from your inventory to target (e.g., `all`, `webservers`). +- **-m [module]**: The Ansible module to run (default is `command`). +- **-a "[options]"**: Arguments to pass to the module. + +## Common Modules + +### Ping + +Check connectivity to your hosts. This doesn't use ICMP ping, but rather verifies that Ansible can login and find a usable Python. + +```bash +ansible all -m ping +``` + +### Shell / Command + +Run arbitrary commands on remote systems. + +**Command module** (default, safer, no shell variables/pipes): +```bash +ansible webservers -m command -a "uptime" +``` + +**Shell module** (allows pipes, redirects): +```bash +ansible webservers -m shell -a "echo 'hello' > /tmp/hello.txt" +``` + +### Package Management (apt/yum) + +Install or remove packages. + +**Install Nginx on Ubuntu/Debian:** +```bash +ansible webservers -m apt -a "name=nginx state=present" --become +``` + +**Install Git on CentOS/RHEL:** +```bash +ansible dbservers -m yum -a "name=git state=present" --become +``` + +*(Note: `--become` is used to escalate privileges, like `sudo`)* + +### Service Management + +Manage services (start, stop, restart). + +**Start Nginx:** +```bash +ansible webservers -m service -a "name=nginx state=started" --become +``` + +**Restart Apache:** +```bash +ansible webservers -m service -a "name=apache2 state=restarted" --become +``` diff --git a/Content/ansible/04-playbooks.md b/Content/ansible/04-playbooks.md new file mode 100644 index 0000000..84830a9 --- /dev/null +++ b/Content/ansible/04-playbooks.md @@ -0,0 +1,95 @@ + +# Ansible Playbooks + +- [What is a Playbook?](#what-is-a-playbook) +- [YAML Syntax](#yaml-syntax) +- [Playbook Structure](#playbook-structure) +- [Example Playbook](#example-playbook) +- [Running a Playbook](#running-a-playbook) +- [Related Labs](#related-labs) + + +## What is a Playbook? + +Playbooks are the files where Ansible code is written. Playbooks are written in YAML format. They allow you to declare configurations, orchestrate steps of any manually ordered process, even on different sets of machines, in a defined order, and launch tasks synchronously or asynchronously. + +## YAML Syntax + +YAML (YAML Ain't Markup Language) is a human-readable data serialization standard. +- Files start with `---`. +- Indentation is significant (use spaces, not tabs). +- Lists are denoted by hyphens `-`. +- Key-value pairs are separated by a colon `:`. + +## Playbook Structure + +A playbook is composed of one or more "plays". +- **Play**: Maps a group of hosts to some well-defined roles or tasks. +- **Task**: A call to an Ansible module. + +```yaml +--- +- name: Update web servers + hosts: webservers + become: yes + + tasks: + - name: Ensure apache is at the latest version + apt: + name: apache2 + state: latest +``` + +## Example Playbook + +Here is a more complete example that installs and starts Nginx. + +```yaml +--- +- name: Install and Configure Nginx + hosts: webservers + become: yes + + tasks: + - name: Install Nginx + apt: + name: nginx + state: present + update_cache: yes + + - name: Start Nginx service + service: + name: nginx + state: started + enabled: yes + + - name: Create a custom index.html + copy: + content: "

Hello from Ansible!

" + dest: /var/www/html/index.html +``` + +## Running a Playbook + +To run a playbook, use the `ansible-playbook` command: + +```bash +ansible-playbook my_playbook.yaml +``` + +You can also limit execution to a specific host: + +```bash +ansible-playbook my_playbook.yaml --limit web1 +``` + +--- + +## Related Labs + +> [!TIP] +> **Practice what you learn!** +> Check out the [Ansible + Vagrant Lab](ansible-labs/ansible+vagrant-lab/README.md) to see real-world playbooks in action. +> * **Master Playbook**: [master-playbook.yaml](ansible-labs/ansible+vagrant-lab/master-playbook.yaml) orchestrates the entire deployment. +> * **Backend Playbook**: [backend-playbook.yaml](ansible-labs/ansible+vagrant-lab/backend-playbook.yaml) configures MongoDB. + diff --git a/Content/ansible/05-variables.md b/Content/ansible/05-variables.md new file mode 100644 index 0000000..6dc1c43 --- /dev/null +++ b/Content/ansible/05-variables.md @@ -0,0 +1,95 @@ + +# Ansible Variables + +- [Defining Variables](#defining-variables) + - [In Playbooks](#in-playbooks) + - [In Inventory](#in-inventory) + - [In Variable Files](#in-variable-files) +- [Registered Variables](#registered-variables) +- [Magic Variables](#magic-variables) +- [Variable Precedence](#variable-precedence) + +--- + +## Defining Variables + +Variables allow you to manage differences between systems without changing your playbooks. + +### In Playbooks + +You can define variables directly in a playbook using the `vars` block. + +```yaml +- hosts: webservers + vars: + http_port: 80 + max_clients: 200 + tasks: + ... +``` + +### In Inventory + +As seen in the Inventory module, you can define variables in your inventory file. + +```ini +[webservers] +web1 http_port=80 +web2 http_port=8080 +``` + +### In Variable Files + +It's often better to keep variables in separate files to keep your playbooks clean and organized. You can include them in your playbook using `vars_files`. + +**1. Create a variable file (e.g., `vars/external_vars.yml`):** + +```yaml +# vars/external_vars.yml +http_port: 80 +max_clients: 200 +``` + +**2. Import it in your Playbook:** + +```yaml +- hosts: webservers + vars_files: + - vars/external_vars.yml + tasks: + - name: Print variable + debug: + msg: "Port is {{ http_port }}" +``` + +## Registered Variables + +You can capture the output of a task into a variable using the `register` keyword. + +```yaml +- hosts: webservers + tasks: + - name: Run a command + command: uptime + register: uptime_result + + - name: Print the result + debug: + var: uptime_result.stdout +``` + +## Magic Variables + +Ansible provides some variables automatically, known as "magic variables". +- `hostvars`: Access variables from other hosts. +- `groups`: List of groups and their hosts. +- `group_names`: List of groups the current host is in. +- `inventory_hostname`: The hostname as defined in the inventory. + +## Variable Precedence + +Ansible has a specific order of precedence for variables. In general: +1. Extra vars (`-e` in command line) - **Highest** +2. Playbook vars +3. Inventory vars +4. Role defaults - **Lowest** diff --git a/Content/ansible/06-conditionals-loops.md b/Content/ansible/06-conditionals-loops.md new file mode 100644 index 0000000..263a12f --- /dev/null +++ b/Content/ansible/06-conditionals-loops.md @@ -0,0 +1,83 @@ + +# Ansible Conditionals and Loops + +- [Conditionals](#conditionals) + - [The `when` Statement](#the-when-statement) + - [Conditionals with Variables](#conditionals-with-variables) +- [Loops](#loops) + - [Standard Loops](#standard-loops) + - [Looping over Hashes](#looping-over-hashes) + +--- + +## Conditionals + +Conditionals allow you to execute tasks only when certain conditions are met. + +### The `when` Statement + +The most common conditional is the `when` statement. + +```yaml +- name: Install Apache on Debian + apt: + name: apache2 + state: present + when: ansible_os_family == "Debian" + +- name: Install Httpd on RedHat + yum: + name: httpd + state: present + when: ansible_os_family == "RedHat" +``` + +### Conditionals with Variables + +You can also use your own variables in conditionals. + +```yaml +vars: + production: true + +tasks: + - name: Restart web server + service: + name: apache2 + state: restarted + when: production +``` + +## Loops + +Loops allow you to repeat a task multiple times. + +### Standard Loops + +Use the `loop` keyword to iterate over a list. + +```yaml +- name: Add several users + user: + name: "{{ item }}" + state: present + groups: "wheel" + loop: + - testuser1 + - testuser2 +``` + +### Looping over Hashes + +You can also loop over a list of dictionaries (hashes). + +```yaml +- name: Add several users with specific uids + user: + name: "{{ item.name }}" + uid: "{{ item.uid }}" + state: present + loop: + - { name: 'testuser1', uid: 1002 } + - { name: 'testuser2', uid: 1003 } +``` diff --git a/Content/ansible/07-templates.md b/Content/ansible/07-templates.md new file mode 100644 index 0000000..7e51a14 --- /dev/null +++ b/Content/ansible/07-templates.md @@ -0,0 +1,355 @@ + +# Ansible Templates + +- [What are Templates?](#what-are-templates) + - [Why Use Templates?](#why-use-templates) +- [Jinja2 Basics](#jinja2-basics) + - [Variables](#variables) + - [Comments](#comments) +- [Using Templates in Ansible](#using-templates-in-ansible) + - [The template Module](#the-template-module) + - [Template File Location](#template-file-location) +- [Jinja2 Filters](#jinja2-filters) + - [Common Filters](#common-filters) + - [Chaining Filters](#chaining-filters) +- [Conditionals in Templates](#conditionals-in-templates) +- [Loops in Templates](#loops-in-templates) +- [Practical Examples](#practical-examples) + - [Example 1: Nginx Configuration](#example-1-nginx-configuration) + - [Example 2: Hosts File](#example-2-hosts-file) + - [Example 3: Application Config](#example-3-application-config) +- [Best Practices](#best-practices) + +--- + +## What are Templates? + +**In short**: A template is a dynamic file - a file that contains variables which are replaced with actual values at runtime. + +Instead of writing: +```plaintext +server_name example.com; +port 80; +``` + +You write a template: +```plaintext +server_name {{ domain }}; +port {{ port }}; +``` + +And when the template is executed, the variables are replaced with the actual values. + +--- + +Templates in Ansible allow you to create dynamic configuration files by combining static content with variables. Instead of maintaining separate configuration files for each server, you can use one template with variables that are replaced at runtime. + +Ansible uses the **Jinja2** templating engine, which is a powerful and flexible templating language for Python. + +### Why Use Templates? + +- **Dynamic Configuration**: Generate files based on variables and facts. +- **Reduce Duplication**: One template for multiple servers with different values. +- **Maintainability**: Update one template instead of many files. +- **Flexibility**: Use conditionals and loops to customize output. + +## Jinja2 Basics + +### Variables + +Variables are enclosed in double curly braces: + +```jinja2 +Hello {{ username }}! +Server IP: {{ ansible_default_ipv4.address }} +``` + +### Comments + +Comments are enclosed in `{# #}` and won't appear in the output: + +```jinja2 +{# This is a comment #} +server_name {{ domain_name }}; +``` + +## Using Templates in Ansible + +### The template Module + +The `template` module processes a Jinja2 template and copies it to the target host. + +```yaml +- name: Deploy Nginx configuration + template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + owner: root + group: root + mode: '0644' +``` + +**Parameters:** +- `src`: Path to the template file (usually ends with `.j2`) +- `dest`: Destination path on the target host +- `owner`, `group`, `mode`: File permissions + +### Template File Location + +Templates can be stored in two main locations: + +**1. Playbook-level templates:** +```plaintext +my-project/ +├── playbook.yml +└── templates/ + └── nginx.conf.j2 +``` + +**2. Role-level templates:** +```plaintext +my-project/ +├── playbook.yml +└── roles/ + └── nginx/ + └── templates/ + └── nginx.conf.j2 +``` + +When using the `template` module, Ansible automatically searches in the appropriate `templates/` directory based on your context (playbook or role). + +## Jinja2 Filters + +Filters modify variables. They are applied using the pipe `|` symbol. + +### Common Filters + +| Filter | Description | Example | +|--------|-------------|---------| +| `default` | Provide a default value | `{{ port \| default(80) }}` | +| `upper` | Convert to uppercase | `{{ name \| upper }}` | +| `lower` | Convert to lowercase | `{{ name \| lower }}` | +| `capitalize` | Capitalize first letter | `{{ name \| capitalize }}` | +| `replace` | Replace substring | `{{ text \| replace('old', 'new') }}` | +| `join` | Join list items | `{{ items \| join(', ') }}` | +| `length` | Get length | `{{ list \| length }}` | +| `int` | Convert to integer | `{{ value \| int }}` | +| `bool` | Convert to boolean | `{{ value \| bool }}` | + +#### Chaining Filters + +You can chain multiple filters: + +```jinja2 +{{ username | default('guest') | upper }} +``` + +## Conditionals in Templates + +Use `{% if %}` statements for conditionals: + +```jinja2 +{% if ansible_os_family == "Debian" %} + Package manager: apt +{% elif ansible_os_family == "RedHat" %} + Package manager: yum +{% else %} + Package manager: unknown +{% endif %} +``` + +**Comparison operators:** +- `==`, `!=`, `<`, `>`, `<=`, `>=` +- `in`, `not in` +- `is defined`, `is not defined` + +## Loops in Templates + +Use `{% for %}` to iterate over lists: + +```jinja2 +{% for user in users %} + User: {{ user.name }} - {{ user.email }} +{% endfor %} +``` + +**With conditionals:** + +```jinja2 +{% for server in servers %} + {% if server.enabled %} + server {{ server.name }}:{{ server.port }}; + {% endif %} +{% endfor %} +``` + +## Practical Examples + +### Example 1: Nginx Configuration + +**Playbook:** + +```yaml +- hosts: webservers + vars: + nginx_port: 80 + server_name: example.com + root_path: /var/www/html + tasks: + - name: Deploy Nginx config + template: + src: nginx.conf.j2 + dest: /etc/nginx/sites-available/default +``` + +**Template (nginx.conf.j2):** + +```jinja2 +server { + listen {{ nginx_port }}; + server_name {{ server_name }}; + + root {{ root_path }}; + index index.html index.htm; + + location / { + try_files $uri $uri/ =404; + } + + {# Enable gzip compression #} + gzip on; + gzip_types text/plain text/css application/json; +} +``` + +### Example 2: Hosts File + +**Playbook:** + +```yaml +- hosts: all + vars: + custom_hosts: + - { ip: "192.168.1.10", hostname: "db.local" } + - { ip: "192.168.1.11", hostname: "cache.local" } + tasks: + - name: Update /etc/hosts + template: + src: hosts.j2 + dest: /etc/hosts + backup: yes +``` + +**Template (hosts.j2):** + +```jinja2 +127.0.0.1 localhost +::1 localhost + +{# Add custom hosts #} +{% for host in custom_hosts %} +{{ host.ip }} {{ host.hostname }} +{% endfor %} + +{# Add all inventory hosts #} +{% for host in groups['all'] %} +{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ host }} +{% endfor %} +``` + +### Example 3: Application Config + +**Playbook:** + +```yaml +- hosts: appservers + vars: + app_name: myapp + app_port: 3000 + database: + host: db.example.com + port: 5432 + name: myapp_db + debug_mode: false + features: + - authentication + - caching + - logging + tasks: + - name: Deploy app configuration + template: + src: app_config.j2 + dest: /opt/{{ app_name }}/config.yml +``` + +**Template (app_config.j2):** + +```jinja2 +# {{ app_name }} Configuration +# Generated by Ansible on {{ ansible_date_time.iso8601 }} + +application: + name: {{ app_name }} + port: {{ app_port }} + debug: {{ debug_mode | lower }} + +database: + host: {{ database.host }} + port: {{ database.port }} + database: {{ database.name }} + +features: +{% for feature in features %} + - {{ feature }} +{% endfor %} + +{% if debug_mode %} +# Debug mode is enabled +log_level: DEBUG +{% else %} +log_level: INFO +{% endif %} + +# Server information +server_hostname: {{ ansible_hostname }} +server_ip: {{ ansible_default_ipv4.address }} +``` + +## Best Practices + +1. **Use `.j2` extension**: Name template files with `.j2` to distinguish them from regular files. + +2. **Add comments**: Document your templates with Jinja2 comments `{# #}`. + +3. **Use `default` filter**: Provide default values to avoid undefined variable errors: + ```jinja2 + {{ port | default(8080) }} + ``` + +4. **Validate before deploying**: Use `validate` parameter to check syntax: + ```yaml + - name: Deploy Nginx config + template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + validate: 'nginx -t -c %s' + ``` + +5. **Use `backup: yes`**: Create backups when modifying critical files: + ```yaml + template: + src: config.j2 + dest: /etc/app/config + backup: yes + ``` + +6. **Keep templates simple**: If a template becomes too complex, consider splitting it or using includes. + +7. **Use whitespace control**: Control whitespace with `-`: + ```jinja2 + {% for item in items -%} + {{ item }} + {%- endfor %} + ``` + +8. **Test with different variables**: Test templates with various variable combinations to ensure they work correctly. diff --git a/Content/ansible/08-roles.md b/Content/ansible/08-roles.md new file mode 100644 index 0000000..91cc457 --- /dev/null +++ b/Content/ansible/08-roles.md @@ -0,0 +1,397 @@ + +# Ansible Roles + +- [What are Roles?](#what-are-roles) + - [Why Use Roles?](#why-use-roles) +- [Role Structure](#role-structure) + - [Directory Breakdown](#directory-breakdown) +- [Creating Roles](#creating-roles) + - [Manual Creation](#manual-creation) + - [Using ansible-galaxy](#using-ansible-galaxy) +- [Using Roles](#using-roles) + - [Basic Usage](#basic-usage) + - [Passing Variables to Roles](#passing-variables-to-roles) + - [Role Dependencies](#role-dependencies) +- [Complete Example: Nginx Role](#complete-example-nginx-role) + - [Step 1: Create the Role Structure](#step-1-create-the-role-structure) + - [Step 2: Define Tasks](#step-2-define-tasks) + - [Step 3: Create Handlers](#step-3-create-handlers) + - [Step 4: Set Variables](#step-4-set-variables) + - [Step 5: Create Templates](#step-5-create-templates) + - [Step 6: Use the Role](#step-6-use-the-role) +- [Ansible Galaxy](#ansible-galaxy) + - [Installing Roles from Galaxy](#installing-roles-from-galaxy) + - [Searching for Roles](#searching-for-roles) +- [Best Practices](#best-practices) + +--- + +## What are Roles? + +Roles are Ansible's way of organizing playbooks into reusable components. Instead of writing one massive playbook with hundreds of tasks, you can break your automation into logical, self-contained units called roles. + +Think of a role as a "package" that contains everything needed to configure a specific service or component (like Nginx, MySQL, or a firewall). + +### Why Use Roles? + +- **Reusability**: Write once, use in multiple playbooks or projects. +- **Organization**: Keep related tasks, variables, and files together. +- **Sharing**: Easily share roles with your team or the community via Ansible Galaxy. +- **Maintainability**: Easier to update and debug when code is organized. + +## Role Structure + +A role has a standardized directory structure. Ansible automatically knows where to find tasks, variables, handlers, etc., based on this structure. + +```plaintext +roles/ + my_role/ + tasks/ + main.yml + handlers/ + main.yml + templates/ + files/ + vars/ + main.yml + defaults/ + main.yml + meta/ + main.yml + README.md +``` + +### Directory Breakdown + +| Directory | Purpose | Example | +|-----------|---------|---------| +| **tasks/** | Main list of tasks to execute | Install packages, start services | +| **handlers/** | Tasks triggered by `notify` | Restart a service after config change | +| **templates/** | Jinja2 templates (.j2 files) | Configuration files with variables | +| **files/** | Static files to copy to hosts | Scripts, certificates, config files | +| **vars/** | Variables with higher priority | Role-specific settings | +| **defaults/** | Default variables (lowest priority) | Fallback values | +| **meta/** | Role metadata and dependencies | Specify other roles this depends on | + +**Important Notes:** +- Each directory should contain a `main.yml` file (except `files/` and `templates/`). +- Ansible automatically loads `tasks/main.yml`, `handlers/main.yml`, etc. +- You can split tasks into multiple files and include them from `main.yml`. + +## Creating Roles + +### Manual Creation + +You can manually create the directory structure: + +```bash +mkdir -p roles/nginx/{tasks,handlers,templates,files,vars,defaults,meta} +touch roles/nginx/tasks/main.yml +``` + +### Using ansible-galaxy + +The recommended way is to use `ansible-galaxy init`: + +```bash +ansible-galaxy init roles/nginx +``` + +This creates the complete structure with placeholder files. + +## Using Roles + +### Basic Usage + +To use a role in a playbook, list it under the `roles` section: + +```yaml +--- +- hosts: webservers + become: yes + roles: + - nginx + - firewall +``` + +Ansible will look for roles in: +1. `./roles/` (relative to the playbook) +2. `/etc/ansible/roles/` +3. Paths defined in `ansible.cfg` + +### Passing Variables to Roles + +You can pass variables to roles in several ways: + +#### Method 1: Inline variables + +```yaml +- hosts: webservers + roles: + - role: nginx + nginx_port: 8080 + nginx_user: www-data +``` + +#### Method 2: Using vars + +```yaml +- hosts: webservers + vars: + nginx_port: 8080 + roles: + - nginx +``` + +#### Method 3: In inventory + +```ini +[webservers] +web1 nginx_port=8080 +``` + +### Role Dependencies + +Roles can depend on other roles. Define dependencies in `meta/main.yml`: + +```yaml +--- +dependencies: + - role: common + - role: firewall + firewall_allowed_ports: + - 80 + - 443 +``` + +When you use a role with dependencies, Ansible automatically runs the dependent roles first. + +## Complete Example: Nginx Role + +Let's build a complete Nginx role step by step. + +### Step 1: Create the Role Structure + +```bash +ansible-galaxy init roles/nginx +``` + +This creates: + +```plaintext +roles/nginx/ +├── defaults/ +│ └── main.yml +├── files/ +├── handlers/ +│ └── main.yml +├── meta/ +│ └── main.yml +├── tasks/ +│ └── main.yml +├── templates/ +└── vars/ + └── main.yml +``` + +### Step 2: Define Tasks + +Edit `roles/nginx/tasks/main.yml`: + +```yaml +--- +# Install Nginx +- name: Install Nginx package + apt: + name: nginx + state: present + update_cache: yes + when: ansible_os_family == "Debian" + +- name: Install Nginx on RedHat + yum: + name: nginx + state: present + when: ansible_os_family == "RedHat" + +# Deploy custom configuration +- name: Deploy Nginx configuration + template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + owner: root + group: root + mode: '0644' + notify: Restart Nginx + +# Deploy custom index page +- name: Deploy custom index page + template: + src: index.html.j2 + dest: /var/www/html/index.html + owner: www-data + group: www-data + mode: '0644' + +# Ensure Nginx is running +- name: Ensure Nginx is started and enabled + service: + name: nginx + state: started + enabled: yes +``` + +### Step 3: Create Handlers + +Edit `roles/nginx/handlers/main.yml`: + +```yaml +--- +- name: Restart Nginx + service: + name: nginx + state: restarted + +- name: Reload Nginx + service: + name: nginx + state: reloaded +``` + +**What are handlers?** +Handlers are special tasks that only run when notified by another task. They run at the end of the play, and only once even if notified multiple times. + +### Step 4: Set Variables + +Edit `roles/nginx/defaults/main.yml`: + +```yaml +--- +nginx_port: 80 +nginx_user: www-data +welcome_message: "Welcome to Nginx configured by Ansible!" +``` + +Edit `roles/nginx/vars/main.yml`: + +```yaml +--- +nginx_config_path: /etc/nginx/nginx.conf +``` + +**Difference between `defaults` and `vars`:** +- `defaults/`: Lowest priority, easily overridden. +- `vars/`: Higher priority, harder to override. + +### Step 5: Create Templates + +Create `roles/nginx/templates/nginx.conf.j2`: + +```nginx +user {{ nginx_user }}; +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen {{ nginx_port }}; + server_name _; + + location / { + root /var/www/html; + index index.html; + } + } +} +``` + +Create `roles/nginx/templates/index.html.j2`: + +```html + + + + Ansible Nginx + + +

{{ welcome_message }}

+

Server: {{ inventory_hostname }}

+

Port: {{ nginx_port }}

+ + +``` + +### Step 6: Use the Role + +Create a playbook `site.yml`: + +```yaml +--- +- name: Configure web servers + hosts: webservers + become: yes + + roles: + - role: nginx + nginx_port: 8080 + welcome_message: "Hello from my custom Nginx server!" +``` + +Run it: + +```bash +ansible-playbook -i inventory site.yml +``` + +## Ansible Galaxy + +Ansible Galaxy is a public repository where the community shares roles. + +### Installing Roles from Galaxy + +```bash +# Install a specific role +ansible-galaxy install geerlingguy.nginx + +# Install to a specific directory +ansible-galaxy install geerlingguy.nginx -p ./roles + +# Install from requirements file +ansible-galaxy install -r requirements.yml +``` + +**requirements.yml example:** + +```yaml +--- +- name: geerlingguy.nginx + version: 3.1.4 + +- name: geerlingguy.mysql +``` + +### Searching for Roles + +```bash +# Search for roles +ansible-galaxy search nginx + +# Get info about a role +ansible-galaxy info geerlingguy.nginx +``` + +## Best Practices + +1. **Use `defaults/` for variables**: Make your roles configurable with sensible defaults. +2. **Document your roles**: Add a `README.md` explaining variables and usage. +3. **Keep roles focused**: One role = one responsibility (e.g., nginx, mysql, not "webserver"). +4. **Use handlers**: Don't restart services unnecessarily. +5. **Test your roles**: Use Molecule or similar tools to test roles. +6. **Version your roles**: Use Git tags for versioning. +7. **Use `meta/main.yml`**: Document dependencies and metadata. diff --git a/Content/ansible/09-error-handling.md b/Content/ansible/09-error-handling.md new file mode 100644 index 0000000..a0787ed --- /dev/null +++ b/Content/ansible/09-error-handling.md @@ -0,0 +1,161 @@ + +# Ansible Error Handling + +- [Ignoring Errors](#ignoring-errors) +- [Defining Failure Conditions](#defining-failure-conditions) + - [failed_when](#failed_when) + - [changed_when](#changed_when) +- [Blocks, Rescue, and Always](#blocks-rescue-and-always) + - [Block Structure](#block-structure) + - [Example: Recovery Mechanism](#example-recovery-mechanism) +- [Debugging Strategies](#debugging-strategies) + - [The debug module](#the-debug-module) + - [Verbose Mode](#verbose-mode) + - [Playbook Debugger](#playbook-debugger) + + + +## Ignoring Errors + +By default, Ansible stops executing tasks on a host if a task fails. Sometimes, you want to continue execution even if a specific task fails. + +Use `ignore_errors: yes` to achieve this. + +```yaml +- name: This task will fail but playbook continues + command: /bin/false + ignore_errors: yes + +- name: This task will run + debug: + msg: "I am running even though the previous task failed!" +``` + +> [!WARNING] +> Use `ignore_errors` sparingly. It can hide actual problems in your automation. + +--- + +## Defining Failure Conditions + +Ansible decides if a task failed based on the return code. You can override this behavior. + +### failed_when + +You can define custom failure conditions using `failed_when`. + +```yaml +- name: Check if file exists + command: cat /tmp/myfile + register: result + failed_when: "'CRITICAL' in result.stdout" + ignore_errors: yes +``` + +In this example, the task only "fails" if the word "CRITICAL" is in the output, regardless of the exit code. + +### changed_when + +Ansible reports "changed" if a task modifies the system. Sometimes commands run successfully but don't actually change anything (e.g., a check command). + +```yaml +- name: Check service status + command: service nginx status + register: service_status + changed_when: false +``` + +This ensures the task reports "ok" (green) instead of "changed" (yellow), keeping your playbook output clean. + +--- + +## Blocks, Rescue, and Always + +Blocks allow you to group tasks together and apply error-handling logic, similar to `try/catch/finally` in programming languages. + +### Block Structure + +- **block**: The main tasks to execute. +- **rescue**: Tasks to run *only* if a task in the `block` fails. +- **always**: Tasks that run *no matter what* (success or failure). + +### Example: Recovery Mechanism + +```yaml +- name: Attempt to upgrade database + block: + - name: Upgrade database schema + command: /usr/local/bin/upgrade-db.sh + + - name: Restart database service + service: + name: postgresql + state: restarted + + rescue: + - name: Revert database schema (on failure) + command: /usr/local/bin/revert-db.sh + + - name: Send alert to Slack + uri: + url: https://hooks.slack.com/services/xxx/yyy + method: POST + body: '{"text": "Database upgrade failed! Reverted changes."}' + body_format: json + + always: + - name: Clean up temporary files + file: + path: /tmp/db_upgrade.log + state: absent +``` + +--- + +## Debugging Strategies + +### The debug module + +The most common way to debug is printing variables. + +```yaml +- name: Print a variable + debug: + var: result.stdout + +- name: Print a custom message + debug: + msg: "The value of x is {{ x }}" +``` + +### Verbose Mode + +When running playbooks, increase verbosity to see more details: + +- `-v`: Show task results. +- `-vv`: Show task results and input configuration. +- `-vvv`: Show connection information. +- `-vvvv`: Connection debugging (SSH level). + +```bash +ansible-playbook site.yml -vv +``` + +### Playbook Debugger + +Ansible has an interactive debugger. You can enable it on failure. + +```yaml +- name: Task that might fail + command: /bin/false + debugger: on_failed +``` + +When this task fails, Ansible drops you into an interactive shell where you can inspect variables and retry the task. + +```text +[host1] TASK: Task that might fail (debug)> p result +... +[host1] TASK: Task that might fail (debug)> update_task_args command="echo hello" +[host1] TASK: Task that might fail (debug)> redo +``` diff --git a/Content/ansible/README.md b/Content/ansible/README.md index c255271..051be7d 100644 --- a/Content/ansible/README.md +++ b/Content/ansible/README.md @@ -4,3 +4,15 @@ Here, in the Ansible lab, we’ll get a feel for what a configuration management and its strong intersection with other tools in the DevOps process, especially those used for infrastructure preperation and setup, as part of the broader Infrastructure as Code (IaC) landscape We’ll walk through a hands-on practice session to understand how Ansible works and have some practical fun together, so let’s get started. + +## Modules + +- [Introduction to Ansible](./01-intro.md) +- [Ansible Inventory](./02-inventory.md) +- [Ad-Hoc Commands](./03-ad-hoc-commands.md) +- [Playbooks](./04-playbooks.md) +- [Variables](./05-variables.md) +- [Conditionals and Loops](./06-conditionals-loops.md) +- [Templates](./07-templates.md) +- [Roles](./08-roles.md) +- [Error Handling](./09-error-handling.md)