Preparation Set 3
Instructions
-
- You will be provided with the root password.
-
- You need to use the hostname , if told in the instructions.
-
- You need to use the IP address, if told in the instructions.
-
- You need to ensure that you can SSH into every machine in your inventory without being prompted for a password.
Q1: Install and configure ansible in the control node
- a. Install the required packages
- b. Create a static inventory file /home/devops/ansible/hosts so that:
- i. Node1 is the member of developer host group
- ii. Node2 is the member of testing host group
- iii. Node3 is the member of production host group
- c. The production group is a member of the webservers host group
- d. Create a configuration file called /home/devops/ansible/ansible.cfg so that:
- i. The host inventory file is /home/devops/ansible/hosts
- ii. The default content collection directory is /home/devops/ansible/collections
- iii. The default role directory is /home/devops/ansible/roles
# Check all the required packages
[devops@ansible-server tasks]$ rpm -q epel-release
epel-release-9-9.el9.noarch
[devops@ansible-server tasks]$ rpm -q ansible-core
ansible-core-2.14.18-1.el9.aarch64
[devops@ansible-server tasks]$ rpm -q ansible
ansible-7.7.0-1.el9.noarch
# Configure indentation for ansible playbooks (yaml)
[devops@ansible-server ~]$ cat .vimrc
autocmd FileType yaml setlocal ai ts=2 sw=2 et
# Make sure ssh and ssh-copy-id is working in all the machines from the control node
[devops@ansible-server tasks]$ ssh devops@192.168.208.181
devops@192.168.208.181's password:
Last login: Mon Feb 24 14:29:40 2025 from 192.168.208.1
[devops@node1 ~]$ exit
logout
Connection to 192.168.208.181 closed.
[devops@ansible-server tasks]$ ssh-copy-id devops@192.168.208.181
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/devops/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
devops@192.168.208.181's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'devops@192.168.208.181'"
and check to make sure that only the key(s) you wanted were added.
# Configure ansible.cfg file and hosts file
[devops@ansible-server tasks]$ ls
ansible.cfg collections hosts roles
[devops@ansible-server tasks]$ cat ansible.cfg
[defaults]
inventory=/home/devops/tasks/hosts
roles_path=/home/devops/tasks/roles
collection_path=/home/devops/tasks/collections
remote_user=devops
[privilege_escalation]
become=true
[devops@ansible-server tasks]$ cat hosts
[developer]
node1
[testing]
node2
[production]
node3
[webservers:children]
production
# Ping all the machines
[devops@ansible-server tasks]$ ansible -m ping all
node1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
node2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
node3 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
# Add ANSIBLE_CONFIG Path
[devops@ansible-server ~]$ tail -1 .bashrc
export ANSIBLE_CONFIG=/home/devops/tasks/ansible.cfg
# Check the ansible config working
[devops@ansible-server ~]$ ansible --version
ansible [core 2.14.18]
config file = /home/devops/tasks/ansible.cfg
configured module search path = ['/home/devops/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3.9/site-packages/ansible
ansible collection location = /home/devops/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.9.18 (main, Sep 7 2023, 00:00:00) [GCC 11.4.1 20230605 (Red Hat 11.4.1-2)] (/usr/bin/python3)
jinja version = 3.1.2
libyaml = True
# Check the hosts
[devops@ansible-server ~]$ ansible all --list-hosts
hosts (3):
node1
node2
node3
Q2: Create and run a ansible playbook. As a system adminstrator you need to install software on managed hosts
- a. Create a playbook called playbook2.yml to create yum repositories on each of the managed nodes as per the following details:
- b. Note: You need to create 2 repos (BaseOS and AppStream) in the managed nodes.
- c. The file should be external_repos.repo file
BaseOS:
- name: BaseOs
- baseurl: http://192.168.208.100/softwares/BaseOS
- description: BaseOS Repo
- gpgcheck: yes
- enabled: yes
- key: http://192.168.208.100/softwares/RPM-GPG-KEY-centosofficial
AppStream:
- name: AppStream
- baseurl: http://192.168.208.100/softwares/AppStream
- description: AppStream Repo
- gpgcheck: yes
- enabled: yes
- key: http://192.168.208.100/softwares/RPM-GPG-KEY-centosofficial
[devops@ansible-server tasks]$ vim playbook2.yml
[devops@ansible-server tasks]$ cat playbook2.yml
- name: Playbook2
hosts: all
tasks:
- name: Import a key from a url
ansible.builtin.rpm_key:
state: present
key: http://192.168.208.100/softwares/RPM-GPG-KEY-centosofficial
- name: Add baseos repository
ansible.builtin.yum_repository:
name: BaseOS
description: BaseOS Repo
baseurl: http://192.168.208.100/softwares/BaseOS
gpgcheck: yes
enabled: yes
file: external_repos
gpgkey: http://192.168.208.100/softwares/RPM-GPG-KEY-centosofficial
- name: Add appstream repository
ansible.builtin.yum_repository:
name: AppStream
description: AppStream Repo
baseurl: http://192.168.208.100/softwares/AppStream
gpgcheck: yes
enabled: yes
file: external_repos
gpgkey: http://192.168.208.100/softwares/RPM-GPG-KEY-centosofficial
[devops@ansible-server tasks]$
[devops@ansible-server tasks]$
[devops@ansible-server tasks]$ ansible-playbook --syntax-check playbook2.yml
playbook: playbook2.yml
[devops@ansible-server tasks]$ ansible-playbook playbook2.yml
[devops@ansible-server tasks]$ ansible all -m command -a 'cat /etc/yum.repos.d/external_repos.repo'
Q3: Create a playbook called /home/devops/tasks/playbook3.yml that:
- Install the php and samba packages in the host in the developer, testing and production host groups only.
- Install the RPM development tools package group on hosts in the developer host group only
- Update all package to the latest version on hosts in the dev developer group only.
[devops@ansible-server tasks]$ vim playbook3.yml
[devops@ansible-server tasks]$ cat playbook3.yml
- name: Playbook3
hosts: all
tasks:
- name: Install the latest version of php and samba
ansible.builtin.yum:
name: "{{ item }}"
state: latest
loop:
- php
- samba
when: inventory_hostname in groups['developer']
- name: Install the 'RPM Development Tools' package group
ansible.builtin.yum:
name: "@RPM Development Tools"
state: present
when: inventory_hostname in groups['developer']
- name: Upgrade all packages
ansible.builtin.yum:
name: '*'
state: latest
[devops@ansible-server tasks]$ ansible-playbook --syntax-check playbook3.yml
[devops@ansible-server tasks]$ ansible-playbook playbook3.yml
Q4: Install the RHEL system roles package and create a playbook called /home/devops/tasks/playbook4.yml that:
- Runs on all managed host
- Use the timesync role
- Configure the role to use the timeserver
- Configure the role to set the iburst parameter as enabled
[devops@ansible-server tasks]$ sudo yum install rhel-system-roles
[devops@ansible-server tasks]$ cat playbook4.yml
- name: Manage timesync
hosts: all
vars:
timesync_ntp_servers:
- hostname: time.google.com
iburst: true
roles:
- /usr/share/ansible/roles/rhel-system-roles.timesync
Q5: Create a role in apache in /home/devops/ansible/roles with the following requirement
- The httpd package should be installed, httpd service should be enabled on boot, and started.
- The firewall is enabled and running with a rule to allow access to the webserver.
- A template file index.html.j2 exists(you have to create this file) and is used to create the file /var/www/html/index.html with the following output: Welcome to hostname on ipaddress, where hostname is the fully qualified domain name of the managed node and the ipaddress is the ipaddress of the managed node.
# Create index.html.j2 file
[devops@ansible-server templates]$ vim index.html.j2
[devops@ansible-server templates]$ cat index.html.j2
welcome to {{ ansible_facts['fqdn'] }} on {{ ansible_facts['default_ipv4']['address'] }}
# Create vars
[devops@ansible-server vars]$ cat main.yml
pkgs:
- httpd
- firewalld
firewall_svcs:
- http
- https
# Create tasks
[devops@ansible-server tasks]$ cat main.yml
- name: Install the latest version of Packages
ansible.builtin.yum:
name: "{{ item }}"
state: latest
loop: "{{ pkgs }}"
- name: Start service if not started
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: yes
loop: "{{ pkgs }}"
- name: permit traffic in default zone for https service
ansible.posix.firewalld:
service: "{{ item }}"
state: enabled
permanent: true
immediate: true
loop: "{{ firewall_svcs }}"
- name: Template a file to /var/www/html
ansible.builtin.template:
src: index.html.j2
dest: /var/www/html/index.html
# Create playbook file and execute
[devops@ansible-server tasks]$ cat playbook5.yml
- name: Playbook5
hosts: developer
roles:
- /home/devops/tasks/roles/apache
[devops@ansible-server tasks]$ ansible-playbook --syntax-check playbook5.yml
playbook: playbook5.yml
[devops@ansible-server tasks]$ ansible-playbook playbook5.yml
# Check
[devops@ansible-server tasks]$ ansible developer -m command -a 'cat /var/www/html/index.html'
node1 | CHANGED | rc=0 >>
welcome to node1 on 192.168.208.136
[devops@ansible-server tasks]$ curl http://192.168.208.181
welcome to node1 on 192.168.208.136
Q6: Use Ansible galaxy with the requirement file /home/devops/tasks/roles/requirements.yml to download and install roles to /home/admin/ansible/roles from the following URLs:
- http://192.168.208.181/downloads/role1.tar.gz . The name of this role should be role1.
- http://192.168.208.181/downloads/role2.tar.gz . The name of this role should be role2.
[devops@ansible-server tasks]$ cd roles/
[devops@ansible-server roles]$ cat requirements.yml
- src: http://192.168.208.100/downloads/role1.tar.gz
name: role1
- src: http://192.168.208.100/downloads/role2.tar.gz
name: role2
[devops@ansible-server roles]$ ansible-galaxy role install -r requirements.yml
Q7: Create a playbook called role1.yml as per the following details.
- The playbook contains a play that runs on hosts in the developer group and uses the role1 role present in your machine.
[devops@ansible-server tasks]$ cat playbook6.yml
- name: Playbook6
hosts: developer
roles:
- /home/devops/tasks/roles/role1
[devops@ansible-server tasks]$ curl http://192.168.208.181
Role1 Tasks !!!
welcome to node1 on 192.168.208.136
Q8: Create a playbook called test.yml as per the following details:
- The playbook runs on the managed nodes in the developer host group.
- Create directory
/webtest
with the group ownershipwebgroup
and having the regular permission rwx for the owner and group, and rx for the others. - Apply the special group permission: set group ID
- Symbollically link /var/www/html/webtest to /webtest directory.
- Create the file /webtest/index.html with a single line of text that reads :Testing.
[devops@ansible-server tasks]$ cat playbook8.yml
- name: Playbook8
hosts: developer
tasks:
- name: Ensure group "webgroup" exists
ansible.builtin.group:
name: webgroup
state: present
- name: Create directory
ansible.builtin.file:
path: /webtest
state: directory
group: webgroup
owner: root
mode: '2775'
- name: Create a symbolic link
ansible.builtin.file:
src: /webtest
dest: /var/www/html/webtest
state: link
- name: Copy using inline content
ansible.builtin.copy:
content: "Testing\n"
dest: /webtest/index.html
group: webgroup
owner: root
mode: '0664'
- name: Appending the group 'webgroup' and 'apache'
ansible.builtin.user:
name: apache
groups: webgroup
append: yes
If curl is returning forbidden request, then set selinux to permissive
- name: Set SELinux to permissive
ansible.posix.selinux:
policy: targeted
state: permissive
Q9: Create an ansible vault to store user password with the following conditions:
- The name of the vault is vault.yml
- The vault contains two variables dev_pass with value as redhat and mgr_pass with value as linux respectively.
- The password to encrypt and decrypt the vault is devops
- The password is stored in the file /home/devops/ansible/password.txt file.
[devops@ansible-server tasks]$ cat password.txt
devops
[devops@ansible-server tasks]$ ansible-vault create --vault-password-file password.txt vault.yml
[devops@ansible-server tasks]$ ansible-vault view vault.yml
Vault password:
dev_pass: redhat
mgr_pass: linux
Q10: Generate host files:
- Download an initial template file called hosts.j2 from the below URL: http://192.168.208.100/content/hosts.j2 to /home/devops/tasks/directory. Complete the template so that it can be used to generate a file with a line for each inventory host in the same format as /etc/hosts.
- Create a playbook called playbook10.yml that uses this template to generate the file /etc/myhosts on hosts in the all host group
- When completed, the file /etc/myhosts on hosts in the all host group should have a line for each managed host:
[devops@ansible-server tasks]$ vim playbook10.yml
[devops@ansible-server tasks]$ cat playbook10.yml
- name: Playbook10
hosts: all
tasks:
- name: Template a file
ansible.builtin.template:
src: /home/devops/tasks/hosts.j2
dest: /etc/myhosts
[devops@ansible-server tasks]$ cat hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for x in groups['all'] %}
{{ hostvars[x]['ansible_facts']['default_ipv4']['address'] }} {{ hostvars[x]['ansible_facts']['hostname'] }} {{ hostvars[x]['ansible_facts']['fqdn'] }}
{% endfor %}
[devops@ansible-server tasks]$ ansible all -m command -a 'cat /etc/myhosts'
node3 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.208.136 node1 node1
192.168.208.151 node2 node2
192.168.208.152 node3 node3
node1 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.208.136 node1 node1
192.168.208.151 node2 node2
192.168.208.152 node3 node3
node2 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.208.136 node1 node1
192.168.208.151 node2 node2
192.168.208.152 node3 node3
Q11: Create a playbook called playbook11.yml that produces an output file called /root/hwreport.txt on all the managed nodes with following information
- Inventory Hostname :
- Total Memory in MB :
- BIOS Version :
- nvme0n1 Size :
- nvme0n2 Size : Each line of the output file contains a single key-value pair. If no device then write NONE
[devops@ansible-server tasks]$ cat playbook11.yml
- name: Playbook11
hosts: all
tasks:
- name: Template a file
ansible.builtin.template:
src: /home/devops/tasks/hwreport.j2
dest: /root/hwreport.txt
[devops@ansible-server tasks]$ cat hwreport.j2
* Inventory Hostname : {{ ansible_facts['hostname'] }}
* Total Memory in MB : {{ ansible_facts['memtotal_mb'] }}
* BIOS Version : {{ ansible_facts['bios_version'] }}
* nvme0n1 Size : {% if ansible_facts['devices']['nvme0n1'] is defined %} {{ ansible_facts['devices']['nvme0n1']['size'] }} {% else %} NONE {% endif %}
* nvme0n2 Size : {% if ansible_facts['devices']['nvme0n2'] is defined %} {{ ansible_facts['devices']['nvme0n2']['size'] }} {% else %} NO
NE {% endif %}
Q12: Create a playbook called /home/devops/tasks/playbook12.yml as per the following requirements
- The playbook runs on all inventory hosts
- The playbook replaces the contents of /etc/issue with a single line of text as:
- On host in the developer host group, the line reads: Development
- On host in the testing host group, the line reads: Testing
- On host in the production host group, the line reads: Production
[devops@ansible-server tasks]$ vim playbook12.yml
[devops@ansible-server tasks]$ cat playbook12.yml
- name: Playbook12
hosts: all
tasks:
- name: Copy using inline content for developer
ansible.builtin.copy:
content: "Development\n"
dest: /etc/issue
when: inventory_hostname in groups['developer']
- name: Copy using inline content for testing
ansible.builtin.copy:
content: "Testing\n"
dest: /etc/issue
when: inventory_hostname in groups['testing']
- name: Copy using inline content for production
ansible.builtin.copy:
content: "Production\n"
dest: /etc/issue
when: inventory_hostname in groups['production']
[devops@ansible-server tasks]$ ansible-playbook playbook12.yml
[devops@ansible-server tasks]$ ansible all -m command -a 'cat /etc/issue'
node3 | CHANGED | rc=0 >>
Production
node2 | CHANGED | rc=0 >>
Testing
node1 | CHANGED | rc=0 >>
Development
Q13: Rekey an existing ansible vault as per the following condition:
- Use the vault.yml file that you have created earlier
- Set the new vault password as ansible
- The vault remains in an encrypted state with the new password
[devops@ansible-server tasks]$ vim password.txt
[devops@ansible-server tasks]$ cat password.txt
ansible
[devops@ansible-server tasks]$ ansible-vault rekey --new-vault-password-file=password.txt vault.yml
Vault password:
Rekey successful
# Using new vault password - ansible
[devops@ansible-server tasks]$ ansible-vault view vault.yml
Vault password:
dev_pass: redhat
mgr_pass: linux
Q14: 14. Create user accounts. A list of users to be created can be found in the file called user_list.yml which you should download from "http://192.168.208.100/content/user_list.yml" and save to /home/devops/tasks/ directory. Using the password vault created elsewhere in this exam, create a playbook called playbook14.yml that creates user accounts as follows:
- Users with a job description of developer should be created on managed nodes in the developer and testing host groups assigned the password from the dev_pass variable and is a member of supplementary group hit_developer.
- Users with a job description of manager should be created on managed nodes in the production host group assigned the password from the mgr_pass variable and is a member of supplementary group hit_manager.
- Passwords should use the SHA512 hash format. Your playbook should work using the vault password file created elsewhere in this exam.
[devops@ansible-server tasks]$ cat user_list.yml
users:
- name: dilane
job: developer
uid: 3300
- name: fahim
job: manager
uid: 3301
- name: safayet
job: developer
uid: 3302
[devops@ansible-server tasks]$ cat playbook14.yml
- name: Playbook14 developer
hosts: developer,testing
vars_files:
- /home/devops/tasks/vault.yml
- /home/devops/tasks/user_list.yml
tasks:
- name: Ensure group "hit_developer" exists
ansible.builtin.group:
name: hit_developer
state: present
- name: Add the user
ansible.builtin.user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
group: hit_developer
password: "{{ dev_pass | password_hash('sha512') }}"
loop: "{{ users }}"
when: item.job == "developer"
- name: Playbook14 manager
hosts: production
vars_files:
- /home/devops/tasks/vault.yml
- /home/devops/tasks/user_list.yml
tasks:
- name: Ensure group "hit_manager" exists
ansible.builtin.group:
name: hit_manager
state: present
- name: Add the user
ansible.builtin.user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
group: hit_manager
password: "{{ dev_pass | password_hash('sha512') }}"
loop: "{{ users }}"
when: item.job == "manager"
Q15: Configure Cron Jobs: Create /home/devops/tasks/playbook15.yml playbook as per the following requirement
- This playbook runs on all managed nodes in the hostgroup
- Configure cronjob, which runs every 2 minutes and executes the following command: logger "EX294 exam in progress" and runs as user natasha
[devops@ansible-server tasks]$ cat playbook15.yml
- name: Playbook15
hosts: all
tasks:
- name: Add the user
ansible.builtin.user:
name: mohit
- name: Ensure a job
ansible.builtin.cron:
name: "cron log"
minute: "*/2"
user: mohit
job: 'logger "EX294 exam in progress"'
[devops@ansible-server tasks]$ ansible all -m command -a 'crontab -l -u mohit'
node3 | CHANGED | rc=0 >>
#Ansible: cron log
*/2 * * * * logger "EX294 exam in progress"
node1 | CHANGED | rc=0 >>
#Ansible: cron log
*/2 * * * * logger "EX294 exam in progress"
node2 | CHANGED | rc=0 >>
#Ansible: cron log
*/2 * * * * logger "EX294 exam in progress"
Q16: Create & use a logical volume: Create a playbook called /home/devops/tasks/playbook16.yml that runs on all the managed nodes and does the following:
- Creates a logical volume with the following requirements:
- The logical volume is created in the developer volume group.
- The logical volume name is data.
- The logical volume size is 1200 Mib.
- Format the logical volume with the ext file-system.
- If the requested logical volume size cannot be created, the error message "could not create logical volume of that size" should be displayed and size 800 MiB should be used instead.
- If the volume research does not exist, the error message "volume group does not exist" should be displayed.
- Don't mount the logical volume in any way.
[devops@ansible-server tasks]$ vim playbook16.yml
[devops@ansible-server tasks]$ cat playbook16.yml
- name: Playbook16
hosts: all
tasks:
- name: block,rescue,always
block:
- name: If VG not present
ansible.builtin.debug:
msg: "volume group does not exist"
when: ansible_facts['lvm']['vgs']['research'] is not defined
- name: Create a logical volume of 1200m
community.general.lvol:
vg: research
lv: data
size: 1200
when: ansible_facts['lvm']['vgs']['research'] is defined
rescue:
- name: If VG size is not sufficient
ansible.builtin.debug:
msg: "could not create logical volume of that size"
when: ansible_facts['lvm']['vgs']['research'] is defined
- name: Create a logical volume of 800m
community.general.lvol:
vg: research
lv: data
size: 800
when: ansible_facts['lvm']['vgs']['research'] is defined
always:
- name: Create a ext4
community.general.filesystem:
fstype: ext4
dev: /dev/research/data
when: ansible_facts['lvm']['vgs']['research'] is defined
[devops@ansible-server tasks]$ ansible-playbook playbook16.yml