一个自由职业程序员的树莓派:内网穿透篇

阅读本篇之前,建议您先了解 基础准备篇 的内容。

《一个自由职业程序员的树莓派》系列文章包括:

对于云盘来说,是 P2P 的技术,所以远程访问时是互相知道对方的,可以直接同步数据。但如果在梅莓派上部署了网站服务,要让不知道访问来源的任何电脑访问,就不得行了。我们必须要有一个公网的 IP 地址,才能对外提供网站服务。

另外,对于自由职业程序员来说,如果要将重要的版本控制代码、项目资料托管在云主机上,总会感觉不那么安全;又或者,如果要开发、测试或演示自己的软件程序,部署大型程序非常占用主机资源,全部买云主机又不划算。这时,我们就可以用到今天说的内网穿透技术了。

我们可以通过手中的树莓派设备,用最小化配置和最简洁的方式,满足自由职业程序员随时随地能开发和演示的需要。

下面,我们就开始来亲手打造吧。

  1. 准备工作
  2. 云主机上安装 frp server
  3. 树莓派上安装 frp client
  4. 远程登录并访问树莓派服务

准备工作

先罗列一下本篇需要准备的资源:

  • 一台核心的树莓派4B设备(推荐:4GB+内存版本,32GB+ TF 卡,1TB 固态硬盘);
  • 一台带公网 IP 的低配置云主机(推荐:1核CPU、1GB内存即可、2MB带宽即可);
  • 一个域名(国内用请先备案)。

为了方便后续介绍,先做以下假定:
对云主机

  • 云主机公网 IP:11.22.33.44
  • 操作系统:Ubuntu,64-bit
  • 默认工作目录:~ (/home/ubuntu)

对树莓派设备

  • 树莓派内网 IP:192.168.31.199
  • 操作系统:Ubuntu on Raspberry Pi,64-bit
  • 默认工作目录:~ (/home/ubuntu)

然后,我们假定自己购买的域名为:mydomain.com,并已解析到公网 IP 11.22.33.44 上。另外假定:
访问云主机上服务的域名和端口:

  • frps dashboard:mydomain.com:7500

访问梅莓派上服务的二级域名:

  • frpc 管理控制台:frpc.mydomain.com
  • 容器管理控制台:portainer.mydomain.com
  • 私有网盘控制台:syncthing.mydomain.com
  • git 代码托管:code.mydomain.com
  • (未来可扩展更多可访问的服务)

云主机上安装 frp server

通过 ssh 登录到云主机。

ssh ubuntu@11.22.33.44

如果没有安装 wget 等相关工具,可提前用 snap 或 apt 安装好。

然后按如下命令安装 frps (frp server),这里用的 0.35.1,你也可以再看看有没有 新版本

# 下载并解压程序包
sudo wget https://github.com/fatedier/frp/releases/download/v0.35.1/frp_0.35.1_linux_amd64.tar.gz
sudo tar -zxvf frp_0.35.1_linux_amd64.tar.gz

# 复制程序和配置文件到系统目录
sudo mv frp_0.35.1_linux_amd64 frp
sudo chmod +x frp/frps
sudo cp frp/frps /usr/bin/
sudo mkdir -p /etc/frp/
sudo cp frp/frps.ini /etc/frp/

# 复制 service 配置文件,以便开机以服务的方式自启动
sudo cp frp/systemd/frps@.service /lib/systemd/system/frps@.service

接下来,参考完整配置格式 frps_full.ini,并根据实际情况,编辑自己的配置文件 frps.ini。

sudo vi /etc/frp/frps.ini
# [common] is integral section
[common]
# A literal address or host name for IPv6 must be enclosed
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
bind_addr = 0.0.0.0
bind_port = 7000

# udp port to help make udp hole to penetrate nat
bind_udp_port = 7001

# udp port used for kcp protocol, it can be same with 'bind_port'
# if not set, kcp is disabled in frps
kcp_bind_port = 7000

# specify which address proxy will listen for, default value is same with bind_addr
# proxy_bind_addr = 127.0.0.1

# if you want to support virtual host, you must set the http port for listening (optional)
# Note: http port and https port can be same with bind_port
vhost_http_port = 80
vhost_https_port = 443

# response header timeout(seconds) for vhost http server, default is 60s
# vhost_http_timeout = 60

# TcpMuxHttpConnectPort specifies the port that the server listens for TCP
# HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP
# requests on one single port. If it's not - it will listen on this value for
# HTTP CONNECT requests. By default, this value is 0.
# tcpmux_httpconnect_port = 1337

# set dashboard_addr and dashboard_port to view dashboard of frps
# dashboard_addr's default value is same with bind_addr
# dashboard is available only if dashboard_port is set
dashboard_addr = 0.0.0.0
dashboard_port = 7500

# dashboard user and passwd for basic auth protect, if not set, both default value is admin
dashboard_user = admin
dashboard_pwd = mypassword

