Skip to content

Commit

Permalink
Pinger service for Multiple Cluster Latency Measurement.
Browse files Browse the repository at this point in the history
Context: #301

This creates a simple HTTP endpoint and/or a rate limited UDP echo service
to be able to easily do RTT latency tests from game clients, to multiple
Agones installs.
  • Loading branch information
markmandel committed Dec 6, 2018
1 parent 5e2ad5e commit c10f834
Show file tree
Hide file tree
Showing 15 changed files with 917 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Documentation and usage guides on how to develop and host dedicated game servers
### Guides
- [Integrating the Game Server SDK](sdks)
- [GameServer Health Checking](./docs/health_checking.md)
- [Latency Testing with Multiple Clusters](./docs/ping_service.md)
- [Accessing Agones via the Kubernetes API](./docs/access_api.md)
- [Troubleshooting](./docs/troubleshooting.md)

Expand Down
32 changes: 27 additions & 5 deletions build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ build_tag = agones-build:$(build_version)
build_remote_tag = $(REGISTRY)/$(build_tag)
controller_tag = $(REGISTRY)/agones-controller:$(VERSION)
sidecar_tag = $(REGISTRY)/agones-sdk:$(VERSION)
ping_tag = $(REGISTRY)/agones-ping:$(VERSION)

go_version_flags = -ldflags "-X agones.dev/agones/pkg.Version=$(VERSION)"
DOCKER_RUN ?= docker run --rm $(common_mounts) -e "KUBECONFIG=/root/.kube/$(kubeconfig_file)" $(DOCKER_RUN_ARGS) $(build_tag)
Expand Down Expand Up @@ -111,7 +112,7 @@ endif
build: build-images build-sdks

# build the docker images
build-images: build-controller-image build-agones-sdk-image
build-images: build-controller-image build-agones-sdk-image build-ping-image

# package the current agones helm chart
build-chart: RELEASE_VERSION ?= $(base_version)
Expand Down Expand Up @@ -147,7 +148,7 @@ test: $(ensure-build-image) test-go test-install-yaml
# Run go tests
test-go:
docker run --rm $(common_mounts) $(build_tag) go test -race $(agones_package)/pkg/... \
$(agones_package)/sdks/...
$(agones_package)/sdks/... $(agones_package)/cmd/...

# Runs end-to-end tests on the current configured cluster
# For minikube user the minikube-test-e2e targets
Expand All @@ -168,21 +169,26 @@ test-install-yaml:
diff /tmp/agones-install/install.yaml.sorted /tmp/agones-install/install.current.yaml.sorted

# Push all the images up to $(REGISTRY)
push: push-controller-image push-agones-sdk-image
push: push-controller-image push-agones-sdk-image push-ping-image

# Installs the current development version of Agones into the Kubernetes cluster
install: ALWAYS_PULL_SIDECAR := true
install: IMAGE_PULL_POLICY := "Always"
install: PING_SERVICE_TYPE := "LoadBalancer"
install: $(ensure-build-image) install-custom-pull-secret
$(DOCKER_RUN) \
helm upgrade --install --wait --namespace=agones-system\
--set agones.image.tag=$(VERSION),agones.image.registry=$(REGISTRY),agones.image.controller.pullPolicy=$(IMAGE_PULL_POLICY),agones.image.sdk.alwaysPull=$(ALWAYS_PULL_SIDECAR),agones.image.controller.pullSecret=$(IMAGE_PULL_SECRET) \
--set agones.image.tag=$(VERSION),agones.image.registry=$(REGISTRY) \
--set agones.image.controller.pullPolicy=$(IMAGE_PULL_POLICY),agones.image.sdk.alwaysPull=$(ALWAYS_PULL_SIDECAR) \
--set agones.image.controller.pullSecret=$(IMAGE_PULL_SECRET) \
--set agones.ping.http.serviceType=$(PING_SERVICE_TYPE),agones.ping.udp.serviceType=$(PING_SERVICE_TYPE) \
agones $(mount_path)/install/helm/agones/

uninstall: $(ensure-build-image)
$(DOCKER_RUN) \
helm delete --purge agones


