|
| 1 | +# AMQP Proxy Sidecar Integration |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The RabbitMQ controller now automatically deploys an AMQP durability proxy as a sidecar container during upgrades and queue type migrations. This proxy enables seamless migration from classic/mirrored queues to quorum queues without requiring immediate OpenStack dataplane reconfiguration. |
| 6 | + |
| 7 | +## Problem Solved |
| 8 | + |
| 9 | +When migrating from RabbitMQ 3.9 (mirrored queues) to 4.2 (quorum queues), external OpenStack services have `amqp_durable_queues=false` configured. However, quorum queues require `durable=True`, causing `PRECONDITION_FAILED` errors. The proxy transparently rewrites AMQP frames to force durability, allowing non-durable clients to work with durable quorum queues. |
| 10 | + |
| 11 | +## Architecture |
| 12 | + |
| 13 | +``` |
| 14 | +┌─────────────────────────────────────────┐ |
| 15 | +│ RabbitMQ Pod │ |
| 16 | +│ │ |
| 17 | +│ ┌───────────────────┐ │ |
| 18 | +│ │ amqp-proxy │ Port: 5672 │ |
| 19 | +│ │ (with TLS) │ (external) │ |
| 20 | +│ └─────────┬─────────┘ │ |
| 21 | +│ │ localhost:5673 │ |
| 22 | +│ ↓ (no TLS) │ |
| 23 | +│ ┌───────────────────┐ │ |
| 24 | +│ │ rabbitmq │ Port: 5673 │ |
| 25 | +│ │ │ (localhost) │ |
| 26 | +│ └───────────────────┘ │ |
| 27 | +└─────────────────────────────────────────┘ |
| 28 | + ↑ Port 5672 (TLS) |
| 29 | + │ |
| 30 | + External clients |
| 31 | +``` |
| 32 | + |
| 33 | +## How It Works |
| 34 | + |
| 35 | +1. **Automatic Activation**: Proxy sidecar is automatically added when: |
| 36 | + - Upgrade is in progress (`Status.UpgradePhase != ""`) |
| 37 | + - Migrating to quorum queues before dataplane is reconfigured |
| 38 | + - Annotation `rabbitmq.openstack.org/enable-proxy=true` is set |
| 39 | + |
| 40 | +2. **TLS Handling**: |
| 41 | + - Proxy terminates TLS on port 5672 (using certs from `Spec.TLS.SecretName`) |
| 42 | + - RabbitMQ listens on localhost:5673 without TLS |
| 43 | + - External clients connect to proxy with TLS |
| 44 | + |
| 45 | +3. **Frame Rewriting**: Proxy intercepts AMQP frames and: |
| 46 | + - Rewrites `queue.declare(durable=False)` → `durable=True` + `x-queue-type=quorum` |
| 47 | + - Rewrites `exchange.declare(durable=False)` → `durable=True` |
| 48 | + - Skips reply queues and system queues |
| 49 | + |
| 50 | +4. **Automatic Removal**: Proxy is removed after: |
| 51 | + - Dataplane is reconfigured with `amqp_durable_queues=true` |
| 52 | + - User sets annotation `rabbitmq.openstack.org/clients-reconfigured=true` |
| 53 | + |
| 54 | +## Files Created/Modified |
| 55 | + |
| 56 | +### New Files |
| 57 | +- `internal/controller/rabbitmq/proxy.go` - Proxy sidecar integration logic |
| 58 | +- `internal/controller/rabbitmq/data/proxy.py` - AMQP proxy script (embedded) |
| 59 | +- `internal/controller/rabbitmq/data/README.md` - Documentation for data files |
| 60 | +- `PROXY_INTEGRATION.md` - Complete integration documentation |
| 61 | + |
| 62 | +### Modified Files |
| 63 | +- `internal/controller/rabbitmq/rabbitmq_controller.go` (lines 805-835): |
| 64 | + - Added proxy sidecar logic after `ConfigureCluster` |
| 65 | + - Calls `ensureProxyConfigMap`, `addProxySidecar`, `configureRabbitMQBackendPort` |
| 66 | + - Removes proxy when `shouldEnableProxy()` returns false |
| 67 | + |
| 68 | +## Key Functions |
| 69 | + |
| 70 | +### proxy.go |
| 71 | + |
| 72 | +- `ensureProxyConfigMap(ctx, instance, helper)` - Creates ConfigMap with embedded proxy script |
| 73 | +- `addProxySidecar(instance, cluster)` - Adds proxy container to StatefulSet |
| 74 | +- `buildProxySidecarContainer(instance)` - Builds container spec with TLS support |
| 75 | +- `removeProxySidecar(cluster)` - Removes proxy sidecar |
| 76 | +- `shouldEnableProxy(instance)` - Determines when proxy should be enabled |
| 77 | +- `configureRabbitMQBackendPort(instance, cluster)` - Configures RabbitMQ to listen on localhost:5673 |
| 78 | + |
| 79 | +## Container Spec |
| 80 | + |
| 81 | +- **Image**: `quay.io/openstack-k8s-operators/openstack-operator-client:latest` |
| 82 | +- **Command**: `python3 /scripts/proxy.py` |
| 83 | +- **Args**: |
| 84 | + - `--backend localhost:5673` |
| 85 | + - `--listen 0.0.0.0:5672` |
| 86 | + - `--tls-cert /etc/rabbitmq-tls/tls.crt` (if TLS enabled) |
| 87 | + - `--tls-key /etc/rabbitmq-tls/tls.key` (if TLS enabled) |
| 88 | + - `--tls-ca /etc/rabbitmq-tls-ca/ca.crt` (if CA specified) |
| 89 | +- **Resources**: |
| 90 | + - Requests: 128Mi memory, 100m CPU |
| 91 | + - Limits: 256Mi memory, 500m CPU |
| 92 | +- **Probes**: TCP liveness and readiness on port 5672 |
| 93 | + |
| 94 | +## Usage |
| 95 | + |
| 96 | +### Automatic (during upgrade) |
| 97 | + |
| 98 | +The proxy is automatically enabled when upgrading RabbitMQ: |
| 99 | + |
| 100 | +```yaml |
| 101 | +apiVersion: rabbitmq.openstack.org/v1beta1 |
| 102 | +kind: RabbitMq |
| 103 | +metadata: |
| 104 | + name: rabbitmq |
| 105 | + annotations: |
| 106 | + rabbitmq.openstack.org/target-version: "4.2.0" |
| 107 | +spec: |
| 108 | + queueType: Quorum |
| 109 | + tls: |
| 110 | + secretName: rabbitmq-tls |
| 111 | +``` |
| 112 | +
|
| 113 | +### Manual activation |
| 114 | +
|
| 115 | +Force proxy activation with annotation: |
| 116 | +
|
| 117 | +```bash |
| 118 | +kubectl annotate rabbitmq rabbitmq \ |
| 119 | + rabbitmq.openstack.org/enable-proxy=true |
| 120 | +``` |
| 121 | + |
| 122 | +### Manual deactivation |
| 123 | + |
| 124 | +After dataplane reconfiguration: |
| 125 | + |
| 126 | +```bash |
| 127 | +kubectl annotate rabbitmq rabbitmq \ |
| 128 | + rabbitmq.openstack.org/clients-reconfigured=true |
| 129 | +``` |
| 130 | + |
| 131 | +## Monitoring |
| 132 | + |
| 133 | +Check proxy logs: |
| 134 | +```bash |
| 135 | +kubectl logs rabbitmq-server-0 -c amqp-proxy |
| 136 | +``` |
| 137 | + |
| 138 | +Proxy prints statistics every 5 minutes: |
| 139 | +``` |
| 140 | +=== Proxy Statistics === |
| 141 | +Total connections: 25 |
| 142 | +Queue rewrites: 120 |
| 143 | +Exchange rewrites: 15 |
| 144 | +Bytes forwarded: 5,432,100 |
| 145 | +``` |
| 146 | + |
| 147 | +## Upgrade Workflow |
| 148 | + |
| 149 | +1. **Start upgrade**: Set target-version annotation → Proxy enabled |
| 150 | +2. **Cluster recreated**: RabbitMQ 4.2 with quorum queues + proxy sidecar |
| 151 | +3. **External services**: Continue using `amqp_durable_queues=false` |
| 152 | +4. **Proxy rewrites**: Frames rewritten to use durable queues |
| 153 | +5. **Dataplane reconfig**: Update services to `amqp_durable_queues=true` |
| 154 | +6. **Set annotation**: `clients-reconfigured=true` → Proxy removed |
| 155 | +7. **Direct connection**: Services connect directly to RabbitMQ |
| 156 | + |
| 157 | +## Important Notes |
| 158 | + |
| 159 | +### Proxy Script Location |
| 160 | + |
| 161 | +The proxy script is located at: |
| 162 | +- `internal/controller/rabbitmq/data/proxy.py` |
| 163 | + |
| 164 | +When updating the proxy: |
| 165 | +1. Edit `internal/controller/rabbitmq/data/proxy.py` |
| 166 | +2. Rebuild: `make` |
| 167 | +3. Test: `make test` |
| 168 | + |
| 169 | +### Performance |
| 170 | + |
| 171 | +- Proxy adds ~0.05ms latency (localhost forwarding) |
| 172 | +- Memory overhead: ~64Mi per RabbitMQ pod |
| 173 | +- CPU overhead: ~20m per RabbitMQ pod |
| 174 | + |
| 175 | +### Security |
| 176 | + |
| 177 | +- Runs as non-root |
| 178 | +- No privilege escalation |
| 179 | +- Drops all capabilities |
| 180 | +- TLS certificates mounted read-only |
| 181 | + |
| 182 | +## Testing |
| 183 | + |
| 184 | +### Go Unit Tests |
| 185 | + |
| 186 | +Proxy sidecar integration tests verify: |
| 187 | +- ✅ Proxy is added during upgrades |
| 188 | +- ✅ TLS configuration is correct |
| 189 | +- ✅ Proxy is removed after client reconfiguration |
| 190 | +- ✅ Manual activation via annotations |
| 191 | + |
| 192 | +Run tests: |
| 193 | +```bash |
| 194 | +make test |
| 195 | +# Test Suite Passed |
| 196 | +# composite coverage: 72.7% of statements |
| 197 | + |
| 198 | +# Run only proxy tests |
| 199 | +cd test/functional |
| 200 | +ginkgo -focus="RabbitMQ Proxy" |
| 201 | +``` |
| 202 | + |
| 203 | +### Python Integration Test |
| 204 | + |
| 205 | +Simulates Oslo messaging client with `amqp_durable_queues=false` connecting through proxy to RabbitMQ with quorum queues. |
| 206 | + |
| 207 | +Quick test: |
| 208 | +```bash |
| 209 | +cd internal/controller/rabbitmq/data |
| 210 | +python3 proxy_test.py --host localhost --port 5672 |
| 211 | +``` |
| 212 | + |
| 213 | +For complete testing documentation, see [TESTING.md](internal/controller/rabbitmq/data/TESTING.md). |
| 214 | + |
| 215 | +## References |
| 216 | + |
| 217 | +- Proxy implementation: `internal/controller/rabbitmq/data/proxy.py` |
| 218 | +- Proxy integration: `internal/controller/rabbitmq/proxy.go` |
| 219 | +- AMQP 0-9-1 spec: https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf |
0 commit comments