Deploying Linodes with Ansible
2020-01-09 12:01
Requirements: Linode user account, and an Ansible compatible server (RHEL, CentOS, Ubuntu Server, etc.).
Install Ansible (by following the official guide https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installation-guide).
The server you install Ansible on is your Control node, from this Control node you deploy to Managed nodes.
Managed nodes are usually configured in a hosts
file, called your Inventory. We never install Ansible on the managed nodes.
Install Linode’s official Python library for the Linode API v4.
$ pip install linode_api4
We need an Ansible configuration file, I prefer to keep my configuration file in /opt/ansible/
, especially if multiple users will utilize the Control node.
sudo mkdir /opt/ansible/
sudo touch /opt/ansible/.ansible.cfg
Export the ANSIBLE_CONFIG
environment variable, and source it.
$ echo 'export ANSIBLE_CONFIG=/opt/ansible/.ansible.cfg' | sudo tee /etc/profile.d/ansible.sh
$ source /etc/profile.d/ansible.sh
Change ownership of the ansible directory and cd
into it.
$ sudo chown -R <your_user>:<your_user> /opt/ansible/ && cd /opt/ansible/
Edit ansible.cfg
.
[defaults]
host_key_checking = False
VAULT_PASSWORD_FILE = ./vault-pass
[inventory]
enable_plugins = linode
Roles
We will use Ansible’s concept of Roles. With roles, we can group things that logically belong together in separate directories. In this article we will create three roles: linode, security, and webserver.
Ansible uses a defined directory structure for roles: seven main standard directories, and you must include at least one of these directories in each role (https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html).
At this point, this is what my directory tree looks like.
ansible/
└── roles
├── linode
│ └── tasks
│ └── main.yml
├── security
│ └── tasks
│ └── main.yml
└── webserver
└── tasks
└── main.yml
Edit roles/linode/tasks/main.yml
.
- name: Deploy Linode
hosts: localhost
vars_files:
- /opt/ansible/group_vars/vars
tasks:
- name: Deploy new Linode
linode_v4:
label: "{{ label }}{{ 100 |random }}"
access_token: "{{ token }}"
type: g6-nanode-1
region: eu-central
image: linode/centos8
root_pass: "{{ password }}"
authorized_keys: "{{ ssh_keys }}"
group: ansible_managed_node
tags: ansible_managed_node
state: present
register: deployed_linode
To find the name of Linode types (size), regions, and images, you can use linode-cli
. Remember to create a Personal Access Token on your Linode account page.
pip install linode-cli
In /opt/ansible/, create the directory group_vars, and inside a file vars
.
ssh_keys: ~/.ssh/id_rsa.pub
label: simple-linode
Use ssh-keygen
to create your key pair, and paste the public key into the file above.
Ansible Vault
For secure storage of passwords and access tokens we will use Ansible Vault to encrypt them.
echo 'My.ANS1BLEvault-c00lPassw0rd' > /opt/ansible/vault-pass
Remember to add vault-pass to .gitignore
Run ansible-vault encrypt_string 'My.c00lPassw0rd' --name 'password'
, and paste the output in /opt/ansible/group_vars/vars
.
Do the same with your Linode Personal Access Token: ansible-vault encrypt_string '<your Linode Personal Access Token>' --name 'token'
, again, paste the output into your vars
file.
My var file now looks like this:
ansible_ssh_user: root
ssh_keys: ~/.ssh/id_rsa.pub
label: linode
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
3463383764 ... secret
token: !vault |
$ANSIBLE_VAULT;1.1;AES256
396665323 ... secret
Remember to also add your public SSH key to your Linode account.
Run the playbook, ansible-playbook roles/linode/tasks/main.yml
.
You can add -v
, -vv
, or -vvv
, specifying levels of verbosity for troubleshooting.
/opt/ansible$ ansible-playbook roles/linode/tasks/main.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does
not match 'all'
PLAY [Deploy Linode] ******************************************************************************************
TASK [Gathering Facts] ****************************************************************************************
ok: [localhost]
TASK [Deploy Linode] ******************************************************************************************
changed: [localhost]
PLAY RECAP ****************************************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
At this point you should be able to SSH into your new Linode server, as root. Find its IP using linode-cli linodes list
.
Inventory
Use linode-cli linodes list
to find the IP of your newly deployed server.
Create a file /opt/ansible/hosts
.
[webserver]
<server IP>
Edit ansible.cfg
and add the path to your inventory (hosts) file. My .ansible.cfg now looks like this:
[defaults]
host_key_checking = False
VAULT_PASSWORD_FILE = ./vault-pass
inventory = /opt/ansible/hosts
enable_plugins = linode
pipelining = True
Run ansible all --list-hosts
to verify Ansible can use your inventory.
For sake of example, let us say our new server will be a web server, maybe it will host your blog. Remember, we talked about Roles earlier, webserver
is the name of our role.
Our web server will have need of some security changes, so let us create the security playbook first.
Edit /opt/ansible/roles/security/tasks/main.yml
.
- hosts: webserver
vars_files:
- /opt/ansible/group_vars/vars
become: true
tasks:
- name: "Update security packages"
yum:
name: '*'
security: yes
state: latest
- name: "Enable firewalld"
firewalld:
permanent: true
state: enabled
service: "{{ item }}"
notify: reload firewalld
loop:
- ssh
For sake of this article, and in the name of example, this is far from a complete security playbook. You should look into best practices and security configurations standards like DISA STIG.
Run the playbook: ansible-playbook -i /opt/ansible/hosts roles/security/tasks/main.yml -l webserver
Here we specify which hosts file we would like to use, with -i
, then follows the playbook, and lastly the role name (as specified in the hosts file).
Now for the web server playbook.
Edit /opt/ansible/roles/webserver/tasks/main.yml
- hosts: webserver
vars_files:
- /opt/ansible/group_vars/vars
become: true
tasks:
- name: "Install Nginx"
yum:
name: nginx
state: present
- name: "Start nginx"
service:
name: nginx
state: started
- name: "Enable systemd service for nginx"
systemd:
name: nginx
enabled: yes
masked: no
- name: "Enable firewalld"
firewalld:
permanent: true
state: enabled
service: "{{ item }}"
immediate: true
loop:
- https
- http
ansible-playbook -i /opt/ansible/hosts roles/webserver/tasks/main.yml -l webserver
If you are running playbooks multiple times, e.g. for troubleshooting, it can be handy to start at a certain task, using --start-at-task <task name>
.
Example:
ansible-playbook -i /opt/ansible/hosts roles/webserver/tasks/main.yml -l webserver --start-at-task "Enable firewalld