# Build a static binary for the gameserver controller
build-controller-binary: $(ensure-build-image)
docker run --rm -e "CGO_ENABLED=0" $(common_mounts) $(build_tag) go build \
Expand Down Expand Up @@ -219,6 +225,20 @@ build-agones-sdk-binary: $(ensure-build-image)
build-agones-sdk-image: $(ensure-build-image) build-agones-sdk-binary
docker build $(agones_path)/cmd/sdk-server/ --tag=$(sidecar_tag) $(DOCKER_BUILD_ARGS)

# Build a static binary for the ping service
build-ping-binary: $(ensure-build-image)
docker run --rm -e "CGO_ENABLED=0" $(common_mounts) $(build_tag) go build \
-tags $(GO_BUILD_TAGS) -o $(mount_path)/cmd/ping/bin/ping \
-a $(go_version_flags) -installsuffix cgo $(agones_package)/cmd/ping

# Pushes up the ping image
push-ping-image: $(ensure-build-image)
docker push $(ping_tag)

# Build the image for the ping service
build-ping-image: $(ensure-build-image) build-ping-binary
docker build $(agones_path)/cmd/ping/ --tag=$(ping_tag) $(DOCKER_BUILD_ARGS)

# Build the cpp sdk linux archive
build-sdk-cpp: $(ensure-build-image)
docker run --rm $(common_mounts) -w $(mount_path)/sdks/cpp $(build_tag) make build install archive VERSION=$(VERSION)
Expand Down Expand Up @@ -471,11 +491,13 @@ minikube-shell: $(ensure-build-image) minikube-agones-profile
minikube-push: minikube-agones-profile
$(MAKE) minikube-transfer-image TAG=$(sidecar_tag)
$(MAKE) minikube-transfer-image TAG=$(controller_tag)
$(MAKE) minikube-transfer-image TAG=$(ping_tag)

# Installs the current development version of Agones into the Kubernetes cluster.
# Use this instead of `make install`, as it disables PullAlways on the install.yaml
minikube-install: minikube-agones-profile
$(MAKE) install DOCKER_RUN_ARGS="--network=host -v $(minikube_cert_mount)" ALWAYS_PULL_SIDECAR=false IMAGE_PULL_POLICY=IfNotPresent
$(MAKE) install DOCKER_RUN_ARGS="--network=host -v $(minikube_cert_mount)" ALWAYS_PULL_SIDECAR=false \
IMAGE_PULL_POLICY=IfNotPresent PING_SERVICE_TYPE=NodePort

minikube-uninstall: $(ensure-build-image) minikube-agones-profile
$(MAKE) uninstall DOCKER_RUN_ARGS="--network=host -v $(minikube_cert_mount)"
Expand Down
5 changes: 4 additions & 1 deletion build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,15 @@ Run a bash shell with the developer tools (go tooling, kubectl, etc) and source
#### `make godoc`
Run a container with godoc (search index enabled)

#### `make build-agones-controller-image`
#### `make build-controller-image`
Compile the gameserver controller and then build the docker image

#### `make build-agones-sdk-image`
Compile the gameserver sidecar and then build the docker image

#### `make build-ping-image`
Compile the ping binary and then build the docker image

#### `make gen-install`
Generate the `/install/yaml/install.yaml` from the Helm template

Expand Down
2 changes: 1 addition & 1 deletion cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ steps:
dir: "build"
args: ["push-build-image"] # push the build image (which won't do anything if it's already there)
timeout: "1h"
images: ['gcr.io/$PROJECT_ID/agones-controller', 'gcr.io/$PROJECT_ID/agones-sdk']
images: ['gcr.io/$PROJECT_ID/agones-controller', 'gcr.io/$PROJECT_ID/agones-sdk', 'gcr.io/$PROJECT_ID/agones-ping']
options:
machineType: 'N1_HIGHCPU_8'
5 changes: 3 additions & 2 deletions cmd/controller/pprof.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
package main

import (
"github.com/sirupsen/logrus"
"net/http"
_ "net/http/pprof"

"github.com/sirupsen/logrus"
)

