用 Traefik 搭配 Docker 快速架設服務

更新: 2019.01.10 新增教學影片

drone traefik docker deploy

相信大家在架設服務肯定會選一套像是 HAProxy, Nginx, ApacheCaddy,這四套架設的難度差不多,如果要搭配 Let’s Encrypt 前面兩套需要自己串接 (Nginx, Apache),而 Caddy 是用 Golang 開發裡面已經內建了 Let’s Encrypt,,管理者不用擔心憑證過期,相當方便。但是本篇我要介紹另外一套工具叫 Traefik,這一套也是用 Go 語言開發,而我推薦這套的原因是,此套可以跟 Docker 很深度的結合,只要服務跑在 Docker 上面,Traefik 都可以自動偵測到,並且套用設定。透過底下的範例讓 Traefik 串接後端兩個服務,分別是 domain1.comdomain2.com。來看看如何快速設定 Traefik。

traefik + docker + golang

影片教學

不想看內文的,可以直接參考 Youtube 影片,如果喜歡的話歡迎訂閱

撰寫服務

我們先透過底下 Go 語言實作後端,並且放到 Docker Hub 內,方便之後透過 docker-compose 設定。

 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
package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"
)

// HelloWorld for hello world
func HelloWorld() string {
    return "Hello World, golang workshop!"
}

func handler(w http.ResponseWriter, r *http.Request) {
    log.Printf("Got http request. time: %v", time.Now())
    fmt.Fprintf(w, "I love %s!", r.URL.Path[1:])
}

func pinger(port string) error {
    resp, err := http.Get("http://localhost:" + port)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode != 200 {
        return fmt.Errorf("server returned not-200 status code")
    }

    return nil
}

