mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-20 11:21:49 +00:00
fix: nginx-alb terraform example uses private IPs for instances (#131)
The ALB reaches backends over the private VPC network, so the Nginx instances no longer need public IPs. Drop associate_public_ip_address and map_public_ip_on_launch, and swap the instance public-IP outputs for private-IP ones. The *.elb.spinifex.local DNS name does not resolve from the operator's host, so document an AWS CLI one-liner (describe-load-balancers with an AvailabilityZones[].LoadBalancerAddresses[].IpAddress query) that retrieves the ALB's public IP for curl/browser testing.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Nginx Web Server (Load Balanced)"
|
||||
description: "Deploy a VPC with a public subnet and two EC2 instances running Nginx, served by an ALB using Terraform on Spinifex."
|
||||
description: "Deploy a VPC with two private EC2 instances running Nginx, fronted by an internet-facing ALB using Terraform on Spinifex."
|
||||
category: "Terraform Workbooks"
|
||||
tags:
|
||||
- terraform
|
||||
@@ -33,12 +33,12 @@ resources:
|
||||
|
||||
## Overview
|
||||
|
||||
Deploy two complete Nginx web servers behind an Application Load Balancer on Spinifex using Terraform/OpenTofu. This workbook provisions a VPC, public subnet, internet gateway, route table, security group, SSH key pair, an application load balancer (ALB) and two EC2 instances with cloud-init user-data that installs and starts Nginx.
|
||||
Deploy two Nginx web servers behind an internet-facing Application Load Balancer on Spinifex using Terraform/OpenTofu. This workbook provisions a VPC, two subnets, internet gateway, route table, security group, SSH key pair, an application load balancer (ALB) and two EC2 instances with cloud-init user-data that installs and starts Nginx. Only the ALB is reachable from outside the VPC — the Nginx instances have **private IPs only** and the ALB talks to them over the private VPC network.
|
||||
|
||||
**What you'll learn:**
|
||||
|
||||
- Configuring the AWS Terraform provider to target Spinifex
|
||||
- Creating a VPC with public internet access
|
||||
- Creating a VPC with an internet-facing load balancer fronting private instances
|
||||
- Provisioning an EC2 instance with cloud-init user-data
|
||||
- Provisioning an internet-facing application load balancer
|
||||
- Generating SSH key pairs with the TLS provider
|
||||
@@ -48,11 +48,11 @@ Deploy two complete Nginx web servers behind an Application Load Balancer on Spi
|
||||
| Resource | Name | Purpose |
|
||||
|---|---|---|
|
||||
| VPC | `nginx-alb-vpc` | Isolated network (10.20.0.0/16) |
|
||||
| Subnets | `nginx-alb-public-a`, `nginx-alb-public-b` | Two public subnets for ALB and instances |
|
||||
| Internet Gateway | `nginx-alb-igw` | Routes internet traffic |
|
||||
| Subnets | `nginx-alb-public-a`, `nginx-alb-public-b` | Two subnets across AZs for the ALB and instances |
|
||||
| Internet Gateway | `nginx-alb-igw` | Routes internet traffic for the ALB |
|
||||
| Security Group | `nginx-alb-sg` | Allows SSH (22) and HTTP (80) inbound |
|
||||
| EC2 Instances | `nginx-alb-1`, `nginx-alb-2` | Debian 12 with Nginx via cloud-init |
|
||||
| ALB | `nginx-alb` | Application Load Balancer on port 80 |
|
||||
| EC2 Instances | `nginx-alb-1`, `nginx-alb-2` | Debian 12 with Nginx via cloud-init (private IPs only) |
|
||||
| ALB | `nginx-alb` | Internet-facing Application Load Balancer on port 80 |
|
||||
| Target Group | `nginx-alb-tg` | HTTP health-checked group for both instances |
|
||||
| Listener | HTTP :80 | Forwards traffic to the target group |
|
||||
|
||||
@@ -90,26 +90,33 @@ tofu apply
|
||||
|
||||
### Step 3. Verify
|
||||
|
||||
> **Note:** EC2 instances can take 30+ seconds to boot after apply. If SSH or HTTP is unreachable, wait and retry.
|
||||
> **Note:** EC2 instances can take 30+ seconds to boot after apply. If the ALB returns 5xx or HTTP is unreachable, wait and retry — the target group health checks need a moment to mark both instances healthy.
|
||||
|
||||
After apply completes, use the outputs to test:
|
||||
The ALB is internet-facing, but the DNS name Spinifex returns (`*.elb.spinifex.local`) will not resolve from your host. Fetch the ALB's public IP with the AWS CLI:
|
||||
|
||||
```bash
|
||||
# Hit the ALB — successive requests should alternate between Server 1 and Server 2
|
||||
curl http://<alb_public_ip>
|
||||
|
||||
# Direct instance access
|
||||
curl http://<instance_1_public_ip>
|
||||
curl http://<instance_2_public_ip>
|
||||
|
||||
# SSH into either instance
|
||||
ssh -i nginx-alb-demo.pem ec2-user@<instance_public_ip>
|
||||
|
||||
# Check target health via AWS CLI
|
||||
aws elbv2 describe-target-health --target-group-arn <tg_arn>
|
||||
ALB_IP=$(aws elbv2 describe-load-balancers --names nginx-alb \
|
||||
--query 'LoadBalancers[0].AvailabilityZones[].LoadBalancerAddresses[].IpAddress' \
|
||||
--output text)
|
||||
echo "ALB public IP: $ALB_IP"
|
||||
```
|
||||
|
||||
Open and refresh the `http://<alb_public_ip>` output in your browser to see the page alternate content served from each instance.
|
||||
Then hit the ALB — successive requests should alternate between Server 1 and Server 2:
|
||||
|
||||
```bash
|
||||
curl http://$ALB_IP
|
||||
curl http://$ALB_IP
|
||||
```
|
||||
|
||||
Open `http://$ALB_IP` in your browser and refresh to see the page alternate content served from each instance.
|
||||
|
||||
The Nginx instances themselves only have private IPs (see the `instance_1_private_ip` / `instance_2_private_ip` outputs) and are only reachable from inside the VPC — go through the ALB.
|
||||
|
||||
Check target health via AWS CLI:
|
||||
|
||||
```bash
|
||||
aws elbv2 describe-target-health --target-group-arn <tg_arn>
|
||||
```
|
||||
|
||||
### Cleanup
|
||||
|
||||
@@ -136,9 +143,17 @@ sudo systemctl status spinifex.target
|
||||
curl -k https://localhost:9999/
|
||||
```
|
||||
|
||||
### SSH Connection Timeout
|
||||
### ALB Returns 5xx / Targets Unhealthy
|
||||
|
||||
Check that the security group allows inbound SSH (port 22) and that the instance has a public IP assigned. Verify the instance is running:
|
||||
Give the instances a moment to finish cloud-init (Nginx has to install before it can answer health checks). Check target health:
|
||||
|
||||
```bash
|
||||
TG_ARN=$(aws elbv2 describe-target-groups --names nginx-alb-tg \
|
||||
--query 'TargetGroups[0].TargetGroupArn' --output text)
|
||||
aws elbv2 describe-target-health --target-group-arn "$TG_ARN"
|
||||
```
|
||||
|
||||
If targets stay unhealthy, verify the instances are running:
|
||||
|
||||
```bash
|
||||
aws ec2 describe-instances --profile spinifex
|
||||
@@ -146,10 +161,10 @@ aws ec2 describe-instances --profile spinifex
|
||||
|
||||
### Nginx Not Responding
|
||||
|
||||
SSH into the instance and check cloud-init logs:
|
||||
The Nginx instances have no public IP, so you can't SSH in directly from your host. If you need to inspect cloud-init logs, launch a small jump host in the same VPC or run commands via the Spinifex console, then:
|
||||
|
||||
```bash
|
||||
ssh -i nginx-alb-demo.pem ec2-user@<instance_public_ip>
|
||||
ssh -i nginx-alb-demo.pem ec2-user@<instance_private_ip>
|
||||
sudo journalctl -u cloud-init --no-pager
|
||||
sudo systemctl status nginx
|
||||
```
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Example: Nginx Web Servers with ALB on Spinifex
|
||||
#
|
||||
# Deploys a VPC with two public subnets, two EC2 instances running Nginx,
|
||||
# and an Application Load Balancer distributing HTTP traffic between them.
|
||||
# Deploys a VPC with two subnets, two EC2 instances running Nginx with
|
||||
# private-only IPs, and an internet-facing Application Load Balancer
|
||||
# distributing HTTP traffic between them. The ALB reaches the instances
|
||||
# over the private VPC network, so the instances do not need public IPs.
|
||||
# Demonstrates: VPC, subnets, internet gateway, route table, security group,
|
||||
# key pair, cloud-init user-data, EC2 instances, ALB, target group, and listener.
|
||||
#
|
||||
@@ -10,11 +12,15 @@
|
||||
# export AWS_PROFILE=spinifex
|
||||
# tofu init && tofu apply
|
||||
#
|
||||
# After apply:
|
||||
# curl http://<alb_dns_name> # Load-balanced Nginx
|
||||
# curl http://<public_ip_1> # Direct to instance 1
|
||||
# curl http://<public_ip_2> # Direct to instance 2
|
||||
# ssh -i nginx-alb-demo.pem ec2-user@<public_ip>
|
||||
# After apply, fetch the ALB's public IP (the *.elb.spinifex.local DNS
|
||||
# name does not resolve from your host):
|
||||
#
|
||||
# aws elbv2 describe-load-balancers --names nginx-alb \
|
||||
# --query 'LoadBalancers[0].AvailabilityZones[].LoadBalancerAddresses[].IpAddress' \
|
||||
# --output text
|
||||
#
|
||||
# Then:
|
||||
# curl http://<alb_public_ip> # Load-balanced Nginx (alternates between instances)
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
@@ -58,9 +64,9 @@ provider "aws" {
|
||||
region = var.region
|
||||
|
||||
endpoints {
|
||||
ec2 = var.spinifex_endpoint
|
||||
iam = var.spinifex_endpoint
|
||||
sts = var.spinifex_endpoint
|
||||
ec2 = var.spinifex_endpoint
|
||||
iam = var.spinifex_endpoint
|
||||
sts = var.spinifex_endpoint
|
||||
elasticloadbalancingv2 = var.spinifex_endpoint
|
||||
}
|
||||
|
||||
@@ -138,10 +144,9 @@ resource "aws_internet_gateway" "igw" {
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
resource "aws_subnet" "public_a" {
|
||||
vpc_id = aws_vpc.main.id
|
||||
cidr_block = "10.20.1.0/24"
|
||||
availability_zone = data.aws_availability_zones.available.names[0]
|
||||
map_public_ip_on_launch = true
|
||||
vpc_id = aws_vpc.main.id
|
||||
cidr_block = "10.20.1.0/24"
|
||||
availability_zone = data.aws_availability_zones.available.names[0]
|
||||
|
||||
tags = {
|
||||
Name = "nginx-alb-public-a"
|
||||
@@ -149,10 +154,9 @@ resource "aws_subnet" "public_a" {
|
||||
}
|
||||
|
||||
resource "aws_subnet" "public_b" {
|
||||
vpc_id = aws_vpc.main.id
|
||||
cidr_block = "10.20.2.0/24"
|
||||
availability_zone = data.aws_availability_zones.available.names[0]
|
||||
map_public_ip_on_launch = true
|
||||
vpc_id = aws_vpc.main.id
|
||||
cidr_block = "10.20.2.0/24"
|
||||
availability_zone = data.aws_availability_zones.available.names[0]
|
||||
|
||||
tags = {
|
||||
Name = "nginx-alb-public-b"
|
||||
@@ -236,8 +240,6 @@ resource "aws_instance" "nginx_1" {
|
||||
vpc_security_group_ids = [aws_security_group.web.id]
|
||||
key_name = aws_key_pair.nginx.key_name
|
||||
|
||||
associate_public_ip_address = true
|
||||
|
||||
user_data_base64 = base64encode(<<-USERDATA
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
@@ -278,8 +280,6 @@ resource "aws_instance" "nginx_2" {
|
||||
vpc_security_group_ids = [aws_security_group.web.id]
|
||||
key_name = aws_key_pair.nginx.key_name
|
||||
|
||||
associate_public_ip_address = true
|
||||
|
||||
user_data_base64 = base64encode(<<-USERDATA
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
@@ -388,41 +388,46 @@ resource "aws_lb_listener" "http" {
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
output "note" {
|
||||
value = "EC2 instances can take 30+ seconds to boot after apply. If SSH or HTTP is unreachable, wait and retry."
|
||||
value = <<-EOT
|
||||
EC2 instances can take 30+ seconds to boot after apply — if HTTP is
|
||||
unreachable, wait and retry.
|
||||
|
||||
The Nginx instances have private IPs only. The ALB DNS name ends in
|
||||
.elb.spinifex.local and will not resolve from your host, so fetch the
|
||||
ALB's public IP with:
|
||||
|
||||
aws elbv2 describe-load-balancers --names nginx-alb \
|
||||
--query 'LoadBalancers[0].AvailabilityZones[].LoadBalancerAddresses[].IpAddress' \
|
||||
--output text
|
||||
|
||||
Then: curl http://<that-ip>
|
||||
EOT
|
||||
}
|
||||
|
||||
output "alb_name" {
|
||||
value = aws_lb.web.name
|
||||
}
|
||||
|
||||
output "alb_arn" {
|
||||
value = aws_lb.web.arn
|
||||
}
|
||||
|
||||
output "alb_dns_name" {
|
||||
value = aws_lb.web.dns_name
|
||||
}
|
||||
|
||||
output "alb_url" {
|
||||
value = "http://${aws_lb.web.dns_name}"
|
||||
}
|
||||
|
||||
output "alb_public_ip" {
|
||||
value = aws_lb.web.public_ip
|
||||
}
|
||||
|
||||
output "instance_1_id" {
|
||||
value = aws_instance.nginx_1.id
|
||||
}
|
||||
|
||||
output "instance_1_public_ip" {
|
||||
value = aws_instance.nginx_1.public_ip
|
||||
output "instance_1_private_ip" {
|
||||
value = aws_instance.nginx_1.private_ip
|
||||
}
|
||||
|
||||
output "instance_2_id" {
|
||||
value = aws_instance.nginx_2.id
|
||||
}
|
||||
|
||||
output "instance_2_public_ip" {
|
||||
value = aws_instance.nginx_2.public_ip
|
||||
}
|
||||
|
||||
output "ssh_command_1" {
|
||||
value = "ssh -i nginx-alb-demo.pem ec2-user@${aws_instance.nginx_1.public_ip}"
|
||||
}
|
||||
|
||||
output "ssh_command_2" {
|
||||
value = "ssh -i nginx-alb-demo.pem ec2-user@${aws_instance.nginx_2.public_ip}"
|
||||
output "instance_2_private_ip" {
|
||||
value = aws_instance.nginx_2.private_ip
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user