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错误信息

排查方向与解决方案
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
相关产品推荐
相关产品推荐

