Single Page App on GCP

Architectural Overview

This is a tutorial for how to get up and running with your own Single Page application and API on GCP quickly. Single Page Apps are web apps that load a single HTML page and dynamically update that page as the user interacts with the application. The browser based applicaiton makes API calls in JavaScript back to the API. This tuotrial assumes that the API is using a different domain and as a result uses Cross Origin Resource Sharing.

From a high level, the static web pages rely on Cloud Storage and Cloud CDN (provided by Firebase Hosting), while the API is hosted in Kubernetes.

Architecture

Deploying the API

API Architecture

The underlying API is deployed to Kubernetes Engine and load balanced using a Cloud Load Balancing which is added using a Kubernetes ingress object. In this case implemented using Elasticsearch with CORS enabled (installable via a helm chart). However, because the API will be exposed directly to the single page web application (SPA) via the public internet it requires additional security. Cloud Endpoints is used to validate authentication to the API inside the Kubernetes cluster and could be used to throttle requests if necessary (though this is out of scope for this tutorial). Identity Aware Proxy is used for authentication and authorization of the user. Identity Aware Proxy is configured using Cloud Identity and Acccess Management.

API Architecture

Kubernetes Environment

The Kubernetes environment is enhanced for this tutuorial in a couple of ways outside the normal course of deploying the required components. First, you'll install helm which will make it easier to deploy many of the Kubernetes components for this tutorial. Using helm you'll add a custom resource definiton and controller for deploying the Cloud Endpoints and associating it with ingress objects. Finally, you'll install a suite of kubectl plugis to help with some of the commands.

button

Task 0 - Setup environment

  1. Set the project, replace YOUR_PROJECT with your project ID:
gcloud config set project YOUR_PROJECT
  1. Install kubectl plugins:
mkdir -p ~/.kube/plugins
git clone https://github.com/danisla/kubefunc.git ~/.kube/plugins/kubefunc
  1. Create GKE cluster:
VERSION=$(gcloud container get-server-config --zone us-central1-f --format='value(validMasterVersions[0])')
gcloud container clusters create dev --zone=us-central1-c --cluster-version=${VERSION} --scopes=cloud-platform
  1. Install helm
kubectl plugin install-helm
  1. Install Cloud Endpoints Controller
kubectl plugin install-cloud-endpoints-controller

Task 1 - Deploy Elastic Search

helm install --name my-release incubator/elasticsearch -f api/es-values.yml

Task 2 - Generate self-signed certificate with cert-manager

  1. Install the cert-manager chart and clusterissuer using the kubectl plugin:
kubectl plugin install-cert-manager
  1. Generate CA key and cert:
PROJECT=$(gcloud config get-value project)
COMMON_NAME="spa-api.endpoints.${PROJECT}.cloud.goog"

openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=${COMMON_NAME}" -days 3650 -reqexts v3_req -extensions v3_ca -out ca.crt

kubectl create secret tls ca-key-pair --cert=ca.crt --key=ca.key
  1. Create the certificate:
PROJECT=$(gcloud config get-value project)
COMMON_NAME="spa-api.endpoints.${PROJECT}.cloud.goog"

cat <<EOF | kubectl apply -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: spa-api-ingress
spec:
  secretName: spa-api-ingress-tls
  issuerRef:
    name: ca-issuer
    # We can reference ClusterIssuers by changing the kind here.
    # The default value is Issuer (i.e. a locally namespaced Issuer)
    kind: Issuer
  commonName: ${COMMON_NAME}
  dnsNames:
  - ${COMMON_NAME}
EOF
  1. Wait for the certificate:
(until kubectl get secret iap-tutorial-ingress-tls 2>/dev/null; do echo "Waiting for certificate..." ; sleep 2; done)

Task 3 - Configure OAuth consent screen

  1. Go to the OAuth consent screen.
  2. Under Email address, select the email address you want to display as a public contact. This must be your email address, or a Google Group you own.
  3. Enter the Product name you would like to display.
  4. Add any optional details you’d like.
  5. Click Save.

Task 4 - Set up IAP access

  1. Go to the Identity-Aware Proxy page.

  2. On the right side panel, next to Access, click Add.

  3. In the Add members dialog that appears, add the email addresses of groups or individuals to whom you want to grant the IAP-Secured Web App User role for the project

    The following kinds of accounts can be members:

    Make sure to add a Google account that you have access to.

Task 5 - Deploy iap-ingress chart

  1. Create values file for chart:
cat > iap-values.yaml <<EOF
projectID: $(gcloud config get-value project)
endpointServiceName: spa-api
targetServiceName: search-api
targetServicePort: 8080
oauthSecretName: iap-oauth
tlsSecretName: iap-tutorial-ingress-tls
esp:
  enabled: true
EOF
  1. Deploy chart to create IAP aware ingress resource:
 helm github install \
    --name api-iap \
    --repo https://github.com/danisla/cloud-endpoints-controller.git \
    --ref master \
    --path examples/iap-esp/charts/iap-ingress
    -f iap-values.yaml
  1. Wait for the load balancer to be provisioned:
PROJECT=$(gcloud config get-value project)
COMMON_NAME="spa-api.endpoints.${PROJECT}.cloud.goog"

(until [[ $(curl -sfk -w "%{http_code}" https://${COMMON_NAME}) == "302" ]]; do echo "Waiting for LB with IAP..."; sleep 2; done)

Task 5 Deploy

  1. Open your browser to https://spa-api.endpoints.PROJECT_ID.cloud.goog replacing PROJECT_ID with your project id.
  2. Login with your Google account and verify the the sample app is show.

NOTE: It may take 10-15 minutes for the load balancer to be provisioned.

Task 6 - Cleanup

  1. Delete the chart:
helm delete --purge iap-tutorial-ingress

This will trigger the load balancer cleanup. Wait a few moments before continuing.

  1. Delete the GKE cluster:
gcloud container clusters delete dev --zone us-central1-c