Permissive mode swimlanes in Service Mesh (ASM) let you deploy partial environments that fall back to a baseline version for missing services. However, ASM cannot route message queue traffic through its mesh proxy the way it routes HTTP traffic. When a trace includes a message queue hop, the swimlane tag is lost unless your application explicitly preserves it.
The adaptation pattern described here keeps swimlane tags intact across message queue boundaries so that traffic continues to reach the correct swimlane after an asynchronous hop.
How it works
In a synchronous HTTP call chain, ASM automatically routes requests to the correct swimlane based on the x-asm-prefer-tag request header. Message queues break this chain because consumers pull messages independently -- the mesh proxy has no opportunity to inspect or route them.
To bridge this gap, your application must handle three responsibilities:
Producer: write swimlane metadata into each message. Embed two pieces of information -- the swimlane the producer belongs to (used as a filter key) and the swimlane tag from the incoming request (used for downstream routing).
Consumer: filter messages by swimlane. Subscribe only to messages whose filter key matches the consumer's own swimlane.
Consumer: restore the swimlane tag on outgoing requests. After consuming a message, extract the swimlane tag from the message metadata and set it as the
x-asm-prefer-tagheader on downstream HTTP requests. This allows the mesh proxy to resume tag-based routing.
Prerequisites
Message queue requirements
The message queue must support:
Custom metadata on messages -- producers attach swimlane information to each message.
Consumer-side filtering -- consumers subscribe selectively based on a filter key (also known as a tag-filtering condition field) to receive only messages from their own swimlane.
Deployment constraints
In a pull-based message queue, the consumer cannot access service discovery information for the swimlane. This means you must deploy producers and consumers together -- deploy both in a swimlane, or neither.
| Swimlane | Producer | Consumer | Valid? |
|---|---|---|---|
| v1 | Deployed | Deployed | Yes |
| v2 | Deployed | Deployed | Yes |
| v2 | Deployed | Not deployed | No |
| v2 | Not deployed | Deployed | No |
| v2 | Not deployed | Not deployed | Yes -- requests fall back to v1 in permissive mode |
Deploying only a producer or only a consumer in a swimlane is an invalid deployment. Always deploy them as a pair, or omit both and let permissive mode route traffic to the baseline swimlane.
Step 1: Write swimlane tags to messages (producer)
When a producer (for example, APP-B) sends a message to the queue, it must include two metadata fields:
| Metadata field | Source | Purpose |
|---|---|---|
| Swimlane of the producer (filter key) | Environment variable injected into the workload | Allows consumers to filter messages by swimlane |
| Swimlane tag from the incoming request | x-asm-prefer-tag request header | Preserves the original routing tag for downstream services |
Where each value comes from:
Swimlane of the producer -- Determined by the workload tag. Expose an environment variable (for example,
ASM_SWIMLANE_TAG) in the pod spec so the application can read its swimlane assignment.Swimlane tag from the incoming request -- After traffic passes through the ASM ingress gateway, ASM passes tags to end-to-end HTTPS through the
x-asm-prefer-tagrequest header. Read this header from the inbound HTTP request and write its value into the message metadata.
Example: producer writes swimlane metadata
The following pseudocode shows how the producer reads the swimlane information and attaches it to the message:
import os
from flask import request
# Read the swimlane assignment from the environment variable
# that ASM injects into the workload pod.
producer_swimlane = os.environ.get("ASM_SWIMLANE_TAG", "default")
# Read the swimlane routing tag from the incoming HTTP request header.
routing_tag = request.headers.get("x-asm-prefer-tag", "")
# Attach both values to the message metadata before publishing.
message = {
"body": payload,
"metadata": {
"filter_key": producer_swimlane, # Consumer-side filtering
"x-asm-prefer-tag": routing_tag # Downstream routing
}
}
mq_client.publish(topic="order-events", message=message)Example message metadata:
Message metadata:
filter_key: "v1" # Swimlane of the producer (APP-B is deployed in v1)
x-asm-prefer-tag: "v2" # Swimlane tag from the original request
Message body:
{ ... actual payload ... }Step 2: Filter messages by swimlane (consumer)
Each consumer subscribes to messages using the filter key that matches its own swimlane. This makes sure a consumer only processes messages from producers in the same swimlane.
Example: consumer subscribes with a filter key
import os
consumer_swimlane = os.environ.get("ASM_SWIMLANE_TAG", "default")
# Subscribe only to messages whose filter_key matches this consumer's swimlane.
mq_client.subscribe(
topic="order-events",
filter_expression=f"filter_key = '{consumer_swimlane}'"
)Scenario 1: Both producer and consumer deployed in each swimlane
When both swimlanes v1 and v2 have producers and consumers:
APP-C(v1) subscribes with filter key
v1and consumes only messages from APP-B(v1).APP-C(v2) subscribes with filter key
v2and consumes only messages from APP-B(v2).
Traffic stays isolated within each swimlane.
Scenario 2: Neither producer nor consumer deployed in a swimlane
When swimlane v2 has only APP-A(v2) and APP-D(v2) -- no producer or consumer:
APP-A(v2) sends a request. Because APP-B(v2) does not exist, permissive mode routes the request to APP-B(v1).
APP-B(v1) writes the message to the queue with filter key
v1. Thex-asm-prefer-tagvalue in the message metadata remainsv2(the original request's swimlane tag).APP-C(v1) consumes the message (filter key matches
v1).APP-C(v1) removes the swimlane v1 tag carried in the message and reads the
x-asm-prefer-tagvalue (v2) from the message metadata, then sets it on the outgoing HTTP request.The mesh proxy routes the request to APP-D(v2) based on the
x-asm-prefer-tag: v2header.
This preserves end-to-end swimlane routing even though the message passed through the baseline swimlane.
Step 3: Restore the swimlane tag on downstream requests (consumer)
After consuming a message, propagate the swimlane tag to all downstream HTTP requests. Without this step, the mesh proxy cannot route subsequent requests to the correct swimlane.
To propagate the tag:
Extract the
x-asm-prefer-tagvalue from the consumed message's metadata.Set the
x-asm-prefer-tagheader on all outgoing HTTP requests to downstream services.
The mesh proxy then routes these requests to the swimlane specified by the header value.
Example: consumer restores the swimlane tag
import requests
def on_message(message):
# Extract the swimlane routing tag from the consumed message.
routing_tag = message.metadata.get("x-asm-prefer-tag", "")
# Set the tag as a request header on all downstream HTTP calls.
headers = {"x-asm-prefer-tag": routing_tag}
response = requests.post(
"http://app-d:8080/process",
json=message.body,
headers=headers
)Quick reference: fields to set and propagate
| Field | Where to set | Value source | Purpose |
|---|---|---|---|
| Message filter key | Message metadata (producer) | Workload environment variable (ASM_SWIMLANE_TAG) | Consumer-side message filtering |
x-asm-prefer-tag | Message metadata (producer) | Inbound HTTP request header | Preserves the swimlane routing tag across the message queue hop |
x-asm-prefer-tag | Outbound HTTP request header (consumer) | Consumed message metadata | Restores tag-based routing for downstream services |