Mastering Last Mile Configuration Automation — Part 3: Terraform Takes Over
In the final part of our series on mastering last mile configuration automation, we focus on how Terraform provisions the environment and ensures everything is in place for Blobfuse to function. By leveraging Terraform’s ability to dynamically inject configuration values into a virtual machine (VM) via a custom script, we overcome the challenge of co-dependencies between infrastructure components. This approach allows us to finalize the Blobfuse setup with precise, environment-specific details.
This article delves into using an Azure Virtual Machine extension with a templated Bash script to complete the configuration process, replacing placeholders and mounting Azure Blob Storage. With these final steps, our automation pipeline becomes both robust and adaptable.
Using Terraform to Provision the Environment
Terraform plays a pivotal role in provisioning the necessary Azure resources and injecting the final configuration into the VM. These resources include:
- Azure Storage Account: Holds the Blob Storage container.
- Blob Storage Container: The target for the Blobfuse mount.
- User-Assigned Managed Identity: Authenticates the VM to access the storage account securely.
- Azure Virtual Machine: Built using the Packer image and provisioned by Terraform.
The key to tying these components together lies in the Azure Virtual Machine Extension, which runs a custom script during the deployment process. This script is templated and filled with placeholders that Terraform replaces with actual values.
Passing Values with the Azure VM Extension
The azurerm_virtual_machine_extension resource in Terraform lets us specify a script, along with dynamically generated configuration values, to be executed on the VM. For this setup, placeholders in the setup-client.sh script are replaced with values from Terraform, such as the storage account name, access key, and container name.
Here’s an example of the extension resource:
resource "azurerm_virtual_machine_extension" "main" {
count = length(var.cluster_regions)
name = "vm-cse"
virtual_machine_id = module.client_vm[count.index].id
publisher = "Microsoft.Azure.Extensions"
type = "CustomScript"
type_handler_version = "2.0"
settings = <<SETTINGS
{
"script": "${
base64encode(
templatefile(
"${path.module}/files/setup-client.sh",
{
storage_account_name = "${azurerm_storage_account.client_storage[count.index].name}"
storage_account_key = "${azurerm_storage_account.client_storage[count.index].primary_access_key}"
storage_container_name = "${azurerm_storage_container.client_storage[count.index].name}"
blobfuse_mount_directory = "/mnt/synthetics-cosmosdb"
blobfuse_config_file = "/opt/blobfuse2/blobfuse-key-config.yaml"
blobfuse_cache_directory = "/tmp/synthetics-cosmosdb"
storage_account_name_identifier = "AZURE_STORAGE_ACCOUNT_NAME"
storage_account_key_identifier = "AZURE_STORAGE_ACCOUNT_KEY"
storage_container_identifier = "AZURE_STORAGE_CONTAINER_NAME"
}
)
)
}"
}
SETTINGS
}
This resource ensures that all required values are passed into the script and that the placeholders are replaced with the actual details during execution.
Templating and Executing the Bash Script
The setup-client.sh script is responsible for completing the last mile configuration. It performs the following steps:
1. Echoing Parameters for Troubleshooting
To verify that Terraform is passing the correct values, the script begins by outputting the variables:
# echo parameters
echo "Storage Account Name: "${storage_account_name}
echo "Storage Account Key: "${storage_account_key}
echo "Storage Container Name: "${storage_container_name}
echo "Blobfuse Config File: "${blobfuse_config_file}
echo "Blobfuse Cache Dir: "${blobfuse_cache_directory}
echo "Blobfuse Mount Dir: "${blobfuse_mount_directory}
echo "Storage Account Identifier (used for lookup): "${storage_account_name_identifier}
echo "Storage Account Identifier (used for lookup): "${storage_account_key_identifier}
echo "Storage Container Identifier (used for lookup): "${storage_container_identifier}
These logs are invaluable for diagnosing any issues during deployment.
2. Saving Parameters as Local Variables
The script saves the values passed from Terraform as local variables:
# save parameters as environment variables
storageAccountName=${storage_account_name}
storageAccountKey=${storage_account_key}
storageContainerName=${storage_container_name}
blobfuseConfigFile=${blobfuse_config_file}
blobfuseCacheDir=${blobfuse_cache_directory}
blobfuseMountDir=${blobfuse_mount_directory}
# used to lookup the placeholder value
storageAccountNameIdentifier=${storage_account_name_identifier}
# used to lookup the placeholder value
storageAccountKeyIdentifier=${storage_account_key_identifier}
# used to lookup the placeholder value
storageContainerIdentifier=${storage_container_identifier}
This step simplifies the subsequent operations.
3. Replacing Placeholders in the Configuration File
Using sed, the script replaces placeholders in the Blobfuse configuration file with actual values:
# update blobfuse-config.yaml, find the placeholder value and replace it with the actual value
sed -i 's@'"$storageAccountNameIdentifier"'@'"$storageAccountName"'@' $blobfuseConfigFile
sed -i 's@'"$storageAccountKeyIdentifier"'@'"$storageAccountKey"'@' $blobfuseConfigFile
sed -i 's@'"$storageContainerIdentifier"'@'"$storageContainerName"'@' $blobfuseConfigFile
This ensures the configuration file is ready for use.
4. Creating Required Directories
The script ensures all necessary directories exist before proceeding:
# the /tmp directory will be deleted anytime the VM is deallocated on azure, therefore any directory structure that we need in here needs to be re-created everytime
# blobfuse uses a file-cache to store temporary files that are uploaded /downloaded from Azure Blob Storage, this /tmp directory is the ideal place to keep these transient files
mkdir -p $blobfuseCacheDir
5. Mounting the Blob Storage Container
The script performs an initial mount to verify connectivity:
# now we are ready to use blobfuse to mount the Azure Blob Storage container to our miencraft worlds folder
blobfuse2 mount $blobfuseMountDir --config-file=$blobfuseConfigFile
6. Adding the Mount to /etc/fstab
Finally, the script ensures the mount is persistent across reboots by adding it to /etc/fstab:
if grep -qs 'blobfuse2' /proc/mounts; then
echo "It's already mounted."
else
echo "blobfuse2 $blobfuseMountDir fuse defaults,_netdev,--config-file=$blobfuseConfigFile,allow_other 0 0" >> /etc/fstab
echo "It's not mounted."
fi
Conclusion
The combination of Terraform, a templated Bash script, and the Azure Virtual Machine extension completes the last mile of Blobfuse configuration. By dynamically injecting values and performing environment-specific setup during deployment, we ensure a robust and adaptable solution. This approach not only simplifies deployment but also makes troubleshooting straightforward by exposing key variables and actions.
With this final piece in place, our three-part series on mastering last mile configuration automation comes to an end. Armed with these techniques, you’re ready to build scalable, reusable automation pipelines that bridge the gap between provisioning and configuration.
Happy Azure Terraforming!!!