WordPress Hosting for Developers & Agencies Looking to Deliver Performance


Introduction

Wordpress development agencies often excel at crafting beautiful, functional websites but may lack the infrastructure skills to offer robust hosting solutions to their clients.

Some agencies even have the in-house skill to run and operate infrastructure, but they streer their strategy towards VPS-based/reseller solutions, eating up a big chunk of their profits. I recently started pondering with how this could be fixed by having something central (so that we didn’t have to deal with a bunch of different resources, for efficiency reasons) and still maintain a good service level.

Enter Kubernetes and vCluster, a powerful duo that can enable agencies to offer multi-tenant hosting solutions with ease (IMO). Hear me out!

Why Kubernetes and vCluster?

Kubernetes has become the go-to orchestration platform for containerized applications, offering scalability, high availability, and a rich ecosystem of tools and community support. But how do we offer isolated environments for each client without managing multiple clusters? That’s where vCluster comes in.

vCluster allows you to create virtual clusters within a single Kubernetes cluster. Each virtual cluster acts like a fully isolated namespace, making it perfect for multi-tenant hosting. This enables you to:

  • Isolate Resources: Each client’s Wordpress instance can be isolated, ensuring that one client’s resource usage or potential security vulnerabilities do not affect others.

  • Centralized Management: With all virtual clusters running within a single Kubernetes cluster, management becomes centralized, making it easier to apply global updates or changes.

  • Cost-Efficiency: Running multiple virtual clusters in a single physical cluster optimizes resource usage, reducing the overall cost.

Architecture

Architectural Overview

To better understand how all these components fit together, let’s look at the architecture diagram below:

sequenceDiagram
  participant GCP as GCP Cloud
  participant K8s as Kubernetes Cluster
  participant vC as vCluster
  participant WP as Wordpress App
  participant DB as Database
  participant Cache as Cache
  
  GCP->>K8s: Hosts
  K8s->>vC: Spawns vCluster
  vC->>WP: Deploys Wordpress
  vC->>DB: Deploys Database
  vC->>Cache: Deploys Cache
  WP->>DB: Reads/Writes
  WP->>Cache: Reads/Writes
  
  1. Create a Kubernetes Cluster: I assume that you already have a Kubernetes cluster set up, eg. a GKE. I’d recommend a managed one, as there is no need to deal with the control-plane internals at this point.

  2. Create a vCluster virtual cluster: Within this K8s cluster, a vCluster is created for each client. This is a simplified solution for having customer-isolated environments.

  3. Deploy Wordpress just as any other K8s app: The Wordpress/PHP application is deployed into the virtual cluster.

  4. Deploy Database, Cache and other stores: Additional services like databases, caches, and file storage are also deployed within the same virtual cluster.

  5. Connect Services: Finally, the Wordpress application connects to these services, completing the setup.

This architecture allows for multi-tenant isolation while benefiting from centralized management in GCP.

A quick Proof-of-Concept

Starting with vCluster

To set up a vCluster, you’ll first need to install the vCluster CLI. See more info about setting this up here, however I’ll simply do a

brew install loft-sh/tap/vcluster

on my Mac.

Once the CLI is installed, you can create a vCluster with the following command:

vcluster create tenant-a-vcluster

This will create a new vCluster named tenant-a-vcluster, considered to be a full Kubernetes cluster intended for use only in relation to the agency’s customer named tenant-a.

How you will organize vClusters with customers is up to you and the complexity you want to maintain as well as the system requirements. Eg. you might want to opt for a vCluster hosting a bunch of small websites. This has several dissadvantages, but anyone can admit it has the lowest barrier to entry. So this can be a perfect development-cluster opportunity? See, not every solution is bad, as long as you understand its limits.

Moving on we’ll see how it can realistically look like - and it’s not that scary.

vCluster has been a strategic decision up to this point. And there is a god reason for this, as vCluster acts as a Kubernetes cluster within your existing Kubernetes cluster. It provides an isolated environment, making it ideal for multi-tenancy use-cases, instead of obliging you to maintain and operate multiple K8s clusters just for Wordpress hosting.

  1. Moving on to some k8s manifests

To deploy a Wordpress instance into your vCluster, you can use the following Kubernetes YAML configuration:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1001
      containers:
      - name: wordpress
        image: wordpress:latest
        ports:
        - containerPort: 80
        securityContext:
          allowPrivilegeEscalation: false
        resources:
          requests:
            cpu: "100m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
        volumeMounts:
        - name: wordpress-data
          mountPath: /var/www/html
      volumes:
      - name: wordpress-data
        emptyDir: {}

Save this configuration to a file, say wordpress-deployment.yaml.

This will create a single replica Deployment of the Wordpress application, accessible on port 80, with an EmptyDir volume mounted and a baseline of security config.

Database and other stores

MySQL

For database deployment, you can use a MySQL Kubernetes configuration. I’m using a stripped down version of mysql in this case just to see this working with my own eyes:

Save the following YAML to mysql-deployment.yaml.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "mypassword"

Redis

Redis is a popular choice. I’ll go with it at least because it just-works. Also I absolutely despise the idea of using WP caching plugins that promise miraculous results by storing in the DB or in-memory the WP pages only to come cracking down in the first traffic spike.

Therefore assuming we got something similar to this in a redis-deployment.yaml file:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:latest

Other stores

For demonstration purposes of how this can be virtuallized and scaled for multi-tenancy, I have quickly scrambled an EmptyDir in your deployment.yaml for now, as it’s irrelevant with the scope of this post. Or depending on the usual use-cases you might want to implement other stores as well, eg. Memcached just because you have already purchased a very expensive WP plugin working with it or whatever… (sorry, being a bit salty here).

