概述

在 Kubernetes 环境中,经常需要从本地访问集群内部的服务(如 Redis、MySQL 等),但出于安全考虑,这些服务通常不对外暴露。本文介绍如何利用已有的 Pod 作为跳板,安全地从本地连接到内部服务。

核心方案:

  • 🔐 Pod 作为代理跳板
  • 🔄 socat 端口转发
  • 🛡️ kubectl port-forward 隧道
  • 🚀 无需暴露服务到公网

适用场景:

  • 访问内部数据库(Redis、MySQL、MongoDB)
  • 调试集群内部服务
  • 开发环境数据查询
  • 临时访问未暴露的服务

方案架构

网络拓扑

1
2
3
4
5
本地电脑 (127.0.0.1:6379)
↓ kubectl port-forward
Pod (my-app:6379)
↓ socat 代理
内部服务 (Redis:10.244.0.5:6379)

流量路径

1
2
3
4
5
6
7
本地 Redis 客户端
→ localhost:6379
→ kubectl port-forward 隧道
→ Pod:6379
→ socat 监听
→ 转发到 10.244.0.5:6379
→ 内部 Redis 服务

实战步骤

Step 0:准备工作

在目标 Pod 中安装必要工具:

1
2
3
4
5
6
7
8
9
10
11
12
# 进入 Pod
kubectl exec -it my-redis-client-pod -- sh

# 安装 socat 和网络工具
apt update
apt install -y socat net-tools

# 或在 Alpine 镜像中
apk add socat

# 验证安装
socat -V

检查网络连通性:

1
2
3
4
5
# 测试能否访问目标服务
telnet 10.244.0.5 6379

# 或使用 nc
nc -zv 10.244.0.5 6379

Step 1:找到跳板 Pod

列出所有 Pod:

1
2
3
4
5
6
7
8
# 查看所有 Pod
kubectl get pods

# 查看特定命名空间
kubectl get pods -n production

# 查看 Pod 详情(包含 IP)
kubectl get pods -o wide

选择合适的 Pod:

1
2
3
4
5
# 确认 Pod 能访问目标服务
kubectl exec -it my-redis-client-pod -- ping 10.244.0.5

# 查看 Pod 的网络配置
kubectl exec -it my-redis-client-pod -- ip addr

示例 Pod 名称: my-redis-client-pod

Step 2:在 Pod 内启动 socat 代理

方案1:前台运行(推荐用于调试)

1
2
3
# 直接运行 socat
kubectl exec -it my-redis-client-pod -- \
socat TCP-LISTEN:6379,fork,reuseaddr TCP:10.244.0.5:6379

参数说明:

参数说明
TCP-LISTEN:6379监听 TCP 端口 6379
fork为每个连接创建独立进程
reuseaddr允许端口重用
TCP:10.244.0.5:6379转发目标地址

方案2:后台运行(推荐用于长期运行)

1
2
3
4
# 后台运行 socat,输出到日志文件
kubectl exec -it my-redis-client-pod -- sh -c \
"nohup socat TCP-LISTEN:6379,fork,reuseaddr TCP:10.244.0.5:6379 \
> /tmp/socat_redis.log 2>&1 &"

验证 socat 运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 进入 Pod 检查
kubectl exec -it my-redis-client-pod -- sh

# 查看进程
ps aux | grep socat

# 查看端口监听
netstat -tlnp | grep 6379
# 或
ss -tlnp | grep 6379

# 查看日志
tail -f /tmp/socat_redis.log

Step 3:本地端口转发

建立 kubectl 隧道:

1
2
3
4
5
6
7
8
9
10
11
# 转发本地 6379 到 Pod 的 6379
kubectl port-forward my-redis-client-pod 6379:6379

# 使用不同的本地端口
kubectl port-forward my-redis-client-pod 16379:6379

# 指定命名空间
kubectl port-forward -n production my-redis-client-pod 6379:6379

# 监听所有网络接口(允许局域网访问)
kubectl port-forward --address 0.0.0.0 my-redis-client-pod 6379:6379

端口转发说明:

1
2
3
kubectl port-forward <pod-name> <local-port>:<pod-port>
↑ ↑
本地电脑端口 Pod 内 socat 监听端口

验证隧道:

1
2
3
4
5
# 在另一个终端测试连接
telnet localhost 6379

# 或使用 nc
nc -zv localhost 6379

Step 4:本地连接服务

使用 redis-cli:

1
2
3
4
5
6
7
8
9
# 连接本地转发端口
redis-cli -h 127.0.0.1 -p 6379

# 测试连接
127.0.0.1:6379> PING
PONG

# 查看信息
127.0.0.1:6379> INFO server

使用 Redis 图形化客户端:

1
2
3
4
5
6
工具:RedisInsight / Another Redis Desktop Manager

配置:
- Host: 127.0.0.1
- Port: 6379
- Name: K8s Internal Redis

使用 MySQL 客户端:

1
2
3
4
5
6
# 如果代理的是 MySQL
mysql -h 127.0.0.1 -P 3306 -u root -p

# DataGrip / Navicat 配置
Host: 127.0.0.1
Port: 3306

进阶用法

代理多个服务

同时代理 Redis 和 MySQL:

1
2
3
4
5
6
7
8
# 在 Pod 中启动两个 socat 进程
kubectl exec -it my-app-pod -- sh -c "
nohup socat TCP-LISTEN:6379,fork,reuseaddr TCP:redis-svc:6379 > /tmp/redis.log 2>&1 &
nohup socat TCP-LISTEN:3306,fork,reuseaddr TCP:mysql-svc:3306 > /tmp/mysql.log 2>&1 &
"

# 本地转发两个端口
kubectl port-forward my-app-pod 6379:6379 3306:3306

使用 Service 名称

通过 Service 访问(推荐):

1
2
3
4
5
6
7
# 使用 Service 名称而非 IP
kubectl exec -it my-app-pod -- \
socat TCP-LISTEN:6379,fork,reuseaddr TCP:redis-service:6379

# 跨命名空间访问
kubectl exec -it my-app-pod -- \
socat TCP-LISTEN:6379,fork,reuseaddr TCP:redis-service.production.svc.cluster.local:6379

持久化运行

创建专用代理 Pod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: port-proxy
namespace: default
spec:
containers:
- name: proxy
image: alpine/socat:latest
command:
- socat
- TCP-LISTEN:6379,fork,reuseaddr
- TCP:redis-service:6379
ports:
- containerPort: 6379

部署并使用:

1
2
3
4
5
# 部署代理 Pod
kubectl apply -f proxy-pod.yaml

# 端口转发
kubectl port-forward port-proxy 6379:6379

清理和停止

停止 socat 进程

方案1:前台运行时

1
# 直接按 Ctrl+C 停止

方案2:后台运行时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 进入 Pod
kubectl exec -it my-redis-client-pod -- sh

# 查找 socat 进程
ps aux | grep socat
# 输出示例:
# root 1234 0.0 0.0 12345 678 ? S 10:00 0:00 socat...

# 杀死进程
kill 1234

# 或强制杀死
kill -9 1234

# 批量清理所有 socat
pkill socat

使用脚本清理:

1
2
3
4
5
6
7
# 创建清理脚本
kubectl exec -it my-redis-client-pod -- sh -c '
for pid in $(ps aux | grep socat | grep -v grep | awk "{print \$2}"); do
echo "Killing socat process: $pid"
kill $pid
done
'

停止 kubectl port-forward

1
2
3
4
5
# 在运行 port-forward 的终端按 Ctrl+C

# 或查找并杀死进程
ps aux | grep "kubectl port-forward"
kill <pid>

故障排查

常见问题

问题1:socat 命令未找到

1
2
3
4
5
6
7
8
# 症状
sh: socat: not found

# 解决方案
kubectl exec -it my-app-pod -- sh
apt update && apt install -y socat # Debian/Ubuntu
apk add socat # Alpine
yum install -y socat # CentOS/RHEL

问题2:端口已被占用

1
2
3
4
5
6
7
8
9
10
11
# 症状
bind: Address already in use

# 检查端口占用
kubectl exec -it my-app-pod -- netstat -tlnp | grep 6379

# 解决方案1:使用不同端口
socat TCP-LISTEN:16379,fork,reuseaddr TCP:redis:6379

# 解决方案2:杀死占用进程
kill <pid>

问题3:无法连接到目标服务

1
2
3
4
5
6
7
8
9
# 测试网络连通性
kubectl exec -it my-app-pod -- telnet redis-service 6379
kubectl exec -it my-app-pod -- nc -zv redis-service 6379

# 检查 DNS 解析
kubectl exec -it my-app-pod -- nslookup redis-service

# 检查网络策略
kubectl get networkpolicies

问题4:kubectl port-forward 断开

1
2
3
4
5
6
7
8
9
10
11
# 症状
Forwarding from 127.0.0.1:6379 -> 6379
Handling connection for 6379
E1010 10:00:00.123456 123 portforward.go:400] an error occurred forwarding 6379 -> 6379: error forwarding port 6379 to pod..., uid : exit status 1

