Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GameServer API #91

Merged
merged 1 commit into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
go-version: 1.16.5
- name: build Docker images
run: make builddockerlocal
- name: GameServer API service unit tests
run: cd tools/gameserverapi && GIN_MODE=release go test
- name: initcontainer unit tests
run: cd initcontainer && go test
- name: sidecar unit tests
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ When you allocate a GameServer, thundernetes needs to do two things:
There are two ways we can accomplish the second step:

- Have a [Kubernetes watch](https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes) from the sidecar to the Kubernetes' API server which will be notified when the GameServer is updated. This approach works well from a security perspective, since you can configure RBAC rules for the GameServer Pod.
- Have the controller's API service (which accepts the allocation requests) forward the allocation request to the sidecar. This is done via having the sidecar expose its HTTP server inside the cluster. Of course, this assumes that we trust the processes running on the containers in the cluster.
- Have the controller's allocation API service forward the allocation request to the sidecar. This is done via having the sidecar expose its HTTP server inside the cluster. Of course, this assumes that we trust the processes running on the containers in the cluster.

For communicating with the sidecar, we eventually picked the first approach. The second approach was used initially but was abandoned due to security concerns.

Expand Down
6 changes: 3 additions & 3 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ IMG=ghcr.io/playfab/thundernetes-operator:${TAG} \
make -C operator install deploy
```

Note that this will install thundernetes without any security for the api service. If you want to enable security for the api service, you can should provide a certificate and key for the api service.
Note that this will install thundernetes without any security for the allocation API service. If you want to enable security for the allocation API service, you can should provide a certificate and key for the allocation API service.

You can use OpenSSL to create a self-signed certificate and key (not recommended for production).

Expand All @@ -117,7 +117,7 @@ kubectl create namespace thundernetes-system
kubectl create secret tls tls-secret -n thundernetes-system --cert=/home/dgkanatsios/public.pem --key=/home/dgkanatsios/private.pem
```

Then, you need to install the operator enabling TLS authentication for the API service.
Then, you need to install the operator enabling TLS authentication for the allocation API service.