Security Considerations

Role-Based Access Control (RBAC)

My recommendation for agencies would be to go with multiple roles, depending on the level of access each role should have. For example, a developer needs to have full pods listing and log viewing access, but potentially no need to list Redis instances, as this is managed by the infra people.

Here’s a sample RBAC configuration that grants read-only access to pods in a specific namespace. Save this YAML configuration in a file named read-only-role.yaml.

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: developer
rules:
- apiGroups: [""]
  resources: ["pods", "pods/logs"]
  verbs: ["get", "list"]

Network Policies

Here’s a sample Network Policy that allows incoming connections to a pod only from a specific namespace. I’ll be blatantly simplistic here and just restrict my DB to allow incoming connections from the Wordpress app namespace and only that.

eg. network-policy.yaml.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          app: wordpress

Please take a minute and think about your security strategy especially if you are about to deploy a bigger website when it comes to end-user traffic - people are malicious more often than you think.

Monitoring

I won’t go very deep in this section as Wordpress hosting monitoring can be very different between a typical few-pager company profile website and an ultra high traffic e-commerce store serving global users via edge locations.

Just some thoughts:

  • Nothing can go wrong with the LGTM stack - it’s even in the name! No kidding, it’s just almost a commoddity stack for observability and you can find plenty of good guides about that anywhere you look.
  • Monitor your cache hits/misses. You’ll make decisions around the use of caching based on these metrics.
  • Have a modern and easy way to easily check the logs for a given website you host. Aggregate and retain the logs that you need.
  • Set up alerting. Combine server-side metrics as well as eg. end-user ping metrics to raise alarms when necessary. Attrack your developers’ attention only when it’s really important, avoid noisy logs. It’s a web development agency after all, things look artifically hectic all the time…

Cost Analysis

Understanding the cost implications is vital for any project. While Kubernetes and vCluster offer resource efficiency, it’s essential to monitor CPU, memory, and storage usage continually.

Tools like Kubecost can provide real-time cost insights and forecasts.

You might charge your customers based on their usage. You might offer standard fixed-price monthly/yearly packages. Monitoring your cost is the way to go nevertheless as it will feed you much needed data to define your offering prices.

Repeatability for multi-tenancy

All of this might be useful just as a PoC, so let’s gather everything together to make these steps repeatable in case someone wants to try out multi-tenancy with a similar approach. I love Helm, but I’d be bored to write my own chart just for this - Kustomize ticks a lot of boxes for this scenario.

With a directory structure looking something like this

.
├── base
│   ├── kustomization.yaml
│   ├── mysql-deployment.yaml
│   ├── redis-deployment.yaml
│   ├── wordpress-deployment.yaml
│   └── rbac.yaml
│   └── network-policies.yaml
└── overlays
    ├── tenant-a
    │   └── kustomization.yaml
    └── tenant-b
        └── kustomization.yaml

I’d be happy to accommodate quite a few clients. Obviously this is not infinitely scalable, but it’s a good-enough starting point for the finite number of customers the averga Wordpress web development agency has.

Base

In the base directory, you’ll place all your common Kubernetes manifests.

  • base/kustomization.yaml
resources:
  - mysql-deployment.yaml
  - redis-deployment.yaml
  - wordpress-deployment.yaml
  - rbac.yaml

Tenant-Specific Overlays

In the overlays directory, you’ll have a sub-directory for each tenant, containing a kustomization.yaml file that refers to the base configuration.

eg. overlays/tenant-a/kustomization.yaml

namespace: tenant-a

resources:
  - ../../base

The namespace is just for show here. For more practical guides how this can be useful look into Kustomize or any other application packaging solution of your liking.

Deploying a new tenant

To deploy resources for a new tenant, you can simply create a new overlay directory for that tenant and run kubectl apply -k on it. You can customize eg. the number of replicas depending on the kind of application.

Edge Cases to Consider

While Kubernetes and vCluster offer robust solutions for multi-tenant Wordpress hosting, there are several edge cases that agencies should be aware of:

Resource Overutilization

Scenario: A tenant’s Wordpress site experiences a sudden spike in traffic, consuming more resources than allocated.

Solution: Implement Horizontal Pod Autoscaling (HPA) to automatically scale the number of pod replicas based on observed CPU utilization. Also make use of ResourceQuotas for the vCluster’s namespace, as well as the in-vCluster namespaces to restrict each app’s resources.

Security Breaches

Scenario: A vulnerability in one tenant’s Wordpress site could potentially risk the security of other tenants.

Solution: vClusters live in a namespace inside the host Kubernetes cluster. Use Network Policies to isolate traffic between tenants.

Configuration Drift

Scenario: Over time, manual changes to individual tenant configurations could lead to inconsistencies.

Solution: Use GitOps to manage your Kubernetes configurations, ensuring that the actual state matches the desired state defined in Git. Or at least have a strict procedure to get everything checked in after editing.

Cost Overruns

Scenario: Without proper monitoring, the costs associated with the Kubernetes cluster can escalate quickly.

Solution: Take a look at the cost-analysis section, and consider cost monitoring tools like Kubecost to get real-time cost reports and set up alerts for budget thresholds.

Conclusion

In today’s competitive landscape, the ability to offer robust, scalable, and secure hosting solutions can be a game-changer for Wordpress development agencies. By leveraging the power of Kubernetes and vCluster, agencies can not only enhance their technical capabilities but also unlock new revenue streams.

This approach provides a win-win scenario: developers gain from streamlined operations and resource efficiency, while business managers benefit from the added value offered to clients, all contributing to a stronger market position and enhanced client retention.

Need to chat and bounce ideas around this? Feel free to DM me.


See also