func init() {
go func() {
logrus.WithError(http.ListenAndServe(":6060", nil)).Info("Closed pprof server")
}()
logrus.Info("*** PPROF PROFILER STARTED on :6060 ***")
}
}
26 changes: 26 additions & 0 deletions cmd/ping/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2018 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM alpine:3.8

RUN apk --update add ca-certificates && \
adduser -D agones

COPY ./bin/ping /home/agones/ping

RUN chown -R agones /home/agones && \
chmod o+x /home/agones/ping

USER agones
ENTRYPOINT ["/home/agones/ping"]
137 changes: 137 additions & 0 deletions cmd/ping/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// binary for the pinger service for RTT measurement.
package main

import (
"context"
"net/http"
"strings"
"time"

"agones.dev/agones/pkg"
"agones.dev/agones/pkg/util/runtime"
"agones.dev/agones/pkg/util/signals"
"github.com/heptiolabs/healthcheck"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"golang.org/x/time/rate"
)

const (
httpResponseFlag = "http-response"
udpRateLimitFlag = "udp-rate-limit"
)

var (
logger = runtime.NewLoggerWithSource("main")
)

func main() {
ctlConf := parseEnvFlags()
if err := ctlConf.validate(); err != nil {
logger.WithError(err).Fatal("could not create controller from environment or flags")
}

logger.WithField("version", pkg.Version).
WithField("ctlConf", ctlConf).Info("starting ping...")

stop := signals.NewStopChannel()

udpSrv := serveUDP(ctlConf, stop)
defer udpSrv.close()

h := healthcheck.NewHandler()
h.AddLivenessCheck("udp-server", udpSrv.Health)

cancel := serveHTTP(ctlConf, h)
defer cancel()

<-stop
logger.Info("shutting down...")
}

func serveUDP(ctlConf config, stop <-chan struct{}) *udpServer {
s := newUDPServer(ctlConf.UDPRateLimit)
s.run(stop)
return s
}

// serveHTTP starts the HTTP handler, and returns a cancel/shutdown function
func serveHTTP(ctlConf config, h healthcheck.Handler) func() {
// we don't need a health checker, we already have a http endpoint that returns 200
mux := http.NewServeMux()
srv := &http.Server{
Addr: ":8080",
Handler: mux,
}

// add health check as well
mux.HandleFunc("/live", h.LiveEndpoint)

mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write([]byte(ctlConf.HTTPResponse)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
logger.WithError(err).Error("error responding to http request")
}
})

go func() {
logger.Info("starting HTTP Server...")
logger.WithError(srv.ListenAndServe()).Fatal("could not start HTTP server")
}()

return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

if err := srv.Shutdown(ctx); err != nil {
logger.WithError(err).Fatal("could not shut down HTTP server")
}
}
}

// config retains the configuration information
type config struct {
HTTPResponse string
UDPRateLimit rate.Limit
}

// validate returns an error if there is a validation problem
func (c *config) validate() error {
if c.UDPRateLimit < 0 {
return errors.New("UDP Rate limit must be greater that or equal to zero")
}

return nil
}

func parseEnvFlags() config {
viper.SetDefault(httpResponseFlag, "ok")
viper.SetDefault(udpRateLimitFlag, 20)

pflag.String(httpResponseFlag, viper.GetString(httpResponseFlag), "Flag to set text value when a 200 response is returned. Can be useful to identify clusters. Defaults to 'ok' Can also use HTTP_RESPONSE env variable")
pflag.Float64(udpRateLimitFlag, viper.GetFloat64(httpResponseFlag), "Flag to set how many UDP requests can be handled by a single source IP per second. Defaults to 20. Can also use UDP_RATE_LIMIT env variable")

viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
runtime.Must(viper.BindEnv(httpResponseFlag))
runtime.Must(viper.BindEnv(udpRateLimitFlag))

return config{
HTTPResponse: viper.GetString(httpResponseFlag),
UDPRateLimit: rate.Limit(viper.GetFloat64(udpRateLimitFlag)),
}
}
Loading

0 comments on commit c10f834

Please sign in to comment.