点击展开更新日志
源起
其实前两天把 OpenVPN 和 WireGuard 跑通之后就准备直接用的,但是依照现有配置无法实现 P2P 直连,必需经过公网服务器中转,还是觉得不太满意,所以就准备再试试 tailscale/headscale 的方案。
tailscale 是基于 WireGuard 的方案,如果客户端满足直连条件,会优先使用 P2P,否则会降级使用中继服务器,本身是商业软件,不过对于个人用户,免费方案足够用了;headscale 是开源实现,功能上和官方版本基本一致,用户客户端也是官方的。
服务端部署
采用 Docker 部署,方便部署。
安装Docker
忽略。
买的腾讯云,直接就是带 Docker 的镜像,也自带了镜像加速,建议。国内的服务器厂商一般也都提供的自家服务器的加速镜像,可以查一下文档。
创建数据目录
1 2 3 4 5 6 7 sudo mkdir -p /data/docker/headscale/{headplane, headscale, traefik}sudo mkdir -p /data/docker/headscale/headplane/datasudo mkdir -p /data/docker/headscale/headscale/{config, lib, run}sudo mkdir -p /data/docker/headscale/traefik/{acme, certs, dynamic}
准备配置
headplane/config.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 server: host: "0.0.0.0" port: 3000 base_url: "http://localhost:3000" cookie_secret: "<SECRET_KEY>" cookie_secure: true cookie_max_age: 86400 data_path: "/var/lib/headplane" headscale: url: "http://headscale:8080" config_path: "/etc/headscale/config.yaml" config_strict: true integration: agent: enabled: false pre_authkey: "<your-preauth-key>" docker: enabled: false container_label: "me.tale.headplane.target=headscale" socket: "unix:///var/run/docker.sock" kubernetes: enabled: false validate_manifest: true pod_name: "headscale" proc: enabled: false
cookie_secret 可以使用 openssl rand -hex 16 生成准确32位的字符串,使用 openssl rand -base64 32 生成的字符串经 base64 编码会超出32位导致启动失败。
headscale/config/config.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 --- server_url: https://headscale.domain.top listen_addr: 0.0 .0 .0 :8080 metrics_listen_addr: 0.0 .0 .0 :9090 grpc_listen_addr: 127.0 .0 .1 :50443 grpc_allow_insecure: false noise: private_key_path: /var/lib/headscale/noise_private.key prefixes: v4: 100.64 .0 .0 /10 v6: fd7a:115c:a1e0::/48 allocation: sequential derp: server: enabled: true region_id: 999 自定义唯一区域ID,建议901-999 region_code: "Tencent" region_name: "Tencent lighthouse in Guangzhou" verify_clients: true stun_listen_addr: "0.0.0.0:3478" private_key_path: /var/lib/headscale/derp_server_private.key automatically_add_embedded_derp_region: true ipv4: <IPv4> ipv6: <IPv6> urls: - https://controlplane.tailscale.com/derpmap/default paths: [] auto_update_enabled: true update_frequency: 3h disable_check_updates: false ephemeral_node_inactivity_timeout: 30m database: type: sqlite debug: false gorm: prepare_stmt: true parameterized_queries: true skip_err_record_not_found: true slow_threshold: 1000 sqlite: path: /var/lib/headscale/db.sqlite write_ahead_log: true wal_autocheckpoint: 1000 acme_url: https://acme-v02.api.letsencrypt.org/directory acme_email: "" tls_letsencrypt_hostname: "" tls_letsencrypt_cache_dir: /var/lib/headscale/cache tls_letsencrypt_challenge_type: HTTP-01 tls_letsencrypt_listen: ":http" tls_cert_path: "" tls_key_path: "" log: level: info format: text policy: mode: file path: "" dns: magic_dns: true base_domain: clients.domain.top override_local_dns: true nameservers: global: - 1.1 .1 .1 - 8.8 .8 .8 split: {} search_domains: [] extra_records: [] unix_socket: /var/run/headscale/headscale.sock unix_socket_permission: "0770" logtail: enabled: false randomize_client_port: false taildrop: enabled: true
根据实际情况修改 server_url 和 base_url,补充DERP服务器的公网地址。
server_url: 访问headscale的yum
base_url: 生成客户端域名地址
证书
将域名证书上传到 traefik/certs 目录下,traefik 可以直接配置 Let’s Encrypt 证书自动申请,只是我已经在 allinssl 中配置了申请,所以第一次直接配置了,后续就用 allinssl 做更新了。
最终完成的结构目录如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /data └── docker └── headscale ├── docker-compose.yml ├── headplane │ ├── config.yaml │ └── data │ └── hp_persist.db ├── headscale │ ├── config │ │ ├── config.yaml │ │ └── dns_records.json │ ├── lib │ │ ├── db.sqlite │ │ ├── db.sqlite-shm │ │ ├── db.sqlite-wal │ │ ├── derp_server_private.key │ │ └── noise_private.key │ └── run │ └── headscale.sock └── traefik ├── acme │ └── acme.json ├── certs │ ├── domain.top.crt │ └── domain.top.key ├── dynamic │ └── certificates.yml └── traefik.yml
开放端口
开放以下端口:
服务部署
docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 services: headplane: image: ghcr.io/tale/headplane:latest container_name: headplane restart: unless-stopped environment: - TZ=Asia/Shanghai volumes: - './headplane/config.yaml:/etc/headplane/config.yaml' - './headplane/data:/var/lib/headplane' - './headscale/config/config.yaml:/etc/headscale/config.yaml' - '/var/run/docker.sock:/var/run/docker.sock:ro' labels: - 'traefik.enable=true' - 'traefik.http.routers.headplane.rule=Host(`headscale.domain.top`) && PathPrefix(`/admin`)' - 'traefik.http.routers.headplane.entrypoints=websecure' - 'traefik.http.routers.headplane.tls=true' - 'traefik.http.routers.headplane.priority=100' - 'traefik.http.routers.headplane.service=headplane-service' - 'traefik.http.services.headplane-service.loadbalancer.server.port=3000' networks: - headscale headscale: image: headscale/headscale:latest container_name: headscale restart: unless-stopped command: serve environment: - TZ=Asia/Shanghai ports: - '3478:3478/udp' volumes: - './headscale/config:/etc/headscale' - './headscale/lib:/var/lib/headscale' - './headscale/run:/var/run/headscale' healthcheck: test: ["CMD" , "headscale" , "health" ] labels: - 'me.tale.headplane.target=headscale' - 'traefik.enable=true' - 'traefik.http.routers.headscale.rule=Host(`headscale.domain.top`) && !PathPrefix(`/admin`)' - 'traefik.http.routers.headscale.entrypoints=websecure' - 'traefik.http.routers.headscale.tls=true' - 'traefik.http.routers.headscale.service=headscale-service' - 'traefik.http.routers.headscale.priority=10' - 'traefik.http.services.headscale-service.loadbalancer.server.port=8080' - 'traefik.http.routers.headscale.middlewares=cors' - 'traefik.http.middlewares.cors.headers.accesscontrolallowheaders=*' - 'traefik.http.middlewares.cors.headers.accesscontrolallowmethods=GET,POST,PUT' - 'traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=https://headscale.domain.top' - 'traefik.http.middlewares.cors.headers.accesscontrolmaxage=100' - 'traefik.http.middlewares.cors.headers.addvaryheader=true' - 'traefik.http.routers.headscale-root.rule=Host(`headscale.domain.top`) && Path(`/`)' - 'traefik.http.routers.headscale-root.entrypoints=websecure' - 'traefik.http.routers.headscale-root.tls=true' - 'traefik.http.routers.headscale-root.service=headscale-service' - 'traefik.http.routers.headscale-root.priority=50' - 'traefik.http.routers.headscale-root.middlewares=rewrite-root,cors' - 'traefik.http.middlewares.rewrite-root.redirectregex.regex=^/$$' - 'traefik.http.middlewares.rewrite-root.redirectregex.replacement=/admin' - 'traefik.http.middlewares.rewrite-root.redirectregex.permanent=true' networks: - headscale traefik: image: traefik:latest container_name: traefik restart: unless-stopped ports: - '80:80' - '443:443' volumes: - './traefik/traefik.yml:/etc/traefik/traefik.yml' - './traefik/dynamic:/etc/traefik/dynamic:ro' - '/var/run/docker.sock:/var/run/docker.sock:ro' - './traefik/certs:/certs' - './traefik/acme:/etc/traefik/acme' - '/etc/localtime:/etc/localtime:ro' labels: - "traefik.enable=true" - "traefik.http.routers.traefik.rule=Host(`traefik.domain.top`)" - "traefik.http.routers.traefik.entrypoints=websecure" - "traefik.http.routers.traefik.service=api@internal" - "traefik.http.routers.traefik.tls=true" - "traefik.http.routers.traefik.middlewares=auth" - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$9IGx0e5f$$7g1ShFdT7B/eFRjrOpzZb1" - "traefik.http.middlewares.security-headers.headers.browserXSSFilter=true" - "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true" - "traefik.http.middlewares.security-headers.headers.frameDeny=true" - "traefik.http.middlewares.security-headers.headers.sslRedirect=true" networks: - headscale networks: headscale: name: headscale
traefik 的 Basic Auth 认证的账号密码,使用以下方法生成进行替换:
1 2 sudo apt install apache2-utilshtpasswd -nb admin <your-password> | sed -e s/\\$/\\$\\$/g
最后启动容器服务即可,检查日志是否存在报错,没有报错就可以继续了。也可以访问 https://headscale.domain.top/health 查看返回结果:
管理地址
headplane 的 Web ui管理地址:https://headscale.domain.top/admin
headscale 服务地址: https://headscale.domain.top
客户端配置
如果到这里一切顺利,服务正常启动,那么就可以开始客户端的配置了。
从 Tailscale 下载页 下载客户端;
生成API Key:登录服务器执行:docker exec headscale headscale apikeys create 生成API Key
选择一:不写入配置,可以一直使用,如果过期/忘记重新生成即可;
选择二:写入配置,如果过期/忘记重新生成后需要修改配置重新部署;
进入 headplane 管理后台,使用 API Key登录,进入【User】标签新增一个用户,记住用户名;
以 Win11 为例,打开【终端】,添加客户端:
1 tailscale up --login-server=https://headscale.domain.top
--login-server:指定控制服务器地址(必须和你设置的 server_url 一致)。
`–
输入之后会弹出一个URL地址,在浏览器打开,会显示一条命令要求到服务端注册客户端:
1 docker exec headscale headscale nodes register --key <KEY> --user <USERNAME>
用户名就是上面手动添加的用户名。
服务器上添加完成后,终端内会显示【Success】说明客户端已添加成功,可以到 headplane后台查看了,后续添加多个客户端如果互访就使用分配的100.64.0.0/10段的IP即可。
tailscale 的一个优势是组建的内网是专用网络,因此网络权限很大,不需要像 Wireguard 需要额外开放策略存在安全风险。