diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..d00a7e8 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,33 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + paths: + - 'README.md' + - '.github/workflows/deploy-docs.yml' + +permissions: + contents: read + pages: write + id-token: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: '.' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4b67d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Terraform +**/.terraform/* +*.tfstate +*.tfstate.* +*.tfvars +.terraform.lock.hcl + +# Terraform backups +*.backup + +# Sensitive files +*.pem +*.key + +# Ansible +*.retry + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index 9ae26e6..eefe790 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,202 @@ + # DevOps Bootcamp Project This repository contains my DevOps bootcamp learning materials and projects. + +# DevOps Bootcamp Final Project + +## Project Overview +This project demonstrates a complete DevOps infrastructure deployment using industry-standard tools and practices. + +**Student Name**: KHAIRUL MUJAHID +**Project Name**: Trust Me, I'm a DevOps Engineer +**Date**: January 2025 + +## 🌐 Live Deployments + +- **Web Application**: https://web.muja-net.com +- **Monitoring Dashboard**: https://monitoring.muja-net.com + - Username: `admin` + - Password: `admin123` + +## 📋 Project Architecture + +### Infrastructure Components + +- **Cloud Provider**: AWS (ap-southeast-1) +- **VPC CIDR**: 10.0.0.0/24 + - Public Subnet: 10.0.0.0/25 + - Private Subnet: 10.0.0.128/25 + +### Server Instances + +1. **Web Server** (Public Subnet) + - Instance Type: t3.micro + - OS: Ubuntu 24.04 + - Private IP: 10.0.0.5 + - Public IP: 52.220.53.85 + - Purpose: Hosts containerized web application + +2. **Ansible Controller** (Private Subnet) + - Instance Type: t3.micro + - OS: Ubuntu 24.04 + - Private IP: 10.0.0.135 + - Purpose: Configuration management automation + +3. **Monitoring Server** (Private Subnet) + - Instance Type: t3.micro + - OS: Ubuntu 24.04 + - Private IP: 10.0.0.136 + - Purpose: Hosts Prometheus and Grafana + +## 🛠️ Technologies Used + +### Infrastructure as Code +- **Terraform**: Provision all AWS resources +- **AWS Services**: VPC, EC2, S3, ECR, IAM, Systems Manager + +### Configuration Management +- **Ansible**: Automated server configuration and application deployment + +### Containerization +- **Docker**: Application containerization +- **Amazon ECR**: Container registry + +### Monitoring +- **Prometheus**: Metrics collection +- **Grafana**: Metrics visualization +- **Node Exporter**: System metrics exporter + +### Security & Access +- **AWS Systems Manager (SSM)**: Secure server access +- **Cloudflare**: DNS management and tunnel for secure Grafana access + +## 🚀 Deployment Steps + +### 1. Infrastructure Provisioning (Terraform) +```bash +cd terraform +terraform init +terraform plan +terraform apply +``` + +**Resources Created:** +- VPC with public and private subnets +- Internet Gateway and NAT Gateway +- Security Groups +- 3 EC2 instances (Web, Ansible Controller, Monitoring) +- ECR repository +- IAM roles for SSM access + +### 2. Configuration Management (Ansible) +```bash +cd ansible +ansible-playbook -i inventory.ini playbooks/install-docker.yml +ansible-playbook -i inventory.ini playbooks/deploy-application.yml +ansible-playbook -i inventory.ini playbooks/deploy-node-exporter.yml +ansible-playbook -i inventory.ini playbooks/deploy-prometheus.yml +ansible-playbook -i inventory.ini playbooks/deploy-grafana.yml +``` + +### 3. Application Deployment + +- Built Docker image from source code +- Pushed to Amazon ECR +- Deployed on web server using Ansible +- Accessible via https://web.yourdomain.com + +### 4. Monitoring Setup + +- **Prometheus**: Scrapes metrics from Node Exporter on web server +- **Grafana**: Visualizes CPU, Memory, and Disk usage +- **Access**: Secured via Cloudflare Tunnel (no public exposure) + +## 📊 Monitoring Metrics + +The following metrics are collected and visualized: + +- **CPU Usage**: Real-time processor utilization +- **Memory Usage**: RAM consumption and availability +- **Disk Usage**: Storage utilization of root filesystem +- **Network I/O**: Network traffic statistics + +## 🔐 Security Implementation + +1. **Network Segmentation**: + - Web server in public subnet (internet-facing) + - Ansible and monitoring servers in private subnet (no direct internet access) + +2. **Security Groups**: + - Web server: Allows HTTP (80) from anywhere, SSH from VPC only + - Private servers: SSH from VPC only + +3. **Access Control**: + - AWS SSM for secure server access (no SSH keys exposed) + - Cloudflare Tunnel for secure Grafana access + - IAM roles for ECR access + +4. **SSL/TLS**: + - Cloudflare provides SSL certificates + - HTTPS enforced for all web traffic + +## 📁 Repository Structure +``` +devops-bootcamp-project/ +├── terraform/ +│ ├── main.tf # Main infrastructure configuration +│ ├── variables.tf # Variable definitions +│ ├── outputs.tf # Output values +│ ├── provider.tf # Provider configuration +│ └── backend.tf # S3 backend configuration +├── ansible/ +│ ├── inventory.ini # Ansible inventory +│ ├── playbooks/ +│ │ ├── install-docker.yml +│ │ ├── deploy-application.yml +│ │ ├── deploy-node-exporter.yml +│ │ ├── deploy-prometheus.yml +│ │ └── deploy-grafana.yml +│ ├── group_vars/ +│ │ └── web_servers.yml +│ └── prometheus.yml # Prometheus configuration +├── .gitignore +└── README.md # This file +``` + +## 🔄 CI/CD Pipeline + +GitHub Actions workflow automatically updates this documentation on GitHub Pages when changes are pushed to the main branch. + +## 📝 Lessons Learned + +1. Infrastructure as Code enables reproducible deployments +2. Ansible simplifies configuration management across multiple servers +3. Container orchestration improves application portability +4. Proper monitoring is essential for production systems +5. Security should be implemented at every layer + +## 🎯 Project Completion Checklist + +- ✅ Infrastructure provisioned with Terraform +- ✅ Servers configured with Ansible +- ✅ Docker installed on all servers +- ✅ Application containerized and deployed +- ✅ ECR repository created and image pushed +- ✅ Prometheus collecting metrics +- ✅ Grafana dashboards configured +- ✅ Node Exporter monitoring web server +- ✅ Cloudflare DNS configured +- ✅ Cloudflare Tunnel securing Grafana access +- ✅ Documentation published on GitHub Pages +- ✅ SSM access enabled on all servers + +## 📧 Contact + +**Name**: MUHAMMAD KHAIRUL MUJAHID BIN ZAKARIA +**Email**: khairulmujahid92@gmail.com +**GitHub**: https://github.com/muzadp + +--- + +*This project was completed as part of the DevOps Bootcamp Final Project - January 2025* diff --git a/ansible/group_vars/web_servers.yml b/ansible/group_vars/web_servers.yml new file mode 100644 index 0000000..5636bce --- /dev/null +++ b/ansible/group_vars/web_servers.yml @@ -0,0 +1,6 @@ +--- +ecr_repository_url: "175558635506.dkr.ecr.ap-southeast-1.amazonaws.com/devops-bootcamp/final-project-mujadp" +aws_region: "ap-southeast-1" +aws_account_id: "175558635506" +app_name: "my-devops-app" +app_port: 80 diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..305822c --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,11 @@ +[web_servers] +web-server ansible_host=10.0.0.5 + +[monitoring_servers] +monitoring-server ansible_host=10.0.0.136 + +[all:vars] +ansible_user=ubuntu +ansible_ssh_private_key_file=~/.ssh/ansible_key +ansible_python_interpreter=/usr/bin/python3 +ansible_ssh_common_args='-o StrictHostKeyChecking=no' diff --git a/ansible/playbooks/deploy-application.yml b/ansible/playbooks/deploy-application.yml new file mode 100644 index 0000000..ae09d30 --- /dev/null +++ b/ansible/playbooks/deploy-application.yml @@ -0,0 +1,101 @@ +--- +- name: Deploy application on web server + hosts: web_servers + become: yes + vars_files: + - ../group_vars/web_servers.yml + tasks: + - name: Install prerequisites for AWS CLI + apt: + name: + - unzip + - curl + state: present + update_cache: yes + + - name: Check if AWS CLI v2 is installed + command: aws --version + register: aws_cli_check + ignore_errors: yes + changed_when: false + + - name: Download AWS CLI v2 + get_url: + url: https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip + dest: /tmp/awscliv2.zip + mode: '0644' + when: aws_cli_check.rc != 0 + + - name: Unzip AWS CLI v2 + unarchive: + src: /tmp/awscliv2.zip + dest: /tmp/ + remote_src: yes + when: aws_cli_check.rc != 0 + + - name: Install AWS CLI v2 + command: /tmp/aws/install + args: + creates: /usr/local/bin/aws + when: aws_cli_check.rc != 0 + + - name: Clean up AWS CLI installation files + file: + path: "{{ item }}" + state: absent + loop: + - /tmp/awscliv2.zip + - /tmp/aws + when: aws_cli_check.rc != 0 + + - name: Create AWS credentials directory for root user + file: + path: /root/.aws + state: directory + owner: root + group: root + mode: '0755' + + - name: Copy AWS credentials to root + copy: + src: ~/.aws/credentials + dest: /root/.aws/credentials + owner: root + group: root + mode: '0600' + + - name: Copy AWS config to root + copy: + src: ~/.aws/config + dest: /root/.aws/config + owner: root + group: root + mode: '0600' + + - name: Login to ECR + shell: | + aws ecr get-login-password --region {{ aws_region }} | docker login --username AWS --password-stdin {{ aws_account_id }}.dkr.ecr.{{ aws_region }}.amazonaws.com + + - name: Pull Docker image from ECR + docker_image: + name: "{{ ecr_repository_url }}" + tag: latest + source: pull + force_source: yes + + - name: Stop and remove existing container + docker_container: + name: "{{ app_name }}" + state: absent + ignore_errors: yes + + - name: Run application container + docker_container: + name: "{{ app_name }}" + image: "{{ ecr_repository_url }}:latest" + state: started + restart_policy: always + ports: + - "{{ app_port }}:80" + env: + NODE_ENV: production diff --git a/ansible/playbooks/deploy-monitoring-stack.yml b/ansible/playbooks/deploy-monitoring-stack.yml new file mode 100644 index 0000000..487b387 --- /dev/null +++ b/ansible/playbooks/deploy-monitoring-stack.yml @@ -0,0 +1,92 @@ +--- +- name: Deploy complete monitoring stack + hosts: monitoring_servers + become: yes + tasks: + - name: Create Docker network + docker_network: + name: monitoring + state: present + + - name: Create Prometheus directory + file: + path: /home/ubuntu/prometheus + state: directory + owner: ubuntu + group: ubuntu + mode: '0755' + + - name: Copy Prometheus configuration + copy: + src: ../prometheus.yml + dest: /home/ubuntu/prometheus/prometheus.yml + owner: ubuntu + group: ubuntu + mode: '0644' + + - name: Create Prometheus data directory + file: + path: /home/ubuntu/prometheus/data + state: directory + owner: nobody + group: nogroup + mode: '0777' + + - name: Stop existing Prometheus container + docker_container: + name: prometheus + state: absent + ignore_errors: yes + + - name: Run Prometheus container + docker_container: + name: prometheus + image: prom/prometheus:latest + state: started + restart_policy: always + networks: + - name: monitoring + ports: + - "9090:9090" + volumes: + - /home/ubuntu/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - /home/ubuntu/prometheus/data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/usr/share/prometheus/console_libraries' + - '--web.console.templates=/usr/share/prometheus/consoles' + - '--web.enable-lifecycle' + + - name: Create Grafana directory + file: + path: /home/ubuntu/grafana + state: directory + owner: "472" + group: "472" + mode: '0755' + + - name: Stop existing Grafana container + docker_container: + name: grafana + state: absent + ignore_errors: yes + + - name: Run Grafana container + docker_container: + name: grafana + image: grafana/grafana:latest + state: started + restart_policy: always + networks: + - name: monitoring + ports: + - "3000:3000" + volumes: + - /home/ubuntu/grafana:/var/lib/grafana + env: + GF_SECURITY_ADMIN_USER: "admin" + GF_SECURITY_ADMIN_PASSWORD: "admin123" + GF_SERVER_ROOT_URL: "https://monitoring.muja-net.com" + GF_SERVER_SERVE_FROM_SUB_PATH: "false" + GF_SERVER_DOMAIN: "monitoring.muja-net.com" diff --git a/ansible/playbooks/deploy-node-exporter.yml b/ansible/playbooks/deploy-node-exporter.yml new file mode 100644 index 0000000..fa746a1 --- /dev/null +++ b/ansible/playbooks/deploy-node-exporter.yml @@ -0,0 +1,20 @@ +--- +- name: Deploy Node Exporter on web server + hosts: web_servers + become: yes + tasks: + - name: Run Node Exporter container + docker_container: + name: node-exporter + image: prom/node-exporter:latest + state: started + restart_policy: always + ports: + - "9100:9100" + command: + - '--path.rootfs=/host' + - '--path.procfs=/host/proc' + - '--path.sysfs=/host/sys' + - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' + volumes: + - '/:/host:ro,rslave' diff --git a/ansible/playbooks/install-docker.yml b/ansible/playbooks/install-docker.yml new file mode 100644 index 0000000..7cdfd64 --- /dev/null +++ b/ansible/playbooks/install-docker.yml @@ -0,0 +1,63 @@ +--- +- name: Install Docker on all servers + hosts: all + become: yes + tasks: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install prerequisites + apt: + name: + - ca-certificates + - curl + - gnupg + - lsb-release + state: present + + - name: Create keyrings directory + file: + path: /etc/apt/keyrings + state: directory + mode: '0755' + + - name: Add Docker GPG key + shell: | + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + args: + creates: /etc/apt/keyrings/docker.gpg + + - name: Add Docker repository + shell: | + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + args: + creates: /etc/apt/sources.list.d/docker.list + + - name: Update apt cache after adding repo + apt: + update_cache: yes + + - name: Install Docker + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + + - name: Start and enable Docker + systemd: + name: docker + state: started + enabled: yes + + - name: Add ubuntu user to docker group + user: + name: ubuntu + groups: docker + append: yes diff --git a/ansible/prometheus.yml b/ansible/prometheus.yml new file mode 100644 index 0000000..7d480f2 --- /dev/null +++ b/ansible/prometheus.yml @@ -0,0 +1,19 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + external_labels: + monitor: 'devops-bootcamp' + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + labels: + instance: 'prometheus' + + - job_name: 'web-server-node-exporter' + static_configs: + - targets: ['10.0.0.5:9100'] + labels: + instance: 'web-server' + role: 'application' diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000..b5e8db8 --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,9 @@ +# Replace 'yourname' with your actual name + +terraform { + backend "s3" { + bucket = "devops-bootcamp-terraform-mujadp" + key = "terraform.tfstate" + region = "ap-southeast-1" + } +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..6714e41 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,320 @@ + +# VPC +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "devops-vpc" + } +} + +# Internet Gateway +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "devops-igw" + } +} + +# Public Subnet +resource "aws_subnet" "public" { + vpc_id = aws_vpc.main.id + cidr_block = var.public_subnet_cidr + availability_zone = "${var.aws_region}a" + map_public_ip_on_launch = true + + tags = { + Name = "devops-public-subnet" + } +} + +# Private Subnet +resource "aws_subnet" "private" { + vpc_id = aws_vpc.main.id + cidr_block = var.private_subnet_cidr + availability_zone = "${var.aws_region}a" + + tags = { + Name = "devops-private-subnet" + } +} + +# Elastic IP for NAT Gateway +resource "aws_eip" "nat" { + domain = "vpc" + + tags = { + Name = "devops-nat-eip" + } + + depends_on = [aws_internet_gateway.main] +} + +# NAT Gateway +resource "aws_nat_gateway" "main" { + allocation_id = aws_eip.nat.id + subnet_id = aws_subnet.public.id + + tags = { + Name = "devops-ngw" + } + + depends_on = [aws_internet_gateway.main] +} + +# Public Route Table +resource "aws_route_table" "public" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id + } + + tags = { + Name = "devops-public-route" + } +} + +# Private Route Table +resource "aws_route_table" "private" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main.id + } + + tags = { + Name = "devops-private-route" + } +} + +# Route Table Association - Public +resource "aws_route_table_association" "public" { + subnet_id = aws_subnet.public.id + route_table_id = aws_route_table.public.id +} + +# Route Table Association - Private +resource "aws_route_table_association" "private" { + subnet_id = aws_subnet.private.id + route_table_id = aws_route_table.private.id +} + +# Security Group - Public (Web Server) +resource "aws_security_group" "public" { + name = "devops-public-sg" + description = "Security group for web server" + vpc_id = aws_vpc.main.id + + # HTTP from anywhere + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + description = "Allow HTTP from anywhere" + } + + # Node Exporter from Monitoring Server (will be updated after creating monitoring server) + ingress { + from_port = 9100 + to_port = 9100 + protocol = "tcp" + security_groups = [aws_security_group.private.id] + description = "Allow Node Exporter from Monitoring Server" + } + + # SSH from VPC only + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [var.vpc_cidr] + description = "Allow SSH from VPC" + } + + # Allow all outbound + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "devops-public-sg" + } +} + +# Security Group - Private (Ansible Controller & Monitoring Server) +resource "aws_security_group" "private" { + name = "devops-private-sg" + description = "Security group for Ansible Controller and Monitoring Server" + vpc_id = aws_vpc.main.id + + # SSH from VPC only + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [var.vpc_cidr] + description = "Allow SSH from VPC" + } + + # Allow all outbound + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "devops-private-sg" + } +} + +# IAM Role for SSM +resource "aws_iam_role" "ssm_role" { + name = "devops-ec2-ssm-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "devops-ec2-ssm-role" + } +} + +# Attach SSM policy to role +resource "aws_iam_role_policy_attachment" "ssm_policy" { + role = aws_iam_role.ssm_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +# IAM Instance Profile +resource "aws_iam_instance_profile" "ssm_profile" { + name = "devops-ec2-ssm-profile" + role = aws_iam_role.ssm_role.name + + tags = { + Name = "devops-ec2-ssm-profile" + } +} + +# Get latest Ubuntu 24.04 AMI +data "aws_ami" "ubuntu_24_04" { + most_recent = true + owners = ["099720109477"] # Canonical + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +# Elastic IP for Web Server +resource "aws_eip" "web_server" { + domain = "vpc" + + tags = { + Name = "devops-web-server-eip" + } +} + +# EC2 Instance - Web Server (Public Subnet) +resource "aws_instance" "web_server" { + ami = data.aws_ami.ubuntu_24_04.id + instance_type = "t3.micro" + subnet_id = aws_subnet.public.id + vpc_security_group_ids = [aws_security_group.public.id] + iam_instance_profile = aws_iam_instance_profile.ssm_profile.name + private_ip = "10.0.0.5" + + root_block_device { + volume_size = 8 + volume_type = "gp3" + } + + tags = { + Name = "devops-web-server" + } +} + +# Associate Elastic IP with Web Server +resource "aws_eip_association" "web_server" { + instance_id = aws_instance.web_server.id + allocation_id = aws_eip.web_server.id +} + +# EC2 Instance - Ansible Controller (Private Subnet) +resource "aws_instance" "ansible_controller" { + ami = data.aws_ami.ubuntu_24_04.id + instance_type = "t3.micro" + subnet_id = aws_subnet.private.id + vpc_security_group_ids = [aws_security_group.private.id] + iam_instance_profile = aws_iam_instance_profile.ssm_profile.name + private_ip = "10.0.0.135" + + root_block_device { + volume_size = 8 + volume_type = "gp3" + } + + tags = { + Name = "devops-ansible-controller" + } +} + +# EC2 Instance - Monitoring Server (Private Subnet) +resource "aws_instance" "monitoring_server" { + ami = data.aws_ami.ubuntu_24_04.id + instance_type = "t3.micro" + subnet_id = aws_subnet.private.id + vpc_security_group_ids = [aws_security_group.private.id] + iam_instance_profile = aws_iam_instance_profile.ssm_profile.name + private_ip = "10.0.0.136" + + root_block_device { + volume_size = 8 + volume_type = "gp3" + } + + tags = { + Name = "devops-monitoring-server" + } +} + +# ECR Repository +resource "aws_ecr_repository" "app_repo" { + name = "devops-bootcamp/final-project-yourname" + image_tag_mutability = "MUTABLE" + + image_scanning_configuration { + scan_on_push = true + } + + tags = { + Name = "devops-bootcamp-final-project" + } +} diff --git a/terraform/main.tf.backup b/terraform/main.tf.backup new file mode 100644 index 0000000..b9d18d1 --- /dev/null +++ b/terraform/main.tf.backup @@ -0,0 +1,320 @@ + +# VPC +resource "aws_vpc" "main" { + cidr_block = var.vpc_cidr + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "devops-vpc" + } +} + +# Internet Gateway +resource "aws_internet_gateway" "main" { + vpc_id = aws_vpc.main.id + + tags = { + Name = "devops-igw" + } +} + +# Public Subnet +resource "aws_subnet" "public" { + vpc_id = aws_vpc.main.id + cidr_block = var.public_subnet_cidr + availability_zone = "${var.aws_region}a" + map_public_ip_on_launch = true + + tags = { + Name = "devops-public-subnet" + } +} + +# Private Subnet +resource "aws_subnet" "private" { + vpc_id = aws_vpc.main.id + cidr_block = var.private_subnet_cidr + availability_zone = "${var.aws_region}a" + + tags = { + Name = "devops-private-subnet" + } +} + +# Elastic IP for NAT Gateway +resource "aws_eip" "nat" { + domain = "vpc" + + tags = { + Name = "devops-nat-eip" + } + + depends_on = [aws_internet_gateway.main] +} + +# NAT Gateway +resource "aws_nat_gateway" "main" { + allocation_id = aws_eip.nat.id + subnet_id = aws_subnet.public.id + + tags = { + Name = "devops-ngw" + } + + depends_on = [aws_internet_gateway.main] +} + +# Public Route Table +resource "aws_route_table" "public" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.main.id + } + + tags = { + Name = "devops-public-route" + } +} + +# Private Route Table +resource "aws_route_table" "private" { + vpc_id = aws_vpc.main.id + + route { + cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.main.id + } + + tags = { + Name = "devops-private-route" + } +} + +# Route Table Association - Public +resource "aws_route_table_association" "public" { + subnet_id = aws_subnet.public.id + route_table_id = aws_route_table.public.id +} + +# Route Table Association - Private +resource "aws_route_table_association" "private" { + subnet_id = aws_subnet.private.id + route_table_id = aws_route_table.private.id +} + +# Security Group - Public (Web Server) +resource "aws_security_group" "public" { + name = "devops-public-sg" + description = "Security group for web server" + vpc_id = aws_vpc.main.id + + # HTTP from anywhere + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + description = "Allow HTTP from anywhere" + } + + # Node Exporter from Monitoring Server (will be updated after creating monitoring server) + ingress { + from_port = 9100 + to_port = 9100 + protocol = "tcp" + security_groups = [aws_security_group.private.id] + description = "Allow Node Exporter from Monitoring Server" + } + + # SSH from VPC only + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [var.vpc_cidr] + description = "Allow SSH from VPC" + } + + # Allow all outbound + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "devops-public-sg" + } +} + +# Security Group - Private (Ansible Controller & Monitoring Server) +resource "aws_security_group" "private" { + name = "devops-private-sg" + description = "Security group for Ansible Controller and Monitoring Server" + vpc_id = aws_vpc.main.id + + # SSH from VPC only + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = [var.vpc_cidr] + description = "Allow SSH from VPC" + } + + # Allow all outbound + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "devops-private-sg" + } +} + +# IAM Role for SSM +resource "aws_iam_role" "ssm_role" { + name = "devops-ec2-ssm-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "devops-ec2-ssm-role" + } +} + +# Attach SSM policy to role +resource "aws_iam_role_policy_attachment" "ssm_policy" { + role = aws_iam_role.ssm_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +# IAM Instance Profile +resource "aws_iam_instance_profile" "ssm_profile" { + name = "devops-ec2-ssm-profile" + role = aws_iam_role.ssm_role.name + + tags = { + Name = "devops-ec2-ssm-profile" + } +} + +# Get latest Ubuntu 24.04 AMI +data "aws_ami" "ubuntu_24_04" { + most_recent = true + owners = ["099720109477"] # Canonical + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +# Elastic IP for Web Server +resource "aws_eip" "web_server" { + domain = "vpc" + + tags = { + Name = "devops-web-server-eip" + } +} + +# EC2 Instance - Web Server (Public Subnet) +resource "aws_instance" "web_server" { + ami = data.aws_ami.ubuntu_24_04.id + instance_type = "t3.micro" + subnet_id = aws_subnet.public.id + vpc_security_group_ids = [aws_security_group.public.id] + iam_instance_profile = aws_iam_instance_profile.ssm_profile.name + private_ip = "10.0.0.5" + + root_block_device { + volume_size = 8 + volume_type = "gp3" + } + + tags = { + Name = "devops-web-server" + } +} + +# Associate Elastic IP with Web Server +resource "aws_eip_association" "web_server" { + instance_id = aws_instance.web_server.id + allocation_id = aws_eip.web_server.id +} + +# EC2 Instance - Ansible Controller (Private Subnet) +resource "aws_instance" "ansible_controller" { + ami = data.aws_ami.ubuntu_24_04.id + instance_type = "t3.micro" + subnet_id = aws_subnet.private.id + vpc_security_group_ids = [aws_security_group.private.id] + iam_instance_profile = aws_iam_instance_profile.ssm_profile.name + private_ip = "10.0.0.135" + + root_block_device { + volume_size = 8 + volume_type = "gp3" + } + + tags = { + Name = "devops-ansible-controller" + } +} + +# EC2 Instance - Monitoring Server (Private Subnet) +resource "aws_instance" "monitoring_server" { + ami = data.aws_ami.ubuntu_24_04.id + instance_type = "t3.micro" + subnet_id = aws_subnet.private.id + vpc_security_group_ids = [aws_security_group.private.id] + iam_instance_profile = aws_iam_instance_profile.ssm_profile.name + private_ip = "10.0.0.136" + + root_block_device { + volume_size = 8 + volume_type = "gp3" + } + + tags = { + Name = "devops-monitoring-server" + } +} + +# ECR Repository +resource "aws_ecr_repository" "app_repo" { + name = "devops-bootcamp/final-project-yourname" + image_tag_mutability = "MUTABLE" + + image_scanning_configuration { + scan_on_push = true + } + + tags = { + Name = "devops-bootcamp-final-project" + } +} \ No newline at end of file diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..3688a68 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,55 @@ + +output "vpc_id" { + description = "VPC ID" + value = aws_vpc.main.id +} + +output "public_subnet_id" { + description = "Public Subnet ID" + value = aws_subnet.public.id +} + +output "private_subnet_id" { + description = "Private Subnet ID" + value = aws_subnet.private.id +} + +output "web_server_public_ip" { + description = "Web Server Public IP" + value = aws_eip.web_server.public_ip +} + +output "web_server_private_ip" { + description = "Web Server Private IP" + value = aws_instance.web_server.private_ip +} + +output "ansible_controller_private_ip" { + description = "Ansible Controller Private IP" + value = aws_instance.ansible_controller.private_ip +} + +output "monitoring_server_private_ip" { + description = "Monitoring Server Private IP" + value = aws_instance.monitoring_server.private_ip +} + +output "ecr_repository_url" { + description = "ECR Repository URL" + value = aws_ecr_repository.app_repo.repository_url +} + +output "web_server_instance_id" { + description = "Web Server Instance ID" + value = aws_instance.web_server.id +} + +output "ansible_controller_instance_id" { + description = "Ansible Controller Instance ID" + value = aws_instance.ansible_controller.id +} + +output "monitoring_server_instance_id" { + description = "Monitoring Server Instance ID" + value = aws_instance.monitoring_server.id +} \ No newline at end of file diff --git a/terraform/provider.tf b/terraform/provider.tf new file mode 100644 index 0000000..20663ab --- /dev/null +++ b/terraform/provider.tf @@ -0,0 +1,15 @@ + +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.aws_region +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..5c5589e --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,30 @@ + +variable "aws_region" { + description = "AWS region" + type = string + default = "ap-southeast-1" +} + +variable "vpc_cidr" { + description = "CIDR block for VPC" + type = string + default = "10.0.0.0/24" +} + +variable "public_subnet_cidr" { + description = "CIDR block for public subnet" + type = string + default = "10.0.0.0/25" +} + +variable "private_subnet_cidr" { + description = "CIDR block for private subnet" + type = string + default = "10.0.0.128/25" +} + +variable "project_name" { + description = "Project name for tagging" + type = string + default = "devops-bootcamp" +}