diff --git a/.gitignore b/.gitignore index bfb5a44..63e08cc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ vendor/ bin/ .idea +.envrc +.infrastructure \ No newline at end of file diff --git a/cmd/q3/app/server/server.go b/cmd/q3/app/server/server.go index 93437c9..abd0bdb 100644 --- a/cmd/q3/app/server/server.go +++ b/cmd/q3/app/server/server.go @@ -3,7 +3,7 @@ package server import ( "context" "fmt" - "log" + "github.com/criticalstack/quake-kube/pkg/extensions" "net/url" "time" @@ -15,8 +15,6 @@ import ( quakeserver "github.com/criticalstack/quake-kube/internal/quake/server" httputil "github.com/criticalstack/quake-kube/internal/util/net/http" "github.com/criticalstack/quake-kube/public" - - sdk "agones.dev/agones/sdks/go" ) var opts struct { @@ -56,37 +54,6 @@ func NewCommand() *cobra.Command { return err } - if opts.WithAgones { - log.Println("starting Agones SDK client") - s, err := sdk.NewSDK() - if err != nil { - return errors.Wrap(err, "Agones SDK could not be initialized") - } - - if err := s.Ready(); err != nil { - log.Println("failed to make the server Ready") - } - - go func() { - tick := time.Tick(2 * time.Second) - maxAttempts := 0 - for { - if err := s.Health(); err != nil { - if maxAttempts > 5 { - log.Fatalf("Could not send health ping: %v", err) - } - maxAttempts++ - } - select { - case <-ctx.Done(): - log.Print("Stopped health pings") - return - case <-tick: - } - } - }() - } - go func() { s := quakeserver.Server{ Dir: opts.AssetsDir, @@ -94,8 +61,16 @@ func NewCommand() *cobra.Command { ConfigFile: opts.ConfigFile, Addr: opts.ServerAddr, } - if err := s.Start(ctx); err != nil { - panic(err) + + if opts.WithAgones { + agones := &extensions.Agones{} + if err := agones.Start(ctx, &s); err != nil { + panic(err) + } + } else { + if err := s.Start(ctx); err != nil { + panic(err) + } } }() diff --git a/examples/fleet.yaml b/examples/fleet.yaml new file mode 100644 index 0000000..bc9f9b9 --- /dev/null +++ b/examples/fleet.yaml @@ -0,0 +1,129 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: quake3-server-config +data: + config.yaml: | + fragLimit: 25 + timeLimit: 15m + bot: + minPlayers: 3 + game: + motd: "Welcome to Critical Stack" + type: FreeForAll + forceRespawn: false + inactivity: 10m + quadFactor: 3 + weaponRespawn: 3 + server: + hostname: "quakekube" + maxClients: 12 + password: "changeme" + commands: + - addbot sarge 2 + maps: + - name: q3dm7 + type: FreeForAll + timeLimit: 10m + - name: q3dm17 + type: FreeForAll + - name: q3wctf1 + type: CaptureTheFlag + captureLimit: 8 + - name: q3tourney2 + type: Tournament + - name: q3wctf3 + type: CaptureTheFlag + captureLimit: 8 + - name: ztn3tourney1 + type: Tournament +--- +apiVersion: "agones.dev/v1" +kind: Fleet +metadata: + name: octops + labels: + cluster: gke-1.17 + region: us-east-1 +spec: + replicas: 1 + template: + metadata: + labels: + cluster: gke-1.17 + region: us-east-1 + annotations: + octops.io/gameserver-ingress-domain: "mydomain.com" + octops.io/terminate-tls: "true" + octops.io/issuer-tls-name: "selfsigned-issuer" + spec: + players: + # Set initial player capacity if using PlayerTracking Alpha() + initialCapacity: 100 + container: gameserver + ports: + - name: default + containerPort: 8081 + protocol: TCP +# Disable should be false if the flag --with-agones=false +# health: +# disabled: true + template: + spec: + containers: + - name: gameserver + imagePullPolicy: Always + image: octops/quake:latest + command: + - q3 + - server + - --config=/config/config.yaml + - --content-server=http://127.0.0.1:9090 + - --agree-eula + - --client-addr=0.0.0.0:8081 + - --with-agones + ports: + - containerPort: 8081 + readinessProbe: + tcpSocket: + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 5 + resources: + requests: + memory: "1Gi" + cpu: "0.5" + limits: + memory: "2Gi" + cpu: "1" + volumeMounts: + - name: quake3-server-config + mountPath: /config + - name: quake3-content + mountPath: /assets + - name: content-server + imagePullPolicy: Always + image: octops/quake:latest + command: + - q3 + - content + - --seed-content-url=http://content.quakejs.com + ports: + - containerPort: 9090 + resources: + requests: + memory: "1Gi" + cpu: "0.5" + limits: + memory: "2Gi" + cpu: "1" + volumeMounts: + - name: quake3-content + mountPath: /assets + volumes: + - name: quake3-server-config + configMap: + name: quake3-server-config + - name: quake3-content + emptyDir: {} \ No newline at end of file diff --git a/pkg/extensions/agones_extension.go b/pkg/extensions/agones_extension.go new file mode 100644 index 0000000..91efc06 --- /dev/null +++ b/pkg/extensions/agones_extension.go @@ -0,0 +1,92 @@ +package extensions + +import ( + sdk "agones.dev/agones/sdks/go" + "context" + quakenet "github.com/criticalstack/quake-kube/internal/quake/net" + quakeserver "github.com/criticalstack/quake-kube/internal/quake/server" + "github.com/pkg/errors" + "log" + "time" +) + +type Agones struct { + Server *quakeserver.Server + sdk *sdk.SDK +} + +func (a *Agones) Start(ctx context.Context, server *quakeserver.Server) error { + if err := a.InitSdk(); err != nil { + return err + } + a.Server = server + + go a.StartHeathCheck(ctx) + go a.TrackStatus(ctx) + + if err := server.Start(ctx); err != nil { + return errors.Wrap(err, "failed to start server") + } + + return nil +} + +func (a *Agones) InitSdk() error { + log.Println("[Agones] starting Agones SDK client") + s, err := sdk.NewSDK() + if err != nil { + return err + } + + a.sdk = s + + return nil +} + +func (a *Agones) StartHeathCheck(ctx context.Context) { + tick := time.Tick(2 * time.Second) + maxAttempts := 0 + for { + if err := a.sdk.Health(); err != nil { + if maxAttempts > 5 { + log.Fatalf("[Agones] could not send health ping: %v", err) + } + maxAttempts++ + } else { + maxAttempts = 0 + } + + select { + case <-ctx.Done(): + log.Print("[Agones] stopped health pings") + return + case <-tick: + } + } +} + +func (a *Agones) TrackStatus(ctx context.Context) { + tick := time.Tick(5 * time.Second) + for { + if status, err := quakenet.GetStatus(a.Server.Addr); err == nil { + if err != nil { + log.Printf("[Agones] failed to get status from server: %s", err.Error()) + continue + } + for _, p := range status.Players { + log.Printf("[Agones] player: %s", p.Name) + if _, err := a.sdk.Alpha().PlayerConnect(p.Name); err != nil { + log.Printf("[Agones] failed to register player: %s", err.Error()) + } + } + log.Println("[Agones] status checked") + } + + select { + case <-ctx.Done(): + log.Print("[Agones] stopped status checks") + return + case <-tick: + } + } +}