如何正确设置基本的 traefik 反向代理?

How can I properly setup basic traefik reverse proxy?

假设我当前的 public IP 是 101.15.14.71,我有一个名为 example.com 的域,我使用 cloudflare 配置了它,并且我创建了多个指向我的 public 的 DNS 条目ip.

例如:

1) new1.example.com - 101.15.14.71
2) new2.example.com - 101.15.14.71
3) new3.example.com - 101.15.14.71

现在,这是我的示例项目结构,

├── myapp
│   ├── app
│   │   └── main.py
│   ├── docker-compose.yml
│   └── Dockerfile
├── myapp1
│   ├── app
│   │   └── main.py
│   ├── docker-compose.yml
│   └── Dockerfile
└── traefik
    ├── acme.json
    ├── docker-compose.yml
    ├── traefik_dynamic.toml
    └── traefik.toml

这里我有两个 fastAPI(即 myapp、myapp1)

这是我在 myapp 和 myapp1 中 main.py 中的示例代码,它完全相同,但 return 状态不同,仅此而已

from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_main():
    return {"message": "Hello world for my project myapp"}

这是我的 Dockerfile,用于 myapp 和 myapp1,两者也完全相同,但唯一的区别是我在 7777 上部署 myapp,在 [=22= 上部署 myapp1 ] 在不同的容器中

FROM ubuntu:latest

ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y
RUN apt install -y -q build-essential python3-pip python3-dev

# python dependencies
RUN pip3 install -U pip setuptools wheel
RUN pip3 install gunicorn fastapi uvloop httptools "uvicorn[standard]"

# copy required files
RUN bash -c 'mkdir -p /app'
COPY ./app /app


ENTRYPOINT /usr/local/bin/gunicorn \
    -b 0.0.0.0:7777 \ # this line I use for myapp dockerfile
    -b 0.0.0.0:7778 \ # this line I change for myapp1 dockerfile
    -w 1 \
    -k uvicorn.workers.UvicornWorker app.main:app \
    --chdir /app

这是我的 docker-compose.yml 文件,用于 myapp 和 myapp1,这里我也完全一样,但唯一不同的是我更改了端口,

services:
  myapp:  # I use this line for myapp docker-compose file
  myapp1: # I use this line for myapp1 docker-compose file
    build: .
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik_public"

      - "traefik.backend=myapp" # I use this line for myapp docker-compose file
      - "traefik.backend=myapp1" # I use this line for myapp1 docker-compose file


      - "traefik.frontend.rule=Host:new2.example.com" # I use this for myapp compose file
      - "traefik.frontend.rule=Host:new3.example.com" # I use this for myapp1 compose file

      - "traefik.port=7777" # I use this line for myapp docker-compose file
      - "traefik.port=7778" # I use this line for myapp1 docker-compose file
    networks:
      - traefik_public

networks:
  traefik_public:
    external: true

现在进入 traefik 文件夹,

  1. acme.json # 我使用 nano acme.json 命令创建了它,其中没有任何内容, 但是 chmod 600 acme.json 获得了适当的权限。

  2. traefik_dynamic.toml

[http]
  [http.routers]
    [http.routers.route0]
      entryPoints = ["web"]
      middlewares = ["my-basic-auth"]
      service = "api@internal"
      rule = "Host(`new1.example.com`)"
      [http.routers.route0.tls]
        certResolver = "myresolver"

[http.middlewares.test-auth.basicAuth]
  users = [
    ["admin:your_encrypted_password"]
  ]
  1. traefik.toml
[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http]
      [entryPoints.web.http.redirections]
        [entryPoints.web.http.redirections.entryPoint]
          to = "websecure"
          scheme = "https"

  [entryPoints.websecure]
    address = ":443"

[api]
  dashboard = true

[certificatesResolvers.myresolver.acme]
  email = "reallygoodtraefik@gmail.com"
  storage= "acme.json"
  [certificatesResolvers.myresolver.acme.httpChallenge]
    entryPoint = "web"

[providers]
  [providers.docker]
    watch = true
    network = "web"
  [providers.file]
    filename = "traefik_dynamic.toml"
  1. docker-compose.yml
