You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker环境下ActionCable与Traefik的WSS连接失败问题

Rails ActionCable WSS连接失败排查请求

我基于Docker搭建环境,使用Traefik作为负载均衡器和WAF。本地环境中Rails ActionCable可正常建立连接,但预发布环境无法创建WSS连接。以下是相关配置及错误信息,请求协助排查:

Traefik全局捕获规则Docker Compose配置

version: "3.3"

services:
  traefik:
    container_name: traefik
    image: "traefik:latest"
    restart: always
    command:
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entryPoints.web.forwardedHeaders.insecure
      - --providers.docker
      #- --providers.docker.exposedByDefault=false
      - --log.level=INFO
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.email=******* #Set your email address here, is for the generation of SSL certificates with Let's Encrypt. 
      - --certificatesresolvers.le.acme.storage=./acme.json
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
      - --api.dashboard=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./acme.json:/acme.json"
    labels:      
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      
      - "traefik.http.routers.dashboard.rule=Host(`traefik.host.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=user:password"

  portainer:
    container_name: portainer
    image: portainer/portainer-ee:latest
    command: -H unix:///var/run/docker.sock
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
    labels:
      # Frontend
      - "traefik.enable=true"
      - "traefik.http.routers.frontend.rule=Host(`portainer.host.com`)"
      - "traefik.http.routers.frontend.entrypoints=websecure"
      - "traefik.http.services.frontend.loadbalancer.server.port=9000"
      - "traefik.http.routers.frontend.service=frontend"
      - "traefik.http.routers.frontend.tls.certresolver=le"

      # Edge
      - "traefik.http.routers.edge.rule=Host(`edge.host.com`)"
      - "traefik.http.routers.edge.entrypoints=websecure"
      - "traefik.http.services.edge.loadbalancer.server.port=8000"
      - "traefik.http.routers.edge.service=edge"
      - "traefik.http.routers.edge.tls.certresolver=le"

volumes:
  portainer_data:

带Traefik标签的应用Docker Compose配置

所有功能均正常,但WSS连接无法建立。

services:
  postgres:
    image: postgres
    volumes:
      - postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: $DATABASE_USER
      POSTGRES_PASSWORD: $DATABASE_PASSWORD
    ports:
      - "5432:5432"
    restart: always

  redis-staging:
    container_name: redis
    image: 'redis:5.0-alpine'
    command: redis-server --port 6380
    volumes:
      - redis:/data
    ports:
      - "6380:6380"

  app-staging:
    container_name: app-staging
    image: ********
    stdin_open: true
    tty: true
    volumes:
      - myapp:/myapp
    ports:
      - "3001:3001"
    depends_on:
      - postgres
      - redis-staging
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app-staging.rule=Host(`staging.api.host.com`)"
      - "traefik.http.routers.app-staging.entrypoints=websecure"
      - "traefik.http.services.app-staging.loadbalancer.server.port=3001"
      - "traefik.http.routers.app-staging.service=app-staging"
      - "traefik.http.routers.app-staging.tls.certresolver=le"
      - "traefik.http.middlewares.app-staging.headers.accesscontrolallowmethods=GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD"
      - "traefik.http.middlewares.app-staging.headers.accesscontrolalloworiginlist=***hosts***"
      - "traefik.http.middlewares.app-staging.headers.accesscontrolmaxage=100"
      - "traefik.http.middlewares.app-staging.headers.addvaryheader=true"
    environment:
      # General
      - RAILS_MASTER_KEY=$RAILS_MASTER_KEY
      - DEVISE_JWT_SECRET_KEY=$DEVISE_JWT_SECRET_KEY
      - DEEPLINK_HOST=$DEEPLINK_HOST
      - RAILS_LOG_LEVEL=$RAILS_LOG_LEVEL
      - RAILS_APP_HOST=$RAILS_APP_HOST

      # Database
      - DATABASE_PASSWORD=$DATABASE_PASSWORD
      - DATABASE_USER=$DATABASE_USER
      - REDIS_URL=$REDIS_URL

      # ActionCable
      - ACTION_CABLE_URL=$ACTION_CABLE_URL
      - ACTION_CABLE_ALLOWED_REQUEST_ORIGINS=$ACTION_CABLE_ALLOWED_REQUEST_ORIGINS

      # OneSignal
      - ONESIGNAL_API_KEY=$ONESIGNAL_API_KEY
      - ONESIGNAL_APP_ID=$ONESIGNAL_APP_ID
      - ONESIGNAL_USER_KEY=$ONESIGNAL_USER_KEY

      # Mailer
      - MAIL_DOMAIN_ADDRESS=$MAIL_DOMAIN_ADDRESS
      - MAIL_SMTP_PORT=$MAIL_SMTP_PORT
      - MAIL_EMAIL_ADDRESS=$MAIL_EMAIL_ADDRESS
      - MAIL_EMAIL_PASSWORD=$MAIL_EMAIL_PASSWORD

      # Hosts
      - CORS_ORIGINS=$CORS_ORIGINS

volumes:
  redis:
  postgres:
  myapp:

networks:
    default:
      name: base_default
      external: true

Rails预发布环境配置

require "active_support/core_ext/integer/time"

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Code is not reloaded between requests.
  config.cache_classes = false

  # Show full error reports.
  config.consider_all_requests_local = true

  # Eager load code on boot. This eager loads most of Rails and
  # your application in memory, allowing both threaded web servers
  # and those relying on copy on write to perform better.
  # Rake tasks automatically ignore this option for performance.
  config.eager_load = true

  # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
  # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
  # config.require_master_key = true

  # Disable serving static files from the `/public` folder by default since
  # Apache or NGINX already handles this.
  config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  # config.asset_host = "http://assets.example.com"

  # Specifies the header that your server uses for sending files.
  # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
  # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX

  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :aws_development

  # Mount Action Cable outside main process or domain.
  config.action_cable.mount_path = 'cable'
  config.action_cable.url = ENV['ACTION_CABLE_URL']
  config.action_cable.allowed_request_origins = ENV['ACTION_CABLE_ALLOWED_REQUEST_ORIGINS'].split(',')

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

  # Include generic and useful information about system operation, but avoid logging too much
  # information to avoid inadvertent exposure of personally identifiable information (PII).
  config.log_level = (ENV['RAILS_LOG_LEVEL'] || 'info').to_sym

  # Prepend all log lines with the following tags.
  config.log_tags = [:request_id]

  # Use a different cache store in production.
  # config.cache_store = :mem_cache_store

  # Use a real queuing backend for Active Job (and separate queues per environment).
  # config.active_job.queue_adapter     = :resque
  # config.active_job.queue_name_prefix = "app_backend_staging"

  config.action_mailer.perform_caching = false

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation cannot be found).
  config.i18n.fallbacks = true

  # Don't log any deprecations.
  config.active_support.report_deprecations = false

  # Use default logging formatter so that PID and timestamp are not suppressed.
  config.log_formatter = ::Logger::Formatter.new

  # Use a different logger for distributed setups.
  # require "syslog/logger"
  # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")

  logger = ActiveSupport::Logger.new(STDOUT)
  logger.formatter = config.log_formatter
  config.logger = ActiveSupport::TaggedLogging.new(logger)

  # Do not dump schema after migrations.
  config.active_record.dump_schema_after_migration = false

  config.action_mailer.default_url_options = { host: ENV["RAILS_APP_HOST"] }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address: ENV['MAIL_DOMAIN_ADDRESS'],
    port: ENV['MAIL_SMTP_PORT'],
    user_name: ENV['MAIL_EMAIL_ADDRESS'],
    password: ENV['MAIL_EMAIL_PASSWORD'],
    authentication: 'plain',
    ssl: true,
    enable_starttls_auto: true
  }

  # Add staging host to allowed hosts
  config.hosts << ENV["RAILS_APP_HOST"]
end

ActionCable错误信息

ActionCable错误截图


排查方向与解决方案

1. 修复Traefik中间件的WebSocket支持配置

当前app-staging的Traefik中间件缺少WebSocket所需的头信息配置,且未将中间件关联到路由。修改app-staging的labels:

labels:
  # 原有配置保留,新增以下内容
  - "traefik.http.routers.app-staging.middlewares=app-staging"
  - "traefik.http.middlewares.app-staging.headers.accesscontrolallowheaders=Upgrade,Connection,Content-Type"
  - "traefik.http.middlewares.app-staging.headers.accesscontrolallowcredentials=true"
  - "traefik.http.middlewares.app-staging.headers.customrequestheaders.Upgrade=Upgrade"
  - "traefik.http.middlewares.app-staging.headers.customrequestheaders.Connection=Connection"

2. 验证Rails ActionCable环境变量配置

  • 确保ACTION_CABLE_URL设置为wss://staging.api.host.com/cable
  • 检查ACTION_CABLE_ALLOWED_REQUEST_ORIGINS的值,确认包含前端域名(格式为https://your-frontend-domain.com,多域名用逗号分隔,无多余空格)

3. 检查Traefik日志与网络连通性

  • 将Traefik的log.level改为DEBUG,重启后查看日志中WebSocket请求的处理细节
  • 确认Traefik与app-staging容器在同一个base_default网络中,执行docker network inspect base_default验证
  • 在app-staging容器内执行curl -I http://localhost:3001/cable,确认ActionCable端点可正常访问

4. 确认SSL证书有效性

访问https://staging.api.host.com,检查浏览器是否提示证书错误,确保Traefik的Let's Encrypt证书已正确生成

内容的提问来源于stack exchange,提问作者luca_bruegger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 11:49:55