The Nautilus DevOps team has some data on each app server in Stratos DC that they want to copy to a different location. However, they want to create an archive of the data first, then they want to copy the same to a different location on the respective app server. Additionally, there are some specific requirements for each server. Perform the task using Ansible playbook as per requirements mentioned below:
Create a playbook named playbook.yml under /home/thor/ansible directory on jump host, an inventory file is already placed under /home/thor/ansible/ directory on Jump Server itself.
- Create an archive
beta.tar.gz(make sure archive format istar.gz) of/usr/src/itadmin/directory ( present on each app server ) and copy it to/opt/itadmin/directory on all app servers. The user and groupownerof archivebeta.tar.gzshould betonyforApp Server 1,steveforApp Server 2andbannerforApp Server 3.
Note: Validation will try to run playbook using command ansible-playbook -i inventory playbook.yml so please make sure playbook works this way, without passing any extra arguments.
Introduction
In the fast-paced world of DevOps, automation is the key to maintaining efficiency, consistency, and reliability across infrastructure. The Nautilus DevOps team recently faced a challenge: they needed to archive data from multiple application servers and move it to different locations with specific ownership requirements. Instead of performing this task manually on each server, they turned to Ansible—and the results were outstanding.
This comprehensive guide walks through the entire process of creating an Ansible playbook to archive a directory and copy it to a different location with specific ownership requirements across multiple servers.
Table of Contents
- Understanding the Requirements
- Prerequisites and Setup
- Inventory Configuration
- Playbook Design and Implementation
- Execution and Verification
- Troubleshooting Common Issues
- Best Practices and Key Takeaways
- Conclusion
Understanding the Requirements
The DevOps team had specific requirements for all application servers in the Stratos DC:
| Component | Details |
|---|---|
| Source Directory |
/usr/src/finance/ (exists on each app server) |
| Destination Directory |
/opt/finance/ (create if not exists) |
| Archive Name | official.tar.gz |
| Archive Format | tar.gz (compressed archive) |
| Server | Owner & Group |
| stapp01 | tony |
| stapp02 | steve |
| stapp03 | banner |
The Task Must Be Performed Using:
ansible-playbook -i inventory playbook.yml
No additional arguments should be required.
Prerequisites and Setup
Before diving into the implementation, ensure the following prerequisites are met:
1. Ansible Installation
# Verify Ansible is installed
ansible --version
# If not installed, install via yum
sudo yum install -y ansible
2. SSH Connectivity
# Test SSH connectivity to each server
ssh -o StrictHostKeyChecking=no tony@stapp01 "echo 'Connected'"
ssh -o StrictHostKeyChecking=no steve@stapp02 "echo 'Connected'"
ssh -o StrictHostKeyChecking=no banner@stapp03 "echo 'Connected'"
3. Directory Structure
# Create the working directory
mkdir -p /home/thor/ansible
cd /home/thor/ansible
Inventory Configuration
The inventory file serves as the foundation for Ansible connectivity. Here's the complete inventory configuration:
cat > /home/thor/ansible/inventory << 'EOF'
[app_servers]
stapp01 ansible_user=tony ansible_ssh_pass=Ir0nM@n ansible_ssh_common_args='-o StrictHostKeyChecking=no'
stapp02 ansible_user=steve ansible_ssh_pass=Am3ric@ ansible_ssh_common_args='-o StrictHostKeyChecking=no'
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_ssh_common_args='-o StrictHostKeyChecking=no'
EOF
Inventory Breakdown:
-
Group:
[app_servers]- All application servers - Host: Unique identifier for each server
- ansible_user: SSH username for authentication
- ansible_ssh_pass: SSH password for authentication
- ansible_ssh_common_args: Additional SSH options
- StrictHostKeyChecking=no: Disables host key verification
Verify Inventory
# Test inventory connectivity
ansible -i inventory app_servers -m ping
Playbook Design and Implementation
The playbook is designed with idempotency, error handling, and flexibility in mind.
Complete Playbook
cat > /home/thor/ansible/playbook.yml << 'EOF'
---
- name: Archive and copy finance directory across app servers
hosts: app_servers
gather_facts: no
vars:
source_dir: /usr/src/finance
dest_dir: /opt/finance
archive_path: /opt/finance/official.tar.gz
tasks:
# Task 1: Create destination directory
- name: Create destination directory
file:
path: "{{ dest_dir }}"
state: directory
mode: '0755'
become: yes
# Task 2: Verify source directory exists
- name: Verify source directory exists
stat:
path: "{{ source_dir }}"
register: source_stat
become: yes
failed_when: not source_stat.stat.exists
# Task 3: Remove existing archive if present
- name: Remove existing archive if present
file:
path: "{{ archive_path }}"
state: absent
become: yes
when: source_stat.stat.exists
# Task 4: Create tar.gz archive
- name: Create tar.gz archive of finance directory
archive:
path: "{{ source_dir }}/"
dest: "{{ archive_path }}"
format: gz
force_archive: yes
become: yes
when: source_stat.stat.exists
# Task 5: Set ownership of the archive
- name: Set ownership of official.tar.gz
file:
path: "{{ archive_path }}"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
become: yes
# Task 6: Verify archive exists
- name: Verify archive exists
stat:
path: "{{ archive_path }}"
register: archive_stat
become: yes
# Task 7: Display completion message
- name: Display completion message
debug:
msg: "Successfully created {{ archive_path }} on {{ inventory_hostname }} with owner {{ ansible_user }}"
when: archive_stat.stat.exists
EOF
Alternative Playbook Using Shell Module
If the archive module is unavailable or problematic:
cat > /home/thor/ansible/playbook.yml << 'EOF'
---
- name: Archive and copy finance directory across app servers
hosts: app_servers
gather_facts: no
vars:
source_dir: /usr/src/finance
dest_dir: /opt/finance
archive_path: /opt/finance/official.tar.gz
tasks:
- name: Create destination directory
file:
path: "{{ dest_dir }}"
state: directory
mode: '0755'
become: yes
- name: Verify source directory exists
stat:
path: "{{ source_dir }}"
register: source_stat
become: yes
failed_when: not source_stat.stat.exists
- name: Remove existing archive if present
file:
path: "{{ archive_path }}"
state: absent
become: yes
when: source_stat.stat.exists
- name: Create tar.gz archive using shell
shell: |
tar -czf "{{ archive_path }}" -C "{{ source_dir | dirname }}" "{{ source_dir | basename }}"
become: yes
when: source_stat.stat.exists
args:
creates: "{{ archive_path }}"
- name: Set ownership of official.tar.gz
file:
path: "{{ archive_path }}"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
become: yes
- name: Verify archive exists
stat:
path: "{{ archive_path }}"
register: archive_stat
become: yes
- name: Display completion message
debug:
msg: "Successfully created {{ archive_path }} on {{ inventory_hostname }} with owner {{ ansible_user }}"
when: archive_stat.stat.exists
EOF
Playbook Features Explained
1. Variables
vars:
source_dir: /usr/src/finance
dest_dir: /opt/finance
archive_path: /opt/finance/official.tar.gz
Variables make the playbook reusable and easier to maintain. Changing the source or destination paths requires only one modification.
2. Directory Creation
- name: Create destination directory
file:
path: "{{ dest_dir }}"
state: directory
mode: '0755'
become: yes
Creates the destination directory with proper permissions. The become: yes ensures administrative privileges.
3. Source Verification
- name: Verify source directory exists
stat:
path: "{{ source_dir }}"
register: source_stat
become: yes
failed_when: not source_stat.stat.exists
Ensures the source directory exists before attempting to archive. Prevents the playbook from failing unexpectedly.
4. Archive Creation
- name: Create tar.gz archive
archive:
path: "{{ source_dir }}/"
dest: "{{ archive_path }}"
format: gz
force_archive: yes
become: yes
Creates the tar.gz archive using the archive module with format: gz. The force_archive: yes ensures the archive is recreated even if it already exists.
5. Dynamic Ownership
- name: Set ownership of official.tar.gz
file:
path: "{{ archive_path }}"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0644'
become: yes
Uses {{ ansible_user }} to dynamically set the correct owner:
- stapp01 → tony
- stapp02 → steve
- stapp03 → banner
6. Verification
- name: Verify archive exists
stat:
path: "{{ archive_path }}"
register: archive_stat
become: yes
Confirms the archive was successfully created before displaying the completion message.
Execution and Verification
Running the Playbook
cd /home/thor/ansible
ansible-playbook -i inventory playbook.yml
Expected Output
PLAY [Archive and copy finance directory across app servers] ********************
TASK [Create destination directory] *********************************************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
TASK [Verify source directory exists] ******************************************
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [Remove existing archive if present] **************************************
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [Create tar.gz archive of finance directory] ******************************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
TASK [Set ownership of official.tar.gz] ****************************************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
TASK [Verify archive exists] ***************************************************
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [Display completion message] **********************************************
ok: [stapp01] => {
"msg": "Successfully created /opt/finance/official.tar.gz on stapp01 with owner tony"
}
ok: [stapp02] => {
"msg": "Successfully created /opt/finance/official.tar.gz on stapp02 with owner steve"
}
ok: [stapp03] => {
"msg": "Successfully created /opt/finance/official.tar.gz on stapp03 with owner banner"
}
PLAY RECAP *********************************************************************
stapp01 : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp02 : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp03 : ok=7 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Verification Commands
# 1. Check if archive exists
ansible -i inventory app_servers -m shell -a "ls -la /opt/finance/official.tar.gz"
# Expected Output:
# stapp01: -rw-r--r-- 1 tony tony 215 /opt/finance/official.tar.gz
# stapp02: -rw-r--r-- 1 steve steve 226 /opt/finance/official.tar.gz
# stapp03: -rw-r--r-- 1 banner banner 210 /opt/finance/official.tar.gz
# 2. Check archive content (should work without errors)
ansible -i inventory app_servers -m shell -a "tar -tzf /opt/finance/official.tar.gz"
# Expected Output:
# stapp01: blog.txt
# stapp02: story.txt
# stapp03: media.txt
# 3. Check ownership
ansible -i inventory app_servers -m shell -a "stat -c '%U:%G %n' /opt/finance/official.tar.gz"
# Expected Output:
# stapp01: tony:tony /opt/finance/official.tar.gz
# stapp02: steve:steve /opt/finance/official.tar.gz
# stapp03: banner:banner /opt/finance/official.tar.gz
# 4. Check archive size
ansible -i inventory app_servers -m shell -a "du -sh /opt/finance/official.tar.gz"
# Expected Output:
# stapp01: 4.0K /opt/finance/official.tar.gz
# stapp02: 4.0K /opt/finance/official.tar.gz
# stapp03: 4.0K /opt/finance/official.tar.gz
# 5. Verify ownership on each server individually
ansible -i inventory stapp01 -m shell -a "stat -c '%U:%G' /opt/finance/official.tar.gz" # Should show tony:tony
ansible -i inventory stapp02 -m shell -a "stat -c '%U:%G' /opt/finance/official.tar.gz" # Should show steve:steve
ansible -i inventory stapp03 -m shell -a "stat -c '%U:%G' /opt/finance/official.tar.gz" # Should show banner:banner
Video Demonstration
For a complete visual guide, watch this demonstration:
Troubleshooting Common Issues
Issue 1: Source Directory Doesn't Exist
Symptom:
fatal: [stapp01]: FAILED! => {
"msg": "Source directory does not exist"
}
Solution:
# Create the directory and test data
ansible -i inventory app_servers -m file -a "path=/usr/src/finance state=directory mode=0755"
ansible -i inventory app_servers -m copy -a "content='Financial data\n' dest=/usr/src/finance/records.txt"
ansible -i inventory app_servers -m copy -a "content='Blog content\n' dest=/usr/src/finance/blog.txt"
Issue 2: Archive Not in Gzip Format
Symptom:
gzip: stdin: not in gzip format
tar: Child returned status 1
Solution:
- Use the shell module version with
tar -czfinstead of the archive module - Ensure
format: gzis specified when using the archive module
# Correct archive module usage
archive:
path: "/usr/src/finance/"
dest: "/opt/finance/official.tar.gz"
format: gz
force_archive: yes
# Or use shell module
shell: tar -czf /opt/finance/official.tar.gz -C /usr/src finance
Issue 3: Permission Denied
Symptom:
fatal: [stapp01]: FAILED! => {
"msg": "Permission denied"
}
Solution:
# Ensure 'become: yes' is used for all administrative tasks
# Check sudo privileges for the user
ansible -i inventory app_servers -m shell -a "sudo -l"
# If password is required for sudo, add to inventory
# ansible_become_pass=Ir0nM@n
Issue 4: Wrong Ownership
Symptom:
-rw-r--r-- 1 root root /opt/finance/official.tar.gz
Solution:
# Verify inventory user mapping
cat /home/thor/ansible/inventory
# Ensure ansible_user variable is correct for each server
# stapp01 -> tony
# stapp02 -> steve
# stapp03 -> banner
# Manually fix ownership if needed
ansible -i inventory stapp01 -m file -a "path=/opt/finance/official.tar.gz owner=tony group=tony mode=0644"
Issue 5: Connection Timeout
Symptom:
fatal: [stapp01]: UNREACHABLE! => {
"msg": "Failed to connect to the host via ssh"
}
Solution:
# Test network connectivity
ping -c 3 stapp01
# Check SSH port
nc -zv stapp01 22
# Verify SSH credentials
ssh -o StrictHostKeyChecking=no tony@stapp01
# Increase timeout in inventory
ansible_ssh_timeout=60
Issue 6: Host Key Verification Failed
Symptom:
Host key verification failed
Solution:
# Add to inventory
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
# Or remove from known_hosts
ssh-keygen -R stapp01
Issue 7: Archive Module Not Available
Symptom:
ERROR! no action detected in task
Solution:
- Use the shell module alternative
- Update Ansible to a newer version
sudo yum update -y ansible
Best Practices and Key Takeaways
1. Dynamic Ownership
Using {{ ansible_user }} from the inventory makes the playbook flexible and reusable across different servers with different users. This eliminates hardcoding and simplifies maintenance.
Benefits:
- No need to create separate playbooks for each server
- Easy to add new servers with different users
- Single source of truth for user mapping
2. Idempotency
The playbook can be run multiple times without causing errors or creating duplicate archives.
Implementation:
-
createsargument in shell commands -
force_archiveoption in archive module -
state: absentbefore creating new archive
3. Error Handling
Using failed_when and conditional execution (when) prevents the playbook from failing unnecessarily.
Best Practices:
- Always check source existence before operations
- Use
registerto capture results - Provide meaningful error messages
- Use
ignore_errorsfor non-critical tasks
4. Verification
Built-in verification tasks provide confidence that the operation was successful on all servers.
Verification Steps:
- Check if archive exists
- Verify correct ownership
- Confirm archive content
- Validate file permissions
5. Use of Become
Using become: yes for administrative tasks ensures the playbook has the necessary permissions.
When to Use:
- Creating directories in
/opt/ - Writing to system directories
- Changing file ownership
- Installing packages
6. Modular Design
Using variables and reusable tasks makes the playbook modular and maintainable.
Benefits:
- Easy to update paths
- Simple to extend functionality
- Clear separation of concerns
7. Documentation
Inline comments explain the purpose of each task, making the playbook self-documenting.
Documentation Tips:
- Add task descriptions
- Explain complex logic
- Include variable definitions
- Provide usage examples
Conclusion
The Ansible playbook successfully automates the process of:
- ✅ Directory Management: Creating destination directories with proper permissions
- ✅ Archive Creation: Compressing source directories into tar.gz format
- ✅ File Transfer: Copying archives to target locations
- ✅ Ownership Management: Setting correct ownership based on server requirements
- ✅ Verification: Confirming successful execution on all servers
- ✅ Idempotency: Safe to run multiple times
Results Summary
| Server | Source File | Archive Created | Owner | Status |
|---|---|---|---|---|
| stapp01 | blog.txt | official.tar.gz | tony:tony | ✅ |
| stapp02 | story.txt | official.tar.gz | steve:steve | ✅ |
| stapp03 | media.txt | official.tar.gz | banner:banner | ✅ |
Key Achievements
- Time Savings: Automated what would be a manual 15-minute task per server
- Consistency: Same process applied across all servers
- Reliability: Idempotent execution ensures no duplicate archives
- Maintainability: Easy to update for future requirements
Scalability: Can be extended to additional servers
Extend the Playbook: Add additional directories or file types
Implement Scheduling: Use Ansible Tower or cron for regular archiving
Add Monitoring: Implement logging and notification for archive operations
Define Retention Policies: Set up automated cleanup of old archives
Implement Backup Integration: Connect with backup systems for disaster recovery
Create Documentation: Document the playbook for team members

Top comments (0)