Scenario Lab · 1 of 10

3-tier app: EC2 se EC2 communication

Web EC2 → App EC2 → DB EC2. Har tier sirf apne pados wale tier se baat kare — security groups se layered access.

⏱ 2–2.5 hours
📶 Beginner → Intermediate
💰 Cost: ~$0.15 (NAT Gateway ~1.5 hr)
🧰 Skills: VPC, public/private subnets, SG chaining, nginx reverse proxy, MariaDB, private DNS
0 / 0 checks done
Scenario

ShopKart apna product catalogue AWS pe le ja raha hai. Architect ne ek 3-tier design diya hai. Internet se sirf web server (nginx) dikhna chahiye. Web server requests ko app server (Python API) ko aage bhejega, aur app server database server (MariaDB) se data laayega. Security team ka niyam hai: "har tier sirf apne theek pados wale tier se baat kare. Web kabhi seedha DB se baat nahi karega, aur internet se app ya DB tak koi raasta nahi hoga." Tumhe yeh network aur teeno servers banane hain.

PL-065 course se — console preview
Create VPC form, VPC only, CIDR
consoleCreate VPC form, VPC only, CIDR
Create internet gateway form
consoleCreate internet gateway form

VPC aur Internet Gateway ka console — poore steps 'Guided solution' mein hain.

