Cloud-First application development with mirrord

mirrord solves the problems every modern cloud developer faces during microservice development by remote services feels local.

In my earlier blog post Remocal, I discussed the concept of Remocal, explained about mirrord, demonstrated its usage in a .NET Aspire based application.

In this blog post, I will be using Online Boutique, a sample cloud-first application with several microservices showcasing Kubernetes, Istio and gRPC. Online Boutique offers functionalities to browse items, add them to the cart, and purchase them.

Architecture

The diagram below illustrates the architecture of Online Boutique application, which comprises 11 microservices written in various programming languages. For more details, you can visit Online Boutique.

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.

Prerequisites

Get the code

# Clone code using this URL
https://github.com/GoogleCloudPlatform/microservices-demo.git

Docker Desktop

Install Docker Desktop and enable Kubernetes option to create a single-node Kubernetes cluster. Alternatively, you may choose from other Kubernetes cluster such as Minikube, k3d, Kind, MicroK8s, Rancher, GKE, AKS, IKS, OKE, AKC and EKS.

Run Skaffold

NOTE: Since, I have been using MacBook Air with the M3 Chip, I have made a few changes in docker files to support AMD and ensure successful builds. Also, it is important to note that before running Skaffold, you should ensure that service-specific dependencies, such as .NET 8 for cartservice are made available and configured properly.

# Updated cartservice dockerfile - linux-x64 to linux-arm64
FROM mcr.microsoft.com/dotnet/sdk:8.0.302-noble@sha256:bd836d1c4a19860ee61d1202b82561f0c750edb7a635443cb001042b71d79569 as builder
WORKDIR /app
COPY cartservice.csproj .
RUN dotnet restore cartservice.csproj \
    -r linux-arm64
COPY . .
RUN dotnet publish cartservice.csproj \
    -p:PublishSingleFile=true \
    -r linux-arm64 \
    --self-contained true \
    -p:PublishTrimmed=true \
    -p:TrimMode=full \
    -c release \
    -o /cartservice
 
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.6-noble-chiseled@sha256:55d6e41f2e7687c597daa4fdca997b07beb3e23b6283729e19bb8ceb272def1a
 
WORKDIR /app
COPY --from=builder /cartservice .
EXPOSE 7070
ENV DOTNET_EnableDiagnostics=0 \
    ASPNETCORE_HTTP_PORTS=7070
USER 1000
ENTRYPOINT ["/app/cartservice"]
# Updated loadgenerator dockerfile - python:3.12.4-slim@sha256:d3a32591680bdfd49da5773495730cf8afdb817e217435db66588b2c64db6d5e to python:3
FROM python:3 as base
 
FROM base as builder
 
COPY requirements.txt .
 
RUN pip install --prefix="/install" -r requirements.txt
 
FROM base
 
WORKDIR /loadgen
 
COPY --from=builder /install /usr/local
 
# Add application code.
COPY locustfile.py .
 
# enable gevent support in debugger
ENV GEVENT_SUPPORT=True
 
ENTRYPOINT locust --host="http://${FRONTEND_ADDR}" --headless -u "${USERS:-10}" 2>&1
# Changes in skaffold.yaml
# I have removed "loadgenerator" as it continuously sends requests imitating realistic user shopping flows to the frontend, which interrupts cartservice debugging.
apiVersion: skaffold/v3
kind: Config
metadata:
  name: app
build:
  artifacts:
  # image tags are relative; to specify an image repo (e.g. GCR), you
  # must provide a "default repo" using one of the methods described
  # here:
  # https://skaffold.dev/docs/concepts/#image-repository-handling
  - image: emailservice
    context: src/emailservice
  - image: productcatalogservice
    context: src/productcatalogservice
  - image: recommendationservice
    context: src/recommendationservice
  - image: shoppingassistantservice
    context: src/shoppingassistantservice
  - image: shippingservice
    context: src/shippingservice
  - image: checkoutservice
    context: src/checkoutservice
  - image: paymentservice
    context: src/paymentservice
  - image: currencyservice
    context: src/currencyservice
  - image: cartservice
    context: src/cartservice/src
    docker:
      dockerfile: Dockerfile
  - image: frontend
    context: src/frontend
  - image: adservice
    context: src/adservice
  tagPolicy:
    gitCommit: {}
  local:
    useBuildkit: false
manifests:
  kustomize:
    paths:
    - kubernetes-manifests
deploy:
  kubectl: {}
# "gcb" profile allows building and pushing the images
# on Google Container Builder without requiring docker
# installed on the developer machine. However, note that
# since GCB does not cache the builds, each build will
# start from scratch and therefore take a long time.
#
# This is not used by default. To use it, run:
#     skaffold run -p gcb
profiles:
- name: gcb
  build:
    googleCloudBuild:
      diskSizeGb: 300
      machineType: N1_HIGHCPU_32
      timeout: 4000s
# "debug" profile replaces the default Dockerfile in cartservice with Dockerfile.debug,
# which enables debugging via skaffold.
#
# This profile is used by default when running skaffold debug.
- name: debug
  activation:
  - command: debug
  patches:
  - op: replace
    path: /build/artifacts/7/docker/dockerfile
    value: Dockerfile.debug
# The "network-policies" profile is not used by default.
# You can use it in isolation or in combination with other profiles:
#     skaffold run -p network-policies, debug
- name: network-policies
  patches:
  - op: add
    path: /manifests/kustomize/paths/1
    value: kustomize/components/network-policies
# Run this command to build images and deploy on the Kubernetes cluster
skaffold run
Generating tags...
 - emailservice -> emailservice:latest
 - productcatalogservice -> productcatalogservice:latest
 - recommendationservice -> recommendationservice:latest
 - shoppingassistantservice -> shoppingassistantservice:latest
 - shippingservice -> shippingservice:latest
 - checkoutservice -> checkoutservice:latest
 - paymentservice -> paymentservice:latest
 - currencyservice -> currencyservice:latest
 - cartservice -> cartservice:latest
 - frontend -> frontend:latest
 - adservice -> adservice:latest
 - loadgenerator -> loadgenerator:latest
Checking cache...
 - emailservice: Not found. Building
 - productcatalogservice: Not found. Building
 - recommendationservice: Not found. Building
 - shoppingassistantservice: Not found. Building
 - shippingservice: Not found. Building
 - checkoutservice: Not found. Building
 - paymentservice: Not found. Building
 - currencyservice: Not found. Building
 - cartservice: Not found. Building
 - frontend: Not found. Building
 - adservice: Not found. Building
 - loadgenerator: Not found. Building
Starting build...
Building [checkoutservice]...

NOTE: Please be aware that the initial setup make take a bit longer, approximately ~20 minutes, to complete the build and deploy activities.

After successful completion, you can run below command to check the status of pods.

kubectl get pods
NAME                                     READY   STATUS    RESTARTS      AGE
adservice-7cf748845b-dm87b               1/1     Running   2 (38h ago)   3d20h
cartservice-59cb4f6fdb-2fzcf             1/1     Running   2 (38h ago)   3d20h
checkoutservice-555c97df5f-wc6fk         1/1     Running   2 (38h ago)   3d20h
currencyservice-74fc67f7-5kzvd           1/1     Running   2 (61s ago)   3d20h
emailservice-78d55c667c-fdhdb            1/1     Running   2 (61s ago)   3d20h
frontend-b6747486c-d8css                 1/1     Running   2 (38h ago)   3d20h
paymentservice-5588565ccc-xj77r          1/1     Running   2 (61s ago)   3d20h
productcatalogservice-86f5555f5f-pshkf   1/1     Running   2 (38h ago)   3d20h
recommendationservice-57f6f5bfc4-mqnd6   1/1     Running   2 (61s ago)   3d20h
redis-cart-84f5b8c59d-gllcq              1/1     Running   2 (38h ago)   3d20h
shippingservice-6db998f76f-spvf9         1/1     Running   2 (38h ago)   3d20h

Port-Forward

kubectl port-forward deployment/frontend 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

You can run following command to check the impact of port-forward.

kubectl get svc
NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
adservice               ClusterIP      10.111.88.96     <none>        9555/TCP       3d20h
cartservice             ClusterIP      10.97.87.162     <none>        7070/TCP       3d20h
checkoutservice         ClusterIP      10.97.43.177     <none>        5050/TCP       3d20h
currencyservice         ClusterIP      10.106.117.61    <none>        7000/TCP       3d20h
emailservice            ClusterIP      10.96.127.1      <none>        5000/TCP       3d20h
frontend                ClusterIP      10.110.213.167   <none>        80/TCP         3d20h
frontend-external       LoadBalancer   10.102.65.249    localhost     80:32688/TCP   3d20h
kubernetes              ClusterIP      10.96.0.1        <none>        443/TCP        5d
paymentservice          ClusterIP      10.96.13.60      <none>        50051/TCP      3d20h
productcatalogservice   ClusterIP      10.104.237.105   <none>        3550/TCP       3d20h
recommendationservice   ClusterIP      10.106.71.106    <none>        8080/TCP       3d20h
redis-cart              ClusterIP      10.101.151.141   <none>        6379/TCP       3d20h
shippingservice         ClusterIP      10.101.213.94    <none>        50051/TCP      3d20h

Now, you can run the application using http://localhost:8080 to the access the Online Boutique application. The images below provide a glimpse of Online Boutique UI.

mirrord in action

Let's consider a scenario where the Online Boutique has reached the staging (or QA) environment, and you would like to address an issue or reproduce it or implement a feature for a specific service, such as the cartservice. Now, you have a few options either replicate entire setup at locally or on the cloud to work on, which can be time-consuming, expensive or occasionally impractical. Moreover, ensuring parity between Dev/Staging/Prod environments can pose additional challenges. In such instances, mirrord offers distinct advantages.

In the context of the Online Boutique application, we will be working with cartservice. Let's open cartservice and put a breakpoint as shown below:

Generate launch.json and task.json using .NET: Generate Assets for Build and Debug from the Command Palette... and enable mirrord and update settings (mirrord.json) as shown below:

Now, we are ready to start the debugging cartservice by pressing F5, and you will be asked to select the pod. From dropdown, choose 'cartservice'.

After selecting the pod, debugger gets activated. (Refer. the below image)

If Online Boutique is not currently open, you can open it and add few items to the Cart. Next, click on the Empty Cart option to see the magic. This activity can also be performed with the other services such as frontend, productcatalogservice, currencyservice, paymentservice, shippingservice, emailservice, checkoutservice, recommendationservice and adservice.

NOTE: While I have utilized mirrord OSS version; however, the Team/Enterprise edition offers more advance capabilities.

Cleanup

Since we have deployed the application using skaffold run command, we can use skaffold delete to clean up the deployed resources.

# Run this command to clean-up the deployed resources
skaffold delete

Happy Learning & Coding...