注:文章记录于2025年。
近期有组织CTF比赛的需求,在Windows服务器上未能成功安装Docker,最后成功部署了CTFd平台。但测试访问过程中出现了线程卡死的问题,最终问题解决、比赛顺利举办。

Docker方案(未成功)

Windows Server 2019上可以安装Docker,安装步骤参考微软文档的开始:为容器准备Windows,使用了install-docker-ce.ps1PowerShell 脚本。

但是实际测试,安装完Docker并不能抓取、运行镜像,出现如下报错:

针对这个报错并没有在网上找到合适的解决办法。

此外docker pull过程中还遇到如下报错,通过设置代理后成功解决。

直接部署

直接在服务器部署CTFd主要参照CTFd的安装文档Standard WSGI Deployment。部署需要3个组件,WEB服务、数据库和Redis。

安装Redis

Redis安装包通过redis-windows项目下载,项目中有两个版本的软件,我实际选择的是msys2带服务的版本。

解压之后通过redis-server.exe redis.conf命令起服务,使用redis-cli.exe连接。

通过修改redis.conf中requirepass配置,可以要求使用密码来连接redis。

redis默认情况下开启了保护模式,且只监听本地端口,所以防火墙不需要额外配置。

通过install_redis_service.bat来安装服务。

安装数据库

CTFd并不推荐使用Postgres数据库,本次我选择了MariaDB。数据库默认监听所有端口,可以通过修改my.ini配置文件使其只监听本地地址。

MariaDB数据库安装之后,需要创建名为ctfd的数据库(不用手工建数据库表,CTFd运行时会自动创建表)。

安装CTFd

安装CTFd需要有Python环境,本次下载的是Python 3.12.10。将Pypi源设置为清华大学TUNA源可以明显提升pip包下载速度。

从源码部署CTFd,主要参照文档的Install部分。

安装依赖,同时修改配置文件中的数据库、Redis配置(使用requirepass配置时默认用户名为default),随后就可通过flask run启动CTFd服务。

此时即可在浏览器中访问到CTFd的初始化界面。

优化设置

CTFd去除广告

默认情况下CTFd的首页是推广广告,在管理后台→Pages,修改页面源代码即可,参考这篇CSDN文章的第五部分。

Nginx托管静态资源

按照WEB服务器部署实践,建议将静态资源和后端服务托管给Nginx来处理,对外只暴露Nginx的监听端口。

安装WSGI

按照部署实践和DeepSeek建议,安装WSGI服务器,按官方安装文档说明应该使用gunicorn作为WSGI服务器,但gunicorn并不支持,我选择了waitress。

将Waitress作为WSGI服务,使用Nginx代理流量:

我测试过程中发现waitress似乎有问题,很容易就出现Task queue,而且一旦出现这个问题卡住的队列并不会清空,所以整个CTFd平台都无法使用。

使用wsgi:app来运行CTFd平台时也会出现task queue的提示,与用CTFd:create_app不同的是1)提示出有问题的URI是/events,2)占用的任务队列会自动清空。

将线程数量设置为16之后,快速点击测试仍会出现任务队列提示。

/events是CTFd的实时推送接口,用于比赛倒计时、实时通知、心跳检测等。

考虑到实际比赛时选手数量和系统稳定性提升的考虑,采取两方面措施来保障可用性:

1)将/events单独剥离出来,在Nginx中单独设置Waitress服务用来专门处理/events长连接。

2)其他业务使用两个Waitress的服务,使用Nginx来做负载均衡同时设置较短的后端连接的超时时长、避免出现任务队列的情况。

经过上述配置之后整个平台在比赛中十分稳定。

补充

压力测试

在我对比使用flask run和Waitress WSGI作为后端服务时,不进行任何其他配置、Nginx不单独处理/events时,flask run的性能表现明显优于Waitress。咨询DeepSeek,答复是flask run是个单线程或简单多线程、处理请求同步阻塞;而waitress是预分配的工作线程池,异步非阻塞。

使用locust进行压力测试,Waitress WSGI按照如下配置设置线程数为16。

8核16G的服务器上单一Waitress实例可以支持100个用户并发,使用期间后端服务器加载数据慢、但系统基本可用。(注:以下所有结果压力测试配置均为模拟100个用户,每秒新增10个,压力测试2分钟)

8核16G的服务器单一flask run实例效果从请求吞吐和响应延时比Waitress要好一些。

使用两个Waitress实例后RPS和95%ile都有提升;

在locust添加了注销操作后,RPS和95%ile都有提升。

Waitress实例数量为2,但是每个实例的降低线程数量为16后,95%ile稍有提升。

压力测试结论是两个Waitress的32线程实例,100个用户在快速同时请求的情况下也能基本可用。实际比赛时用户数量、并发请求远远达不到压力测试的程度。

疑问:实际测试中flask run的性能表现并没有AI说的那么差,甚至比用Waitress要好。

nginx代理两个flask run服务,flask run启用线程。

真实源IP

修改CTFd的配置文件,将REVERSE_PROXY设置为true可以从请求头获取真实源IP:

配置后并不能使后端获取到真实的服务器地址。