快轉到主要內容

Headscale:自架Tailscale控制伺服器,docker-compose部署教學

· 民國115年丙午年
·
切換繁體/簡體
分類 Linux系統 Self-hosting自架
標籤 Tailscale NAT Traversal
目錄

本文Ivon討論如何架設Headscale控制伺服器 + Headplane網頁控制面板。

之前分享過Tailscale,這是一款讓你實現內網穿透功能的VPN服務,可以讓個裝置組成虛擬區網,互相連線。

雖然Tailscale客戶端是開源的方案,不過它有一個關鍵點在於,伺服端是閉源產品。連線需要經過Tailscale公司的伺服器,需要註冊帳號才能使用服務。而那些伺服端的設施,是Tailscale公司的商業機密。

如果我們想要完全掌控連線過程呢,不想要在Tailscale官網註冊帳號呢?其實呀,Tailscale客戶端是允許你更換連線伺服器的喔!

來自架一個「Headscale」伺服器吧!它能夠取代Tailscale公司的控制伺服器與中繼伺服器,讓你能自主掌控Tailscale網路。

headscale.webp

即使你已經註冊了Tailscale帳號,也能夠快速地切換到Headscale的體系。MagicDNS設定的當的話能夠直接沿用。

這個伺服器軟體的名字很有趣,用Head映襯Tail,可以說是對應「妳是主人我是僕」的關係吧!

1. 預期的連線架構
#

Tailscale原本的連線是這樣,所有Tailscale的客戶端,登入Tailscale的帳號,加入名為Tailnet的虛擬區網,需要依賴Tailscale公司的控制伺服器才能找到彼此。當P2P失敗的時候,relay伺服器(DERP)還要承擔傳輸流量的功能。

diagram1.webp

在我們自架Headscale後,所有Tailnet的連線過程都掌握在我們手裡。只需要更換控制伺服器的部分,Tailscale客戶端可以繼續沿用。它充當控制伺服器與relay伺服器(DERP)的角色。你就從此與Tailscale公司的伺服器無關了。

diagram2.webp

2. 你應該自架Headscale嗎?
#

跟Tailscale免費方案限制100台裝置比起來,Headscale能夠加入的裝置理論上是無上限,就看你的伺服器撐不撐得住。

最好準備一台獨立的伺服器跑Headscale。考慮到跑Headscale伺服器要一直在線上負責處理連線。租個VPS來架會比較妥當,並且要有自訂網域與SSL。Headscale很輕量,不到1GB RAM就可以部署。流量部份,如果Tailscale成功讓裝置P2P,不依賴relay伺服器,則大部分網路傳輸流量是不會經過Headscale所在的伺服器的,因此這個不用太在意。另外要注意延遲問題,最好選離台灣近一點的國家的機房。

再者,要想想維護容易度。Headscale是第三方開發者Juan Font維護的專案,不是Tailscale公司開發的。根據官網FAQ,作者說這個專案不適合用於大型組織,而是適合個人用戶存取自家服務使用。

Headscale功能不保證能像Tailscale的公司提供的產品一樣完整,使用起來沒有那麼無腦了。

Headscale只提供基本的指令控制面板,做到最小相容Tailscale客戶端所使用的協定。Headscale本身就是第三方專案了,還得靠第三方開發的方案才有網頁管理界面。

總體來說,自架Headscale並不如Tailscale公司提供的控制面板好上手,你要自己處理一切麻煩事。如果日後要進一步擴張你的私人Tailscale虛擬區域網路,甚至邀請不太懂技術的用戶加入網路的話,要處理的問題就更多了。

但若你要自主可控的Tailscale連線環境,就學著適應吧。

3. 用Docker部署Headscale
#

參考Headscale官方文件

  1. Linux安裝Docker

  2. 建立存放資料的目錄

cd ~

mkdir -p ./headscale/{config,lib}

cd ./headscale
  1. 建立docker-compose
vim docker-compose.yaml
  1. 範本如下,大部分維持預設。ports冒號左邊的通訊埠可以任意更改。這裡加入一個STUN的3478通訊埠協助P2P hole punching。
