A tool/way to automotize operations done in machine environments.
First install ansible with sudo apt install ansible -y
and then check it with ansible --version
. This is my version info:
osboxes@jumphost:~/ansible-tutorial$ ansible --version
ansible 2.10.8
config file = None
configured module search path = ['/home/osboxes/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.10.12 (main, Nov 6 2024, 20:22:13) [GCC 11.4.0]
From what i understand, basically, first you provide an inventory of machine connection infos to connect, and give some tasks to be completed in them.
You provide machines to be connected in a file. This file can be a ini format or json/yaml format. Initially i wanted to learn ini format.
You can give specific names and group/subgroup machine connection infos
[<GROUP NAME>]
<MACHINE CONNECTION(S) INFOS>
An example of an inventory
ini file, a localhost connection grouped with the name local
[local]
localhost ansible_connection=local
localhost ansible_connection=local
- connect to localhost
- via ansible connection type of local
localhost ansible_connection=local ansible_become=true ansible_become_password=...
- connect to
localhost
- using ansible connection type of local
- but get the elevated privileges (sudo) by becomeing root user with
ansible_become=true
- and for the sudo password, it is provided in
ansible_become_password
- connect to
now write a inventory
file like this:
Generate a workspace under $HOME
(/home/<YOUR ACCOUNT NAME>/
):
cd $HOME
mkdir ansible-learning
cd ansible-learning
nano inventory
Enter these to the inventory
file and save it:
[local]
localhost ansible_connection=local
press CTRL and X keys, and then press Y key to save, and lastly press ENTER key to save it to the file /home/<YOUR ACCOUNT NAME>/ansible-learning/inventory
(basic nano usage)
After saving the inventory
file, then run this command to ping the connections to given hosts.
ansible -i inventory local -m ping
ansible
: is the command for temporary or single use (ad-hoc) operations, like ping'ing host or other small and easy tasks.-i
: a flag for the ansible command that supplies the inventory file.inventory
: file name(that we've generated earlier) for the-i
flag.local
: group name to be checked inside theinventory
-m
: a flag for the ansible command that a module will be loaded and run.ping
: module name(shorthand foransible.builtin.ping
, already existing inside ansible) for the-m
flag.
You should see a successfull operation result like this. To see the logs more detailed add -vvvv
at the end of the command mentioned above.
osboxes@jumphost:~/ansible-tutorial$ ansible -i inventory local -m ping
localhost | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
With localhost connection added to the inventory file and grouped with the name local
, let's try to update the system packages (normally you do this with sudo apt update -y
and sudo apt upgrade -y
)
# This is equivalent of apt update, run this command
ansible -i inventory local -m apt -a "update-cache=yes"
ansible
: is the command for temporary or single use (ad-hoc) operations, like ping'ing host or other small and easy tasks.-i
: a flag for the ansible command that supplies the inventory file.inventory
: file name(that we've generated earlier) for the-i
flag.local
: group name to be checked inside theinventory
-m
: a flag for the ansible command that a module will be loaded and run.apt
: module name(shorthand foransible.builtin.apt
, already existing inside ansible, docs)-a
: a flag that some arguments will be given to the ansible module"update-cache=yes"
arguments for the module that will run theapt update
command(docs).
If you run this command you will see an error like this:
osboxes@jumphost:~/ansible-tutorial$ ansible -i inventory local -m apt -a "update-cache=yes"
localhost | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"msg": "Failed to lock apt for exclusive operation: Failed to lock directory /var/lib/apt/lists/: E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)"
}
This error tells that ansible operation done on localhost
has FAILED
due to a (13. Permission denied)
as you know apt commands require system administration elevation(sudo) to be run successfully, and we're trying to do it without it. We need to tell ansible that you need to run this via sudo, meaning you need to become
an elevated user. There's so many ways to achieve this, but for the purpose of this topic (which is learning the inventory), i'll write the inventory host connection must become an elevated user
way here:
modify the inventory file like this:
nano inventory
# modify the inventory to become like this:
[local]
localhost ansible_connection=local ansible_become=true ansible_become_password=<YOUR_ACCOUNT_PASSORD>
After that, the same command does the apt update
with the given become flag and giving the user password(which is not secure, will cover this) and returns that update is successful. There's my result
osboxes@jumphost:~/ansible-tutorial$ ansible -i inventory local -m apt -a "update-cache=yes"
localhost | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"cache_update_time": 1736773491,
"cache_updated": true,
"changed": true
}
We're able to sudo apt update
successfully, but writing your account password to a file openly and clearly is a very bad practice,
we must not do it like this. For one of the more secure approaches, Ansible allows us to use the env variables, instead of entering
in a file, which makes our sensitive data to be stored elsewhere and can be accessed according to our needs.
The sensitive data we'we entered ansible_become_password=<YOUR_ACCOUNT_PASSORD>
in inventory file can be given as an uppercase environment variable,
like this: ANSIBLE_BECOME_PASS
In summary do it like this:
First remove the sensitive info(ansible_become_password=<YOUR_ACCOUNT_PASSORD>
) from the inventory file:
nano inventory
# modify the inventory to become like this:
[local]
localhost ansible_connection=local ansible_become=true
After that run this command to enter the sensitive data to a env variable (for the current session).
export ANSIBLE_BECOME_PASS=<YOUR_ACCOUNT_PASSORD>
And then run the command again, value will be picked up by ansible and you'll see the success result:
ansible -i inventory local -m apt -a "update-cache=yes"
here is my output:
osboxes@jumphost:~/ansible-tutorial$ export ANSIBLE_BECOME_PASS=<YOUR_ACCOUNT_PASSORD>
osboxes@jumphost:~/ansible-tutorial$ ansible -i inventory local -m apt -a "update-cache=yes"
localhost | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"cache_update_time": 1736775582,
"cache_updated": true,
"changed": true
}
# this is equivalent of apt upgrade
ansible -i inventory local -m apt -a "upgrade=yes"
# Optional, written here to show that can combine multiple arguments to a ansible module if you check the docs
# this is the combined version update and upgrade
ansible -i inventory local -m apt -a "update-cache=yes upgrade=yes"
The examples above, make use of the ansible internal modules, and we're calling these operation/task one by one by calling the necessary ansible command one by one. To make this process more robust/automated/less error-prone, we can put these operations/tasks in a yaml file/files and group them. In Ansible terminology, these operations/tasks are defined as tasks, and these group of tasks is called playbook(s). Here's an example that does the same thing above (apt update):
Write a file named 1.local-apt-update.yaml
.
nano 1.local-apt-update.yaml
# add these inside
---
- name: Example Playbook for localhost to update the repos
hosts: localhost
become: true
tasks:
- name: Update the package cache
ansible.builtin.apt:
update_cache: yes
# Check become password exists
echo $ANSIBLE_BECOME_PASS
# if it doesn't, add like this:
export ANSIBLE_BECOME_PASS=<YOUR ACCOUNT PASSWORD>
# run this command to use the inventory file and run the playbook file named `local-apt-update.yaml`
ansible-playbook -i inventory 1.local-apt-update.yaml
Here's my result of these commands:
osboxes@jumphost:~/ansible-tutorial$ ansible-playbook -i inventory 1.local-apt-update.yaml
PLAY [Example Playbook for localhost to update the repos] **********************************************
TASK [Gathering Facts] *********************************************************************************
ok: [localhost]
TASK [Update the package cache] ************************************************************************
changed: [localhost]
PLAY RECAP *********************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As you can see, by supplying the inventory and the playbook files (of course the sensitive ones too) to the ansible-playbook
command,
we can run the same operation, defined in a yaml file, more easily. This way is less error-prone.
Todos:
- preparation for ansible connection type ssh
- install necessary sshpass app to localhost, and add the ssh key to known hosts by writing playbooks and running them
- connect to localhost via ansible connection type ssh
- write the necessary steps to install docker (adding the gpg, trusting it, add apt repo of docker and installing docker, then verifying by running this command
docker run hello-world
) - advanced inventory ini, advanced playbooks topics
- organizing playbooks
- reading other's playbooks
- ansible galaxy and its usage
For ansible to connect more easily, Set up SSH key-based authentication:
Here’s a step-by-step guide on how to set up SSH key-based authentication for your Ubuntu VMs.
On your local machine (the one from which you want to access the remote VMs), generate an SSH key pair (if you don’t have one already). Run:
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa
Explanation:
When prompted, you can leave the passphrase empty (just press Enter) if you don’t want to set a passphrase. You can also enter a passphrase for added security.
2. Copy the SSH Public Key to the Remote VM
Now you need to copy the public key (~/.ssh/id_rsa.pub) to the remote machine(s) in order to enable passwordless login. You can do this easily using the ssh-copy-id tool.
ssh-copy-id user@remote_host
For example, if you want to copy your key to 10.0.2.5 as the osboxes user:
ssh-copy-id [email protected]
You will be prompted for the password of the remote user (osboxes in this case). Once authenticated, the tool will copy your public key to the remote machine’s ~/.ssh/authorized_keys file.
3. Manually Copy the SSH Public Key (If ssh-copy-id is Unavailable)
If ssh-copy-id is unavailable or you prefer to do it manually, follow these steps:
cat ~/.ssh/id_rsa.pub
This will output your public key, which should look something like this:
ssh-rsa AAAAB3... rest of the key ... user@hostname
On the remote machine, log in via SSH:
ssh [email protected]
On the remote machine, create the ~/.ssh directory if it doesn’t exist:
mkdir -p ~/.ssh
Append the public key to the ~/.ssh/authorized_keys file:
echo "your_public_key_here" >> ~/.ssh/authorized_keys
Make sure to replace "your_public_key_here" with the public key you copied earlier (from ~/.ssh/id_rsa.pub).
Set the correct permissions for the ~/.ssh directory and authorized_keys file:
Now that your public key is on the remote machine, test the connection:
ssh [email protected]
You should be able to log in without entering the password.
5. (Optional) Configure SSH to Disable Password Authentication
To further secure your server, you can disable password authentication, forcing users to log in only using SSH keys.
sudo nano /etc/ssh/sshd_config
Ensure the following lines are present and not commented out (remove # if they exist):
PasswordAuthentication no
ChallengeResponseAuthentication no
Restart the SSH service to apply the changes:
To set up key-based authentication for multiple remote hosts, repeat the process for each VM or server: