GZCTF 单实例快速部署指南
Apr 22, 2026 · 9367 字
GZ::CTF 是一个基于 ASP.NET Core 的开源 CTF 平台,采用 Docker 或 K8s 作为容器部署后端,提供了可自定义的题目类型、动态容器和动态分值功能。
本文将介绍如何在单节点 k3s 集群上快速部署 GZCTF,适合初学者和小型 CTF 赛事使用。
目标环境
- 系统:Ubuntu 22.04+ 或 CentOS 7
安装前准备
Ubuntu
sudo apt update
sudo apt install -y curl ca-certificates gnupg lsb-release
sudo swapoff -a
sudo sed -ri 's@^([^#].*\sswap\s+sw\s+.*)$@#\1@g' /etc/fstab
sudo modprobe br_netfilter
cat <<'EOF' | sudo tee /etc/sysctl.d/99-kubernetes.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
CentOS 7
sudo yum install -y curl ca-certificates iptables-services
sudo swapoff -a
sudo sed -ri 's@^([^#].*\sswap\s+sw\s+.*)$@#\1@g' /etc/fstab
cat <<'EOF' | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF
sudo modprobe br_netfilter
cat <<'EOF' | sudo tee /etc/sysctl.d/99-kubernetes.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
安装 k3s
curl -sfL https://get.k3s.io | sh -
准备部署目录
sudo mkdir -p /opt/gzctf/manifests
sudo chown -R $(id -u):$(id -g) /opt/gzctf
cd /opt/gzctf
创建基础资源
在 /opt/gzctf/manifests 下创建以下文件。
01-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: gzctf-server
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gzctf-sa
namespace: gzctf-server
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gzctf-crb
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: gzctf-sa
namespace: gzctf-server
02-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: gzctf-config
namespace: gzctf-server
data:
appsettings.json: |
{
"ConnectionStrings": {
"Database": "Host=gzctf-db:5432;Database=ctf;Username=postgres;Password=gzctf"
},
"ContainerProvider": {
"Type": "Kubernetes"
},
"ForwardedOptions": {
"ForwardedHeaders": 7,
"ForwardLimit": 1,
"ForwardedForHeaderName": "X-Forwarded-For",
"KnownIPNetworks": [
"10.42.0.0/16",
"127.0.0.1/32"
],
"KnownProxies": []
},
"XorKey": "gzctf123"
}
创建存储
创建宿主机目录:
sudo mkdir -p /opt/gzctf/files /opt/gzctf/db
sudo chmod -R 777 /opt/gzctf/files /opt/gzctf/db
03-storage.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: gzctf-files-pv
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
storageClassName: ""
hostPath:
path: /opt/gzctf/files
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: gzctf-db-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: ""
hostPath:
path: /opt/gzctf/db
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gzctf-files
namespace: gzctf-server
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
resources:
requests:
storage: 2Gi
volumeName: gzctf-files-pv
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gzctf-db
namespace: gzctf-server
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
resources:
requests:
storage: 1Gi
volumeName: gzctf-db-pv
部署 GZCTF
04-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gzctf
namespace: gzctf-server
labels:
app: gzctf
spec:
replicas: 1
strategy:
type: RollingUpdate
selector:
matchLabels:
app: gzctf
template:
metadata:
labels:
app: gzctf
spec:
serviceAccountName: gzctf-sa
containers:
- name: gzctf
image: registry.cn-shanghai.aliyuncs.com/gztime/gzctf:latest
imagePullPolicy: Always
env:
- name: GZCTF_ADMIN_PASSWORD
value: gzctf # 管理员密码
- name: LC_ALL
value: zh_CN.UTF-8
ports:
- containerPort: 8080
name: http
- containerPort: 3000
name: metrics
volumeMounts:
- name: gzctf-files
mountPath: /app/files
- name: gzctf-config
mountPath: /app/appsettings.json
subPath: appsettings.json
resources:
requests:
cpu: 1000m
memory: 384Mi
volumes:
- name: gzctf-files
persistentVolumeClaim:
claimName: gzctf-files
- name: gzctf-config
configMap:
name: gzctf-config
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gzctf-garnet
namespace: gzctf-server
labels:
app: gzctf-garnet
spec:
replicas: 1
selector:
matchLabels:
app: gzctf-garnet
template:
metadata:
labels:
app: gzctf-garnet
spec:
containers:
- name: gzctf-garnet
image: ghcr.io/microsoft/garnet-alpine:latest
imagePullPolicy: Always
ports:
- containerPort: 6379
name: garnet
args: ["--bind", "0.0.0.0"]
resources:
requests:
cpu: 10m
memory: 64Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gzctf-db
namespace: gzctf-server
labels:
app: gzctf-db
spec:
replicas: 1
selector:
matchLabels:
app: gzctf-db
template:
metadata:
labels:
app: gzctf-db
spec:
containers:
- name: gzctf-db
image: postgres:alpine
imagePullPolicy: Always
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_PASSWORD
value: gzctf # 数据库密码,需要和 appsettings.json 中的数据库密码一致
volumeMounts:
- name: gzctf-db
mountPath: /var/lib/postgresql
resources:
requests:
cpu: 500m
memory: 512Mi
volumes:
- name: gzctf-db
persistentVolumeClaim:
claimName: gzctf-db
创建访问入口
配置 traefik
cat <<'EOF' > /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
additionalArguments:
- "--entryPoints.web.forwardedHeaders.insecure"
- "--entryPoints.websecure.forwardedHeaders.insecure"
- "--entryPoints.web.proxyProtocol.insecure"
- "--entryPoints.websecure.proxyProtocol.insecure"
service:
spec:
externalTrafficPolicy: Local
deployment:
kind: DaemonSet
EOF
05-network.yaml
有域名
有域名时,把 ctf.example.com 改成你的真实域名,并把 DNS 解析到服务器公网 IP。
apiVersion: v1
kind: Service
metadata:
name: gzctf
namespace: gzctf-server
spec:
selector:
app: gzctf
ports:
- protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: gzctf-db
namespace: gzctf-server
spec:
selector:
app: gzctf-db
ports:
- protocol: TCP
port: 5432
targetPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: gzctf-garnet
namespace: gzctf-server
spec:
selector:
app: gzctf-garnet
ports:
- protocol: TCP
port: 6379
targetPort: 6379
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gzctf
namespace: gzctf-server
spec:
ingressClassName: traefik
rules:
- host: ctf.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gzctf
port:
number: 8080
无域名
没有域名时,不需要写 host,直接用下面这个版本覆盖同名文件。这个规则会把所有访问都路由到 gzctf:
apiVersion: v1
kind: Service
metadata:
name: gzctf
namespace: gzctf-server
spec:
selector:
app: gzctf
ports:
- protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: gzctf-db
namespace: gzctf-server
spec:
selector:
app: gzctf-db
ports:
- protocol: TCP
port: 5432
targetPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: gzctf-garnet
namespace: gzctf-server
spec:
selector:
app: gzctf-garnet
ports:
- protocol: TCP
port: 6379
targetPort: 6379
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gzctf
namespace: gzctf-server
spec:
ingressClassName: traefik
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gzctf
port:
number: 8080
应用资源
kubectl apply -f /opt/gzctf/manifests/
验证
kubectl -n gzctf-server get pods
kubectl -n gzctf-server get svc
kubectl -n gzctf-server get ingress
kubectl -n gzctf-server logs deploy/gzctf -f
浏览器访问 http://ctf.example.com ,使用 GZCTF_ADMIN_PASSWORD 设置的密码完成首次登录。
常见问题
root 用户找不到 kubectl
echo "export PATH=$PATH:/usr/local/bin" >> ~/.bashrc
source ~/.bashrc
操作系统不支持 cgroup v2
安装最后一个支持 cgroup v1 的 k3s 版本:
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION="v1.24.15+k3s1" sh -