# enable_prometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api.
enable_prometheus = true

# dashboard assets directory(only for debug mode)
# assets_dir = ./static
# console or real logFile path like ./frps.log
log_file = /var/log/frp/frps.log

# trace, debug, info, warn, error
log_level = info

log_max_days = 3

# disable log colors when log_file is console, default is false
disable_log_color = false

# DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true.
detailed_errors_to_client = true

# AuthenticationMethod specifies what authentication method to use authenticate frpc with frps.
# If "token" is specified - token will be read into login message.
# If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token".
authentication_method = token

# AuthenticateHeartBeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
authenticate_heartbeats = false

# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
authenticate_new_work_conns = false

# auth token
token = mytoken

# OidcClientId specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
# By default, this value is "".
oidc_client_id =

# OidcClientSecret specifies the client secret to use to get a token in OIDC authentication if AuthenticationMethod == "oidc".
# By default, this value is "".
oidc_client_secret = 

# OidcAudience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
oidc_audience = 

# OidcTokenEndpointUrl specifies the URL which implements OIDC Token Endpoint.
# It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "".
oidc_token_endpoint_url = 

# heartbeat configure, it's not recommended to modify the default value
# the default value of heartbeat_timeout is 90
# heartbeat_timeout = 90

# only allow frpc to bind ports you list, if you set nothing, there won't be any limit
#allow_ports = 2000-3000,3001,3003,4000-50000

# pool_count in each proxy will change to max_pool_count if they exceed the maximum value
max_pool_count = 5

# max ports can be used for each client, default value is 0 means no limit
max_ports_per_client = 0

# TlsOnly specifies whether to only accept TLS-encrypted connections. By default, the value is false.
tls_only = false

# tls_cert_file = server.crt
# tls_key_file = server.key
# tls_trusted_ca_file = ca.crt

# if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file
# when subdomain is test, the host used by routing is test.frps.com
subdomain_host = mydomain.com

# if tcp stream multiplexing is used, default is true
tcp_mux = true

# custom 404 page for HTTP requests
# custom_404_page = /path/to/404.html

# specify udp packet size, unit is byte. If not set, the default value is 1500.
# This parameter should be same between client and server.
# It affects the udp and sudp proxy.
udp_packet_size = 1500

在官方的完整默认配置基础上,主要做了这几点改动:

dashboard_pwd = mypassword
log_file = /var/log/frp/frps.log
token = mytoken
#allow_ports = 2000-3000,3001,3003,4000-50000
subdomain_host = mydomain.com

说明:

  • dashboard_pwd:frps 管理控制台登录密码,建议你修改一下为你自己的密码;
  • log_file:改放到系统常规放置的地方,注意 chmod 文件权限,否则可能出现无权限问题,比如:
# 创建日志文件
sudo mkdir -p /var/log/frp
sudo touch /var/log/frp/frps.log
sudo chmod -R 666 /var/log/frp/frps.log
  • token: frp client 与 server 之间的约定令牌,两端必须相同。建议自己通过 uuidgen 生成一个唯一的令牌,比如:
uuidgen | sed 's/-//g'
ff9acab846cd4de88d5bc8840fa181e2
  • allow_ports:为了方便开发和测试,暂不配置,即允许所有端口(待需要时再添加)。
  • subdomain:填写自己购买的域名,方便 vhost 配置的子域名访问。

接下来,使用 @user 方式启动或管理服务。因为树莓派的 Ubuntu 系统默认用户是 ubuntu,所以按以下命令操作即可。

# 重命名配置文件
sudo mv /etc/frp/frps.ini /etc/frp/ubuntu.ini
# 注册和启动服务
sudo systemctl enable frps@ubuntu.service
sudo systemctl start frps@ubuntu.service

如果要检查是否启动成功、查看日志等,可以使用:

# 查询服务状态
sudo systemctl status frps@ubuntu.service
# 查看服务日志
sudo journalctl -e -u frps@ubuntu.service

其它可能还会偶尔用到的命令:

# 停止、重加载、重启或关闭服务
sudo systemctl stop frps@ubuntu.service
sudo systemctl daemon-reload
sudo systemctl restart frps@ubuntu.service
sudo systemctl disable frps@ubuntu.service

补充1:
如果配置了 vhost 参数,希望直接使用 80 和 443 这种 1024 以下的端口,可能出现无法启动 frps 的情况,查看服务日志,可以看到是因为无权限。

frps[126933]: Create vhost http listener error, listen tcp 0.0.0.0:80: bind: permission denied

这时需要 +s 提升当前启动服务的用户权限:

sudo chmod u+s /usr/bin/frps
ls -l /usr/bin/frps
-rwsr-xr-x 1 root root 13205504 Oct 24 22:48 /usr/bin/frps

树莓派上安装 frp client

下载并解压程序包:

sudo wget https://github.com/fatedier/frp/releases/download/v0.35.1/frp_0.35.1_linux_arm64.tar.gz
sudo tar -zxvf frp_0.35.1_linux_arm64.tar.gz

