In this tutorial, you will learn how to provision servers (AWS EC2) using Terraform, and then configure them as you need it with Ansible.
Initially, I did what follows for quickly provisioning a group of servers so that I could test Ansible playbooks or other things. Using the AWS Free Tier Account at any time you can quickly build a test environment for yourself at no cost.
I hope this article will make someone’s job easier and clarify a lot of things for beginners who are learning these tools.
What is Terraform?
With Terraform, we describe our entire infrastructure in code, even if it spans multiple service providers. Our servers may be from AWS, our DNS may be from CloudFlare, and our database may be from Azure. Terraform will create all of these resources in parallel for all of these providers. Terraform is one of the best tools for infrastructure preparation.
What is Ansible?
Ansible is an IT automation tool. You can use it to configure the virtual machines we created with Terraform. Ansible can also deploy software and organize more complex IT tasks, such as continuous deployment or updates with zero downtime.
Prerequisites
If you want to follow this tutorial, please make sure that all requirements are met.
- AWS account. You can create a new account for free here if you don’t already have one. After that you can set it up according to this official guide in setting it up.
- AWS CLI
- Terraform
- Ansible
- VSCode (optionally)
Creating a new user in AWS and configuring the AWS CLI for beginners (others may skip it)
Once you have created a new AWS account, you should have created a new user to practice with. Your account that you created is root user and has full administrative access. As a best practice, do not use the AWS account root user for any task where it’s not required. Instead, create a new IAM user for each person that requires administrator access. Here is a detailed guide. But you can also use the root user to get started or follow the guide below to quickly create a new user for practice.
To create a new user, log in to your AWS account, type IAM in the search box at the top, then click on IAM.Click Users on the left menu, then click Add users. Enter a user name, then select Access key — Programmatic access for the AWS access type. Click Next: Permissions

Next, we need to create a group for our user. Enter a Group name, give it AdministratorAccess, and click Create group. You can add Tags (optional), click Next: Tags and click Next: Review to view the user information, and then click Create user.

After that you will see a screen for downloading the user’s security credentials. Click Download.csv.

I hope you have already installed the AWS CLI, if not, do it as soon as possible. Is it done? Now we are ready to configure the AWS CLI. Go to the terminal and run the following command: aws configure
. Add the user Access Key ID and Secret Access Key as prompted. Then enter your preferred AWS region and Output format.

