首页 > 解决方案 > Docker Traefik 无法路由 websocket

问题描述

我有几个作为 docker 容器运行的微服务。所有 Web 服务都可以正常工作并正确路由。

唯一的问题是 websocket 服务。websocket 服务本身使用 python websockets 并拥有自己的 TLS 证书。

尝试访问 websocketwss://websocket.localhost失败,在下面的设置中它根本找不到页面。在我之前的配置中,它会导致Bad Gateway错误。

显然traefik开箱即用,无需额外配置即可使用 websockets。

情况似乎并非如此。任何指针?

websocket 连接在不涉及 docker 或 traefik 的情况下工作,所以我排除了这个问题。对此的任何帮助将不胜感激。

docker-compose.yml

version: "3.7"

networks:
  web:
    external: true
  internal:
    external: false

volumes:
  mysql_data:

services:
  traefik:
    image: traefik:v2.2.1
    container_name: traefik
    restart: always
    ports:
      - "80:80"
      - "443:443"
    expose:
      - 8080
    environment:
      - /var/run/docker.sock:/var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./config/:/config
      - ./traefik.yml:/traefik.yml
    networks:
      - web
      - internal
    labels:
      - traefik.http.routers.traefik.tls=true
      - traefik.http.routers.traefik.entrypoints=secure
      - traefik.http.routers.traefik.rule=Host(`traefik.localhost`)
      - traefik.http.routers.traefik.service=api@internal

  dozzle:
    image: amir20/dozzle:latest
    container_name: dozzle
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    expose:
      - 8080
    labels:
      - traefik.http.routers.dozzle.tls=true
      - traefik.http.routers.dozzle.entrypoints=secure
      - traefik.http.routers.dozzle.rule=Host(`dozzle.localhost`) || Host(`logs.localhost`)
    networks:
      - internal

  db:
    image: mysql:latest
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: ########
    restart: always
    healthcheck:
      test: "exit 0"
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - '3306:3306'
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - internal

  websocket:
    image: local-websocket-image
    container_name: websocket-stage
    restart: on-failure
    command: python server.py
    depends_on:
      db:
        condition: service_healthy
    expose:
      - 8080
    networks:
      - web
      - internal
    environment:
      - PATH_TO_CONFIG=/src/setup.cfg
    volumes:
      - ${PWD}/docker-config:/src
      - ${PWD}/config/certs/socket:/var
    labels:
      - traefik.http.routers.core-socket-stage-router.tls=true
      - traefik.http.routers.core-socket-stage-router.entrypoints=secure
      - traefik.http.routers.core-socket-stage-router.rule=Host(`websocket.localhost`)

traefik.yml

entryPoints:
  insecure:
    address: :80
    http:
      redirections:
        entryPoint:
          to: secure
          scheme: https
  secure:
    address: :443

log:
  level: INFO
 
accessLog:
  filePath: "traefik-access.log"
  bufferingSize: 100

api:
  dashboard: true
  insecure: true

ping: {}

providers:
  file:
    filename: /config/dynamic.yml # traefik dynamic configuration
    watch: true                   # everytime it changes, it will be reloaded 
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: true

配置

tls:
  stores:
    default:
      defaultCertificate:
        certFile: cert.crt
        keyFile: key.key

  certificates:
    - certFile: crt.crt
      keyFile: key.key
      stores:
        - default
 
  domains:
    - main: "localhost"

标签: pythondockerwebsocketdocker-composetraefik

解决方案


在查看您的配置时,以下内容不适合:

  • docker-compose 项目名将成为域名的一部分。默认使用 docker-compose.yaml 的父文件夹名称。您没有在这里指定它,因此我假设它是 by traefik。您可以在 docker-compose 调用中使用docker-compose -p traefik up或通过设置环境变量 PROJECT_NAME 来显式设置它。

  • 您正在使用域名“.localhost”,但您没有明确定义域名。这意味着使用默认名称,该名称源自服务名称、项目名称(存储 docker-compose 文件的文件夹)以及您使用此模式附加到的 docker-network 名称:servicename.projectname_networkname. 使用属性hostnamedomainname明确定义名称(仅适用于具有 的网络internal=false)。当有两个网络连接和一个域名定义时,您将获得以下域名:

    1. db.traefik_internal(只有实习生,db.localhost 不起作用)
    2. dozzle.traefik_internal(只有实习生,dozzle.localhost 不起作用)
    3. traefik.localhost
    4. traefik.traefik_web
    5. traefik.traefik_internal
    6. websocket.localhost
    7. websocket.traefik_web
    8. websocket.traefik_internal
  • external=true只是意味着网络是docker network create由另一个 docker-compose 项目在外部创建的。主要效果是,它在做的时候没有被选中docker-compose down。它与与外界的联系无关。要获得隔离的内部网络,您必须使用该选项internal: true

  • condition: service_healthy不再支持该选项version: "3.7",因此请删除该选项(但它不会像您期望的那样工作)或将版本更改为2.4

这是我当前版本的 docker-compose.yaml:

version: "2.4"

networks:
  web:
  internal:
    internal: true

volumes:
  mysql_data:

services:
  traefik:
    image: traefik:v2.2.1
    container_name: traefik
    hostname: traefik
    domainname: localhost
    restart: always
    ports:
      - "80:80"
      - "443:443"
    expose:
      - 8080
    environment:
      - /var/run/docker.sock:/var/run/docker.sock
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./config/:/config
      - ./traefik.yml:/traefik.yml
    networks:
      - web
      - internal
    labels:
      - traefik.http.routers.traefik.tls=true
      - traefik.http.routers.traefik.entrypoints=secure
      - traefik.http.routers.traefik.rule=Host(`traefik.localhost`)
      - traefik.http.routers.traefik.service=api@internal

  dozzle:
    image: amir20/dozzle:latest
    container_name: dozzle
    hostname: dozzle
    domainname: localhost
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    expose:
      - 8080
    labels:
      - traefik.http.routers.dozzle.tls=true
      - traefik.http.routers.dozzle.entrypoints=secure
      - traefik.http.routers.dozzle.rule=Host(`dozzle.traefik_internal`) || Host(`logs.localhost`)
    networks:
      - internal

  db:
    image: mysql:latest
    container_name: db
    hostname: db
    domainname: localhost
    environment:
      MYSQL_ROOT_PASSWORD: ########
    restart: always
    healthcheck:
      test: "exit 0"
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - '3306:3306'
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - internal

  websocket:
    image: local-websocket-image
    container_name: websocket-stage
    hostname: websocket
    domainname: localhost
    restart: on-failure
    command: python server.py
    depends_on:
      db:
        condition: service_healthy
    expose:
      - 8080
    networks:
      - web
      - internal
    environment:
      - PATH_TO_CONFIG=/src/setup.cfg
    volumes:
      - ${PWD}/docker-config:/src
      - ${PWD}/config/certs/socket:/var
    labels:
      - traefik.http.routers.core-socket-stage-router.tls=true
      - traefik.http.routers.core-socket-stage-router.entrypoints=secure
      - traefik.http.routers.core-socket-stage-router.rule=Host(`websocket.localhost`)

推荐阅读