func main() {
    var port string
    var ping bool
    flag.StringVar(&port, "port", "8080", "server port")
    flag.StringVar(&port, "p", "8080", "server port")
    flag.BoolVar(&ping, "ping", false, "check server live")
    flag.Parse()

    if p, ok := os.LookupEnv("PORT"); ok {
        port = p
    }

    if ping {
        if err := pinger(port); err != nil {
            log.Printf("ping server error: %v\n", err)
        }

        return
    }

    http.HandleFunc("/", handler)
    log.Println("http server run on " + port + " port")
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

撰寫 Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
FROM alpine:3.8

LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \
  org.label-schema.name="Drone Workshop" \
  org.label-schema.vendor="Bo-Yi Wu" \
  org.label-schema.schema-version="1.0"

RUN apk add --no-cache ca-certificates && \
  rm -rf /var/cache/apk/*

ADD release/linux/i386/helloworld /bin/

ENTRYPOINT ["/bin/helloworld"]

設定 Drone 自動上傳到 DockerHub,使用 drone-docker 外掛。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  - name: publish
    image: plugins/docker:17.12
    settings:
      repo: appleboy/test
      auto_tag: true
      dockerfile: Dockerfile.alpine
      default_suffix: alpine
      username:
        from_secret: docker_username
      password:
        from_secret: docker_password
    when:
      event:
        - push
        - tag

其中 docker_usernamedocker_password 可以到 drone 後台設定。

啟動 Traefik 服務

如果只是單純綁定在非 80 或 443 port,您可以用一般帳號設定 Traefik,設定如下:

 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
debug = false

logLevel = "INFO"
defaultEntryPoints = ["http"]

[entryPoints]
  [entryPoints.http]
  address = ":8080"

[retry]

################################################################
# Docker Provider
################################################################

# Enable Docker Provider.
[docker]

# Docker server endpoint. Can be a tcp or a unix socket endpoint.
#
# Required
#
endpoint = "unix:///var/run/docker.sock"

# Enable watch docker changes.
#
# Optional
#
watch = true

上面設定可以看到將 Traefik 啟動在 8080 port,並且啟動 Docker Provider,讓 Traefik 可以自動偵測目前 Docker 啟動了哪些服務。底下是啟動 Traefik 的 docker-compose 檔案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
version: '2'

services:
  traefik:
    image: traefik
    restart: always
    ports:
      - 8080:8080
      # - 80:80
      # - 443:443
    networks:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.toml:/traefik.toml
      # - ./acme.json:/acme.json
    container_name: traefik

networks:
  web:
    external: true

啟動 Traefik 環境前需要建立虛擬網路名叫 web

1
2
$ docker network create web
$ docker-compose up -d

啟動 App 服務

接著只要透過 docker-compose 來啟動您的服務

 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
version: '3'

services:
  app_1:
    image: appleboy/test:alpine
    restart: always
    networks:
      - web
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:domain1.com"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

  app_2:
    image: appleboy/test:alpine
    restart: always
    networks:
      - web
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:domain2.com"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

networks:
  web:
    external: true

大家可以清楚看到透過設定 docker label 可以讓 Traefik 自動偵測到系統服務

1
2
3
4
5
6
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:domain2.com"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

其中 traefik.basic.frontend.rule 可以填入網站 DNS Name,另外 traefik.basic.port=8080 則是服務預設啟動的 port (在 Go 語言內實作)。

驗證網站是否成功

1
2
$ curl -v http://domain1.com:8080/test
$ curl -v http://domain2.com:8080/test

bash screen

搭配 Let’s Encrypt

這邊又要感謝 Go 語言內建 Let’s Encrypt 套件,讓 Go 開發者可以快速整合憑證,這邊只需要修正 Traefik 服務設定檔

 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
logLevel = "INFO"
defaultEntryPoints = ["https","http"]

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
  [entryPoints.https.tls]

[retry]

[docker]
endpoint = "unix:///var/run/docker.sock"
watch = true
exposedByDefault = false

[acme]
email = "appleboy.tw@gmail.com"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"

跟之前 Traefik 比較多了 entryPointsacme,另外在 docker-compose 內要把 80 及 443 port 啟動,並且將 acme.json 掛載進去

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
version: '2'

services:
  traefik:
    image: traefik
    restart: always
    ports:
      - 80:80
      - 443:443
    networks:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.toml:/traefik.toml
      - ./acme.json:/acme.json
    container_name: traefik

networks:
  web:
    external: true

其中先建立 acme.json 並且設定權限為 600

1
2
$ touch acme.json
$ chmod 600 acme.json

再重新啟動 Traefik 服務

1
2
$ docker-compose down
$ docker-compose up -d

最後只要改 traefik.basic.frontend.rule 換成真實的 Domain,你會發現 Traefik 會將憑證內容寫入 acme.json。這也為什麼我們需要將 acme.json 建立在 Host 空間上。

搭配 Drone 自動化更新服務

未來所有服務都可以透過 docker-compose 來啟動,所以只要透過 Drone 將 一些 yaml 設定檔案傳到服務器即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  - name: scp
    image: appleboy/drone-scp
    pull: true
    settings:
      host: demo1.com
      username: deploy
      key:
        from_secret: deploy_key
      target: /home/deploy/gobento
      rm: true
      source:
        - release/*
        - Dockerfile
        - docker-compose.yml

上面將檔案丟到遠端機器後,再透過 ssh 編譯並且部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  - name: ssh
    image: appleboy/drone-ssh
    pull: true
    settings:
      host: console.gobento.co
      user: deploy
      key:
        from_secret: deploy_key
      target: /home/deploy/demo
      rm: true
      script:
        - cd demo && docker-compose build
        - docker-compose up -d --force-recreate --no-deps demo
        - docker images --quiet --filter=dangling=true | xargs --no-run-if-empty docker rmi -f

心得

本篇教大家一步一步建立 Traefik 搭配 Docker,相對於 Nginx 我覺得簡單非常多,尤其時可以在 docker-compose 內設定 docker Label,而 Traefik 會自動偵測設定,並且重新啟動服務。希望這篇對於想要快速架設網站的開發者有幫助。如果您有在用 AWS 服務,想省錢可以使用 Traefik 幫您省下一台 ALB 或 ELB 的費用。最後補充一篇效能文章:『NGINX、HAProxy和Traefik负载均衡能力对比』有興趣可以參考一下。


See also