Difference between revisions of "Ansible"
Plittlefield (talk | contribs) |
Plittlefield (talk | contribs) |
||
Line 15: | Line 15: | ||
== Installation == | == Installation == | ||
+ | |||
+ | sudo add-apt-repository ppa:ansible/ansible | ||
+ | sudo apt update | ||
+ | sudo apt install ansible | ||
+ | |||
+ | OLD | ||
sudo apt remove ansible | sudo apt remove ansible | ||
Line 20: | Line 26: | ||
sudo mv /lib/pythonX.XX/EXTERNALLY-MANAGED /root/ | sudo mv /lib/pythonX.XX/EXTERNALLY-MANAGED /root/ | ||
python3 -m pip install --user ansible | python3 -m pip install --user ansible | ||
− | |||
== Upgrade == | == Upgrade == |
Revision as of 06:53, 19 August 2024
Introduction
Ansible is an open-source software provisioning, configuration management, and application-deployment tool enabling infrastructure as code.
It is used to deploy and maintain many servers at once, from the command line.
- Control Node = Admin Computer
- Host Node = Remote Server
How To Install and Configure Ansible on Ubuntu 20.04
Requirements
Python 2.6+ or Python 3.5+
Installation
sudo add-apt-repository ppa:ansible/ansible sudo apt update sudo apt install ansible
OLD
sudo apt remove ansible sudo apt install python3-pip sudo mv /lib/pythonX.XX/EXTERNALLY-MANAGED /root/ python3 -m pip install --user ansible
Upgrade
python3 -m pip install --upgrade ansible ansible-core
Configuration
Add your hosts...
sudo nano /etc/ansible/hosts
[servers] myserver ansible_host=123.456.789.0 [all:vars] ansible_python_interpreter=/usr/bin/python3
Check the config...
ansible-inventory --list -y
all: children: servers: hosts: myserver: ansible_host: 123.456.789.0 ansible_python_interpreter: /usr/bin/python3 ungrouped: {}
Check the config with hosts file specified...
ansible-inventory --list -y -i ./inventory/hosts
Using an Ansible playbook with an SSH bastion / jump host
Add the following configuration inside your ~/.ssh/config file:
Host bastion User username Hostname bastion.example.com Host private-server-*.example.com ProxyJump bastion
https://www.jeffgeerling.com/blog/2022/using-ansible-playbook-ssh-bastion-jump-host
Testing
List Hosts
./inventory/hosts
[servers] server1 ansible_connection=ssh ansible_user=username1 domain.uk ansible_connection=ssh ansible_user=username2 nas ansible_connection=ssh ansible_user=username3
ansible -i ./inventory/hosts servers --list-hosts hosts (3): server1 domain.uk nas
Ping
ansible -i ./inventory/hosts servers -m ping ansible -i ./inventory/hosts server1.mydomain.com -m ping
Echo
Get all hosts to output a word on the command line...
ansible all -a "/bin/echo hello" -i ./inventory/hosts
Uptime
Uptime for a single host...
ansible hostname -m command -a "uptime" -i ./inventory/hosts
Uptime for all hosts in a group...
ansible groupname -m command -a "uptime" -i ./inventory/hosts
Uptime for all hosts...
ansible all -m command -a "uptime" -i ./inventory/hosts
AWS SSH Key
ansible all -m ping -u ubuntu --private-key=~/.ssh/myserver.pem
myserver | SUCCESS => { "changed": false, "ping": "pong" }
Commands
You can perform one off commands on a single host or multiple hosts.
Hostname
ansible all -u ubuntu --private-key=~/.ssh/myserver.pem -a "hostname -f"
Update Package Cache
ansible all -u ubuntu --private-key=~/.ssh/myserver.pem -a "sudo apt-get update"
Playbooks
Playbooks are YAML text files which contain commands and options in a text file, just like a docker compose file.
The file contains 'modules' which perform different tasks.
Examples
Add line to end of text file
- hosts: all tasks: - name: insert a line at the end of a file lineinfile: path: /to/the/file.txt line: last - name: check file debug: msg="{{ item }}" with_lines: tail -n1 /to/the/file.txt
See if hosts are alive by using only Gather Facts
99_gather.yml
- name: gather facts become: true hosts: all
... and run it on all but 1 host (by using the limit option with an exclamation mark (!host) (!host1:!host2)...
ansible-playbook -i ~/Bin/ansible-homelab/inventory/hosts ~/Bin/ansible-homelab/playbooks/ubuntu/99_gather.yml --limit '!server1'
Update Package List and Upgrade System
Create hosts file...
./inventory/hosts
[servers] server1 ansible_connection=ssh ansible_user=user1 server2 ansible_connection=ssh ansible_user=user2 server3 ansible_connection=ssh ansible_user=user3
Check the list of hosts is readable..
ansible -i ./inventory/hosts all --list-hosts
Create the playbook file...
./playbooks/apt.yml
- hosts: "*" become: yes tasks: - name: apt apt: update_cache: yes upgrade: 'yes'
Run the playbook command...
ansible-playbook ./playbooks/apt.yml -i ./inventory/hosts
To run this playbook on a single host or multiple hosts, you would use the -l (limit) option at the end of the command line...
ansible-playbook ./playbooks/apt.yml -i ./inventory/hosts -l server1 ansible-playbook ./playbooks/apt.yml -i ./inventory/hosts -l server1,server2
Create single directory using the 'file' module
Create the playbook...
nano mkdir.yml
- hosts: all tasks: - name: Ansible file module create directory file: path: ~/backups state: directory
Run the playbook (dry run)...
ansible-playbook -C mkdir.yml
Run the playbook...
ansible-playbook mkdir.yml
Create multiple directories using the 'file' module
Create the playbook...
nano mkdirs.yml
- hosts: all tasks: - name: Ansible create multiple directories with_items file: path: ~/backups/{{item}} state: directory with_items: - 'mysql' - 'repository' - 'config'
Run the playbook...
ansible-playbook mkdirs.yml
https://linuxhint.com/create-directory-ansible/
Install Apache web server software and start it on a Red Hat based system
- name: Playbook hosts: webservers become: yes become_user: root tasks: - name: ensure apache is at the latest version yum: name: httpd state: latest - name: ensure apache is running service: name: httpd state: started
Touch a file based on the date of another file
- hosts: alpine become: yes tasks: - name: Get stats of a file ansible.builtin.stat: path: /usr register: usr - name: Print a debug message ansible.builtin.debug: msg: "Path exists and is a directory" when: usr.stat.isdir is defined and usr.stat.isdir - name: Print a debug message ansible.builtin.debug: msg: "ctime is {{ usr.stat.ctime }}" - name: touch file /root/misc/system_installed file: path: /root/misc/system_installed state: touch modification_time: '{{ "%Y%m%d%H%M.%S" | strftime(usr.stat.ctime) }}'
Copying SSH Keys
https://medium.com/@visualskyrim/ansible-playbook-deploy-the-public-key-to-remote-hosts-da3f3b4b5481
Advanced Usage
Blocks, Rescue and Always
The tasks in the block
execute normally. If any tasks in the block return failed, the rescue
section executes tasks to recover from the error. The always
section runs regardless of the results of the block and rescue sections.
- name: Attempt and graceful roll back demo
block: - name: Print a message ansible.builtin.debug: msg: 'I execute normally' - name: Force a failure ansible.builtin.command: /bin/false - name: Never print this ansible.builtin.debug: msg: 'I never execute, due to the above task failing, :-(' rescue: - name: Print when errors ansible.builtin.debug: msg: 'I caught an error' - name: Force a failure in middle of recovery! >:-) ansible.builtin.command: /bin/false - name: Never print this ansible.builtin.debug: msg: 'I also never execute :-(' always: - name: Always do this ansible.builtin.debug: msg: "This always executes"
https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html
Example - Set up a server Firewall
This is good when you want to set up a server's firewall using the UFW module. It prevents you from accidentally blocking yourself out of a server :-)
- name: Setup admin production UFW hosts: my_hosts gather_facts: no tasks: - block: - name: Reset UFW ufw: state: reset - name: Allow outgoing ufw: default: allow direction: outgoing - name: Disallow ingoing ufw: default: deny direction: incoming - name: Establish regular admin rules ufw: rule: allow direction: in port: '5432' proto: tcp from_ip: '{{ item }}' loop: - 123.456.789/32 - 987.654.321/32 always: - name: Grant ssh access ufw: rule: allow direction: in port: '22' proto: tcp from_ip: 321.456.987/32 - name: Enable Firewall ufw: state: enabled
Example - Add Entry To Hosts File
This is useful for testing new web sites and for deploying docker containers in cinjunction with Traefik.
ansible-playbook --extra-vars "ip_address=192.168.0.189 host_name=example5 domain_name=domain.xyz db_name=example5domainxyz" 99_add_to_etc_hosts.yml
99_add_to_etc_hosts.yml
- name: add entry to computer hosts file hosts: localhost become: yes gather_facts: no tasks: - name: check variables debug: msg: "{{ ip_address }} {{ host_name}}.{{domain_name }}" - name: pausing to check domain_name variable pause: seconds: 10 - name: insert a line at the end of the file lineinfile: path: /etc/hosts line: "{{ ip_address }} {{ host_name }}.{{ domain_name }}" - name: check file debug: msg="{{ item }}" with_lines: tail -n1 /etc/hosts
Do Release Upgrade
IT DOES WORK BUT USE WITH EXTREME CAUTION
Variables
Standard variables
{{ ansible_facts["eth0"]["ipv4"]["address"] }}
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html
Custom variables
Example 1 - run the WordPress command line tool to install or update the software...
ansible-playbook wp.yml --extra-vars "major=true installcli=true"
wp.yml
- name: Install WordPress Cli register: wpclidonwloaded become: yes when: installcli is defined get_url: url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar dest: "{{ wpclipath }}" mode: '0755' owner: "{{ wpcliuser }}" group: "{{ wpcligroup }}" - name: Update WordPress Core (Major version) command: "{{ wpclipath }} core update" when: major is defined args: chdir: '{{ projects[inventory_hostname].blog_folder }}'
Example 2 - create a folder from a one-off command line custom variable...
ansible-playbook website.yml --extra-vars "domain=mybusiness.com"
website.yml
- name: create directory for web site file: path: "/var/www/{{ domain }}" state: directory
Advanced variables
Using variables for Docker container labels (for Traefik)
Here, you can pass the extra variables via the playbook command line and then that is used to build the docker container labels.
I won't lie, I spent 6 hours on this, and it was all solved by a jinja2 option 'items2dict' :)
Now, you too can create Docker containers which Traefik will dynamically add to the hosts because of the labels!
ansible-playbook --extra-vars "host_name=example4 domain_name=domain.xyz db_name=example4domainxyz" 99_dictionary_test_2.yml
- name: Dynamic dict hosts: localhost gather_facts: false vars: ID: - "{{ db_name }}" my_attributes: - key: "traefik.enable" value: "true" # - key: "traefik.http.routers.{{ ID[0] }}.entrypoints" - key: "traefik.http.routers.{{ db_name }}.entrypoints" value: "web" # - key: "traefik.http.routers.{{ ID[0] }}.rule" - key: "traefik.http.routers.{{ db_name }}.rule" value: "Host(`{{ host_name }}.{{ domain_name }}`)" tasks: - name: construct a dynamic dict and debug vars: manager_attributes: "{{ my_attributes | items2dict }}" debug: var: manager_attributes - name: pausing to check pause: seconds: 10 - name: deploy nginx docker container with custom labels community.docker.docker_container: name: "{{ db_name }}" image: nginx labels: "{{ my_attributes | items2dict }}" networks: - name: traefik_default state: started
Troubleshooting
ERROR: [WARNING]: sftp transfer mechanism failed on
[WARNING]: sftp transfer mechanism failed on [130.130.0.232]. Use ANSIBLE_DEBUG=1 to see detailed information
https://fantashit.com/warning-sftp-transfer-mechanism-failed/