起源
由于被监控设备无公网IP, 而且处于不同的内网环境, 为解决无法远程执行命令问题, 最初阶段使用的VPN模式,每个机器运行一个 VPN 容器,容器内部网络拨号的得到 一个VPN IP
外部请求这个VPN IP, VPN 容器再将端口转发到宿主机 (转发到容器NAT网关就是转发到宿主机) ,实现内网穿透
但后来发现, 有些设备处于互联网深处的多层 NAT 网络之下,无法大部分VPN 协议都无法穿透,后面决定使用 FRP 进行内往穿透,就有了本文
思路流程
- 在zabbix 的数据库中新建一个 frp 的数据库, 导入以下 Sql 命令
- 服务端新建一个 PHP 并开放接口, 以便 zabbix agent 请求接口获取一个随机端口, 并将端口绑定到
- 将获取到的端口用 frp 绑定到被监控的 zabbix-agent 客户端端口 也就是zabbix agent 配置中的 ListenPort
- 使用脚本将服务端的端口改成 frp 端口, 如下图,箭头指向的两个值都是脚本自动更新的,左边 frp 代理 zabbix-agent 端口, 右边代理 ssh 端口
- 如果使用脚本对进行修改参考 Zabbix Api https://www.zabbix.com/documentation/5.0/zh/manual/api/reference/configuration
脚本
用于获取 frp 端口的PHP脚本
frp.php
<?php
include_once $_SERVER['DOCUMENT_ROOT'] . '/script/php/extend/frp_base.php';
class Controller
{
public function index()
{
return '您好!这是一个[api]示例应用';
}
public function Get()
{
$v['obj'] = new FrpBase();
$v['get'] = $_POST;
$v['json'] = json_decode($v['get']['json'], true);
$v['ret']['hostname'] = $v['json']['hostname'];
foreach ($v['json']['port'] as $key => $value) {
$v['ret']['port'][$value] = $v['obj']->select_service_port($v['json']['hostname'], $value);
}
// print_r($v['ret']);
echo json_encode($v['ret']);
// $v['port'] = $v['obj']->select_service_port();
// return json_encode($v['port']);
// print_r($v);
}
function __construct()
{
if ($_GET['i'] == 'Get') {
$this->Get();
}
}
}
$use = new Controller();
if (false) {
$help = <<<USE
# view-source:http://zabbix.dcache.kuaicdn.cn/script/php/controller/frp.php?i=Get&json={ "hostname": "00A01FB40CC46036-dcache", "port": ["22","12104"] }
USE;
}
frp_base.php
<?php
class Connect_mysql
{
/* 成员变量 */
var $info;
var $pdo;
function connect()
{
try {
$this->pdo = new PDO(
"mysql:host=" . $this->info["host"] . ";dbname=" . $this->info["db_name"],
$this->info["db_user"],
$this->info["db_pwd"]
); //创建一个pdo对象
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 设置sql语句查询如果出现问题 就会抛出异常
//set_exception_handler("cus_exception_handler");
} catch (PDOException $e) {
die("connect error:" . $e->getMessage());
}
$this->pdo->exec("set names 'utf8'");
}
function init()
{
$this->info = array(
'host' => 'localhost',
'db_name' => 'frp',
'db_user' => 'root',
'db_pwd' => 'passwd',
);
}
function __construct()
{
$this->init();
$this->connect();
}
}
class FrpBase
{
public $v = [];
public $pdo;
function __construct()
{
$connect_mysql = new Connect_mysql();
$this->pdo = $connect_mysql->pdo;
}
function new_record($hostname, $port)
{
# 查询并锁行
$v['args']['hostname'] = $hostname;
$v['args']['port'] = $port;
$this->pdo->beginTransaction();
$v['sql'] = "SELECT * FROM `port` ORDER BY `port`.`time_last` ASC, `port`.`port_service` ASC limit 1 for UPDATE";
$v['stmt'] = $this->pdo->prepare($v['sql']);
$v['rs'] = $v['stmt']->execute();
$v['select'] = $v['stmt']->fetch(PDO::FETCH_ASSOC);
# 更新
$v['stmt'] = $this->pdo->prepare("UPDATE `port` SET `time_last` = current_timestamp(), `host_sign` = :host_sign, `port_app` = :port_app WHERE `port`.`port_service` = :port_service");
$v['stmt']->bindValue(":port_app", $v['args']['port'], PDO::PARAM_STR);
$v['stmt']->bindValue(":host_sign", $v['args']['hostname'], PDO::PARAM_STR);
$v['stmt']->bindValue(":port_service", $v['select']['port_service'], PDO::PARAM_STR);
$v['execute'] = $v['stmt']->execute();
$this->pdo->commit();
return $v['select'];
}
function select_service_port($hostname, $port)
{
$v['args']['hostname'] = $hostname;
$v['args']['port'] = $port;
$v['sql'] = "SELECT * FROM `port` WHERE `port_app` = :port_app AND `host_sign` = :host_sign";
$v['stmt'] = $this->pdo->prepare($v['sql']);
$v['stmt']->bindValue(":port_app", $v['args']['port'], PDO::PARAM_STR);
$v['stmt']->bindValue(":host_sign", $v['args']['hostname'], PDO::PARAM_STR);
$v['execute'] = $v['stmt']->execute();
$v['select'] = $v['stmt']->fetch(PDO::FETCH_ASSOC);
if (!isset($v['select']['port_service'])) {
$v['select'] = $this->new_record($v['args']['hostname'], $v['args']['port']);
$v['select'] = $this->select_service_port($hostname, $port);
} else {
$v['update'] = $this->pdo->exec("UPDATE `port` SET `time_last` = current_timestamp() WHERE `port`.`port_service` = " . $v['select']["port_service"]);
}
$this->dev[__METHOD__] = $v;
return $v['select'];
}
}