Python Fundamentals for Cloud Engineers

Build a server fleet health checker using core Python automation patterns.

Introduction

30 Second Summary

Imagine you manage dozens of servers and need to know which ones are running hot, running out of disk space, or completely offline. Checking each one manually is not an option.

In this project, you will build a Server Fleet Health Checker in Python. Your script analyzes server data from a JSON file and generates a report that flags exactly which servers have problems.

What You'll Build

You run a single command in your terminal, and your script instantly tells you which servers in your fleet need attention and why.

By the end of this project, you'll have:

  • A health checker script that flags specific issues for each server, like high CPU usage, low disk space, or an offline status.
  • A formatted health report printed to your terminal and saved to a text file, with a fleet-wide summary you can share or archive.
  • Error handling that gracefully manages missing files and incomplete server data instead of crashing.
  • Secret Mission: Add severity levels and sort the report so the most urgent problems appear first.

Are there any prerequisites?

No prerequisites needed. This project walks you through installing Python and VS Code from scratch on a Windows machine.

Before We Start

Before you start installing tools and writing code, take a moment to lock in what you're about to build and why it matters for your cloud engineering journey.

Set Up Python and VS Code

Before you can write any code, you need two things on your machine: Python to run your scripts, and a code editor to write them in.

In this step, you will install Python using the Microsoft Store, set up VS Code as your code editor, and create the project folder where your health checker will live.

In this step, get ready to:
  • Install Python on your Windows machine.
  • Install VS Code and the Python extension.
  • Create your project folder and verify everything works.
Install Python from the Microsoft Store

Python is the programming language you will use throughout this project. The easiest way to install it on Windows is through the Python Install Manager in the Microsoft Store.

  • Press the Windows key to open your search bar.
  • Type Microsoft Store and press Enter to open it.
  • In the Microsoft Store, click the search bar at the top and type Python.
  • Find the result called Python Install Manager published by the Python Software Foundation.
  • Click Install and wait for the installation to finish.

What is the Python Install Manager?

The Python Install Manager is the official way to install Python on Windows. It handles downloading and setting up the Python runtime for you.

After you install it, the python and py commands become available in your terminal. The first time you run python, it automatically downloads and installs the latest Python version.

You might also see individual Python version listings (like Python 3.13) in the Store. Look for the one named Python Install Manager instead. It gives you more flexibility for managing Python versions later.

Now confirm that the python command works on your machine.

  • Press the Windows key, type Command Prompt, and press Enter to open a new terminal window.
  • Check your Python version by running this command:
python --version

The first time you run this, the install manager automatically downloads the latest Python version. This may take a moment.

✔️ I see Python 3.13 or higher

You should see something like Python 3.13.15 or Python 3.14.7. The exact version does not matter for this project. Python is ready to go.

ⓧ I see an older version

If you see a version lower than 3.13, you may have an older Python installation taking priority. You can install a newer version alongside it using the install manager.

  • In the same Command Prompt window, run the following command to install the latest Python:
py install
  • Close and reopen Command Prompt.
  • Run python --version again. You should now see 3.13 or higher.

Still seeing the old version?

Open Settings, search for Manage app execution aliases, and make sure the toggle for python.exe and python3.exe are turned on. These toggles control which python command Windows uses.

Help me troubleshoot my Python version on Windows.

ⓧ Command not found

If you see an error saying the command is not recognized, the install manager may not have finished setting up.

  • Close the Command Prompt window completely.
  • Open a new Command Prompt window (press the Windows key, type Command Prompt, press Enter).
  • Run python --version again.

If it still does not work, check that the install completed in the Microsoft Store. Open the Store, search for Python Install Manager, and confirm the button says Open (not Install).

Still not working?

Open Settings, search for Manage app execution aliases, and make sure the toggle for python.exe is turned on.

Help me get the python command working on Windows.

Install VS Code and the Python extension

VS Code is a free code editor that gives you syntax highlighting, error detection, and a built-in terminal all in one window. You will use it for every step of this project.

  • Open your browser and go to https://code.visualstudio.com/download.
  • Under the Windows section, click the User Installer link for x64.
  • Run the downloaded installer file and follow the prompts to complete the installation.

What does the User Installer do?

The User Installer does not require administrator permissions. It installs VS Code just for your Windows account and adds the code command to your system PATH. This lets you open any folder in VS Code straight from the terminal.

Now open VS Code and install the Python extension.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Press Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows) to open the Extensions view.
  • In the search bar, type Python.
  • Find the extension called Python published by Microsoft (extension ID: ms-python.python).
  • Click Install.

What if VS Code asks me to trust the publisher?

When you install an extension for the first time, VS Code shows a dialog asking you to confirm that you trust the publisher. Click the button to trust Microsoft and proceed with the installation. This is a normal security feature.

After installation, the Install button changes to a gear icon. This confirms the Python extension is active.

