How to connect Terraform and Ansible together? - azure

I have written a Terraform script to spin-up an infrastructure on Azure. I have also written an Ansible script to patch the VMs launched on Azure with latest updates. But when I am not able to automate the process of patching the VMs once they get launched.

You can use Provisioners in Terraform to execute Ansible Playbooks on Provisioned VM. I'm not sure about your terraform Version. But below code might help. Keep in mind Provisioners are to be used as last resort
provisioner "local-exec" {
command = "ansible-playbook -u user -i '${self.public_ip},' --private-key ${var.ssh_key_private} provision.yml"
}
https://www.terraform.io/docs/language/resources/provisioners/syntax.html

To have end-to-end automation in which the Ansible is run when the instances are launched (and/or at every restart) you can pass in cloud-init configuration from Terraform. This is nice because that config may be referencing other parts of your infrastructure which can be sorted out by Terraform's dependency resolution. You would do this by providing Terraform cloudinit_config to the custom_data argument of the Azure VM in Terraform.
On the Ansible side you can also use the Azure dynamic inventory. With this dynamic inventory you add tags to your resources in Terraform in such a way that they can be filtered and grouped into the Ansible inventory when Ansible is run. This is helpful if the Ansible tasks need to gather facts from hosts.

Related

How To Capture information about Virtual Machine resources that will be destroyed?

Background
I was kind of dropped into an IaC project that uses Packer => Terraform => Ansible to create RHEL Virtual Machines on an on-prem VMware Vsphere cluster.
Our vmware module registers output variables that we use once the VMs are created, those variables feed a local_file resource template to build an Ansible inventory with the vm names and some other variables.
Ansible is then run using local_exec with the above created inventory to do configuration actions and run scripts both on the newly deployed VM's and against some external management applications, for example to join the VM to a domain (FreeIPA, sadly no TF good provider is available).
Issue Description
The issue that I have been wrestling with is when we run a terraform destroy (or apply with some VM count changes that destroy a VM resource), we would like to be able to repeat the process in reverse.
Capture the names of the VMs to be destroyed(Output vars from resource creation) so they can be removed from the IPA domain and have some general cleanup.
We've tried different approaches with Destroy Time Provisioners and it just seems like it would require a fundamental change in the approach outlined above to make that work.
Question
I'm wondering if there is a way to get an output variable on destroy that could be used to populate a list the VMs that would be removed.
So far my search has turned up nothing. Thanks for your time.
In general, it is good to plan first, even when destroying:
terraform plan -destroy -out tfplan
Then, you you can proceed with the destroy:
terraform apply tfplan
But at this moment (or before actual destroy), you have a plan what was destroyed, and you can do any analysis or automation on it. Example:
terraform show -json tfplan | jq > tfplan.json
Source:
https://learn.hashicorp.com/tutorials/terraform/plan

Regarding terraform script behaviour

I am using Terraform scripts to create azure services, I am having some doubts regarding Terraform,
1) If I have one environment let say dev in azure having some azure resources how can I copy all the resources to new environment lest say prod using terraform script.
2)what are the impact of re-run the terraform file with additional azure resources, what it will do.
3)What if I want to create an app service with the same name from Terraform script that already present in the azure will it update the resource or do nothing after terraform execution completed.
Please feel free to answer the question, it will be great help.
To answer your questions:
You could create a new workspace with terraform workspace new and copy all configuration files (.tf) to the new environment, then run terraform init, plan, apply.
The terraform will compare the content in your current state file with your configuration file, then update the new attributes or creating new resources other than re-creating the existing resources.
You could run terraform import to import existing infrastructure into Terraform. For referencing existing resources in the portal, you can use data sources.

How Do I Combine Terraform With Azure CLI and REST API?

I have a most of my Azure infrastructure managed with Terraform.
However, I am quickly finding that a lot of the small details are missing.
e.g. client secrets aren't fully supported https://github.com/terraform-providers/terraform-provider-azuread/issues/95
It doesn't seem possible to add an Active Directory Provider to APIM
How Do I Add Active Directory To APIM Using Terraform?
Creating the APIM leaves demo products on it that can't be removed
How Can I Remove Demo Products From APIM Created With Terraform?
etc, etc.
Solutions to these seems to be utilising the cli
e.g. https://learn.microsoft.com/en-us/cli/azure/ad/app/permission?view=azure-cli-latest#az-ad-app-permission-add
Or falling back to the REST API:
e.g.
https://learn.microsoft.com/en-us/rest/api/apimanagement/2019-01-01/apis/delete
How can I mix terraform with the CLI and REST API?
Can they be embedded in terraform?
Or do I just run some commands to run them after terraform has finished?
Is there a way to do these commands in a cross platform way?
Will running the CLI and REST API after terraform cause the state to be wrong and likely cause problems the next time terraform is run?
How can I mix terraform with the CLI and REST API?
You can use the Terraform provisioner local-exec or remote-exec. In these ways, you can run the script with CLI commands or the REST API. For more details, see local-exec and remote-exec. But you need to take care of them. These two ways just run the scripts and display the output, but they do not have the outputs.
If you want to use the result of the script in the same Terraform file for other resources, you need to use the Terraform external data source, see the details here.
Update:
Here is an example.
Bash script file vmTags.sh:
#!/bin/bash
az vm show -d -g myGroup -n myVM --query tags
Terraform external data source:
data "external" "test" {
program = ["/bin/bash", "./vmTags.sh"]
}
output "value" {
value = "${data.external.test.result}"
}

Terraform persistent and dynamic infrastructure parts?

