diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ee7d8674 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Local Terraform directories +.terraform/ +.terraform.lock.hcl +terraform.tfstate +terraform.tfstate.backup +**/.terraform/ +*.tfstate +*.tfstate.* +crash.log + +# Crash logs +crash.log + +# Override files +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# CLI configuration files +.terraformrc +terraform.rc diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 00000000..dce703ca --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +inventory = hosts +host_key_checking = False + +ssh_args = -F ~/.ssh/config \ No newline at end of file diff --git a/ansible/docker-images/backend.yml b/ansible/docker-images/backend.yml new file mode 100644 index 00000000..c479ce0e --- /dev/null +++ b/ansible/docker-images/backend.yml @@ -0,0 +1,37 @@ +# Ironhack note - This file is for running in docker compose with prebuilt images +# Use these if you're stuck, or if you want to start slowly migrating from external images into your own. + +services: + worker: + # build: ./worker + image: cocomo1/worker:latest + environment: + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + PG_HOST: ${DB_HOST} + DB_HOST: ${DB_HOST} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + DB_NAME: ${DB_NAME} + networks: + - back-tier + + redis: + image: redis:alpine + ports: + - "6379:6379" + volumes: + - "./healthchecks:/healthchecks" + healthcheck: + test: /healthchecks/redis.sh + interval: "5s" + + networks: + - back-tier + + +volumes: + db-data: + +networks: + back-tier: \ No newline at end of file diff --git a/ansible/docker-images/db.yml b/ansible/docker-images/db.yml new file mode 100644 index 00000000..150cbb3e --- /dev/null +++ b/ansible/docker-images/db.yml @@ -0,0 +1,25 @@ +# Ironhack note - This file is for running in docker compose with prebuilt images +# Use these if you're stuck, or if you want to start slowly migrating from external images into your own. + +services: + db: + image: postgres:15-alpine + ports: + - "5432:5432" + environment: + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - "db-data:/var/lib/postgresql/data" + - "./healthchecks:/healthchecks" + healthcheck: + test: /healthchecks/postgres.sh + interval: "5s" + networks: + - back-tier + +volumes: + db-data: + +networks: + back-tier: \ No newline at end of file diff --git a/ansible/docker-images/frontend.yml b/ansible/docker-images/frontend.yml new file mode 100644 index 00000000..66787db0 --- /dev/null +++ b/ansible/docker-images/frontend.yml @@ -0,0 +1,33 @@ +# Ironhack note - This file is for running in docker compose with prebuilt images +# Use these if you're stuck, or if you want to start slowly migrating from external images into your own. + +services: + vote: + image: cocomo1/vote:latest + ports: + - "80:80" + environment: + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + networks: + - back-tier + + result: + image: cocomo1/result:latest + ports: + - "81:80" + environment: + PG_HOST: ${PG_HOST} + PG_PORT: "5432" + PG_USER: "postgres" + PG_PASSWORD: "postgres" + PG_DATABASE: "postgres" + + networks: + - back-tier + +volumes: + db-data: + +networks: + back-tier: \ No newline at end of file diff --git a/ansible/inventory/hosts b/ansible/inventory/hosts new file mode 100644 index 00000000..44155d77 --- /dev/null +++ b/ansible/inventory/hosts @@ -0,0 +1,8 @@ +[frontend] +frontend-instance-1 + +[backend] +backend-instance-1 + +[db] +db-instance-1 \ No newline at end of file diff --git a/ansible/playbooks/deploy-compose.yml b/ansible/playbooks/deploy-compose.yml new file mode 100644 index 00000000..0e86f30d --- /dev/null +++ b/ansible/playbooks/deploy-compose.yml @@ -0,0 +1,169 @@ +- name: Setup Docker and Docker Compose on Ubuntu + hosts: all + vars_files: + - vars.yml + become: yes + tasks: + - name: Install required packages + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + state: present + update_cache: yes + + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + + - name: Add Docker repository + apt_repository: + repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} stable" + state: present + + - name: Install Docker + apt: + name: docker-ce + state: present + update_cache: yes + + - name: Start and enable Docker service + systemd: + name: docker + state: started + enabled: yes + + - name: Install Docker Compose plugin + apt: + name: docker-compose-plugin + state: present + update_cache: yes + + - name: Ensure docker compose command works + command: docker compose version + register: compose_version + + - debug: + var: compose_version.stdout + + - name: Ensure docker group exists + group: + name: docker + state: present + + - name: Add ubuntu user to docker group + user: + name: ubuntu + groups: docker + append: yes + +- name: Setup database + hosts: db + vars_files: + - vars.yml + become: yes + tasks: + - name: Create application directory + file: + path: /home/ubuntu/healthchecks + state: directory + + - name: Copy local file to remote + copy: + src: ../../healthchecks/postgres.sh # path on your control node + dest: /home/ubuntu/healthchecks/postgres.sh # destination path on remote Ubuntu + owner: ubuntu # optional: set owner + group: ubuntu # optional: set group + mode: '0755' + + - name: Copy local file to remote + copy: + src: ../docker-images/db.yml # path on your control node + dest: /home/ubuntu/docker-compose.yml # destination path on remote Ubuntu + owner: ubuntu # optional: set owner + group: ubuntu # optional: set group + mode: '0644' + + - name: Run docker compose with env vars + command: docker compose up -d + args: + chdir: /home/ubuntu + environment: + POSTGRES_USER: "{{DB_USERNAME}}" + POSTGRES_PASSWORD: "{{DB_PASSWORD}}" + + + +- name: Setup backend + hosts: backend + vars_files: + - vars.yml + become: yes + tasks: + + - name: Create healthchecks directory + file: + path: /home/ubuntu/healthchecks + state: directory + + - name: Copy healthcheck file + copy: + src: ../../healthchecks/postgres.sh # path on your control node + dest: /home/ubuntu/healthchecks/postgres.sh # destination path on remote Ubuntu + owner: ubuntu # optional: set owner + group: ubuntu # optional: set group + mode: '0755' + + - name: Copy docker compose file + copy: + src: ../docker-images/backend.yml # path on your control node + dest: /home/ubuntu/docker-compose.yml # destination path on remote Ubuntu + owner: ubuntu # optional: set owner + group: ubuntu # optional: set group + mode: '0644' + + - name: Run docker compose with env vars + command: docker compose up -d + args: + chdir: /home/ubuntu + environment: + REDIS_HOST: "{{REDIS_HOST}}" + REDIS_PORT: "{{REDIS_PORT}}" + PG_HOST: "{{DB_HOST}}" + DB_HOST: "{{DB_HOST}}" + DB_USERNAME: "{{DB_USERNAME}}" + DB_PASSWORD: "{{DB_PASSWORD}}" + DB_NAME: "{{DB_NAME}}" + + +- name: Setup frontend + hosts: frontend + vars_files: + - vars.yml + become: yes + tasks: + - name: Copy local file to remote + copy: + src: ../docker-images/frontend.yml # path on your control node + dest: /home/ubuntu/docker-compose.yml # destination path on remote Ubuntu + owner: ubuntu # optional: set owner + group: ubuntu # optional: set group + mode: '0644' + + - name: Run docker compose with env vars + command: docker compose up -d + args: + chdir: /home/ubuntu + environment: + REDIS_HOST: "{{REDIS_HOST}}" + REDIS_PORT: "{{REDIS_PORT}}" + PG_HOST: "{{DB_HOST}}" + PG_PORT: "{{DB_PORT}}" + PG_USER: "{{DB_USERNAME}}" + PG_PASSWORD: "{{DB_PASSWORD}}" + PG_DATABASE: "{{DB_NAME}}" + + diff --git a/ansible/playbooks/docker-install.yml b/ansible/playbooks/docker-install.yml new file mode 100644 index 00000000..07580dab --- /dev/null +++ b/ansible/playbooks/docker-install.yml @@ -0,0 +1,177 @@ +# ----------------------------------------- +# ---------------- all ---------------- +# ----------------------------------------- +- name: Install Docker and run vote app (Ubuntu) + hosts: all + become: yes + + tasks: + + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Upgrade all packages + apt: + upgrade: dist + + - name: Install required packages + apt: + name: + - docker.io + - python3-docker + state: present + + - name: Start and enable Docker + service: + name: docker + state: started + enabled: yes + + - name: Ensure back-tier network exists + docker_network: + name: back-tier + state: present + +# ----------------------------------------- +# ---------------- backend ---------------- +# ----------------------------------------- +- name: Install on Backend + hosts: backend + become: yes + + tasks: + + - name: Deploy Redis container + docker_container: + name: redis + image: redis:8.4.0-alpine + networks: + - name: back-tier + volumes: + - /opt/healthchecks:/healthchecks + healthcheck: + test: /healthchecks/redis.sh + interval: 5s + restart_policy: always + state: started + + + + +# ----------------------------------------- +# ---------------- DB ---------------- +# ----------------------------------------- +- name: PostgreSQL + hosts: db + become: yes + + tasks: + + # ------------------------- + # POSTGRES CONTAINER + # ------------------------- + + - name: Create postgres volume + community.docker.docker_volume: + name: postgres_data + + - name: Run PostgreSQL container + community.docker.docker_container: + name: postgres-db + image: postgres:15-alpine + networks: + - name: back-tier + state: started + restart_policy: always + ports: + - "5432:5432" + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - postgres_data:/var/lib/postgresql/data + +- name: Install on Backend + hosts: backend + become: yes + + tasks: +# ---------------- WORKER ---------------- + + - name: Pull Docker Worker Image + docker_image: + name: cocomo1/worker + tag: latest + source: pull + + - name: Run Container worker + docker_container: + name: worker + image: cocomo1/worker:latest + env: + DB_HOST: "10.0.2.167" + DB_USERNAME: "postgres" + DB_PASSWORD: "postgres" + DB_NAME: "postgres" + REDIS_HOST: "10.0.2.53" + networks: + - name: back-tier + state: started + restart_policy: always +# ----------------------------------------- +# ---------------- frontend ---------------- +# ----------------------------------------- + +- name: Install Docker and run vote app (Ubuntu) + hosts: frontend + become: yes + + tasks: + + # ---------------- VOTE ---------------- + - name: Pull Docker Vote Image + docker_image: + name: cocomo1/vote + tag: latest + source: pull + + - name: Run Container Vote + docker_container: + name: vote_client + image: cocomo1/vote:latest + networks: + - name: back-tier + state: started + restart_policy: always + ports: + - "80:80" + env: + REDIS_HOST: "10.0.2.167" + REDIS_PORT: "6379" + + # ---------------- RESULT ---------------- + + - name: Pull Docker Result Image + docker_image: + name: cocomo1/result + tag: latest + source: pull + + - name: Run Container Result + docker_container: + name: result_client + image: cocomo1/result:latest + networks: + - name: back-tier + state: started + restart_policy: always + ports: + - "81:80" + env: + PG_HOST: "10.0.2.53" + PG_PORT: "5432" + PG_USER: "postgres" + PG_PASSWORD: "postgres" + PG_DATABASE: "postgres" \ No newline at end of file diff --git a/ansible/playbooks/print.yml b/ansible/playbooks/print.yml new file mode 100644 index 00000000..83daae81 --- /dev/null +++ b/ansible/playbooks/print.yml @@ -0,0 +1,19 @@ +- name: Print variables and environment settings + hosts: all + vars_files: + - vars.yml + + tasks: + - name: Show all variables and env settings + debug: + msg: + - "db_ip: {{ db_ip }}" + - "backend_ip: {{ backend_ip }}" + - "frontend_ip: {{ frontend_ip }}" + - "REDIS_HOST: {{ REDIS_HOST }}" + - "REDIS_PORT: {{ REDIS_PORT }}" + - "DB_HOST: {{ DB_HOST }}" + - "DB_PORT: {{ DB_PORT }}" + - "DB_USERNAME: {{ DB_USERNAME }}" + - "DB_PASSWORD: {{ DB_PASSWORD }}" + - "DB_NAME: {{ DB_NAME }}" diff --git a/ansible/playbooks/vars.yml b/ansible/playbooks/vars.yml new file mode 100644 index 00000000..317fd6b3 --- /dev/null +++ b/ansible/playbooks/vars.yml @@ -0,0 +1,24 @@ +# vars.yml +db_ip: "{{ hostvars['db-instance-1'].ansible_default_ipv4.address }}" +backend_ip: "{{ hostvars['backend-instance-1'].ansible_default_ipv4.address }}" +frontend_ip: "{{ hostvars['frontend-instance-1'].ansible_default_ipv4.address }}" + +#db_ip: "10.0.2.53" +#backend_ip: "10.0.2.171" +#frontend_ip: "18.196.81.108" + +REDIS_HOST: "{{ backend_ip }}" +REDIS_PORT: "6379" + +DB_HOST: "{{db_ip}}" +DB_PORT: "5432" +DB_USERNAME: "postgres" +DB_PASSWORD: "postgres" +DB_NAME: "postgres" + + +PG_HOST: "{{db_ip}}" +PG_PORT: "{{PG_PORT}}" +PG_USER: "postgres" +PG_PASSWORD: "postgres" +PG_DATABASE: "postgres" diff --git a/scripts/s3bucket-dynamotable-creation.sh b/scripts/s3bucket-dynamotable-creation.sh new file mode 100755 index 00000000..110a324b --- /dev/null +++ b/scripts/s3bucket-dynamotable-creation.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +aws s3api create-bucket \ + --bucket my-terraform-state-bucket-faheem \ + --region eu-central-1 \ + --create-bucket-configuration LocationConstraint=eu-central-1 + +aws dynamodb create-table \ + --table-name terraform-locks-faheem \ + --attribute-definitions AttributeName=LockID,AttributeType=S \ + --key-schema AttributeName=LockID,KeyType=HASH \ + --billing-mode PAY_PER_REQUEST \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 00000000..64aa9f26 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,285 @@ +terraform { + required_version = ">= 1.5" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + backend "s3" { + bucket = "my-terraform-state-bucket-faheem" + key = "vote-app/terraform.tfstate" + region = "eu-central-1" + dynamodb_table = "terraform-locks-faheem" + encrypt = true + } + +} + +provider "aws" { + region = var.region +} + +############################ +# VPC +############################ + +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "vote-vpc-${var.signature}" + } +} + +############################ +# Subnets +############################ + +resource "aws_subnet" "public" { + vpc_id = aws_vpc.main.id + cidr_block = "10.0.1.0/24" + map_public_ip_on_launch = true + availability_zone = "${var.region}a" + + tags = { + Name = "public-subnet-${var.signature}" + } +} + +resource "aws_subnet" "private" { + vpc_id = aws_vpc.main.id + cidr_block = "10.0.2.0/24" + availability_zone = "${var.region}b" + + tags = { + Name = "private-subnet-${var.signature}" + } +} + +######################### +# NAT Gateway +######################### + +resource "aws_eip" "nat" { + domain = "vpc" +} + +resource "aws_nat_gateway" "nat" { + allocation_id = aws_eip.nat.id + subnet_id = aws_subnet.public.id +} + + +############################ +# Internet Gateway +############################ + +resource "aws_internet_gateway" "igw" { + vpc_id = aws_vpc.main.id +} + + +############################ +# Route Tables +############################ + +resource "aws_route_table" "public_rt" { + vpc_id = aws_vpc.main.id +} + +resource "aws_route" "public_internet" { + route_table_id = aws_route_table.public_rt.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id +} + +resource "aws_route_table_association" "public_assoc" { + subnet_id = aws_subnet.public.id + route_table_id = aws_route_table.public_rt.id +} + +resource "aws_route_table" "private_rt" { + vpc_id = aws_vpc.main.id +} + +resource "aws_route" "private_default" { + route_table_id = aws_route_table.private_rt.id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.nat.id +} + + +resource "aws_route_table_association" "private_assoc" { + subnet_id = aws_subnet.private.id + route_table_id = aws_route_table.private_rt.id +} + +############################ +# Security Groups +############################ + +# Vote/Result SG +resource "aws_security_group" "faheem-vote-result-sg" { + name = "faheem-vote-result-sg" + vpc_id = aws_vpc.main.id + + ingress { + description = "HTTP" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + +ingress { + description = "HTTP" + from_port = 81 + to_port = 81 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "HTTPS" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "SSH access" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Change to your IP for security + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + +} + +# Redis/Worker SG +resource "aws_security_group" "faheme-redis-worker-sf" { + name = "faheme-redis-worker-sf" + vpc_id = aws_vpc.main.id + + + ingress { + from_port = 6379 + to_port = 6379 + protocol = "tcp" + security_groups = [aws_security_group.faheem-vote-result-sg.id] + } + + ingress { + description = "SSH access" + from_port = 22 + to_port = 22 + protocol = "tcp" + security_groups = [aws_security_group.faheem-vote-result-sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +#Postgres SG +resource "aws_security_group" "faheme-postgres-sf" { + name = "faheme-postgres-sf" + vpc_id = aws_vpc.main.id + + + ingress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + security_groups = [aws_security_group.faheme-redis-worker-sf.id,aws_security_group.faheem-vote-result-sg.id] + } + + ingress { + description = "SSH access" + from_port = 22 + to_port = 22 + protocol = "tcp" + security_groups = [aws_security_group.faheem-vote-result-sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +############################ +# EC2 Instances +############################ + +# Instance A — (Vote + Result) +resource "aws_instance" "frontend" { + ami = var.ami + instance_type = var.instance_type + subnet_id = aws_subnet.public.id + vpc_security_group_ids = [aws_security_group.faheem-vote-result-sg.id] + key_name = var.key_name + + tags = { + Name = "instance-a-frontend-(Vote + Result)-${var.signature}" + } +} + +# Instance B — (Redis + Worker) +resource "aws_instance" "backend" { + ami = var.ami + instance_type = var.instance_type + subnet_id = aws_subnet.private.id + vpc_security_group_ids = [aws_security_group.faheme-redis-worker-sf.id] + key_name = var.key_name + + tags = { + Name = "instance-b-backend-(Redis + Worker)-${var.signature}" + } +} + +# Instance C — (PostgreSQL) +resource "aws_instance" "database" { + ami = var.ami + instance_type = var.instance_type + subnet_id = aws_subnet.private.id + vpc_security_group_ids = [aws_security_group.faheme-postgres-sf.id] + key_name = var.key_name + + tags = { + Name = "instance-c-database-(PostgreSQL)-${var.signature}" + } +} + +############################ +# Outputs for Ansible +############################ + +output "bastion_public_ip" { + value = aws_instance.frontend.public_ip +} + +output "backend_private_ip" { + value = aws_instance.backend.private_ip +} + +output "database_private_ip" { + value = aws_instance.database.private_ip +} \ No newline at end of file diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 00000000..abdfd13b --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,5 @@ +region = "eu-central-1" +ami = "ami-0a854fe96e0b45e4e" +instance_type = "t2.micro" +key_name = "faheem-key-frankfurt" +signature = "faheem" \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 00000000..413b51ab --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,30 @@ +variable "region" { + description = "AWS region" + type = string + default = "eu-central-1" +} + +variable "ami" { + description = "ami" + type = string + default = "ami-0a854fe96e0b45e4e" +} + +variable "instance_type" { + description = "EC2 instance size" + type = string + default = "t2.micro" +} + + +variable "key_name" { + description = "key name" + type = string + default = "faheem-key-frankfurt" +} + +variable "signature" { + description = "My Signature" + type = string + default = "faheem" +} \ No newline at end of file