1οΈβ£ Why Auto Scaling?
Real traffic is never constant π
π Day β high traffic
π Night β low traffic
If you size your servers for one fixed number, you always lose:
Size for peak traffic β you overpay πΈ during quiet hours, running servers nobody needs
Size for average traffic β you crash β the moment traffic spikes above it
π‘ The fix: an Auto Scaling Group (ASG) watches real demand and adjusts the number of EC2 instances for you β automatically, in both directions.
2οΈβ£ What an ASG Actually Does
An ASG constantly compares current load against your configured thresholds and reacts:
β Scale out β add instances
When traffic climbs π, the ASG launches more EC2 instances π»π»π» to absorb the load.
β Scale in β remove instances
When traffic drops π, the ASG terminates instances it no longer needs, so you stop paying for idle capacity.
<div class="infographic-asg">
<style>
.infographic-asg {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
max-width: 920px;
margin: 0 auto;
padding: clamp(1.25rem, 5vw, 3rem);
box-sizing: border-box;
line-height: 1.5;
}
.infographic-asg[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.infographic-asg * { box-sizing: border-box; }
.asg-controls { display: flex; gap: 0.75rem; align-items: center; justify-content: center; margin-bottom: 1.5rem; }
.asg-ctrl-btn {
width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
background: var(--accent); color: #fff; border: none; cursor: pointer; padding: 0; border-radius: 2px;
}
.asg-ctrl-btn.asg-restart { background: none; color: var(--ink-soft); border: 1px solid var(--line); }
.asg-ctrl-btn svg { width: 16px; height: 16px; }
.asg-ctrl-btn .icon-pause { display: none; }
.asg-ctrl-btn.is-running .icon-play { display: none; }
.asg-ctrl-btn.is-running .icon-pause { display: block; }
.asg-diagram { width: 100%; height: auto; overflow: visible; }
.asg-bracket { stroke: var(--ink-soft); stroke-width: 1.5; fill: none; }
.asg-label { font-family: var(--body-font); font-size: 13px; font-weight: 600; fill: var(--ink); }
.asg-title-label { font-family: var(--display-font); font-size: 22px; font-weight: 600; fill: var(--ink); }
.asg-arrow-shape { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 2; }
.asg-box { stroke-width: 2; transition: fill 0.4s ease, stroke-dasharray 0.4s ease; }
.asg-box.is-active { fill: var(--accent); stroke: var(--accent); stroke-dasharray: 0; }
.asg-box.is-inactive { fill: var(--paper); stroke: var(--accent); stroke-dasharray: 4 3; }
.asg-box-pin { stroke: var(--paper); stroke-width: 1.5; opacity: 0.9; }
.asg-box.is-inactive + .asg-box-pins .asg-box-pin { stroke: var(--ink-soft); }
.asg-scale-arrow { stroke: var(--ink-soft); stroke-width: 2; fill: none; stroke-dasharray: 4 4; }
.asg-scale-arrow.asg-anim { animation: asg-flow 0.6s linear infinite; animation-play-state: paused; }
.infographic-asg.is-active .asg-scale-arrow.asg-anim { animation-play-state: running; }
@keyframes asg-flow { to { stroke-dashoffset: -16; } }
@media (prefers-reduced-motion: reduce) {
.infographic-asg *, .infographic-asg *::before, .infographic-asg *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<div class="asg-controls">
<button type="button" class="asg-ctrl-btn asg-toggle" aria-label="Play or pause">
<svg class="icon-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
<svg class="icon-pause" viewBox="0 0 24 24" fill="currentColor"><path d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
</button>
<button type="button" class="asg-ctrl-btn asg-restart" aria-label="Restart">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 4v6h6"/><path d="M3.5 15a9 9 0 1 0 2-9.5L1 10"/></svg>
</button>
</div>
<svg class="asg-diagram" viewBox="0 0 900 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Auto Scaling Group capacity diagram showing minimum, desired, and maximum instance counts, with instances filling in and out to illustrate scaling">
<!-- Maximum size bracket: spans all 6 boxes -->
<path class="asg-bracket" d="M 140 78 L 140 66 L 654 66 L 654 78" />
<line class="asg-bracket" x1="397" y1="66" x2="397" y2="52" />
<text class="asg-label" x="397" y="42" text-anchor="middle">Maximum size</text>
<!-- Desired capacity bracket: spans boxes 1-3 -->
<path class="asg-bracket" d="M 140 128 L 140 116 L 384 116 L 384 128" />
<line class="asg-bracket" x1="262" y1="116" x2="262" y2="102" />
<text class="asg-label" x="262" y="92" text-anchor="middle">Actual Size / Desired Capacity</text>
<!-- Minimum size bracket: spans box 1 only -->
<path class="asg-bracket" d="M 140 178 L 140 166 L 204 166 L 204 178" />
<line class="asg-bracket" x1="172" y1="166" x2="172" y2="152" />
<text class="asg-label" x="172" y="142" text-anchor="middle">Minimum size</text>
<!-- Scale Out as Needed -->
<text class="asg-label" x="522" y="142" text-anchor="middle">Scale Out as Needed</text>
<defs>
<marker id="asg-arrow-l" viewBox="0 0 10 10" refX="2" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M10,0 L0,5 L10,10 Z" fill="var(--ink-soft)" />
</marker>
<marker id="asg-arrow-r" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 Z" fill="var(--ink-soft)" />
</marker>
</defs>
<path class="asg-scale-arrow asg-anim" marker-start="url(#asg-arrow-l)" marker-end="url(#asg-arrow-r)" d="M 400 160 L 644 160" />
<!-- Big Auto Scaling Group arrow shape -->
<path class="asg-arrow-shape" d="
M 20 220
L 70 190 L 70 210
L 720 210 L 720 190 L 780 220
L 720 250 L 720 230
L 70 230 L 70 250
Z" />
<!-- 6 instance boxes -->
<g id="asg-boxes">
<g class="asg-box-group" data-i="0" transform="translate(140,220)"></g>
<g class="asg-box-group" data-i="1" transform="translate(230,220)"></g>
<g class="asg-box-group" data-i="2" transform="translate(320,220)"></g>
<g class="asg-box-group" data-i="3" transform="translate(410,220)"></g>
<g class="asg-box-group" data-i="4" transform="translate(500,220)"></g>
<g class="asg-box-group" data-i="5" transform="translate(590,220)"></g>
</g>
<text class="asg-title-label" x="450" y="300" text-anchor="middle">AUTO SCALING GROUP</text>
</svg>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-asg')
|| document.querySelector('.infographic-asg');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
var NS = 'http://www.w3.org/2000/svg';
var groups = wrapper.querySelectorAll('.asg-box-group');
function buildBox(g) {
var box = document.createElementNS(NS, 'rect');
box.setAttribute('class', 'asg-box is-inactive');
box.setAttribute('x', -30); box.setAttribute('y', -24);
box.setAttribute('width', 60); box.setAttribute('height', 48);
box.setAttribute('rx', 4);
g.appendChild(box);
var pinCoords = [
[-14,-24,-14,-30],[0,-24,0,-30],[14,-24,14,-30],
[-14,24,-14,30],[0,24,0,30],[14,24,14,30],
[-30,-10,-36,-10],[-30,10,-36,10],
[30,-10,36,-10],[30,10,36,10]
];
pinCoords.forEach(function (c) {
var l = document.createElementNS(NS, 'line');
l.setAttribute('class', 'asg-box-pin');
l.setAttribute('x1', c[0]); l.setAttribute('y1', c[1]);
l.setAttribute('x2', c[2]); l.setAttribute('y2', c[3]);
g.appendChild(l);
});
return box;
}
var boxes = [];
groups.forEach(function (g) { boxes.push(buildBox(g)); });
var activeCount = 3;
var direction = 1; // 1 = scaling out, -1 = scaling in
function render() {
boxes.forEach(function (box, i) {
if (i < activeCount) {
box.classList.add('is-active');
box.classList.remove('is-inactive');
} else {
box.classList.remove('is-active');
box.classList.add('is-inactive');
}
});
}
render();
var isRunning = false;
var timer = null;
function step() {
activeCount += direction;
if (activeCount >= 6) { activeCount = 6; direction = -1; }
if (activeCount <= 3) { activeCount = 3; direction = 1; }
render();
}
function startTimer() {
stopTimer();
timer = setInterval(step, 900);
}
function stopTimer() {
if (timer) { clearInterval(timer); timer = null; }
}
var toggleBtn = wrapper.querySelector('.asg-toggle');
var restartBtn = wrapper.querySelector('.asg-restart');
toggleBtn.addEventListener('click', function () {
isRunning = !isRunning;
toggleBtn.classList.toggle('is-running', isRunning);
wrapper.classList.toggle('is-active', isRunning);
if (isRunning) startTimer(); else stopTimer();
});
restartBtn.addEventListener('click', function () {
isRunning = false;
toggleBtn.classList.remove('is-running');
wrapper.classList.remove('is-active');
stopTimer();
activeCount = 3;
direction = 1;
render();
});
})();
</script>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});π― The goal isn't "more" or "fewer" servers β it's always running the right number for current demand.
3οΈβ£ Architecture: ALB + ASG Together
<div class="infographic-asglb">
<style>
.infographic-asglb {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
max-width: 920px;
margin: 0 auto;
padding: clamp(1.25rem, 5vw, 3rem);
box-sizing: border-box;
line-height: 1.5;
}
.infographic-asglb[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.infographic-asglb * { box-sizing: border-box; }
.asglb-controls { display: flex; gap: 0.75rem; align-items: center; justify-content: center; margin-bottom: 1.5rem; }
.asglb-ctrl-btn {
width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
background: var(--accent); color: #fff; border: none; cursor: pointer; padding: 0; border-radius: 2px;
}
.asglb-ctrl-btn.asglb-restart { background: none; color: var(--ink-soft); border: 1px solid var(--line); }
.asglb-ctrl-btn svg { width: 16px; height: 16px; }
.asglb-ctrl-btn .icon-pause { display: none; }
.asglb-ctrl-btn.is-running .icon-play { display: none; }
.asglb-ctrl-btn.is-running .icon-pause { display: block; }
.asglb-diagram { width: 100%; height: auto; overflow: visible; }
.asglb-label { font-family: var(--body-font); font-size: 13px; font-weight: 600; fill: var(--ink); }
.asglb-title-label { font-family: var(--display-font); font-size: 22px; font-weight: 600; fill: var(--ink); }
.asglb-arrow-shape { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 2; }
.asglb-box { stroke-width: 2; transition: fill 0.4s ease, stroke-dasharray 0.4s ease; }
.asglb-box.is-active { fill: var(--accent); stroke: var(--accent); stroke-dasharray: 0; }
.asglb-box.is-inactive { fill: var(--paper); stroke: var(--accent); stroke-dasharray: 4 3; }
.asglb-box-pin { stroke: var(--paper); stroke-width: 1.5; opacity: 0.9; }
.asglb-hub-ring { fill: var(--paper-alt); stroke: var(--accent); stroke-width: 2.5; }
.asglb-hub-mark { stroke: var(--accent); stroke-width: 2; fill: none; }
.asglb-fan-line { stroke: var(--accent); stroke-width: 1.75; fill: none; stroke-dasharray: 3 5; opacity: 0.75; }
.asglb-fan-line.asglb-anim { animation: asglb-flow 0.7s linear infinite; animation-play-state: paused; }
.asglb-traffic-arrow { fill: var(--accent); }
.infographic-asglb.is-active .asglb-fan-line.asglb-anim { animation-play-state: running; }
@keyframes asglb-flow { to { stroke-dashoffset: -16; } }
@media (prefers-reduced-motion: reduce) {
.infographic-asglb *, .infographic-asglb *::before, .infographic-asglb *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<div class="asglb-controls">
<button type="button" class="asglb-ctrl-btn asglb-toggle" aria-label="Play or pause">
<svg class="icon-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
<svg class="icon-pause" viewBox="0 0 24 24" fill="currentColor"><path d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
</button>
<button type="button" class="asglb-ctrl-btn asglb-restart" aria-label="Restart">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 4v6h6"/><path d="M3.5 15a9 9 0 1 0 2-9.5L1 10"/></svg>
</button>
</div>
<svg class="asglb-diagram" viewBox="0 0 900 380" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Web traffic hits a load balancer, which fans requests out to Auto Scaling Group instances, some active and some held in reserve">
<!-- Web Traffic -->
<text class="asglb-label" x="310" y="38" text-anchor="middle">Web Traffic</text>
<path class="asglb-traffic-arrow" d="M 388 15 L 412 15 L 412 32 L 424 32 L 400 56 L 376 32 L 388 32 Z" />
<!-- Load Balancer hub -->
<rect class="asglb-hub-ring" x="368" y="66" width="90" height="68" rx="12" />
<g transform="translate(400,100)">
<circle class="asglb-hub-mark" r="14" />
<line class="asglb-hub-mark" x1="12" y1="-6" x2="30" y2="-20" />
<line class="asglb-hub-mark" x1="14" y1="0" x2="34" y2="0" />
<line class="asglb-hub-mark" x1="12" y1="6" x2="30" y2="20" />
<circle class="asglb-hub-mark" cx="33" cy="-20" r="4.5" />
<circle class="asglb-hub-mark" cx="37" cy="0" r="4.5" />
<circle class="asglb-hub-mark" cx="33" cy="20" r="4.5" />
</g>
<text class="asglb-label" x="468" y="105" text-anchor="start">Load Balancer</text>
<!-- fan lines from LB down to each instance -->
<defs>
<marker id="asglb-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 Z" fill="var(--accent)" />
</marker>
</defs>
<path class="asglb-fan-line asglb-anim" marker-end="url(#asglb-arrow)" d="M 390 134 L 170 226" />
<path class="asglb-fan-line asglb-anim" marker-end="url(#asglb-arrow)" d="M 400 134 L 260 226" />
<path class="asglb-fan-line asglb-anim" marker-end="url(#asglb-arrow)" d="M 412 134 L 350 226" />
<path class="asglb-fan-line asglb-anim" marker-end="url(#asglb-arrow)" d="M 424 134 L 470 226" />
<path class="asglb-fan-line asglb-anim" marker-end="url(#asglb-arrow)" d="M 434 134 L 560 226" />
<path class="asglb-fan-line asglb-anim" marker-end="url(#asglb-arrow)" d="M 446 134 L 650 226" />
<!-- Big Auto Scaling Group arrow shape -->
<path class="asglb-arrow-shape" d="
M 20 260
L 70 230 L 70 250
L 750 250 L 750 230 L 800 260
L 750 290 L 750 270
L 70 270 L 70 290
Z" />
<!-- 6 instance boxes -->
<g id="asglb-boxes">
<g class="asglb-box-group" data-i="0" transform="translate(170,260)"></g>
<g class="asglb-box-group" data-i="1" transform="translate(260,260)"></g>
<g class="asglb-box-group" data-i="2" transform="translate(350,260)"></g>
<g class="asglb-box-group" data-i="3" transform="translate(470,260)"></g>
<g class="asglb-box-group" data-i="4" transform="translate(560,260)"></g>
<g class="asglb-box-group" data-i="5" transform="translate(650,260)"></g>
</g>
<text class="asglb-title-label" x="410" y="340" text-anchor="middle">AUTO SCALING GROUP</text>
</svg>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-asglb')
|| document.querySelector('.infographic-asglb');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
var NS = 'http://www.w3.org/2000/svg';
var groups = wrapper.querySelectorAll('.asglb-box-group');
function buildBox(g) {
var box = document.createElementNS(NS, 'rect');
box.setAttribute('class', 'asglb-box is-inactive');
box.setAttribute('x', -30); box.setAttribute('y', -24);
box.setAttribute('width', 60); box.setAttribute('height', 48);
box.setAttribute('rx', 4);
g.appendChild(box);
var pinCoords = [
[-14,-24,-14,-30],[0,-24,0,-30],[14,-24,14,-30],
[-14,24,-14,30],[0,24,0,30],[14,24,14,30],
[-30,-10,-36,-10],[-30,10,-36,10],
[30,-10,36,-10],[30,10,36,10]
];
pinCoords.forEach(function (c) {
var l = document.createElementNS(NS, 'line');
l.setAttribute('class', 'asglb-box-pin');
l.setAttribute('x1', c[0]); l.setAttribute('y1', c[1]);
l.setAttribute('x2', c[2]); l.setAttribute('y2', c[3]);
g.appendChild(l);
});
return box;
}
var boxes = [];
groups.forEach(function (g) { boxes.push(buildBox(g)); });
var activeCount = 3;
var direction = 1;
function render() {
boxes.forEach(function (box, i) {
if (i < activeCount) {
box.classList.add('is-active');
box.classList.remove('is-inactive');
} else {
box.classList.remove('is-active');
box.classList.add('is-inactive');
}
});
}
render();
var isRunning = false;
var timer = null;
function step() {
activeCount += direction;
if (activeCount >= 6) { activeCount = 6; direction = -1; }
if (activeCount <= 3) { activeCount = 3; direction = 1; }
render();
}
function startTimer() {
stopTimer();
timer = setInterval(step, 900);
}
function stopTimer() {
if (timer) { clearInterval(timer); timer = null; }
}
var toggleBtn = wrapper.querySelector('.asglb-toggle');
var restartBtn = wrapper.querySelector('.asglb-restart');
toggleBtn.addEventListener('click', function () {
isRunning = !isRunning;
toggleBtn.classList.toggle('is-running', isRunning);
wrapper.classList.toggle('is-active', isRunning);
if (isRunning) startTimer(); else stopTimer();
});
restartBtn.addEventListener('click', function () {
isRunning = false;
toggleBtn.classList.remove('is-running');
wrapper.classList.remove('is-active');
stopTimer();
activeCount = 3;
direction = 1;
render();
});
})();
</script>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});Users π β βοΈ Load Balancer β βοΈ Auto Scaling Group β π» EC2 InstancesThe load balancer is the fixed entry point traffic hits first. Behind it, the ASG owns the instances β deciding how many exist at any moment and handing that live pool to the load balancer to distribute traffic across.
!image.png
4οΈβ£ Key ASG Settings
Every ASG is defined by three numbers that set its scaling boundaries:
π’ Minimum capacity
The floor β the ASG will never run fewer than this many instances, even at zero traffic. π Example: 1
π― Desired capacity
The target it maintains under normal conditions β what "steady state" looks like for your app. π Example: 2
π Maximum capacity
The ceiling β the most instances the ASG is allowed to launch, no matter how high traffic spikes. This caps your worst-case cost. π Example: 5
Put together:
Min = 1
Desired = 2
Max = 5π Day to day, the ASG sits at 2 instances. Under load, it can scale anywhere between 1 and 5 β but never outside that range.
5οΈβ£ ASG + Load Balancer: The Key Integration
<div class="infographic-asgcost">
<style>
.infographic-asgcost {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
max-width: 720px;
margin: 0 auto;
padding: clamp(1.25rem, 5vw, 3rem);
box-sizing: border-box;
line-height: 1.5;
}
.infographic-asgcost[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.infographic-asgcost * { box-sizing: border-box; }
.ac-controls { display: flex; gap: 0.75rem; align-items: center; justify-content: center; margin-bottom: 1.5rem; }
.ac-ctrl-btn {
width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
background: var(--accent); color: #fff; border: none; cursor: pointer; padding: 0; border-radius: 2px;
}
.ac-ctrl-btn.ac-restart { background: none; color: var(--ink-soft); border: 1px solid var(--line); }
.ac-ctrl-btn svg { width: 16px; height: 16px; }
.ac-ctrl-btn .icon-pause { display: none; }
.ac-ctrl-btn.is-running .icon-play { display: none; }
.ac-ctrl-btn.is-running .icon-pause { display: block; }
.ac-diagram { width: 100%; height: auto; overflow: visible; }
.ac-panel-label { font-family: var(--display-font); font-size: 17px; font-weight: 600; fill: var(--ink); }
.ac-meter-track { fill: var(--paper-alt); stroke: var(--line); stroke-width: 1.5; }
.ac-meter-fill { fill: var(--accent); transition: y 0.4s ease, height 0.4s ease; }
.ac-meter-fill.is-fixed { fill: var(--ink-soft); }
.ac-box { stroke-width: 2; transition: fill 0.4s ease, stroke-dasharray 0.4s ease; }
.ac-box.is-active { fill: var(--accent); stroke: var(--accent); stroke-dasharray: 0; }
.ac-box.is-inactive { fill: var(--paper); stroke: var(--accent); stroke-dasharray: 3 3; }
.ac-box.is-fixed-on { fill: var(--ink-soft); stroke: var(--ink-soft); stroke-dasharray: 0; }
.ac-box-pin { stroke: var(--paper); stroke-width: 1.25; opacity: 0.9; }
.ac-divider { stroke: var(--line); stroke-width: 1.5; }
@media (prefers-reduced-motion: reduce) {
.infographic-asgcost *, .infographic-asgcost *::before, .infographic-asgcost *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<div class="ac-controls">
<button type="button" class="ac-ctrl-btn ac-toggle" aria-label="Play or pause">
<svg class="icon-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
<svg class="icon-pause" viewBox="0 0 24 24" fill="currentColor"><path d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
</button>
<button type="button" class="ac-ctrl-btn ac-restart" aria-label="Restart">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 4v6h6"/><path d="M3.5 15a9 9 0 1 0 2-9.5L1 10"/></svg>
</button>
</div>
<svg class="ac-diagram" viewBox="0 0 640 260" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison: without an auto scaling group, servers and cost stay fixed at maximum all the time; with an auto scaling group, running servers and cost track demand">
<line class="ac-divider" x1="320" y1="20" x2="320" y2="240" />
<!-- WITHOUT ASG -->
<text class="ac-panel-label" x="150" y="30" text-anchor="middle">Without ASG</text>
<rect class="ac-meter-track" x="30" y="50" width="26" height="160" rx="3" />
<rect class="ac-meter-fill is-fixed" id="meter-left" x="30" y="50" width="26" height="160" rx="3" />
<g id="boxes-left">
<g class="ac-box-group" data-panel="left" data-i="0" transform="translate(110,80)"></g>
<g class="ac-box-group" data-panel="left" data-i="1" transform="translate(170,80)"></g>
<g class="ac-box-group" data-panel="left" data-i="2" transform="translate(230,80)"></g>
<g class="ac-box-group" data-panel="left" data-i="3" transform="translate(110,150)"></g>
<g class="ac-box-group" data-panel="left" data-i="4" transform="translate(170,150)"></g>
<g class="ac-box-group" data-panel="left" data-i="5" transform="translate(230,150)"></g>
</g>
<!-- WITH ASG -->
<text class="ac-panel-label" x="470" y="30" text-anchor="middle">With ASG</text>
<rect class="ac-meter-track" x="350" y="50" width="26" height="160" rx="3" />
<rect class="ac-meter-fill" id="meter-right" x="350" y="130" width="26" height="80" rx="3" />
<g id="boxes-right">
<g class="ac-box-group" data-panel="right" data-i="0" transform="translate(430,80)"></g>
<g class="ac-box-group" data-panel="right" data-i="1" transform="translate(490,80)"></g>
<g class="ac-box-group" data-panel="right" data-i="2" transform="translate(550,80)"></g>
<g class="ac-box-group" data-panel="right" data-i="3" transform="translate(430,150)"></g>
<g class="ac-box-group" data-panel="right" data-i="4" transform="translate(490,150)"></g>
<g class="ac-box-group" data-panel="right" data-i="5" transform="translate(550,150)"></g>
</g>
</svg>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-asgcost')
|| document.querySelector('.infographic-asgcost');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
var NS = 'http://www.w3.org/2000/svg';
var groups = wrapper.querySelectorAll('.ac-box-group');
function buildBox(g) {
var box = document.createElementNS(NS, 'rect');
box.setAttribute('class', 'ac-box is-inactive');
box.setAttribute('x', -25); box.setAttribute('y', -18);
box.setAttribute('width', 50); box.setAttribute('height', 36);
box.setAttribute('rx', 3);
g.appendChild(box);
var pinCoords = [
[-10,-18,-10,-23],[10,-18,10,-23],
[-10,18,-10,23],[10,18,10,23],
[-25,-7,-30,-7],[-25,7,-30,7],
[25,-7,30,-7],[25,7,30,7]
];
pinCoords.forEach(function (c) {
var l = document.createElementNS(NS, 'line');
l.setAttribute('class', 'ac-box-pin');
l.setAttribute('x1', c[0]); l.setAttribute('y1', c[1]);
l.setAttribute('x2', c[2]); l.setAttribute('y2', c[3]);
g.appendChild(l);
});
return box;
}
var leftBoxes = [], rightBoxes = [];
groups.forEach(function (g) {
var box = buildBox(g);
if (g.dataset.panel === 'left') leftBoxes[+g.dataset.i] = box;
else rightBoxes[+g.dataset.i] = box;
});
// left panel: always fixed at max
leftBoxes.forEach(function (box) {
box.classList.remove('is-inactive');
box.classList.add('is-fixed-on');
});
var meterRight = wrapper.querySelector('#meter-right');
var trackTop = 50, trackBottom = 210, trackHeight = 160;
var activeCount = 3;
var direction = 1;
function render() {
rightBoxes.forEach(function (box, i) {
if (i < activeCount) {
box.classList.add('is-active');
box.classList.remove('is-inactive');
} else {
box.classList.remove('is-active');
box.classList.add('is-inactive');
}
});
var frac = activeCount / 6;
var h = trackHeight * frac;
meterRight.setAttribute('y', trackBottom - h);
meterRight.setAttribute('height', h);
}
render();
var isRunning = false;
var timer = null;
function step() {
activeCount += direction;
if (activeCount >= 6) { activeCount = 6; direction = -1; }
if (activeCount <= 3) { activeCount = 3; direction = 1; }
render();
}
function startTimer() { stopTimer(); timer = setInterval(step, 900); }
function stopTimer() { if (timer) { clearInterval(timer); timer = null; } }
var toggleBtn = wrapper.querySelector('.ac-toggle');
var restartBtn = wrapper.querySelector('.ac-restart');
toggleBtn.addEventListener('click', function () {
isRunning = !isRunning;
toggleBtn.classList.toggle('is-running', isRunning);
if (isRunning) startTimer(); else stopTimer();
});
restartBtn.addEventListener('click', function () {
isRunning = false;
toggleBtn.classList.remove('is-running');
stopTimer();
activeCount = 3;
direction = 1;
render();
});
})();
</script>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});Scaling only helps if new traffic actually finds the new instances β and that's handled automatically:
Instance created β β it's automatically registered with the ALB's target group and starts receiving traffic
Instance removed β β it's automatically deregistered, so the ALB stops sending it traffic before it disappears
π You never manually add or remove targets β the ASG and ALB stay in sync on their own π€
6οΈβ£ Self-Healing: Free Fault Tolerance
<div class="infographic-selfheal">
<style>
.infographic-selfheal {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
max-width: 640px;
margin: 0 auto;
padding: clamp(1.25rem, 5vw, 3rem);
box-sizing: border-box;
line-height: 1.5;
}
.infographic-selfheal[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.infographic-selfheal * { box-sizing: border-box; }
.sh-controls { display: flex; gap: 0.75rem; align-items: center; justify-content: center; margin-bottom: 1.75rem; }
.sh-ctrl-btn {
width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
background: var(--accent); color: #fff; border: none; cursor: pointer; padding: 0; border-radius: 2px;
}
.sh-ctrl-btn.sh-restart { background: none; color: var(--ink-soft); border: 1px solid var(--line); }
.sh-ctrl-btn svg { width: 16px; height: 16px; }
.sh-ctrl-btn .icon-pause { display: none; }
.sh-ctrl-btn.is-running .icon-play { display: none; }
.sh-ctrl-btn.is-running .icon-pause { display: block; }
.sh-diagram { width: 100%; height: auto; overflow: visible; }
/* stepper */
.sh-step-dot { fill: var(--paper-alt); stroke: var(--line); stroke-width: 2; transition: fill 0.3s, stroke 0.3s; }
.sh-step-dot.is-current { fill: var(--accent); stroke: var(--accent); }
.sh-step-dot.is-done { fill: var(--ink-soft); stroke: var(--ink-soft); }
.sh-step-line { stroke: var(--line); stroke-width: 2; }
.sh-step-label { font-family: var(--body-font); font-size: 12px; fill: var(--ink-soft); transition: fill 0.3s, font-weight 0.3s; }
.sh-step-label.is-current { fill: var(--ink); font-weight: 700; }
/* instance icon */
.sh-instance-box { fill: var(--paper-alt); stroke: var(--ink-soft); stroke-width: 2; transition: opacity 0.5s ease, transform 0.5s ease, stroke 0.3s; transform-box: fill-box; transform-origin: center; }
.sh-instance-pin { stroke: var(--ink-soft); stroke-width: 2; transition: opacity 0.5s ease; }
.sh-mark-ok { stroke: var(--accent); stroke-width: 3; fill: none; stroke-linecap: round; stroke-linejoin: round; transition: opacity 0.3s ease; }
.sh-mark-bad { stroke: var(--ink-soft); stroke-width: 3; fill: none; stroke-linecap: round; transition: opacity 0.3s ease; }
.sh-placeholder { fill: none; stroke: var(--line); stroke-width: 2; stroke-dasharray: 5 5; transition: opacity 0.3s ease; }
@media (prefers-reduced-motion: reduce) {
.infographic-selfheal *, .infographic-selfheal *::before, .infographic-selfheal *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<div class="sh-controls">
<button type="button" class="sh-ctrl-btn sh-toggle" aria-label="Play or pause">
<svg class="icon-play" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
<svg class="icon-pause" viewBox="0 0 24 24" fill="currentColor"><path d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
</button>
<button type="button" class="sh-ctrl-btn sh-restart" aria-label="Restart">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 4v6h6"/><path d="M3.5 15a9 9 0 1 0 2-9.5L1 10"/></svg>
</button>
</div>
<svg class="sh-diagram" viewBox="0 0 560 260" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Self-healing cycle: healthy instance becomes unhealthy, is terminated, then replaced with a fresh healthy instance">
<!-- stepper -->
<line class="sh-step-line" x1="70" y1="34" x2="490" y2="34" />
<g id="step-0"><circle class="sh-step-dot" cx="70" cy="34" r="8" /><text class="sh-step-label" x="70" y="60" text-anchor="middle">Healthy</text></g>
<g id="step-1"><circle class="sh-step-dot" cx="230" cy="34" r="8" /><text class="sh-step-label" x="230" y="60" text-anchor="middle">Unhealthy</text></g>
<g id="step-2"><circle class="sh-step-dot" cx="350" cy="34" r="8" /><text class="sh-step-label" x="350" y="60" text-anchor="middle">Terminated</text></g>
<g id="step-3"><circle class="sh-step-dot" cx="490" cy="34" r="8" /><text class="sh-step-label" x="490" y="60" text-anchor="middle">New Instance</text></g>
<!-- placeholder slot (always present, faint) -->
<rect class="sh-placeholder" x="240" y="110" width="80" height="60" rx="4" />
<!-- the instance -->
<g id="sh-instance" transform="translate(280,140)">
<line class="sh-instance-pin" x1="-14" y1="-30" x2="-14" y2="-24"/><line class="sh-instance-pin" x1="0" y1="-30" x2="0" y2="-24"/><line class="sh-instance-pin" x1="14" y1="-30" x2="14" y2="-24"/>
<line class="sh-instance-pin" x1="-14" y1="30" x2="-14" y2="24"/><line class="sh-instance-pin" x1="0" y1="30" x2="0" y2="24"/><line class="sh-instance-pin" x1="14" y1="30" x2="14" y2="24"/>
<line class="sh-instance-pin" x1="-30" y1="-10" x2="-24" y2="-10"/><line class="sh-instance-pin" x1="-30" y1="10" x2="-24" y2="10"/>
<line class="sh-instance-pin" x1="30" y1="-10" x2="24" y2="-10"/><line class="sh-instance-pin" x1="30" y1="10" x2="24" y2="10"/>
<rect class="sh-instance-box" x="-24" y="-24" width="48" height="48" rx="4" />
<path class="sh-mark-ok" d="M -9 0 L -3 8 L 10 -10" />
<g class="sh-mark-bad">
<line x1="-9" y1="-9" x2="9" y2="9" />
<line x1="9" y1="-9" x2="-9" y2="9" />
</g>
</g>
<text class="sh-step-label" x="280" y="210" text-anchor="middle" style="font-size:13px;">EC2 Instance</text>
</svg>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-selfheal')
|| document.querySelector('.infographic-selfheal');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
var steps = [
wrapper.querySelector('#step-0'),
wrapper.querySelector('#step-1'),
wrapper.querySelector('#step-2'),
wrapper.querySelector('#step-3')
];
var box = wrapper.querySelector('.sh-instance-box');
var pins = wrapper.querySelectorAll('.sh-instance-pin');
var markOk = wrapper.querySelector('.sh-mark-ok');
var markBad = wrapper.querySelector('.sh-mark-bad');
var instanceGroup = wrapper.querySelector('#sh-instance');
var phase = 0; // 0 healthy, 1 unhealthy, 2 terminated, 3 replaced(new)
function setStepStates() {
steps.forEach(function (g, i) {
var dot = g.querySelector('.sh-step-dot');
var label = g.querySelector('.sh-step-label');
dot.classList.remove('is-current', 'is-done');
label.classList.remove('is-current');
if (i === phase) { dot.classList.add('is-current'); label.classList.add('is-current'); }
else if (i < phase) { dot.classList.add('is-done'); }
});
}
function applyPhase() {
setStepStates();
if (phase === 0) {
// healthy
instanceGroup.style.opacity = 1;
instanceGroup.style.transform = 'translate(280px,140px) scale(1)';
box.style.stroke = 'var(--accent)';
markOk.style.opacity = 1;
markBad.style.opacity = 0;
pins.forEach(function (p) { p.style.opacity = 1; });
} else if (phase === 1) {
// unhealthy
instanceGroup.style.opacity = 1;
instanceGroup.style.transform = 'translate(280px,140px) scale(1)';
box.style.stroke = 'var(--ink-soft)';
markOk.style.opacity = 0;
markBad.style.opacity = 1;
pins.forEach(function (p) { p.style.opacity = 1; });
} else if (phase === 2) {
// terminated: shrink away
instanceGroup.style.opacity = 0;
instanceGroup.style.transform = 'translate(280px,140px) scale(0.3)';
markOk.style.opacity = 0;
markBad.style.opacity = 0;
pins.forEach(function (p) { p.style.opacity = 0; });
} else if (phase === 3) {
// replaced: grow back in, healthy again
instanceGroup.style.opacity = 1;
instanceGroup.style.transform = 'translate(280px,140px) scale(1)';
box.style.stroke = 'var(--accent)';
markOk.style.opacity = 1;
markBad.style.opacity = 0;
pins.forEach(function (p) { p.style.opacity = 1; });
}
}
applyPhase();
var isRunning = false;
var timer = null;
function step() {
phase = (phase + 1) % 4;
applyPhase();
}
function startTimer() { stopTimer(); timer = setInterval(step, 1100); }
function stopTimer() { if (timer) { clearInterval(timer); timer = null; } }
var toggleBtn = wrapper.querySelector('.sh-toggle');
var restartBtn = wrapper.querySelector('.sh-restart');
toggleBtn.addEventListener('click', function () {
isRunning = !isRunning;
toggleBtn.classList.toggle('is-running', isRunning);
if (isRunning) startTimer(); else stopTimer();
});
restartBtn.addEventListener('click', function () {
isRunning = false;
toggleBtn.classList.remove('is-running');
stopTimer();
phase = 0;
applyPhase();
});
})();
</script>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});Auto scaling isn't just about load β it also protects against failure.
π₯ If an instance fails:
The ASG detects it's unhealthy β (via the same health checks the ALB uses)
It terminates the failed instance π§Ή
It immediately launches a fresh replacement β to keep the group at its desired capacity
π― Result: your app recovers from a crashed server without you touching anything.
7οΈβ£ Cost Optimization
Without ASG: you provision for peak traffic and pay for that capacity 24/7 πΈ, even while most of it sits idle
With ASG: capacity tracks demand in real time β you pay for roughly what you actually use π―
π This is what "elastic" in elastic cloud computing really means.
βοΈ ASG vs Load Balancer β Different Jobs
They're often used together, but they solve different problems:
Role: the Load Balancer distributes traffic; the ASG manages how many instances exist
Handles failures: both do β the ALB routes around an unhealthy instance, the ASG replaces it
Scaling: only the ASG scales capacity up or down
Entry point: only the ALB is a traffic entry point; the ASG has no public address
π Exam Tips (very important)
ASG = elasticity β‘
Scale out = add instances β
Scale in = remove instances β
Works together with an ALB βοΈ
Self-healing = automatically replaces unhealthy instances π‘οΈ