# 复制程序和配置文件到系统目录:
sudo mv frp_0.35.1_linux_arm64 frp
sudo chmod +x frp/frpc
sudo cp frp/frpc /usr/bin/
sudo mkdir -p /etc/frp/
sudo cp frp/frpc.ini /etc/frp/

复制 service 配置文件,以便开机以服务的方式自启动:

sudo cp frp/systemd/frpc@.service /lib/systemd/system/frpc@.service

接下来,参考完整配置格式 frpc_full.ini,并根据实际情况,编辑自己的配置文件 frpc.ini。

# [common] is integral section
[common]
# A literal address or host name for IPv6 must be enclosed
# in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80"
server_addr = 11.22.33.44
server_port = 7000

# console or real logFile path like ./frpc.log
log_file = /var/log/frp/frpc.log

# trace, debug, info, warn, error
log_level = info

log_max_days = 3

# disable log colors when log_file is console, default is false
disable_log_color = false

# for authentication
token = mytoken

# set admin address for control frpc's action by http api such as reload
admin_addr = 0.0.0.0
admin_port = 7400
admin_user = admin
admin_pwd =mypassword
# Admin assets directory. By default, these assets are bundled with frpc.
# assets_dir = ./static

# connections will be established in advance, default value is zero
#pool_count = 5

# if tcp stream multiplexing is used, default is true, it must be same with frps
tcp_mux = true

# your proxy name will be changed to {user}.{proxy}
user = raspi

# 'ssh' is the unique proxy name
# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
[ssh]
# tcp | udp | http | https | stcp | xtcp, default is tcp
type = tcp
local_ip = 127.0.0.1
local_port = 22
# limit bandwidth for this proxy, unit is KB and MB
bandwidth_limit = 1MB
# true or false, if true, messages between frps and frpc will be encrypted, default is false
use_encryption = false
# if true, message will be compressed
use_compression = false
# remote port listen by frps
remote_port = 6022

在官方的完整默认配置的基础部分之上,主要做了这几点改动:

server_addr = 11.22.33.44
log_file = /var/log/frp/frpc.log
token = mytoken
admin_pwd =mypassword
user = raspi
[ssh] remote_port = 6022

说明:

  • server_addr:frps 所在云主机的公网 IP 地址;
  • log_file:改放到系统常规放置的地方,注意 chmod 文件权限,否则可能出现无权限问题,比如:
# 创建日志文件
sudo mkdir -p /var/log/frp
sudo touch /var/log/frp/frpc.log
sudo chmod -R 666 /var/log/frp/frpc.log
  • token: frp client 与 server 之间的约定令牌,两端必须相同;
  • admin_pwd:frp client 的管理控制台密码;
  • user: 这个相当于给下面的代理名称加个前缀,以避免同其它客户端的配置出现名称重复
  • [ssh] remote_port:公网 IP 上暴露的端口,用于远程登录时的访问。

有了以上的配置,我们其实就可以通过 ssh 远程登录到树莓派,进行相应的系统管理了。
不过,按我们的原定计划,继续配置各应用服务的代理,以便提供更多服务访问。

# Resolve your domain names to [server_addr] so you can use http://xxx.mydomain.com to browse xxx.
[frpc-admin-http]
type = http
local_port = 7400
subdomain = frpc

[portainer-http]
type = http
local_port = 9000
subdomain = portainer

[syncthing-http]
type = http
local_port = 8384
subdomain = syncthing

[gitea-http]
type = http
local_port = 3000
subdomain = code

# more ...

frp 支持的更多功能,请参见官方的帮助文档:https://github.com/fatedier/frp

接下来,使用 @user 方式启动或管理服务。因为树莓派的 Ubuntu 系统默认用户是 ubuntu,所以按以下命令操作即可。

# 重命名配置文件
sudo mv /etc/frp/frpc.ini /etc/frp/ubuntu.ini
# 注册和启动服务
sudo systemctl enable frpc@ubuntu.service
sudo systemctl start frpc@ubuntu.service

如果要检查是否启动成功、查看日志等,可以使用:

# 查询服务状态
sudo systemctl status frpc@ubuntu.service
# 查看服务日志
sudo journalctl -e -u frpc@ubuntu.service

远程登录并访问树莓派服务

现在,可以分别访问自己的服务网站了。

frps dashboard
frpc 管理控制台
portainer 控制台

还可以通过 ssh 远程访问树莓派,进行系统管理了:

sudo ssh ubuntu@11.22.33.44:6022
Welcome to Ubuntu ...
ubuntu@raspi:~$

恭喜您,已完成整个内网穿透的搭建。如果你还想扩展更多的开发或演示网站,添加相应的代理配置就可以了。

我是几昆虫,一个追求终身成长的努力者。感谢您完整阅读这篇文章,期待与你的思想相遇。

推荐阅读更多精彩内容