services:
  traefik:
    image: traefik:latest
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.toml:/traefik.toml
      - ./acme.json:/acme.json
      - ./traefik_dynamic.toml:/traefik_dynamic.toml
    networks:
      - web

networks:
  web:

这些是关于我的文件的详细信息,我在这里想要实现的是,

我想使用基本身份验证设置 traefik 和 traefik 仪表板,我部署了两个快速api 服务,

所有这些都应该是 https,并且还启用了认证自动更新。

我从最新版本的 traefik 的在线文章中获得了所有这些。但问题是这是行不通的。我使用 docker-compose 来构建和部署 traefik,然后打开 api 仪表板。它要求输入密码和用户 (basic auth I setup) 我输入了我在 traefik_dynamic.toml 中设置的用户详细信息,但它不起作用。

我哪里做错了?请帮助我纠正配置中的错误。我真的很想了解更多。

错误更新:

traefik_1  | time="2021-06-16T01:51:16Z" level=error msg="Unable to obtain ACME certificate for domains \"new1.example.com\": unable to generate a certificate for the domains [new1.example.com]: error: one or more domains had a problem:\n[new1.example.com] acme: error: 403 :: urn:ietf:params:acme:error:unauthorized :: Invalid response from http://new1.example.com/.well-known/acme-challenge/mu85LkYEjlvnbDI-wM2xMaRFO1QsPDNjepTDb47dWF0 [2606:4700:3032::6815:55c4]: 404\n" rule="Host(`new1.example.com`)" routerName=api@docker providerName=myresolver.acme

traefik_1  | time="2021-06-16T01:51:19Z" level=error msg="Unable to obtain ACME certificate for domains \"new2.example.com\": unable to generate a certificate for the domains [new2.example.com]: error: one or more domains had a problem:\n[new2.example.com] acme: error: 403 :: urn:ietf:params:acme:error:unauthorized :: Invalid response from http://new2.example.com/.well-known/acme-challenge/ykiCAEpJeQ1qgVdeFtSRo3q-ATTwgKdRdGHUs2kgIsY [2606:4700:3031::ac43:d1e9]: 404\n" providerName=myresolver.acme routerName=myapp1@docker rule="Host(`new2.example.com`)"

traefik_1  | time="2021-06-16T01:51:20Z" level=error msg="Unable to obtain ACME certificate for domains \"new3.example.com\": unable to generate a certificate for the domains [new3.example.com]: error: one or more domains had a problem:\n[new3.example.com] acme: error: 403 :: urn:ietf:params:acme:error:unauthorized :: Invalid response from http://new3.example.com/.well-known/acme-challenge/BUZWuWdNd2XAXwXCwkeqe5-PHb8cGV8V6UtzeLaKryE [2606:4700:3031::ac43:d1e9]: 404\n" providerName=myresolver.acme routerName=myapp@docker rule="Host(`new3.example.com`)"

所有服务只需要一个docker-compose文件,无需为每个容器定义一个

您应该使用的项目结构应该类似于:

├── docker-compose.yml
├── myapp
│   ├── .dockerignore
│   ├── Dockerfile
│   └── app
│       └── main.py
├── myapp1
│   ├── .dockerignore
│   ├── Dockerfile
│   └── app
│       └── main.py
└── traefik
    ├── acme.json
    └── traefik.yml

创建容器时,除非是用于开发目的,否则建议不要使用成熟的镜像,如ubuntu。特别是为了您的目的,我会推荐 python 图片,例如 python:3.7-slim.

不确定您是将其用于开发还是生产目的,但您也可以使用卷将应用程序目录挂载到容器内(如果您将其用于开发则特别有用),并且只使用一个 Dockerfile myappmyapp1,通过环境变量自定义它。

由于您已经在使用 traefik 的动态配置,我将通过 docker-compose.yml 文件中的 docker 标签完成容器配置的大部分设置。

您的 myappmyapp1 的 docker 文件在这一点上将非常相似,但我将它们作为单独的文件保存,因为您可能需要更改它们取决于您将来应用程序的要求。我为端口使用了一个环境变量,它允许您从 docker-compose.yml 文件更改端口。

