本文介绍使用 Docker Compose 部署回归终端平台服务、PostgreSQL、Valkey、NATS、registry、VictoriaLogs 和入口网关,同时通过外置 Kubernetes 控制面运行动态题目容器。
新手推荐
Docker Compose 部署方案可以用于生产环境,尤其适合把平台节点和题目 Kubernetes 集群分离的比赛或练习场。它的主要限制是:平台服务和基础组件不在 Kubernetes 内,无法使用 Kubernetes 对这些组件做统一调度、滚动发布、节点亲和、PodDisruptionBudget、PVC 调度和多节点故障迁移。 Kubernetes 只负责调度 challenge pods;如果希望平台、数据库、缓存、队列、日志和题目容器都由 Kubernetes 统筹规划,请使用 集群内部部署。
本方案也支持通过 Podman 与 Podman Compose 部署,甚至可以说更推荐使用 Podman 技术栈,Podman 不依赖宿主机 root 权限,可以纯用户权限运行和管理,在某些场景下安全性可能相比 Docker 更佳一些。
部署结构
推荐结构如下:
| 组件 | 部署位置 | 说明 |
|---|---|---|
platform | Docker Compose | 回归终端平台服务,读取 /etc/ret2shell/config.toml,通过挂载的 kubeconfig 访问外部 Kubernetes。 |
| PostgreSQL | Docker Compose | 平台主数据库,使用 Compose volume 持久化。 |
| Valkey | Docker Compose | 会话、令牌、缓存和限速状态。 |
| NATS JetStream | Docker Compose | 后台任务和事件队列。 |
| VictoriaLogs | Docker Compose | 可选但推荐保留,用于集中日志检索。 |
| registry | Docker Compose | 可选受控题目镜像仓库。Kubernetes 节点必须能访问它的外部地址。 |
| gateway | Docker Compose / 外置网关 | 暴露 Web、API 和 WebSocket。TLS 可以由此处或外部负载均衡处理。 |
| Kubernetes | 外置集群 | 只运行 ret2shell-challenge namespace 下的动态题目 Pod 和 Service。 |
这种结构把平台数据放在稳定的 Compose 主机上,把题目算力放在 Kubernetes 集群中。平台主机需要能访问 Kubernetes API;Kubernetes 节点需要能访问题目镜像仓库。
准备目录
以下示例默认把部署文件放在 /opt/ret2shell。如果使用其他路径,后续命令中的路径也要同步调整。
sudo mkdir -p /opt/ret2shell/config /opt/ret2shell/nginx
sudo chown -R "$USER:$USER" /opt/ret2shell
cd /opt/ret2shell使用 uuidgen 生成若干个随机 uuidv4,把生成的 UUID 分别用于 config.toml 中的 signing_key、.env 中的 POSTGRES_PASSWORD、VALKEY_PASSWORD 和 NATS_TOKEN。不要继续使用示例密码。
创建 .env:
R2S_VERSION=<release-version>
POSTGRES_PASSWORD=<replace-with-random-postgres-password>
VALKEY_PASSWORD=<replace-with-random-valkey-password>
NATS_TOKEN=<replace-with-random-nats-token>
REGISTRY_BIND=10.0.0.10R2S_VERSION 应替换为实际部署的 release 版本。REGISTRY_BIND 是平台主机上允许 Kubernetes 节点访问 registry 的内网地址;如果不用内置 registry,可以删除 registry 服务和 [cluster.registry] 配置。
推荐 Compose 配置
创建 docker-compose.yml:
name: ret2shell
services:
database:
image: postgres:18-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ret2shell
POSTGRES_USER: ret2shell
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- database:/var/lib/postgresql/data
cache:
image: valkey/valkey:8-alpine
restart: unless-stopped
command:
- valkey-server
- --appendonly
- "yes"
- --requirepass
- ${VALKEY_PASSWORD}
volumes:
- cache:/data
message_queue:
image: nats:2-alpine
restart: unless-stopped
command:
- -js
- -sd
- /data
- --auth
- ${NATS_TOKEN}
volumes:
- message_queue:/data
vlogs:
image: victoriametrics/victoria-logs:v1.43.1
restart: unless-stopped
command:
- --storageDataPath=/vlogs
volumes:
- vlogs:/vlogs
registry:
image: registry:3
restart: unless-stopped
environment:
REGISTRY_STORAGE_DELETE_ENABLED: "true"
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry
command:
- serve
- /etc/docker/registry/config.yml
volumes:
- registry:/var/lib/registry
ports:
- "${REGISTRY_BIND}:5000:5000"
platform:
image: ghcr.io/ret2shell/ret2shell:${R2S_VERSION}
restart: unless-stopped
depends_on:
- database
- cache
- message_queue
- vlogs
- registry
volumes:
- platform:/var/lib/ret2shell
- platform_cache:/var/cache/ret2shell
- platform_log:/var/log/ret2shell
- ./config/config.toml:/etc/ret2shell/config.toml:ro
- ./config/sensitive_word_list.txt:/etc/ret2shell/sensitive_word_list.txt:ro
- ./config/kubeconfig.yaml:/etc/ret2shell/kubeconfig.yaml:ro
expose:
- "8080"
gateway:
image: nginx:alpine
restart: unless-stopped
depends_on:
- platform
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
volumes:
database:
cache:
message_queue:
vlogs:
registry:
platform:
platform_cache:
platform_log:这个配置只把 gateway 的 80 端口和 registry 的内网 5000 端口暴露到宿主机。PostgreSQL、Valkey、NATS 和 VictoriaLogs 保持在 Compose 网络内部。生产环境应在宿主机防火墙或云安全组中限制 5000 端口只允许 Kubernetes 节点访问。
创建 nginx/nginx.conf:
user nginx;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
map $http_upgrade $connection_upgrade {
default upgrade;
"" close;
}
server {
listen 80 default_server;
client_max_body_size 1024m;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
location / {
proxy_pass http://platform:8080;
}
}
}示例让 Nginx 反向代理所有路径,由平台镜像内置的前端静态文件服务返回 Web 页面。需要更高静态文件吞吐时,可以把前端产物单独挂载给 Nginx,并只把 /api、WebSocket 和 registry 管理接口代理到平台。
生产环境通常还需要 TLS。可以在 gateway 前放置云负载均衡、Caddy、宿主机 Nginx,或扩展上面的 Nginx 配置监听 443。启用 HTTPS 后,config.toml 中的 external_https 必须设置为 true。
配置平台
创建 config/sensitive_word_list.txt,可以先放一个空文件:
touch config/sensitive_word_list.txt创建 config/config.toml。下面是与上方 Compose 配套的生产配置起点:
[auditor]
sensitive_word_list = '/etc/ret2shell/sensitive_word_list.txt'
[auth]
buffer_time = 21600
expires_time = 86400
signing_key = '<replace-with-random-signing-key>'
[bucket]
path = '/var/lib/ret2shell/bucket'
[cache]
url = 'redis://:<replace-with-valkey-password>@cache:6379'
[captcha]
enabled = true
difficulty = 4
validator = 'pow'
[cluster]
enabled = true
auto_infer = false
try_default = false
kube_config_path = '/etc/ret2shell/kubeconfig.yaml'
node_selector = 'challenge'
enable_capture = true
capture_directory = '/var/cache/ret2shell/captures'
[cluster.registry]
enabled = true
server = 'registry:5000'
external = 'registry.internal.example.com:5000'
insecure = true
[database]
host = 'database'
port = 5432
ssl_mode = 'disable'
user = 'ret2shell'
password = '<replace-with-postgres-password>'
db = 'ret2shell'
# email 节推荐在平台部署完成后从 web 界面配置
[email]
enabled = false
host = 'smtp.example.com'
password = ''
port = 587
sender = 'Ret2Shell'
tls = 'starttls'
username = ''
[logging]
compress = true
directory = '/var/log/ret2shell'
files_kept = 3
level = 'info,tower_http=info,sqlx=warn'
victoria = 'http://vlogs:9428'
[media]
anti_theft = true
limit = 100
path = '/var/lib/ret2shell/media'
[queue]
host = 'message_queue'
port = 4222
token = '<replace-with-nats-token>'
[server]
host = '0.0.0.0'
port = 8080
api_base_path = '/api'
cors_origins = 'https://ctf.example.com'
external_domain = 'ctf.example.com'
external_https = true
[server.rate_limit]
burst_limit = 32
burst_restore_rate = 500
[server.frontend]
serve_type = 'static'
path = '/var/www/html'.env 变量仅通过 docker-compose 应用于各个服务组件,所以你还需要将其填入 config.toml 中,便于平台后续使用,示例中的密码、token、域名和 registry 地址都要写成最终值。修改配置文件后需要重启 platform 服务:
docker compose restart platform配置项说明
| 配置节 | 说明 |
|---|---|
[auditor] | 敏感词文件路径,用于队伍名等内容审核。Compose 中应指向 /etc/ret2shell/sensitive_word_list.txt。 |
[auth] | 登录令牌配置。signing_key 必须替换为随机强密钥,泄露后需要让所有用户重新登录。 |
[bucket] | 题目仓库、附件、checker、环境配置等文件的持久化目录。Compose 中放在 platform volume。 |
[cache] | Valkey / Redis 连接地址。上方 Compose 开启了 Valkey 密码,因此 URL 使用 redis://:<password>@cache:6379。 |
[captcha] | 注册、登录等场景的验证码策略。默认 pow 不依赖第三方服务。 |
[cluster] | 动态题目容器控制配置。Compose 场景应关闭 auto_infer 和 try_default,通过 kube_config_path 指向挂载进容器的 kubeconfig。node_selector = 'challenge' 会让题目 Pod 选择带有 ret.sh.cn/workload=challenge 标签的节点。 |
[cluster.registry] | 受控题目镜像仓库。server 是平台容器推送镜像使用的 Compose 内部地址,external 是 Kubernetes 节点拉取镜像使用的地址。两者经常不同。 |
[database] | PostgreSQL 连接配置。必须和 Compose 中的数据库用户名、密码、库名保持一致。 |
[email] | SMTP 邮件配置。不开启邮件时保持 enabled = false。 |
[logging] | 本地日志和 VictoriaLogs 配置。victoria 指向 Compose 内部的 vlogs 服务后,后台日志查询功能才有集中日志来源。 |
[media] | 用户上传媒体文件配置。path 应在持久化 volume 内。 |
[queue] | NATS JetStream 连接配置。上方 Compose 使用 token 鉴权,因此这里填写同一个 token。 |
[server] | 平台监听地址、外部域名、HTTPS 状态和 CORS。external_domain 必须是用户实际访问的平台域名;有 TLS 入口时设置 external_https = true。 |
[server.rate_limit] | API 限流参数。默认值适合起步,正式比赛可以按访问量调整。 |
[server.frontend] | 平台内置静态文件服务。使用上方 Nginx 代理所有路径时保持 /var/www/html。 |
配置文件在平台启动时读取,运行过程中不会自动热重载。部分站点名称、页脚、题目运行配置可以在后台管理界面覆盖;基础连接信息、路径、密钥和 kubeconfig 仍应通过 DevOps 流程修改并重启服务。
注入外置 Kubernetes kubeconfig
如果平台不使用 Kubernetes 与动态容器功能,那么接下来的过程都可以忽略。可以在同一服务器上同时安装 docker 与 k8s 集群,有关 k8s/k3s 的快速安装设置可以参考 环境准备。
平台容器不在 Kubernetes 集群内,不能使用集群内 ServiceAccount 自动发现。推荐在外置 Kubernetes 集群中创建专用 ServiceAccount,然后把 kubeconfig 以只读文件挂载到 /etc/ret2shell/kubeconfig.yaml。
准备 Kubernetes API 地址
平台主机必须能访问 Kubernetes API,例如:
curl -k https://k8s-api.example.com:6443/version如果使用 k3s,并且 kubeconfig 中的 server 还是 https://127.0.0.1:6443,需要改成平台容器可访问的控制面地址。生产环境建议在安装 k3s 时加入固定的 API 域名或内网 IP 作为 TLS SAN:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --tls-san k8s-api.example.com" sh -已经安装的集群也可以导出 kubeconfig 后手动把 clusters[].cluster.server 改成可访问地址,但证书必须信任该地址,否则平台连接 Kubernetes API 会失败。
创建控制账号
在 Kubernetes 控制面执行:
apiVersion: v1
kind: Namespace
metadata:
name: ret2shell-challenge
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: ret2shell-controller
namespace: ret2shell-challenge
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ret2shell-controller-read
rules:
- apiGroups: [""]
resources: ["namespaces", "nodes", "configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ret2shell-controller
namespace: ret2shell-challenge
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/portforward", "services"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ret2shell-controller
subjects:
- kind: ServiceAccount
name: ret2shell-controller
namespace: ret2shell-challenge
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ret2shell-controller-read
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ret2shell-controller
namespace: ret2shell-challenge
subjects:
- kind: ServiceAccount
name: ret2shell-controller
namespace: ret2shell-challenge
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ret2shell-controller保存为 ret2shell-controller.yaml 后应用:
kubectl apply -f ret2shell-controller.yaml
kubectl label node worker-1 ret.sh.cn/workload=challenge
kubectl label node worker-2 ret.sh.cn/workload=challengenode_selector = 'challenge' 时,平台创建的题目 Pod 只会调度到这些节点。不同比赛也可以在后台单独设置 node selector。
生成 kubeconfig
在能访问 Kubernetes 的机器上生成 config/kubeconfig.yaml:
cd /opt/ret2shell
TOKEN="$(kubectl -n ret2shell-challenge create token ret2shell-controller --duration=8760h)"
SERVER="$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.server}')"
CA_DATA="$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')"
cat > config/kubeconfig.yaml <<EOF
apiVersion: v1
kind: Config
clusters:
- name: ret2shell
cluster:
server: ${SERVER}
certificate-authority-data: ${CA_DATA}
users:
- name: ret2shell-controller
user:
token: ${TOKEN}
contexts:
- name: ret2shell
context:
cluster: ret2shell
user: ret2shell-controller
current-context: ret2shell
EOF
chmod 600 config/kubeconfig.yaml如果生成出来的 SERVER 不是平台容器可访问地址,先编辑 config/kubeconfig.yaml 再启动平台。Token 有有效期,到期前需要重新生成 kubeconfig 并重启 platform。
配置 registry 拉取地址
启用 Compose 内置 registry 时,Kubernetes 节点会从 [cluster.registry].external 拉取镜像。这个地址必须从每个 Kubernetes 节点可达。
如果使用 k3s 且 registry 通过内网 HTTP 暴露,可以在每个节点配置:
mirrors:
"registry.internal.example.com:5000":
endpoint:
- "http://registry.internal.example.com:5000"保存为 /etc/rancher/k3s/registries.yaml 后重启 k3s:
sudo systemctl restart k3s如果 registry 使用正式 TLS 和可信证书,可以把 [cluster.registry].insecure 设置为 false,并让 external 指向 HTTPS registry 域名。无论是否使用 TLS,都应通过防火墙限制 registry 只允许平台主机和 Kubernetes 节点访问。
启动和检查
启动服务:
cd /opt/ret2shell
docker compose pull
docker compose up -d
docker compose ps检查平台:
docker compose logs -f platform
curl -fsS http://127.0.0.1/api/ping检查 Kubernetes 连接时,先在宿主机使用同一份 kubeconfig:
KUBECONFIG=/opt/ret2shell/config/kubeconfig.yaml kubectl get nodes -o wide
KUBECONFIG=/opt/ret2shell/config/kubeconfig.yaml kubectl -n ret2shell-challenge get pods,svc平台启动后会确认 ret2shell-challenge namespace 和默认网络策略。如果日志中出现 kubeconfig、证书、权限或 API 连接错误,优先检查 config/kubeconfig.yaml 中的 server、token 有效期、RBAC 和平台主机到 Kubernetes API 的网络连通性。
备份和升级
生产环境至少备份 PostgreSQL、platform volume、registry volume 和部署配置目录。
数据库备份:
mkdir -p backups
docker compose exec -T database pg_dump -U ret2shell ret2shell > "backups/ret2shell.$(date +%F).sql"升级平台时,先备份数据库和配置,再修改 .env 中的 R2S_VERSION:
docker compose pull platform
docker compose up -d platform gateway
docker compose logs -f platform如果同时升级 PostgreSQL、Valkey、NATS、registry 或 VictoriaLogs,先阅读对应组件的升级说明,并确认 volume 数据格式兼容。不要在没有备份的情况下直接跨大版本升级数据库。
