DEV Community

Cover image for Day 55: The Volume Hides nginx's Log Links, and Every Azure Subnet Gives Up Five Addresses
Nnamdi Felix Ibe
Nnamdi Felix Ibe

Posted on AI-assisted

Day 55: The Volume Hides nginx's Log Links, and Every Azure Subnet Gives Up Five Addresses

Day 55 of DevOps, Day 5 of Azure. In both tasks today, the interesting part was something that was already there before I wrote a line.

The nginx image had already decided where its logs go, and the sidecar task only works because a volume quietly overrides that decision. And every Azure subnet has five addresses taken before anything is deployed into it.

One Kubernetes task, one Azure task. Run a sidecar container that reads nginx's logs, then create a small virtual network with a fixed address range. The tasks come from the KodeKloud Engineer platform.

A sidecar that reads nginx's logs

volumes:
- name: shared-logs
  emptyDir: {}
containers:
- name: nginx-container
  image: nginx:latest
  volumeMounts:
  - name: shared-logs
    mountPath: /var/log/nginx
- name: sidecar-container
  image: ubuntu:latest
  command: ["sh", "-c", "while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
  volumeMounts:
  - name: shared-logs
    mountPath: /var/log/nginx
Enter fullscreen mode Exit fullscreen mode

Two containers share nginx's log directory through an emptyDir, and the sidecar prints both log files every 30 seconds. Make a request, and it shows up in the sidecar's output:

kubectl exec webserver -c nginx-container -- curl localhost
kubectl logs webserver -c sidecar-container
Enter fullscreen mode Exit fullscreen mode

The task is short. The reason it works is the interesting part.

Why it works at all

The official nginx image does not keep log files. Its Dockerfile links them to the container's output streams:

# forward request and error logs to docker log collector
    && ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log \
Enter fullscreen mode Exit fullscreen mode

nginx's packaged configuration writes to exactly those two paths, so in a plain nginx container, every log line goes straight to stdout or stderr. There is nothing on disk for a sidecar to read.

Mounting the volume at /var/log/nginx changes that. An emptyDir starts empty, and a mount hides whatever the image had at that path. The Kubernetes docs, describing a ConfigMap volume, say the mount makes files the image had in that directory inaccessible, and Docker's docs describe pre-existing files as "obscured by the mount". With the links hidden, nginx creates ordinary files on the shared volume, and those are what the sidecar reads.

So the volume does two jobs. It gives the sidecar something to read, and it takes nginx's logs away from nginx's own output. kubectl logs shows what a container writes to stdout and stderr, so after this change, the nginx container's own log should show no requests at all. They only come back through the sidecar.

Related trap: kubectl logs webserver with no -c shows only one container. kubectl uses the pod's default-container annotation if there is one, and otherwise the first container, which here is nginx, not the sidecar. In a pod with more than one container, always pass -c.

The pattern, and its cost

Kubernetes' logging docs describe this streaming sidecar pattern, and they are candid about the price: "writing logs to a file and then streaming them to stdout can double how much storage you need on the node." For an application that writes to a single file, the same docs recommend setting /dev/stdout as the destination instead, which is exactly what the nginx image already did.

So this lab builds the pattern around an application that did not need it. It is still worth learning, because plenty of software can only write to files, and a sidecar is how you bring that output back to where the cluster collects logs. My version also re-prints both files in full every 30 seconds, so every line repeats on every pass. A real shipper follows the file instead.

Kubernetes also has native sidecars now, stable since v1.33: an init container with restartPolicy: Always, which starts before the app container, keeps running, and is stopped after it. The two regular containers I used are still valid, and the docs describe that form as appropriate when you do not need to control which container starts or stops first.

Five addresses you never get

The Azure task was a VNet, nautilus-vnet, with a fixed range of 192.168.0.0/24. Yesterday's was 10.0.0.0/16. That is 256 addresses against 65,536, and the smaller number shrinks further once you carve it into subnets.

Azure reserves five addresses in every subnet:

Address Use
.0 Network address
.1 Default gateway
.2, .3 Mapping the Azure DNS IP addresses into the VNet
last Network broadcast address

A /24 subnet leaves 251 usable addresses. The smallest subnet Azure supports is a /29, which leaves 3.

AWS reserves the same five positions, with slightly different jobs: .1 is the VPC router, .2 the DNS server, .3 is held for future use, and the last address is reserved because AWS does not support broadcast. The arithmetic carries over from the AWS track. The size limits do not, since AWS subnets stop at /28.

What a /24 cannot hold

Some Azure services need their own subnet, with a fixed name and a minimum size:

Subnet Size
GatewaySubnet /27 or larger for every VPN gateway SKU except Basic
AzureFirewallSubnet /26
AzureBastionSubnet /26 or larger

Two /26 subnets take half of a /24 before a single VM exists. A VNet that might one day need a firewall and Bastion should not start this small. My notes had the gateway subnet as "/29, /27 recommended"; Microsoft's current page makes /27 the minimum for everything except the Basic SKU.

One more correction to my notes. I called the three RFC 1918 blocks the ranges Azure accepts. They are the ranges Microsoft recommends. Azure also treats the RFC 6598 range, 100.64.0.0/10, as private, and it allows public ranges too. Those are a bad idea for a practical reason: Azure routes every range in a VNet's address space inside the VNet, so the real internet hosts at those addresses become unreachable from it.

What was there before you

The nginx image made a choice about logs before I wrote any YAML, and my volume overrode it without saying so. Azure took five addresses out of every subnet before I deployed anything. Neither is hidden. Both are in the documentation, and both change what your own configuration actually does.

So here is the Day 55 question. What does the base image you use most already do that your configuration quietly depends on, or quietly undoes?

Day 55 down. Forty-five to go.

Top comments (8)

Collapse
 
merbayerp profile image
Mustafa ERBAY •

Really good catch on the nginx log symlinks. The sidecar example looks trivial until you realize the volume mount is actually changing nginx’s logging behavior, not merely sharing its logs.

That distinction matters a lot in production: the base image was already doing the Kubernetes-friendly thing by sending logs to stdout/stderr, and mounting /var/log/nginx quietly undoes it. At that point the sidecar isn’t just observing the application anymore — it becomes part of the logging pipeline.

The Azure subnet section is a good reminder too. People often calculate CIDR capacity as if every address belongs to the workload, then discover later that platform reservations and dedicated service subnets make the original address plan painfully small.

Nice Day 55. The recurring theme here is probably the most useful one: always inspect what the platform or image already does before adding another layer on top of it. 👍

Collapse
 
ndcodes profile image
Nnamdi Felix Ibe •

Thank you, and "the sidecar becomes part of the logging pipeline", I think it's a better way to put it.

Which gives it a failure mode it never had as an observer. If that sidecar crashloops, nginx carries on serving traffic perfectly, the pod sits there running at 1/2 ready, and the logs simply stop. No error, nothing a user would notice. And because the volume hid the stdout symlinks, there's no fallback path either. They aren't going anywhere else.

So the mount converts an observer into a dependency, and the dependency fails quietly. Another one for the pile: the thing that broke is fine, the thing that reports is gone.

And your CIDR point is the one I'd put on a wall for anyone starting an Azure estate. The reservations are easy to find, and nobody looks until they've run out of room.

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

ATLOCK v5 very close to be released.

Collapse
 
ndcodes profile image
Nnamdi Felix Ibe •

Good luck with the release! 👊

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

yeah thanks.
are you interested in ATLOCK? just curious to know.

Thread Thread
 
ndcodes profile image
Nnamdi Felix Ibe •

Honestly, I'm a Linux and cloud person, so a Windows desktop app isn't really my stack. I'd make a poor tester for it.

But I'm curious what it actually does. I've seen you mention ATLOCK a few times now and I still couldn't say what problem it solves. That's worth leading with, because it's the first thing anyone deciding whether to download it would want to know.

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

You are 100% right, and I really appreciate the callout! I got so deep into the threading and architecture weeds of v5 that I completely buried the lede. "What problem does this solve?" should absolutely be front and center.

Since you're in the Linux/Cloud space, the easiest way to explain it is: Think of ATLOCK as a local, application-layer HashiCorp Vault + Endpoint HSM for Windows.

Here is the core problem it solves: Active Session Security & Anti-Forensics.

Full Disk Encryption (like BitLocker on Windows or LUKS on Linux) only protects data at rest (when the machine is powered off). The second you log in, your sensitive files, crypto keys, and passwords are exposed in plaintext to the OS, memory-scraping malware, or anyone who walks up to your desk while you're grabbing coffee.

ATLOCK is built for the "paranoid" Windows developer or security researcher who needs an extra application-layer defense. Here is what it actually does:

🔒 1. The "Walk-Away" Panic (Fail-Closed Security)
The millisecond you hit Win+L to lock your Windows session, ATLOCK detects the OS event, forcefully locks the encrypted vault, and nukes the clipboard and RAM secrets. No relying on the OS to handle memory cleanup.

🛡️ 2. Hardware-Backed Local Auth
It brings enterprise-grade MFA to local files. You can require a FIDO2 hardware key (YubiKey) + Windows Hello (Biometrics) just to decrypt a local text file or credential, completely bypassing Windows' native (and sometimes clunky) EFS/Certificate managers.

📸 3. Anti-Tamper & Intrusion Detection
If someone tries to brute-force the vault while you're away, it doesn't just block them—it silently snaps a photo via the webcam, logs the event, and can trigger network disconnects or Telegram alerts.

☁️ 4. Cloud-Sync Safety
A lot of devs keep their notes or keys in Dropbox/OneDrive. ATLOCK allows you to keep your vault in a cloud-synced folder, but it remains cryptographically sealed locally. Even if your cloud account is compromised, the attacker only gets encrypted blobs.

It’s definitely a niche tool for Windows power-users handling sensitive local data!

Since you're on Linux/Cloud, I'm curious—how do you handle local endpoint secret management on your rigs? Are you using age, SOPS, pass, or just relying on strict file permissions and LUKS?

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

You are 100% right, and I really appreciate the callout! I got so deep into the threading and architecture weeds of v5 that I completely buried the lede. "What problem does this solve?" should absolutely be front and center.

Since you're in the Linux/Cloud space, the easiest way to explain it is: Think of ATLOCK as a local, application-layer HashiCorp Vault + Endpoint HSM for Windows.

Here is the core problem it solves: Active Session Security & Anti-Forensics.

Full Disk Encryption (like BitLocker on Windows or LUKS on Linux) only protects data at rest (when the machine is powered off). The second you log in, your sensitive files, crypto keys, and passwords are exposed in plaintext to the OS, memory-scraping malware, or anyone who walks up to your desk while you're grabbing coffee.

ATLOCK is built for the "paranoid" Windows developer or security researcher who needs an extra application-layer defense. Here is what it actually does:

🔒 1. The "Walk-Away" Panic (Fail-Closed Security)**
The millisecond you hit Win+L to lock your Windows session, ATLOCK detects the OS event, forcefully locks the encrypted vault, and nukes the clipboard and RAM secrets. No relying on the OS to handle memory cleanup.

🛡️ 2. Hardware-Backed Local Auth
It brings enterprise-grade MFA to local files. You can require a FIDO2 hardware key (YubiKey) + Windows Hello (Biometrics) just to decrypt a local text file or credential, completely bypassing Windows' native (and sometimes clunky) EFS/Certificate managers.

📸 3. Anti-Tamper & Intrusion Detection
If someone tries to brute-force the vault while you're away, it doesn't just block them—it silently snaps a photo via the webcam, logs the event, and can trigger network disconnects or Telegram alerts.

☁️ 4. Cloud-Sync Safety
A lot of devs keep their notes or keys in Dropbox/OneDrive. ATLOCK allows you to keep your vault in a cloud-synced folder, but it remains cryptographically sealed locally. Even if your cloud account is compromised, the attacker only gets encrypted blobs.
That's what makes us different from other security tools.
they need your data, they protect you but didn't capture your intruder by image videos.
also ATLOCK has world class cryptography(Lol I built this alone 0 budget)
It’s definitely a niche tool for Windows power-users handling sensitive local data!

Since you're on Linux/Cloud, I'm curious—how do you handle local endpoint secret management on your rigs? Are you using age, SOPS, pass, or just relying on strict file permissions and LUKS?