总的来说这次平台运维和搭建还是比较成功的,比赛期间没有出现大规模的崩溃和数据丢失。 出现最多的就是由于代理网络问题引起的网络波动。
平台选择
目前我们能够选择的 CTF 平台不多,我们选择的顾虑点如下:
- 希望是自主搭建,自主可控
- 能够对平台进行适配性修改
- 尽量是 UI 美观好看,现代化
- 开源
最终讨论结果就是 A1CTF 平台,十分成熟可用的基座平台。
GitHub - carbofish/A1CTF: A CTF platform designed for A1natas.
整个使用下来十分赞,这里感谢 A1CTF 平台。
架构解析:云原生混合容器架构
本项目采用 混合容器架构:
- Docker Compose 管理核心服务:用于部署相对“静态”的基础设施(Web 后端、Postgres 数据库、Redis 缓存)。这种方式配置简单,适合管理生命周期较长的长连接服务。
- K3s 管理动态任务:CTF 平台的特殊性在于需要为每个选手/队伍动态生成、销毁题目环境(Pod)。利用 Kubernetes 的调度能力,可以轻松实现题目的资源隔离、自动清理和端口管理。
工作流程
当一名选手点击“启动题目”时,数据流是这样的:
- [选手操作]:点击前端网页按钮。
- [Web 平台 (Docker)]:
- 收到 HTTP 请求。
- 读取挂载进来的
k8sconfig.yaml文件(这是通往 K3s 的“钥匙”)。 - 跨系统调用:Web 平台作为 Kubernetes Client,向本机运行的 K3s API Server 发送指令:“请给我启动一个名为
challenge-101-team-A的 Pod”。
- [K3s 集群]:
- API Server 收到指令,调度器开始工作。
- 拉取镜像,启动 Pod。
- 分配一个随机的高端口(比如
31001)映射给这个 Pod。
- [反馈回路]:
- K3s 告诉 Web 平台:“任务完成,端口是
31001,IP 是1.2.3.4”。 - Web 平台把这个
IP:Port显示给选手。
- K3s 告诉 Web 平台:“任务完成,端口是
当选手和题目交互时候,流量流如下:
选手的电脑 <---> 服务器公网IP:31001 <---> K3s Service <---> 题目 Pod运维实录
服务器选择
由于预算有限,根据之前比赛经验:
- 参与人数 200 多人
- 动态在线靶机巅峰数量在 100 以内等
本次 HnuCTF 比赛选取的是一台雨云的 8 核 16G 的服务器,all in one,没有采用多主机形式。
具体配置见图,是一台国内宁波的机子:

基础系统配置
system: gin-port: 8082 # 后端服务监听端口 gin-host: 0.0.0.0 # 监听地址 baseURL: http://ctf.hnusec.com # 平台对外的基础 URL trusted-proxies: # 信任的代理 IP,用于正确获取客户端真实 IP - 127.0.0.1/32 - 114.66.61.197/32 # 宿主机 IP,Nginx 转发时需要Kubernetes 集成
后端通过 k8sconfig.yaml 连接 K3s 集群,并使用 NodePort 模式暴露题目服务。
k8s: k8s-config-file: "k8sconfig.yaml" node-ip-map: # 节点名称到 IP 的映射,用于生成题目访问链接 - { name: "instance-a2lxuib0", "address": "114.66.61.197" }注意:
name必须与kubectl get nodes输出的节点名称一致。
缓存策略
采用多级缓存策略:应用内内存缓存 (Ristretto) + Redis 分布式缓存,大幅降低数据库压力。
cache-time: # 内存缓存时间 game-scoreboard: 500ms challenges-for-game: 1sredis-cache-time: # Redis 缓存时间 user-list: 500ms痛点解决:代理网络问题
由于服务器在国内,拉取 Docker Hub 的镜像非常慢甚至失败,这是部署初期最大的痛点。
本次采用的是 clash-for-linux-install 这个项目,给服务器配置了代理环境,加速镜像拉取。
添加节点,十分简单,不需要我们做代理转换:
clashsub add
可以直接从 ui 平台查看和管理节点状态:
clashui
进入 web-ui 界面:

我们为了速度开启的全局 tun 模式:
$ clashtun😾 Tun 状态:关闭
$ clashtun on😼 Tun 模式已开启比赛与平台观察
运行状况
比赛期间服务器情况,服务器总体情况如下:

平台监控如下:

总的来说其实性能是过剩了,CPU 使用率和内存整理都是富余的,只是储存 30GB 还是有点吃力。 对于动态容器顶峰时刻,有几十多个动态靶机同时启动,平台的整体速度没问题,但是需要实时的去协调存储不够带来的问题。 由于每个类型题目的形式不一样所以说不好在 k8s 层面做对应大小限制,比如说 web 方向 nextjs 漏洞那题将近 1G 的容器大小。
反作弊情况
通过平台的异常事件,我们是抓到了两组提交对方 flag 的情况,也是给予了红屏锁定,平台本身是没问题的。