I want to divide my infrastructure into two parts:
Persistent (firewalls, block storages, etc)
Dynamic (that will consume persistent resources from #1)
I want to be sure that persistent part never would be deleted and at the same time, there would be an option terraform destroy on the dynamic infrastructure part.
All resources you do not want to destroy you have to add the lifecycle policy: prevent_destroy
Have a look at the documenation: https://www.terraform.io/docs/configuration/resources.html#prevent_destroy
To fully prevent the destruction you would have to fine tune the permissions on the resources at your provider. However there is an easy way to divide your infrastructure.
Terraform offers a remote state data source which allows you to use output from a different project so you won't be able to destroy those resources while working with the dynamic part.
I have a bit of a different work around. The resources I do not want to delete with "terraform destroy" I create as "null_resource" using a provisioner with CLI. You can still use your variables in terraform as well.
for example (Create a resource group, but it is persistent due to null_resource)
resource "null_resource" "backend-config" {
provisioner "local-exec" {
command = <<EOT
az group create --location ${var.Location} --name ${var.Resource_group_name} --tags 'LineOfBusiness=${var.Lob}' 'Region=${var.Region}' 'Purpose="Terraform-Primary-Resource-Group-${var.Lob}'
EOT
interpreter = ["Powershell", "-Command"]
}
}
Now if you destroy the resources using terraform destroy. Any null_resource will remain intact.
I would solve this by having 2 Terraform deployments. You create the "static" resources once, and don't touch them. For extra safety, manually add a deletion lock to those resources (eg. I know you can do this in Azure, I assume other cloud providers have a similar solution).
Import these resources in your Dynamic Terraform deployment, using data blocks (not resources). Terraform will never attempt to delete resources you import using data blocks.

Terraform and Updates

Being able to capture infrastructure in a single Terraform file has obvious benefits. However, I am not clear in my mind how - once, for example, a virtual machine has been created - subsequent updates are handled.
So, to provide a specific scenario. Suppose that using Terraform we set up an Azure vm with SQL Server 2014. Then, after a month we decide that we should like to update that vm with the latest service pack for SQL Server 2014 that has just been released.
Is the recommended practice that we update the Terraform configuration file and re-apply it?
I have to disagree with the other two responses. Terraform can handle infrastructure updates just fine. The key thing to understand, however, is that Terraform largely follows an immutable infrastructure paradigm, which means that to "update" a resource, you delete the old resource and create a new one to replace it. This is much like functional programming, where variables are immutable, and to "update" something, you actually create a new variable.
The typical pattern with Terraform is to use it to deploy a server image, such as an Virtual Machine (VM) Image (e.g. an Amazon Machine Image (AMI)) or a Container Image (e.g. a Docker Image). When you want to "update" something, you create a new version of your image, deploy that onto a new server, and undeploy the old server.
Here's an example of how that works:
Imagine that you're building a Ruby on Rails app. You get the app working in dev and it's time to deploy to prod. The first step is to package the app as an AMI. You could do this using a tool like Packer. Now you have an AMI with id ami-1234.
Here is a Terraform template you could use to deploy this AMI on a server (an EC2 Instance) in AWS with an Elastic IP Address attached to it:
resource "aws_instance" "example" {
ami = "ami-1234"
instance_type = "t2.micro"
}
resource "aws_eip" "example" {
instance = "${aws_instance.example.id}"
}
When you run terraform apply, Terraform deploys the server, attaches an IP address to it, and now when users visit that IP, they will see v1 of your Rails app.
Some time later, you update your Rails app and want to deploy the new version, v2. To do that, you build a new AMI (i.e. you run Packer again) to get an ami with ID "ami-5678". You update your Terraform templates accordingly:
resource "aws_instance" "example" {
ami = "ami-5678"
instance_type = "t2.micro"
}
When you run terraform apply, Terraform undeploys the old server (which it can find because Terraform records the state of your infrastructure), deploys a new server with the new AMI, and now users will see v2 of your code at that same IP.
Of course, there is one problem here: in between the time when Terraform undeploys v1 and when it deploys v2, your users would see downtime. To work around that, you could use Terraform's create_before_destroy lifecycle setting:
resource "aws_instance" "example" {
ami = "ami-5678"
instance_type = "t2.micro"
lifecycle {
create_before_destroy = true
}
}
With create_before_destroy set to true, Terraform will create the replacement server first, switch the IP to it, and then remove the old server. This allows you to do zero-downtime deployment with immutable infrastructure (note: zero-downtime deployment works better with a load balancer that can do health checks than a simple IP address, especially if your server takes a long time to boot).
For more information on this, check out the book Terraform: Up & Running. The code samples for the book include an example of a zero-downtime deployment with a cluster of servers and a load balancer: https://github.com/brikis98/terraform-up-and-running-code
Terraform is an infrastructure provision tool, th configuration/deployment tools will be:
chef
saltstack
ansible
etc.,
As I am working with chef, so basically, I provision the server instance by terraform, then terraform (terraform provisioner) handles the control to chef for system configuration and deployment.
For the moment, terraform cannot delete the node/client in chef server, so after you terraform destroy, you need remove them by yourself.
Terraform isn't best placed for this sort of task. Terraform is an infrastructure management tool, not configuration management.
You should use tools such as chef, puppet, and ansible to deal with the configuration of the system.
If you must use terraform for this task; you could create a template_file resource and place in the configuration required to install the SQL server, and how to upgrade if a different version is presented. Reference: here
Put that code inside a provisioner under the null_resource resource. reference: here.
The trigger for this could be the variable containing the SQL version. So, when you present a different version of SQL it'll execute that provisioner on each instance to upgrade the versions.

Resources