メインコンテンツへスキップ

Palworld サーバーを Kubernetes で立ててみた

·
tech homelab

だいぶ前にこういうポストをしていて、諸々環境が整ってきたので実際にやってみました。

目新しい情報は特になくて、巨人の肩に乗っかると簡単に Kubernetes で起動できるという話です。

Kubernetes で立てるモチベーション
#

  • CronJob を定義すれば、バックアップなどの任意の処理を定期的に実行できる
  • 設定値を宣言的に定義できる
  • やはり浪漫…浪漫がある…

というわけで、カジュアルにおうち k8s クラスタに立ててみます。

まずはネイティブで試す
#

Minecraft などのゲームサーバーを立てたことがなかったので、どう立てるかわかってない状態でした。

まずは Kubernetes を使わずにサーバーを立ててみます。公式ドキュメントの通りに進めるとサクッと起動できました。

専用サーバーの構築 | Palworld tech guide

SteamCMD という CLI ツールをインストールして、適切なパラメータを渡して起動するだけです。デフォルトでは UDP 8211 番で待ち受ける形となっています。

外部ネットワークからアクセスするにはルーターのポートフォワードなどの設定を行うことで可能です。Palworld だと 8211 がデフォルトポートで、このポートが使用されることが多いのだと思います。

Kubernetes 起動への道のりを考える
#

Docker Image を用意する
#

SteamCMD は Windows と Linux に対応しているので、 SteamCMD が使える Docker Image を作ってあげれば Kubernetes で起動できそうです。

で、調べると既に有志により palworld-server-docker というリポジトリが公開されており、活発にメンテナンスされているのがわかります。

thijsvanloef/palworld-server-docker

A Docker Container to easily run a Palworld dedicated server.

Shell
2386
299

このリポジトリで管理されている Docker Image に乗っかるのが簡単そうです。

Kubernetes リソースを用意する
#

いい感じの Docker Image が公開されているので、いい感じに Deployment やら PVC やらのリソースを定義して kubectl apply -f palworld.yml すれば起動できそう感があります。

palworld-server-docker のリポジトリの README を読み進めると、世界は広いのか狭いのか、既に有志により Helm Chart が別リポジトリで作られていることがわかります。

Twinki14/palworld-server-chart

A helm chart for the popular palworld-server-docker Docker image

null
6
1

Fork 数や Star 数をみると、 Kubernetes で Palworld サーバーを立てようとする人口の少なさを感じますが、 Official Helm Chart ということで使ってみます。

定義されているリソースもトリッキーなものはなさそうで、 Kubernetes 初心者かつゲームサーバー初心者の自分には勉強になります。

手持ちの Kubernetes クラスタで起動する
#

Argo CD アプリケーションを定義する
#

自宅の Kubernetes クラスタでは Argo CD が動いていて、 GitOps できるようになっています。

Helm Chart を導入するときは Argo CD の Application リソースとして定義して、宣言的に管理するようにしています。

