背景
一些场景下,基于灵活性与成本的原则,我们需要局域网服务节点能够直接服务于Internet用户。即可以让用户能够通过云服务节点直接访问局域网服务节点,如下图:

具体的场景有:
局域网成本有优势的时候:比如有大量存储和算力需求的时候。特别是非实时的批量业务,可以将一部分服务节点部署在本地局域网,通过与云服务节点的配合,做到成本最优。
开发阶段需要暴露接口给第三方:例如调试微信支付的时候,支付状态的通知消息只能发送到一个Internet服务节点上,但是本阶段服务还在局域网的开发环境下。
SSH隧道
有多种方法可以做到这一点,这里介绍的是SSH隧道方式,一般linux类服务都会安装ssh服务,因此具有较广泛的通用性。
SSH能力介绍
SSH除了可以登陆服务器管理之外,还可以用来建立服务节点之间的端口互通隧道。
将本地端口转发到远程端口
ssh -N -L <本地IP>:<本地端口>:<目标主机>:<目标端口> <用户名>@<SSH服务器>
# 示例:将远程 MySQL(3306)映射到本地的 3307 端口
ssh -N -L 3307:localhost:3306 user@remote-server
# 访问 localhost:3307 即等同于访问 remote-server 的 3306 端口。
将本地端口暴露到远程端口
ssh -N -R <远程端口>:<目标主机>:<目标端口> <用户名>@<SSH服务器>
# 示例:将本地的 8080 端口暴露到远程服务器的 8888 端口
ssh -N -R 8888:localhost:8080 user@remote-server
# 在 remote-server 上访问 localhost:8888 即访问你本地的 8080 服务。启动socks代理
启动后,可以配置浏览器使用本地的socks代理,就可以借助remote-server的连通性。
ssh -N -D <本地端口> <用户名>@<SSH服务器>
# 示例:在本地 1080 端口启动 SOCKS5 代理
ssh -N -D 1080 user@remote-server
#配置浏览器或应用使用 localhost:1080 作为代理。准备
这里定义好目标,本地开发微信支付应用,通过云服务节点接受支付状态通知。
确认云服务节点可通过SSH访问,有相关用户、口令或密钥信息;规划好端口映射关系,例如20080-80等;明确域名指向云服务器节点,并通过端口映射或者nginx代理转发url等。如下:
局域网服务节点
Java Spring boot 服务
这里使用了Java Spring boot开发了一个微信支付服务,在本地运行起来,服务端口为8080.
$ gradle bootRun
Starting a Gradle Daemon (subsequent builds will be faster)
> Task :bootRun
09:51:23.608 [Thread-0] DEBUG org.springframework.boot.devtools.restart.classloader.RestartClassLoader - Created RestartClassLoader org.springframework.boot.devtools.restart.classloader.RestartClassLoader@3120e283
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.18)
2025-07-05 09:51:23.844 INFO 3046228 --- [ restartedMain] bdm.payment.BdmPaymentApplication : Starting BdmPaymentApplication using Java 11.0.27 on gaoyong-HP-Elite-Dragonfly-G2-Notebook-PC with PID 3046228 (/media/gaoyong/FE3AACC83AAC7F71/work/tgit/mysoft.space/code/bdm_payment/build/classes/java/main started by gaoyong in /media/gaoyong/FE3AACC83AAC7F71/work/tgit/mysoft.space/code/bdm_payment)
2025-07-05 09:51:23.844 DEBUG 3046228 --- [ restartedMain] bdm.payment.BdmPaymentApplication : Running with Spring Boot v2.7.18, Spring v5.3.31
2025-07-05 09:51:23.845 INFO 3046228 --- [ restartedMain] bdm.payment.BdmPaymentApplication : No active profile set, falling back to 1 default profile: "default"
2025-07-05 09:51:23.873 INFO 3046228 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2025-07-05 09:51:23.874 INFO 3046228 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2025-07-05 09:51:24.198 INFO 3046228 --- [ restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-07-05 09:51:24.222 INFO 3046228 --- [ restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 19 ms. Found 1 JPA repository interfaces.
2025-07-05 09:51:24.559 INFO 3046228 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2025-07-05 09:51:24.565 INFO 3046228 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-07-05 09:51:24.565 INFO 3046228 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.83]
2025-07-05 09:51:24.600 INFO 3046228 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-07-05 09:51:24.600 INFO 3046228 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 726 ms
2025-07-05 09:51:24.671 INFO 3046228 --- [ restartedMain] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2025-07-05 09:51:24.694 INFO 3046228 --- [ restartedMain] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.15.Final
2025-07-05 09:51:24.755 INFO 3046228 --- [ restartedMain] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2025-07-05 09:51:24.790 INFO 3046228 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-07-05 09:51:24.856 INFO 3046228 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2025-07-05 09:51:24.863 INFO 3046228 --- [ restartedMain] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2025-07-05 09:51:25.115 INFO 3046228 --- [ restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2025-07-05 09:51:25.119 INFO 3046228 --- [ restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-07-05 09:51:25.269 WARN 3046228 --- [ restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-07-05 09:51:25.320 DEBUG 3046228 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2025-07-05 09:51:25.331 INFO 3046228 --- [ restartedMain] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2025-07-05 09:51:25.351 DEBUG 3046228 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : 9 mappings in 'requestMappingHandlerMapping'
2025-07-05 09:51:25.361 DEBUG 3046228 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2025-07-05 09:51:25.366 DEBUG 3046228 --- [ restartedMain] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2025-07-05 09:51:25.412 INFO 3046228 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2025-07-05 09:51:25.428 INFO 3046228 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2025-07-05 09:51:25.434 INFO 3046228 --- [ restartedMain] bdm.payment.BdmPaymentApplication : Started BdmPaymentApplication in 1.82 seconds (JVM running for 2.026)
<==========---> 80% EXECUTING [1m 19s]
> :bootRun
nginx
通过本地nginx做一个代理,将域名www.my-soft.net.cn的wepay子目录代理到8080.
$ cat www.aigrow.space.conf
server
{
listen 80;
server_name www.aigrow.space;
location / {
proxy_pass http://192.168.99.44:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
$ nginx -s reload
# 验证配置有效性 : 如下指令返回结果应该一致
$ curl 192.168.99.44:8080
$ curl 127.0.0.1 -H "Host: www.aigrow.space"autossh
可以保持隧道的持续性,中断自动重联。
sudo apt install autossh通过autossh将80号端口投射到云服务器20080号端口
autossh -M 0 -o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-o "ExitOnForwardFailure=yes" \
-NTR 20080:127.0.0.1:80 ubuntu@www.aigrow.spacesystemd
如果局域网服务节点重启,需要自动拉起,那么就应该将这里的隧道纳入systemctl管理。如下:
在/etc/systemd/system目录,创建autossh-aigrow-dev.service文件
[Unit]
Description=AutoSSH Tunnel for Remote SSH Access
After=network-online.target
[Service]
# 使用您的本地用户名(不要用root),需要确保用户存在,可用id ubuntu查看
User=ubuntu
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -M 0 \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-o "ExitOnForwardFailure=yes" \
-NTR 20022:127.0.0.1:22 \
-NTR 20080:127.0.0.1:80 \
ubuntu@oss_aigrow
# 自动重启配置
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target通过systemctl来管理服务,并通过
sudo systemctl daemon-reload
sudo systemctl enable autossh-aigrow-dev.service # 启用开机自启
sudo systemctl start autossh-aigrow-dev.service # 立即启动
systemctl status autossh-aigrow-dev.service
journalctl -u autossh-aigrow-dev.service -f # 查看实时日志云服务节点
nginx
在www.aigrow.space的配置文件中增加如下地址代理,然后更新nginx配置。
location /dev/{
proxy_pass http://127.0.0.1:20080/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
查看隧道状态
$ sudo lsof -i :20080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 1039952 ubuntu 7u IPv6 1422654863 0t0 TCP ip6-localhost:20080 (LISTEN)
sshd 1039952 ubuntu 9u IPv4 1422654864 0t0 TCP localhost:20080 (LISTEN)
# 或者
$ ss -tulnp | grep 20080
tcp LISTEN 0 128 127.0.0.1:20080 0.0.0.0:*
tcp LISTEN 0 128 [::1]:20080 [::]:*
# 或者
$ netstat -an|grep 20080
tcp 0 0 127.0.0.1:20080 0.0.0.0:* LISTEN
tcp6 0 0 ::1:20080 :::* LISTEN
用户
可通过浏览器或者curl就能够查看是否正确。
$ curl https://www.aigrow.space/dev/