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

add gke-whereami to samples #132

Merged
merged 9 commits into from
Aug 2, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 19 additions & 0 deletions gke-whereami/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:3.8-alpine

#MAINTAINER Alex Mattson "alex.mattson@gmail.com"
# test
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

COPY ./requirements.txt /app/requirements.txt

WORKDIR /app

RUN pip install -r requirements.txt

COPY . /app

RUN addgroup -S appuser && adduser -S -G appuser appuser
USER appuser

ENTRYPOINT [ "python" ]

CMD [ "app.py" ]
1 change: 1 addition & 0 deletions gke-whereami/Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: python app.py
85 changes: 85 additions & 0 deletions gke-whereami/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# gke-whereami

A simple Kubernetes-oriented app for describing the location of the pod serving a request via its attributes (cluster name, cluster region, pod name, namespace, service account, etc). The response payload includes an emoji that is hashed from the pod name, which makes it a little easier for a human to visually identify the pod you're dealing with.

This was originally written for testing & debugging multi-cluster ingress use cases on GKE (now Ingress for Anthos).

### Setup

Create a GKE cluster
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

First define your environment variables (substituting where #needed#):

```
export PROJECT_ID=#YOUR_PROJECT_ID#

export COMPUTE_REGION=#YOUR_COMPUTE_REGION#

export CLUSTER_NAME=whereami
```

Now create your resources:

```
gcloud beta container clusters create $CLUSTER_NAME \
--enable-ip-alias \
--enable-stackdriver-kubernetes \
--region=$COMPUTE_REGION \
--num-nodes=1 \
theemadnes marked this conversation as resolved.
Show resolved Hide resolved
--release-channel=regular

gcloud container clusters get-credentials $CLUSTER_NAME --region $COMPUTE_REGION
```

Deploy the service/pods:

```kubectl apply -k k8s```

*or*

```kustomize build k8s | kubectl apply -f -```
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

Get the service endpoint:
theemadnes marked this conversation as resolved.
Show resolved Hide resolved
```
WHEREAMI_ENDPOINT=$(kubectl get svc | grep -v EXTERNAL-IP | awk '{ print $4}')
theemadnes marked this conversation as resolved.
Show resolved Hide resolved
```

Wrap things up by `curl`ing the `EXTERNAL-IP` of the service.

```curl $WHEREAMI_ENDPOINT -H "Host: hello"```
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

Result:

```{"cluster_name":"cluster-1","host_header":"34.66.118.115","id_string":"frontend","node_name":"gke-cluster-1-default-pool-c91b5644-v8kg.c.alexmattson-scratch.internal","pod_ip":"10.4.2.15","pod_name":"whereami-c657c68f5-9jtkw","pod_name_emoji":"👇","pod_namespace":"default","pod_service_account":"whereami-ksa","project_id":"alexmattson-scratch","timestamp":"2020-07-29T19:08:31","zone":"us-central1-c"}```


#### using gke-whereami to call downstream services
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

`gke-whereami` has an optional flag within its configmap that will cause it to attempt to call another backend service within your GKE cluster (for example, a different, non-public instance of itself). This is helpful for demonstrating a public microservice call to a non-public microservice, and then including the responses of both microservices in the payload delivered back to the user.

*NOTE:* this backend call assumes the downstream service is returning JSON.
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

First, build the "backend" instance of `gke-whereami`:

```kustomize build k8s-backend-overlay-example | kubectl apply -f -```

*or*

```kubectl apply -k k8s-backend-overlay-example```
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

Once that service is up and running, modify `k8s/configmap.yaml`'s `BACKEND_ENABLED` to `"True"`. You will have to redeploy the pods in the whereami service as they will not automatically be recreated when you update the configmap.
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

The (*slightly* busy-looking) result should look like this:
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

```{"backend_result":{"cluster_name":"cluster-1","host_header":"whereami-backend","id_string":"backend","node_name":"gke-cluster-1-default-pool-c91b5644-q7w5.c.alexmattson-scratch.internal","pod_ip":"10.4.0.10","pod_name":"whereami-backend-966547575-nk9df","pod_name_emoji":"📔","pod_namespace":"default","pod_service_account":"whereami-ksa-backend","project_id":"alexmattson-scratch","timestamp":"2020-07-29T19:32:15","zone":"us-central1-c"},"cluster_name":"cluster-1","host_header":"34.72.90.134","id_string":"frontend","node_name":"gke-cluster-1-default-pool-c91b5644-v8kg.c.alexmattson-scratch.internal","pod_ip":"10.4.2.16","pod_name":"whereami-c657c68f5-r65s5","pod_name_emoji":"🌋","pod_namespace":"default","pod_service_account":"whereami-ksa","project_id":"alexmattson-scratch","timestamp":"2020-07-29T19:32:15","zone":"us-central1-c"}```
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

If you wish to call a different backend service, modify `k8s/configmap.yaml`'s `BACKEND_SERVICE` to some other service name.


#### Notes

The operating port of the pod has been switched from `5000` to `8080` to work easily with the managed version of Cloud Run.

If you'd like to build & publish via Google's buildpacks, something like this should do the trick (leveraging the local `Procfile`:
theemadnes marked this conversation as resolved.
Show resolved Hide resolved

```pack build --builder gcr.io/buildpacks/builder:v1 --publish gcr.io/${PROJECT_ID}/whereami```
150 changes: 150 additions & 0 deletions gke-whereami/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import requests
from flask import Flask, request, Response, jsonify
import logging
import json
import sys
import socket
import os
from datetime import datetime
import emoji
import random
from flask_cors import CORS

METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/'
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False # otherwise our emojis get hosed
CORS(app) # enable CORS

# set up emoji list
emoji_list = list(emoji.unicode_codes.UNICODE_EMOJI.keys())


@app.route('/')
def home():

# get project ID
try:
theemadnes marked this conversation as resolved.
Show resolved Hide resolved
r = requests.get(METADATA_URL +
'project/project-id',
headers=METADATA_HEADERS)
if r.ok:
project_id = r.text
else:
project_id = None
except:

project_id = None

# get zone
try:
r = requests.get(METADATA_URL +
'instance/zone',
headers=METADATA_HEADERS)
if r.ok:
zone = str(r.text.split("/")[3])
else:
zone = None
except:

zone = None

# get gke node_name
try:
r = requests.get(METADATA_URL +
'instance/hostname',
headers=METADATA_HEADERS)
if r.ok:
node_name = str(r.text)
else:
node_name = None
except:

node_name = None

# get cluster
try:
r = requests.get(METADATA_URL +
'instance/attributes/cluster-name',
headers=METADATA_HEADERS)
if r.ok:
cluster_name = str(r.text)
else:
cluster_name = None
except:

cluster_name = None

# get host header
try:
host_header = request.headers.get('host')
except:
host_header = None

# get pod name
pod_name = socket.gethostname()

# get datetime
timestamp = datetime.now().replace(microsecond=0).isoformat()

# get k8s namespace, pod ip, and pod service account
pod_namespace = os.getenv('POD_NAMESPACE')
pod_ip = os.getenv('POD_IP')
pod_service_account = os.getenv('POD_SERVICE_ACCOUNT')

# get the whereami ID_STRING envvar
id_string = os.getenv('ID_STRING')

payload = {}
payload['cluster_name'] = cluster_name
payload['host_header'] = host_header
payload['node_name'] = node_name
payload['pod_ip'] = pod_ip
payload['pod_name'] = pod_name
payload['pod_name_emoji'] = emoji_list[hash(pod_name) % len(emoji_list)]
theemadnes marked this conversation as resolved.
Show resolved Hide resolved
payload['pod_namespace'] = pod_namespace
payload['pod_service_account'] = pod_service_account
payload['project_id'] = project_id
payload['timestamp'] = timestamp
payload['id_string'] = id_string
payload['zone'] = zone

# should we call a backend service?
call_backend = os.getenv('BACKEND_ENABLED')

if call_backend == 'True':

backend_service = os.getenv('BACKEND_SERVICE')

try:
r = requests.get('http://' + backend_service)
if r.ok:
backend_result = r.json()
else:
backend_result = None
except:

print(sys.exc_info()[0])
backend_result = None

payload['backend_result'] = backend_result

return jsonify(payload)


@app.route('/healthz')
def i_am_healthy():
return ('OK')


if __name__ == '__main__':
out_hdlr = logging.StreamHandler(sys.stdout)
fmt = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
out_hdlr.setFormatter(fmt)
out_hdlr.setLevel(logging.INFO)
logging.getLogger().addHandler(out_hdlr)
logging.getLogger().setLevel(logging.INFO)
app.logger.handlers = []
app.logger.propagate = True
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))
theemadnes marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions gke-whereami/k8s-backend-overlay-example/cm-flag.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: whereami-configmap
data:
BACKEND_ENABLED: "False" # assuming you don't want a chain of backend calls
ID_STRING: "backend"
8 changes: 8 additions & 0 deletions gke-whereami/k8s-backend-overlay-example/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
nameSuffix: "-backend"
commonLabels:
app: whereami-backend
bases:
- ../k8s
patches:
- cm-flag.yaml
- service-type.yaml
6 changes: 6 additions & 0 deletions gke-whereami/k8s-backend-overlay-example/service-type.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: "v1"
kind: "Service"
metadata:
name: "whereami"
spec:
type: ClusterIP
8 changes: 8 additions & 0 deletions gke-whereami/k8s/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: whereami-configmap
data:
BACKEND_ENABLED: "False" # flag to enable backend service call "False" || "True"
BACKEND_SERVICE: "whereami-backend" # substitute with corresponding service name - this example assumes both services are in the same namespace
ID_STRING: "frontend" # arbitrary string that gets returned in payload - can be used to track which services you're interacting with
65 changes: 65 additions & 0 deletions gke-whereami/k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: whereami
spec:
replicas: 3
selector:
matchLabels:
app: whereami
template:
metadata:
labels:
app: whereami
version: v1
spec:
serviceAccountName: whereami-ksa
containers:
- name: whereami
image: gcr.io/alexmattson-scratch/whereami@sha256:b592d113dff6ca1584d8f8d5a3aa2aca437e1764b2c86db061d9a7eaa3e81815
theemadnes marked this conversation as resolved.
Show resolved Hide resolved
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 15
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: BACKEND_ENABLED
valueFrom:
configMapKeyRef:
name: whereami-configmap
key: BACKEND_ENABLED
- name: BACKEND_SERVICE
valueFrom:
configMapKeyRef:
name: whereami-configmap
key: BACKEND_SERVICE
- name: ID_STRING
valueFrom:
configMapKeyRef:
name: whereami-configmap
key: ID_STRING
4 changes: 4 additions & 0 deletions gke-whereami/k8s/ksa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: whereami-ksa
5 changes: 5 additions & 0 deletions gke-whereami/k8s/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resources:
- ksa.yaml
- deployment.yaml
- service.yaml
- configmap.yaml
13 changes: 13 additions & 0 deletions gke-whereami/k8s/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: "v1"
kind: "Service"
metadata:
name: "whereami"
#namespace: "whereami"
spec:
ports:
- port: 80
targetPort: 8080
name: http # adding for Istio
selector:
app: "whereami"
type: "LoadBalancer"
4 changes: 4 additions & 0 deletions gke-whereami/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flask
requests
emoji
flask-cors