Module 7 takes the agent you built in Module 6 and turns it into a production cloud service. You'll containerize the stack, orchestrate it on Kubernetes, automate delivery, and operate it with observability, security, and cost controls. The goal: a reliable Digital FTE that runs 24/7 for real users.
Prerequisites: Modules 4-6. You need a working agent service to deploy.
Your todo-api stores tasks in Redis through Dapr's state API. But real systems don't live in isolation. When a task is created, you need to notify users. When a task is completed, you might update a billing service. Microservices talk to each other constantly.
In traditional architectures, service-to-service calls require solving several problems: How does todo-api find notification-service? What if notification-service moves to a different IP? How do you secure the traffic? What happens when notification-service is temporarily down?
Dapr's service invocation building block solves all of these with a single API call. You call localhost:3500/v1.0/invoke/{app-id}/method/{method}, and Dapr handles discovery, load balancing, mTLS encryption, and retries. Your code never knows where the target service actually lives or how to reach it securely.
By the end of this lesson, your todo-api will call a notification-service through Dapr, with automatic service discovery and encrypted traffic.
Dapr exposes service invocation through a simple HTTP endpoint:
When you call this endpoint:
No hardcoded URLs. No certificate management. No service registry configuration.
On Kubernetes, Dapr uses the mDNS (multicast DNS) or Kubernetes DNS for service discovery. Each service announces itself using its dapr.io/app-id annotation. When todo-api wants to call notification-service:
The key insight: you never configure the target service's address. Dapr resolves notification-service to the correct pod automatically. If notification-service scales to 10 replicas, Dapr load-balances across them.
Here's what happens behind the scenes that you don't have to configure:
The Dapr Sentry service acts as a Certificate Authority. When each sidecar starts:
Every call between sidecars is encrypted with mTLS. Certificate rotation happens automatically (default: every 24 hours). You write zero TLS code.
Let's create a simple notification service that todo-api will call:
Output:
This service runs on port 8001. Nothing Dapr-specific in the code itself.
Both services need Dapr sidecar injection. The key annotations:
Three critical annotations:
Apply the deployment:
Output:
Verify the sidecar is injected:
Output:
2/2 means two containers: your app and the Dapr sidecar.
Now update todo-api to call notification-service when a task is created:
Output:
The invoke_method() parameters:
If you prefer raw HTTP calls over the SDK, use the dapr-app-id header:
Output:
The dapr-app-id header tells your sidecar which service to route to. This is equivalent to the /v1.0/invoke/{app-id}/method/{method} endpoint pattern.
When invoke_method() fails, the error tells you what went wrong:
Enable API logging to see invocation details:
Then check sidecar logs:
Output:
You built a dapr-deployment skill in earlier lessons. Update it with service invocation patterns.
What you're learning: The complete service invocation pattern from deployment through code. You're seeing how annotations enable discovery and how one line of Python replaces pages of service mesh configuration.
What you're learning: How Dapr eliminates certificate management from your concerns. Sentry acts as an automated Certificate Authority, issuing short-lived certificates to each sidecar. You get encrypted service-to-service traffic without writing TLS code or managing cert rotation.
What you're learning: Systematic debugging of service invocation. The pod running doesn't mean the Dapr sidecar is healthy or that the app-id matches. You'll learn to verify sidecar injection (2/2 Ready), check annotation spelling, and use dapr logs to trace the invocation path.
Safety note: When testing service invocation between different namespaces, ensure Dapr's namespace scoping is configured correctly. By default, services can only invoke within the same namespace unless you explicitly configure cross-namespace access.