Skip to content

Commit

Permalink
Adding an allocator service that acts as a reverse proxy.
Browse files Browse the repository at this point in the history
  • Loading branch information
pooneh-m committed May 20, 2019
1 parent c0723c2 commit a788e0d
Show file tree
Hide file tree
Showing 17 changed files with 981 additions and 12 deletions.
22 changes: 20 additions & 2 deletions build/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ build_tag = agones-build:$(build_version)
controller_tag = $(REGISTRY)/agones-controller:$(VERSION)
sidecar_tag = $(REGISTRY)/agones-sdk:$(VERSION)
ping_tag = $(REGISTRY)/agones-ping:$(VERSION)
allocator_tag = $(REGISTRY)/agones-allocator:$(VERSION)

gomod_on = GO111MODULE=on

Expand Down Expand Up @@ -197,7 +198,7 @@ include ./includes/sdk.mk
build: build-images build-sdks

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

# package the current agones helm chart
build-chart: RELEASE_VERSION ?= $(base_version)
Expand Down Expand Up @@ -262,12 +263,13 @@ 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-ping-image
push: push-controller-image push-agones-sdk-image push-ping-image push-allocator-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: ALLOCATOR_SERVICE_TYPE := "LoadBalancer"
install: CRD_CLEANUP := true
install: $(ensure-build-image) install-custom-pull-secret
$(DOCKER_RUN) \
Expand All @@ -276,6 +278,7 @@ install: $(ensure-build-image) install-custom-pull-secret
--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) \
--set agones.allocator.http.serviceType=$(ALLOCATOR_SERVICE_TYPE) \
--set agones.crds.cleanupOnDelete=$(CRD_CLEANUP) \
agones $(mount_path)/install/helm/agones/

Expand Down Expand Up @@ -351,6 +354,20 @@ push-ping-image: $(ensure-build-image)
build-ping-image: $(ensure-build-image) build-ping-binary build-licenses build-required-src-dist
docker build $(agones_path)/cmd/ping/ --tag=$(ping_tag) $(DOCKER_BUILD_ARGS)

# Build a static binary for the allocator service
build-allocator-binary: $(ensure-build-image)
$(GO_BUILD_LINUX_AMD64) \
-tags $(GO_BUILD_TAGS) -o $(go_build_base_path)/cmd/allocator/bin/allocator \
$(go_rebuild_flags) $(go_version_flags) -installsuffix cgo $(agones_package)/cmd/allocator

# Pushes up the allocator image
push-allocator-image: $(ensure-build-image)
docker push $(allocator_tag)

# Build the image for the allocator service
build-allocator-image: $(ensure-build-image) build-allocator-binary build-licenses build-required-src-dist
docker build $(agones_path)/cmd/allocator/ --tag=$(allocator_tag) $(DOCKER_BUILD_ARGS)

# push the gameservers sidecar image
push-agones-sdk-image: $(ensure-build-image)
docker push $(sidecar_tag)
Expand All @@ -360,6 +377,7 @@ gen-install: $(ensure-build-image)
docker run --rm $(common_mounts) $(DOCKER_RUN_ARGS) $(build_tag) bash -c \
'helm template --name=agones-manual --namespace agones-system $(mount_path)/install/helm/agones \
--set agones.controller.generateTLS=false \
--set agones.allocator.generateTLS=false \
--set agones.crds.cleanupOnDelete=false \
> $(mount_path)/install/yaml/install.yaml'

Expand Down
2 changes: 1 addition & 1 deletion build/build-required-src-dist.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ tar -zcf ${TMP_DEPS_SRC} -C ${SRC_ROOT}/vendor/ \
github.com/hashicorp/golang-lru \
github.com/hashicorp/hcl

for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ; do
for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ${SRC_ROOT}/cmd/allocator/bin/ ; do
mkdir -p ${ddir}
cp ${TMP_DEPS_SRC} ${ddir}
done
Expand Down
2 changes: 1 addition & 1 deletion build/extract-licenses.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ while read -r entry; do
append_license ${LIBRARY} ${entry}
done <<< "$(find vendor/ -regextype posix-extended -iregex '.*LICENSE(\.txt)?')"

for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ; do
for ddir in ${SRC_ROOT}/cmd/controller/bin/ ${SRC_ROOT}/cmd/ping/bin/ ${SRC_ROOT}/cmd/sdk-server/bin/ ${SRC_ROOT}/cmd/allocator/bin/ ; do
mkdir -p ${ddir}
cp ${TMP_LICENSES} ${ddir}
done
Expand Down
7 changes: 7 additions & 0 deletions build/helm.tf
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ variable "image_pull_secret" {
variable "ping_service_type" {
default = "LoadBalancer"
}
variable "allocator_service_type" {
default = "LoadBalancer"
}

variable "values_file" {
default = "../install/helm/agones/values.yaml"
Expand Down Expand Up @@ -177,6 +180,10 @@ resource "helm_release" "agones" {
name = "agones.ping.udp.serviceType"
value = "${var.ping_service_type}"
}
set {
name = " agones.allocator.http.serviceType"
value = "${var.allocator_service_type}"
}
version = "${var.agones_version}"
namespace = "agones-system"
}
Expand Down
24 changes: 24 additions & 0 deletions cmd/allocator/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2019 Google LLC 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 --chown=agones:root ./bin/allocator /home/agones/allocator
COPY --chown=agones:root ./bin/LICENSES ./bin/dependencies-src.tgz /home/agones/

USER agones
ENTRYPOINT ["/home/agones/allocator"]
169 changes: 169 additions & 0 deletions cmd/allocator/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2019 Google LLC 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.
package main

import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"

allocationv1alpha1 "agones.dev/agones/pkg/apis/allocation/v1alpha1"
"agones.dev/agones/pkg/client/clientset/versioned"
"agones.dev/agones/pkg/util/runtime"
k8serror "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/rest"
)

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

const (
certDir = "/home/allocator/client-ca/"
tlsDir = "/home/allocator/tls/"
port = "8443"
)

// A handler for the web server
type handler func(w http.ResponseWriter, r *http.Request)

func main() {
agonesClient, err := getAgonesClient()
if err != nil {
logger.WithError(err).Fatal("could not create agones client")
}

h := httpHandler{
agonesClient: agonesClient,
namespace: os.Getenv("NAMESPACE"),
}

// TODO: add liveness probe
http.HandleFunc("/v1alpha1/gameserverallocation", h.postOnly(h.allocateHandler))

caCertPool, err := getCACertPool(certDir)
if err != nil {
logger.WithError(err).Fatal("could not get CA certs")
}

cfg := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: caCertPool,
}
srv := &http.Server{
Addr: ":" + port,
TLSConfig: cfg,
}

err = srv.ListenAndServeTLS(tlsDir+"tls.crt", tlsDir+"tls.key")
logger.WithError(err).Fatal("allocation service crashed")
}

// Set up our client which we will use to call the API
func getAgonesClient() (*versioned.Clientset, error) {
// Create the in-cluster config
config, err := rest.InClusterConfig()
if err != nil {
return nil, errors.New("Could not create in cluster config")
}

// Access to the Agones resources through the Agones Clientset
agonesClient, err := versioned.NewForConfig(config)
if err != nil {
return nil, errors.New("Could not create the agones api clientset")
}

return agonesClient, nil
}

func getCACertPool(path string) (*x509.CertPool, error) {
// Add all certificates under client-certs path because there could be multiple clusters
// and all client certs should be added.
caCertPool := x509.NewCertPool()
filesInfo, err := ioutil.ReadDir(path)
if err != nil {
return nil, fmt.Errorf("error reading certs from dir %s: %s", path, err.Error())
}

for _, file := range filesInfo {
if strings.HasSuffix(file.Name(), ".crt") || strings.HasSuffix(file.Name(), ".pem") {
certFile := filepath.Join(path, file.Name())
caCert, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, fmt.Errorf("ca cert is not readable or missing: %s", err.Error())
}
if !caCertPool.AppendCertsFromPEM(caCert) {
return nil, fmt.Errorf("client cert %s cannot be installed", certFile)
}
logger.Infof("client cert %s is installed", certFile)
}
}

return caCertPool, nil
}

// Limit verbs the web server handles
func (h *httpHandler) postOnly(in handler) handler {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
in(w, r)
return
}
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}

type httpHandler struct {
agonesClient versioned.Interface
namespace string
}

func (h *httpHandler) allocateHandler(w http.ResponseWriter, r *http.Request) {
gsa := allocationv1alpha1.GameServerAllocation{}
if err := json.NewDecoder(r.Body).Decode(&gsa); err != nil {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}

allocation := h.agonesClient.AllocationV1alpha1().GameServerAllocations(h.namespace)
allocatedGsa, err := allocation.Create(&gsa)
if err != nil {
http.Error(w, err.Error(), httpCode(err))
logger.Debug(err)
return
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(allocatedGsa)
if err != nil {
http.Error(w, "internal server error", http.StatusInternalServerError)
logger.Error(err)
return
}
}

func httpCode(err error) int {
code := http.StatusInternalServerError
switch t := err.(type) {
case k8serror.APIStatus:
code = int(t.Status().Code)
}
return code
}
Loading

0 comments on commit a788e0d

Please sign in to comment.