> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# 仅 K8s 部署

:::info

本篇指南仅适用于**接触过 K8s** 的用户。此篇文章**不会介绍**如何搭建 K8s 集群，也不会含有如何使用 `kubectl` 等命令的教程，同时文中可能出现一些 K8s 的专业术语，需要一定的基础进行阅读。

本文将着重介绍如何在 K8s 集群中部署 GZCTF，GZCTF 自身的配置教程请参考 [快速上手](/zh/guide/start/quick-start.md)。

:::

## 部署须知

1. GZCTF 支持多实例部署，但经测试，目前来说仅单实例部署且和数据库位于同一节点的部署方式是最稳定的，因此本文中将以单实例部署为例进行介绍。
2. 多实例部署时，全部的实例应当使用 S3 对象存储以确保文件一致性和并发写入（参见 [#365](https://github.com/GZTimeWalker/GZCTF/issues/365)），此外，需要部署 Redis 以确保多实例之间的缓存一致性。请参考 [appsettings](/zh/config/appsettings.md) 文档进行配置。支持 Azure / AWS / Minio。
3. 多实例部署时，负载均衡器需要配置 sticky session，否则无法使用 websocket 来获取实时数据。
4. **如果你不想那么麻烦，那就部署单实例吧！**
5. 既然选择使用 K8s 进行部署，一定程度上说明你需要更大的 Pod 数量，请务必注意如下配置项：
   - 在安装时指定 `--kube-controller-manager-arg=node-cidr-mask-size=22`，默认的 CIDR 为 `/24`，每个节点最多支持 255 个 Pod。我们推荐使用 `/22`（每个节点约 1024 个 Pod），如有需要可使用 `/20`（每个节点约 4096 个 Pod）。使用更小的掩码（例如 `/16`）只会为每个节点保留过大的地址块，并降低集群横向扩展工作节点的能力。**此配置项在安装后无法更改**。

     ```bash
     INSTALL_K3S_EXEC="--kube-controller-manager-arg=node-cidr-mask-size=22"
     ```

   - 请自行调整 `maxPods` 的值，否则可能会导致 Pod 数量达到上限而无法调度。

     ```yaml
     apiVersion: kubelet.config.k8s.io/v1beta1
     kind: KubeletConfiguration
     maxPods: 800
     ```

## 部署 GZCTF

1. 创建命名空间及配置文件，参见 [appsettings](/zh/config/appsettings.md)

   ```yaml
   apiVersion: v1
   kind: Namespace
   metadata:
     name: gzctf-server
   ---
   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=...",
          "RedisCache": "gzctf-garnet:6379,abortConnect=false",
          "Storage": "s3://accessKey=...;secretKey=...;bucket=...;endpoint=...;forcePathStyle=true"
        },
        ...
       }
   ---
   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 # 用于访问 Kubernetes API
   subjects:
     - kind: ServiceAccount
       name: gzctf-sa
       namespace: gzctf-server
   ---
   apiVersion: v1
   kind: Secret
   metadata:
     name: gzctf-tls
     namespace: gzctf-server
   type: kubernetes.io/tls
   data:
     tls.crt: ... # base64 编码后的 TLS 证书
     tls.key: ... # base64 编码后的 TLS 私钥
   ```

2. 创建本地 PV（如果需要多实例共享存储请自行更改配置）

   ```yaml
   apiVersion: v1
   kind: PersistentVolume
   metadata:
     name: gzctf-files-pv
     namespace: gzctf-server
   spec:
     capacity:
       storage: 2Gi # 若采用多实例部署，则此路径仅被用于存储日志文件，可以适当调整大小
     accessModes:
       - ReadWriteOnce
     hostPath:
       path: /mnt/path/to/gzctf/files # 本地路径
   ---
   apiVersion: v1
   kind: PersistentVolume
   metadata:
     name: gzctf-db-pv
     namespace: gzctf-server
   spec:
     capacity:
       storage: 1Gi
     accessModes:
       - ReadWriteOnce
     hostPath:
       path: /mnt/path/to/gzctf/db # 本地路径
   ---
   apiVersion: v1
   kind: PersistentVolumeClaim
   metadata:
     name: gzctf-files
     namespace: gzctf-server
   spec:
     accessModes:
       - ReadWriteOnce
     resources:
       requests:
         storage: 2Gi # 若采用多实例部署，则此路径仅被用于存储日志文件，可以适当调整大小
     volumeName: gzctf-files-pv
   ---
   apiVersion: v1
   kind: PersistentVolumeClaim
   metadata:
     name: gzctf-db
     namespace: gzctf-server
   spec:
     accessModes:
       - ReadWriteOnce
     resources:
       requests:
         storage: 1Gi
     volumeName: gzctf-db-pv
   ```

3. 创建 GZCTF 的 Deployment

   ```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
         nodeSelector:
           kubernetes.io/hostname: xxx # 指定部署节点，强制和数据库位于同一节点
         containers:
           - name: gzctf
             image: gztime/gzctf:latest
             imagePullPolicy: Always
             env:
               - name: GZCTF_ADMIN_PASSWORD
                 value: xxx # 管理员密码
               # choose your backend language `en_US` / `zh_CN` / `ja_JP`
               - 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:
         nodeSelector:
           kubernetes.io/hostname: xxx # 指定部署节点，强制和 GZCTF 位于同一节点
         containers:
           - name: gzctf-db
             image: postgres:alpine
             imagePullPolicy: Always
             ports:
               - containerPort: 5432
                 name: postgres
             env:
               - name: POSTGRES_PASSWORD
                 value: xxx # 数据库密码，需要和 appsettings.json 中的数据库密码一致
             volumeMounts:
               - name: gzctf-db
                 mountPath: /var/lib/postgresql
             resources:
               requests:
                 cpu: 500m
                 memory: 512Mi
         volumes:
           - name: gzctf-db
             persistentVolumeClaim:
               claimName: gzctf-db
   ```

4. 创建 Service 和 Ingress

   ```yaml
   apiVersion: v1
   kind: Service
   metadata:
     name: gzctf
     namespace: gzctf-server
     annotations: # 开启 Traefik 的 Sticky Session
       traefik.ingress.kubernetes.io/service.sticky.cookie: "true"
       traefik.ingress.kubernetes.io/service.sticky.cookie.name: "LB_Session"
       traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true"
   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
     annotations: # 一些 Traefik 的 TLS 设置，可以根据自己的需求修改
       kubernetes.io/ingress.class: "traefik"
       traefik.ingress.kubernetes.io/router.tls: "true"
       ingress.kubernetes.io/force-ssl-redirect: "true"
   spec:
     tls:
       - hosts:
           - ctf.example.com # 域名
         secretName: gzctf-tls # 证书名称，需要自行创建对应的 Secret
     rules:
       - host: ctf.example.com # 域名
         http:
           paths:
             - path: /
               pathType: Prefix
               backend:
                 service:
                   name: gzctf
                   port:
                     number: 8080
   ```

5. Traefik 的额外配置

   为了让 GZCTF 能够正常通过 XFF 获取用户真实 IP，需要让 Traefik 能够正确地添加 XFF 头。请注意如下内容不一定总是具有时效性和适用于所有版本的 Traefik，此处举例为 helm values，请自行查找最新的配置方法。

   ```yaml
   service:
     spec:
       externalTrafficPolicy: Local # 为了让 XFF 能够正常工作，需要将 externalTrafficPolicy 设置为 Local
   deployment:
     kind: DaemonSet
   ports:
     web:
       redirectTo: websecure # 重定向 HTTP 到 HTTPS
   additionalArguments:
     - "--entryPoints.web.proxyProtocol.insecure"
     - "--entryPoints.web.forwardedHeaders.insecure"
     - "--entryPoints.websecure.proxyProtocol.insecure"
     - "--entryPoints.websecure.forwardedHeaders.insecure"
   ```

## 部署提示

1. 如果需要让 GZCTF 在初始化时自动创建管理员账户请注意传递 `GZCTF_ADMIN_PASSWORD` 环境变量，否则需要手动创建管理员账户。
2. 请在系统日志界面调试并参考是否能够正常获取用户真实 IP，如果不能请检查 Traefik 的配置是否正确。
3. 如有监控需求，请自行部署 Prometheus 和 Grafana，并打开 Traefik 的 Prometheus 支持，并且你可以通过 node exporter 监控题目容器的资源使用情况。
4. 如果需要根据配置文件更改自动更新 GZCTF 的部署，请参考 [Reloader](https://github.com/stakater/Reloader)。
5. 在集群内部，可以使用 `https://kubernetes.default.svc.cluster.local:443` 作为集群配置文件中的 `server` 字段。
