Cloud Functions Simple(st) HTTP Multi-host Proxy
- 2 minutes read - 353 wordsTweaked yesterday’s solution so that it will randomly select one from several hosts with which it’s configured.
package proxy
import (
"log"
"math/rand"
"net/http"
"net/url"
"os"
"strings"
"time"
)
func robin() {
hostsList := os.Getenv("PROXY_HOST")
if hostsList == "" {
log.Fatal("'PROXY_HOST' environment variable should contain comma-separated list of hosts")
}
// Comma-separated lists of hosts
hosts := strings.Split(hostsList, ",")
urls := make([]*url.URL, len(hosts))
for i, host := range hosts {
var origin = Endpoint{
Host: host,
Port: os.Getenv("PROXY_PORT"),
}
url, err := origin.URL()
if err != nil {
log.Fatal(err)
}
urls[i] = url
}
s := rand.NewSource(time.Now().UnixNano())
q := rand.New(s)
Handler = func(w http.ResponseWriter, r *http.Request) {
// Pick one of the URLs at random
url := urls[q.Int31n(int32(len(urls)))]
log.Printf("[Handler] Forwarding: %s", url.String())
// Forward to it
reverseproxy(url, w, r)
}
}
This requires a minor tweak to the deployment to escape the commas within the PROXY_HOST
string to disambiguate these for gcloud
:
FUNCTION=multi
gcloud functions deploy ${FUNCTION} \
--entry-point=Handler \
--runtime=go111 \
--set-env-vars=^:^PROXY_HOST=${NODEHOSTS}:PROXY_PORT=${NODEPORT} \
--trigger-http \
--project=${PROJECT}
NB The --set-env-vars
flag is followed by ^:^
to denote the command uses :
instead of ,
because the value of ${HOSTS}
contains commas
How to get the list of hosts? Assuming
SERVICE=nginx
NODEPORT=$(\
kubectl get service/${SERVICE} \
--output=jsonpath="{.spec.ports[0].nodePort}")
TAG=$(\
gcloud compute instances list \
--project=${PROJECT} \
--format="value(tags.items)" \
| shuf --head-count=1) \
&& echo ${TAG}
FIREWALL=lb
gcloud compute firewall-rules create ${FIREWALL} \
--project=${PROJECT} \
--network=default \
--action=ALLOW \
--rules=tcp:${NODEPORT} \
--target-tags=microk8s
NODEHOSTS=$(\
gcloud compute instances list \
--project=${PROJECT} \
--filter="tags.items=${TAG}" \
--format="value(networkInterfaces[0].accessConfigs[0].natIP)" \
| tr '\n' ',' \
| sed 's|,*$||g') \
&& echo $HOSTS
NB This is hacky because it picks a tag randomly from the project’s instances’ tags
Once deployed:
ENDPOINT=$(\
gcloud functions describe ${FUNCTION} \
--project=$PROJECT \
--format="value(httpsTrigger.url)")
curl \
--silent \
--write-out "%{http_code}" \
--output /dev/null \
${ENDPOINT}
Attempting to determine how efficient the process is:
gcloud logging read "resource.type=\"cloud_function\" resource.labels.function_name=\"${FUNCTION}\"" \
--project=$PROJECT \
--freshness=1d \
--format=json \
| jq --raw-output .[].textPayload \
| egrep -o "\[Handler\] Forwarding: http://[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}.[0-9]{1,3}:30536" \
| awk '{print $3}' \
| sort \
| uniq -c
103 http://HOST-1:30536
124 http://HOST-2:30536
105 http://HOST-3:30536
So, not bad ;-)