记录一些常用的函数和瞎折腾的过程……

Docker环境中使用frp内网穿透服务

提醒:本文最后更新于 713 天前,文中所描述的信息可能已发生改变,请谨慎使用。

因为本人习惯用docker跑服务,所以对于frp的内网穿透服务我使用的也是docker。

frp在github上的项目地址为:https://github.com/fatedier/frp,里面有相关文档,可以参考配置。由于我使用的docker,自己做了个简单的记录。

frp的docker项目的服务器端和客户端是独立的,地址分别为:https://hub.docker.com/r/snowdreamtech/frps和https://hub.docker.com/r/snowdreamtech/frpc,文档说明地址为:https://gofrp.org/docs/。

先安装服务器端的frp,这台服务器在公网中,作为内网穿透的服务器。

拉取镜像:

docker pull snowdreamtech/frps

建立容器:

docker run \
    --restart=always \
    --network host -d \
    -v /docker/frp/frps.ini:/etc/frp/frps.ini \
    --name frps \
    snowdreamtech/frps

由于挂载了文件到宿主主机,可以直接在宿主主机上编辑/docker/frp/frps.ini进行配置:

[common]
bind_port = 7000
# 启用面板
dashboard_port = 7500
# 面板登录名和密码
dashboard_user = admin
dashboard_pwd = 12345678
# 使用http代理并使用8888端口进行穿透
vhost_http_port = 8888
# 使用https代理并使用9999端口进行穿透
vhost_https_port = 9999

服务器上的frp安装配置完成,然后在你内网的客户机上安装frp。

拉取镜像:

docker pull snowdreamtech/frpc

建立容器:

docker run \
    --restart=always \
    --network host -d \
    -v /docker/frp/:/etc/frp/ \
    --name frpc \
    snowdreamtech/frpc

在配置客户端前,我先说下这台内网机器上我跑的docker服务:

1、使用nginx和php的docker跑了2个网站,通过虚拟主机方式配置了2个网站,在虚拟网站配置文件中,server_name的域名指向分别为one.yourdomain.com和two.yourdomain.com,一个使用的80端口一个使用443端口,并在域名商处将域名全部解析到frp的服务器的ip上(注意不是客户端,因为客户端在内网没有公网ip,需要通过服务器的公网ip来穿透),在同个端口下配置了多个虚拟主机也能通过自定域名来区分服务。

使用nginx虚拟主机配置为(以https为例子):

server {
  listen 443 ssl http2;
  server_name two.yourdomain.com;
  index index.html index.htm index.php;
  root /usr/share/nginx/html/two.yourdomain.com;

  ssl_certificate /etc/nginx/SSL/two.yourdomain.com.cer;
  ssl_certificate_key /etc/nginx/SSL/two.yourdomain.com.key;
  ssl_session_timeout 5m;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers on;
  ssl_ciphers "TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
  ssl_session_cache builtin:1000 shared:SSL:10m;

  location ~ [^/]\.php(/|$) { # 处理php文件交给php容器
    root /var/www/html/two.yourdomain.com;
    fastcgi_pass php:9000; # php的容器名和端口
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME /var/www/html/two.yourdomain.com$fastcgi_script_name;
    include fastcgi_params;
  }

  location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
    expires 30d;
  }
  location ~ .*\.(js|css)?$ {
    expires 12h;
  }
  location ~ /.well-known {
    allow all;
  }
  location ~ /\. {
    deny all;
  }
  access_log /var/log/nginx/two.yourdomain.com.log;
}

2、安装了filebrowser容器,宿主主机与容器的端口映射为8004:80,nginx配置中使用了反代但未启用ssl,已将要使用的域名file.yourdomain.com解析到公网服务器主机的ip上。

基本配置如下:

server {
  listen 80;
  server_name file.yourdomain.com;

  # Allow large attachments
  client_max_body_size 128M;

  location / {
    proxy_pass http://file; # filebrowser的容器名(默认端口80)
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
  access_log /var/log/nginx/file.yourdomain.com.log;
}

3、安装了bitwarden的容器,已将宿主主机的8008映射到了容器的80端口,同时配置了nginx进行反代,并已设置了ssl证书,已将要使用的域名pwd.yourdomain.com解析到公网服务器主机的ip上;

nginx反代bitwarden的配置文件为:

server
  {
    listen 443 ssl http2;
    server_name pwd.yourdomain.com;
    ssl_certificate /etc/nginx/SSL/pwd.yourdomain.com.cer;
    ssl_certificate_key /etc/nginx/SSL/pwd.yourdomain.com.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
    ssl_session_cache builtin:1000 shared:SSL:10m;

    # Allow large attachments
    client_max_body_size 128M;

    location / {
      proxy_pass http://bitwarden;  # bitwarden的容器名(容器端口80)
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /admin {
      # See: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
      auth_basic "Private";
      auth_basic_user_file /etc/nginx/htpasswd;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://bitwarden;  # bitwarden的容器名(容器端口80)
    }
    access_log /var/log/nginx/pwd.yourdomain.com.log;
}

按以上的实际情况,我在frp的客户端上进行了如下配置:

[common]
# server_addr为frp的服务器ip或已解析到该ip的域名
server_addr = x.x.x.x
# server_port与服务端配置bind_port一致
server_port = 7000

[ssh]
# 开启ssh内网穿透
type = tcp
local_ip = 127.0.0.1
# 内网主机ssh端口
local_port = 22
# 远程连接ssh的端口
remote_port = 2222

[http web1]
# 第一个网站,nginx配置为http访问
type = http
local_port = 80
custom_domains = one.yourdomain.com

[https web2]
# 第二个网站,nginx配置为https访问
type = https
local_port = 443
custom_domains = two.yourdomain.com

[https filebrowser]
# filebrowser容器,直接使用https访问
type = https
custom_domains = file.yourdomain.com
plugin = https2http
# 宿主机ip地址和宿主机映射到容器的端口8004
plugin_local_addr = 127.0.0.1:8004
plugin_crt_path = /etc/frp/yourdomain.com.cer
plugin_key_path = /etc/frp/yourdomain.com.key
# 宿主机的ip地址
plugin_host_header_rewrite = 127.0.0.1
plugin_header_X-From-Where = frp

[https bitwarden]
# bitwarden容器,已使用nginx反代并配置为https访问
type = https
local_port = 443
custom_domains = pwd.yourdomain.com

注意:上面filebrowser和bitwarden的https配置是可以互换的,区别在于,一个直接访问容器,一个通过nginx容器进行了反代。在上面的例子中,filebrowser安装后,没有配置nginx,直接在frp配置文件中设置ssl证书,使用宿主机映射的端口8004映射到容器,完成加密传输;bitwarden安装后,配置了nginx,在nginx的配置文件中设置了ssl证书,并进行了反代,使用nginx容器的443端口完成加密传输。

安装配置完成后,即可通过http://one.yourdomain.com:8888、https://two.yourdomain.com:9999、https://file.yourdomain.com:9999和https://pwd.yourdomain.com:9999分别穿透内网,并访问不同的服务了。

对于使用ssh等其它服务,可以参考官方文档进行配置,大同小异,只是配置不同而已。gitee上的文档也可参考:https://gitee.com/yijicai/frp#frp

注意,如果要使用frp进行内网穿透,需要注意以下几点:

vhost_http_port和vhost_https_port只能设置一个,但我要运行多个http服务,可以使用subdomain_host进行区别,具体如下:

1、在Docker的Nginx容器中做好端口映射,明确宿主主机和nginx容器之间的端口关系,如:

-p 9080:80
-p 9081:81
-p 9082:82
-p 9083:83
-p 9443:443

2、在frps.ini中添加:

subdomain_host = psay.cn

3、在frpc.ini中修改,主要就是删除custom_domains,并添加subdomain:

[web d.psay.cn]
type = http
# 第一个http服务的宿主主机端口9080,nginx容器的配置文件监听映射端口80
local_port = 9080
# 使用二级域名d.psay.cn区分
subdomain = d

[web a.psay.cn]
type = http
# 第二个http服务的宿主主机端口9081,nginx容器的配置文件监听映射端口81
local_port = 9081
# 使用二级域名a.psay.cn区分
subdomain = a
proxy_protocol_version = v2

[web a2.psay.cn]
type = http
# 第三个http服务的宿主主机端口9082,nginx容器的配置文件监听映射端口82
local_port = 9082
# 使用二级域名a2.psay.cn区分
subdomain = a2
proxy_protocol_version = v2

贴一个我使用没问题的配置文件,一个aria远程下载的ui界面,一个aria2服务,一个php的filerun用来查看下载内容(包括nginx和frp):

第一个nginx配置,反代并运行php文件(跑FileRun):

server {
  listen 80;
  server_name d.psay.cn;
  index index.html index.php;
  root /usr/share/nginx/html/d.psay.cn;

  location ~ [^/]\.php(/|$) { # 处理php文件交给php容器
    root /var/www/html/d.psay.cn;
    fastcgi_pass php:9000; # php的容器名和端口
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME /var/www/html/d.psay.cn$fastcgi_script_name;
    include fastcgi_params;
  }

  location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
    expires 30d;
  }
  location ~ .*\.(js|css)?$ {
    expires 12h;
  }
  location ~ /.well-known {
    allow all;
  }
  location ~ /\. {
    deny all;
  }
  access_log /var/log/nginx/d.psay.cn.log;
}

第二个nginx配置文件,反代ariang可视界面:

server {
  listen 81 proxy_protocol;
  server_name a.psay.cn;

  # 配合使用proxy_protocol获取真实ip
  real_ip_header proxy_protocol;
  real_ip_recursive on;
  set_real_ip_from 172.18.0.1;

  # Allow large attachments
  client_max_body_size 128M;

  location / {
    proxy_pass http://ariang:6880; # aira2容器名和端口
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  access_log /var/log/nginx/a.psay.cn.log;
}

第三个nginx配置文件,反代aria2下载服务:

server {
  listen 82 proxy_protocol;
  server_name a2.psay.cn;

  # 配合使用proxy_protocol获取真实ip
  real_ip_header proxy_protocol;
  real_ip_recursive on;
  set_real_ip_from 172.18.0.1;

  # Allow large attachments
  client_max_body_size 128M;

  location / {
    proxy_pass http://aria2:6800; # aira2容器名和端口
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  access_log /var/log/nginx/a2.psay.cn.log;
}

frp server 配置文件:

[common]
# 客户端连接服务端的端口和允许的IP
bind_addr = 0.0.0.0
bind_port = 9000

# 启用面板指定端口
dashboard_port = 9001

# 设置udp端口,以帮助udp进行nat穿透
bind_udp_port = 9002

# 用于kcp协议的udp端口,它可以与'bind_port'相同。
# 如果不设置,在frps中kcp默认禁用。
kcp_bind_port = 9000

# 面板登录名和密码
dashboard_user = admin
dashboard_pwd = admin

# 定义主域名,此处配置了,客户端中不能配置custom_domains,相应的增加subdomain指定子域
subdomain_host = psay.cn

# 指定http和https代理端口进行穿透
vhost_http_port = 9080
vhost_https_port = 9443

# 认证token
token = ed2c4592529

# 日启并设置日志存储路径
log_file = /etc/frp/frps.log

# 日志级别:trace, debug, info, warn, error
log_level = info

# 日志存储天数
log_max_days = 3

frp client 配置文件:

[common]
# server_addr为frp的服务器ip或已解析到该ip的域名
server_addr = x.x.x.x
# server_port与服务端配置bind_port一致
server_port = 9000
# 认证token
token = ed2c4592529

[ssh]
# 开启ssh内网穿透
type = tcp
local_ip = 127.0.0.1
# 内网主机ssh端口
local_port = 22
# 远程连接ssh的端口
remote_port = 2020

[web d.psay.cn]
type = http
local_port = 9080
# 二级域名 d.psay.cn
subdomain = d

[web a.psay.cn]
type = http
local_port = 9081
# 二级域名 a.psay.cn
subdomain = a
proxy_protocol_version = v2
# ui界面设置账户密码
http_user = webmaster
http_pwd = webmaster

[aira2 a2.psay.cn]
type = http
local_port = 9082
# 二级域名 a2.psay.cn
subdomain = a2
proxy_protocol_version = v2