Don't see the Python extension by Microsoft?

Make sure you type Python in the Extensions search bar, not the main search bar at the top of VS Code. The Extensions view has its own search field.

Look for the one with the extension ID ms-python.python shown underneath the extension name. There are several Python-related extensions, but this is the official one.

Help me find and install the Python extension in VS Code.

Create your project folder and verify your setup

With Python and VS Code ready, it is time to create a folder for your project and confirm everything works from inside VS Code.

  • In VS Code, open the integrated terminal by pressing Ctrl+` (the backtick key, above Tab).

Before you run the next command, what version number do you expect to see?

  • Verify Python works inside VS Code by running this command:
python --version

You see the same Python version as before (e.g., Python 3.13.15). This confirms VS Code's terminal can find your Python installation.

Python not found in VS Code's terminal?

If the terminal says the command is not recognized, close VS Code completely and reopen it. VS Code needs to reload PATH changes from the Python installation.

If that does not help, check that the terminal type is set to PowerShell or Command Prompt. Click the dropdown arrow next to the + button in the terminal panel to see your options.

Help me get Python working in my VS Code terminal.

Now create your project folder on the Desktop so it is easy to find.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir fleet-health-checker
cd fleet-health-checker
  • Open this folder in VS Code by running:
code .

A new VS Code window opens with fleet-health-checker as your workspace. You can see the folder name in the top title bar and in the Explorer sidebar on the left.

What does code . do?

The code command launches VS Code from the terminal. The . tells it to open the current folder as a workspace. This is a shortcut you will use often when starting projects.

VS Code did not open a new window?

Make sure you ran cd fleet-health-checker before running code .. If you are still in the Desktop folder, code . opens the Desktop instead.

If the code command is not recognized, close and reopen your terminal. The VS Code installer adds code to your PATH, but the terminal needs to reload to pick it up.

Help me open my project folder in VS Code from the terminal.

Your tools are ready. Next up, you will create realistic server data and write your first Python script to load and display it.

Create Your Server Fleet and Load the Data

Your Python environment is ready and VS Code is set up. Now it's time to write your first real script.

Cloud services like AWS send infrastructure data in a format called JSON. Before you can check server health, you need server data to check. In this step, you'll create a realistic JSON file representing a fleet of servers and write Python code to read and display it.

In this step, get ready to:
  • Create a JSON file with realistic server fleet data.
  • Write a Python script that loads the JSON and prints each server's details.
  • Verify the script outputs all six servers in the fleet.
Create the server fleet data file

JSON is a structured text format that uses keys and values to represent data. Every major cloud API returns data in JSON, so learning to work with it now means you'll be ready to handle real cloud responses later.

  • In the VS Code Explorer sidebar on the left, hover over the fleet-health-checker heading.
  • Click the New File icon (the page with a plus sign).
  • Type server_data.json as the filename and press Enter.
  • Paste the following JSON into the file. This represents the first three servers in your fleet:
{
    "fleet_name": "Production Fleet",
    "servers": [
        {
            "name": "web-server-01",
            "ip": "192.168.1.10",
            "status": "running",
            "cpu_usage": 45,
            "memory_usage": 62,
            "disk_usage": 71
        },
        {
            "name": "web-server-02",
            "ip": "192.168.1.11",
            "status": "running",
            "cpu_usage": 92,
            "memory_usage": 85,
            "disk_usage": 40
        },
        {
            "name": "db-server-01",
            "ip": "192.168.1.20",
            "status": "stopped",
            "cpu_usage": 0,
            "memory_usage": 0,
            "disk_usage": 88
        }
    ]
}

What does this data represent?

Each server object has six fields. name and ip identify the machine. status tells you if it's running or stopped.

The three usage metrics (cpu_usage, memory_usage, disk_usage) are percentages. Notice that web-server-02 has a CPU usage of 92 and db-server-01 is stopped. These are the kinds of problems your health checker will flag later.

  • Save server_data.json by pressing Ctrl+S.
  • Confirm the file was created by running this command in the terminal:
ls

You should see server_data.json listed in the output.

Don't see server_data.json?

  • Make sure you saved the file with Ctrl+S. A dot or circle on the file tab in VS Code means it has unsaved changes.
  • Check that your terminal is in the fleet-health-checker folder. Run cd fleet-health-checker if you're in the wrong directory.
  • Why isn't my server_data.json showing up in the terminal?
Write the health checker script

Now you'll write a Python script that opens the JSON file, reads its contents, and prints each server's details to the terminal.

  • In the Explorer sidebar, click the New File icon again.
  • Name the file health_checker.py and press Enter.
  • Paste the following code into health_checker.py:
# Import the json module to read JSON files
import json

# Initialize a variable to hold the server data
data = None

# Open the JSON file and load its contents
with open("server_data.json", "r") as file:
    data = json.load(file)

# Print the fleet name and total number of servers
print(f"Fleet: {data['fleet_name']}")
print(f"Total servers: {len(data['servers'])}")

# Loop through each server and display its details
for server in data["servers"]:
    print(f"{server['name']} | {server['ip']} | {server['status']}")

What does this code do?

  • import json loads Python's built-in JSON module, which knows how to convert JSON text into Python data structures (dictionaries and lists).
  • with open("server_data.json", "r") as file opens the file safely. The with keyword automatically closes the file when the block finishes.
  • json.load(file) reads the file and converts the JSON into a Python dictionary stored in data.
  • The for loop walks through each server in the data["servers"] list and prints its name, IP address, and status using an f-string.
  • Save health_checker.py with Ctrl+S.
  • Run the script by entering this command in the terminal:
python health_checker.py

You should see the fleet name, the total server count of 3, and each server's name, IP, and status printed on separate lines.

Seeing an error instead of server data?

  • If you see FileNotFoundError, make sure server_data.json is saved in the same folder as health_checker.py. Both files should be inside fleet-health-checker.
  • If you see json.decoder.JSONDecodeError, there is a typo in your JSON. Check for missing commas between objects, missing closing braces, or extra trailing commas after the last server entry.
  • If you see SyntaxError, double-check the f-strings in your Python code. The curly braces and quotes must match the code above exactly.
  • My script is crashing and I can't figure out why.

✔️ Awesome, I've got everything!

Great. Make sure you've saved health_checker.py before moving on.

ⓧ I'd like to double check the full code

Here is the full health_checker.py file. Compare it with yours and fix any differences:

import json

data = None

with open("server_data.json", "r") as file:
    data = json.load(file)

print(f"Fleet: {data['fleet_name']}")
print(f"Total servers: {len(data['servers'])}")

for server in data["servers"]:
    print(f"{server['name']} | {server['ip']} | {server['status']}")
Complete the server fleet

Your script works with three servers. A real fleet has more machines, so you'll add three more to give your health checker a fuller data set to analyze.

  • Switch back to server_data.json in VS Code.
  • Find the closing } of db-server-01 (the last server in the list). It's on line 22.
  • That } currently has no comma after it. Add a comma so the line reads }, instead.
  • Place your cursor on the empty line after }, and paste the following three servers:
        {
            "name": "api-server-01",
            "ip": "192.168.1.30",
            "status": "running",
            "cpu_usage": 55,
            "memory_usage": 70,
            "disk_usage": 95
        },
        {
            "name": "cache-server-01",
            "ip": "192.168.1.40",
            "status": "running",
            "cpu_usage": 22,
            "memory_usage": 35,
            "disk_usage": 50
        },
        {
            "name": "worker-server-01",
            "ip": "192.168.1.50",
            "status": "running",
            "cpu_usage": 78,
            "memory_usage": 91,
            "disk_usage": 60
        }

What's interesting about these new servers?

Each new server has a different health profile. api-server-01 has a disk usage of 95%, which is dangerously high. worker-server-01 has memory usage of 91%. These values will trigger health warnings when you build the check logic in the next step.

  • Save server_data.json with Ctrl+S.

Before you run the script again, how many servers do you expect to see in the output?

  • Run the script again by entering this command:
python health_checker.py

You should now see all six servers printed, with Total servers: 6 at the top. Each server shows its name, IP, and status separated by pipes.

Still seeing 3 servers, or getting a JSON error?

  • Make sure you saved server_data.json after adding the new servers. The script reads the file from disk each time it runs.
  • Check that you added a comma after the closing } of db-server-01. Without the comma, JSON treats the next { as a syntax error.
  • Make sure the last server (worker-server-01) does NOT have a trailing comma after its closing }. JSON does not allow a comma before ].
  • My JSON file has an error and I can't find the problem.

✔️ Awesome, I've got everything!

Great. Make sure both server_data.json and health_checker.py are saved before moving on.

ⓧ I'd like to double check the full code

You built server_data.json in two parts. Scroll up and confirm that your file starts with the first three servers (web-server-01, web-server-02, db-server-01) and ends with the three you just added (api-server-01, cache-server-01, worker-server-01), all inside the servers array.

Here is the full health_checker.py for reference:

import json

data = None

with open("server_data.json", "r") as file:
    data = json.load(file)

print(f"Fleet: {data['fleet_name']}")
print(f"Total servers: {len(data['servers'])}")

for server in data["servers"]:
    print(f"{server['name']} | {server['ip']} | {server['status']}")

Your script can read a JSON file and display every server in the fleet. Next up, you'll build the health check logic that flags servers with problems instead of just listing them.

Build the Health Check Logic

Your script can load server data and print names and IPs. That's a start, but a flat list doesn't tell you which servers actually need attention.

In this step, you'll define threshold values for CPU, memory, and disk usage. Then you'll write a function that checks each server against those thresholds and flags any that exceed them.

In this step, get ready to:
  • Define health thresholds and a function that checks each server against them.
  • Update the main loop to use the health check function and display results.
  • Extract the JSON loading code into its own reusable function.
Define health thresholds and the check function

Cloud engineers set threshold values to define what "healthy" means. A server using 45% CPU is fine. A server using 92% CPU needs attention.

  • In health_checker.py, select everything below import json (from data = None all the way to the last line) and delete it.
  • Add the following code below import json:
# Health thresholds for server metrics (in percent)
THRESHOLDS = {
    "cpu_usage": 80,
    "memory_usage": 80,
    "disk_usage": 90
}


def check_server_health(server):
    # Collect any issues found with this server
    issues = []

    # Check if the server is not running
    if server["status"] != "running":
        issues.append(f"Server is {server['status']}")

    # Check each metric against its threshold
    for metric in ["cpu_usage", "memory_usage", "disk_usage"]:
        if server[metric] > THRESHOLDS[metric]:
            issues.append(f"{metric} is {server[metric]}% (threshold: {THRESHOLDS[metric]}%)")

    # Return the result with status and any issues
    if issues:
        return {"name": server["name"], "status": "unhealthy", "issues": issues}
    else:
        return {"name": server["name"], "status": "healthy", "issues": []}

What does this code do?

THRESHOLDS is a dictionary that stores the maximum acceptable value for each metric. Any server that exceeds these numbers gets flagged.

check_server_health takes one server dictionary, checks its status and each metric against the thresholds, and collects any problems into an issues list. It returns a result dictionary with the server's name, a status of "healthy" or "unhealthy", and any issues found.

Use the health check function in your main loop

The function is defined, but nothing is calling it yet. You need to load the server data and loop through each server, passing it to check_server_health.

  • Add the following code at the bottom of health_checker.py, below the check_server_health function:
# Load the server data from the JSON file
data = None

with open("server_data.json", "r") as file:
    data = json.load(file)

# Check each server and collect results
results = []
for server in data["servers"]:
    result = check_server_health(server)
    results.append(result)

# Print the health check results
for result in results:
    print(f"{result['name']}: {result['status']}")
    for issue in result["issues"]:
        print(f"  - {issue}")

What's happening here?

This code opens server_data.json the same way your old script did. The difference is what happens next.

Instead of printing raw data, the loop passes each server to check_server_health and collects the results. Then a second loop prints each server's name, health status, and any issues found underneath.

Before you run this, take a look at the data in server_data.json. How many servers do you think will be marked unhealthy, and why?

  • Save health_checker.py.
  • Run your script by running this command in the terminal:
python health_checker.py

You should see each server listed as either healthy or unhealthy, with specific issues listed under the unhealthy ones.

What should I see?

Four servers are flagged as unhealthy. web-server-02 has high CPU and memory. db-server-01 is stopped. api-server-01 has high disk usage. worker-server-01 has high memory.

Each issue shows the metric name, its current value, and the threshold it exceeded (e.g., cpu_usage is 92% (threshold: 80%)).

Not seeing any output?

  • Make sure you deleted the old code (the data = None line, the with open block, and the old for loop) before adding the new code. Having both old and new code can cause unexpected behavior.
  • Check that the new main body code sits outside and below the check_server_health function (not indented inside it). Python uses indentation to determine what belongs to a function.
  • Confirm that server_data.json is in the same folder as health_checker.py.

Help me debug my health check script

Extract the data loading into its own function

Your script works, but the JSON loading code is sitting loose in the main body. Wrapping it in a function makes it reusable and easier to add error handling to later.

  • In health_checker.py, find these lines below the check_server_health function:
data = None

with open("server_data.json", "r") as file:
    data = json.load(file)
  • Delete those four lines and replace them with a single function call:
data = load_server_data("server_data.json")
  • Now add the load_server_data function definition between the THRESHOLDS dictionary and the check_server_health function:
def load_server_data(filepath):
    # Open and parse the JSON file
    with open(filepath, "r") as file:
        data = json.load(file)
    return data

Why extract this into a function?

load_server_data takes a filepath parameter instead of hardcoding the filename. This means you could reuse it to load data from different JSON files without changing the function.

It also gives you a single place to add error handling later. If the file is missing or contains bad data, you only need to fix one function instead of hunting through your script.

  • Save health_checker.py.
  • Run the script again to confirm everything still works:
python health_checker.py

You should see the exact same healthy/unhealthy output as before. The behavior hasn't changed. You've just organized the code so it's easier to build on.

Seeing a NameError?

  • If you see NameError: name 'load_server_data' is not defined, make sure the load_server_data function is defined above the line that calls it. Python reads your file top to bottom, so the function must come before its first use.
  • Check that the function body is indented under the def line. The with open and return lines must be inside the function, not at the top level.

Help me fix my load_server_data function

✔️ Awesome, I've got everything!

Great. Double check you've saved health_checker.py and your output shows healthy and unhealthy servers with specific issues listed.

ⓧ I'd like to double check the full code

The full file is built from three sections. Scroll up and confirm each one is present in this order:

  • import json at the top, followed by the THRESHOLDS dictionary.
  • The load_server_data function, then the check_server_health function.
  • The main body: data = load_server_data("server_data.json"), the loop that calls check_server_health on each server, and the loop that prints results.

Your script now analyzes server health instead of just listing raw data. Next up, you'll turn that output into a formatted report and save it to a file.

Generate a Formatted Health Report

Your health checker from the previous step correctly identifies which servers have problems and which are healthy. But scrolling through plain text output gets unwieldy fast when you manage dozens of servers.

A production-ready script produces a structured report that is easy to scan at a glance. In this step, you'll format the output into a professional report and save it to a file.

In this step, get ready to:
  • Build a generate_report function that formats results into a structured report with headers and labels.
  • Write a save_report function to write the report to a text file.
  • Test the script with incomplete data to see how it handles missing fields.
Add the generate_report function

Your script currently prints each server on its own line. The generate_report function will replace that with a formatted string that includes a header, status labels, and a summary.

  • In health_checker.py, find this line at the top of the file:
import json
  • Add from datetime import datetime on the line directly below it, so the top of your file looks like this:
import json
from datetime import datetime
  • Below your check_server_health function, add this new function:
def generate_report(fleet_name, results):
    # Build the report header with fleet name and timestamp
    report_lines = []
    report_lines.append("=" * 60)
    report_lines.append(f"  SERVER FLEET HEALTH REPORT")
    report_lines.append(f"  Fleet: {fleet_name}")
    report_lines.append(f"  Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    report_lines.append("=" * 60)
    healthy_count = 0
    unhealthy_count = 0
    # Label each server and count healthy vs unhealthy
    for result in results:
        report_lines.append("")
        if result["status"] == "healthy":
            report_lines.append(f"  [HEALTHY] {result['name']}")
            healthy_count += 1
        else:
            report_lines.append(f"  [UNHEALTHY] {result['name']}")
            for issue in result["issues"]:
                report_lines.append(f"    - {issue}")
            unhealthy_count += 1
    # Add a summary section with total counts
    report_lines.append("")
    report_lines.append("-" * 60)
    report_lines.append(f"  SUMMARY")
    report_lines.append(f"  Total servers: {len(results)}")
    report_lines.append(f"  Healthy: {healthy_count}")
    report_lines.append(f"  Unhealthy: {unhealthy_count}")
    report_lines.append("-" * 60)
    return "\n".join(report_lines)

What does this function do?

The function builds a report line by line, collecting each line into a list called report_lines.

  • The header section uses datetime.now().strftime() to stamp the report with the current date and time.
  • The loop labels each server [HEALTHY] or [UNHEALTHY] and lists specific issues under unhealthy ones.
  • The summary at the bottom counts healthy and unhealthy servers so you can see fleet health at a glance.
  • At the end, "\n".join(report_lines) joins every line with a newline character to create the final report string.

Now you need to wire this function into the main body of your script so it actually runs.

  • Scroll to the bottom of health_checker.py and find these four lines:
for result in results:
    print(f"{result['name']}: {result['status']}")
    for issue in result["issues"]:
        print(f"  - {issue}")
  • Delete those four lines and replace them with the following:
# Generate and print the formatted report
report = generate_report(data["fleet_name"], results)
print(report)
  • Save health_checker.py.
  • Run the script to see your formatted report by running this command:
python health_checker.py

You should see a report with a header showing the fleet name and timestamp, each server labeled [HEALTHY] or [UNHEALTHY], issues listed under unhealthy servers, and a summary section at the bottom.

Seeing a NameError or no output?

  • If you see NameError: name 'generate_report' is not defined, make sure the function is defined above the main body code that calls it. Python reads top to bottom.
  • If you see NameError: name 'datetime' is not defined, check that the from datetime import datetime line is at the very top of your file, below import json.
  • If the report prints but looks jumbled, make sure you deleted the old for result in results: print loop and replaced it with the two new lines.

Still stuck? Help me debug why my generate_report function is not working. I added the function and updated the main body but the output is wrong.

Save the report to a file

The report shows up in your terminal, but it disappears once you close the window. A save_report function lets you write it to a file you can share or archive.

  • In health_checker.py, add this function below your generate_report function:
def save_report(report, filename):
    # Write the report string to a file
    with open(filename, "w") as file:
        file.write(report)
    print(f"Report saved to {filename}")

What does this function do?

The with open(filename, "w") pattern opens a file for writing. The "w" mode creates the file if it does not exist, or overwrites it if it does. After writing, the function prints a confirmation message so you know the save succeeded.

  • Scroll to the bottom of the file and find this line:
print(report)
  • Add this line directly below print(report):
save_report(report, "health_report.txt")
  • Save health_checker.py.
  • Run the script again by running this command:
python health_checker.py

You should see the same formatted report as before, plus a new line at the bottom saying Report saved to health_report.txt.

  • Confirm the file was created by running:
dir

You should see health_report.txt listed alongside health_checker.py and server_data.json in your project folder.

Don't see health_report.txt?

  • Make sure the save_report function is defined above the main body code that calls it.
  • Check that the save_report(report, "health_report.txt") line is not indented inside a function. It should be at the same indent level as print(report).

Need a hand? Help me figure out why health_report.txt was not created when I ran my script.

✔️ Awesome, I've got everything!

Great. Double check you have saved health_checker.py.

ⓧ I'd like to double check the full code

Your health_checker.py is getting long, so scroll through and confirm each of these sections is present in order:

  • Two import lines at the top: import json and from datetime import datetime.
  • The THRESHOLDS dictionary.
  • The load_server_data function.
  • The check_server_health function.
  • The generate_report function.
  • The save_report function.
  • The main body calling load_server_data, looping through servers, calling generate_report, printing the report, and calling save_report.
Test with incomplete server data

Your script handles healthy and unhealthy servers well. But what happens when the data itself is incomplete?

  • Open server_data.json in VS Code.
  • Find the closing } of the last server entry (worker-server-01).
  • Add a comma after that closing }, then add this new server entry below it:
        {
            "name": "test-server-01",
            "ip": "192.168.1.99"
        }

Why only two fields?

This server entry is deliberately incomplete. It only has name and ip. It is missing status, cpu_usage, memory_usage, and disk_usage. In the real world, cloud APIs sometimes return incomplete data. This simulates that scenario.

  • Save server_data.json.

Before you run the script, what do you think will happen when it hits this server with missing fields?

  • Run the script again by running this command:
python health_checker.py

The script crashes with a KeyError. Your check_server_health function tried to access server["status"], but that field does not exist on the incomplete server entry.

Why did this crash?

When you access a dictionary key that does not exist, Python raises a KeyError and stops the entire script. The function assumed every server would have all six fields. That assumption just broke.

This is exactly the kind of problem that happens in production. You will fix it in the next step.

You now have a formatted health report that saves to a file. But that crash exposed a serious gap. Your script assumes every server has all the right fields, and it falls apart when that assumption is wrong. In the next step, you'll make the script bulletproof so it handles missing data gracefully.

Make Your Script Bulletproof

Your script generated a professional health report in the last step. But it also crashed the moment it hit a server with missing data. That's the exact problem cloud engineers face every day.

Cloud APIs sometimes return incomplete records. Files can go missing or become corrupted. A production-ready script handles these problems gracefully instead of crashing. In this step, you'll add error handling so your script keeps running no matter what bad data it encounters.

In this step, get ready to:
  • Add field validation so missing data produces an error report instead of a crash.
  • Handle missing files and bad JSON with clean error messages.
  • Organize your script with a main() function.
Add field validation and error reporting

The crash from the last step happened because check_server_health assumed every server has all six fields. When test-server-01 was missing status, Python threw a KeyError. The fix is to check that all required fields exist before trying to use them.

  • In health_checker.py, find the check_server_health function and locate the issues = [] line.
  • Add the following field validation code directly below issues = [] and above the existing if server["status"] check:
    required_fields = ["name", "ip", "status", "cpu_usage", "memory_usage", "disk_usage"]
    for field in required_fields:
        if field not in server:
            return {
                "name": server.get("name", "UNKNOWN"),
                "status": "error",
                "issues": [f"Missing field: {field}"]
            }

What does this code do?

Before checking any metrics, this code loops through a list of every field the function needs. If any field is missing from the server dictionary, it immediately returns an error result instead of continuing and crashing.

server.get("name", "UNKNOWN") is a safe way to get the server's name. If even the name field is missing, it returns "UNKNOWN" instead of crashing.

Now check_server_health can return results with a status of "error". But generate_report doesn't know what to do with that status yet. You need to update three spots inside generate_report to handle errors.

  • In the generate_report function, find these two counter lines:
    healthy_count = 0
    unhealthy_count = 0
  • Add a third counter directly below unhealthy_count = 0 so the section looks like this:
    healthy_count = 0
    unhealthy_count = 0
    error_count = 0
  • Next, find the for result in results: loop inside the same function. It currently looks like this:
    for result in results:
        report_lines.append("")
        if result["status"] == "healthy":
            report_lines.append(f"  [HEALTHY] {result['name']}")
            healthy_count += 1
        else:
            report_lines.append(f"  [UNHEALTHY] {result['name']}")
            for issue in result["issues"]:
                report_lines.append(f"    - {issue}")
            unhealthy_count += 1
  • Replace that entire loop with this updated version that handles the "error" status:
    for result in results:
        report_lines.append("")
        if result["status"] == "healthy":
            report_lines.append(f"  [HEALTHY] {result['name']}")
            healthy_count += 1
        elif result["status"] == "unhealthy":
            report_lines.append(f"  [UNHEALTHY] {result['name']}")
            for issue in result["issues"]:
                report_lines.append(f"    - {issue}")
            unhealthy_count += 1
        else:
            report_lines.append(f"  [ERROR] {result['name']}")
            for issue in result["issues"]:
                report_lines.append(f"    - {issue}")
            error_count += 1

What changed in this loop?

The old loop used if/else, which treated everything that wasn't healthy as unhealthy. The new loop uses if/elif/else to distinguish between three statuses: healthy, unhealthy, and error.

Servers with missing data now get their own [ERROR] label instead of being lumped in with unhealthy servers that have real metric problems.

  • Finally, find the summary section at the bottom of generate_report. Locate these two lines:
    report_lines.append(f"  Healthy: {healthy_count}")
    report_lines.append(f"  Unhealthy: {unhealthy_count}")
  • Add a new line directly below Unhealthy: {unhealthy_count} so the section looks like this:
    report_lines.append(f"  Healthy: {healthy_count}")
    report_lines.append(f"  Unhealthy: {unhealthy_count}")
    report_lines.append(f"  Errors: {error_count}")
  • Save health_checker.py.

Before you run this, think back to what happened last time. The script crashed with a KeyError on test-server-01. What do you think will happen this time?

  • Run your script to see the result:
python health_checker.py

Instead of a crash, you should see a full report. test-server-01 now appears with an [ERROR] label and a message about the missing field. The summary at the bottom shows Errors: 1.

Still seeing a KeyError crash?

  • Make sure you placed the required_fields validation block above the if server["status"] != "running": line. The validation needs to run first.
  • Check that the indentation of the new code matches the existing code inside check_server_health. Python uses indentation to determine what code belongs inside a function.
  • Confirm you saved the file before running the script.

Still stuck? Help me fix a KeyError crash in my check_server_health function.

Handle missing files and bad JSON

Your script now handles incomplete server data. But what if the entire server_data.json file is missing or contains corrupted data? Right now, Python would throw a traceback that means nothing to someone who just wants their report.

  • In health_checker.py, find the load_server_data function. It currently looks like this:
def load_server_data(filepath):
    with open(filepath, "r") as file:
        data = json.load(file)
    return data
  • Replace the entire function with this version that wraps the file operations in a try/except block:
def load_server_data(filepath):
    try:
        with open(filepath, "r") as file:
            data = json.load(file)
        return data
    except FileNotFoundError:
        print(f"Error: File '{filepath}' not found.")
        return None
    except json.JSONDecodeError:
        print(f"Error: File '{filepath}' contains invalid JSON.")
        return None

What does try/except do?

A try/except block tells Python to attempt the code inside try. If a specific error occurs, Python jumps to the matching except block instead of crashing.

  • FileNotFoundError catches the case where the JSON file doesn't exist at all.
  • json.JSONDecodeError catches the case where the file exists but contains invalid JSON (corrupted or misformatted data).
  • Returning None signals to the rest of the script that something went wrong, so it can exit cleanly.

Now the function returns None when something goes wrong. But the code at the bottom of your script doesn't check for that yet. It tries to use data["servers"] regardless, which would crash if data is None.

  • Scroll to the bottom of your file and find this line:
data = load_server_data("server_data.json")
  • Add a check directly below it so the script exits early if the file couldn't be loaded. The bottom of your file should now look like this:
data = load_server_data("server_data.json")
if data is None:
    exit()

results = []
for server in data["servers"]:
    result = check_server_health(server)
    results.append(result)

report = generate_report(data["fleet_name"], results)
print(report)
save_report(report, "health_report.txt")

What does this check do?

if data is None: catches the case where load_server_data returned None because the file was missing or invalid. The exit() call stops the script immediately so it doesn't try to process data that doesn't exist.

  • Save health_checker.py.

Time to test the file-not-found handling. If you renamed server_data.json to something else, what do you think the script will print instead of a traceback?

  • Rename your data file temporarily by running this command:
ren server_data.json server_data_backup.json
  • Run the script with the file missing:
python health_checker.py

Instead of a long traceback, you should see a single clean message: Error: File 'server_data.json' not found.

  • Rename the file back by running this command:
ren server_data_backup.json server_data.json

Still seeing a traceback?

  • Make sure you wrapped the with open() and json.load() calls inside the try: block. If they're outside it, Python doesn't catch their errors.
  • Check that except FileNotFoundError: is spelled exactly, including the capital letters. Python error names are case-sensitive.

Need help? Help me debug my try/except error handling in load_server_data.

Wrap your code in a main function

The last improvement is organizational. Right now, the code that runs your script sits bare at the bottom of the file. The standard Python pattern wraps this code in a function called main(). This makes it clear where your script's entry point is.

  • Find the bare code at the bottom of your file. It starts with data = load_server_data("server_data.json") and ends with save_report(report, "health_report.txt").
  • Replace that entire section (everything from data = load_server_data to the end of the file) with this main() function and its call:
def main():
    data = load_server_data("server_data.json")
    if data is None:
        return

    results = []
    for server in data["servers"]:
        result = check_server_health(server)
        results.append(result)

    report = generate_report(data["fleet_name"], results)
    print(report)
    save_report(report, "health_report.txt")


main()

What changed here?

The logic is identical to what you had before. The only change is that it's now indented inside a def main(): function definition, and main() is called at the very bottom of the file to kick everything off.

Notice that exit() changed to return. Inside a function, return exits the function cleanly. Since main() is the last thing that runs, exiting it ends the script.

  • Save health_checker.py.

Your server_data.json still has the incomplete test-server-01 entry in it. What do you expect to see in the report for that server?

  • Run the script one final time to verify everything works:
python health_checker.py

You should see the full formatted report with test-server-01 showing as [ERROR] with a missing field message, and the summary counting Errors: 1. The script also confirms the report was saved to health_report.txt.

Seeing a different error?

  • If you see an IndentationError, make sure all the code inside main() is indented by four spaces. The main() call at the very bottom should not be indented.
  • Make sure you deleted the old bare code before pasting the main() function. Having both the old and new code at the bottom will cause duplicate runs or errors.

Still stuck? Help me debug my health_checker.py main function.

✔️ Awesome, I've got everything!

Great. Make sure you've saved health_checker.py.

ⓧ I'd like to double check the full code

Scroll through your health_checker.py and confirm each section is present in this order:

  • Imports: import json and from datetime import datetime at the top.
  • THRESHOLDS: Dictionary with cpu_usage: 80, memory_usage: 80, disk_usage: 90.
  • load_server_data: Uses try/except to catch FileNotFoundError and json.JSONDecodeError. Returns None on error.
  • check_server_health: Starts with required_fields validation before checking thresholds. Returns "error" status for missing fields.
  • generate_report: Handles "healthy", "unhealthy", and "error" statuses. Has error_count in the summary.
  • save_report: Unchanged from the previous step.
  • main(): Wraps the script logic. Checks if data is None: return for early exit.
  • main() call: main() at the very bottom, not indented.

Secret mission

Add Severity Levels and Sort by Urgency

Your health checker flags problems, but it treats every issue the same way. A stopped server gets the same [UNHEALTHY] label as one with slightly high CPU. Real monitoring tools prioritize alerts so you fix the most urgent problems first. Can you add severity levels and sort the report by urgency?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally with free tools, so there are no ongoing costs.

Resources you used:

  • fleet-health-checker project folder (contains health_checker.py, server_data.json, and health_report.txt).
  • Python (installed from the Microsoft Store).
  • VS Code with the Python extension.

Keep everything running

No action needed. Choose this if you plan to keep building Python scripts on your machine.

  • Your fleet-health-checker folder stays on your Desktop for reference.
  • Python and VS Code are free and useful for future cloud engineering projects.

Pause - I'll come back to this later

This project has no running processes or cloud resources to pause. Close VS Code if you want to free up memory.

  • Your project files stay on your Desktop until you decide to delete them.
  • Python and VS Code remain installed and ready for when you return.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow these steps to clean up everything.

Delete the project folder:

  • Open a new PowerShell window.
  • Delete the project folder by running these commands:
cd ~/Desktop
Remove-Item -Recurse -Force fleet-health-checker
  • Confirm the folder is gone by running:
ls ~/Desktop

You should no longer see fleet-health-checker in the output.

Uninstall Python (optional):

  • Open Settings on your Windows machine.
  • Click Apps.
  • Search for Python, click the three dots next to it, and click Uninstall.

Uninstall VS Code (optional):

  • In the same Apps page in Settings, search for Visual Studio Code.
  • Click the three dots next to it and click Uninstall.

Keep Python and VS Code for future projects

Python and VS Code are free tools you will use in nearly every cloud engineering project. Consider keeping them installed unless you have a specific reason to remove them.

Nice Work!

Nice Work!

You just built a server fleet health checker from scratch using Python. You can now run a single command and instantly see which servers need attention, why they are flagging, and how your fleet is doing overall.

You've learned how to:

  • Load and parse JSON data using Python's standard library, the same format used by every major cloud API.
  • Build functions that analyze server metrics against configurable thresholds and generate a formatted health report saved to a file.
  • Add error handling to gracefully manage missing files and incomplete data instead of crashing.
  • Secret Mission: Added severity levels (CRITICAL, WARNING, OK) and sorted the report so the most urgent servers appear first.

Ready to quiz yourself?