Now we are ready to move on.
Provisioning AWS EC2 instances with Terraform
First of all, it’s worth saying that, in a production environment, we should have first created an S3 Bucket and used it to store Terraform state files. If we have multiple engineers working on the project, we will store Terraform configuration files in a Version Control system (GitHub. GitLab, etc.) and Terraform state files in the Remote State Backend (Amazon S3, Terraform Cloud). This is to ensure that at any point in time, any team member using Terraform will not break the infrastructure already built. You can read more about this here. We will omit this step in this tutorial.
Now we are going to work with Terraform. You can use VSCode or any text editor you like.
All files are available in my repository.
Let’s create some terraform files:main.tf
— here is our main code to provide ec2variables.tf -
this is where we store the variables referenced in main.tfoutput.tf
— here we describe the information we want to see in the console after main.tf is executed. For example, the ip addresses of the created servers and their dns names.inventory.tf
— here we form the data for filling the inventory.tfpl
template, which will eventually generate an inventory file inventory.ini
for Ansible.
/home/gl1tch/aws_ec2_terraform_ansible/
├── inventory.tf
├── inventory.tftpl
├── main.tf
├── output.tf
└── variables.tf
In my case, I created files in the folder aws_ec2_terraform_ansible in the home directory of my user gl1tch.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.31.0"
}
}
}
provider "aws" {
alias = "eu"
region = "eu-central-1"
}
data "aws_region" "current" {}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "AWS VPC"
}
}
resource "aws_internet_gateway" "gateway" {
vpc_id = aws_vpc.main.id
}
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = aws_vpc.main.cidr_block
availability_zone = "${data.aws_region.current.name}a"
}
resource "aws_route_table" "route_table" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gateway.id
}
}
resource "aws_route_table_association" "route_table_association" {
subnet_id = aws_subnet.main.id
route_table_id = aws_route_table.route_table.id
}
resource "tls_private_key" "key" {
algorithm = "RSA"
}
resource "aws_key_pair" "aws_key" {
key_name = "ansible-ssh-key"
public_key = tls_private_key.key.public_key_openssh
}
resource "aws_security_group" "allow_ssh" {
name = "allow_ssh"
description = "Allow SSH traffic"
vpc_id = aws_vpc.main.id
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
cidr_blocks = ["0.0.0.0/0"]
from_port = 0
protocol = "-1"
to_port = 0
}
}
resource "aws_security_group" "allow_http" {
name = "allow_http"
description = "Allow HTTP traffic"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
cidr_blocks = ["0.0.0.0/0"]
from_port = 0
protocol = "-1"
to_port = 0
}
}
resource "aws_instance" "server" {
count = var.instance_count # here we define with the variable instance_count how many servers we want to create (see variables.tf)
ami = var.ami
instance_type = var.instance_type
key_name = aws_key_pair.aws_key.key_name
associate_public_ip_address = true
subnet_id = aws_subnet.main.id
vpc_security_group_ids = [aws_security_group.allow_ssh.id, aws_security_group.allow_http.id]
tags = {
Name = element(var.instance_tags, count.index)
}
}
The above HCL (the low-level syntax of the Terraform language is defined in terms of a syntax called HCL ) will create:
- A new VPC, subnet, internet gateway and route table
- A 3 new AWS t2.micro ec2 called “tf-ansible-[1..3]” with a public ip address and port 22 (ssh) and 80 (http) accessible. The number of servers we want to create we define in the
variables.tf
file in the instance_count variable - A ssh key for accessing the instance
# variables.tf
# below we define with the variable instance_count how many servers we want to create
variable "instance_count" {
default = "3"
}
# below we define the default server names
variable "instance_tags" {
type = list(string)
default = ["tf-ansible-1", "tf-ansible-2", "tf-ansible-3", "tf-ansible-4", "tf-ansible-5"]
}
# we use Ubuntu as the OS
variable "ami" {
type = string
default = "ami-0a10efeaee4955739" #eu-central-1 image for x86_64 Ubuntu_20.04 2021-05-28T21:06:05.000Z
}
variable "instance_type" {
type = string
default = "t2.micro"
}
# output.tf
output "server-data" {
value = [for vm in aws_instance.server[*] : {
ip_address = vm.public_ip
public_dns = vm.public_dns
}]
description = "The public IP and DNS of the servers"
}
With the files above we can already run terraform apply and it will create us 3 new servers in AWS. But we want to manage their configuration immediately with Ansible. All we need to do now is to pass the information about our servers to Ansible. Moving on.
Passing information to Ansible
In addition to the servers, Terraform will also create ssh keys so that we can use Ansible to connect to them and configure them. We need to pass information about our servers to Ansible.
Ansible requires an inventory file to work. This cannot be a static file, as it must contain the ip address and ssh key created by Terraform. Create two files: inventory.tf and inventory.tftpl
# inventory.tftpl
[aws_ec2]
%{ for addr in ip_addrs ~}
${addr}
%{ endfor ~}
[aws_ec2:vars]
ansible_ssh_user=ubuntu
ansible_ssh_private_key_file=${ssh_keyfile}
# inventory.tf
resource "local_sensitive_file" "private_key" {
content = tls_private_key.key.private_key_pem
filename = format("%s/%s/%s", abspath(path.root), ".ssh", "ansible-ssh-key.pem")
file_permission = "0600"
}
resource "local_file" "ansible_inventory" {
content = templatefile("inventory.tftpl", {
ip_addrs = [for i in aws_instance.server:i.public_ip]
ssh_keyfile = local_sensitive_file.private_key.filename
})
filename = format("%s/%s", abspath(path.root), "inventory.ini")
}
inventory.tf
will save the private key registered on the EC2 instances to a local file. It will also create a local inventory.ini
file containing the ip addresses of the new instances as well as the absolute path to the private key file.
inventory.ini
will look something like this:
[aws_ec2]
3.120.177.48
18.184.59.230
18.196.70.80
[aws_ec2:vars]
ansible_ssh_user=ubuntu
ansible_ssh_private_key_file=/home/gl1tch/aws_ec2_terraform_ansible/.ssh/ansible-ssh-key.pem
Now we need to tell Ansible about our inventory.ini file so that it can use it. In the directory where you created the terraform files, create ansible
directory and inside it create ansible.cfg
Add the following lines to ansible.cfg
/home/gl1tch/aws_ec2_terraform_ansible/
├── ansible
│ └── ansible.cfg
[defaults]
inventory=/home/gl1tch/aws_ec2_terraform_ansible/inventory.ini
host_key_checking = False
This will instruct Ansible to read the inventory.ini
file created by Terraform so that it can connect to our new EC2 instances.
Using Ansible to bring up a web server on AWS servers
At this point we are ready to run the terraform apply
command and after that we can run ansible -m ping aws_ec2
. We will see the following, our servers are accessible by Ansible.
02:45 gl1tch@powerpc:~/aws_ec2_terraform_ansible/ansible$ ansible -m ping aws_ec2
3.120.177.48 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
18.184.59.230 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
18.196.70.80 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
But first let’s just create a playbook.yaml
in a separate ansible
directory, which will deploy nginx on all the servers.
/home/gl1tch/aws_ec2_terraform_ansible/
├── ansible
│ ├── ansible.cfg
│ └── playbook.yaml
playbook.yaml
- hosts: aws_ec2
become: yes
tasks:
- name: Update apt cache and install Nginx
apt:
name: nginx
state: latest
update_cache: yes
- name: Allow all access to tcp port 80
ufw:
rule: allow
port: '80'
proto: tcp
- name: Start nginx
service:
name: nginx
state: started
Let’s create and configure our infrastructure
At this point, your operations repository should look like this:
/home/gl1tch/aws_ec2_terraform_ansible/
├── ansible
│ ├── ansible.cfg
│ └── playbook.yaml
├── inventory.ini
├── inventory.tf
├── inventory.tftpl
├── main.tf
├── output.tf
└── variables.tf
In the directory where you created all the above files, run one command at a time in the console:terraform init
terraform apply
(enter yes when prompted to apply the change)
At this point, you can look at your new instances in the AWS web console.

