如果我们家里 Nas 部署的服务希望能让自己在外面也能访问主要有两种方式,一种是通过公网将服务暴露出去,一种是搭建 VPN,自己想访问时开启 VPN。这里主要介绍前面的方式。

访问形式

家宽公网 IP

需要将服务通过公网暴露出去那就一定需要一个公网 IP,如果自己家的宽带就有公网 IP 那是最好的了。这种情况需要自己配置端口映射。整体拓扑如下(一般情况下是这样三层结构)

image.png

这样当光猫的 10000 端口收到访问请求,最后就被一步步转发到了 Nas 的 10000 端口。

内网穿透

内网传透有很多工具可以实现,这里拿 frp 举例,整体都差不多。如果自己没有 VPS,可以试下 CloudFlare Tunnel,ZeroTier 方案。
内网穿透的拓扑结构如下。
image.png

Nginx 配置

前面介绍了公网到 Nas 环节的访问配置方式,流量到了 Nas 内部还需要做一次分发,根据域名的不同分发到这个具体的服务,也即反向代理。
目前常用的反向代理有这样几个 Nginx,Caddy,Traefik。
Caddy 配置比 Nginx 简单点,然后支持自己续期 HTTPS 证书,这三个都不会的可以学习下用这个。
Traefik 是一个比较新的反向代理工具,支持自己续期 HTTPS 证书。然后他还支持自动注册代理,意思是启动新的服务和域名后,Nginx 和 Caddy 都需要手动配置下新域名,但 Traefik 不一样,我们可以在 docker-compose 的配置文件里描述好域名信息,Traefik 就能自动发现并为之代理。还有没有其它优势我也不了解了。有兴趣研究下新工具的可以试试 Traefik。
我一开始就用的 Nginx,最近也还没准备尝试 Traefik(后面研究完了再在这篇文章里补上),所以这里还是介绍 Nginx 的配置。

部署

nginx 的 docker-compose.yaml(我一般放在 /opt/app/nginx 下)

services:
  nginx:
    image: nginx:alpine
    container_name: nginx
    network_mode: host
    volumes:
      - ./conf.d:/etc/nginx/conf.d
      - ./sslcert:/etc/sslcert
    restart: always

在同目录创建 conf.d 目录和 sslcert 目录,后面使用。

域名配置

我在 Nas 上部的服务很多,所以是配置的是泛域名,比如你有一个域名 foo.com,可以直接配置 *.foo.com,如果下多加一级用泛域名可以用类似 *.nas.foo.com,不过我以前有后者,发现有的域名太长了(家宽的公网 IP 访问需要加上端口就更长了),现在就用了 *.foo.com 这种形式。
泛域名的好处是不用每次加新服务就要申请一次证书配置一次。
在 conf.d 目录里用以你选择的域名命名创建一个新文件,比如 foo.com.conf ,在里面贴上这样的内容。

server {
    listen 10000 ssl;
    server_name *.foo.com;  # 匹配所有子域

	ssl_certificate /etc/sslcert/*.foo.com/fullchain.cer;
	ssl_certificate_key /etc/sslcert/*.foo.com/*.foo.com.key;

    location / {
        # 使用映射的端口构建上游服务器地址
        proxy_pass http://127.0.0.1:$map_upstream;

        # 普通代理设置
        proxy_set_header Host $http_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;


        # WebSocket 支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;
    }
}

map $host $map_upstream{
    emby.foo.com 8057;
    nas.foo.com 5000;
}

前面的 server_name 和 ssl_certificate 相关的内容要根据自己的域名修改。最后面 map 里则是填你需要配置的域名和本地的端口。
每次部署了新服务,只需要在最后的 map 里加上一个映射,然后 reload nginx(docker compose exec nginx /usr/sbin/nginx -s reload) 就好了。
完整的配置文件可以看这里

单域名配置

单域名的配置整体上和泛域名差不多,主要是 server_name 和 ssl_certificate 相关的位置要写好域名,然后 proxy_pass 要写端口。

server {
    listen 10000 ssl;
    server_name aaa.foo.com;  # 匹配所有子域

	ssl_certificate /etc/sslcert/aaa.foo.com/fullchain.cer;
	ssl_certificate_key /etc/sslcert/aaa.foo.com/aaa.foo.com.key;

    location / {
        # 使用映射的端口构建上游服务器地址
        proxy_pass http://127.0.0.1:<port>;

        # 普通代理设置
        proxy_set_header Host $http_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;


        # WebSocket 支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;
    }
}

证书申请

经过上面的配置 Nginx 侧都做完了,接下来是申请证书。我自己还是比较习惯 acme.sh 这个工具。
首先安装 acme

curl https://get.acme.sh | sh -s email=<your email>

然后根据文档去获取你域名所有 dns 提供商的 token, https://github.com/acmesh-official/acme.sh/wiki/dnsapi
然后修改下面这份文件里关于 dns 的配置(DNS 提供商的 token 信息和 DNS_PROVIDER),创建一个文件比如 gencert.sh

#!/bin/bash
# gen_cert.sh sub.a.com

# set your dns token and dns provider
# https://github.com/acmesh-official/acme.sh/wiki/dnsapi
#
# for exapmle
# export CF_Token=''
# export CF_Account_ID=''
# DNS_PROVIDER=dns_cf

domain=$1

CMD=$HOME/.acme.sh/acme.sh
$CMD --set-default-ca --server letsencrypt
$CMD --issue --dns $DNS_PROVIDER -d $domain

path="/opt/app/nginx/sslcert/$domain" # 提前创建好这个目录,并且把权限给给到当前用户,不是给到 root。
sudo mkdir -p /opt/app/nginx/sslcert
sudo chown $USER /opt/app/nginx/sslcert

mkdir -p $path

$CMD --install-cert -d $domain \
    --key-file $path/$domain.key \
    --fullchain-file $path/fullchain.cer \
    --reloadcmd "docker exec nginx nginx -s reload"

然后 bash ./gencert.sh *.foo.com 就可以创建好 foo.com 的泛域名证书了,非泛域名就是 bash ./gencert.sh bar.foo.com。之后创建新证书也是这样执行命令就好了。
可以去 /opt/app/nginx/ 下看看是否已经正常申请到证书。

接下来就可以启动 nginx 了,docker compose up -d,理论上域名就可以正常访问了。