Our SaaS offering is based on services provided by AWS. We are using Ansible to automatically configure our infrastructure. In doing so, we hit an interesting obstacle when dealing with NVMe-backed (SSD) EBS volumes:
First, we use the ec2_vol
plugin to set up and attach the volume:
- name: Create EC2 volume
amazon.aws.ec2_vol:
volume_size: "16G"
volume_type: "gp2"
instance: "{{ instance_id }}"
device_name: sdf
name: "some-data"
tags:
purpose: "{{ purpose }}"
state: present
register: ec2_volume
According to the docs, the volume should now appear under the device_name
, in this case /dev/sdf
. However, this does not seem to be the case.
First thing, the device names are assigned by the kernel and are not predictable. Second thing, NVMe SSDs are following the naming scheme of /dev/nvmeXnY
. The documentation from AWS concerning EBS/NVMe points out this fact and explains that the device_name
is actually stored in a vendor-specific extension of the NVMe controller.
How to mount a volume that has just been created dynamically?
The device name is assigned somewhat randomly, and properties like UUIDs are not yet known for new volumes: how can we still access the correct volume? There are some solutions, but they require quite some shell scripting. I want to show a simpler approach, using only built-in Ansible tools:
We can exploit that AWS assigns an identifier like vol-abcdef0123456
to the volume. Inside the EC2 instance, this identifier appears as the serial number of the volume, but without the hyphen, e.g. volabcdef0123456
.
Let’s retrieve the volume information from AWS…
- name: Get EC2 volume details
delegate_to: localhost
amazon.aws.ec2_vol_info:
filters:
"tag:Name": "some-data"
register: ec2_vol_info
… and extract the volume ID:
- name: Store volume serial number
ansible.builtin.set_fact:
ebs_volume_id: "{{ ec2_vol_info.volumes[0].id | replace('-', '') }}"
Now we can use lsblk
to obtain both the device path and the serial numbers of all volumes on the instance. We filter the output for the serial number of our EBS volume:
- name: List Linux block devices
ansible.builtin.shell:
cmd: "lsblk -o PATH,SERIAL | grep {{ ebs_volume_id }} | cut -d' ' -f1"
register: lsblk
- name: Store EBS volume device name
ansible.builtin.set_fact:
ebs_volume_device_name: "{{ lsblk.stdout }}"
The results
And voilà, the fact ebs_volume_device_name
now contains the full path to our volume. This can be used to format and mount the volume:
- name: Format EC2 volume
community.general.filesystem:
fstype: ext4
dev: "{{ ebs_volume_device_name }}"
force: no
- name: Mount EC2 volume
ansible.posix.mount:
path: /mnt/ebs
src: "{{ ebs_volume_device_name }}"
fstype: ext4
boot: yes
state: mounted
As you see, aside from the one-line shell invocation around lsblk
only well-readable and maintainable Ansible-Plugins were necessary.
This approach works across reboots of the instance and is in line with Ansible’s idempotent design.