更完善的 Docker + Traefik 使用方案

蘇洋發表於2018-09-18

本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或重新修改使用,但需要註明來源。 署名 4.0 國際 (CC BY 4.0)

本文作者: 蘇洋

統計字數: 10813字 閱讀時間: 22分鐘閱讀 本文連結: soulteary.com/2018/08/28/…


更完善的 Docker + Traefik 使用方案

在踩坑無數之後,多次修改後,這篇草稿箱中的文字終於得以成型,撒花。

六月更新架構的時候,去掉了 openresty 作為伺服器前端,取而代之的是裸跑 Traefik,因為只暴露閘道器的 80 / 443,後面所有子容器都是以 expose 方案對內暴露埠到一塊虛擬網路卡上,安全問題也不大,閘道器掛載著萬用字元證書,可以方便的新增刪除後面的應用,雖說用起來挺舒服的,但是有兩點始終讓我不是很爽。

  1. 因為 docker 直接使用 iptable 操作埠轉發,在不修改 iptable 的前提下,之前積累了大量的 fail2ban 規則無法使用了,也就是說得忍受大量掃描器汙染日誌。
  2. 因為應用跑完全隔離的方案,子容器能拿得到的客戶端 IP 都是虛擬網路卡 IP ,想統計個資料,得用對賬的方案,比較麻煩。

問了一下之前出去創業的師傅,他們直接在給容器分配公網,壕無人道,而且直接分配公網 IP 配置專案也不少...

如何能在最少配置的情況下,最少資源消耗的情況下,達到現有使用的便捷程度,便更為了我這篇文字的主要目的。

趕在休假結束之前,重新梳理了伺服器執行環境和Traefik的使用,記錄下來,或許對摺騰“免”運維的服務的你也會有幫助。

在折騰具體配置之前,需要先提供標準的系統環境。

基於 Ubuntu 18.04 系統配置 Docker 執行環境

2018年第三個季度,Ubuntu 18.04 更新了快一個季度了,基本上該更新的軟體也都更新了,該相容的軟體也都進行了相容處理;Docker 在經歷改名風波後,版本迭代高歌猛進,一大波編排軟體蜂擁而至,現在版本也升級到了 v18 。

考慮到後面軟體會做越來越多向後相容的事情,16.04 的維護週期也近半,那麼這次就使用最新的系統和軟體來進行基礎環境的配置吧,這裡沒有選擇 CoreOS 是因為我這裡還有一些其他軟體的使用需求,想在一個相對中立的環境中使用。

截止這篇文章寫成: 我使用的國外主流雲廠商皆支援 Ubuntu 18.04, 國內的雲廠商中,阿里雲支援,但是騰訊雲暫時還不支援。

如果你的雲主機廠商支援最新版本的系統,那麼可以直接參考我下面給出的命令進行基礎環境配置。

如果你的雲主機廠商不支援,那麼請使用 do-release-upgrade 命令先進行手動升級,升級過程中可以一路 yes,以及使用當前軟體維護的最新版本: install the package maintainer's version

下面開始介紹如何配置最基礎的系統環境。

先更新軟體包列表,升級軟體版本到最新的穩定版,然後為避免系統中殘留一些老古董影響軟體執行,我們要進行嘗試性的解除安裝老版本軟體操作,以及安裝一些常用軟體。

apt update && apt upgrade -y
apt remove docker docker-engine docker.io
apt install -y apt-transport-https  ca-certificates curl software-properties-common
複製程式碼

向系統中新增 Docker 官方 GPG Key,然後驗證該 Key 有效性,並更新倉庫源到系統,Ubuntu 18.04 會直接觸發拉取軟體包列表的操作,比較人性化,最後直接敲入 install 命令,進行社群版的 docker 安裝即可。

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
apt-key fingerprint 0EBFCD88
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt install -y docker-ce
複製程式碼

https://github.com/docker/compose/releases 找到最新穩定版本安裝。

curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
複製程式碼

至此,基礎的 docker 環境就安裝就緒了。

Docker version 18.06.1-ce, build e68fc7a
docker-compose version 1.22.0, build f46880fe
Storage Driver: overlay2
複製程式碼

簡單的系統加固

基礎的修改 ssh 埠,規避掃描器,應該人人都會,就略過不提,如果不會可以百度或者翻閱之前的部落格文章,我們來說說 ufw 防火牆的配置。