今回の場合は以下のようになりました。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: palworld
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: palworld
    server: {{.Values.spec.destination.server}}
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
  source:
    chart: palworld
    repoURL: https://twinki14.github.io/palworld-server-chart
    targetRevision: 0.30.1
    helm:
      releaseName: palworld
      values: |-
        # 一部省略
        server:
          config:
            env:
              # All environment variables
              # https://github.com/thijsvanloef/palworld-server-docker/blob/main/.env.example

              # Server Settings
              # https://palworld-server-docker.loef.dev/getting-started/configuration/server-settings
              BACKUP_ENABLED: "true"
              DELETE_OLD_BACKUPS: "false"
              OLD_BACKUP_DAYS: "30"
              BACKUP_CRON_EXPRESSION: "0 4 * * *"  # 日本時間の毎日 04:00
              AUTO_UPDATE_ENABLED: "false"
              AUTO_UPDATE_CRON_EXPRESSION: "0 * * * *"
              AUTO_UPDATE_WARN_MINUTES: "30"
              AUTO_REBOOT_ENABLED: "false"
              AUTO_REBOOT_EVEN_IF_PLAYERS_ONLINE: "false"
              AUTO_REBOOT_WARN_MINUTES: "5"
              AUTO_REBOOT_CRON_EXPRESSION: "0 0 * * *"
              ENABLE_PLAYER_LOGGING: "true"
              PLAYER_LOGGING_POLL_PERIOD: "10"

              # Game Settings
              # https://palworld-server-docker.loef.dev/getting-started/configuration/game-settings
              DIFFICULTY: "None"
              DAYTIME_SPEEDRATE: "1.000000"
              NIGHTTIME_SPEEDRATE: "1.000000"
              EXP_RATE: "5.000000"
              PAL_CAPTURE_RATE: "0.500000"
              PAL_SPAWN_NUM_RATE: "1.000000"
              PAL_DAMAGE_RATE_ATTACK: "1.000000"
              PAL_DAMAGE_RATE_DEFENSE: "1.000000"
              PLAYER_DAMAGE_RATE_ATTACK: "2.000000"
              PLAYER_DAMAGE_RATE_DEFENSE: "1.00000"
              PLAYER_STOMACH_DECREASE_RATE: "0.100000"
              PLAYER_STAMINA_DECREASE_RATE: "0.0100000"
              PLAYER_AUTO_HP_REGEN_RATE: "1.000000"
              PLAYER_AUTO_HP_REGEN_RATE_IN_SLEEP: "1.000000"
              PAL_STOMACH_DECREASE_RATE: "0.100000"
              PAL_STAMINA_DECREASE_RATE: "0.100000"
              PAL_AUTO_HP_REGEN_RATE: "1.000000"
              PAL_AUTO_HP_REGEN_RATE_IN_SLEEP: "1.000000"
              BUILD_OBJECT_DAMAGE_RATE: "0.1000000"
              BUILD_OBJECT_DETERIORATION_DAMAGE_RATE: "1.000000"
              COLLECTION_DROP_RATE: "5.000000"
              COLLECTION_OBJECT_HP_RATE: "1.000000"
              COLLECTION_OBJECT_RESPAWN_SPEED_RATE: "10.000000"
              ENEMY_DROP_ITEM_RATE: "5.000000"
              DEATH_PENALTY: "Item"
              ENABLE_PLAYER_TO_PLAYER_DAMAGE: "False"
              ENABLE_FRIENDLY_FIRE: "False"
              ENABLE_INVADER_ENEMY: "True"
              ACTIVE_UNKO: "False"
              ENABLE_AIM_ASSIST_PAD: "True"
              ENABLE_AIM_ASSIST_KEYBOARD: "False"
              DROP_ITEM_MAX_NUM: "3000"
              DROP_ITEM_MAX_NUM_UNKO: "100"
              BASE_CAMP_MAX_NUM: "128"
              BASE_CAMP_WORKER_MAX_NUM: "20"
              DROP_ITEM_ALIVE_MAX_HOURS: "1.000000"
              AUTO_RESET_GUILD_NO_ONLINE_PLAYERS: "False"
              AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS: "72.00000"
              GUILD_PLAYER_MAX_NUM: "20"
              PAL_EGG_DEFAULT_HATCHING_TIME: "0.00000"
              WORK_SPEED_RATE: "2.000000"
              IS_MULTIPLAY: "False"
              IS_PVP: "False"
              CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP: "False"
              ENABLE_NON_LOGIN_PENALTY: "True"
              ENABLE_FAST_TRAVEL: "True"
              IS_START_LOCATION_SELECT_BY_MAP: "True"
              EXIST_PLAYER_AFTER_LOGOUT: "False"
              ENABLE_DEFENSE_OTHER_GUILD_PLAYER: "False"
              COOP_PLAYER_MAX_NUM: "4"
              REGION: ""
              USEAUTH: "True"
              BAN_LIST_URL: "https://api.palworldgame.com/api/banlist.txt"
              SHOW_PLAYER_LIST: "True"

              # Engine Settings
              # https://palworld-server-docker.loef.dev/getting-started/configuration/engine-settings
              DISABLE_GENERATE_SETTINGS: "false"        

values.yaml に相当する内容は長いので一部省略していますが Helm Chart のデフォルトの values.yaml を移して、適宜修正して記載しています。

ゲーム内容の設定値は環境変数で指定するようになっていて、 palworld-server-docker のドキュメント 公式ドキュメント を見ながら、必要に応じて設定していきます。

ちなみに秘匿な情報は Sealed Secrets で管理するようにしていて、以下のように定義して、 values で指定しています。

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: palworld
  namespace: palworld
spec:
  encryptedData:
    admin-password: AgB2s6SZtB672+qMdez2aJiNTvJBQIr96FBblwQYRLcBW+7ktNh1WY+/W9l4AFcD2p2meSjEP6LDX9/hZMfv3rUPs/qaY9eLQ38uJ+9DwZJvSZ/+R+Qyy52Svd9B0IzqNAFp14CusUmnh5Je2mo29k6wkBz5ywgGJYNJRfJ7Iyhtbyuvv+f1DIaRyITCp2+xABA9V4/DUFUfeQSWBOglboA24KAJEjcfaHi4OpHtjTvdevInyJ3QqJCTqJPnm1JBYc29ww3hnIo4k98VChxLzcsR+s0MVlnNUg3ivbs1B7VREW5KvoQktfR1A/LrAYa8Vid+x+KgfVboUkQQnmWODy3AIWnhtLFv3FDI+skVPuoJutF5jlhfm9xkFoIijnUVYKq03lNnTJtGAq9Kq4v0CaYAIJaJy2Vv/6mZcE/+VrkE6fAU+A97zyIN2dZNOIb6ATaBK+O7J/2+PWqI2b0x1Ol+Bsj90qRahEQ4bH+BAMoJbVvatlSRirC6LswKD230QoZnbU/J/VtZc3tdNcv2xYXm3gnGZ9ZoU+ASDEYiZCtEw0cB0i3jDAd5pY6ETXhz22s92E7roz/f06ixa5BFdUzBwyQzHErFcWUjvy739EUV/+OH1bJo4jv1Z2OYCryoTP2t1M9q88KW/5hNz1cd24V8WDulFLxTG2bwn7y3i5Pywk8RHXJP4GPISyYj83HZ9a5y2CADZK6mV7uVCKWWApk5HIN3RqAJf+H0uP9hdy5P1Q==
    server-password: AgAmQRBosh6kyxITFFspicL5EaRedIigZF7bgkIcv8Cq7ZeJLORZ4BR4sr7LiL5cw58ffimxSkp9U2ehKNvzmiPe99I56QPJZMVJXy9l4UAqqqIE5tmRkQavGxZih4ppbDdIOyikpzeSlD52KH32novHf4jVg5G8tZoRQpfsEElBW4jDm0+SuKr7xkkjLQ33CeYfIVB7ljSqDkTqmVZWvKXAMS+USWrCHOxNl/ChjGg7UDsdWjAymtGrO+MohjGnHanXhsdumm+f24Chon3ZO2nNukHhy4xJ0qKRqaUNBXDChsYRrHVp1aijrqbBplbQbLgzTYiKag6vPNoVjAXWZyYp3B7nRW0iP6BgUc6h9JK2EzbOzIayurY4tZOZNYW431SLx0IngVCJMjkUBqULWcpXCQZcg3mtZ3YmZYhhSure9GNiOKrTLwbGieAnJheDBaqeQiUQIV3x4WFobpSSgtOmhqGigwJypJGL8k+WNeEfhxvO3Q4oVmyqqHUJfogn3ZUw3ceZNQXoRpxlJ93bX1MAQIrYMqORym+dueLcDvsQ4s7xHgro89UtNd3CoN/rxK5Clt6JV8kiUTmdoydNFtBtRoihdnZ9Y7oidwP11vgshdcIrwU8P6mPYtq4tFIQ+wCUGiKuP4OEKws4tmz+0HQP0BezqwpvypQfpZXLYf/GdfgG/qN1yrXMbPy3YnP8hPRezvdWcqih
  template:
    metadata:
      creationTimestamp: null
      name: palworld
      namespace: palworld

Argo CD の Application リソースを管理するリポジトリ自体は非公開にしているんですが、上記のように public リポジトリにおいても運用できるので安心感が高いですね。

ネットワーク周りをどうにかする
#

Argo CD の Application を作成したことで Palworld サーバーの起動はできたものの、このままだと外から接続することができません。

Helm Chart の values.yaml の service 周りの設定値を確認します。

  service:
    enabled: true
    annotations: {}
    labels: {}

    # For minikube, set this to NodePort, elsewhere use LoadBalancer
    # Use ClusterIP if your setup includes ingress controller
    #
    type: ClusterIP

いつもだと ClusterIP を指定して、 Ingress でいい感じにサブドメインを切って外部公開しているので、今回もその方法を取ろうとしました。が、調べてみると Kubernetes の Ingress って HTTP トラフィックのルーティングのみ対応しているんですね。

ピンチと思いきや、自分のクラスタでは ingress-nginx を使っているのですが、 HTTP 以外の TCP/UDP の外部公開に対応していました。ありがたい…

HTTP 以外のトラフィックを扱おうとして初めて直面して知ったので、ゲームでもなんでもやってみるもんですね。

自分の場合は ingress-nginx も Argo CD の Application として定義しているので、以下のように parameter を追加しました。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ingress-nginx
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: ingress-nginx
    server: {{.Values.spec.destination.server}}
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
  source:
    chart: ingress-nginx
    repoURL: https://kubernetes.github.io/ingress-nginx
    targetRevision: 4.9.1
    helm:
      releaseName: ingress-nginx
      valueFiles:
        - values.yaml
      parameters:
        # Palworld Settings
        - name: tcp.25575
          value: "palworld/palworld-svc:25575"
        - name: udp.8211
          value: "palworld/palworld-svc:8211"
        - name: udp.27015
          value: "palworld/palworld-svc:27015"

Palworld の values を見直し、今回は例外的に service.type に LoadBalancer を指定して、 UDP でルーティングできるようにします。

External IP で IP が払い出されているのが確認できました。

$ kubectl get svc -n palworld
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                          AGE
palworld-svc   LoadBalancer   10.233.38.238   10.3.0.2      8211:31950/UDP,27015:31387/UDP,25575:31614/TCP   13d

ルーターでこの IP を公開するように設定して、完了です。

接続テストしてみる
#

palworld.chanyou.app:8211 でアクセスできました。

パスワードは知人の方にはお伝えしますので、気軽に @chanyou0311 宛に DM で聞いてもらえたらと思います。

さいごに
#

巨人の肩に乗っかって、 Palworld サーバーを Kubernetes で動かすことができました。UDP を扱ったのが初めてだったので、ルーティング周りが勉強になりました。

設定値を宣言的に設定できるので、カジュアルに設定変えられて体験いい感じです。

ちなみに values で LoadBalancerIP を指定できるとより宣言的で良いんですが、非対応のようでした。コントリビュートチャンスかも。

遊ぶ時間が取れてないので、代わりに上述のサーバーで遊んでほしいです。

おわり。

このエントリーをはてなブックマークに追加

Related

Kubernetes でブログを配信するようにした
tech homelab
Kubespray 使ってみた # 呉高専 Advent Calendar 2022 の14日目の記事を担当しているんだけど、せっかくなのでこのブログにポストしたい。
記事にタイトルを設定してみた
tech homelab
深く考えずに ブログを立ち上げたが、かれこれ一ヶ月も経ってしまった。
ブログを立ち上げた
tech homelab
家にサーバーが転がっているので、活かそうと思って余っているドメインでブログを立ち上げた。仕事で散々クラウドアーキテクトやっているので、自宅サーバーで気軽にサービス稼働させるのも楽しい。