# 解决方案:添加超时和重试
while true; do
kubectl port-forward my-app-pod 6379:6379
echo "Port-forward disconnected, retrying in 5s..."
sleep 5
done

调试技巧

启用 socat 调试模式:

1
2
3
4
5
6
# 详细日志输出
socat -d -d TCP-LISTEN:6379,fork,reuseaddr TCP:redis:6379

# -d: 增加调试级别(可多次使用)
# -d -d: 详细日志
# -d -d -d: 非常详细的日志

监控流量:

1
2
3
4
5
# 在 Pod 中抓包
kubectl exec -it my-app-pod -- tcpdump -i any -n port 6379

# 查看连接状态
kubectl exec -it my-app-pod -- netstat -an | grep 6379

最佳实践

安全建议

建议说明
临时使用仅用于调试,不要长期运行
限制访问使用 NetworkPolicy 限制 Pod 网络
日志记录记录所有代理访问日志
清理资源使用完毕立即停止代理
⚠️ 生产环境谨慎使用,建议使用专用工具

性能优化

socat 参数优化:

1
2
3
4
5
6
7
8
9
10
# 增加连接池
socat TCP-LISTEN:6379,fork,reuseaddr,max-children=10 TCP:redis:6379

# 设置超时
socat TCP-LISTEN:6379,fork,reuseaddr,keepalive,keepidle=10 TCP:redis:6379

# 完整优化配置
socat \
TCP-LISTEN:6379,fork,reuseaddr,keepalive,keepidle=10,max-children=20 \
TCP:redis:6379,connect-timeout=10,keepalive,keepidle=10

生产环境替代方案

推荐使用专业工具:

方案说明推荐指数
Telepresence本地与集群环境集成⭐⭐⭐⭐⭐
kubectl exec直接在 Pod 中执行命令⭐⭐⭐⭐
VPN 接入通过 VPN 直接访问集群网络⭐⭐⭐⭐⭐
Bastion Host使用堡垒机跳板⭐⭐⭐⭐
Service MeshIstio/Linkerd 提供安全访问⭐⭐⭐⭐⭐

实用脚本

一键启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
# k8s-proxy.sh

POD_NAME=${1:-"my-app-pod"}
TARGET_HOST=${2:-"redis-service"}
TARGET_PORT=${3:-"6379"}
LOCAL_PORT=${4:-"6379"}

echo "Starting proxy..."
echo "Pod: $POD_NAME"
echo "Target: $TARGET_HOST:$TARGET_PORT"
echo "Local port: $LOCAL_PORT"

# 启动 socat
kubectl exec -it $POD_NAME -- sh -c \
"nohup socat TCP-LISTEN:$TARGET_PORT,fork,reuseaddr TCP:$TARGET_HOST:$TARGET_PORT > /tmp/socat.log 2>&1 &" &

sleep 2

# 端口转发
kubectl port-forward $POD_NAME $LOCAL_PORT:$TARGET_PORT

使用方式:

1
2
3
4
5
# 基本用法
./k8s-proxy.sh my-app-pod redis-service 6379 6379

# 简化用法(使用默认值)
./k8s-proxy.sh

清理脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# cleanup-proxy.sh

POD_NAME=${1:-"my-app-pod"}

echo "Cleaning up proxy in pod: $POD_NAME"

# 杀死 socat 进程
kubectl exec -it $POD_NAME -- sh -c '
for pid in $(ps aux | grep socat | grep -v grep | awk "{print \$2}"); do
echo "Killing process: $pid"
kill $pid
done
'

echo "Cleanup completed"

总结

核心要点:

  1. socat 代理:在 Pod 内部创建端口转发
  2. kubectl port-forward:建立本地到 Pod 的隧道
  3. 安全访问:无需暴露服务到公网
  4. 灵活组合:可代理任意 TCP 服务

适用场景:

1
2
3
4
5
✅ 开发调试
✅ 数据查询
✅ 问题排查
✅ 临时访问
❌ 生产环境长期使用(建议专业方案)

关键命令回顾:

1
2
3
4
5
6
7
8
9
10
11
# 1. 安装 socat
kubectl exec -it <pod> -- apt install -y socat

# 2. 启动代理
kubectl exec -it <pod> -- socat TCP-LISTEN:6379,fork TCP:<target>:6379

# 3. 端口转发
kubectl port-forward <pod> 6379:6379

# 4. 本地连接
redis-cli -h 127.0.0.1 -p 6379