用 Traefik 搭配 Docker 快速架設服務

用 Traefik 搭配 Docker 快速架設服務
用 Traefik 搭配 Docker 快速架設服務

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

用 Traefik 搭配 Docker 快速架設服務
用 Traefik 搭配 Docker 快速架設服務

撰寫服務

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

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

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 插件。

- 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,設置如下:

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 文件

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

啟動環境錢需要創建 web 網絡

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

啟動 App 服務

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

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 自動偵測到系統服務

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 語言內實作)。

驗證網站是否成功

$ curl -v http://domain1.com:8080/test
$ curl -v http://domain2.com:8080/test
用 Traefik 搭配 Docker 快速架設服務
用 Traefik 搭配 Docker 快速架設服務

搭配 Let’s Encrypt

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

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 掛載進去

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

$ touch acme.json
$ chmod 600 acme.json

再重新啟動 Traefik 服務

$ docker-compose down
$ docker-compose up -d

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

搭配 Drone 自動化更新服務

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

- 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 編譯並且部署

- 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 的費用。