2.1.4 VCN, Terraform and Ansible (Nginx example)
Example for deploying Nginx service on OCI Instance with Terraform and Ansible
This tutorial will show you how to use Terraform and Ansible for deploying an Nginx service on an OCI Instance.
I advise you to read tutorials 2.1.1, 2.1.2 and 2.1.3 before moving on with this one.
For the instance creation with Terraform, take a look at "Terraform - small test"
The purpose of using both Terraform and Ansible
Terraform will implement a VCN and its resources along with a Compute Instance
Ansible will deploy nginx service on the deployed Instance
Prepare environment
For installation of Ansible on Ubuntu, perform following steps:.
1.
Check if Python3 is installed and configured:
root@deploymentmachine:/home/terra_and_ansible# python3 --version
Python 3.8.5
2.
Proceed with
installation steps
(example on Ubuntu):
root@deploymentmachine:/home/terra_and_ansible# apt update
Hit:1 http://repo.mysql.com/apt/ubuntu focal InRelease
Hit:2 http://eu-frankfurt-1-ad-3.clouds.archive.ubuntu.com/ubuntu focal InRelease
[...... snip .....]
root@deploymentmachine:/home/terra_and_ansible#
root@deploymentmachine:/home/terra_and_ansible# apt install software-properties-common
Reading package lists... Done
Building dependency tree
Reading state information... Done
[...... snip .....]
root@deploymentmachine:/home/terra_and_ansible# apt-add-repository --yes --update ppa:ansible/ansible
Hit:1 http://repo.mysql.com/apt/ubuntu focal InRelease
Hit:2 http://security.ubuntu.com/ubuntu focal-security InRelease
Hit:3 https://packages.grafana.com/oss/deb stable InRelease
[...... snip .....]
root@deploymentmachine:/home/terra_and_ansible# apt install ansible -y
Reading package lists... Done
Building dependency tree
Reading state information... Done
[...... snip .....]
3.
Check the installed version
root@deploymentmachine:/home/terra_and_ansible# ansible --version
ansible 2.9.6
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0]
Content of my working directory
root@deploymentmachine:/home/terra_and_ansible# tree .
.
├── ansible.cfg
├── compartment.tf
├── data.tf
├── dhcp_opt.tf
├── instance.tf
├── int_gateway.tf
├── nginx
│  ├── index.html
│  └── static-site.cfg
├── nginx.yml
├── provider.tf
├── remote.tf
├── route.tf
├── security_list.tf
├── subnet.tf
├── variables.tf
└── vcn.tf
1 directory, 16 files
...and now, let's provide the content of each file (terraform and ansible)
Ansible files, Nginx files and folders for deploying Nginx
The Ansible playbook that will install and configure the Nginx service:
root@deploymentmachine:/home/terra_and_ansible# more nginx.yml
---
- hosts: all
tasks:
- name: install latest version of nginx
apt: name=nginx state=latest
become: yes
- name: start nginx
service:
name: nginx
state: started
- name: copy static-site.cfg to remote host
copy:
src: /home/terra_and_ansible/nginx/static-site.cfg
dest: /etc/nginx/sites-available/static-site.cfg
become: yes
- name: create symlink
file:
src: /etc/nginx/sites-available/static-site.cfg
dest: /etc/nginx/sites-enabled/static-site.cfg
state: link
become: yes
- name: copy index.html to remote host
copy:
src: /home/terra_and_ansible/nginx/
dest: /home/ubuntu/static-site/
- name: restart nginx
service:
name: nginx
state: restarted
become: yes
The folder "nginx" contains files "index.html" and "static-site.cfg" that are necessary to configure nginx after its installation:
root@deploymentmachine:/home/terra_and_ansible# more nginx/static-site.cfg
server {
listen 8080 default_server;
listen [::]:8080 default_server;
root /home/ubuntu/static-site;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
root@deploymentmachine:/home/terra_and_ansible# more nginx/index.html
<h1> Henlo! </h1>
root@deploymentmachine:/home/terra_and_ansible#
For ignoring SSH authenticity checking, file
ansible.cfg
was created
root@deploymentmachine:/home/terra_and_ansible# more ansible.cfg
[defaults]
host_key_checking = False
You can also set this as an environment variable:
root@deploymentmachine:/home/terra_and_ansible# export ANSIBLE_HOST_KEY_CHECKING=False
root@deploymentmachine:/home/terra_and_ansible#
root@deploymentmachine:/home/terra_and_ansible# echo $ANSIBLE_HOST_KEY_CHECKING
False
root@deploymentmachine:/home/terra_and_ansible#
The Terraform files for provisioning VCN and Instance
root@deploymentmachine:/home/terra_and_ansible# tree -I '*.tfstate|*.tfstate.backup|*.yml|*.cfg|nginx'
.
├── compartment.tf
├── data.tf
├── dhcp_opt.tf
├── instance.tf
├── int_gateway.tf
├── provider.tf
├── remote.tf
├── route.tf
├── security_list.tf
├── subnet.tf
├── variables.tf
└── vcn.tf
0 directories, 12 files
1.
The content of
provider.tf
root@deploymentmachine:/home/terra_and_ansible# more provider.tf
provider "oci" {
tenancy_ocid = "ocid1.tenancy.oc1..aaaaaaaaxxxxxxxxxxxxxxxxxxxxxxxx"
user_ocid = "ocid1.user.oc1..aaaaaaaaqqqqqqqqqqqqqqqqqqqqqqq"
private_key_path = "/root/.oci/oci_api_key.pem"
fingerprint = "52:x2:x0:xx:12:xx:xx:xx:xx:cd:sd:as:as:as:as:as"
region = "eu-frankfurt-1"
}
2.
The content of
variables.tf
(added variables for VCN & Instance creation):
We will create an instance with Ubuntu image. Notice that we allow traffic on ports 22, 8080 and 443. As you probably have noticed earlier, in the static-site.cfg, the default port for nginx has been changed from 80 to 8080.
root@deploymentmachine:/home/terra_and_ansible# more variables.tf
variable "compartment_ocid" {
default = "ocid1.tenancy.oc1..aaaaaaaaThisIsTheR0otC0mpartmentOc1d"
}
# for vcn block
variable "cidrblockz" {
type = list(string)
default = ["10.0.0.0/16"]
}
# for subnet
variable "cidrsubnet" {
default = "10.0.1.0/24"
}
# for ingress
variable "cidr_ingress" {
default = "10.0.0.0/16"
}
# for security list
variable "portz" {
default = [22,8080,443]
}
## for instance
# Ubuntu image
variable "instance_image" {
default = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaa2fbceq23oofnxf4v23urfnfzui6n6det6ianoyvtmsbo5nzv2efq"
}
variable "instance_name" {
default = "WildTestInstance"
}
variable "instance_shape" {
default = "VM.Standard.E2.1"
}
variable "available_dom" {
default = "Aodz:EU-FRANKFURT-1-AD-1"
}
variable "private_key_path" {
default = "/root/.ssh/id_rsa"
}
3.
The content of
compartment.tf:
root@deploymentmachine:/home/terra_and_ansible# more compartment.tf
resource "oci_identity_compartment" "WildTestCompartment" {
compartment_id = var.compartment_ocid
description = "Compartment test for VCN"
name = "WildTestCompartment"
}
This will create a child compartment for the root compartment, named "WildTestCompartment"
4.
The content of
vcn.tf
:
root@deploymentmachine:/home/terra_and_ansible# more vcn.tf
resource "oci_core_virtual_network" "WildTestVCN" {
cidr_blocks = var.cidrblockz
compartment_id = oci_identity_compartment.WildTestCompartment.id
display_name = "WildTestVCN"
dns_label = "WildTestVCN"
}
5.
Content of
subnet.tf
:
root@deploymentmachine:/home/terra_and_ansible# more subnet.tf
resource "oci_core_subnet" "WildTestSubnet"{
cidr_block = var.cidrsubnet
compartment_id = oci_identity_compartment.WildTestCompartment.id
vcn_id = oci_core_virtual_network.WildTestVCN.id
display_name = "WildTestSubnet"
# security list
security_list_ids = [oci_core_security_list.WildTestSecurityList.id]
# route table
route_table_id = oci_core_route_table.WildTestRouteTable.id
# dhcp
dhcp_options_id = oci_core_dhcp_options.WildTestDHCPOptions.id
# dns
dns_label = "WildTest"
}
6.
Content of creating an internet gateway,
int_gateway.tf
:
root@deploymentmachine:/home/terra_and_ansible# more int_gateway.tf
resource "oci_core_internet_gateway" "WildTestInternetGateway" {
compartment_id = oci_identity_compartment.WildTestCompartment.id
display_name = "WildTestInternetGateway"
vcn_id = oci_core_virtual_network.WildTestVCN.id
}
7.
Content of creating security list,
security_list.tf
:
root@deploymentmachine:/home/terra_and_ansible# more security_list.tf
resource "oci_core_security_list" "WildTestSecurityList" {
compartment_id = oci_identity_compartment.WildTestCompartment.id
display_name = "WildTestSecurityList"
vcn_id = oci_core_virtual_network.WildTestVCN.id
egress_security_rules {
stateless = false
protocol = "6"
destination = "0.0.0.0/0"
}
# apply ingress tcp rules for each port
# of variable portz
dynamic "ingress_security_rules" {
for_each = toset(var.portz)
content {
protocol = "6"
source = "0.0.0.0/0"
tcp_options {
max = ingress_security_rules.value
min = ingress_security_rules.value
}
}
}
ingress_security_rules {
stateless = false
protocol = "6"
source = var.cidr_ingress
}
}
8.
Content of
route.tf
:
root@deploymentmachine:/home/terra_and_ansible# more route.tf
resource "oci_core_route_table" "WildTestRouteTable" {
compartment_id = oci_identity_compartment.WildTestCompartment.id
vcn_id = oci_core_virtual_network.WildTestVCN.id
display_name = "WildTestRouteTable"
route_rules {
destination = "0.0.0.0/0"
network_entity_id = oci_core_internet_gateway.WildTestInternetGateway.id
}
}
9.
Content for creating dhcp options,
dhcp_opt.tf
:
root@deploymentmachine:/home/terra_and_ansible# more dhcp_opt.tf
resource "oci_core_dhcp_options" "WildTestDHCPOptions" {
compartment_id = oci_identity_compartment.WildTestCompartment.id
vcn_id = oci_core_virtual_network.WildTestVCN.id
display_name = "WildTestDHCPOptions"
options {
type = "DomainNameServer"
server_type = "VcnLocalPlusInternet"
}
options {
type = "SearchDomain"
search_domain_names = ["wildtest.com"]
}
}
10.
Content for creating the instance,
instance.tf
root@deploymentmachine:/home/terra_and_ansible# more instance.tf
resource "oci_core_instance" "WildTestInstance" {
availability_domain = var.available_dom
shape = var.instance_shape
compartment_id = oci_identity_compartment.WildTestCompartment.id
source_details {
source_id = var.instance_image
source_type = "image"
}
display_name = var.instance_name
metadata = {
ssh_authorized_keys = file("/root/.ssh/id_rsa.pub")
}
create_vnic_details {
assign_public_ip = true
subnet_id = oci_core_subnet.WildTestSubnet.id
}
}
11.
The content of
data.tf
- this file allows us to obtain the primary VNIC ID:
root@deploymentmachine:/home/terra_and_ansible# more data.tf
# get a list of vnic attachments
data "oci_core_vnic_attachments" "WildTestVNICs" {
compartment_id = oci_identity_compartment.WildTestCompartment.id
availability_domain = var.available_dom
instance_id = oci_core_instance.WildTestInstance.id
}
# get the primary VNIC ID
data "oci_core_vnic" "WildTestVNICprimary" {
vnic_id = lookup(data.oci_core_vnic_attachments.WildTestVNICs.vnic_attachments[0], "vnic_id")
}
12.
The content of
remote.tf
:
There will be two new provisioners here
"local-exec"
and
"remote-exec"
.
The "local-exec" will run the ansible playbook to install nginx on the new instance. Make sure you run this as a non-root user (in this case, "ubuntu" user)
root@deploymentmachine:/home/terra_and_ansible# more remote.tf
resource "null_resource" "WildTestNginx" {
depends_on = [oci_core_instance.WildTestInstance]
provisioner "remote-exec" {
inline = ["echo I am in ", "hostname", "python3 --version", "sleep 10"]
connection {
type = "ssh"
user = "ubuntu"
host = data.oci_core_vnic.WildTestVNICprimary.public_ip_address
private_key = file(var.private_key_path)
}
}
provisioner "local-exec" {
command = "ansible-playbook -i '${data.oci_core_vnic.WildTestVNICprimary.public_ip_address},' --private-key ${var.private_key_path} nginx.yml -u ubuntu"
}
}
Time to deploy
Run the three Terraform commands: "terraform init", "terraform plan" and, if no Terraform syntax errors, "terraform apply"
In the output of "terraform apply" you will see everything related to Ansible.
If no errors, you should see an output of this kind (in order)
:
>> Output generated by "remote.tf", "remote-exec" provisioner:

>> Output generated by "remote.tf", "local-exec" provisioner:

Perform the checking
Our instance has been successfully deployed, and according to the "terraform apply" output, so has been the nginx service:

If you try to telnet into the public IP of the new instance, and port 8080, you would get the following error:
telnet: Unable to connect to remote host: No route to host
You can perform a few checks in the OCI UI, just to make sure everything is properly configured:


This is only an issue caused by firewall, that blocks the port 8080.
Login to new instance, and install firewalld:
root@deploymentmachine:/home/terra_and_ansible# ssh ubuntu@158.101.165.232
Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-1035-oracle x86_64)
[ ......... snip .......... ]
Last login: Wed Feb 10 20:54:27 2021 from 130.61.186.96
ubuntu@wildtestinstance:~$
ubuntu@wildtestinstance:~$ sudo -i
root@wildtestinstance:~#
root@wildtestinstance:~# apt update
[ ......... snip .......... ]
root@wildtestinstance:~# apt install firewalld -y
[ ......... snip .......... ]
... and now open port 8080
root@wildtestinstance:~# firewall-cmd --add-port=8080/tcp --permanent
success
root@wildtestinstance:~# firewall-cmd --reload
success
root@wildtestinstance:~#
root@wildtestinstance:~# # and let's check the status of nginx since we are here
root@wildtestinstance:~#
root@wildtestinstance:~# systemctl status nginx | grep -i active
Active: active (running) since Wed 2021-02-10 21:52:18 UTC; 11s ago
root@wildtestinstance:~#
root@wildtestinstance:~# lsof -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 2666 root 8u IPv4 32067 0t0 TCP *:http-alt (LISTEN)
nginx 2666 root 9u IPv6 32068 0t0 TCP *:http-alt (LISTEN)
nginx 2667 www-data 8u IPv4 32067 0t0 TCP *:http-alt (LISTEN)
nginx 2667 www-data 9u IPv6 32068 0t0 TCP *:http-alt (LISTEN)
nginx 2668 www-data 8u IPv4 32067 0t0 TCP *:http-alt (LISTEN)
nginx 2668 www-data 9u IPv6 32068 0t0 TCP *:http-alt (LISTEN)
root@wildtestinstance:~#
Let's see if it works now:
root@wildtestinstance:~# exit
logout
ubuntu@wildtestinstance:~$ exit
logout
Connection to 158.101.165.232 closed.
root@deploymentmachine:/home/terra_and_ansible# telnet 158.101.165.232 8080
Trying 158.101.165.232...
Connected to 158.101.165.232.
Escape character is '^]'.
^]
telnet> q
Connection closed.
root@deploymentmachine:/home/terra_and_ansible# curl -v 158.101.165.232:8080
* Trying 158.101.165.232:8080...
* TCP_NODELAY set
* Connected to 158.101.165.232 (158.101.165.232) port 8080 (#0)
> GET / HTTP/1.1
> Host: 158.101.165.232:8080
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Wed, 10 Feb 2021 20:19:56 GMT
< Content-Type: text/html
< Content-Length: 18
< Last-Modified: Wed, 10 Feb 2021 20:09:28 GMT
< Connection: keep-alive
< ETag: "60243d78-12"
< Accept-Ranges: bytes
<
<h1> Henlo! </h1>
* Connection #0 to host 158.101.165.232 left intact
... and from a browser of your choice:

Notes
Feel free to automate the installation of firewalld from remote.tf file, at
"remote-exec", inline
Destroy VCN resources and instance
Run
"terraform destroy"
in the working directory:
root@deploymentmachine:/home/terra_and_ansible# terraform destroy
[........ snip ...... ]
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
null_resource.WildTestNginx: Destroying... [id=5939405965601922241]
null_resource.WildTestNginx: Destruction complete after 0s
oci_core_instance.WildTestInstance: Destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.antheljt34qs2dycqeanu2hoxfppdiwn4mqb2b4252usjhxx36ts5cbdo2aq]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 10s elapsed]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 20s elapsed]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 30s elapsed]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 40s elapsed]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 50s elapsed]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 1m0s elapsed]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 1m10s elapsed]
oci_core_instance.WildTestInstance: Still destroying... [id=ocid1.instance.oc1.eu-frankfurt-1.anthe...hoxhoxhoxhoxhoxhoxhoxhoxhoxhoxhox, 1m20s elapsed]
oci_core_instance.WildTestInstance: Destruction complete after 1m24s
oci_core_subnet.WildTestSubnet: Destroying... [id=ocid1.subnet.oc1.eu-frankfurt-1.aaaaaaaafffffffffffuuuuuuuuuuuuuu]
oci_core_subnet.WildTestSubnet: Destruction complete after 0s
oci_core_route_table.WildTestRouteTable: Destroying... [id=ocid1.routetable.oc1.eu-frankfurt-1.aaaaaaaauuuuuuuuuuuuuuuu]
oci_core_dhcp_options.WildTestDHCPOptions: Destroying... [id=ocid1.dhcpoptions.oc1.eu-frankfurt-1.aaaaaaaakkkkkkkkkkkkkkkkkkk]
oci_core_security_list.WildTestSecurityList: Destroying... [id=ocid1.securitylist.oc1.eu-frankfurt-1.aaaaaaaazzzzzzzzzzzzzzzzzzzz]
oci_core_dhcp_options.WildTestDHCPOptions: Destruction complete after 0s
oci_core_security_list.WildTestSecurityList: Destruction complete after 1s
oci_core_route_table.WildTestRouteTable: Destruction complete after 1s
oci_core_internet_gateway.WildTestInternetGateway: Destroying... [id=ocid1.internetgateway.oc1.eu-frankfurt-1.aaaaaaaappppppppppppppppppppppppppp]
oci_core_internet_gateway.WildTestInternetGateway: Destruction complete after 0s
oci_core_virtual_network.WildTestVCN: Destroying... [id=ocid1.vcn.oc1.eu-frankfurt-1.amaaaaaannnnnnnnnnnnnnnnnnnnnnn]
oci_core_virtual_network.WildTestVCN: Destruction complete after 0s
oci_identity_compartment.WildTestCompartment: Destroying... [id=ocid1.compartment.oc1..aaaaaaaaaothhhhhhhhhhhhhhhhhhhhhhh]
oci_identity_compartment.WildTestCompartment: Destruction complete after 0s
Destroy complete! Resources: 9 destroyed.
Last updated