DEV Community

Cover image for Cloud & DevOps Series: Week 2 - Writing Better Scripts, Tuning Storage, and Reading Network Traffic
Samuel Ojo
Samuel Ojo

Posted on

Cloud & DevOps Series: Week 2 - Writing Better Scripts, Tuning Storage, and Reading Network Traffic

Following up on our core Git pipeline setup from last week, Week 2 shifts focus from simple, one-line terminal commands to building real, resilient automation scripts. While I was setting things up, my own automated check script actually triggered an alert about a hidden storage issue on my virtual machine. It turned what should have been a simple task into a fantastic real-world troubleshooting exercise.

Getting a Better Terminal
During Week 1, I quickly realized the default VMware console window can slow me down and it doesn't let me paste text easily and typing long commands by hand costs too much time. To create a much smoother workflow, I installed an SSH server on my Ubuntu VM so I could connect securely using PuTTY from my Windows desktop:

sudo apt install -y openssh-server
sudo systemctl enable --now ssh
ip -4 a
Enter fullscreen mode Exit fullscreen mode

Use code with caution.

Once I saved the VM's IP address inside a PuTTY session profile, managing the terminal became effortless. Now, a simple right-click pastes text and highlighting text copies it instantly. Connecting this way is identical to how remote servers are managed in a real DevOps job, keeping you closer to how production environments actually function.

Writing Three Better Scripts

This week, I built three distinct utility scripts. To make sure they are reliable and handle errors properly, all three use set -euo pipefail at the top (which instructs the script to stop immediately if a command fails, a variable is undefined, or a pipeline breaks). They also check for proper input parameters and exit with a clear error status code if something is wrong.

1. The Disk Check Utility (disk_check.sh)

#!/usr/bin/env bash
set -euo pipefail

threshold="${1:-80}"
usage=$(df / --output=pcent | tail -1 | tr -dc '0-9')

echo "Root disk usage: ${usage}%"
if (( usage >= threshold )); then
  echo "WARNING: usage is at or above ${threshold}%" >&2
  exit 1
fi
echo "OK: below ${threshold}%"
Enter fullscreen mode Exit fullscreen mode

The syntax ${1:-80} provides a smart fallback option: it uses the first number you type after the script, or defaults to 80% if you don't provide one. To test it, I ran the script with a threshold of 1 to intentionally force the warning message and confirmed that it threw an exit code of 1. Checking exit codes is exactly how automation systems and CI/CD pipelines know a task failed!

The syntax  raw `${1:-80}` endraw  output

2. Multi-File Log Analytics Parser

I wrote a for loop that reads through multiple log files one by one. Instead of crashing if a file doesn't exist, the script safely catches the missing file, prints a helpful message, and continues checking the rest of the files for ERROR and INFO trends.

3. Timestamped Compressing Backups

This utility takes a target source folder, packages it cleanly into a compressed .tar.gz archive labeled with the exact date and time, and completely refuses to run if the requested folder doesn't exist.

Enforcing Quality with ShellCheck

Before committing my scripts to GitHub, I ran them through a linting tool to verify my code quality:

sudo apt install -y shellcheck
shellcheck scripts/*.sh
Enter fullscreen mode Exit fullscreen mode

If shellcheck returns nothing, your code is clean and structured properly. Making this a quick habit prevents messy errors from slipping through to production environments.

The Problem My Script Uncovered

When I ran my new disk_check.sh script on my machine, it immediately warned me that my root disk was sitting at 85% capacity. For a system meant to run massive modern DevOps utilities like Terraform, Docker containers, and Kubernetes clusters, running out of storage is a disaster waiting to happen.

Standard cleanup commands didn't help much:

sudo du -xh / --max-depth=1 2>/dev/null | sort -h | tail -8
sudo apt autoremove -y && sudo apt clean
sudo journalctl --vacuum-size=100M
Enter fullscreen mode Exit fullscreen mode

Clearing system logs and loose software caches only brought usage down from 85% to 76%. Because there wasn't any data left to safely delete, I turned my attention to the physical storage structure itself:

lsblk
sudo vgs
Enter fullscreen mode Exit fullscreen mode

The raw disk layout (sda) was provisioned at 20 GB, yet the logical volume holding my primary filesystem was locked to a strict 10 GB limit. The Volume Group status (vgs) showed that 8.22 GB of capacity was sitting unallocated—left completely untouched by the default automated Ubuntu OS installer.

lsblk and vgs output showing unallocated space

To fix this with zero downtime and without deleting a single file, I extended the logical volume to absorb that lost space:

sudo lvextend -r -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
df -h /
Enter fullscreen mode Exit fullscreen mode

The -r flag expands the active filesystem in the exact same step as the volume extension. The target space immediately grew from 10 GB to 18 GB, and my active disk utilization plummeted to a very healthy 49% without a single reboot.

df output after extending the logical volume

Safety Tip: I generated a full virtual machine snapshot before running this. While runtime scaling with Logical Volume Management (LVM) is common practice, a single typo in a device path can corrupt a filesystem, so a safety net is always a good idea.

Reading Network Telemetry

With my systems storage running smoothly, I ran a series of diagnostic commands to map out how my machine handles virtual networking:

ip a                              # My address: 192.168.93.132/24
ip route                          # Default gateway: 192.168.93.2
ping -c 3 8.8.8.8                 # 0% loss, roughly 17 ms average speed
dig github.com +short             # DNS translation: 20.26.156.215
curl -I https://github.com        # HTTP/2 200 OK Response
sudo ss -tulpn                    # Auditing which ports are open and listening
Enter fullscreen mode Exit fullscreen mode

Network commands run on the Ubuntu VM

What I Learned From the Networking Logs:

• Subnet Boundaries: The /24 mask means 24 bits are dedicated entirely to specifying the network id, leaving 8 bits open for host assignments. This translates to an available range of 256 local addresses.
• Gateway Egress: The line default via 192.168.93.2 indicates the primary router path all traffic must take when traveling out to the public internet.
• Reading Response Headers: Passing -I to curl pulls only the metadata headers. It returned an HTTP 200 OK code, and checking the data origin points proved it routed to GitHub via an Azure server instance sitting inside the UK South data center region.
• Port Visibility: Running ss -tulpn isolates hidden application pathways. For example, binding SSH (sshd) to 0.0.0.0:22 means it can accept connections from the outside network, while binding a printing service to 127.0.0.1 locks it safely inside the machine. Understanding these interface boundaries translates directly to configuring fine-grained cloud Network Security Groups (NSGs).

Subnet Geometry (Calculated by Hand)

To prepare for scaling out infrastructures in the public cloud, I sat down and mapped out standard address blocks manually:
• A /16 Virtual Network (VNet) layout provides 2¹⁶ = 65,536 unique network host addresses.
• A common /24 subnet yields 256 addresses. In public cloud environments like Azure, 5 endpoints are natively reserved for platform management (gateway, broadcast, and internal cloud DNS paths), leaving 251 addresses wide open for server workloads.
• A single /16 deployment scope holds exactly (2^{(24-16)} = 256) independent /24 subnets.

I have updated the repository blueprint files with complete breakdowns under docs/networking-notes.md, matching how these local Linux networking concepts align directly with cloud VNets, public/private subnets, custom DNS zones, and firewall controls.

What I Learned This Week

1. Resilient Automation: Writing scripts with explicit error checking parameters, input validation, and proper exit tracking is the secret to building stable infrastructure tools.
2. Telemetry Highlights: Operating on a real machine exposes authentic hardware and configuration limitations that textbook tutorials completely gloss over.
3. Tiered Systems Logic: When application-level cleanup tasks fail to give you your storage space back, drop a layer deeper into the block storage layout configuration to find the bottleneck.
4. Network Translation: Figuring out what ss, ip route, and dig are telling you is the exact knowledge base required to configure complex cloud architectures later.

What's Next?

Week 3: Infrastructure as Code (IaC) via Terraform. We are officially moving out of manual machine configurations and stepping into the cloud. Next week, we will write code to spin up live Azure infrastructure, configure a remote state engine inside secured Azure Storage Accounts, and build out an isolated Virtual Network framework complete with multiple custom subnets.

Repo: link

See you on the next one...

Top comments (0)