services:
  headscale:
    image: docker.io/headscale/headscale:latest
    restart: unless-stopped
    container_name: headscale
    read_only: true
    tmpfs:
      - /var/run/headscale
    ports:
      - "127.0.0.1:8080:8080"
      - "127.0.0.1:9090:9090"
      - "3478:3478/udp"
    volumes:
      - ./config:/etc/headscale:ro
      - ./lib:/var/lib/headscale
    command: serve
    healthcheck:
        test: ["CMD", "headscale", "health"]
  1. 從Github下載Headscale範本設定檔
wget -O ./config/config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml 
  1. 編輯config.yaml
vim ./config/config.yaml
  1. 在config.yaml填入你的網域。我這裡是分配一個子網域的方式來處理Headscale的連線。例如https://headscale.example.com
server_url: https://headscale.example.com
  1. 若上面的docker-compose有修改預設的通訊埠,設定監聽的網址為0.0.0.0
listen_addr: 0.0.0.0:8080
  1. 在dns這一段,設定建立MagicDNS的規則,例如在你的網域前面加上一串子網域,裝置加入後網址就會變成linux-machine.tailscale.example.com。你可以打ssh use@linux-machine連線。
dns:
  magic_dns: true
  base_domain: tailscale.example.com
  1. (選擇性)在derp這一段,啟用DERP伺服器,也就是relay伺服器。Tailscale客戶端在P2P失敗之後會嘗試去連Tailscale公司的relay伺服器,這可以保留以備不時之需。若將url填入空陣列,Tailscale就只能使用我們自架的DERP了。DERP預設使用443進行HTTPS連線,但後面Nginx已經佔用了。讓DERP監聽另一個通訊埠,再由Nginx轉發。
derp:
  server:
    enabled: true
    region_id: 999
    region_code: "headscale"
    region_name: "Headscale Embedded DERP"
	stun_listen_addr: "0.0.0.0:3478"

  urls:
    - https://controlplane.tailscale.com/derpmap/default
  paths: []
  auto_update_enabled: true
  update_frequency: 24h
  1. 編輯完退出。啟動容器
docker compose up -d
  1. 檢查伺服器是否上線,應該會回傳success。
curl http://127.0.0.1:8080/health
  1. docker logs headscale指令檢視伺服器輸出日誌。

4. 建立Nginx反向代理
#

  1. 我這裡是分配一個子網域的方式來處理Headscale的連線。例如https://headscale.example.com,透過Nginx代理到伺服器上Headscale的http://127.0.0.1:8080通訊埠。

  2. 編輯/etc/nginx/nginx.conf,加入這段

http {

map $http_upgrade $connection_upgrade {
    default      upgrade;
    ''           close;
}
  1. 再編輯/etc/nginx/sites-available/headscale,加入以下內容
server {
    listen 80;
    server_name <子網域>;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name <子網域>;

    ssl_certificate /etc/letsencrypt/live/<子網域>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<子網域>/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080; # Docker容器的通訊埠
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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_buffering off;
    }
}       
  1. 重新載入nginx設定檔
sudo ln -s /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/

sudo nginx -t

sudo systemctl restart nginx
  1. 若有啟用防火牆,允許STUN通過
sudo ufw allow 3478/udp

5. 初始化Headscale管理員帳號
#

Headscale註冊帳號的機制很基本,就是建立一個管理員帳號,然後加入節點。沒有外部帳號註冊機制。

  1. 進入容器
docker exec -it headscale \
headscale help
  1. 新增任意使用者名稱
docker exec -it headscale \
  headscale users create <使用者名稱>

6. 給Headscale安裝Headplane網頁管理界面
#

(選擇性步驟)

不幸的是,Headscale沒有網頁管理界面,這個也得靠第三方開發的方案。

這裡我採用Aarnav Tale開發的「Headplane」方案,它是各方方案中相對完備的管理面板。

  1. 在Headscale建立一個API金鑰,用於給Headplane存取
docker exec headscale \
headscale apikeys create
  1. 增存放資料的目錄,我將它與Headscale放在一起
cd ~/headscale

mkdir -p ./{headplane-config,headplane-data}
  1. 編輯剛剛新增的docker-compose,額外加入一個新的容器服務。Headplane同時要讀取Headscale的設定檔
