Using Divert
This guide covers the practical implementation of Divert in your Okteto development environments, including manifest configuration, header propagation, and common patterns for databases and message queues.
Okteto Manifest Configuration
The divert section goes under deploy in your okteto.yaml to configure traffic routing between your development environment and a shared namespace.
Basic Divert Configuration (nginx driver)
deploy:
commands:
- helm upgrade --install myservice chart --set image=${OKTETO_BUILD_IMAGE}
divert:
driver: nginx # Optional, nginx is the default
namespace: staging
| Field | Description |
|---|---|
driver | The backend for divert routing. Options: nginx (default) or istio |
namespace | The shared namespace containing the full application stack |
When you run okteto deploy, Okteto automatically:
- Deploys only the services defined in your manifest
- Configures routing to redirect requests for missing services to the shared namespace
- Injects the
baggage: okteto-divert=<your-namespace>header into requests through your endpoints
Istio Driver Configuration
If your cluster uses Istio for service mesh, use the istio driver:
deploy:
commands:
- helm upgrade --install myservice chart --set image=${OKTETO_BUILD_IMAGE}
divert:
driver: istio
virtualServices:
- name: frontend-vs
namespace: staging
routes:
- route-to-frontend
hosts:
- virtualService: frontend
namespace: staging
For complete configuration details, see the manifest reference.
Project Structure Patterns
Single Service Development
For working on a single service, create a dedicated manifest:
# okteto.frontend.yaml
build:
frontend:
context: frontend
deploy:
commands:
- helm upgrade --install frontend chart/frontend --set image=${OKTETO_BUILD_FRONTEND_IMAGE}
divert:
namespace: ${OKTETO_SHARED_NAMESPACE:-staging}
Multi-Service Development
When working on related services together:
# okteto.rentals.yaml
build:
rent:
context: rentals
worker:
context: worker
deploy:
commands:
- helm upgrade --install rent chart/rent --set image=${OKTETO_BUILD_RENT_IMAGE}
- helm upgrade --install worker chart/worker --set image=${OKTETO_BUILD_WORKER_IMAGE}
- helm upgrade --install kafka chart/kafka
- helm upgrade --install postgresql chart/postgresql
divert:
namespace: ${OKTETO_SHARED_NAMESPACE:-staging}
Using Environment Variables
Reference the shared namespace via environment variable for flexibility:
export OKTETO_SHARED_NAMESPACE="movies-shared"
okteto deploy -f okteto.frontend.yaml
Header Propagation
For Divert to work across your service mesh, implement header propagation in your services. The baggage header must be extracted from incoming requests and included in all outgoing requests.
JavaScript/Node.js (Express)
const express = require('express');
const axios = require('axios');
const app = express();
// Middleware to capture baggage header
app.use((req, res, next) => {
req.baggage = req.headers['baggage'] || '';
next();
});
// Propagate in outgoing requests
app.get('/api/movies', async (req, res) => {
const response = await axios.get('http://catalog:8080/movies', {
headers: { 'baggage': req.baggage }
});
res.json(response.data);
});
Go
package main
import (
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
baggage := r.Header.Get("baggage")
// Create downstream request
req, _ := http.NewRequest("GET", "http://catalog:8080/movies", nil)
req.Header.Set("baggage", baggage)
client := &http.Client{}
resp, _ := client.Do(req)
// Handle response...
}
Java/Spring Boot
@RestController
public class ApiController {
private final WebClient webClient;
@GetMapping("/api/movies")
public Mono<Movies> getMovies(@RequestHeader(value = "baggage", required = false) String baggage) {
return webClient.get()
.uri("http://catalog:8080/movies")
.header("baggage", baggage != null ? baggage : "")
.retrieve()
.bodyToMono(Movies.class);
}
}
Python (FastAPI)
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
@app.get("/api/movies")
async def get_movies(request: Request):
baggage = request.headers.get("baggage", "")
async with httpx.AsyncClient() as client:
response = await client.get(
"http://catalog:8080/movies",
headers={"baggage": baggage}
)
return response.json()
Database Isolation Patterns
When using Divert, you can choose between shared or isolated databases depending on your needs.
Shared Database (Default)
Services connect to the database in the shared namespace. This is the simplest approach and works well when you don't need to modify the database schema or data:
# Your diverted service uses the shared database
env:
- name: DATABASE_URL
value: postgresql://postgres:5432/movies # Resolves to shared namespace
Isolated Database per Developer
Deploy your own database instance when you need isolation for schema changes or test data:
deploy:
commands:
- helm upgrade --install mongodb chart/mongodb # Local database
- helm upgrade --install catalog chart/catalog --set image=${OKTETO_BUILD_IMAGE}
divert:
namespace: staging
Your service then connects to the local database:
env:
- name: MONGODB_URL
value: mongodb://mongodb:27017/catalog # Local instance
Message Queue Routing Patterns
For queue-based systems, you can route messages based on the baggage header to ensure proper service isolation.
SQS Queue Routing
When publishing messages, include the namespace in the message attributes:
// Producer: Include routing info in message
const baggage = req.headers['baggage'] || '';
const namespace = extractNamespace(baggage); // Extract from "okteto-divert=namespace"
await sqs.sendMessage({
QueueUrl: QUEUE_URL,
MessageBody: JSON.stringify(orderData),
MessageAttributes: {
'okteto-namespace': {
DataType: 'String',
StringValue: namespace || 'shared'
}
}
});
Consumer filters messages by namespace:
// Consumer: Filter messages by namespace
const messages = await sqs.receiveMessage({
QueueUrl: QUEUE_URL,
MessageAttributeNames: ['okteto-namespace']
});
for (const message of messages.Messages) {
const targetNamespace = message.MessageAttributes?.['okteto-namespace']?.StringValue;
if (targetNamespace === CURRENT_NAMESPACE || targetNamespace === 'shared') {
// Process this message
await processOrder(JSON.parse(message.Body));
}
}
Kafka Topic Routing
Use message headers for Kafka routing:
// Producer
await producer.send({
topic: 'orders',
messages: [{
value: JSON.stringify(order),
headers: {
'okteto-namespace': namespace
}
}]
});
// Consumer
await consumer.run({
eachMessage: async ({ message }) => {
const targetNamespace = message.headers['okteto-namespace']?.toString();
if (targetNamespace === CURRENT_NAMESPACE || !targetNamespace) {
await processOrder(JSON.parse(message.value));
}
}
});
Testing Your Diverted Environment
Using curl
Test routing with the baggage header:
# Without header - uses shared services
curl https://movies-staging.okteto.example.com/api/catalog/healthz
# Response: {"status": "ok", "namespace": "staging"}
# With header - routes to your namespace
curl -H "baggage: okteto-divert=alice" \
https://movies-staging.okteto.example.com/api/catalog/healthz
# Response: {"status": "ok", "namespace": "alice"}
Using Browser Extensions
Install a header modification extension (like ModHeader) and add:
- Header Name:
baggage - Header Value:
okteto-divert=<your-namespace>
Automated Testing
Include header propagation in your test setup:
// Jest/Mocha test setup
const request = require('supertest');
describe('Catalog API', () => {
it('should return movies', async () => {
const response = await request(app)
.get('/api/movies')
.set('baggage', `okteto-divert=${process.env.OKTETO_NAMESPACE}`)
.expect(200);
expect(response.body.namespace).toBe(process.env.OKTETO_NAMESPACE);
});
});
Multi-Developer Collaboration
Accessing Another Developer's Environment
To test a colleague's changes, use their namespace in the baggage header:
curl -H "baggage: okteto-divert=bob-feature" \
https://movies-staging.okteto.example.com/api/movies
Sharing Your Work
Others can access your diverted environment using:
- Your personal endpoint:
https://movies-alice.okteto.example.com - Or the shared endpoint with your header:
baggage: okteto-divert=alice
Troubleshooting
This section covers common issues developers encounter when using Divert. For admin-level troubleshooting, see Configure Divert.
Verify Sidecar Injection (nginx driver)
Check if Linkerd sidecar is running:
kubectl get pods -n <your-namespace> -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].name}{"\n"}{end}'
Should show both your application container and linkerd-proxy.
If sidecar is missing:
# Check namespace annotation
kubectl get namespace <your-namespace> -o jsonpath='{.metadata.annotations}'
# Restart deployment to trigger injection
kubectl rollout restart deployment/<your-deployment> -n <your-namespace>
Check Ingress Controller
Verify ingress is created:
kubectl get ingress -n <your-namespace>
Should show your service ingress with divert annotations.
Check ingress controller logs:
kubectl logs -n okteto deployment/okteto-nginx -f | grep <your-namespace>
Verify Linkerd Deployment (nginx driver)
Check Linkerd control plane:
# Quick health check
linkerd check
# Or manually verify deployments
kubectl get deployments -n linkerd
Watch traffic flow:
linkerd viz tap deployment/<your-deployment> --namespace <your-namespace>
Header Propagation Issues
Test header at ingress:
curl -v https://your-app.okteto.example.com 2>&1 | grep -i baggage
# Should see: baggage: okteto-divert=<your-namespace>
Verify header format:
# ✅ Correct
baggage: okteto-divert=alice-feature
# ❌ Incorrect
baggage.okteto-divert=alice-feature
okteto-divert: alice-feature
Check application code:
- Ensure all services propagate the
baggageheader to downstream calls - Add logging at service boundaries:
console.log('Baggage header:', req.headers['baggage']) - Verify header exists in: HTTP clients, gRPC calls, message queue producers
Test service-to-service routing:
kubectl exec -it deployment/<your-service> -n <your-namespace> -- \
curl -H "baggage: okteto-divert=<your-namespace>" \
http://api-service/endpoint
Traffic Not Being Diverted
- Check header format: Ensure you're using
baggage: okteto-divert=<namespace>(notbaggage.okteto-divert) - Verify header propagation: All services in the call chain must forward the baggage header
- Check namespace name: The namespace in the header must match your Okteto namespace exactly
- Verify divert config: In your
okteto.yaml:deploy:
divert:
namespace: staging # Must match shared environment
driver: nginx # Must match cluster config
Services Not Discovered
-
Verify shared namespace: Ensure the shared namespace is running and healthy
kubectl get pods -n staging
# All pods should be Running -
Check service names: Service discovery uses Kubernetes DNS
# Services are accessible at:
<service-name>.<namespace>.svc.cluster.local
# Test DNS resolution:
kubectl exec -it deployment/<your-service> -- \
nslookup api-service.staging.svc.cluster.local -
Review divert config: Ensure
divert.namespacepoints to the correct shared environment -
Check network policies:
kubectl get networkpolicies -n staging
kubectl get networkpolicies -n <your-namespace>
# Ensure cross-namespace communication is allowed
Database Connection Issues
-
Check connection strings: Ensure they resolve to the correct database (shared vs. local)
# For shared database:
postgresql.staging.svc.cluster.local
# For local database:
postgresql.<your-namespace>.svc.cluster.local -
Verify network policies: Ensure cross-namespace communication is allowed
# Test connectivity
kubectl exec -it deployment/<your-service> -n <your-namespace> -- \
nc -zv postgresql.staging.svc.cluster.local 5432 -
Test connectivity: Use
kubectl execto test database connectivity from your podkubectl exec -it deployment/<your-service> -n <your-namespace> -- \
psql -h postgresql.staging.svc.cluster.local -U user -d database
Quick Diagnostic Checklist
Run through this checklist when troubleshooting:
- Linkerd sidecars are running in your pods (nginx driver)
- Ingress is created with correct annotations
- Shared namespace exists and all services are Running
- Header format is correct:
baggage: okteto-divert=<namespace> - Application code propagates headers to all downstream calls
- DNS resolves shared services:
<service>.staging.svc.cluster.local - Network policies allow cross-namespace traffic
-
divert.namespacein okteto.yaml matches shared environment
Getting Additional Help
If issues persist:
-
Check pod logs:
kubectl logs deployment/<your-deployment> -n <your-namespace> -
Review pod events:
kubectl describe pod <pod-name> -n <your-namespace> -
Consult related documentation:
- Divert Core Concepts - Architecture and setup troubleshooting
- Configure Divert - Admin configuration issues
- Linkerd Installation - Service mesh issues
Best Practices
- Name namespaces descriptively: Use patterns like
<username>-<feature>for clarity - Clean up when done: Delete personal namespaces after completing work
- Keep shared environment updated: Regularly deploy updates to the shared staging environment
- Document header propagation: Ensure all team members understand which headers to propagate
- Use environment variables: Reference shared namespace via variables for flexibility
- Monitor resource usage: Track namespace quotas and clean up unused resources
Next Steps
- Divert Core Concepts - Understanding Divert architecture
- Divert Tutorial - Step-by-step getting started guide
- Manifest Reference - Complete configuration options
- Example Repositories - Working code samples
Example Repositories
- Movies with Divert - Multi-service example
- TacoShop with Divert Queues - Queue routing patterns
- Divert with Istio Sample - Istio driver configuration