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

# K8sのみでのデプロイ

:::info

このガイドは、**すでにK8sを使用した経験がある**ユーザー向けです。この記事では、K8sクラスタの構築方法や`kubectl`などのコマンドの使い方については**紹介していません**。また、記事中にはK8sの専門用語が含まれている可能性があり、一定の基礎知識が必要です。

この記事では、K8sクラスタ内でGZCTFをデプロイする方法に焦点を当てています。GZCTF自体の設定チュートリアルについては、[クイックスタート](/ja/guide/start/quick-start.md)を参照してください。

:::

## デプロイに関する注意事項

1. GZCTFは複数のインスタンスをデプロイすることをサポートしていますが、テストの結果、現時点では最も安定しているのは、単一のインスタンスをデプロイし、データベースが同一のノードに存在するデプロイ方法です。そのため、この記事では単一インスタンスのデプロイを例に説明します。
2. マルチインスタンスデプロイメントの場合、すべてのインスタンスはファイルの一貫性と書き込みの同時実行性を確保するためにS3/Object Storageを使用する必要があります（[#365](https://github.com/GZTimeWalker/GZCTF/issues/365)を参照してください）。さらに、マルチインスタンス間のキャッシュの一貫性を確保するためにRedisをデプロイする必要があります。両方を設定するには、[appsettings](/ja/config/appsettings.md) を参照してください。Azure / AWS / Minio などの複数のストレージプロバイダーがサポートされています。
3. 複数のインスタンスをデプロイする場合、ロードバランサーはsticky sessionを設定する必要があります。そうしないと、websocketを使用してリアルタイムデータを取得することができません。
4. **もし面倒なら、単一インスタンスをデプロイしましょう！**
5. クラスタ内でGZCTFをデプロイすることを選択した場合、より多くの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](/ja/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 # S3ストレージを設定した場合、このPVはログの保存にのみ使用されるため、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 # S3ストレージを設定した場合、このPVはログの保存にのみ使用されるため、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: ja_JP.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. クラスタ内で、クラスタ構成ファイルの`server`フィールドに`https://kubernetes.default.svc.cluster.local:443`を使用できます。