Let’s run playbook ansible-playbook ansible/playbook.yaml
02:54 gl1tch@powerpc:~/aws_ec2_terraform_ansible/ansible$ ansible-playbook ansible/playbook.yaml
PLAY [aws_ec2] ****************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [18.196.70.80]
ok: [18.184.59.230]
ok: [3.120.177.48]
TASK [Update apt cache and install Nginx] *************************************************************************************************************
changed: [3.120.177.48]
changed: [18.196.70.80]
changed: [18.184.59.230]
TASK [Allow all access to tcp port 80] ****************************************************************************************************************
changed: [18.196.70.80]
changed: [3.120.177.48]
changed: [18.184.59.230]
TASK [Start nginx] ************************************************************************************************************************************
ok: [3.120.177.48]
ok: [18.196.70.80]
ok: [18.184.59.230]
PLAY RECAP ********************************************************************************************************************************************
18.184.59.230 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
18.196.70.80 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3.120.177.48 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Now we can check that Nginx is running

or from the terminal
03:04 gl1tch@powerpc:~/aws_ec2_terraform_ansible/ansible$ curl 18.184.59.230
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Cleanup
Run terraform destroy
in the terminal and all that we have created will be destroyed.
The following shows the console output as we run Terraform and as we run playbook.yaml in Ansible (click to expand)
02:17 gl1tch@powerpc:~/aws_ec2_terraform_ansible$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.31.0"...
- Finding latest version of hashicorp/local...
- Finding latest version of hashicorp/tls...
- Installing hashicorp/aws v4.31.0...
- Installed hashicorp/aws v4.31.0 (signed by HashiCorp)
- Installing hashicorp/local v2.2.3...
- Installed hashicorp/local v2.2.3 (signed by HashiCorp)
- Installing hashicorp/tls v4.0.2...
- Installed hashicorp/tls v4.0.2 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
02:18 gl1tch@powerpc:~/aws_ec2_terraform_ansible$ terraform apply
data.aws_region.current: Reading...
data.aws_region.current: Read complete after 0s [id=eu-central-1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.server[0] will be created
+ resource "aws_instance" "server" {
+ ami = "ami-0a10efeaee4955739"
+ arn = (known after apply)
+ associate_public_ip_address = true
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = "ansible-ssh-key"
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "tf-ansible-1"
}
+ tags_all = {
+ "Name" = "tf-ansible-1"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
+ capacity_reservation_specification {
+ capacity_reservation_preference = (known after apply)
+ capacity_reservation_target {
+ capacity_reservation_id = (known after apply)
+ capacity_reservation_resource_group_arn = (known after apply)
}
}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ enclave_options {
+ enabled = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
+ maintenance_options {
+ auto_recovery = (known after apply)
}
+ metadata_options {
+ http_endpoint = (known after apply)
+ http_put_response_hop_limit = (known after apply)
+ http_tokens = (known after apply)
+ instance_metadata_tags = (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_card_index = (known after apply)
+ network_interface_id = (known after apply)
}
+ private_dns_name_options {
+ enable_resource_name_dns_a_record = (known after apply)
+ enable_resource_name_dns_aaaa_record = (known after apply)
+ hostname_type = (known after apply)
}
+ root_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
}
# aws_instance.server[1] will be created
+ resource "aws_instance" "server" {
+ ami = "ami-0a10efeaee4955739"
+ arn = (known after apply)
+ associate_public_ip_address = true
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = "ansible-ssh-key"
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "tf-ansible-2"
}
+ tags_all = {
+ "Name" = "tf-ansible-2"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
+ capacity_reservation_specification {
+ capacity_reservation_preference = (known after apply)
+ capacity_reservation_target {
+ capacity_reservation_id = (known after apply)
+ capacity_reservation_resource_group_arn = (known after apply)
}
}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ enclave_options {
+ enabled = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
+ maintenance_options {
+ auto_recovery = (known after apply)
}
+ metadata_options {
+ http_endpoint = (known after apply)
+ http_put_response_hop_limit = (known after apply)
+ http_tokens = (known after apply)
+ instance_metadata_tags = (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_card_index = (known after apply)
+ network_interface_id = (known after apply)
}
+ private_dns_name_options {
+ enable_resource_name_dns_a_record = (known after apply)
+ enable_resource_name_dns_aaaa_record = (known after apply)
+ hostname_type = (known after apply)
}
+ root_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
}
# aws_instance.server[2] will be created
+ resource "aws_instance" "server" {
+ ami = "ami-0a10efeaee4955739"
+ arn = (known after apply)
+ associate_public_ip_address = true
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = "ansible-ssh-key"
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "tf-ansible-3"
}
+ tags_all = {
+ "Name" = "tf-ansible-3"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
+ capacity_reservation_specification {
+ capacity_reservation_preference = (known after apply)
+ capacity_reservation_target {
+ capacity_reservation_id = (known after apply)
+ capacity_reservation_resource_group_arn = (known after apply)
}
}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ enclave_options {
+ enabled = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
+ maintenance_options {
+ auto_recovery = (known after apply)
}
+ metadata_options {
+ http_endpoint = (known after apply)
+ http_put_response_hop_limit = (known after apply)
+ http_tokens = (known after apply)
+ instance_metadata_tags = (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_card_index = (known after apply)
+ network_interface_id = (known after apply)
}
+ private_dns_name_options {
+ enable_resource_name_dns_a_record = (known after apply)
+ enable_resource_name_dns_aaaa_record = (known after apply)
+ hostname_type = (known after apply)
}
+ root_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
}
# aws_internet_gateway.gateway will be created
+ resource "aws_internet_gateway" "gateway" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_key_pair.aws_key will be created
+ resource "aws_key_pair" "aws_key" {
+ arn = (known after apply)
+ fingerprint = (known after apply)
+ id = (known after apply)
+ key_name = "ansible-ssh-key"
+ key_name_prefix = (known after apply)
+ key_pair_id = (known after apply)
+ key_type = (known after apply)
+ public_key = (known after apply)
+ tags_all = (known after apply)
}
# aws_route_table.route_table will be created
+ resource "aws_route_table" "route_table" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ propagating_vgws = (known after apply)
+ route = [
+ {
+ carrier_gateway_id = ""
+ cidr_block = "0.0.0.0/0"
+ core_network_arn = ""
+ destination_prefix_list_id = ""
+ egress_only_gateway_id = ""
+ gateway_id = (known after apply)
+ instance_id = ""
+ ipv6_cidr_block = ""
+ local_gateway_id = ""
+ nat_gateway_id = ""
+ network_interface_id = ""
+ transit_gateway_id = ""
+ vpc_endpoint_id = ""
+ vpc_peering_connection_id = ""
},
]
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_route_table_association.route_table_association will be created
+ resource "aws_route_table_association" "route_table_association" {
+ id = (known after apply)
+ route_table_id = (known after apply)
+ subnet_id = (known after apply)
}
# aws_security_group.allow_http will be created
+ resource "aws_security_group" "allow_http" {
+ arn = (known after apply)
+ description = "Allow HTTP traffic"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = ""
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = "HTTP"
+ from_port = 80
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 80
},
]
+ name = "allow_http"
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_security_group.allow_ssh will be created
+ resource "aws_security_group" "allow_ssh" {
+ arn = (known after apply)
+ description = "Allow SSH traffic"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = ""
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = "SSH"
+ from_port = 22
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 22
},
]
+ name = "allow_ssh"
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_subnet.main will be created
+ resource "aws_subnet" "main" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "eu-central-1a"
+ availability_zone_id = (known after apply)
+ cidr_block = "10.0.0.0/16"
+ enable_dns64 = false
+ enable_resource_name_dns_a_record_on_launch = false
+ enable_resource_name_dns_aaaa_record_on_launch = false
+ id = (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
+ ipv6_native = false
+ map_public_ip_on_launch = false
+ owner_id = (known after apply)
+ private_dns_hostname_type_on_launch = (known after apply)
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
# aws_vpc.main will be created
+ resource "aws_vpc" "main" {
+ arn = (known after apply)
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_classiclink = (known after apply)
+ enable_classiclink_dns_support = (known after apply)
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_network_border_group = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "AWS VPC"
}
+ tags_all = {
+ "Name" = "AWS VPC"
}
}
# local_file.ansible_inventory will be created
+ resource "local_file" "ansible_inventory" {
+ content = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "/home/gl1tch/aws_ec2_terraform_ansible/inventory.yaml"
+ id = (known after apply)
}
# local_sensitive_file.private_key will be created
+ resource "local_sensitive_file" "private_key" {
+ content = (sensitive value)
+ directory_permission = "0700"
+ file_permission = "0600"
+ filename = "/home/gl1tch/aws_ec2_terraform_ansible/.ssh/ansible-ssh-key.pem"
+ id = (known after apply)
}
# tls_private_key.key will be created
+ resource "tls_private_key" "key" {
+ algorithm = "RSA"
+ ecdsa_curve = "P224"
+ id = (known after apply)
+ private_key_openssh = (sensitive value)
+ private_key_pem = (sensitive value)
+ private_key_pem_pkcs8 = (sensitive value)
+ public_key_fingerprint_md5 = (known after apply)
+ public_key_fingerprint_sha256 = (known after apply)
+ public_key_openssh = (known after apply)
+ public_key_pem = (known after apply)
+ rsa_bits = 2048
}
Plan: 14 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ server-data = [
+ {
+ ip_address = (known after apply)
+ public_dns = (known after apply)
},
+ {
+ ip_address = (known after apply)
+ public_dns = (known after apply)
},
+ {
+ ip_address = (known after apply)
+ public_dns = (known after apply)
},
]
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
tls_private_key.key: Creating...
tls_private_key.key: Creation complete after 0s [id=73cae05d4390124c1cbd578a3b0722e8bd91d66f]
local_sensitive_file.private_key: Creating...
local_sensitive_file.private_key: Creation complete after 0s [id=39ea092b365f77d13cee7d476cd0ba2caaeca19c]
aws_key_pair.aws_key: Creating...
aws_vpc.main: Creating...
aws_key_pair.aws_key: Creation complete after 1s [id=ansible-ssh-key]
aws_vpc.main: Still creating... [10s elapsed]
aws_vpc.main: Creation complete after 12s [id=vpc-0f1ce8f080aa055af]
aws_internet_gateway.gateway: Creating...
aws_subnet.main: Creating...
aws_security_group.allow_http: Creating...
aws_security_group.allow_ssh: Creating...
aws_subnet.main: Creation complete after 1s [id=subnet-0b84074ed497c7099]
aws_internet_gateway.gateway: Creation complete after 1s [id=igw-002991c26595a189b]
aws_route_table.route_table: Creating...
aws_route_table.route_table: Creation complete after 2s [id=rtb-0919e12b730771aff]
aws_route_table_association.route_table_association: Creating...
aws_route_table_association.route_table_association: Creation complete after 0s [id=rtbassoc-08250e3b25193b7bb]
aws_security_group.allow_ssh: Creation complete after 3s [id=sg-031c1bbeb42157a6c]
aws_security_group.allow_http: Creation complete after 3s [id=sg-0685265635f3ebe45]
aws_instance.server[2]: Creating...
aws_instance.server[0]: Creating...
aws_instance.server[1]: Creating...
aws_instance.server[2]: Still creating... [10s elapsed]
aws_instance.server[0]: Still creating... [10s elapsed]
aws_instance.server[1]: Still creating... [10s elapsed]
aws_instance.server[2]: Still creating... [20s elapsed]
aws_instance.server[0]: Still creating... [20s elapsed]
aws_instance.server[1]: Still creating... [20s elapsed]
aws_instance.server[1]: Still creating... [30s elapsed]
aws_instance.server[2]: Still creating... [30s elapsed]
aws_instance.server[0]: Still creating... [30s elapsed]
aws_instance.server[0]: Creation complete after 33s [id=i-0e8804bb98c6bb8c1]
aws_instance.server[1]: Creation complete after 33s [id=i-09e81317f324dcbbc]
aws_instance.server[2]: Still creating... [40s elapsed]
aws_instance.server[2]: Creation complete after 43s [id=i-0a0a21cb81c35dad8]
local_file.ansible_inventory: Creating...
local_file.ansible_inventory: Creation complete after 0s [id=2458e627bbab1372582b683ec15c1ac10965fe57]
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.
Outputs:
server-data = [
{
"ip_address" = "3.120.177.48"
"public_dns" = "ec2-3-120-177-48.eu-central-1.compute.amazonaws.com"
},
{
"ip_address" = "18.184.59.230"
"public_dns" = "ec2-18-184-59-230.eu-central-1.compute.amazonaws.com"
},
{
"ip_address" = "18.196.70.80"
"public_dns" = "ec2-18-196-70-80.eu-central-1.compute.amazonaws.com"
},
]
02:53 gl1tch@powerpc:~/aws_ec2_terraform_ansible$ cd ansible/
02:54 gl1tch@powerpc:~/aws_ec2_terraform_ansible/ansible$ ansible-playbook playbook.yaml
PLAY [aws_ec2] ****************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************
ok: [18.196.70.80]
ok: [18.184.59.230]
ok: [3.120.177.48]
TASK [Update apt cache and install Nginx] *************************************************************************************************************
changed: [3.120.177.48]
changed: [18.196.70.80]
changed: [18.184.59.230]
TASK [Allow all access to tcp port 80] ****************************************************************************************************************
changed: [18.196.70.80]
changed: [3.120.177.48]
changed: [18.184.59.230]
TASK [Start nginx] ************************************************************************************************************************************
ok: [3.120.177.48]
ok: [18.196.70.80]
ok: [18.184.59.230]
PLAY RECAP ********************************************************************************************************************************************
18.184.59.230 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
18.196.70.80 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3.120.177.48 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
03:04 gl1tch@powerpc:~/aws_ec2_terraform_ansible/ansible$ curl 18.184.59.230
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>