服務預設會是未啟用狀態,在啟用之前,務必先豁免 ssh 埠,避免再使用 vnc 登入上去補救。

> ufw status
Status: inactive

ufw all SSH_PORT
Rules updated
Rules updated (v6)
複製程式碼

當然,如果你不喜歡修改 SSH 埠的話,可以直接使用下面的命令。

ufw disable

ufw reset
ufw default deny incoming
ufw default allow outgoing

ufw allow 22/tcp
ufw allow 80/tcp

ufw enable
複製程式碼

然後啟用防火牆狀態。

ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
複製程式碼

防火牆啟動完畢,可以順便折騰一下 fail2ban ,大幅減少常規的掃描器對於日誌的騷擾,和一些初級的猜解、滲透,後面再寫一篇詳述。

Docker 埠繫結和 UFW 的衝突

在配置完畢防火牆後,接下來可以試驗配置是否生效,啟動一個對映到 80 埠的 nginx:alpine 映象,然後瀏覽器或者命令列訪問伺服器公網IP,可以看到熟悉的 nginx 預設歡迎頁,ufw 並沒有什麼作用。

root@VM-0-10-ubuntu:~# docker run --rm -p 80:80 nginx:alpine
Unable to find image 'nginx:alpine' locally
alpine: Pulling from library/nginx
911c6d0c7995: Pull complete 
131e13eca73f: Pull complete 
95376bf29516: Pull complete 
6717402ec973: Pull complete 
Digest: sha256:23e4dacbc60479fa7f23b3b8e18aad41bd8445706d0538b25ba1d575a6e2410b
Status: Downloaded newer image for nginx:alpine
114.xxx.xxx.xxx - - [27/Aug/2018:16:44:17 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" "-"
2018/08/27 16:44:18 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 114.xxx.xxx.xxx, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "119.28.182.48", referrer: "http://119.28.182.48/"
114.xxx.xxx.xxx - - [27/Aug/2018:16:44:18 +0000] "GET /favicon.ico HTTP/1.1" 404 571 "http://119.28.182.48/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" "-"
複製程式碼

這裡 ufw 沒有生效的原因在於 docker 預設使用了 iptable 新增了一些轉發規則,壓根沒有走到 ufw 的規則中。

一般網上推薦的方案是,關閉這個特性,比如使用類似下面的操作。

echo '{"iptables": false}' | sudo tee /etc/docker/daemon.json > /dev/null
複製程式碼

但是如果你真的這樣做了,接下來你將無法獲得客戶端訪問時,使用的IP資訊,各個容器直接訪問網際網路、以及容器互通方面也會遇到一些小問題,然後你又不得不新增一些 iptable 去修正這個問題。

如果你還是選擇關閉 iptable 特性,執行容器,那麼如果你想獲取客戶端IP,便只能使用以下幾個方案達成:

  1. 前端啟動一個L7的Haproxy / nginx反向代理後面的服務。
  2. 執行模式改為 host ,放棄容器完整虛擬化,將埠直接暴露到 host 上,此時將不再能夠通過 docker ps 檢視到你的容器埠狀態,只能通過 docker network inspect 網路卡看到對應的埠開啟狀態,十分不利於維護。
    • 如果你是使用經典的 docker run 命令,那麼需要配合 --net=host 引數。
    • 如果你是使用非 swarm 模式的 compose, 則需要宣告 network_mode: "host", compose 版本需要宣告 3.2 及以上, port 匯出實際並不需要用繁瑣的模式定義。
    • 不用嘗試建立自定義網路為 host ,截止本文完成時的編排工具版本以及 docker 版本,這個功能不支援。
  3. 修改 ufw 、docker 的 iptable 轉發規則,完成你想要的轉發方式。

可以看到,不管是哪種方案,搞起來都十分繁瑣,而且不利於重複部署,未來除錯維護成本太高了。

更好的方案

讓 Docker 保持預設配置和行為,但是留出埠控制權給 UFW 以及外層的閘道器,子容器依舊全部使用 expose 使用私有化的方法匯出埠給閘道器。

這個方案是不是看起來和上面小節中的方案1很相似,但是其實差別還不小,使用 Traefik 可以在不不配 consul / zk 的情況下,自動監聽 docker daemon 的狀況,做到服務發現、負載均衡、可用性自動切換、甚至自動繫結域名證書。

之前不得不說是過分追求全容器方案,導致我使用 Traefik 都是在容器中。雖說升級相對輕鬆,只需要修改 compose 配置中的版本欄位即可,程式可用性也不需要太過關注,直接交付給 Linux Daemon 去維護,但是這樣就面臨一個問題,閘道器拿到資料的時候,已經經過了至少兩塊虛擬網路卡的轉發,一來浪費效能,二來丟失客戶端IP,三來如果要保障更高階別的安全,還得關閉 docker iptable 轉發的特性,這個面臨的問題,上面的小節裡說的夠多了。

在決定“裸”執行 Traefik 後,我們需要對它的配置進行一定的改動,我這裡提供一份最簡單的配置,相信已經可以滿足許多常見場景。

################################################################
# 全域性設定
################################################################

# 啟用除錯模式 (預設關閉)
debug = true

# 日誌等級 (預設 ERROR)
logLevel = "INFO"

# 全域性入口點型別 (預設 http)
defaultEntryPoints = ["https", "http"]

# 不上報統計資訊
sendAnonymousUsage = false

################################################################
# 入口點設定
################################################################

[entryPoints]

    # 預設前端
    [entryPoints.http]
        address = ":80"
        compress = true
        [entryPoints.http.redirect]
            entryPoint = "https"
    [entryPoints.https]
        address = ":443"
        compress = true
    [entryPoints.https.tls]
        [[entryPoints.https.tls.certificates]]
            certFile = "/data/ssl/your.com.cer"
            keyFile = "/data/ssl/your.com.key"

    # 控制檯埠
    [entryPoints.traefik-api]
        address = ":4399"

    # Ping埠
    [entryPoints.traefik-ping]
        address = ":4398"


################################################################
# Traefik File configuration
################################################################

[file]
    [backends]
        [backends.dashboard]
            [backends.dashboard.servers.server1]
                url = "http://127.0.0.1:4399"
        [backends.ping]
            [backends.ping.servers.server1]
                url = "http://127.0.0.1:4398"

[frontends]
    [frontends.dashboard]
        entrypoints = ["https"]
        backend = "dashboard"
        [frontends.dashboard.routes.route01]
            rule = "Host:dashboard.your.com"
    [frontends.ping]
        entrypoints = ["https"]
        backend = "ping"
        [frontends.ping.routes.route01]
            rule = "Host:ping.your.com"
        [frontends.ping.routes.route02]
            rule = "ReplacePathRegex: ^/ /ping"

################################################################
# Traefik logs configuration
################################################################

# Traefik logs
# Enabled by default and log to stdout
#
# Optional
#
# Default: os.Stdout
[traefikLog]
  filePath = "/data/logs/traefik.log"

[accessLog]
  filePath = "/data/logs/access.log"

# Format is either "json" or "common".
#
# Optional
# Default: "common"
#
# format = "common"

################################################################
# 訪問日誌 配置
################################################################

# Enable access logs
# By default it will write to stdout and produce logs in the textual
# Common Log Format (CLF), extended with additional fields.
#
# Optional
#
# [accessLog]

# Sets the file path for the access log. If not specified, stdout will be used.
# Intermediate directories are created if necessary.
#
# Optional
# Default: os.Stdout
#
# filePath = "/path/to/log/log.txt"

# Format is either "json" or "common".
#
# Optional
# Default: "common"
#
# format = "common"

################################################################
# API 及 控制檯 配置
################################################################

# 啟用API以及控制檯
[api]
    # 入口點名稱
    entryPoint = "traefik-api"

    # 開啟控制檯(預設開啟)
    dashboard = true

    # 預設協議
    defaultEntryPoints = ["https"]

################################################################
# Ping 配置
################################################################

# 啟用 ping
[ping]
    # 入口點名稱
    entryPoint = "traefik-ping"

################################################################
# Docker 後端配置
################################################################

# 啟用Docker後端
[docker]

# Docker服務後端
endpoint = "unix:///var/run/docker.sock"
# 預設域名
domain = "traefix.your.com"
# 監控docker變化
watch = true

# 使用自定義模板(可選)
# filename = "docker.tmpl"

# 對容器預設進行暴露(預設開啟)
#   如果關閉選項,則容器不包含 `traefik.enable=true` 標籤,就不會被暴露
exposedbydefault = false

# 使用繫結埠的IP地址取代內部私有網路(預設關閉)
usebindportip = false

# 使用 Swarm Mode (預設關閉)
swarmmode = false

# Enable docker TLS connection.
#
# Optional
#
#  [docker.tls]
#  ca = "/etc/ssl/ca.crt"
#  cert = "/etc/ssl/docker.crt"
#  key = "/etc/ssl/docker.key"
#  insecureskipverify = true
複製程式碼

將配置做適當修改,儲存之後,執行即可:

traefik -c /etc/traefik.toml
複製程式碼

當然,此時你是無法訪問到你的 Traefik 閘道器提供的服務的,為什麼呢,因為這個軟體埠繫結會受限制於 UFW 的規則,所以我們要更新 UFW 規則,允許外網訪問我們的 80 和 443 埠。

root@VM-0-10-ubuntu:~# ufw allow 80
Rule added
Rule added (v6)
複製程式碼

如果你操作順利,此刻你已經能夠順利訪問你的網站了。

當然,這裡少了 docker daemon 的協助,程式管理還是要看護一下的,推薦使用 supervisor 進行輔助管理,之前的部落格有介紹過不止一次,有興趣可以翻閱,這裡同樣給出一份最基礎的配置參考:

[program:traefik]
command=traefik -c /etc/traefik.toml --sendAnonymousUsage=false
user=root
autostart=true
startsecs=3
startretries=100
autorestart=true
stderr_logfile=/data/traefik/error.log
stderr_logfile_maxbytes=50MB
stderr_logfile_backups=10
stdout_logfile=/dara/traefik/access.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10
複製程式碼

你的閘道器就就緒之後,我們隨便找一個目錄使用一個叫做 whoami 的軟體映象幫助我們驗證:閘道器能夠如期的使用,除了自動服務發現,負載解析,還能提供包括統計、轉發、header重寫等功能。

version: '3'

services:
  whoami:
    image: emilevauge/whoami
    expose:
      - 80
    labels:
      - "traefik.enable=true"
      - "traefik.port=80"
      - "traefik.frontend.rule=Host:who.your.com"
複製程式碼

將上面的配置儲存為 docker-compose.yml,然後後臺執行起來。

瀏覽器或者命令列訪問 who.your.com,獲得下面的資訊:

Hostname: b0cd60b18550
IP: 127.0.0.1
IP: 172.20.0.2
GET / HTTP/1.1
Host: who.your.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 221.xxx.xx.xxx
X-Forwarded-Host: who.your.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: VM-0-10-ubuntu
X-Real-Ip: 221.xxx.xx.xxx
複製程式碼

可以看到服務發現、SSL證書掛載、源站IP轉發等功能都能夠正確使用,而且不出意料,QPS 也會高不少(畢竟少了至少一層網路轉發、至少一層完整的虛擬化)。

另外,由於沒有修改 docker 的配置,容器不會出現不允許訪問外網的情況,簡單驗證:

root@VM-0-10-ubuntu:/data/who# docker run -it --rm alpine ping -c 1 8.8.8.8
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
8e3ba11ec2a2: Pull complete 
Digest: sha256:7043076348bf5040220df6ad703798fd8593a0918d06d3ce30c6c93be117e430
Status: Downloaded newer image for alpine:latest
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=46 time=14.075 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 14.075/14.075/14.075 ms
複製程式碼

接下來就是逐步升級每臺伺服器以及做資料遷移了。

資源連結

其實關於容器內獲取外部IP,社群有大量討論,比如:#15086 等等,涉及不同的網路卡模式,不同的編排工具,不同的埠對映模式,又有許多延伸話題。

而 Docker 和 UFW 防火牆的恩怨情仇,其實也是老化長談,但是不知道為何,網上能看到的資料一邊倒到修改 iptable ...

其他

希望本文能夠給你一些額外的啟示,幫到正在使用 Traefik 和 Docker 來做服務化的你。


我現在有一個小小的折騰群,裡面聚集了一些喜歡折騰的小夥伴。

在不發廣告的情況下,我們在裡面會一起聊聊軟體、HomeLab、程式設計上的一些問題,也會在群裡不定期的分享一些技術沙龍的資料。

喜歡折騰的小夥伴歡迎掃碼新增好友。(請註明來源和目的,否則不會通過稽核) 關於折騰群入群的那些事

關於折騰群入群的那些事

相關文章