├── LICENSE ├── README.md ├── ansible ├── ansible.cfg ├── files │ ├── nginx.conf │ ├── static_website.conf │ └── static_website │ │ └── index.html └── playbook.yml ├── networkTerraform ├── output.tf ├── resources.tf └── variable.tf ├── packer └── packer.json └── terraform ├── modules ├── instance │ ├── output.tf │ ├── resources.tf │ └── variable.tf └── securityGroup │ ├── output.tf │ ├── resources.tf │ └── variable.tf ├── output.tf ├── resources.tf └── variable.tf /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mitesh Sharma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Immutable Infrastructure Using Packer, Ansible, and Terraform 2 | 3 | We are going to use ansible code for provisioning static website using nginx during the baking process. We need to make sure nginx is enabled in systemctl and starts on every boot so it is ready to process on instance start. We are going assign tags in bake process to AMI. This tag is used by Terraform to identify the latest AMI available and use it for EC2 instance creation. We need to provide subnet id to packer builder so it can use this subnet id while creating AMI. We are going to divide our terraform code into two parts, one which contains all network details: create VPC, subnet, and other network details. Another part which spawns our EC2 instance inside our network using AMI generated by the packer. 4 | 5 | ## Getting Started 6 | 7 | Step 1: Setup a network using Terraform 8 | 9 | Step 2: Create AMI using packer and ansible inside the above-created network 10 | 11 | Step 3: Setup EC2 instance inside the network with packer AMI 12 | 13 | Provide access key and token in Terraform and Packer code. 14 | 15 | ### Command to run network Terraform 16 | 17 | Go in folder networkTerraform, run command: 18 | 19 | 1. terraform init 20 | 21 | 2. terraform plan 22 | 23 | 3. terraform apply 24 | 25 | ### Command to run Packer 26 | 27 | Provide subnet id created in network terraform in packer.json 28 | 29 | packer build packer.json 30 | 31 | ### Command to run main Terraform 32 | 33 | Go in folder terraform, run command: 34 | 35 | 1. terraform init 36 | 37 | 2. terraform plan 38 | 39 | 3. terraform apply -------------------------------------------------------------------------------- /ansible/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = ./inventory 3 | remote_user = ec2-user -------------------------------------------------------------------------------- /ansible/files/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | pid /var/run/nginx.pid; 4 | error_log /var/log/nginx/error.log; 5 | 6 | # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. 7 | include /usr/share/nginx/modules/*.conf; 8 | 9 | events { 10 | worker_connections 1024; 11 | } 12 | 13 | http { 14 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 15 | '$status $body_bytes_sent "$http_referer" ' 16 | '"$http_user_agent" "$http_x_forwarded_for"'; 17 | 18 | access_log /var/log/nginx/access.log main; 19 | 20 | sendfile on; 21 | tcp_nopush on; 22 | tcp_nodelay on; 23 | keepalive_timeout 65; 24 | types_hash_max_size 2048; 25 | 26 | include /etc/nginx/mime.types; 27 | default_type application/octet-stream; 28 | 29 | # Load modular configuration files from the /etc/nginx/conf.d directory. 30 | # See http://nginx.org/en/docs/ngx_core_module.html#include 31 | # for more information. 32 | include /etc/nginx/conf.d/*.conf; 33 | 34 | index index.html index.htm; 35 | } -------------------------------------------------------------------------------- /ansible/files/static_website.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | server_name miteshsharma.com; 5 | root /var/www/miteshsharma.com/; 6 | index index.html index.htm; 7 | 8 | location / { 9 | default_type "text/html"; 10 | try_files $uri.html $uri $uri/ /index.html; 11 | } 12 | 13 | access_log /var/log/nginx/miteshsharma.com_access.log main; 14 | error_log /var/log/nginx/miteshsharma.com_error.log error; 15 | } -------------------------------------------------------------------------------- /ansible/files/static_website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Welcome to nginx website

6 | 7 |

This is created using Packer, Ansible and Terraform.

8 | 9 | 10 | -------------------------------------------------------------------------------- /ansible/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: yes 4 | become_user: root 5 | become_method: sudo 6 | tasks: 7 | - name: Enable nginx for amazon linux 2 8 | shell: "amazon-linux-extras enable nginx1.12" 9 | become: yes 10 | 11 | - name: Install nginx 12 | yum: 13 | name: nginx 14 | state: latest 15 | 16 | - name: Copy nginx config files 17 | copy: 18 | src: "./files/nginx.conf" 19 | dest: "/etc/nginx/nginx.conf" 20 | mode: 0644 21 | 22 | - name: Creates directory 23 | file: 24 | path: "/var/www/miteshsharma.com" 25 | state: directory 26 | 27 | - name: Copy static files 28 | copy: 29 | src: "./files/static_website/" 30 | dest: "/var/www/miteshsharma.com/" 31 | mode: 0644 32 | 33 | - name: Update static nginx config 34 | copy: 35 | src: "./files/static_website.conf" 36 | dest: "/etc/nginx/conf.d/miteshsharma.com.conf" 37 | mode: 0644 38 | 39 | - name: Enable Nginx 40 | service: name=nginx enabled=yes 41 | become: yes -------------------------------------------------------------------------------- /networkTerraform/output.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | value = "${aws_vpc.vpc.id}" 3 | } 4 | output "public_subnets" { 5 | value = ["${aws_subnet.subnet_public.id}"] 6 | } 7 | output "ec2keyName" { 8 | value = "${aws_key_pair.ec2key.key_name}" 9 | } -------------------------------------------------------------------------------- /networkTerraform/resources.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = "${var.access_key}" 3 | secret_key = "${var.secret_key}" 4 | region = "${var.region}" 5 | } 6 | 7 | #resources 8 | resource "aws_vpc" "vpc" { 9 | cidr_block = "${var.cidr_block_range}" 10 | enable_dns_support = true 11 | enable_dns_hostnames = true 12 | tags { 13 | "Environment" = "${var.environment_tag}" 14 | } 15 | } 16 | 17 | resource "aws_internet_gateway" "igw" { 18 | vpc_id = "${aws_vpc.vpc.id}" 19 | tags { 20 | "Environment" = "${var.environment_tag}" 21 | } 22 | } 23 | 24 | resource "aws_subnet" "subnet_public" { 25 | vpc_id = "${aws_vpc.vpc.id}" 26 | cidr_block = "${var.subnet1_cidr_block_range}" 27 | map_public_ip_on_launch = "true" 28 | availability_zone = "${var.availability_zone}" 29 | tags { 30 | "Environment" = "${var.environment_tag}" 31 | "Type" = "Public" 32 | } 33 | } 34 | 35 | resource "aws_route_table" "rtb_public" { 36 | vpc_id = "${aws_vpc.vpc.id}" 37 | 38 | route { 39 | cidr_block = "0.0.0.0/0" 40 | gateway_id = "${aws_internet_gateway.igw.id}" 41 | } 42 | 43 | tags { 44 | "Environment" = "${var.environment_tag}" 45 | } 46 | } 47 | 48 | resource "aws_route_table_association" "rta_subnet_public" { 49 | subnet_id = "${aws_subnet.subnet_public.id}" 50 | route_table_id = "${aws_route_table.rtb_public.id}" 51 | } 52 | 53 | resource "aws_key_pair" "ec2key" { 54 | key_name = "publicKey" 55 | public_key = "${file(var.public_key_path)}" 56 | } -------------------------------------------------------------------------------- /networkTerraform/variable.tf: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | variable "access_key" {} 4 | variable "secret_key" {} 5 | variable "region" { 6 | default = "us-east-2" 7 | } 8 | variable "availability_zone" { 9 | default = "us-east-2a" 10 | } 11 | variable "cidr_block_range" { 12 | description = "The CIDR block for the VPC" 13 | default = "10.1.0.0/16" 14 | } 15 | variable "subnet1_cidr_block_range" { 16 | description = "The CIDR block for public subnet of VPC" 17 | default = "10.1.0.0/24" 18 | } 19 | variable "subnet2_cidr_block_range" { 20 | description = "The CIDR block for private subnet of VPC" 21 | default = "10.2.0.0/24" 22 | } 23 | variable "environment_tag" { 24 | description = "Environment tag" 25 | default = "" 26 | } 27 | variable "public_key_path" { 28 | description = "Public key path" 29 | default = "~/.ssh/id_rsa.pub" 30 | } -------------------------------------------------------------------------------- /packer/packer.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "aws_access_key": "{{env `AWS_ACCESS_KEY_ID`}}", 4 | "aws_secret_key": "{{env `AWS_SECRET_ACCESS_KEY`}}", 5 | "region": "us-east-2", 6 | "ssh_username": "ec2-user", 7 | "base_ami": "ami-0303c7b2e7066b60d", 8 | "instance_type": "t2.micro", 9 | "subnet_id": "subnet-0553e12f46221b5b7" 10 | }, 11 | "builders": [ 12 | { 13 | "type": "amazon-ebs", 14 | "access_key": "{{user `aws_access_key`}}", 15 | "secret_key": "{{user `aws_secret_key` }}", 16 | "region": "{{user `region` }}", 17 | "subnet_id": "{{user `subnet_id` }}", 18 | "source_ami": "{{user `base_ami`}}", 19 | "instance_type": "{{user `instance_type` }}", 20 | "ssh_username": "{{user `ssh_username`}}", 21 | "ami_name": "packer-base-{{timestamp}}", 22 | "associate_public_ip_address": true, 23 | "tags": { 24 | "Name": "Packer-Ansible" 25 | } 26 | } 27 | ], 28 | "provisioners": [ 29 | { 30 | "type": "ansible", 31 | "playbook_file": "../ansible/playbook.yml" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /terraform/modules/instance/output.tf: -------------------------------------------------------------------------------- 1 | output "instance_eip" { 2 | value = "${aws_eip.testInstanceEip.public_ip}" 3 | } -------------------------------------------------------------------------------- /terraform/modules/instance/resources.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = "${var.access_key}" 3 | secret_key = "${var.secret_key}" 4 | region = "${var.region}" 5 | } 6 | 7 | resource "aws_instance" "instance" { 8 | ami = "${var.instance_ami}" 9 | instance_type = "${var.instance_type}" 10 | subnet_id = "${var.subnet_public_id}" 11 | vpc_security_group_ids = ["${var.security_group_ids}"] 12 | key_name = "${var.key_pair_name}" 13 | 14 | tags { 15 | "Environment" = "${var.environment_tag}" 16 | } 17 | } 18 | 19 | resource "aws_eip" "testInstanceEip" { 20 | vpc = true 21 | instance = "${aws_instance.instance.id}" 22 | 23 | tags { 24 | "Environment" = "${var.environment_tag}" 25 | } 26 | } -------------------------------------------------------------------------------- /terraform/modules/instance/variable.tf: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | variable "access_key" {} 4 | variable "secret_key" {} 5 | variable "region" { 6 | default = "us-east-2" 7 | } 8 | variable "vpc_id" { 9 | description = "VPC id" 10 | default = "" 11 | } 12 | variable "subnet_public_id" { 13 | description = "VPC public subnet id" 14 | default = "" 15 | } 16 | variable "security_group_ids" { 17 | description = "EC2 ssh security group" 18 | type = "list" 19 | default = [] 20 | } 21 | variable "environment_tag" { 22 | description = "Environment tag" 23 | default = "" 24 | } 25 | variable "key_pair_name" { 26 | description = "EC2 Key pair name" 27 | default = "" 28 | } 29 | variable "instance_ami" { 30 | description = "EC2 instance ami" 31 | default = "ami-0cf31d971a3ca20d6" 32 | } 33 | variable "instance_type" { 34 | description = "EC2 instance type" 35 | default = "t2.micro" 36 | } -------------------------------------------------------------------------------- /terraform/modules/securityGroup/output.tf: -------------------------------------------------------------------------------- 1 | output "sg_22" { 2 | value = "${aws_security_group.sg_22.id}" 3 | } 4 | 5 | output "sg_80" { 6 | value = "${aws_security_group.sg_80.id}" 7 | } -------------------------------------------------------------------------------- /terraform/modules/securityGroup/resources.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = "${var.access_key}" 3 | secret_key = "${var.secret_key}" 4 | region = "${var.region}" 5 | } 6 | 7 | resource "aws_security_group" "sg_22" { 8 | name = "sg_22" 9 | vpc_id = "${var.vpc_id}" 10 | 11 | # SSH access from the VPC 12 | ingress { 13 | from_port = 22 14 | to_port = 22 15 | protocol = "tcp" 16 | cidr_blocks = ["0.0.0.0/0"] 17 | } 18 | 19 | egress { 20 | from_port = 0 21 | to_port = 0 22 | protocol = "-1" 23 | cidr_blocks = ["0.0.0.0/0"] 24 | } 25 | 26 | tags { 27 | "Environment" = "${var.environment_tag}" 28 | } 29 | } 30 | 31 | resource "aws_security_group" "sg_80" { 32 | name = "sg_80" 33 | vpc_id = "${var.vpc_id}" 34 | 35 | ingress { 36 | from_port = 80 37 | to_port = 80 38 | protocol = "tcp" 39 | cidr_blocks = ["0.0.0.0/0"] 40 | } 41 | 42 | egress { 43 | from_port = 0 44 | to_port = 0 45 | protocol = "-1" 46 | cidr_blocks = ["0.0.0.0/0"] 47 | } 48 | 49 | tags { 50 | "Environment" = "${var.environment_tag}" 51 | } 52 | } -------------------------------------------------------------------------------- /terraform/modules/securityGroup/variable.tf: -------------------------------------------------------------------------------- 1 | variable "access_key" {} 2 | variable "secret_key" {} 3 | variable "region" { 4 | default = "us-east-2" 5 | } 6 | 7 | variable "vpc_id" { 8 | description = "VPC id" 9 | default = "" 10 | } 11 | variable "environment_tag" { 12 | description = "Environment tag" 13 | default = "" 14 | } -------------------------------------------------------------------------------- /terraform/output.tf: -------------------------------------------------------------------------------- 1 | output "instance_eip" { 2 | value = "${module.instanceModule.instance_eip}" 3 | } -------------------------------------------------------------------------------- /terraform/resources.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = "${var.access_key}" 3 | secret_key = "${var.secret_key}" 4 | region = "${var.region}" 5 | } 6 | 7 | data "aws_ami" "ec2-ami" { 8 | filter { 9 | name = "state" 10 | values = ["available"] 11 | } 12 | owners = ["self"] 13 | filter { 14 | name = "tag:Name" 15 | values = ["Packer-Ansible"] 16 | } 17 | 18 | most_recent = true 19 | } 20 | 21 | data "terraform_remote_state" "network" { 22 | backend = "local" 23 | 24 | config { 25 | path = "../networkTerraform/terraform.tfstate" 26 | } 27 | } 28 | 29 | module "securityGroupModule" { 30 | source = "./modules/securityGroup" 31 | access_key = "${var.access_key}" 32 | secret_key = "${var.secret_key}" 33 | region = "${var.region}" 34 | vpc_id = "${data.terraform_remote_state.network.vpc_id}" 35 | environment_tag = "${var.environment_tag}" 36 | } 37 | 38 | module "instanceModule" { 39 | source = "./modules/instance" 40 | access_key = "${var.access_key}" 41 | secret_key = "${var.secret_key}" 42 | region = "${var.region}" 43 | instance_ami = "${data.aws_ami.ec2-ami.id}" 44 | vpc_id = "${data.terraform_remote_state.network.vpc_id}" 45 | subnet_public_id = "${data.terraform_remote_state.network.public_subnets[0]}" 46 | key_pair_name = "${data.terraform_remote_state.network.ec2keyName}" 47 | security_group_ids = ["${module.securityGroupModule.sg_22}", "${module.securityGroupModule.sg_80}"] 48 | environment_tag = "${var.environment_tag}" 49 | } 50 | -------------------------------------------------------------------------------- /terraform/variable.tf: -------------------------------------------------------------------------------- 1 | variable "access_key" { } 2 | variable "secret_key" { } 3 | variable "region" { 4 | default = "us-east-2" 5 | } 6 | variable "availability_zone" { 7 | default = "us-east-2a" 8 | } 9 | variable "environment_tag" { 10 | description = "Environment tag" 11 | default = "dev" 12 | } --------------------------------------------------------------------------------