services:
  headplane:
    image: ghcr.io/tale/headplane:latest
    container_name: headplane
    restart: unless-stopped
    ports:
      - '127.0.0.1:3000:3000'
    volumes:
      - './headplane-config/config.yaml:/etc/headplane/config.yaml'
      - './headplane-data:/var/lib/headplane'
  1. 下載Headplane的範本設定檔
wget -O ./headplane-config/config.yaml  https://raw.githubusercontent.com/tale/headplane/refs/heads/main/config.example.yaml
  1. 編輯headplane-config/config.yaml

  2. 填入Headscale伺服器的公網網址,子網域

headscale:
  url: "https://headscale.example.com"
  1. 填入Headscale 的 API Key
  headscale_api_key: ""
  1. openssl rand -base64 32 | head -c 32 ; echo指令產生一個長隨機字串作為cookie secret
server:
  cookie_secret: ""
  1. 重新啟動容器
docker compose down

docker compose up -d
  1. 新增一個子網域,例如https://headplane.exmaple.com

  2. docker logs headplane指令檢視伺服器輸出日誌。

  3. 設定Nginix服務/etc/nginx/sites-available/headplane,填入以下內容。透過子網域連向Headplane的http://127.0.0.1:3000

server {
    listen 80;
    listen [::]:80;

    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name <子網域>;

    ssl_certificate /etc/letsencrypt/live/<子網域>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<子網域>/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://localhost:8080/; # Headscale的通訊埠
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_buffering off;

        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;
        add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
    }

    location /admin/ {
        proxy_pass http://localhost:3000; # Headplane通訊埠
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_buffering off;
        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;
    }
}
  1. 重新載入nginx設定檔
sudo ln -s /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/

sudo nginx -t

sudo systemctl restart nginx
  1. 開啟https://headplane.exmaple.com/admin登入,輸入API金鑰。
    hp.webp

註:目前版本似乎有bug無法連線,參見https://github.com/tale/headplane/issues/82

7. 將Tailscale客戶端連線到Headscale伺服器
#

目前還沒有所謂第三方開發的Tailscale客戶端,就沿用官方專案的就好,它允許你切換連線伺服器。要是哪天Tailscale公司吃相變難看,禁止使用第三方伺服器,到時候就會有fork出來了吧。

請先將Tailscale客戶端的帳號登出,斷開與Tailscale公司伺服器的連結。Headscale的資料與Tailscale公司無關,登出Tailscale之後,你在Tailscale公司伺服器的資料還是會在,日後要用再加回去就好。

Linux
#

執行以下指令,連線到Headscale(註:若你是透過SSH操作,建議不要用Tailscale的IP登入,改用主機的真實IP,免得設定到一半斷線)

tailscale up --force-reauth --login-server https://網域
  1. 點選畫面顯示的網址

    re.webp

  2. 按照網頁顯示的指令,在Headscale伺服器輸入金鑰

docker exec -it headscale \
headscale nodes register --key <金鑰> --user <使用者名稱>
  1. 這樣就加入節點成功了。

  2. 在Headscale使用以下指令確認節點,以及虛擬區域IP。

docker exec -it headscale \
headscale nodes list
  1. MagicDNS的網址依照節點的名稱決定,重新命名節點為你喜歡的名字
docker exec -it headscale \
headscale nodes rename <id> <新名稱>

9 . 在Tailscale客戶端使用指令確認目前的連線

tailscale status

tailscale netcheck

Android
#

Android手機點右上角 Accounts → use an alternative server,填入Headscale伺服器網域

然後依照網頁指示在Headscale伺服器執行指令,加入節點。

參考資料
#

相關文章


此處提供二種留言板。點選按鈕,選擇您覺得方便的留言板。

(留言板載入中)這是Giscus留言板,需要Github帳號才能留言。支援Markdown語法,若要上傳圖片請善用外部圖床。您的留言會在Github Discussions向所有人公開。

Click here to edit your comments.

(留言板載入中)這是Disqus留言板,您可能會看到Disqus強制投放的廣告。為防止垃圾內容,有時留言可能會被系統判定需審核,導致延遲顯示,請見諒。若要上傳圖片請善用外部圖床網站。