1. 背景与场景说明
在企业网络环境中,有些 Rancher 集群位于内网或受限网络中,无法直接从外网访问。
此时,我们可以借助 VPN(如 Sangfor EasyConnect)建立内网访问通道,从本地或 CI/CD 环境发布和管理 Rancher 应用。
本文将详细介绍如何通过 EasyConnect VPN 执行 Rancher 应用的部署操作。
方案介绍
- 通过hagb/docker-easyconnect:cli连接vpn
- 在vpn 镜像中 放入deploy脚本 ps: 为什么放到这里,因为kubectl脚本执行,需要能访问对应的网络,如果放在别的地方就需要对网络做NET映射,非常麻烦,而且不合适
- 搭建deploy服务,解析传入参数,找到对应的vpn执行发布脚本
vpn Dockerfile
# 基于 CLI 镜像
FROM hagb/docker-easyconnect:cli
USER root
# 安装基础工具
RUN apt-get update && \
apt-get install -y \
iputils-ping \
iproute2 \
net-tools \
iptables \
jq \
procps \
ca-certificates \
bash \
&& rm -rf /var/lib/apt/lists/*
# ===== COPY 本地 kubectl 到镜像 =====
COPY deploy/kubectl /usr/local/bin/kubectl
RUN chmod +x /usr/local/bin/kubectl
# 注意:kubectl version 检查在跨架构构建时会失败,实际运行时再验证
# ===== kubeconfig & deploy 脚本 =====
RUN mkdir -p /kubeconfig
COPY configs/kubeconfig/ /kubeconfig/
COPY deploy/deploy.sh /usr/local/bin/deploy.sh
RUN chmod +x /usr/local/bin/deploy.sh
# ⚠️ 不要加 ENTRYPOINT / CMD,会导致 docker-easyconnect:cli 登录失败
deploy.sh 这里你可能需要根据自己的公司的项目情况做改动,主要是DEPLOY_NAMESPACE的解析和KCFG的路径解析
#!/bin/bash
set -e
# 传入参数
PLATFORM=$1 # xinhee / shanshan
DEPLOY_ENV=$2 # test / prod
DEPLOY_KEY=$3 # deployment 名称
IMAGE_VERSION=$4 # 镜像 tag 比如 registry/app:v1
DEPLOY_NAMESPACE=$5 # 默认 web-cloud-test
if [ -z "$PLATFORM" ] || [ -z "$DEPLOY_ENV" ] || [ -z "$DEPLOY_KEY" ] || [ -z "$IMAGE_VERSION" ]; then
echo "参数不足,需要:PLATFORM DEPLOY_ENV DEPLOY_KEY IMAGE_VERSION"
exit 1
fi
# 如何deploy_env = main prod pre 则 使用 prod 对应的 kubeconfig
if [ "$DEPLOY_ENV" = "main" ] || [ "$DEPLOY_ENV" = "prod" ]; then
DEPLOY_ENV="prod"
fi
# 如何deploy_env = test stage dev 则 使用 test 对应的 kubeconfig
if [ "$DEPLOY_ENV" = "test" ] || [ "$DEPLOY_ENV" = "stage" ]; then
DEPLOY_ENV="test"
fi
# 如果 DEPLOY_NAMESPACE 没传,
if [ -z "$DEPLOY_NAMESPACE" ]; then
if [ "$DEPLOY_ENV" = "prod" ]; then
DEPLOY_NAMESPACE="web-cloud"
elif [ "$DEPLOY_ENV" = "test" ]; then
DEPLOY_NAMESPACE="web-cloud-test"
fi
fi
# 动态选 kubeconfig
KCFG="/kubeconfig/${PLATFORM}.${DEPLOY_ENV}.yml"
if [ ! -f "$KCFG" ]; then
echo "kubeconfig 不存在: $KCFG"
exit 1
fi
export KUBECONFIG="$KCFG"
echo "使用 kubeconfig:$KCFG"
echo "部署:deployment/$DEPLOY_KEY"
echo "镜像:$IMAGE_VERSION"
echo "部署 namespace:$DEPLOY_NAMESPACE"
# 更新镜像
kubectl -n "$DEPLOY_NAMESPACE" set image deployment/"$DEPLOY_KEY" \
"$DEPLOY_KEY"="$IMAGE_VERSION" --record
# 查看 rollout 过程
kubectl -n "$DEPLOY_NAMESPACE" rollout status deployment/"$DEPLOY_KEY"
发布核心代码,我这里是用的nodejs,你可以自己选择使用的语言和方式
import { Injectable } from '@nestjs/common';
import { exec } from 'child_process';
import { promisify } from 'util';
import { readFileSync } from 'fs';
import { join } from 'path';
const execAsync = promisify(exec);
@Injectable()
export class DockerService {
/**
* 获取当前运行的容器列表
*/
async listContainers(): Promise<string[]> {
try {
const { stdout } = await execAsync(
'docker ps --format "{{.Names}}"',
{ maxBuffer: 1024 * 1024 }
);
return stdout
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0);
} catch (error) {
return [];
}
}
async getVpn(platform: string): Promise<any> {
const configPath = join(__dirname, 'config.json');
const config = readFileSync(configPath, 'utf8');
const configData = JSON.parse(config);
const vpn = configData.vpns.find((v: any) => v.name === platform);
return vpn;
}
/**
* 执行部署命令
*/
async deployVpn(
platform: string,
deployEnv: string,
deployKey: string,
imageVersion: string,
namespace: string,
): Promise<{ success: boolean; output?: string; error?: string }> {
const vpn = await this.getVpn(platform);
const dockerName = vpn ? platform : 'docker-default-cli';
const platformName = platform.replace('vpn_', '');
const cmd = [
'docker',
'exec',
dockerName,
'bash',
'/usr/local/bin/deploy.sh',
platformName,
deployEnv,
deployKey,
imageVersion,
namespace,
].join(' ');
try {
const { stdout } = await execAsync(cmd, {
maxBuffer: 10 * 1024 * 1024, // 10MB
timeout: 1200000, // 20 minutes
});
return {
success: true,
output: stdout,
};
} catch (error: any) {
return {
success: false,
error: error.stdout || error.stderr || error.message || 'deploy failed',
};
}
}
/**
* Ping VPN 容器
*/
async pingVpn(platform: string): Promise<{
success: boolean;
server?: string;
error?: string;
}> {
const containers = await this.listContainers();
if (!containers.includes(platform)) {
return {
success: false,
error: `container ${platform} not running`,
};
}
try {
// 根据 platform 从config.json 中读取server
const vpn = await this.getVpn(platform);
if (!vpn) {
return {
success: false,
error: `vpn ${platform} not found`,
};
}
const vpnServer = vpn.server;
const vpnHost = vpnServer.split(':')[0];
// 在容器内部 ping VPN 地址
try {
await execAsync(
`docker exec ${platform} ping -c 2 -W 2 ${vpnHost}`,
{ maxBuffer: 1024 * 1024 }
);
return {
success: true,
server: vpnServer,
};
} catch (pingError) {
return {
success: false,
server: vpnServer,
};
}
} catch (error: any) {
return {
success: false,
error: error.message || String(error),
};
}
}
}
完整项目地址
项目使用介绍
添加kubeconfig配置
- 登录rancher 找到对应的project 右上角 copy kubeConfig clipboard
- 文件命名规范,projectName.env.yml
添加vpn 配置,命名为 config.json 如果需要vpn的话, 支持多个vpn, 不需要vpn则不用配,格式如下
{
"name": "vpn_${projectName}",
"server": "${vpn_server}",
"user": "${vpn_user}",
"pass": "${vpn_pass}",
"ec_ver": "7.6.7"
}
下载 kubectl-cli 放到 deploy 目录下 命名为kubectl
参考download-kubectl.sh
下载 docker-cli 放到server目录下 docker-cli/docker
参考 download-docker-cli.sh
安装docker-compose依赖
cd docker-compose yarn install sh start.sh
docker 使用的是主机的docker,所以需要你的主机安装docker
测试网络是否正常
curl -X POST http://localhost:9090/ping \
-H "Content-Type: application/json" \
-d '{"platform": "vpn_projectName"}'
返回success true则为正常
测试发布
curl -X POST http://localhost:9090/deploy -H "Content-Type: application/json" -d '{
"platform": "vpn_projectName",
"env": "test",
"key": "scm-saas",
"image": "your_image_name"
}'
其他介绍,如何通过NAT,让主机能访问容器内的网络
如果我就想在主机上通过docker内的网络访问,如何打通网络
- 容器内开启转发 + NAT: sysctl -w net.ipv4.ip_forward=1
允许 Linux 把“不是发给自己”的 IP 包,从一个网卡转发到另一个网卡 收到包
目的IP = 本机 → 本地处理
目的IP ≠ 本机 → 查路由表 → 从另一个接口发出去
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
把“从容器出去、走 tun0 的包”,源 IP 改成 tun0 自己的 IP
- 查看容器路由信息,找到需要跳转的ip
- 宿主机添加路由: sudo route add -host 172.16.5.42 172.19.0.3
- 测试 ping: ping 172.16.5.42
- 如果成功,包走向如下: 宿主机 → docker0 → 容器 (veth) → tun0 → VPN → 内网目标 内网目标 → VPN → 容器 → docker0 → 宿主机