Elastic Load Balancing (ELB) is an AWS service that helps distribute incoming traffic across multiple resources, such as EC2 instances, containers, or IP addresses, so that no single component becomes overwhelmed. By directing traffic only to healthy targets and operating across multiple Availability Zones, ELB helps keep applications available, resilient, and able to scale automatically as demand changes. AWS offers four types of load balancers, each designed for different kinds of applications.
The Application Load Balancer (ALB) works at the application layer (Layer 7) and is ideal for handling HTTP and HTTPS traffic. It supports smart routing based on factors such as URL paths or hostnames, making it a great fit for microservices and container-based setups.
The Network Load Balancer (NLB) operates at the transport layer (Layer 4) and is built for extremely fast, low-latency traffic such as TCP, UDP, and TLS. It can manage millions of requests per second and offers static IP addresses.
The Gateway Load Balancer (GLB) functions at the network layer (Layer 3) and helps simplify the deployment and scaling of third-party virtual appliances like firewalls or intrusion detection systems.
Classic Load Balancer (CLB) is an older-generation option that works at both Layer 4 and Layer 7. AWS now recommends migrating to ALB or NLB, especially since CLB was designed for the legacy EC2-Classic network.
AWS Auto Scaling is a service that monitors your applications and automatically adjusts resource capacity so performance stays consistent and costs remain under control. It ensures you have the right amount of compute power exactly when you need it.
Dynamic scaling responds in real time by adding or removing capacity based on CloudWatch metrics such as CPU usage or network activity.
Target tracking scaling is a type of dynamic scaling that aims to maintain a specific metric—like average CPU utilization—at a target level by adjusting the number of instances.
Step scaling changes capacity in predefined increments depending on how much a CloudWatch alarm is breached.
Predictive scaling uses machine learning to anticipate future traffic trends and adjusts capacity ahead of time, making it ideal for predictable spikes in usage.
Scheduled scaling lets you set specific times to scale resources up or down, such as boosting capacity during business hours and reducing it overnight.
Amazon CloudWatch is a monitoring and observability service that helps you keep track of your AWS resources, applications, and even on-premises systems. It gives you the data and insights you need to understand how your applications are performing, make better use of your resources, and maintain the overall health of your environment. CloudWatch gathers operational data such as metrics, logs, and events and brings them together to create a single, cohesive view of your system’s performance.
Architectural diagram:
+------------------------------- AWS Account ---------------------------------+
| Region: us-east-1 |
| |
| +----------------------- ASB-Lab (10.0.0.0/16) ------------------------+ |
| | | |
| | +-------- Public Subnet A (10.0.1.0/24) -------+ +-------------+ | |
| | | | | Public | | |
| | | Application Load Balancer (HTTP:80) | | Subnet B | | |
| | | - SG: ALB Security Group |<->| (10.0.2.0/ | | |
| | | - AZ: us-east-1a | | 24), | | |
| | +----------------------------------------------+ | AZ: us-east | | |
| | | -1b | | |
| | +-------------+ | |
| | | |
| | | |
| | +------- Private Subnet A (10.0.3.0/24) -------+ +--------------+ | |
| | | Auto Scaling Group (EC2) | | Private | | |
| | | - SG: EC2 Security Group |<->| Subnet B | | |
| | | - AZ: us-east-1a | | (10.0.4.0/ | | |
| | +----------------------------------------------+ | 24), | | |
| | | AZ: us-east | | |
| | +--------------+ | |
| | | |
| | Internet Gateway (IGW) for public subnets | |
| | - Public RT: 0.0.0.0/0 -> IGW | |
| | - Private RT: no direct IGW route | |
| +-----------------------------------------------------------------------+ |
| |
| CloudWatch: Detailed monitoring, metrics, alarms (CPU target tracking) |
| SNS: Email alerts on scaling events |
+-----------------------------------------------------------------------------+
Step 1: Set Up the VPC and Subnets
-
Create a VPC
- Go to VPC Dashboard > Create VPC
- VPC and more
- Name tag:
ASB-Lab - IPv4 CIDR:
10.0.0.0/16 - Tenancy:
Default - Number of Availability Zones (AZs):
2 - Number of public subnets:
2 - Number of private subnets:
2 - Enable DNS resolution: Checked
- Enable DNS hostnames: Checked
- Create VPC
- Go to VPC Dashboard > Create VPC
-
Subnets The above action will result in:
ASB-Lab-subnet-public1-us-east-1aASB-Lab-subnet-public2-us-east-1bASB-Lab-subnet-private1-us-east-1aASB-Lab-subnet-private2-us-east-1b
-
Internet Gateway
- VPC > Internet gateways
ASB-Lab-igwwill be automatically attached toASB-Lab
-
Route Tables
- VPC > Route tables
- Select public route table
ASB-Lab-rtb-public. InspectRoutesto0.0.0.0/0and10.0.0.0/16via Internet Gateway and local, associate with Public Subnets - Inspect Private route table: No direct route to IGW
Step 3: Launch and Configure Initial EC2 Instance
-
Go to EC2 > Instances > Launch Instances.
-
Configure:
- Name:
MyAppServer - AMI: Amazon Linux
- Instance type:
t2.micro - Key pair: Create
- Network: Use the earlier VPC,
ASB-Lab-vpc - Subnet:
ASB-Lab-subnet-public1-us-east-1a# Public subnet for initial testing - Auto-assign public IP:
Enable - Security Group:
- Create a Security Group:
- Security group name:
ASB Security Group- Description: Allowing SSH and HTTP access to the public
- Inbound rules:
- Allow HTTP (TCP/80) from anywhere
0.0.0.0/0 - Allow SSH (TCP/22) from anywhere
0.0.0.0/0
- Allow HTTP (TCP/80) from anywhere
- Outbound: > Type: All traffic > Destination type: Anywhere-IPV4
- Security group name:
- Create a Security Group:
- Name:
-
Advanced details
-
Detailed CloudWatch monitoring: Enable
-
User Data:
- Add a script to install and start your web app.
-
#!/bin/bash
sudo dnf update -y
sudo dnf install -y httpd php-cli php-fpm php-mysqlnd php-common php-gd php-xml php-opcache php-curl
sudo systemctl enable --now php-fpm httpd
- Launch the instance and wait until the status is
running.
SSH login:
- EC2 > Instances
- Select Instance > Connect > SSH client
- Example: ssh -i "AWSLabCSIT2087.pem" ec2-user@ec2-18-206-71-43.compute-1.amazonaws.com
or
Connect
- EC2 > Instances > > Connect to instance > Connect using a Public IP > Connect
- In the terminal:
cd /var/www/html- Create
sudo vi index.html
<?php
require_once __DIR__ . '/util.php';
$metadata = get_instance_metadata();
$cpuPct = get_cpu_usage_pct(0.35);
$class = classify_utilization($cpuPct);
$isLoadActive = get_load_flag();
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AWS EC2 Info & CPU Monitor</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 20px; color: #222; }
h1 { margin-top: 0; }
.grid { display: grid; grid-template-columns: 220px 1fr; gap: 8px; max-width: 780px; }
.label { color: #555; }
.value { font-weight: 600; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 12px; color: #fff; }
.low { background: #2d9b59; }
.med { background: #e6a700; }
.high { background: #d93025; }
.nav a { margin-right: 12px; }
.card { border: 1px solid #e1e4e8; border-radius: 6px; padding: 16px; margin: 16px 0; }
.muted { color: #666; font-size: 13px; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
</style>
</head>
<body>
<h1>EC2 Instance Metadata & CPU Monitor</h1>
<div class="card">
<h2>Instance Metadata</h2>
<div class="grid">
<div class="label">Instance ID</div><div class="value mono"><?= h($metadata['instance_id']) ?></div>
<div class="label">Instance type</div><div class="value mono"><?= h($metadata['instance_type']) ?></div>
<div class="label">Availability zone</div><div class="value mono"><?= h($metadata['availability_zone']) ?></div>
<div class="label">Private IP</div><div class="value mono"><?= h($metadata['local_ipv4']) ?></div>
<div class="label">Public IP</div><div class="value mono"><?= h($metadata['public_ipv4']) ?></div>
</div>
<div class="muted">If "N/A", this app may be running outside EC2 or IMDS access is restricted.</div>
</div>
<div class="card">
<h2>CPU Utilization</h2>
<?php $badgeClass = $class === 'Low' ? 'low' : ($class === 'Medium' ? 'med' : 'high'); ?>
<div class="grid">
<div class="label">Approx. utilization</div>
<div class="value">
<?= number_format($cpuPct, 1) ?>%
<span class="badge <?= $badgeClass ?>"><?= h($class) ?></span>
</div>
</div>
<div class="muted">Computed from /proc/stat over a short interval. This is an approximation.</div>
</div>
<div class="card">
<h2>Navigation</h2>
<div class="nav">
<a href="load.php">View CPU load generator</a>
<?php if ($isLoadActive): ?>
<a href="load.php?action=stop">Stop artificial load</a>
<?php else: ?>
<a href="load.php?action=start">Start artificial load</a>
<?php endif; ?>
<a href="index.php?ts=<?= time() ?>">Refresh CPU metrics</a>
</div>
</div>
<div class="muted">Server time: <?= date('Y-m-d H:i:s T') ?></div>
</body>
</html>- Create
sudo vi health.php
<?php
// health.php - Fast health check for ALB/Target Group
header('Content-Type: text/plain');
http_response_code(200);
echo "OK\n";- Create
sudo vim load.php
<?php
require_once __DIR__ . '/util.php';
function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
$action = isset($_GET['action']) ? strtolower((string)$_GET['action']) : '';
$target = isset($_GET['target']) ? max(10, min(95, (int)$_GET['target'])) : 60; // default 60% to match your lab
$maxWorkers = isset($_GET['workers']) ? max(1, min(64, (int)$_GET['workers'])) : max(1, min(8, cpu_core_count()));
$burstSeconds = isset($_GET['burst']) ? max(1, min(20, (int)$_GET['burst'])) : 3;
$intervalSec = 1;
$message = '';
if ($action === 'start') {
if (!is_controller_running()) {
$pid = start_controller($target, $maxWorkers, $burstSeconds, $intervalSec);
$message = $pid ? "Controller started (PID $pid)" : "Failed to start controller. Is php-cli installed?";
} else {
$message = 'Controller already running.';
}
}
if ($action === 'stop') {
if (is_controller_running()) {
stop_controller();
$message = 'Controller stopped.';
} else {
set_load_flag(false);
stop_all_bursts();
$message = 'No controller was running. Any bursts will exit shortly.';
}
}
$isActive = is_controller_running();
$controllerPid = get_controller_pid();
$cpu = get_cpu_usage_pct(0.5);
$class = classify_utilization($cpu);
$alive = active_pids();
$activeCount = count($alive);
$cores = cpu_core_count();
$defaultWorkers = max(1, min(8, $cores));
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CPU Load Generator</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 20px; color: #222; }
h1 { margin-top: 0; }
.card { border: 1px solid #e1e4e8; border-radius: 6px; padding: 16px; margin: 16px 0; }
.row { display: grid; grid-template-columns: 220px 1fr; gap: 8px; }
.muted { color: #666; font-size: 13px; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
label { margin-right: 10px; }
button { padding: 6px 10px; }
.ok { color: #0b7a0b; }
.warn { color: #b77000; }
</style>
</head>
<body>
<h1>CPU Load Generator</h1>
<?php if ($message): ?>
<div class="card"><strong><?= h($message) ?></strong></div>
<?php endif; ?>
<div class="card">
<div class="row"><div>Status</div><div class="mono"><?= $isActive ? 'Active' : 'Idle' ?><?= $isActive ? " (PID $controllerPid)" : '' ?></div></div>
<div class="row"><div>Approx. CPU usage</div><div class="mono"><?= number_format($cpu, 1) ?>% (<?= h($class) ?>)</div></div>
<div class="row"><div>Active worker processes</div><div class="mono"><?= $activeCount ?></div></div>
<div style="margin-top:10px;">
<a href="index.php">Back</a>
<?php if ($isActive): ?>
<a href="load.php?action=stop">Stop controller</a>
<?php else: ?>
<a href="load.php?action=start&target=<?= $target ?>&workers=<?= $maxWorkers ?>&burst=<?= $burstSeconds ?>">Start controller</a>
<?php endif; ?>
<a href="load.php">Refresh</a>
</div>
<div class="muted" style="margin-top:8px;">Background controller keeps CPU near target by spawning short bursts.</div>
</div>
<div class="card">
<h2>Generate Load (Manual burst)</h2>
<form id="loadForm" onsubmit="return false;">
<label>Seconds: <input type="number" id="seconds" min="1" max="1800" value="120" required></label>
<label>Workers: <input type="number" id="workers" min="1" max="64" value="<?= h($defaultWorkers) ?>" required></label>
<label><input type="checkbox" id="background" checked> Run in background</label>
<button id="runBtn" type="button">Generate Load</button>
</form>
<pre id="out" class="mono" style="white-space:pre-wrap;background:#f7f7f7;padding:10px;border-radius:6px;margin-top:10px;"></pre>
<div class="muted">Calls /api/load_api.php and returns JSON. No auto-refresh.</div>
</div>
<script>
const btn = document.getElementById('runBtn');
const out = document.getElementById('out');
btn.addEventListener('click', async () => {
out.textContent = 'Requesting...';
const s = document.getElementById('seconds').value || '60';
const w = document.getElementById('workers').value || '1';
const bg = document.getElementById('background').checked ? 1 : 0;
const url = `api/load_api.php?s=${encodeURIComponent(s)}&w=${encodeURIComponent(w)}&bg=${bg}`;
try {
const resp = await fetch(url, { cache: 'no-store' });
const text = await resp.text();
try {
const data = JSON.parse(text);
out.textContent = JSON.stringify(data, null, 2);
} catch (e) {
out.textContent = 'Non-JSON response:\n' + text;
}
} catch (e) {
out.textContent = 'Fetch error: ' + e;
}
});
</script>
<div class="muted">Server time: <?= date('Y-m-d H:i:s T') ?></div>
</body>
</html>- Create
sudo vi util.php
<?php
// util.php
// Shared utility functions for EC2 metadata, CPU metrics, and artificial load control.
// Designed for Amazon Linux 2. No external libraries required.
define('IMDS_BASE', 'http://169.254.169.254');
define('IMDS_TOKEN_PATH', '/latest/api/token');
define('IMDS_METADATA_BASE', '/latest/meta-data');
define('STATE_DIR', __DIR__);
define('FLAG_FILE', STATE_DIR . '/load.flag');
define('PIDS_FILE', STATE_DIR . '/load.pids');
define('CONTROLLER_PID_FILE', STATE_DIR . '/controller.pid');
// ---------------------------
// PHP CLI detection
// ---------------------------
function php_cli_path(): string {
if (is_executable('/usr/bin/php')) return '/usr/bin/php';
$p = trim((string)@shell_exec('command -v php 2>/dev/null'));
return $p !== '' ? $p : 'php';
}
// ---------------------------
// File state (flag + PIDs + controller PID)
// ---------------------------
function set_load_flag(bool $state): void {
@file_put_contents(FLAG_FILE, $state ? "1" : "0", LOCK_EX);
}
function get_load_flag(): bool {
if (!file_exists(FLAG_FILE)) return false;
$v = trim((string)@file_get_contents(FLAG_FILE));
return $v === "1";
}
function reset_pids_file(): void {
@file_put_contents(PIDS_FILE, "", LOCK_EX);
}
function record_pid(int $pid): void {
@file_put_contents(PIDS_FILE, $pid . PHP_EOL, FILE_APPEND | LOCK_EX);
}
function read_pids(): array {
if (!file_exists(PIDS_FILE)) return [];
$lines = array_filter(array_map('trim', explode("\n", (string)@file_get_contents(PIDS_FILE))));
$pids = [];
foreach ($lines as $line) {
if ($line !== '' && ctype_digit($line)) $pids[] = (int)$line;
}
return $pids;
}
function is_pid_running(int $pid): bool {
return $pid > 0 && is_dir("/proc/" . $pid);
}
function active_pids(): array {
$pids = read_pids();
$alive = [];
foreach ($pids as $pid) {
if (is_pid_running($pid)) $alive[] = $pid;
}
@file_put_contents(PIDS_FILE, implode(PHP_EOL, $alive) . (count($alive) ? PHP_EOL : ''), LOCK_EX);
return $alive;
}
function stop_all_bursts(): void {
$pids = read_pids();
foreach ($pids as $pid) {
if (is_pid_running($pid)) {
@shell_exec("kill -TERM " . escapeshellarg((string)$pid) . " >/dev/null 2>&1");
}
}
@unlink(PIDS_FILE);
}
// Controller PID helpers
function get_controller_pid(): int {
if (!file_exists(CONTROLLER_PID_FILE)) return 0;
$pid = (int)trim((string)@file_get_contents(CONTROLLER_PID_FILE));
return $pid > 0 ? $pid : 0;
}
function is_controller_running(): bool {
$pid = get_controller_pid();
return $pid > 0 && is_pid_running($pid);
}
function start_controller(int $targetPct, int $maxWorkers, int $burstSeconds, int $intervalSeconds): ?int {
// Spawn controller.php as a background PHP-CLI process
$php = php_cli_path();
$cmd = sprintf(
'nohup %s %s %d %d %d %d >/dev/null 2>&1 & echo $!',
escapeshellcmd($php),
escapeshellarg(__DIR__ . '/controller.php'),
$targetPct,
$maxWorkers,
$burstSeconds,
$intervalSeconds
);
$pidStr = @shell_exec($cmd);
$pid = (int)trim((string)$pidStr);
if ($pid > 0) {
@file_put_contents(CONTROLLER_PID_FILE, (string)$pid, LOCK_EX);
return $pid;
}
return null;
}
function stop_controller(): void {
$pid = get_controller_pid();
if ($pid > 0 && is_pid_running($pid)) {
@shell_exec("kill -TERM " . escapeshellarg((string)$pid) . " >/dev/null 2>&1");
}
@unlink(CONTROLLER_PID_FILE);
set_load_flag(false);
stop_all_bursts();
}
// ---------------------------
// EC2 Metadata (IMDSv2 with IMDSv1 fallback)
// ---------------------------
function curl_request(string $url, array $headers = [], string $method = 'GET', float $timeoutSec = 1.5): ?string {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int)ceil(min($timeoutSec, 2.0)));
curl_setopt($ch, CURLOPT_TIMEOUT, (int)ceil($timeoutSec));
if (!empty($headers)) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_PROXY, '');
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
$resp = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($resp === false || $httpCode < 200 || $httpCode >= 300) return null;
return $resp;
}
function imdsv2_get_token(): ?string {
return curl_request(IMDS_BASE . IMDS_TOKEN_PATH, ['X-aws-ec2-metadata-token-ttl-seconds: 21600'], 'PUT', 1.5);
}
function imds_get_path(string $path, ?string $token = null): ?string {
$headers = $token ? ['X-aws-ec2-metadata-token: ' . $token] : [];
return curl_request(IMDS_BASE . $path, $headers, 'GET', 1.5);
}
function get_instance_metadata(): array {
$token = imdsv2_get_token();
$paths = [
'instance_id' => IMDS_METADATA_BASE . '/instance-id',
'instance_type' => IMDS_METADATA_BASE . '/instance-type',
'availability_zone' => IMDS_METADATA_BASE . '/placement/availability-zone',
'local_ipv4' => IMDS_METADATA_BASE . '/local-ipv4',
'public_ipv4' => IMDS_METADATA_BASE . '/public-ipv4',
];
$out = [];
foreach ($paths as $key => $p) {
$val = imds_get_path($p, $token);
if ($val === null && $token !== null) $val = imds_get_path($p, null);
$out[$key] = $val !== null && trim($val) !== '' ? trim($val) : 'N/A';
}
return $out;
}
// ---------------------------
// CPU Metrics via /proc/stat
// ---------------------------
function read_proc_stat_aggregate(): ?array {
$contents = @file_get_contents('/proc/stat');
if ($contents === false) return null;
foreach (explode("\n", trim($contents)) as $line) {
if (strpos($line, 'cpu ') === 0) {
$parts = preg_split('/\s+/', trim($line));
if (count($parts) < 5) return null;
$nums = array_map('floatval', array_slice($parts, 1));
$user = $nums[0] ?? 0;
$nice = $nums[1] ?? 0;
$system = $nums[2] ?? 0;
$idle = $nums[3] ?? 0;
$iowait = $nums[4] ?? 0;
$irq = $nums[5] ?? 0;
$softirq = $nums[6] ?? 0;
$steal = $nums[7] ?? 0;
$idleAll = $idle + $iowait;
$nonIdle = $user + $nice + $system + $irq + $softirq + $steal;
$total = $idleAll + $nonIdle;
return ['idle' => $idleAll, 'total' => $total];
}
}
return null;
}
function get_cpu_usage_pct(float $intervalSeconds = 0.4): float {
$a = read_proc_stat_aggregate();
if ($a === null) return 0.0;
// BUGFIX: ensure we cast AFTER multiplying to microseconds
$sleepUs = (int)round(max($intervalSeconds, 0.1) * 1_000_000);
if ($sleepUs > 0) usleep($sleepUs);
$b = read_proc_stat_aggregate();
if ($b === null) return 0.0;
$idleDelta = $b['idle'] - $a['idle'];
$totalDelta = $b['total'] - $a['total'];
if ($totalDelta <= 0) return 0.0;
$usage = (1.0 - ($idleDelta / $totalDelta)) * 100.0;
return max(0.0, min(100.0, $usage));
}
function classify_utilization(float $pct): string {
if ($pct < 30.0) return 'Low';
if ($pct < 70.0) return 'Medium';
return 'High';
}
function cpu_core_count(): int {
$n = (int)trim((string)@shell_exec('nproc 2>/dev/null'));
if ($n > 0) return $n;
$contents = @file_get_contents('/proc/stat');
if ($contents !== false) {
$count = 0;
foreach (explode("\n", $contents) as $line) {
if (preg_match('/^cpu[0-9]+\s/', $line)) $count++;
}
if ($count > 0) return $count;
}
return 1;
}
// ---------------------------
// Artificial CPU load (burst processes)
// ---------------------------
/**
* Spawn short-lived PHP CLI workers that run busy loops for $seconds.
* Returns array of spawned PIDs.
*/
function spawn_cpu_burst_processes(int $seconds, int $workers): array {
$seconds = max(1, min($seconds, 1800));
$workers = max(1, min($workers, 64));
$php = php_cli_path();
$pids = [];
for ($i = 0; $i < $workers; $i++) {
$phpCode = '
$end = microtime(true) + ' . $seconds . ';
$x = 0.0;
while (microtime(true) < $end) {
for ($j = 0; $j < 50000; $j++) {
$x += sqrt($j + 1) + cos($x);
}
}
file_put_contents("php://stdout", "");
';
$cmd = "nohup " . escapeshellcmd($php) . " -r " . escapeshellarg($phpCode) . " >/dev/null 2>&1 & echo $!";
$pidStr = @shell_exec($cmd);
$pid = (int)trim((string)$pidStr);
if ($pid > 0) {
$pids[] = $pid;
record_pid($pid);
}
}
return $pids;
}
- Create
sudo vi controller.php
<?php
// controller.php
// Background controller that keeps CPU near target by spawning short bursts.
// Usage: php controller.php <targetPct> <maxWorkers> <burstSeconds> <intervalSeconds>
require_once __DIR__ . '/util.php';
$targetPct = isset($argv[1]) ? max(10, min(95, (int)$argv[1])) : 60;
$maxWorkers = isset($argv[2]) ? max(1, min(64, (int)$argv[2])) : max(1, min(8, cpu_core_count()));
$burstSeconds = isset($argv[3]) ? max(1, min(60, (int)$argv[3])) : 3;
$intervalSec = isset($argv[4]) ? max(1, min(10, (int)$argv[4])) : 1;
@ini_set('implicit_flush', '1');
ignore_user_abort(true);
set_time_limit(0);
// Mark controller active
set_load_flag(true);
reset_pids_file();
while (get_load_flag()) {
$cpu = get_cpu_usage_pct(0.5);
$alive = active_pids();
$activeCount = count($alive);
$toSpawn = 0;
if ($cpu < $targetPct) {
$gap = $targetPct - $cpu;
$idealAdd = (int)ceil(max(1, $gap / 20)); // ~1 worker per 20% gap
$slots = max(0, $maxWorkers - $activeCount);
$toSpawn = min($idealAdd, $slots);
}
if ($toSpawn > 0) {
spawn_cpu_burst_processes($burstSeconds, $toSpawn);
}
sleep($intervalSec);
}
// On exit, do not hard-kill bursts (they are short-lived). Cleanup is handled by stop_controller().
exit(0);-Create mkdir api
sudo vi api/load_api.php
<?php
// /api/load_api.php
// Returns JSON and can generate CPU load either in the background (spawn PHP CLI workers)
// or inline (busy loop within this request).
require_once dirname(__DIR__) . '/util.php';
header('Content-Type: application/json');
$seconds = isset($_GET['s']) ? (int)$_GET['s'] : 2;
$workers = isset($_GET['w']) ? (int)$_GET['w'] : max(1, min(8, cpu_core_count()));
$background = isset($_GET['bg']) ? ((int)$_GET['bg'] === 1) : true;
$seconds = max(1, min($seconds, 1800));
$workers = max(1, min($workers, 64));
$result = [
'ok' => true,
'mode' => $background ? 'background' : 'inline',
'seconds' => $seconds,
'workers' => $workers,
];
$cpuBefore = get_cpu_usage_pct(0.3);
if ($background) {
$pids = spawn_cpu_burst_processes($seconds, $workers);
$result['spawned_pids'] = $pids;
$result['message'] = empty($pids)
? 'Failed to spawn workers. Ensure php-cli is installed and executable by the web server user.'
: 'Load generation active...';
} else {
// Inline busy loop
$end = microtime(true) + $seconds;
$x = 0.0;
while (microtime(true) < $end) {
for ($j = 0; $j < 50000; $j++) {
$x += sqrt($j + 1) + cos($x);
}
}
$result['message'] = 'Inline load loop completed.';
}
$cpuAfter = get_cpu_usage_pct(0.3);
$result['cpu_before_pct'] = round($cpuBefore, 1);
$result['cpu_after_pct'] = round($cpuAfter, 1);
echo json_encode($result);
Step 4: Create an AMI (Machine Image) from the Instance
- In EC2 > Instances, select
MyAppServer. - Actions > Image and templates > Create image.
- Image name:
AppServerAMI - Description:
Lab AMI for APP Server
- Image name:
- Click Create image
- EC2 > AMIs, Wait until status: Available.
Step 5: Create Target Group and Load Balancer
a) Create Target Group
Targets are the individual instances that will respond to requests from the Load Balancer.
- EC2 > Load Balancing > Target Groups
- Create target group
- Target type: Instances
- Target group name:
LabTargetGroup - Protocol
- VPC:
ASB-Lab-vpc - Protocol version: HTTP1
- Health checks: Protocol
HTTP, Health check path:/health.php - Advanced health check settings
- Traffic port
- Healthy threshold: 2
- Unhealthy threshold: 2
- Timeout: 5s
- Interval: 10s
- Success codes: 200
- Next
- The
Register targetsscreen appears.
- The
- Next
- Create a target group
b) Create Load Balancer
- EC2 > Load Balancing > Load Balancers > Create Load Balancer
- Type: Application Load Balancer
- Name:
ALB - Scheme: Internet-facing
- Load balancer IP address type: IPv4
- VPC:
ASB-Lab-vpc - Availability Zones and Subnets: Select both, assign Public Subnet 1 & 2
- us-east-1a:
ASB-Lab-subnet-public1-us-east-1a
- us-east-1b:
ASB-Lab-subnet-public2-us-east-1b
- us-east-1a:
- Security Groups: Attach
ASB Security Groupand removedefault - Listeners and routing
- Protocol:
HTTPPort:80 - Default action
- Forward to target groups >
LabTargetGroup
- Forward to target groups >
- Protocol:
- Name:
- Create a load balancer and note the DNS name
Step 6: Create Launch Template
- EC2 > Instances > Launch Templates > Create launch template
- Name:
ALBConfig - Template version description: V1.0
- Auto Scaling guidance: (✓)
- Application and OS Images (Amazon Machine Image)
- My AMIs > Owned by me > Select
AppServerAMI
- My AMIs > Owned by me > Select
- Instance type:
t2.micro - Key pair: Select the key that was created previously
- Security Group: Select the security group that was created previously
-Advanced details
- Detailed CloudWatch monitoring: Enable
- Create launch template
- Name:
- EC2 > Instances > Launch Templates
- From the Actions menu, choose Create Auto Scaling group (Step 7 Cont.)
Step 7: Configure Auto Scaling Group
- Create Auto Scaling Group
- EC2 > Auto Scaling Groups > Create
- Name:
ALB Scaling Group - Launch template:
ALBConfigNext
- Name:
- VPC:
ASB-Lab - Subnets: Private Subnet 1 & 2
- Availability Zone distribution: Balanced best effort Next
- Load balancing
- Attach to an existing load balancer
- Choose from your load balancer target groups > select
LabTargetGroup | HTTP
- Choose from your load balancer target groups > select
- Attach to an existing load balancer
- Health checks
- Turn on Elastic Load Balancing health checks
- Turn on Amazon EBS health checks Next
- Group size:
- Desired capacity: 1
- Scaling
- Min: 1
- Max: 6
- Automatic scaling - optional
- Target tracking scaling policy
- Name:
ASBScalingPolicy - Metric type: Average CPU Utilization
- Target value: 60
- Name:
- Additional settings:
- Monitoring
- (✓) Select Enable group metrics collection within CloudWatch Next
- Monitoring
- Target tracking scaling policy
- Add notifications - optional
- Add notification
- Notification 1
- Create Topic:
- Send a notification to:
MyAppMonitor - With these recipients:
Your email IDNext
- Send a notification to:
- Create Topic:
- Notification 1
- Add notification
- Tags:
- Add tag
- Key=
Name, Value=LoadBalanceforMyApp
- Review & Create Auto Scaling group
- EC2 > Auto Scaling Groups > Create
Step 8: Verify Load Balancing & Auto Scaling
- Check Instances
- EC2 Dashboard > Instances
- Should see 1 running instances named
LoadBalanceforMyApp(created by Auto Scaling)
- Check Target Group
- EC2 > Load Balancing > Target Groups >
LabTargetGroup> Targets - Instance should show "healthy" status
- EC2 > Load Balancing > Target Groups >
- Test Load Balancer
- EC2 > Details > Public DNS
- ec2-3-224-211-36.compute-1.amazonaws.com
- Open in a web browser
- EC2 > Details > Public DNS
Step 9: Test Auto Scaling Response
- Check CloudWatch Alarms
- Search > CloudWatch > Alarms > All Alarms
- Should see alarms for Average CPU Utilization
- Generate Load
- Access the web app and use any built-in load test feature
- Alternatively, run a stress test on instances
sudo dnf install stress-ng -y
or
sudo yum install stress -y stress-ng --cpu 80 --timeout 60s stress-ng --cpu 100 --timeout 10m-
Observe Scaling
- CloudWatch > Alarms: Watch for AlarmHigh to trigger
- EC2 Dashboard > Instances: More instances appear as demand increases
-
Terminate all the running instances
- Did you receive an email notification and see an instance automatically created after a few minutes?