traefik tutorial
前言
當我們在架設後端 service 的時候很常會遇到要將 service A 和 service B 放在同一個伺服器中運行,這時候會遇到一個問題:要如何設置 Endpoint 讓使用者能分別使用 service A 與 service B 呢?
- 方法ㄧ:將 service A 暴露(expose?)在 port A (例如:9000),將 service B 暴露在與 port A 不同的 port B (例如:3000),這樣使用者就能分別從 <伺服器 IP>:<portA> 、<伺服器 IP>:<portB> (例如:140.116.241.66:9000,140.116.241.66:3000)存取 service A 與 B
- 方法二:使用 reverse proxy ,設置 reverse proxy 監聽 port C (例如:80),使用者只要向 <伺服器 IP>:<portC> 發送 request , reverse proxy 會根據使用者請求分別將 request 轉送到不同的 service (例如:140.116.241.66:80/A → service A、140.116.241.66:80/B → service B)。以下文章會介紹如何使用 traefik 作為 reverse proxy
簡單介紹 traefik
在 traefik 的官網中是如此介紹 traefik 的
Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience.
traefik 的功能其實不只有 reverse proxy 而已,還有 load balancing 、 api gateway …等功能,本篇文章將 focus 在 reverse proxy 上。
與其他 reverse proxy 的區別
traefik 與其他 reverse proxy 軟體(例如: Nginx、HAProxy、Caddy …)最大的區別在於 traefik 在設置上能與 docker 緊密結合(Label Based,接下來會仔細說明)也能與許多環境配合(kubernetes ,docker swarm…)。
traefik 設置的架構
Configuration Discovery
在我們一般的認知中當我們要設置軟體時,通常會想到透過更改 config file 或透過 command line argument 去更改軟體設定,但 traefik 的設定比較複雜。在 traefik 中有所謂的 provider (例如:Docker, Kubernetes IngressRoute…),traefik 會透過這些 provider 去拿到設置的詳細資訊(當然也可透過 config file 或 command line 設定)。以下以 docker-compose 舉例:
example-docker-compose.yaml
version: "3.8"
services:
whoami:
image: "containous/whoami"
labels:
- "traefik.enable=true" # <- 注意這裏
當我們設置 traefik 使用 Docker 作為 provider 時,traefik 就會從 docker 抓取 container 資料,以上述 docker-compose 為例,當 traefik 發現有 container 的 labels 為 traefik.enable=true
時,就會認定該 container 需要用到 traefik 的功能,並根據其設定為該 container 開啟 traefik 的功能。
Routing & Load Balancing
EntryPoints
在設定所有的 routing 之前,我們必須先設定 traefik 的 entrypoint 。所有的 request 都會從 entrypoint 進入,再由 traefik 轉送到指定的 service 。以下以 traefik 的 static config file 為例:
traefik.yaml
entryPoints:
web: # <- 這邊為使用者自己定義的名稱
address: ":80"
web2:
address: ":8080"
以上述的 config file 為例,若我們要發送 request 到伺服器,就必須發送到 <伺服器 IP>:<80>
或 <伺服器 IP>:<8080>
Routers
當 request 由 entrypoint 進入後要如何決定將其轉發到哪一個 service 就需要透過設定 Routers 解決。在設定 Router 時需要考慮以下幾點:
EntryPoint: traefik 預設 router 會監聽每一個 entryPoint ,但我們也可以設定成指監聽某幾個 entryPoint 。以下範例我們設置一個 Router 名稱為 “myrouter” 並指定其 entryPoint 為 web2
example-docker-compose.yaml
version: "3.8" services: whoami: image: "containous/whoami" labels: - "traefik.enable=true" - - "traefik.http.routers.myrouter.entrypoints=web2" # <- "myrouter" 為使用者自己定義的名稱
Rule: traefik 會根據使用者設定的 rule 決定 request 會到哪一個 router 。以下範例我們設置一個 rule 只讓 host 名稱為
myserverdomainname.com
的 request 進到此 router 。example-docker-compose.yaml
version: "3.8" services: whoami: image: "containous/whoami" labels: - "traefik.enable=true" - "traefik.http.routers.myrouter.entrypoints=web2" - "traefik.http.routers.myrouter.rule=Host(`myserverdomainname.com`)"
以上範例為使用
Host()
來設置 rule ,其實還有許多方法Method()
、Path()
…,更多方法可以參考官網。Priority: 在設置 rule 時可能發生 request 同時滿足兩個 router 的 rule,這時後需要設定 Priority 才能決定 request 要轉送到哪個 router 。
Middlewares: 我們可以針對 route 設定 middleware ,在 request 轉送到 service 之前對其處理,詳細的設置內容會在下一個章節提到。
Service: 每一個 route 必須指定一個 service 作為目標,當 request 進到該 router 後會轉送到目標 service 。這時候你可能會想:為什麼需要一個 service 呢?直接將 router 目標指向某個 後端應用程式的 url 就好了啊?原因是 traefik 除了提供 reverse proxy 以外還有 LoadBalancer 的功能,透過接下來的範例能更清楚了解。
在 traefik 中需要另外設置 service ,我們先以 config file 的形式再以 docker-compose 的設定方式能更了解 traefik 的 service 是如何運作的。
以下為 config file 形式的 service 設定,其中我們設置一個名稱為
my-service
的 servicetraefik.yaml
http: services: my-service: loadBalancer: servers: - url: "<http://private-ip-server-1/>" - url: "<http://private-ip-server-2/>"
從以上的 config file 可以知道當 request 從某個 router 轉送到 “my-service” 時有可能被轉送到 “http://private-ip-server-1/” 以及 “http://private-ip-server-2/” 而會轉送到哪一個 url 是根據 Round-robin 演算法。
以下為 docker-compose 的設定,其中我們設置一個名稱為
my-service
的 service ,而這個 service 會與 container 的 port 80 連結。example-docker-compose.yaml
version: "3.8" services: whoami: image: "containous/whoami" labels: - "traefik.enable=true" - "traefik.http.routers.myrouter.entrypoints=web2" - "traefik.http.routers.myrouter.rule=Host(`myserverdomainname.com`)" - "traefik.http.services.my-service.loadbalancer.server.port=80"
從以上 docker-compose 可發現若我們使用 labels 設定 service , traefik 會自動將該 container 與設定的 service 連結,我們不需要像 config file 那樣設置 “url” 。
最後我們需要將該 router 指向一個 service 。在以下範例我們設置 Router 名稱為
myrouter
指向一個名稱為my-service
的 serviceexample-docker-compose.yaml
version: "3.8" services: whoami: image: "containous/whoami" labels: - "traefik.enable=true" - "traefik.http.routers.myrouter.entrypoints=web2" - "traefik.http.routers.myrouter.rule=Host(`myserverdomainname.com`)" - "traefik.http.services.my-service.loadbalancer.server.port=80" - "traefik.http.routers.myrouter.service=my-service"
TLS: traefik 的功能也包含處理 HTTPS 連線,此設定較為複雜,可參考另一篇文章(未完成)。
Middlewares
當 request 進入 router 後,我們可以設置 middlewares 對 request 進行處理,再送到 service 。
以下範例我們設置一個名稱為 mymiddleware
的 middleware 並使用 traefik 內建的 ratelimit ,最後我們將 mymiddleware
加到 myrouter
中。
example-docker-compose.yaml
version: "3.8"
services:
whoami:
image: "containous/whoami"
labels:
- "traefik.enable=true"
- "traefik.http.routers.myrouter.entrypoints=web2"
- "traefik.http.routers.myrouter.rule=Host(`myserverdomainname.com`)"
- "traefik.http.services.my-service.loadbalancer.server.port=80"
- "traefik.http.routers.myrouter.service=my-service"
- "traefik.http.middlewares.mymiddleware.ratelimit.average=100"
- "traefik.http.middlewares.mymiddleware.ratelimit.burst=30"
- "traefik.http.routers.myrouter.middlewares=mymiddleware"
traefik 設定範例
以下文章將會實際設定 traefik 作為 reverse proxy ,並會使用 whoami 和 podinfo 作為示範 container。
在開始教學前,請先確認你的環境滿足以下條件
- 可以使用 Docker 以及 Docker-Compose
- 擁有一個 domain name
- 確保 port 80 開啟且並沒有被其他程式使用(不一定要使用 port 80 ,也可改成其他的)
本教學的伺服器環境為 debian bullseye docker 版本為 20.10.12 、 domain name 為 ainimal.io
建立 directory
首先我們建立一個 directory ,接下來的設定檔案都會放在這個 directory 裡面。
mkdir traefik-example
cd traefik-example
docker 環境設定
在設定 traefik 前我們必須建立一個 docker network ,以下範例我們建立一個名稱為 mynetwork
的 docker network 。
docker network create mynetwork
traefik 必需與 container 存在於同一個 docker network 才能順利運行,我們會將此 docker network 在接下來的 docker-compose 中設定為預設 network。
建立 docker-compose 環境變數
在 traefik-example
目錄下建立一個 .env
檔案,將所有 docker-compose 環境變數儲存在其中。此步驟並非必須,但之後若要更改 docker-compose 中的一些參數會較方便。
.env
MY_DOMAIN=ainimal.io
DEFAULT_NETWORK=mynetwork
Traefik static configuration
在 traefik-example
目錄下建立一個 traefik.yaml
檔案。
我們通常會在此設置較為“靜態”的設定,以下範例我們設定其 entryPoints 與 providers
traefik.yaml
entryPoints:
web:
address: ":80"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
注意 exposedByDefault 設定,因為這裏設置為 false 所以如果有 container 需要使用到 traefik 的功能就必須設定該 container 的 label 為 "traefik.enable=true"
Docker-compose 設定
設定 traefik
在
traefik-example
目錄下建立一個docker-compose.yaml
檔案。我們需要先將 docker 的 socket mount 到 traefik container 中,這樣 traefik 才能從 docker socket 獲取資訊,接著將設定檔 traefik.yaml mount 到 traefik container 中。備註:其中
....:ro
代表 read only 。docker-compose.yaml
version: "3.8" services: traefik: image: "traefik:v2.5" ports: - "80:80" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "./traefik.yaml:/traefik.yaml:ro" # 將上述建立的 static config mount 到 container 中
接著我們設定 docker-compose 的 預設 network ,讓docker-compose 裡的 container 預設都是使用
$DEFAULT_NETWORK
作為 docker network 。docker-compose.yaml
version: "3.8" services: traefik: image: "traefik:v2.5" container_name: "traefik" ports: - "80:80" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "./traefik.yaml:/traefik.yaml:ro" networks: default: external: name: $DEFAULT_NETWORK # 在此範例相當於 "mynetwork"
這時候我們執行 docker-compose
docker-compose -f docker-compose.yaml up -d
來測試 traefik container 是否能正確運行。如果 traefik 成功啟動,在執行上述指令後應該會出現 “Creating traefik … done” 。我們可以透過指令
docker logs traefik
來查看 logs ,若 traefik 成功讀取 config file 則 logs 中會出現 “Configuration loaded from file: /traefik.yml” 。這時候若我們在伺服器中執行
curl localhost
應該會出現 “404 page not found” ,我們也可以直接在本地端透過指令curl <伺服器 IP>
測試或在瀏覽器輸入伺服器 IP 位置。設定 domain name
設置 domain name A record ,指向伺服器 IP,以下範例設置了 whoami.ainimal.io 以及 podinfo.yungen.me 兩個 A record
設定 whoami podinfo container
我們接下來在 docker-compose file 中增加whoami container
docker-compose.yaml
version: "3.8" services: traefik: image: "traefik:v2.5" container_name: "traefik" ports: - "80:80" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "./traefik.yaml:/traefik.yaml:ro" whoami: image: "traefik/whoami" container_name: "whoami" labels: - "traefik.enable=true" - "traefik.http.routers.whoami-router.entrypoints=web" - "traefik.http.routers.whoami-router.rule=Host(`whoami.$MY_DOMAIN`)" # 在此範例相當於 "whoami.ainimal.io" - "traefik.http.services.whoami-service.loadbalancer.server.port=80" - "traefik.http.routers.whoami-router.service=whoami-service" networks: default: external: name: $DEFAULT_NETWORK
從以上範例中可以看到我們設置了 Router:
whoami-router
、 Service:whoami-service
,並將 Router 的 rule 設定為Host(
whoami.$MY_DOMAIN)
,也就是說只有 request host 為whoami.$MY_DOMAIN
才能進入這個 Router 。設定 podinfo container 與 whoami 大同小異
docker-compose.yaml
version: "3.8" services: traefik: image: "traefik:v2.5" container_name: "traefik" ports: - "80:80" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "./traefik.yaml:/traefik.yaml:ro" whoami: image: "traefik/whoami" container_name: "whoami" labels: - "traefik.enable=true" - "traefik.http.routers.whoami-router.entrypoints=web" - "traefik.http.routers.whoami-router.rule=Host(`whoami.$MY_DOMAIN`)" # 在此範例相當於 "whoami.ainimal.io" - "traefik.http.services.whoami-service.loadbalancer.server.port=80" - "traefik.http.routers.whoami-router.service=whoami-service" podinfo: image: "stefanprodan/podinfo" container_name: "podinfo" labels: - "traefik.enable=true" - "traefik.http.routers.podinfo-router.entrypoints=web" - "traefik.http.routers.podinfo-router.rule=Host(`podinfo.$MY_DOMAIN`)" # 在此範例相當於 "podinfo.ainimal.io" - "traefik.http.services.podinfo-service.loadbalancer.server.port=9898" - "traefik.http.routers.podinfo-router.service=podinfo-service" networks: default: external: name: $DEFAULT_NETWORK
執行
docker-compose -f docker-compose.yaml up -d
這時候若我們對 whoami.ainimal.io 以及 podinfo.ainimal.io 發送 request 會得到相對應的 response ,如下圖: