-
Notifications
You must be signed in to change notification settings - Fork 1
Installation Linux Pulumi
Optional infrastructure-as-code layer for Kubernetes. Use Pulumi when you want programmatic deploy, CI parity, and multi-world management from develop.json — you do not need Pulumi to run fb on Kubernetes if you maintain your own manifests.
Parent guide: Linux Installation
For single-host Docker without Kubernetes, see Docker Compose.
| Layer | How it runs |
|---|---|
| Build | Docker images (server/*/Dockerfile) pushed to GHCR |
| Deploy | Pulumi (infra/pulumi/) creates resources in namespace fb
|
| Config |
infra/pulumi/develop.json + Pulumi secret host
|
| Data |
fb/data image (Excel → JSON); maps bundled in game image |
Typical workflows:
-
CI (recommended) — push to
release/**triggers image build and Pulumi deploy (.github/workflows/build.yml). -
Manual full cycle — run
tools/build.shon a build machine (build, push, sethost,pulumi up). -
Deploy only — images already in registry; run Pulumi with the correct
hostvalue.
| Use Pulumi | Use plain Kubernetes instead |
|---|---|
CI deploy on release/** (repo default) |
Custom cluster, hand-written YAML |
Multi-world from develop.json
|
Single world (dev), minimal shards in infra/k8s/
|
| Team already uses Pulumi | Prefer kubectl / GitOps only |
The checked-in dev overlay (infra/k8s/overlays/dev) uses the same StatefulSet/Service naming as Pulumi — only replica and shard counts are reduced.
Images are always built with Docker first — see Kubernetes — Build images.
Pulumi targets the cluster from your current kubeconfig (kubectl config current-context).
Stock manifests assume a self-hosted cluster with:
| Requirement | Used by | Notes |
|---|---|---|
Node label infra=true
|
MySQL, Redis, RabbitMQ StatefulSets | Required |
Node label cpu=epyc
|
Gateway, Login, Game, Internal, … | Preferred only |
Host path /mnt/fb/
|
MySQL, Redis data volumes |
DirectoryOrCreate on infra nodes |
| NodePort range | Client-facing services | Fixed ports in develop.json (e.g. gateway 30000) |
If your cluster uses different labels or storage, update affinity / hostPath in infra/pulumi/*.js before deploying.
| Tool | Purpose |
|---|---|
| Docker + buildx | Multi-stage builds (tools/build.sh, CI) |
| Git | Clone with submodules |
| Registry login | Push to ghcr.io (or retag to your registry) |
tools/build.sh uses sudo docker when the current user cannot access the Docker daemon.
| Tool | Purpose |
|---|---|
| Node.js (LTS) |
npm install in infra/pulumi/
|
| Pulumi CLI |
pulumi up / pulumi destroy
|
| kubectl | Cluster access |
| Pulumi access token | If using Pulumi Cloud (pulumi login) |
CI uses stack name dev (.github/workflows/build.yml deploy job).
host is the IP or hostname clients use to reach Gateway, Login, and Game. It is injected at deploy time, not stored in develop.json:
conf.host = new pulumi.Config().require('host')Set before every deploy when the public address may change:
cd infra/pulumi
pulumi stack select dev
pulumi config set --secret host "<YOUR_EXTERNAL_IP_OR_HOSTNAME>"tools/build.sh sets this from the machine's public IP (curl -s https://ifconfig.me).
| Situation | What to do |
|---|---|
| Clients via public IP | Use that IP, or build.sh / ifconfig.me
|
| LAN only | Node LAN IP (not 127.0.0.1) |
| DNS name | FQDN clients resolve |
| NAT / wrong auto-detect | Set host manually |
CI passes the secret as REMOTE_IP in the deploy job.
Main static config: worlds, ports, replicas, client metadata.
| Section | What to change |
|---|---|
worlds.<name>.deploy |
true to deploy, false to skip |
worlds.<name>.login.port |
Login NodePort |
worlds.<name>.game.containers |
Game shards and NodePorts |
worlds.<name>.mysql / redis
|
Per-world DB/cache ports |
gateway.port |
Gateway NodePort (default 30000) |
internal.port, marketplace.port, admin-tool.port
|
HTTP NodePorts |
unified-infra |
Shared MySQL, Redis, RabbitMQ |
database.autoMigration |
EF migrations on startup |
admin-tool.security.elevationSecret |
Change for non-dev |
Default: only world-1 and world-2 have "deploy": true. Others are templates with "deploy": false.
Port collisions: node values must be unique within the NodePort range (typically 30000–32767).
Pulumi references ghcr.io/boyism80/fb/<service>:latest. tools/build.sh pushes to the same paths.
For your own registry: retag images and update strings in infra/pulumi/*.js.
Without infra=true on at least one node, MySQL/Redis/RabbitMQ pods stay Pending.
Data paths on infra nodes:
/mnt/fb/mysql/<world>/...
/mnt/fb/redis/<world>/...
MySQL and RabbitMQ defaults are in Pulumi (fb / admin). Change in mysql.js, rabbitmq.js, and connection strings in service modules for production.
- CI stack:
dev - Local:
pulumi stack init devorpulumi stack select dev
On push to release/**, .github/workflows/build.yml:
-
build-linux — builds and pushes images to
ghcr.io/<owner>/fb/* - Per-service jobs — parallel image builds
-
deploy —
pulumi destroythenpulumi upon stackdevwithhostfromREMOTE_IP
Required GitHub secrets:
| Secret | Purpose |
|---|---|
PULUMI_ACCESS_TOKEN |
Pulumi Cloud API |
REMOTE_IP |
Client-facing host value |
Self-hosted runners need Docker, kubeconfig, and GHCR access.
From the repository root on a machine that can push images and run Pulumi:
bash tools/build.sh ReleaseWhat the script does:
-
tools/prune-build-cache.sh— prune old BuildKit cache - Build and push images:
data→build→ gateway, login, game, bot, internal, write-back, admin-tool, log, marketplace pulumi config set --secret host "$EXTERNAL_IP"-
pulumi down -y && pulumi up -y— destroys then recreates the stack
Warning:
pulumi downdeletes all stack-managed resources. For incremental updates, usepulumi upwithoutdown.
Override external IP:
cd infra/pulumi
npm install
pulumi config set --secret host "203.0.113.10"
pulumi up -yWhen images are already in the registry:
cd infra/pulumi
npm install
pulumi stack select dev
pulumi config set --secret host "<YOUR_EXTERNAL_IP_OR_HOSTNAME>"
pulumi up -yUse pulumi refresh if state drifted. Use pulumi destroy to tear down namespace fb resources.
With stock develop.json and world-1 + world-2 deployed:
| Service | NodePort | Notes |
|---|---|---|
| Gateway | 30000 |
Client entry |
| Login (world-1) | 30010 |
World "연" |
| Login (world-2) | 30011 |
World "무휼" |
| Game (world-1) |
30030–30038
|
Per container in game.containers
|
| Internal (HTTP) | 30200 |
Ops / API |
| Admin tool | 30210 |
|
| Marketplace | 30220 |
Game and Login addresses sent to clients use Pulumi host, not in-cluster DNS.
infra/pulumi/index.js orchestrates:
- Namespace
fb - MySQL, Redis, RabbitMQ (parallel)
- Internal, Write-back, Admin Tool, Log, Marketplace
- Gateway, Login, Game
- Bot (commented out in
index.js)
Worlds with "deploy": false are stripped before resources are created.
kubectl -n fb get pods
kubectl -n fb get svc
pulumi stack output
nc -zv <host> 30000
pulumi config get hostWith database.autoMigration: true, Internal applies migrations from infra/db/migrations/ on first start.
| Symptom | Likely cause |
|---|---|
| MySQL/Redis pods Pending | No node with infra=true
|
| Client cannot connect | Wrong host; pulumi config set --secret host + pulumi up
|
ImagePullBackOff |
Registry auth or wrong image name |
| NodePort already allocated | Duplicate port in develop.json
|
| Data loss concern after deploy |
pulumi down removes K8s resources; hostPath data under /mnt/fb/ remains unless deleted on disk |
ifconfig.me wrong IP |
Set host manually |
| Path | Role |
|---|---|
tools/build.sh |
Build all images, set host, Pulumi up |
tools/prune-build-cache.sh |
BuildKit cache cleanup |
infra/pulumi/develop.json |
Worlds, ports, replicas |
infra/pulumi/index.js |
Deploy graph and host injection |
infra/pulumi/*.js |
Per-service Kubernetes manifests |
.github/workflows/build.yml |
CI build + deploy |
server/*/Dockerfile |
Service image definitions |