您可以使用以下 Dockerfile./myapp/Dockerfile./myapp1/Dockerfile):

FROM python:3.7-slim

ARG DEBIAN_FRONTEND=noninteractive

ENV PYTHONUNBUFFERED=1

RUN pip3 install -U pip setuptools wheel && \
    pip3 install gunicorn fastapi uvloop httptools "uvicorn[standard]"

COPY . /app

ENV PORT=7777 # and 7778 for myapp1

ENTRYPOINT /usr/local/bin/gunicorn -b 0.0.0.0:$PORT -w 1 -k uvicorn.workers.UvicornWorker app.main:app --chdir /app

注意:您真的应该为您的应用程序依赖项使用 poetry or a requirements.txt 文件之类的东西。

.dockerignore 文件(./myapp/.dockerignore./myapp1/.dockerignore)应包含:

Dockerfile

因为整个目录都被复制到容器中,你不需要 Dockerfile 在那里。

您的主要 traefik 配置 (./traefik/traefik.yml) 可以是这样的:

providers:
  docker:
    exposedByDefault: false

global:
  checkNewVersion: false
  sendAnonymousUsage: false

api: {}
accessLog: {}

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: "websecure"
          scheme: "https"
  websecure:
    address: ":443"

ping:
  entryPoint: "websecure"

certificatesResolvers:
  myresolver:
    acme:
      caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
      email: "example@example.com"
      storage: "/etc/traefik/acme.json"
      httpChallenge:
        entryPoint: "web"

注意:上面的acme配置将使用stage letsencrypt server。确保所有细节都正确,并在测试一切正常后删除 caServer,以便与 letsencrypt 生产服务器通信。

您的 ./docker-compose.yml 文件应该类似于:

version: "3.9"

services:
  myapp:
    build:
      context: ./myapp
      dockerfile: ./Dockerfile
    image: myapp
    depends_on:
      - traefik
    expose:
      - 7777
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp.tls=true"
      - "traefik.http.routers.myapp.tls.certResolver=myresolver"
      - "traefik.http.routers.myapp.entrypoints=websecure"
      - "traefik.http.routers.myapp.rule=Host(`new2.example.com`)"
      - "traefik.http.services.myapp.loadbalancer.server.port=7777"
  myapp1:
    build:
      context: ./myapp1
      dockerfile: ./Dockerfile
    image: myapp1
    depends_on:
      - traefik
    expose:
      - 7778
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.myapp1.tls=true"
      - "traefik.http.routers.myapp1.tls.certResolver=myresolver"
      - "traefik.http.routers.myapp1.entrypoints=websecure"
      - "traefik.http.routers.myapp1.rule=Host(`new3.example.com`)"
      - "traefik.http.services.myapp1.loadbalancer.server.port=7778"
  traefik:
    image: traefik:v2.4
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik/traefik.yml:/etc/traefik/traefik.yml
      - ./traefik/acme.json:/etc/traefik/acme.json
    ports:
      - 80:80
      - 443:443
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.tls=true"
      - "traefik.http.routers.api.tls.certResolver=myresolver"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.rule=Host(`new1.example.com`)"
      - "traefik.http.routers.api.service=api@internal"
      - "traefik.http.routers.api.middlewares=myAuth"
      - "traefik.http.middlewares.myAuth.basicAuth.users=admin:$$apr1$zjvsq3w$$fLCqJddLvrIZA.CCoGE2E." # generate with htpasswd. replace $ with $$

您可以使用以下命令生成密码:

htpasswd -n admin | sed 's/$/$$/g'

注意:如果您需要在 docker-compose 文件中使用文字美元符号,您需要使用 $$ 作为记录 here.

在目录中发出 docker-compose up 应该会启动所有服务,并按预期工作。

根据您提供的详细信息,以上内容应该适合您,但可以根据您的需要在多个方面进一步改进。

此外,在 docker-compose.yml 文件中拥有 traefik 仪表板的凭据可能不是最好的,您可能希望为此使用 docker 秘密。您还可以添加健康检查并考虑将 myappmyapp1 放入单独的内部网络。

如果你想深入了解,我建议你从Get started with Docker Compose and also read: Dockerfile reference and Compose file version 3 reference

开始