• 日本語
  • K8sのみでのデプロイ

    Info

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

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

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

    1. GZCTFは複数のインスタンスをデプロイすることをサポートしていますが、テストの結果、現時点では最も安定しているのは、単一のインスタンスをデプロイし、データベースが同一のノードに存在するデプロイ方法です。そのため、この記事では単一インスタンスのデプロイを例に説明します。
    2. マルチインスタンスデプロイメントの場合、すべてのインスタンスはファイルの一貫性と書き込みの同時実行性を確保するためにS3/Object Storageを使用する必要があります(#365を参照してください)。さらに、マルチインスタンス間のキャッシュの一貫性を確保するためにRedisをデプロイする必要があります。両方を設定するには、appsettings を参照してください。Azure / AWS / Minio などの複数のストレージプロバイダーがサポートされています。
    3. 複数のインスタンスをデプロイする場合、ロードバランサーはsticky sessionを設定する必要があります。そうしないと、websocketを使用してリアルタイムデータを取得することができません。
    4. もし面倒なら、単一インスタンスをデプロイしましょう!
    5. クラスタ内でGZCTFをデプロイすることを選択した場合、一定の程度のPod数が必要であることに注意してください。
      • インストール時に --kube-controller-manager-arg=node-cidr-mask-size=16 を指定してください。デフォルトのCIDRは /24 で、最大255個のPodをサポートします。インストール後にこの設定を変更することはできません

        INSTALL_K3S_EXEC="--kube-controller-manager-arg=node-cidr-mask-size=16"
      • maxPodsの値を適切に調整してください。そうしないと、Podの数が上限に達してスケジュールできなくなる可能性があります。

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

    GZCTFのデプロイ

    1. ネームスペースと設定ファイルの作成

      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": "minio.s3://serviceUrl=...;key=...;bucket=..."
           },
           ...
          } # appsettings.json の内容
      ---
      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の作成(複数のインスタンスで共有ストレージが必要な場合は、設定を自分で変更してください)

      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を作成

      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の作成

      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の例を示していますが、最新の設定方法を自分で調べてください。

      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を参照してください。
    5. クラスタ内で、クラスタ構成ファイルのserverフィールドにhttps://kubernetes.default.svc.cluster.local:443を使用できます。