Port Forwarding
The Kubernetes adapter provides comprehensive port forwarding capabilities, enabling secure tunneling from local ports to services running in pods. This includes support for static and dynamic port allocation, service integration, and advanced forwarding patterns for development and debugging workflows.
Basic Port Forwarding
Simple Port Forward
Forward local ports to pod ports:
import { $ } from '@xec-sh/core';
import { KubernetesAdapter } from '@xec-sh/core';
const k8s = new KubernetesAdapter();
// Forward local port 8080 to pod port 80
const portForward = await k8s.portForward(
'nginx-pod', // Pod name
8080, // Local port
80, // Remote port (in pod)
{
namespace: 'production'
}
);
await portForward.open();
console.log(`Port forward active: localhost:${portForward.localPort} -> pod:${portForward.remotePort}`);
// Use the forwarded port
const response = await $`curl -s http://localhost:8080/health`;
console.log('Health check:', response.stdout);
// Close port forward when done
await portForward.close();
Dynamic Port Allocation
Let the system choose an available local port:
// Use dynamic port allocation (local port = 0)
const dynamicPortForward = await k8s.portForward(
'api-server-pod',
0, // Dynamic local port
8080, // Remote port
{
namespace: 'production',
dynamicLocalPort: true
}
);
await dynamicPortForward.open();
console.log(`Dynamic port forward: localhost:${dynamicPortForward.localPort} -> pod:8080`);
// The actual port is available after opening
const localPort = dynamicPortForward.localPort;
await $`curl -s http://localhost:${localPort}/api/status`;
await dynamicPortForward.close();
Label-Based Port Forwarding
Forward to pods selected by labels:
// Find pod by label and forward
const selectedPod = await k8s.getPodFromSelector('app=web-server', 'production');
if (!selectedPod) {
throw new Error('No pod found matching selector');
}
const labelPortForward = await k8s.portForward(
selectedPod,
3000,
8080,
{ namespace: 'production' }
);
await labelPortForward.open();
console.log(`Forwarding to selected pod: ${selectedPod}`);
// Test the forwarded connection
await $`curl -s http://localhost:3000/metrics`;
await labelPortForward.close();
Pod API Integration
Using Pod Context
Use the pod API for convenient port forwarding:
import { $k8s } from '@xec-sh/core';
// Create Kubernetes context
const k8s = $k8s({
namespace: 'production'
});
// Get pod instance
const webPod = k8s.pod('web-server-pod');
// Static port forwarding
const staticForward = await webPod.portForward(8080, 80);
console.log(`Static forward: localhost:${staticForward.localPort} -> pod:${staticForward.remotePort}`);
// Dynamic port forwarding
const dynamicForward = await webPod.portForwardDynamic(3000);
console.log(`Dynamic forward: localhost:${dynamicForward.localPort} -> pod:3000`);
// Use forwarded ports
await $`curl -s http://localhost:${staticForward.localPort}/`;
await $`curl -s http://localhost:${dynamicForward.localPort}/api`;
// Cleanup
await staticForward.close();
await dynamicForward.close();
Multiple Port Forwards
Manage multiple port forwards for a single pod:
const dbPod = k8s.pod('database-pod');
// Forward multiple ports
const pgForward = await dbPod.portForward(5432, 5432); // PostgreSQL
const redisForward = await dbPod.portForwardDynamic(6379); // Redis
const adminForward = await dbPod.portForwardDynamic(8080); // Admin interface
console.log('Database connections:');
console.log(`PostgreSQL: localhost:${pgForward.localPort}`);
console.log(`Redis: localhost:${redisForward.localPort}`);
console.log(`Admin: localhost:${adminForward.localPort}`);
// Test database connections
await $`pg_isready -h localhost -p ${pgForward.localPort}`;
await $`redis-cli -h localhost -p ${redisForward.localPort} ping`;
await $`curl -s http://localhost:${adminForward.localPort}/status`;
// Cleanup all forwards
await Promise.all([
pgForward.close(),
redisForward.close(),
adminForward.close()
]);
Service Port Forwarding
Service-to-Pod Forwarding
Forward to services and their underlying pods:
// Get service endpoints
async function getServiceEndpoints(serviceName, namespace) {
const result = await k8s.executeKubectl([
'get', 'endpoints', serviceName,
'-n', namespace,
'-o', 'jsonpath={.subsets[0].addresses[0].targetRef.name}'
]);
return result.stdout.trim();
}
// Forward to service backend
const servicePod = await getServiceEndpoints('web-service', 'production');
const serviceForward = await k8s.portForward(
servicePod,
8080,
80,
{ namespace: 'production' }
);
await serviceForward.open();
// Test service connectivity
await $`curl -s http://localhost:8080/api/health`;
await serviceForward.close();
Load Balancer Integration
Integrate with Kubernetes load balancing:
// Forward to multiple service replicas
async function forwardToAllReplicas(serviceName, namespace, remotePort) {
// Get all pods behind service
const podsResult = await k8s.executeKubectl([
'get', 'pods',
'-l', `app=${serviceName}`,
'-n', namespace,
'-o', 'jsonpath={.items[*].metadata.name}'
]);
const pods = podsResult.stdout.trim().split(' ').filter(Boolean);
const forwards = [];
// Create port forwards for each replica
for (const pod of pods) {
const forward = await k8s.portForward(
pod,
0, // Dynamic port
remotePort,
{ namespace, dynamicLocalPort: true }
);
await forward.open();
forwards.push({ pod, forward });
}
return forwards;
}
// Forward to all web server replicas
const replicaForwards = await forwardToAllReplicas('web-server', 'production', 8080);
console.log('Replica forwards:');
replicaForwards.forEach(({ pod, forward }) => {
console.log(`${pod}: localhost:${forward.localPort}`);
});
// Test load distribution
for (const { pod, forward } of replicaForwards) {
const response = await $`curl -s http://localhost:${forward.localPort}/hostname`;
console.log(`${pod} hostname:`, response.stdout.trim());
}
// Cleanup all replica forwards
await Promise.all(replicaForwards.map(({ forward }) => forward.close()));
Advanced Forwarding Patterns
Conditional Port Forwarding
Implement conditional forwarding based on pod status:
async function conditionalPortForward(podSelector, namespace, localPort, remotePort) {
// Find healthy pod
const pods = await k8s.executeKubectl([
'get', 'pods',
'-l', podSelector,
'-n', namespace,
'--field-selector=status.phase=Running',
'-o', 'jsonpath={.items[*].metadata.name}'
]);
const podList = pods.stdout.trim().split(' ').filter(Boolean);
for (const pod of podList) {
if (await k8s.isPodReady(pod, namespace)) {
console.log(`Found healthy pod: ${pod}`);
const forward = await k8s.portForward(pod, localPort, remotePort, { namespace });
await forward.open();
// Verify connectivity
try {
await $`curl -f --max-time 5 http://localhost:${localPort}/health`;
console.log(`Port forward established to healthy pod: ${pod}`);
return forward;
} catch (error) {
console.warn(`Pod ${pod} not responding, trying next...`);
await forward.close();
}
}
}
throw new Error(`No healthy pods found for selector: ${podSelector}`);
}
// Use conditional forwarding
try {
const healthyForward = await conditionalPortForward(
'app=api,tier=backend',
'production',
8080,
80
);
// Use the healthy connection
await $`curl -s http://localhost:8080/api/data`;
await healthyForward.close();
} catch (error) {
console.error('Failed to establish healthy connection:', error.message);
}
Resilient Port Forwarding
Implement resilient forwarding with automatic recovery:
class ResilientPortForward {
constructor(k8s, podSelector, namespace, localPort, remotePort) {
this.k8s = k8s;
this.podSelector = podSelector;
this.namespace = namespace;
this.localPort = localPort;
this.remotePort = remotePort;
this.currentForward = null;
this.isActive = false;
}
async start() {
this.isActive = true;
await this.establishConnection();
this.startHealthMonitoring();
}
async establishConnection() {
try {
const pod = await this.k8s.getPodFromSelector(this.podSelector, this.namespace);
if (!pod) {
throw new Error(`No pod found for selector: ${this.podSelector}`);
}
if (this.currentForward) {
await this.currentForward.close();
}
this.currentForward = await this.k8s.portForward(
pod,
this.localPort,
this.remotePort,
{ namespace: this.namespace }
);
await this.currentForward.open();
console.log(`Resilient forward established: localhost:${this.localPort} -> ${pod}:${this.remotePort}`);
} catch (error) {
console.error('Failed to establish connection:', error.message);
if (this.isActive) {
setTimeout(() => this.establishConnection(), 5000);
}
}
}
startHealthMonitoring() {
const healthCheck = async () => {
if (!this.isActive) return;
try {
// Test connectivity
await $`curl -f --max-time 3 http://localhost:${this.localPort}/health`;
} catch (error) {
console.warn('Health check failed, re-establishing connection...');
await this.establishConnection();
}
if (this.isActive) {
setTimeout(healthCheck, 10000); // Check every 10 seconds
}
};
setTimeout(healthCheck, 10000);
}
async stop() {
this.isActive = false;
if (this.currentForward) {
await this.currentForward.close();
this.currentForward = null;
}
}
get localPortNumber() {
return this.currentForward?.localPort;
}
get isConnected() {
return this.currentForward?.isOpen || false;
}
}
// Use resilient port forwarding
const resilientForward = new ResilientPortForward(
k8s,
'app=web-server',
'production',
8080,
80
);
await resilientForward.start();
// Use the resilient connection
setInterval(async () => {
if (resilientForward.isConnected) {
try {
const response = await $`curl -s http://localhost:${resilientForward.localPortNumber}/timestamp`;
console.log('Service response:', response.stdout.trim());
} catch (error) {
console.error('Service call failed:', error.message);
}
}
}, 5000);
// Stop after 60 seconds
setTimeout(async () => {
await resilientForward.stop();
}, 60000);
Multi-Protocol Forwarding
Support multiple protocols through port forwarding:
// Forward multiple protocols for a database pod
async function setupDatabaseForwarding(podName, namespace) {
const dbPod = k8s.pod(podName);
// PostgreSQL main connection
const pgMain = await dbPod.portForward(5432, 5432);
// PostgreSQL streaming replication
const pgStream = await dbPod.portForwardDynamic(5433);
// HTTP admin interface
const adminHttp = await dbPod.portForwardDynamic(8080);
// Metrics endpoint
const metrics = await dbPod.portForwardDynamic(9187);
console.log('Database forwarding setup:');
console.log(`PostgreSQL: localhost:${pgMain.localPort}`);
console.log(`Replication: localhost:${pgStream.localPort}`);
console.log(`Admin: http://localhost:${adminHttp.localPort}`);
console.log(`Metrics: http://localhost:${metrics.localPort}/metrics`);
// Test all connections
await Promise.all([
$`pg_isready -h localhost -p ${pgMain.localPort}`,
$`curl -f http://localhost:${adminHttp.localPort}/health`,
$`curl -f http://localhost:${metrics.localPort}/metrics`
]);
return { pgMain, pgStream, adminHttp, metrics };
}
// Setup and use multi-protocol forwarding
const dbForwards = await setupDatabaseForwarding('postgres-primary', 'production');
// Use different protocols
await $`psql -h localhost -p ${dbForwards.pgMain.localPort} -U admin -c "SELECT version();"`;
await $`curl -s http://localhost:${dbForwards.adminHttp.localPort}/api/stats`;
// Cleanup
await Promise.all(Object.values(dbForwards).map(forward => forward.close()));