Internet │ :80 ┌───────────────────┴───────────────────────────────────┐ shopkart-vpc 10.60.0.0/16 │ public 10.60.1.0/24 │ │ [web EC2: nginx] ─── /api/* ──┐ [NAT GW] │ │───────────────────────────────── │ ─────────────────── │ │ private 10.60.2.0/24 ▼ :8080 │ │ [app EC2: Python API] ── :3306 ──► [db EC2: MariaDB] │ └────────────────────────────────────────────────────────┘ SG chain: web-sg (80 ← 0.0.0.0/0) → app-sg (8080 ← web-sg) → db-sg (3306 ← app-sg)
Requirements — solution tabhi complete jab yeh sab sach ho
  1. VPC 10.60.0.0/16 banana hai, jisme ek public subnet (web ke liye) aur ek private subnet (app aur db ke liye) ho. Private servers ko packages install karne ke liye bahar internet jaane ki suvidha (NAT) chahiye, lekin bahar se andar aane ka koi raasta nahi hona chahiye.
  2. Browser mein http://<web-public-ip>/api/products kholne pe DB se aayi products ki list (JSON mein) dikhni chahiye.
  3. Web se app tak sirf port 8080 khula ho, aur app se db tak sirf port 3306. Web se DB tak koi connection nahi hona chahiye.
  4. Kisi bhi server pe SSH port (22) khula nahi hona chahiye; servers tak pahunchne ka akela raasta Session Manager ho.
  5. App aur DB servers ka koi public IP nahi hona chahiye.
Hints (sirf zaroorat ho toh)
  • Security group rule ka source IP ki jagah ek doosra security group bhi ho sakta hai. Is scenario mein yahi tareeka use karo.
  • DB ke user-data mein dnf install -y mariadb105-server chalao aur phir mysql -e "CREATE DATABASE ..." se database banao. Naye MariaDB mein root user socket se bina password ke login kar leta hai.
  • App ke user-data mein DB ka IP chahiye hoga, isliye DB ko pehle launch karo aur uska private IP nikaal lo.
  • nginx mein location /api/ { proxy_pass http://APP_IP:8080/; } likho. Aakhir ka / request ke path se /api prefix hata deta hai.
  • "Web se DB tak blocked hai" yeh test karne ke liye timeout 3 bash -c "</dev/tcp/DB_IP/3306" && echo open || echo blocked chalao.
Guided solution dekho (pehle khud try karo!)
1
Network: VPC, 2 subnets, IGW, NAT, route tables

Pehla block kaam ka folder banata hai, Amazon Linux 2023 ki latest AMI ID nikaalta hai, aur ek chhota tag function banata hai jo har resource pe Name aur Project tag lagane ka text tayyar karta hai.

Uske baad shopkart-vpc (10.60.0.0/16) banta hai aur usme DNS hostnames chalu kiye jaate hain. Phir do subnets bante hain: public (10.60.1.0/24) aur private (10.60.2.0/24). Public subnet mein launch hone wale servers ko apne aap public IP milta hai.

Phir ek internet gateway banake VPC se joda jaata hai. Public route table mein "0.0.0.0/0 internet gateway se" wala route daal ke use public subnet se joda jaata hai.

Aakhir mein public subnet mein ek Elastic IP ke saath NAT Gateway banta hai, aur uske available hone ka intezaar kiya jaata hai. Private route table mein "0.0.0.0/0 NAT se" wala route daala jaata hai, taaki private servers bahar ja sakein lekin bahar se koi andar na aa sake.

export AWS_DEFAULT_REGION=us-east-1 TYPE=t3.micro
mkdir -p ~/deepdive/scn1 && cd ~/deepdive/scn1
AMI_ID=$(aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --query Parameter.Value --output text)
tag() { echo "ResourceType=$1,Tags=[{Key=Name,Value=$2},{Key=Project,Value=deepdive-scn1}]"; }

VPC=$(aws ec2 create-vpc --cidr-block 10.60.0.0/16 --tag-specifications "$(tag vpc shopkart-vpc)" --query Vpc.VpcId --output text)
aws ec2 modify-vpc-attribute --vpc-id $VPC --enable-dns-hostnames
PUB=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.60.1.0/24 --availability-zone us-east-1a --tag-specifications "$(tag subnet shopkart-public)" --query Subnet.SubnetId --output text)
PRIV=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.60.2.0/24 --availability-zone us-east-1a --tag-specifications "$(tag subnet shopkart-private)" --query Subnet.SubnetId --output text)
aws ec2 modify-subnet-attribute --subnet-id $PUB --map-public-ip-on-launch

IGW=$(aws ec2 create-internet-gateway --query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC
PUB_RT=$(aws ec2 create-route-table --vpc-id $VPC --query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $PUB_RT --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW >/dev/null
aws ec2 associate-route-table --route-table-id $PUB_RT --subnet-id $PUB >/dev/null

EIP=$(aws ec2 allocate-address --query AllocationId --output text)
NAT=$(aws ec2 create-nat-gateway --subnet-id $PUB --allocation-id $EIP --query NatGateway.NatGatewayId --output text)
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT
PRIV_RT=$(aws ec2 create-route-table --vpc-id $VPC --query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $PRIV_RT --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT >/dev/null
aws ec2 associate-route-table --route-table-id $PRIV_RT --subnet-id $PRIV >/dev/null
echo "VPC=$VPC PUB=$PUB PRIV=$PRIV NAT=$NAT"
🖱 Console se bhi ho sakta hai (Lab 4 wala tareeka)
  1. VPC → Create VPC (VPC only, 10.60.0.0/16) → DNS hostnames Enable.
  2. Subnets → do subnets banao (public 10.60.1.0/24, private 10.60.2.0/24), public subnet pe auto-assign public IP Enable karo.
  3. Internet Gateway banake VPC se attach karo → public route table mein 0.0.0.0/0 → IGW → public subnet se associate.
  4. NAT Gateway (public subnet mein, naya Elastic IP ke saath) → private route table mein 0.0.0.0/0 → NAT → private subnet se associate.
PL-065 course se — asli console reference
Create VPC form, VPC only, CIDR
consoleCreate VPC form, VPC only, CIDR
Create internet gateway form
consoleCreate internet gateway form
Create NAT gateway, regional, elastic IP automatic
consoleCreate NAT gateway, regional, elastic IP automatic

VPC create, Internet Gateway aur NAT Gateway banane ka wahi console — is scenario mein subnets/CIDR tumhare apne honge.

2
Security group chain

Yeh block teeno tiers ke liye teen security groups banata hai aur unhe ek zanjeer (chain) mein jodta hai. Web SG pe port 80 poori duniya ke liye khula hai. App SG pe port 8080 sirf un servers ke liye khula hai jinpe web SG laga ho, aur DB SG pe port 3306 sirf un servers ke liye jinpe app SG laga ho. Source mein IP ki jagah security group dene ka fayda yeh hai ki servers ke IP badal jaayein, tab bhi rules sahi rehte hain. Kisi bhi SG mein port 22 nahi khola gaya hai.

sg() { aws ec2 create-security-group --vpc-id $VPC --group-name $1 --description "$2" --query GroupId --output text; }
WEB_SG=$(sg shopkart-web-sg "web tier")
APP_SG=$(sg shopkart-app-sg "app tier")
DB_SG=$(sg shopkart-db-sg "db tier")
aws ec2 authorize-security-group-ingress --group-id $WEB_SG --protocol tcp --port 80   --cidr 0.0.0.0/0 >/dev/null
aws ec2 authorize-security-group-ingress --group-id $APP_SG --protocol tcp --port 8080 --source-group $WEB_SG >/dev/null
aws ec2 authorize-security-group-ingress --group-id $DB_SG  --protocol tcp --port 3306 --source-group $APP_SG >/dev/null
🖱 Console se bhi ho sakta hai
  1. EC2 → Security Groups → teen groups banao (web-sg, app-sg, db-sg), koi bhi inbound rule na daalo shuru mein.
  2. web-sg mein inbound HTTP 80 source 0.0.0.0/0. app-sg mein Custom TCP 8080, source dropdown mein web-sg chuno (IP nahi). db-sg mein Custom TCP 3306, source app-sg.
PL-065 course se — asli console reference
SG inbound rules tab
consoleSG inbound rules tab

Security group inbound rules tab — source ki jagah tum yahan doosre SG ka reference doge.

3
DB server (MariaDB) — pehle

Pehle db-userdata.sh banaya jaata hai. Yeh server ke pehli baar boot hone pe MariaDB install karke chalu karta hai. Phir shop database aur ek app user banata hai, products table banata hai aur usme teen products daal deta hai.

Uske baad ek launch function banaya jaata hai. Yeh diye gaye naam, subnet, security group aur user-data ke saath instance launch karta hai; har instance pe Lab 1 wala role lagata hai aur IMDSv2 zaroori karta hai. Function instance ID aur private IP lautata hai.

Aakhir mein DB server bina public IP ke private subnet mein launch hota hai, aur uska ID aur IP DB_ID aur DB_IP mein rakh liye jaate hain.

cat > db-userdata.sh <<'EOF'
#!/bin/bash
dnf install -y mariadb105-server
systemctl enable --now mariadb
mysql -e "
CREATE DATABASE shop;
CREATE USER 'app'@'10.60.%' IDENTIFIED BY 'AppPass123!';
GRANT ALL ON shop.* TO 'app'@'10.60.%';
CREATE TABLE shop.products (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), price INT);
INSERT INTO shop.products (name, price) VALUES ('Keyboard', 1499), ('Mouse', 699), ('Monitor', 8999);"
EOF
launch() {  # name subnet sg userdata [extra args]
  aws ec2 run-instances --image-id $AMI_ID --instance-type $TYPE --subnet-id $2 --security-group-ids $3 \
    --iam-instance-profile Name=deepdive-ec2-role --user-data file://$4 --metadata-options HttpTokens=required \
    --tag-specifications "$(tag instance $1)" "${@:5}" --query 'Instances[0].[InstanceId,PrivateIpAddress]' --output text
}
read DB_ID DB_IP <<< "$(launch shopkart-db $PRIV $DB_SG db-userdata.sh --no-associate-public-ip-address)"
echo "DB $DB_ID $DB_IP"

Dhyan do ki DB user sirf 10.60.% se, yaani sirf isi VPC ke andar se, login kar sakta hai. Is tarah database ke level pe bhi least privilege lagaya gaya hai.

4
App server (Python API) — DB IP ke saath

Yeh block app server ka user-data banata hai. Bahar wale heredoc mein EOF bina quotes ke hai, isliye shell $DB_IP ki jagah DB ka asli IP bhar deta hai. Andar ke 'PY' aur 'UNIT' quotes mein hain, taaki server pe unke andar kuch na badle.

Server pe yeh script pymysql install karti hai aur /opt/app/app.py likhti hai. Yeh ek chhota HTTP server hai jo port 8080 pe do raaste deta hai: /health aur /products. /products DB se products padh ke JSON mein lautata hai, aur DB tak na pahunch paaye toh error ko JSON mein dikhata hai.

Phir ek systemd service banti hai, taaki app reboot ke baad bhi apne aap chalu ho aur crash hone pe dobara start ho.

Launch se pehle grep se check kiya jaata hai ki DB ka IP file mein sahi se bhar gaya hai. Uske baad app server bhi bina public IP ke private subnet mein launch hota hai.

cat > app-userdata.sh <<EOF
#!/bin/bash
dnf install -y python3-pip
pip3 install pymysql
mkdir -p /opt/app
cat > /opt/app/app.py <<'PY'
import json
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer

import pymysql

DB_HOST = "$DB_IP"


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/health":
            return self.send_json(200, {"ok": True, "host": socket.gethostname()})
        if self.path == "/products":
            try:
                conn = pymysql.connect(host=DB_HOST, user="app", password="AppPass123!",
                                       database="shop", connect_timeout=3)
                with conn.cursor() as cur:
                    cur.execute("SELECT id, name, price FROM products")
                    rows = [{"id": r[0], "name": r[1], "price": r[2]} for r in cur.fetchall()]
                conn.close()
                return self.send_json(200, {"served_by": socket.gethostname(), "products": rows})
            except Exception as exc:
                return self.send_json(500, {"error": str(exc)})
        self.send_json(404, {"error": "not found"})

    def send_json(self, code, body):
        data = json.dumps(body).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)


HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
PY
cat > /etc/systemd/system/shop-app.service <<'UNIT'
[Unit]
Description=ShopKart API
After=network-online.target
[Service]
ExecStart=/usr/bin/python3 /opt/app/app.py
Restart=always
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now shop-app
EOF
grep DB_HOST app-userdata.sh        # DB ka IP bhara hua dikhna chahiye
read APP_ID APP_IP <<< "$(launch shopkart-app $PRIV $APP_SG app-userdata.sh --no-associate-public-ip-address)"
echo "APP $APP_ID $APP_IP"
5
Web server (nginx reverse proxy)

Yeh block web server ka user-data banata hai. Isme nginx install hota hai aur ek config likhi jaati hai. Config ke hisaab se /api/ se shuru hone wali har request app server ke port 8080 pe bhej di jaati hai (yahan bhi $APP_IP ki jagah asli IP bhar jaata hai), aur baaki saari requests pe seedha "ShopKart web tier OK" lauta diya jaata hai.

Web server public subnet mein launch hota hai, isliye use public IP milta hai. Phir teeno servers ke chalu hone ka intezaar kiya jaata hai, web server ka public IP nikaala jaata hai, aur browser mein kholne wala URL print kiya jaata hai.

cat > web-userdata.sh <<EOF
#!/bin/bash
dnf install -y nginx
cat > /etc/nginx/conf.d/shopkart.conf <<'CONF'
server {
    listen 80;
    server_name shopkart;
    location /api/ {
        proxy_pass http://$APP_IP:8080/;
        proxy_connect_timeout 3s;
    }
    location / {
        default_type text/plain;
        return 200 "ShopKart web tier OK\n";
    }
}
CONF
systemctl enable --now nginx
EOF
read WEB_ID WEB_PRIV <<< "$(launch shopkart-web $PUB $WEB_SG web-userdata.sh)"
aws ec2 wait instance-running --instance-ids $DB_ID $APP_ID $WEB_ID
WEB_IP=$(aws ec2 describe-instances --instance-ids $WEB_ID --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
echo "open: http://$WEB_IP/api/products   (2-3 min baad)"
🖱 Teeno instances console se bhi launch ho sakte hain
  1. EC2 → Launch instances (Lab 1/2 jaisa) — DB pehle: subnet private, SG db-sg, Advanced details → User data mein db-userdata.sh ka content paste karo, public IP Disable.
  2. DB ka private IP note karke App ka user-data (upar wala, $DB_IP ki jagah asli IP) banao aur wahi tareeke se launch karo, subnet private, SG app-sg.
  3. App ka private IP note karke Web ka user-data ($APP_IP bhar ke) banao, subnet public, SG web-sg, public IP Enable rakho.

nginx ka apna default server bhi port 80 pe hota hai. conf.d wala config pehle load hota hai, isliye aam taur pe wahi default ban jaata hai. Agar phir bhi nginx ka default welcome page dikhe, toh /etc/nginx/nginx.conf mein se default server {} block hata do aur systemctl reload nginx chalao.

✓ Verification tests

V1
End-to-end aur layered access

Apne laptop se teen checks karo. Pehla web tier ka jawab dekhta hai. Doosra poora raasta (web se app se DB) check karta hai. Teesra pakka karta hai ki app server laptop se seedha nahi dikhta:

# Laptop:
curl -s http://$WEB_IP/                  # ShopKart web tier OK
curl -s http://$WEB_IP/api/products      # {"served_by": "ip-10-60-2-..", "products": [...]}
curl -m 5 -s http://$APP_IP:8080/health || echo "app private — laptop se unreachable ✔"

Ab Session Manager se web server ke shell mein jao aur check karo ki web se app tak raasta khula hai, lekin web se DB tak band hai (angle brackets mein asli IPs daalo):

curl -s http://<APP_IP>:8080/health                                               # ✔ allowed
timeout 3 bash -c "</dev/tcp/<DB_IP>/3306" && echo "DB open ✘" || echo "DB blocked ✔"

Phir app server ke shell mein timeout 3 bash -c "</dev/tcp/<DB_IP>/3306" && echo "DB open ✔" chala ke dekho ki app se DB tak raasta khula hai. curl -s https://checkip.amazonaws.com bhi chalao; yeh NAT ka public IP dikhayega, jisse saabit hota hai ki bahar jaane wala traffic chal raha hai.

Acceptance criteria (verify)
  • Browser ya curl mein DB se aaye 3 products dikhe.
  • Web se app tak raasta khula hai, web se DB tak band hai, aur app se DB tak khula hai.
  • Laptop se app aur DB tak nahi pahuncha ja sakta, aur kisi bhi SG mein port 22 khula nahi hai.
V2
Private DNS: IPs ki jagah naam (bonus)

Route 53 mein shopkart.internal naam ka ek private hosted zone banao aur use VPC shopkart-vpc se jodo. Usme do A records banao: app jo APP_IP ki taraf ho, aur db jo DB_IP ki taraf ho. Phir web server pe /etc/nginx/conf.d/shopkart.conf mein IP ki jagah app.shopkart.internal likho, aur sudo nginx -t && sudo systemctl reload nginx chalao. Ab agar app server badla jaaye, toh sirf DNS record badalna padega, config nahi.

Acceptance criteria (verify)
  • Web server se dig +short app.shopkart.internal chalane pe private IP milta hai, aur /api/products naam ke through bhi chal raha hai.
Break & Fix — error message se tier pehchano
  1. shopkart-app-sg ka inbound rule hata do. Ab /api/products pe 504 Gateway Time-out aayega, kyunki nginx app tak pahunch hi nahi paaya. Rule wapas laga do.
  2. DB ke shell mein sudo systemctl stop mariadb chalao. Ab API "error": "... Can't connect ... (111 Connection refused)" lautayega, jiska matlab hai ki network theek hai lekin service band hai.
  3. DB wapas chalu karo aur ab shopkart-db-sg ka rule hatao. Ab API "... (timed out)" lautayega, jiska matlab hai ki packet DB tak pahuncha hi nahi (SG, NACL ya route ki wajah se).

Lesson: refused ka matlab hai ki tum host tak pahunch gaye, lekin us port pe koi sun nahi raha. timeout ka matlab hai ki raaste mein kisi (jaise firewall) ne packet gira diya. 502 ka matlab hai ki backend ne toota hua jawab diya, aur 504 ka matlab hai ki backend tak samay pe pahuncha hi nahi ja saka.

Challenge — no steps given

App tier ko do instances mein (do alag AZs mein) chalao, aur web aur app ke beech ek internal ALB lagao, taaki nginx ab ALB ke DNS naam pe request bheje. Saath hi DB server ko RDS MariaDB se badlo (private, Multi-AZ band), aur password Secrets Manager se lo.

🧹 Cleanup

Yeh commands teeno servers ko terminate karti hain aur NAT Gateway delete karti hain. Phir dono ke poori tarah hat jaane ka intezaar karke Elastic IP chhod deti hain:

aws ec2 terminate-instances --instance-ids $WEB_ID $APP_ID $DB_ID >/dev/null
aws ec2 delete-nat-gateway --nat-gateway-id $NAT >/dev/null
aws ec2 wait instance-terminated --instance-ids $WEB_ID $APP_ID $DB_ID
aws ec2 wait nat-gateway-deleted --nat-gateway-ids $NAT && aws ec2 release-address --allocation-id $EIP
# Private hosted zone banaya tha toh uske records + zone delete karo

Uske baad VPC console mein shopkart-vpc chuno aur Delete VPC dabao; subnets, route tables, IGW aur SGs bhi saath mein delete ho jaate hain.