```bash
export TAG=0.0.1.2
Expand Down Expand Up @@ -177,7 +177,7 @@ thundernetes-controller-manager LoadBalancer 10.0.62.144 20.83.72.255 50

The External-Ip field is the Public IP of the LoadBalancer that we can use to call the allocation API.

If you have configured your API service with no security:
If you have configured your allocation API service with no security:

```bash
IP=...
Expand Down
8 changes: 4 additions & 4 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ You can use a variety of options to run Kubernetes locally, either [kind](https:
* Install kind using the instructions [here](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)
* Create a "kind-config.yaml" file to configure the cluster, using the contents listed below.

Special attention is needed on the ports you will forward (the "containerPort" listed below). First of all, you need to expose port 5000 since this is the port used by the thundernetes API service. You will use this port to do game server allocations.
Special attention is needed on the ports you will forward (the "containerPort" listed below). First of all, you need to expose port 5000 since this is the port used by the thundernetes GameServer allocation API service. You will use this port to do game server allocations.
After that, you can optionally specify ports to test your game server by sending traffic to it. Thundernetes dynamically allocates ports for your game server, ranging from 10000 to 50000. Port assignment from this range is sequential. For example, if you use two game servers with each one having a single port, the first game server port will be mapped to port 10000 and the second will be mapped to port 10001. Be aware that if you scale down your GameServerBuild and scale it up again, you probably will not get the same port. Consequently, pay special attention to the ports that you will use in your kind configuration.

Save this content to a file called `kind-config.yaml`.
Expand Down Expand Up @@ -109,7 +109,7 @@ Read the following section if you want to have TLS based authentication for the

### Install thundernetes with TLS authentication

You need to create/configure the certificate that will be used to protect the thundernetes API service.
You need to create/configure the certificate that will be used to protect the allocation API service.

For testing purposes, you can generate a self-signed certificate and use it to secure the allocation API service. You can use OpenSSL to create a self-signed certificate and key (of course, this scenario is not recommended for production).

Expand All @@ -125,7 +125,7 @@ kubectl create namespace thundernetes-system
kubectl create secret tls tls-secret -n thundernetes-system --cert=/path/to/public.pem --key=/path/to/private.pem
```

Then, you can run the following script to install thundernetes with TLS security for the API service.
Then, you can run the following script to install thundernetes with TLS security for the allocation API service.

```bash
kubectl apply -f https://raw.githubusercontent.com/PlayFab/thundernetes/master/installfiles/operator_with_security.yaml
Expand Down Expand Up @@ -177,7 +177,7 @@ Allocating a GameServer will transition its state from "StandingBy" to "Active"
If you are running on Azure Kubernetes Service, you can use the following command to allocate a game server:

```bash
# grab the IP of the external load balancer that is used to route traffic to the thundernetes API service
# grab the IP of the external load balancer that is used to route traffic to the allocation API service
IP=$(kubectl get svc -n thundernetes-system thundernetes-controller-manager -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# do the allocation call. Make sure the buildID is the same as the one that you created your Build with
curl -H 'Content-Type: application/json' -d '{"buildID":"85ffe8da-c82f-4035-86c5-9d2b5f42d6f6","sessionID":"ac1b7082-d811-47a7-89ae-fe1a9c48a6da"}' http://${IP}:5000/api/v1/allocate
Expand Down
2 changes: 1 addition & 1 deletion e2e/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func main() {
handleError(err)
}

// get certificates to authenticate to operator API server
// get certificates to authenticate to operator allocation API service
certFile := os.Getenv("TLS_PUBLIC")
keyFile := os.Getenv("TLS_PRIVATE")
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
Expand Down
2 changes: 1 addition & 1 deletion e2e/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fi
# trap finish EXIT

# certificate generation for the TLS security on the allocation API server
echo "-----Creating temp certificates for TLS security on the operator's API server-----"
echo "-----Creating temp certificates for TLS security on the operator's allocation API service-----"
export TLS_PRIVATE=/tmp/${RANDOM}.pem
export TLS_PUBLIC=/tmp/${RANDOM}.pem
openssl req -x509 -newkey rsa:4096 -nodes -keyout ${TLS_PRIVATE} -out ${TLS_PUBLIC} -days 365 -subj '/CN=localhost'
Expand Down
2 changes: 1 addition & 1 deletion operator/controllers/gameserver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type GameServerReconciler struct {
GetPublicIpForNodeProvider func(ctx context.Context, r client.Reader, nodeName string) (string, error) // we abstract this for testing purposes
}

// we request secret RBAC access here so they can be potentially used by the API service (for GameServer allocations)
// we request secret RBAC access here so they can be potentially used by the allocation API service (for GameServer allocations)

//+kubebuilder:rbac:groups=mps.playfab.com,resources=gameservers,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=mps.playfab.com,resources=gameserverdetails,verbs=create
Expand Down
4 changes: 2 additions & 2 deletions operator/http/allocate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var (
gsName string = "testgs"
)

var _ = Describe("API server tests", func() {
var _ = Describe("allocation API service tests", func() {
It("empty body should return error", func() {
req := httptest.NewRequest(http.MethodPost, "/api/v1/allocate", nil)
w := httptest.NewRecorder()
Expand Down Expand Up @@ -158,7 +158,7 @@ func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecsWithDefaultAndCustomReporters(t,
"API Server Suite",
"Allocation API Service Suite",
[]Reporter{printer.NewlineReporter{}})
}

Expand Down
12 changes: 6 additions & 6 deletions operator/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ func (s *ApiServer) setupIndexers(mgr ctrl.Manager) error {
return nil
}

// NeedLeaderElection returns false since we need the API server to run all on controller Pods
// NeedLeaderElection returns false since we need the allocation API service to run all on controller Pods
func (s *ApiServer) NeedLeaderElection() bool {
return false
}

// Start starts the HTTP(S) API Server
// Start starts the HTTP(S) allocation API service
// if user has provided public/private cert details, it will create a TLS-auth HTTPS server
// otherwise it will create a HTTP server with no auth
func (s *ApiServer) Start(ctx context.Context) error {
Expand All @@ -95,7 +95,7 @@ func (s *ApiServer) Start(ctx context.Context) error {
scheme: s.scheme,
})

log.Info("serving API server", "addr", addr, "port", listeningPort)
log.Info("serving allocation API service", "addr", addr, "port", listeningPort)

srv := &http.Server{
Addr: addr,
Expand All @@ -105,7 +105,7 @@ func (s *ApiServer) Start(ctx context.Context) error {
done := make(chan struct{})
go func() {
<-ctx.Done()
log.Info("shutting down API server")
log.Info("shutting down allocation API service")

// TODO: use a context with reasonable timeout
if err := srv.Shutdown(context.Background()); err != nil {
Expand All @@ -116,12 +116,12 @@ func (s *ApiServer) Start(ctx context.Context) error {
}()

if crtBytes != nil && keyBytes != nil {
log.Info("starting TLS enabled API server")
log.Info("starting TLS enabled allocation API service")
if err := customListenAndServeTLS(srv, crtBytes, keyBytes); err != nil && err != http.ErrServerClosed {
return err
}
} else {
log.Info("starting insecure API server")
log.Info("starting insecure allocation API service")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion operator/http/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type RequestMultiplayerServerResponse struct {
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away. We use if for TLS-auth enabled API server
// go away. We use if for TLS-auth enabled allocation API service
type tcpKeepAliveListener struct {
*net.TCPListener
}
Expand Down
2 changes: 1 addition & 1 deletion sidecar-go/sidecarmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (sm *sidecarManager) gameServerUpdated(oldObj, newObj interface{}) {
sessionDetailsMutex.Unlock()

// closing the channel will cause the informer to stop
// we don't expect any more state changes so we close the watch to decrease the pressue on Kubernetes API server
// we don't expect any more state changes so we close the watch to decrease the pressure on Kubernetes API server
close(watchStopper)
}
}
Expand Down
2 changes: 1 addition & 1 deletion sidecar-go/sidecarmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const (
testGameServerNamespace = "default"
)

var _ = Describe("API server tests", func() {
var _ = Describe("allocation API service tests", func() {
It("heartbeat with empty body should return error", func() {
req := httptest.NewRequest(http.MethodPost, "/v1/sessionHosts/sessionHostID", nil)
w := httptest.NewRecorder()
Expand Down
4 changes: 4 additions & 0 deletions thundernetes.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"name": "sidecar-go",
"path": "sidecar-go"
},
{
"name": "gameserverapi",
"path": "tools/gameserverapi"
},
{
"name": "thundernetes",
"path": "."
Expand Down
28 changes: 28 additions & 0 deletions tools/gameserverapi/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# build stage
FROM golang:1.16 as builder

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod ./
COPY go.sum ./
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download

# Copy the go source
COPY *.go ./

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o thundernetes-gameserver-api-service

# Use distroless as minimal base image to package the sidecar binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
ENV GIN_MODE release
WORKDIR /
COPY --from=builder /workspace/thundernetes-gameserver-api-service .
USER 65532:65532

EXPOSE 8080

ENTRYPOINT ["/thundernetes-gameserver-api-service"]
3 changes: 3 additions & 0 deletions tools/gameserverapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# GameServer API service (work in progress)

GameServer API service is a RESTful API service that facilitates access to GameServerBuild/GameServer/GameServerDetail Custom Resources on your Kubernetes clusters.
16 changes: 16 additions & 0 deletions tools/gameserverapi/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/playfab/thundernetes/tools/gameserverapi

go 1.16

require (
github.com/gin-gonic/gin v1.7.4
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.13.0
github.com/playfab/thundernetes/operator v0.0.0-20211119172533-f8e29f6b7145
github.com/sirupsen/logrus v1.8.1
k8s.io/apimachinery v0.21.3
k8s.io/client-go v0.21.3
sigs.k8s.io/controller-runtime v0.9.2
)

replace github.com/playfab/thundernetes/operator => ../../operator
Loading