Proxy mesh in Kubernetes: Tor exit nodes and geo-routing with hola-proxy

Published: 2026-04-19

The infra cluster runs a proxy namespace with two kinds of outbound proxies: a Tor rotating exit node cluster and hola-proxy instances that tunnel through residential IPs in specific countries.


The proxy namespace

namespace: proxy
├── tor-proxy          ← rotating-tor-http-proxy (HAProxy + N Tor circuits)
├── hola-proxy-jp      ← Japan exit node via Hola peer network
├── hola-proxy-sg      ← Singapore exit node
└── hola-proxy-au      ← Australia exit node

All proxies listen on port 8080 inside the cluster. External access goes through APISIX routes or direct VPN access.


Tor rotating proxy

Uses zhaow-de/rotating-tor-http-proxy — a container that runs N Privoxy+Tor pairs behind HAProxy. Each circuit is a separate Tor identity, round-robin selected by HAProxy.

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: tor-proxy
  namespace: proxy
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: tor-proxy
          image: registry.example.com/docker/proxy/rotating-tor-http-proxy:latest
          env:
            - name: TOR_INSTANCES
              value: "10"
          ports:
            - containerPort: 30118  # HAProxy frontend port
          resources:
            requests:
              memory: "512Mi"
              cpu: "200m"
            limits:
              memory: "1Gi"
              cpu: "500m"

10 Tor instances = 10 concurrent circuits, round-robin. Each request gets a different exit IP. The container exposes HAProxy on port 30118.


hola-proxy instances (routing through Tor)

Each country is a separate Deployment. hola-proxy routes through the Tor proxy for anonymization:

yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: hola-proxy-jp
  namespace: proxy
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: hola-proxy-jp
          image: registry.example.com/docker/proxy/hola-proxy:latest
          args:
            - -bind-address
            - 0.0.0.0:8080
            - -country
            - jp
            - -proxy
            - http://tor-proxy.proxy.svc.cluster.local:30118
            - -timeout
            - 35s
            - -verbosity
            - "20"
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "256Mi"
              cpu: "300m"
          startupProbe:
            tcpSocket:
              port: 8080
            failureThreshold: 30
            periodSeconds: 5
          readinessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 10
          livenessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 20
            failureThreshold: 3

Hola uses residential P2P exit nodes. Tor anonymizes the connection from the cluster to the Hola peer network.

The startupProbe with 30 retries (150s total) gives hola-proxy time to establish its first peer connection before being considered failed.


Services

yamlapiVersion: v1
kind: Service
metadata:
  name: hola-proxy-jp
  namespace: proxy
spec:
  type: ClusterIP
  selector:
    app: hola-proxy
    country: jp
  ports:
    - name: http
      port: 8080
      targetPort: 8080

Repeat for sg, au. Internal consumers use hola-proxy-jp.proxy.svc.cluster.local:8080.


APISIX route for external access

Expose one proxy via APISIX for external (VPN) access:

yamlapiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: proxy-jp
  namespace: proxy
spec:
  http:
    - name: proxy-jp
      match:
        hosts:
          - proxy-jp.infra.example.com
        paths:
          - "/*"
      backends:
        - serviceName: hola-proxy-jp
          servicePort: 8080

Why raw manifests instead of HelmRelease

These Deployments live in spoke/proxy/ as plain YAML, not HelmReleases:

  1. No templating needed across environments — these only exist on infra
  2. Country-specific proxies don't make sense on dev/test
  3. The pod spec is simple — raw YAML is cleaner than a chart with one template

Image source

The hola-proxy image is built from an internal GitLab project and pushed to registry.example.com. Pull credentials come from gitlab-registry-docker imagePullSecret (a SealedSecret in spoke/secrets/).


Monitoring proxy health

Check all proxy pods:

bashkubectl get pods -n proxy -o wide

Test a specific exit node:

bash# Test Japan exit IP
curl -x http://hola-proxy-jp.proxy.svc.cluster.local:8080 https://api.myip.com

# Test Tor rotating proxy
curl -x http://tor-proxy.proxy.svc.cluster.local:30118 https://check.torproject.org/api/ip

Watch logs for connection errors:

bashkubectl logs -n proxy deployment/hola-proxy-jp -f

What can go wrong

hola-proxy returns "no available upstreams". Hola's peer network occasionally rotates available peers. Restart the pod: kubectl rollout restart deployment/hola-proxy-<country> -n proxy. If it persists, the country code may no longer be supported — check the hola-proxy GitHub for the current list.

Tor circuits take too long to bootstrap. The Tor proxy pod logs show "Bootstrapped 0%" for minutes after startup. This usually means the VPS IP is listed in a Tor blocklist, or the network blocks the Tor port. Try a different Tor bridge configuration.

APISIX route returns 502 for the proxy endpoints. The backend service is probably reachable but the upstream health check is failing. Check APISIX admin API: curl http://apisix-admin:9180/apisix/admin/upstreams.


Summary

  • Two proxy types in one namespace: Tor rotating exit nodes (anonymity) and hola-proxy (geo-specific residential IPs)
  • Each hola-proxy instance targets a single country (country: JP, country: US, etc.) and runs as a separate Deployment
  • Services use ClusterIP — external access goes through APISIX routes with optional auth
  • Raw Kubernetes manifests instead of HelmRelease because the setup changes rarely and doesn't benefit from Helm's values templating
  • Test geo-routing: curl -x http://hola-proxy-jp.proxy.svc.cluster.local:8080 https://api.myip.com