Project Structure
Setting up the Ansible control node, Python environment, project layout, and local version control.
What I Will Be Completing In This Part
- Provision an Ubuntu 22.04 VM as the dedicated Ansible control node
- Create an isolated Python virtual environment for Ansible
- Install Ansible and verify the installation
- Build the project directory structure for
network-automation-lab - Create the
ansible.cfgproject configuration - Initialise a local Git repository and make the first commit
01 VM Specification
I created a new VM in my Proxmox server with the following specifications. This VM will be dedicated as the Ansible control node.
OS: Ubuntu Server 22.04 LTS
Hostname: ansible-ctrl
CPU: 2 vCPU
RAM: 4 GB
Disk: 40 GB
Network: 1 NIC (bridged to management network)2 vCPU and 4GB is more than needed for an Ansible control node. I also gave this VM a static IP on the management network and added a DNS entry.
02 System Setup
After installing the OS I ran a full system update and installed the base dependencies that Ansible and the Python virtual environment will need.
| |
- Line 1:
- Updates the package list and upgrades all installed packages.
- Line 2:
python3-venvprovides thevenvmodule for creating isolated environments.gitis for version control.sshpassallows password-based SSH connections during initial device bootstrapping.libffi-devandlibssl-devare C libraries required to compileparamikoandcryptography.
sshpass is a bootstrap tool, not a long-term solution. I’m only using it for the initial connection to devices that don’t yet have SSH keys configured. Once I deploy key-based authentication via Ansible in later parts, sshpass becomes unnecessary. In a production environment, password-based SSH access should be disabled as soon as keys are in place.03 Python Virtual Environment
Creating a dedicated Python virtual environment is best practice because it prevents version conflicts with system Python packages. It also makes the Ansible installation reproducible, and it lets me pin specific versions of Ansible and its dependencies without affecting other tools on the VM.
| |
- Line 1–2:
- Created the project root directory and moved into it. I will create everything related to this project within this directory (laybooks, roles, inventory, configs, and the virtual environment itself).
- Line 3:
- Created the virtual environment in a
.venvdirectory. - Line 4:
- Activated the virtual environment. After this,
python3andpippoint to the copies inside.venv/rather than the system installation.
To avoid forgetting to activate the venv every time I SSH into the control node, I added the activation command to my ~/.bashrc.
# Auto-activate the Ansible venv when entering the project directory
if [ -d "$HOME/network-automation-lab/.venv" ]; then
source "$HOME/network-automation-lab/.venv/bin/activate"
cd ~/network-automation-lab
fiOn a shared machine, I would use direnv or a wrapper script instead, that way the venc only activates when explicitly entering the project directory.
04 Installing Ansible
After activating the virtual environement, I installed Ansible using pip. I installed the full ansible package because it includes community collections that I’ll need for network device modules.
| |
- Line 1:
- Upgraded
pipitself first. The version bundled with Ubuntu 22.04’s Python is old and will print deprecation warnings otherwise. - Line 2:
ansiblepulls inansible-coreplus community collections.paramikois the Python SSH library that Ansible uses as a fallback transport.- Line 3:
- Froze the exact package versions into
requirements.txt. If I rebuild the control node six months from now, I can recreate the identical environment withpip install -r requirements.txt.
sudo pip install outside a virtual environment. This modifies system Python packages and can break OS tools that depend on specific Python library versions (like apt itself on Ubuntu). Always use a venv.Next, I installed the specific Ansible collections I’ll need for the 3 network platforms in this lab.
| |
- Line 1:
- IOS-XE modules for the Cat9kv nodes.
- Line 2:
- NX-OS modules for the N9Kv spine/leaf switches.
- Line 3:
- PAN-OS collection for the PA-VM firewall.
To be able to reproduce this evironment more easily, I created a requirements.yml file to pin these collections. This makes it so I can reinstall all collections with 1 command: ansible-galaxy collection install -r requirements.yml.
---
collections:
- name: cisco.ios
- name: cisco.nxos
- name: paloaltonetworks.panos05 Directory Structure
I like to setup the directory structure before writing playbooks, that way everything is layed out beforehand.
(.venv) mkdir -p inventory/group_vars inventory/host_vars
(.venv) mkdir -p playbooks
(.venv) mkdir -p roles
(.venv) mkdir -p templates
(.venv) mkdir -p files
(.venv) mkdir -p docs
(.venv) mkdir -p filter_plugins
(.venv) touch README.mdThe directory tree should look like this:
This follows Ansible’s recommended directory layout for a single project repo.
06 Ansible Configuration
While in the project root I created an ansible.cfg file. When Ansible runs it searched for this configuration in this order: ANSIBLE_CONFIG environment variable, ansible.cfg in the current directory, ~/.ansible.cfg, /etc/ansible/ansible.cfg.
[defaults]
inventory = inventory/
roles_path = roles/
collections_path = ~/.ansible/collections
remote_user = admin
timeout = 30
forks = 10
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
callbacks_enabled = ansible.posix.timer
[persistent_connection]
connect_timeout = 60
command_timeout = 60- Line 2:
- Points Ansible to the
inventory/directory. It will read all valid inventory files in this directory automatically. - Line 4:
- Keeps collections in the default user location.
- Line 5:
- Default SSH user for device connections.
- Line 7:
- Number of parallel processes.
10means Ansible will configure up to 10 devices simultaneously. - Line 8:
- Disables SSH host key checking. Necessary in a lab where devices are frequently rebuilt and their host keys change. In production, this should be
True. - Line 9:
- Disables
.retryfiles that Ansible creates on failed playbook runs. - Line 10:
- Switches playbook output from the default (dense, hard to read) to YAML format.
- Line 11:
- Enables the timer callback plugin which displays how long each playbook run takes.
- Lines 13–15:
- The
persistent_connectionsection controls network device connections. Default 30-second timeouts cause failures on slower devices (especially N9Kv in Containerlab), so I increased both to 60 seconds.
host_key_checking = False is to make it easier for this lab project. If this were a real environment I would manage SSH known hosts properly. I could either create a pre-populated known_hosts file distributed via configuration management, or by using a host key verification machanism tied to the source of truth.
07 Git Initialisation
I then initialized a local Git repo at the project root. I will be setting up Gitea later on.
| |
- Line 1:
- Created the
.git/directory in the project root. This is now a tracked repository. - Lines 2–3:
- Set my identity for commits.
07a .gitignore
Before commiting anything I created the .gitignore file. This file will ommit any file or directory from being pushed to the repo.
# Python virtual environment
.venv/
__pycache__/
*.pyc
# Ansible artifacts
*.retry
# Vault password file (NEVER commit this)
.vault/
.vault_pass
.vault_password
# IDE and editor files
.vscode/
*.swp
*.swo
*~
# OS artifacts
.DS_Store
Thumbs.dbThe .vault/ directory and vault password files are the most important since they contain the encryption key for all my secrets. If this file is committed to Git then the entire secret is compromised.
07b First Commit
After creating the directory structure, configuration files, and .gitingore, I staged everything and made the first commit.
(.venv) git add -A
(.venv) git statusRunning git status before commiting is so I can verify that only the intended files are staged. The output should show the project files but not .venv/.
On branch main
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: README.md
new file: ansible.cfg
new file: requirements.txt
new file: requirements.ymlThe empty directories won’t appear in git status because Git doesn’t track empty directories. I could add a .gitkeep placeholder file to each but it’s not that important.
(.venv) git commit -m "init: control node setup, ansible install, project structure"initis used for bootstrappingfeatis used for new featuresfixis used for bug fixesdocsis used for documentationrefractoris used for restructuring without changing behavior
07 Verification
I then ran a few checks to verify that everything is working correctly.
Ansible Version
(.venv) which python3
/home/nesto/network-automation-lab/.venv/bin/python3
(.venv) which ansible
/home/nesto/network-automation-lab/.venv/bin/ansibleBoth paths should point to .venv/bin/, if they point to /usr/bin/ then the virtual environment is not activated.
Installed Collections
(.venv) ansible-galaxy collection list | grep -E "cisco|paloalto"This should show cisco.ios., cisco.nxos, and paloaltonetworks.panos.
Git Log
(.venv) git log --oneline
a1b2c3d init: control node setup, ansible install, project structureShould only show 1 commit.
After this I took a Proxmox snapshot of the VM. That way if anything messes up I can quickly rollback to a known working state of the VM.