Optimising Web Applications For Container Virtualization

May 13, 2023
Manmohan Singh

Of the various virtualization approaches available, container-based virtualization remains the easiest to set up, deploy and maintain. Here at Langoor, we use OpenVZ to manage our virtual servers. The OpenVZ web panel lets us administer multiple virtual environments (VEs) across different physical machines from one central console, giving us a loosely coupled cloud platform where we can spin up, migrate and reinstall servers, and build custom templates that cut infrastructure setup time considerably. Offsite backups sit on Amazon Glacier, kept deliberately separate from the nodes doing the actual serving.

In this post we'll look at how we performance-tune an OpenVZ container for a typical Apache2, PHP and MySQL deployment. We'll assume the container is already running and the site itself is configured correctly; this is about squeezing reliable performance out of the environment underneath it.

The core issue with container virtualization

Unlike full virtualization, where each guest gets its own kernel and a hypervisor arbitrates hardware access, container virtualization means every VE on a node shares one kernel. That's exactly what makes containers lightweight and fast to provision, and it's also the source of almost every performance problem you'll run into.

Two issues show up most often:

Noisy neighbours. Because containers share the host's I/O scheduler, a heavy process on one VE, a backup job, a large file copy, an unthrottled cron task, can saturate disk I/O for every other container on that node. Your MySQL queries don't get slower because your site got busier; they get slower because someone else's VE is hammering the same disk.

No real swap. OpenVZ containers don't have a genuine swap partition the way a full VM does. Memory limits are enforced through User Beancounters (UBC) instead, a set of hard ceilings on kernel memory, process memory, shared pages and more. Hit one of those ceilings and the kernel doesn't gracefully swap; it starts denying allocations, which is a much less forgiving failure mode.

Both issues are manageable, but only if you tune for them explicitly rather than assuming a container behaves like a dedicated box.

Tuning MySQL for a beancounter-limited environment

Because there's no swap cushion to fall back on, MySQL needs to run conservatively rather than generously. On a modest container, that typically means:

  • Keeping innodb_buffer_pool_size proportional to what the container actually guarantees, not what the host node has available
  • Disabling the query cache (query_cache_size = 0, query_cache_type = 0) since it adds memory overhead that rarely pays for itself on smaller, cache-friendly sites
  • Capping max_connections to a number your container's memory ceiling can genuinely support, rather than the MySQL default

The goal isn't peak throughput on paper. It's predictability: a configuration that degrades gracefully under load instead of triggering an out-of-memory condition at your busiest hour.

Tuning Apache to match the container, not the hardware

Apache's default prefork settings are written with a dedicated server in mind, not a resource-bounded VE. Left alone, MaxRequestWorkers and similar directives will happily let Apache spawn more child processes than your container's memory ceiling can actually hold, which is a direct path into the same UBC failures MySQL runs into. Set these limits deliberately, based on the real memory footprint of an average Apache child process on your stack, not the host node's total RAM.

Watch the beancounters, not just the dashboard

The single most useful diagnostic in OpenVZ is cat /proc/user_beancounters from inside the container. Each resource, privvmpages, shmpages, numproc, kmemsize and the rest, carries a failcnt column. A non-zero failcnt means the container tried to exceed a limit and was silently denied, often long before any error shows up in an application log. Checking this regularly, rather than only after a site starts misbehaving, catches resource ceilings before they turn into downtime.

Keep backups and templates off the hot path

Custom templates are one of the biggest practical wins in an OpenVZ setup: a pre-built, pre-tuned template turns provisioning a new VE into a matter of minutes rather than a full reinstall and reconfiguration cycle. But templates and backups both involve heavy disk I/O, which is exactly the kind of activity that creates noisy-neighbour problems if it runs on the same node during peak traffic. Pushing backups offsite, as we do with Amazon Glacier, keeps that I/O load off the node entirely rather than just off the container.

The underlying principle

Container virtualization rewards teams who tune for the environment they actually have, not the environment a dedicated server would give them. Shared kernel, shared I/O scheduler, hard memory ceilings instead of forgiving swap: once you design your stack around those constraints instead of fighting them, OpenVZ containers run Apache, PHP and MySQL reliably, and the speed and flexibility that make container virtualization attractive in the first place stop coming at the cost of stability.