但是也发现一些如 Re,Misc 代打情况,由于没有对于每个队伍的附件做一个隐藏水印等处理,所以没办法精准的追溯到人。 后续计划是对平台反作弊的这个功能持续升级一下,提升追溯能力。
比赛结果
经过 24H 的激烈竞争,各个方向的选手竞争非常激烈。

在凌晨十分还有题目被解出,在内部 QQ 群部署了专门适配 A1CTF 的赛事通报 Bot(orixian.制作):

附:搭建完整流程教程
这里记录一下,方便我或者大家后续直接使用。
1. 前置条件
- 操作系统: Ubuntu 20.04 或 22.04 LTS
- 硬件配置:
- CPU: 4Core+
- RAM: 8GB+ (取决于题目数量)
- Disk: 40GB+
- 软件依赖:
- Git
- Docker & Docker Compose
- K3s (用于题目容器调度)
2. 安装步骤
2.1 安装 Docker
# 更新 apt 索引sudo apt-get update
# 安装必要的证书和工具sudo apt-get install -y ca-certificates curl gnupg
# 添加 Docker 官方 GPG keysudo install -m 0755 -d /etc/apt/keyringscurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpgsudo chmod a+r /etc/apt/keyrings/docker.gpg
# 设置仓库echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker Enginesudo apt-get updatesudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 启动 Docker 并设置开机自启sudo systemctl enable --now docker2.2 安装 K3s
HnuCTF 使用 Kubernetes (K3s) 来动态生成题目环境。
# 安装 K3s (国内建议使用镜像源或手动下载,这里使用官方脚本)curl -sfL https://get.k3s.io | sh -
# 确认 K3s 正常运行sudo k3s kubectl get nodes2.3 获取代码
git clone https://github.com/Fruit-Guardians/HnuCTF.gitcd HnuCTF3. 配置
3.1 配置文件 (config.yaml)
复制示例配置文件并根据环境修改:
cp config.example.yaml config.yaml关键配置项修改建议 (vi config.yaml):
- System:
gin-host: 保持0.0.0.0baseURL: 修改为您服务器的公网 IP 或域名 (例如http://1.2.3.4:8082或https://ctf.example.com)
- Postgres:
port: 注意docker-compose.yml中 Postgres 映射的端口。默认配置中 Postgres 映射为5433:5432。由于 App 使用 host 网络模式,请将此处改为 5433,并在host处填写127.0.0.1或localhost。user/password: 如果修改了docker-compose.yml中的数据库密码,请同步修改此处。默认用户/密码为postgres/postgres。dbname: 默认为a1ctf。
- Redis:
address:localhost:6379(Docker Host 模式下可用)password: 默认为空,如有修改请同步。
- K8s:
k8s-config-file: 默认为k8sconfig.yaml。node-ip-map: 必须配置节点 IP,用于题目容器端口映射。
node-ip-map: - { name: "您的主机名(运行 hostname 查看)", "address": "服务器公网IP" }* `pull-secret-names`: 如果题目镜像在私有仓库,需要配置 image pull secret。3.2 Kubernetes 配置 (k8sconfig.yaml)
平台需要通过 kubeconfig 文件连接 K3s 集群。
# 复制 K3s 的配置文件sudo cp /etc/rancher/k3s/k3s.yaml ./k8sconfig.yaml
# 修改权限使得当前用户/容器可读 (重要!)sudo chown $(id -u):$(id -g) k8sconfig.yamlsudo chmod 644 k8sconfig.yaml注意: App 默认在 docker-compose.yml 中以 network_mode: "host" 运行,因此 k8sconfig.yaml 中的 server: https://127.0.0.1:6443 通常可以直接工作。如果遇到连接问题,尝试将其改为宿主机 IP (例如 172.17.0.1 或局域网 IP)。
4. 启动服务
使用 Docker Compose 构建并启动服务:
# 构建镜像并后台启动sudo docker compose up -d --build查看日志确认运行正常:
sudo docker compose logs -f app5. 后续操作
- 访问平台: 浏览器访问
http://<服务器IP>:8082(默认端口)。 - 管理员账号: 首次注册用户即为 root。
- 题目管理: 登录后台添加题目,配置题目镜像和端口。
6. 常见问题
- Database Connection Failed:
- 检查
config.yaml中的 Postgres 端口是否为5433(对应docker-compose.yml)。 - 检查
postgres容器是否健康 (docker ps)。
- 检查
- K8s Cluster Connect Failed:
- 检查
k8sconfig.yaml是否存在且有读取权限。 - 检查 K3s 服务状态:
systemctl status k3s。
- 检查
- 题目容器无法访问:
- 检查服务器防火墙 (UFW/Security Group) 是否放行了 NodePort 范围 (默认 30000-32767)。
- 检查
config.yaml中node-ip-map是否配置了正确的公网 IP。