Linux:Ansible-Ivo
This page was written and contributed by Ivo Palli.
TODO: v vaults v use external variable files
- actions on regex (kan met lineinfile)
v create crontab v create repo v create, download and use roles (!)
- configure selinux
- jinja2 (.j2) templates (gewoon een text file waar je variables Template:Xx in kan zetten, en dan kopieert met module template
- check info over installeren van python en instellen
v ansible-galaxy
- tags
Ansible
- On the control node, set up a separate account 'ansible' with SSH keys to the three client nodes
- On ALL nodes. We do this first so we can ssh-copy-id over the credentials later:
useradd ansible # Can be any username echo password | passwd --stdin ansible echo "ansible ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/ansible # Make sure this user can do root commands
- On the CONTROL node:
cat << 'EOF' >> /etc/hosts # We're not setting up DNS 192.168.1.1 control 192.168.1.2 ansible1 192.168.1.3 ansible2 192.168.1.4 ansible3 EOF echo 'control.example.com' > /etc/hostname # Ubuntu: apt-add-repository --yes --update ppa:ansible/ansible apt install ansible # RHEL: yum -y install python3 python3-pip pip3 install --upgrade pip alternatives --set python /usr/bin/python3 su - ansible pip3 install ansible --user # We're installing this to the user account only # If you get after "connection broken by NewConnectionError", check your DNS ansible --version ssh-keygen # Default settings for everything. Just hit Enter ssh-copy-id ansible1 # Do so for all nodes ssh ansible1 sudo yum -y install python3 python3-pip # Install and set prerequisites on all clients ssh ansible1 sudo alternatives --set python /usr/bin/python3 # Do so for all nodes
ansible.cfg
[defaults] inventory=inventory remote_user=ansible host_key_checking=false deprecation_warnings=false forks=10 roles_path = roles:/usr/share/ansible/roles [privilege_escalation] become=false become_method=sudo become_user=root become_ask_pass=false
- You can view all settings you can set with: ansible-config list
- You can check for spelling errors in the config with ansible-config dump --only-changed
- If the exam says 'Privilege escalation is disabled by default.' then set 'become=false' and run playbooks with the 'ansible-playbook -b' switch (on set 'become: true' in the playbook itself) .
- Instead of 'fork', you can also batch servers in groups with 'serial'. https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html
- Check /etc/ansible/ansible.cfg if you're not sure about options
inventory
[an1] ansible1 # serv1 is an alias [an2] ansible2 serv1 ansible_hostname=serv1.longdomain.nl [hosts_with_ranges] server[1:3].example.com 192.168.1.[1:5] [variable_single_host_example] ansible1 my_var=5 my_other_var=6 # Define vars for every host in a group, in this case group an2 [an2:vars] my_var=7 # Create group an with subgroups an1 and an2 [an:children] an1 an2
You can also add variables per host in a 'host_vars' directory with files names as the host. Or in a 'group_vars' directory, named as the defined groups.
Adhoc examples
ansible all -m user -a "name=lisa" # Adds a user lisa to all specified hosts
ansible all -m command -a "id lisa" # Shows the attributes of user lisa
ansible all -m user -a "name=lisa state=absent remove=yes" # Removes a user (if present) and removes the home directory
ansible all -m ping # See if the host is responsive
ansible ansible1 -m copy -a 'content="Hello world" dest=/tmp/hello' # Put something in a file
ansible ansible1 -m raw -a "yum -y install python3" # Since 'raw' doesn't use python, we can use it to set up python
ansible localhost -m debug -a "msg='{{lookup('file', '/etc/motd')}}'" # Use lookup to insert the content of a file
ansible-doc
ansible-doc --list # Show all modules ansible-doc user # Show the documentation for modules 'user' ll /usr/lib/python3/dist-packages/ansible/modules/ # See all the modules
Creating a batch file to set up accounts for ansible
Ok, so a host NEEDS python installed to run ansible commands. If no python is present, you can still use the 'raw' module in ansible since this has no dependencies. You can use the ping module to see if ansible would work (ping in this context is not the network ping).
#!/bin/bash # Execute this as root. (Assuming root has passwordless access to all servers) # (If not, I suggest ssh-copy-id) export ANSIBLE_CONFIG=/home/automation/plays export ANSIBLE_REMOTE_USER=root ansible all -m ping ansible all -m raw -a "adduser automation" ansible all -m raw -a "mkdir /home/automation/.ssh" # Note that you can read in id_rsa.pub in nano with CTRL-R ansible all -m raw -a "echo 'ssh-rsa AAAxxxxxx' > /home/automation/.ssh/authorized_keys" # TAKE NOTE OF THE FORMAT! Check the existing %wheel in /etc/sudoers when in doubt! ansible all -m raw -a "echo -e 'automation\tALL=(ALL)\tNOPASSWD: ALL'" > /etc/sudoers.d/automation"
but if ping fails, also do:
ssh ansible1 sudo yum -y install python3 python3-pip # Install and set prerequisites on all clients ssh ansible1 sudo alternatives --set python /usr/bin/python3
If ping works (i.e. ansible works 100% on the target server) then you can use ansible native commands:
#!/bin/bash export ANSIBLE_CONFIG=/home/automation/plays export ANSIBLE_REMOTE_USER=root ansible all -m user -a "name=automation state=present" ansible all -m file -a "path=/home/automation/.ssh state=directory" ansible all -m copy -a "src=/home/automation/.ssh/id_rsa.pub dest=/home/automation/.ssh/authorized_keys" ansible all -m copy -a "dest=/etc/sudoers.d/automation content='automation\tALL=(ALL)\tNOPASSWD: ALL\n'"
Important modules
- ping
You can use this to test is a host is reachable and able to run Ansible playbooks
- user
Handles user creation, modification, creating SSH keys, etc. Generate SHA512 passwords with user_password=$(openssl passwd -6 my_password)
- dnf, yum, apt, package
Package management, although 'package' tries to be platform agnostic
- service
start, stop, restart, enable services
- copy
speaks for itself
- file
attributes, symlinks and directories. You can copy files around in the remote filesystem.
- lineinfile
basically "sed -i" with regex support, or adding lines if they don't exist
- archive
create or extract archives
- shell / command
Execute CLI commands of which the output you can store in a variable with 'register: xxx'. Use 'xxx.stdout' in your script
- setup
Gives you a dump of all the 'facts' of the target server. Use the 'filter' option to reduce the output.
- include
Lets you insert other playbooks
- mysql_user
Do MySQL user related things
- debug
Help you debug playbooks but can also be used to simple output a message to the user with 'msg:'
https://opensource.com/article/19/9/must-know-ansible-modules
Playbooks / YAML
- NEVER USER TABS!
- Most commonly used indentation is 2 spaces
- Elements on the same level are indented the same
- List items start with a dash (-)
- Child elements are indented further
- A playbook starts with '---' and ends with '...'
- A playbook has at least 1 play (i.e. top level list item)
- Comments start with a '#'
Runs playbooks with:
ansible-playbook [-b] [-e var1=val1 [...]] [--syntax-check] vsftpd.yml
Setting up nano
On freshly installed hosts, you usually only have nano (and vi). Which can be made better for YAML files:
cat << 'EOF' > $HOME/.nanorc set autoindent set tabsize 2 set tabstospaces EOF
Playbook examples
vsftpd.yml
---
# The first level of elements are the plays themselves
- name: deploy vsftpd
hosts: ansible2.example.com
# Then the tasks are defined on how to complete this play
tasks:
# Since this is a list of tasks, you put a dash first.
- name: install vsftpd # This is the text that is shown during the playbook run. Note that this comment is not shown
yum: name=vsftpd # yum in is the module. You can add arguments to the module on the same line, as in adhoc or spread them out on the next lines
- name: enable vsftpd
service: name=vsftpd enabled=true
- name: create readme file
copy:
content: "welcome to this FTP server"
dest: /var/ftp/pub/README
force: no
mode: 0444
...
facts.yml
---
- name: We're using facts!
hosts: all
become: true
tasks:
- name: task1
debug:
msg: "doing task1"
- name: task2
debug:
msg: "This is host {{ansible_hostname}}"
...
Set up repository
---
- name: Set up repo
gather_facts: false
hosts: localhost
tasks:
- name: Setting up
yum_repository:
name: mysql80-community
description: MySQL 8.0 YUM Repo
gpgcheck: 1
enabled: 1
baseurl: http://repo.mysql.com/yum/mysql-8.0-community/el/8/x86_64/
gpgkey: http://repo.mysql.com/RPM-GPG-KEY-mysql
...
Do something on first number of UID
---
- name: Doing a regex test
gather_facts: false
hosts: localhost
vars:
- uid: 1234
tasks:
- name: A test
debug:
msg: "True"
when: (uid | string)[0] | int == 1
...
Creating LVMs
---
- name: Using LVMs
hosts: localhost
gather_facts: no
become: true
tasks:
- name: Partition /dev/sdb
parted:
device: /dev/sdb
part_start: 0%
part_end: 800MB
number: 1
flags: [ lvm ]
state: present
- name: Create Volume Group
lvg:
vg: vg_database
pvs: /dev/sdb1
- name: Create LV
lvol:
lv: lv_mysql
vg: vg_database
size: 512M
shrink: false
- name: Create XFS filesystem on lv1
filesystem:
dev: /dev/vg_database/lv_mysql
fstype: xfs
- name: Create mount point
file:
path: /mnt/mysql_backups
state: directory
- name: Mount lv1 to /data/lv1
mount:
path: /mnt/mysql_backups
src: /dev/vg_database/lv_mysql
fstype: xfs
state: mounted
...
Note that if you do something with a VDO, that one needs a systemd startup script, not an fstab entry. So add to the 'mount' section:
opts: defaults,x-systemd.requires=vdo.service
These options can also be found in the VDO man page.
Creating an archive
---
- name: Backup /etc directory
hosts: localhost
become: true
gather_facts: false
tasks:
- name: Archiving
archive:
path: /etc
dest: /tmp/etc.tgz
format: gz
...
Setting swappiness
- name: Set vm.swappiness to 5 in /etc/sysctl.conf
sysctl:
name: vm.swappiness
value: '5'
state: present
cron jobs
Install, enable and start service if needed. Think 'at/atd' or 'cronie/crond'
- name: backup users database tomorrow
at:
command: 'tar -czf /root/users.tgz /etc/passwd /etc/group /etc/shadow'
count: 1
units: days
unique: true
or
- name: backup users database tomorrow
cron:
name: Backup Users
hour: 5
minute: 30
weekday: 1-5
user: root
job: 'tar -czf /root/user.tgz /etc/passwd /etc/shadow'
cron_file: user_backup
flowcontrol.yml
---
- name: Flow control
hosts: all
become: false
gather_facts: false
tasks:
- name: Loop
debug:
msg: "This is loop: {{ item }}"
loop:
- alpha
- beta
- gamma
- name: Conditional
debug:
msg: "Variable hello is set to: {{ hello }}"
when: hello is defined and hello != ''
...
Useful variables
- group_names
List of groups the current host is part of when: "'webservers' in group_names"
- when: (item.uid | string)[0] | int == 1
Get first number of uid
Docs:
- https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
- https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html
Variables
---
- name: Storing results and testing them
hosts: localhost
gather_facts: false
vars:
- here: are
- some: more
vars_files:
- vars/users
- vars/groups
tasks:
- name: Register a variable
shell: cat /etc/motd
register: motd_contents
- name: Use the variable in conditional statement
debug: msg="motd contains the word in"
when: motd_contents.stdout.find('in') != -1
- name: More! More!
debug:
msg: "here:{{here}} some:{{some}}"
- name: var_files
debug:
msg: "user: {{item.name}} has {{item.password}} password"
loop: "{{ users }}"
...
Handlers
Note that handlers only run once, and only when the tasks are completed
---
- name: Using a handler
gather_facts: false
hosts: localhost
tasks:
- name: Do something that has a non-ok state
file:
path: /tmp/123
state: touch
notify: result_handler
handlers:
- name: result_handler
debug:
msg: "Something happened!"
...
You can force handlers to run at a specific point, for example a reboot before you can continue with the other tasks. You do this via an:
- name: run handlers now meta: flush_handlers
Firewall
---
- name: Setting up the firewall
gather_facts: false
hosts: localhost
tasks:
- name: Firewall Package
package:
name: "{{ firewall_pkg }}"
state: present
- name: Firewall Service
service:
name: "{{ firewall_pkg }}"
enabled: true
state: started
- name: UFW Ubuntu
ufw:
state: enabled
policy: deny
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "80"
- "22"
when: ansible_distribution == "Ubuntu"
- name: Firewalld CentOS
firewalld:
service: "{{ item }}"
permanent: true
immediate: true
state: enabled
loop:
- "http"
- "ssh"
when: ansible_distribution == "CentOS"
...
Set boot to graphical
Just softlink the right target to /etc/systemd/system/default.target
---
- name: set default boot target
hosts: ansible2
tasks:
- name: set boot target to graphical
file:
src: /usr/lib/systemd/system/graphical.target
dest: /etc/systemd/system/default.target
state: link
Generating user passwords
You can use Python's function to hash a password:
# Leave the salt empty or omit it to let the function generate one for you
ansible localhost -m debug -a "msg={{ 'my_password' | password_hash('sha512', 'my_salt') }}"
This is equal to:
openssl passwd -6 -salt my_salt my_password
Vault
ansible-vault create more_vars.yml # For newly created files. Will open an editor ansible-vault encrypt more_vars.yml # To encrypt already existing files ansible-vault view more_vars.yml ansible-vault edit more_vars.yml ansible-playbook --ask-vault-pass some_script.yml # Will ask for a password ansible-playbook --vault-password-file=passwd.txt some_script.yml # Reads password from a file.. not really secure
Roles
Creating
- Add 'roles_path = roles' to the ansible.cfg
ansible-galaxy role init roles/firewall ansible-galaxy list
- Now you edit 'roles/firewall/tasks/main.yml' and put the tasks in there
- In the calling playbook you add the roll, which will run it automatically. The 'service_name' var is referenced in the role
---
- name: My playbook
vars:
- service_name: http
roles:
- firewall
Downloading
ansible-galaxy search php ansible-galaxy search --author geerlingguy php ansible-galaxy info geerlingguy.php ansible-galaxy install geerlingguy.php ansible-galaxy list
RHEL System Roles
RHEL System Roles are based on the community Linux System Roles and provide a uniform interface to make configuration tasks easier where significant differences may exist between versions of the managed operating system.
yum install rhel-system-roles
Also make sure you ansible.cfg has the default location for package installed roles included:
roles_path = roles:/usr/share/ansible/roles
To use them, you set the parameters in vars and then include the role:
vars:
- selinux_policy: targeted
- selinux_state: enforcing
- include_role:
name: rhel-system-roles.selinux
When looking for help: /usr/share/ansible/roles/rhel-system-roles.selinux/README.md Something simpler:
---
- hosts: ansible
vars:
timesync_ntp_servers:
- hostname: pool.ntp.org
iburst: yes
roles:
- rhel-system-roles.timesync
Doing it directly:
- name: Set httpd_can_network_connect flag on and keep it persistent across reboots
seboolean:
name: httpd_can_network_connect
state: yes
persistent: yes
Doing it via a system role:
---
- name: Enable the boolean httpd_can_network_connect
hosts: webserver
become: yes
vars:
selinux_booleans:
- name: httpd_can_network_connect
state: on
persistent: yes
roles:
- linux-system-roles.selinux
Commonly used facts
- ansible_facts[‘distribution’]
- ansible_facts[‘distribution_major_version’]
- ansible_facts[‘os_family’]
Create your own facts
---
- hosts: database
become: yes
tasks:
- name: Ensure the directory exists
file:
path: "/etc/ansible/facts.d/"
state: directory
recurse: yes
- name: Copy content to file in new directory
copy:
content: "[sample_exam]\nserver_role=mysql\n"
dest: "/etc/ansible/facts.d/custom.fact"
...