Architecture diagram of the Microservices demo repository showing the 10 apps and their dependencies.

Today we’ll deploy the Online Boutique source code on a GKE cluster.

Online Boutique (aka Microservices Demo) is a cloud-native microservices demo application. Online Boutique consists of a 10-tier microservices application. The application is a web-based e-commerce app where users can browse items, add them to the cart, and purchase them.

ServiceLanguageDescription
frontendGoExposes an HTTP server to serve the website. Does not require signup/login and generates session IDs for all users automatically
cartserviceC#Stores the items in the user’s shopping cart in Redis and retrieves it
productcatalogserviceGoProvides the list of products from a JSON file and ability to search products and get individual products
currencyserviceNode.jsConverts one money amount to another currency. Uses real values fetched from European Central Bank. It’s the highest QPS service
paymentserviceNode.jsCharges the given credit card info (mock) with the given amount and returns a transaction ID
shippingserviceGoGives shipping cost estimates based on the shopping cart. Ships items to the given address (mock)
emailservicePythonSends users an order confirmation email (mock)
checkoutserviceGoRetrieves user cart, prepares order and orchestrates the payment, shipping and the email notification
recommendationservicePythonRecommends other products based on what’s given in the cart
adserviceJavaProvides text ads based on given context words
loadgeneratorPython/LocustContinuously sends requests imitating realistic user shopping flows to the frontend
git clone https://github.com/GoogleCloudPlatform/microservices-demo
cd microservices-demo

gcloud services enable cloudprofiler.googleapis.com

# Assumption here that you already have a GCP project and a GKE cluster in it
projectId=FIXME
clusterName=FIXME
clusterZone=FIXME
clusterRegion=FIXME
gcloud config set project $projectId
gcloud container clusters get-credentials $clusterName \
    --zone $clusterZone

# Create a dedicated namespace
namespace=boutique
kubectl create ns $namespace
kubectl config set-context \
    --current \
    --namespace $namespace

Now, here is 4 options with associated scripts to deploy this solution on GKE:

Deployment on GKE with pre-built images

kubectl apply \
    -f ./release/kubernetes-manifests.yaml
kubectl get all,secrets,configmaps
kubectl get service frontend-external | awk '{print $4}'

Deployment on GKE with custom and private images

In some cases you may need to only deploy container images coming from your own private container registry, for example if you have your GKE cluster leveraging Binary Authorization.

publicContainerRegistry=gcr.io/google-samples/microservices-demo
privateContainerRegistry=$clusterRegion-docker.pkg.dev/$projectId/containers/boutique
services="adservice cartservice checkoutservice currencyservice emailservice frontend loadgenerator paymentservice productcatalogservice recommendationservice shippingservice"
imageTag=$(curl -s https://api.github.com/repos/GoogleCloudPlatform/microservices-demo/releases | jq -r '[.[]] | .[0].tag_name')

# Copy the pre-built images into your own private GCR:
for s in $services; do docker pull $publicContainerRegistry/$s:$imageTag; docker tag $publicContainerRegistry/$s:$imageTag $privateContainerRegistry/$s:$imageTag; docker push $privateContainerRegistry/$s:$imageTag; done
docker pull redis:alpine
docker tag redis:alpine $privateContainerRegistry/redis:alpine
docker push $privateContainerRegistry/redis:alpine

# Update the Kubernetes manifests with these new container images
for s in $services; do sed -i "s,image: $s,image: $privateContainerRegistry/$s:$imageTag,g" ./kubernetes-manifests/$s.yaml; done
sed -i "s,image: redis:alpine,image: $privateContainerRegistry/redis:alpine,g" ./kubernetes-manifests/redis.yaml

# Deploy the solution
kubectl apply \
    -f ./kubernetes-manifests
kubectl get all,secrets,configmaps,sa
kubectl get service frontend-external | awk '{print $4}'

Deployment on GKE with Workload Identity

Note: It’s highly recommended to have your GKE clusters with Workload Identity enabled, I discussed about the why and how if you are interested in knowing more, here: GKE’s service account.

# Create a dedicated service account
ksaName=boutique-ksa
kubectl create serviceaccount $ksaName
gsaName=$projectId-boutique-gsa
gsaAccountName=$gsaName@$projectId.iam.gserviceaccount.com
gcloud iam service-accounts create $gsaName
gcloud iam service-accounts add-iam-policy-binding \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:$projectId.svc.id.goog[$namespace/$ksaName]" \
    $gsaAccountName
kubectl annotate serviceaccount \
    $ksaName \
    iam.gke.io/gcp-service-account=$gsaAccountName
roles="roles/cloudtrace.agent roles/monitoring.metricWriter roles/cloudprofiler.agent roles/clouddebugger.agent"
for r in $roles; do gcloud projects add-iam-policy-binding $projectId --member "serviceAccount:$gsaAccountName" --role $r; done

# Update the Kubernetes manifests with this service account
files="`pwd`/kubernetes-manifests/*"
for f in $files; do sed -i "s/serviceAccountName: default/serviceAccountName: $ksaName/g" $f; done

# Deploy the solution
kubectl apply \
    -f ./kubernetes-manifests
kubectl get all,secrets,configmaps,sa
kubectl get service frontend-external | awk '{print $4}'

Replace the in-cluster redis database by Google Cloud Memorystore

Important notes:

  • You can connect to a Memorystore (redis) instance only from GKE clusters that are in the same region and use the same network as your instance.
  • You cannot connect to a Memorystore (redis) instance from a GKE cluster without VPC-native/IP aliasing enabled. For this you should create a GKE cluster with this option --enable-ip-alias.
# Create the Memorystore (redis) instance
gcloud services enable redis.googleapis.com
gcloud redis instances create redis-cart --size=1 --region=$clusterRegion --zone=$clusterZone --redis-version=redis_6_x
gcloud redis instances list --region $clusterRegion

# Update the Kubernetes manifests
REDIS_IP=$(gcloud redis instances describe redis-cart --region=$clusterRegion --format='get(host)')
sed -i "s/value: \"redis-cart:6379\"/value: \"${REDIS_IP}\"/g" ./release/kubernetes-manifests.yaml
# You could also remove the `Deployment` and `Service` related to the not needed anymore `redis-cart`.

# Deploy the solution
kubectl apply -f ./release/kubernetes-manifests.yaml
kubectl get pods
kubectl delete deployment redis-cart
kubectl detele service redis-cart
kubectl get service frontend-external | awk '{print $4}'

That’s a wrap! We now have handy scripts for the Online Boutique solution, ready to be deployed on both GKE w/ or w/o Workload Identity and as a bonus we also replaced the in-cluster redis container by Memorystore (redis).

Further and complementary resources: