Microsoft recently announced Radius. As stated in their inaugural blog post, it is “a tool to describe, deploy, and manage your entire application”. With Radius, you describe your application in a bicep file. This can include containers, databases, the connections between those and much more. Radius is an open-source solution started from within Microsoft. The name is somewhat confusing because of RADIUS, a network authentication solution developed in 1991!
Starting point: app running locally
Instead of talking about it, let’s start with an application that runs locally on a development workstation and uses Dapr:
The ui is a Flask app that presents a text area and a button. When the user clicks the button, the code that handles the event calls the api using Dapr invoke. If you do not know what Dapr is, have a look at docs.dapr.io. The api saves the user’s question and a fake response to Redis. If Redis cannot be found, the api will simply log it could not save the data. The response is returned to the ui.
To run the application with Dapr on a development machine, I use a dapr.yaml
file in combination with dapr run -f .
See multi-app run for more details.
Here’s the yaml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
version: 1 apps: - appID: ui appDirPath: ./ui appPort: 8001 daprHTTPPort: 3510 env: DAPR_APP: api command: ["python3","app.py"] - appID: api appDirPath: ./api appPort: 8000 daprHTTPPort: 3511 env: REDIS_HOST: localhost REDIS_PORT: 6379 REDIS_DB: 0 command: ["python3","app.py"] |
Note that the api needs a couple of environment variables to find the Redis instance. The ui needs one environment variable DAPR_APP
that holds the Dapr appId of the api. The Dapr invoke call needs this appId in order to find the api on the network.
In Python, the Dapr invoke call looks like this:
1 2 3 4 5 |
with DaprClient() as d: log.info(f"Making call to {dapr_app}") resp = d.invoke_method(dapr_app, 'generate', data=bytes_data, http_verb='POST', content_type='application/json') log.info(f"Response from API: {resp}") |
The app runs fine locally if you have Python and the dependencies as listed in both the ui’s and api’s requirements.txt file. Let’s try to deploy the app with Radius.
Deploying the app with Radius
Before we can deploy the app with Radius, you need to install a couple of things:
- rad CLI: I installed the CLI on MacOS; see the installation instructions for more details
- VS Code extension: Radius uses a forked version of Bicep that is older than the current version of Bicep. The two will eventually converge but for now, you need to disable the official Bicep extension in VS Code and install the Radius Bicep extension. This is needed to support code like
import radius as radius
, which is not supported in the current version of Bicep. - Kubernetes cluster: Radius uses Kubernetes and requires the installation of the Radius control plane on that cluster. I deployed a test & dev AKS cluster in Azure and ensured it was set as my current context. Use
kubectl config current-context
to check that. - Install Dapr: our app uses Dapr and Radius supports it; however, Dapr needs to be manually installed on the cluster; if you have Dapr on your local machine, run
dapr init -k
to install it on Kubernetes
Now you can clone my raddemo repo. Use git clone https://github.com/gbaeke/raddemo.git
. In the raddemo folder, you will see two folders: api and ui. In the root folder, run the following command:
1 |
rad init |
Select Yes
to use the current folder.
Running rad init
does the following:
- Installs Radius to the cluster in the radius-system namespace
- Creates a new environment and workspace (called
default
) - Sets up a
local-dev
recipe pack: recipes allow you to install resources your app needs like Redis, MySQL, etc…
After installation, this is the view on the radius-system Kubernetes namespace with k9s:
There should also be a .rad
folder with a rad.yaml
file:
1 2 |
workspace: application: "raddemo" |
The file defines a workspace with our application name raddemo
. raddemo is the name of the folder where I ran rad init
. You can have multiple workspaces defined with one selected as the default. For instance, you could have a dev and prod workspace where each workspace uses a different Kubernetes cluster and environment. The default could be set to dev but you can easily switch to prod using the rad CLI. Check this overview of workspaces for more information. I am going to work with just one workspace called default, which uses an environment called default. When you just run rad init
, those are the defaults.
You also get a default app.bicep file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import radius as radius param application string resource demo 'Applications.Core/containers@2023-10-01-preview' = { name: 'demo' properties: { application: application container: { image: 'radius.azurecr.io/samples/demo:latest' ports: { web: { containerPort: 3000 } } } } } |
This is deployable code. If you run rad run app.bicep
, a Kubernetes pod will be deployed to your cluster, using the image above. Radius would also setup port forwarding to access the app on it’s containerPort (3000).
We will change this file to deploy the ui. We will remove the application parameter and define our own application. That application needs an environment which we will pass in via a parameter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import radius as radius @description('Specifies the environment for resources.') param environment string resource app 'Applications.Core/applications@2023-10-01-preview' = { name: 'raddemo' properties: { environment: environment } } resource ui 'Applications.Core/containers@2023-10-01-preview' = { name: 'ui' properties: { application: app.id container: { image: 'gbaeke/radius-ui:latest' ports: { web: { containerPort: 8001 } } } extensions: [ { kind: 'daprSidecar' appId: 'ui' } ] } } |
Above, we define the following:
- a resource of type
Applications.Core/applications
: because applications run on Kubernetes, you can use a different namespace than the default and also set labels and annotations. All labels and annotations would be set on all resources belonging to the app, such as containers - the app resource needs an environment: the environment parameter is defined in the Bicep file and is set automatically by the rad CLI; it will match the environment used by your current workspace; environments can also have cloud credentials attached to deploy resources in Azure or AWS; we are not using that here
- a resource of type
Applications.Core/containers
: this will create a pod in a Kubernetes namespace; the container belongs to the app we defined (application property) and uses the imagegbaeke/ui-radius:latest
on Docker Hub. Radius supports Dapr via extensions. The Dapr sidecar is added via these extensions with the app Id of ui.
In Kubernetes, this results in a pod with two containers: the ui container and the Dapr sidecar.
When you run rad run app.bicep
, you should see the resources in namespace default-raddemo
. The logs of all containers should stream to your console and local port 8001 should be mapped to the pod’s port 8001. http://localhost:8001 should show:
We will end this post by also deploying the api. It also needs Dapr and we need to update the definition of the ui container by adding an environment variable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import radius as radius @description('Specifies the environment for resources.') param environment string resource app 'Applications.Core/applications@2023-10-01-preview' = { name: 'raddemo' properties: { environment: environment } } resource ui 'Applications.Core/containers@2023-10-01-preview' = { name: 'ui' properties: { application: app.id container: { image: 'gbaeke/radius-ui:latest' ports: { web: { containerPort: 8001 } } env: { DAPR_APP: api.name // api name is the same as the Dapr app id here } } extensions: [ { kind: 'daprSidecar' appId: 'ui' } ] } } resource api 'Applications.Core/containers@2023-10-01-preview' = { name: 'api' properties: { application: app.id container: { image: 'gbaeke/radius-api:latest' ports: { web: { containerPort: 8000 } } } extensions: [ { kind: 'daprSidecar' appId: 'api' appPort: 8000 } ] } } |
Above, we added the api container, enabled Dapr, and set the Dapr appId to api. In the ui, we set environment variable DAPR_APP to api.name. We can do this because the name of the api resource is the same as the appId. This also makes Radius deploy the api before the ui. Note that the api does not have Redis environment variables. It will default to finding Redis at localhost, which will fail. But that’s ok.
You now have two pods in your namespace:
Note that instead of running rad run app.bicep
, you can also run rad deploy app.bicep
. The latter simply deploys the application. It does not forward ports or stream logs.
Summary
In this post, we touched on the basics of using Radius to deploy an application that uses Dapr. Under the hood, Radius uses Kubernetes to deploy container resources specified in the Bicep file. To run the application, simply run rad run app.bicep
to deploy the app, stream all logs and set up port forwarding.
We barely scratched the surface here so in a next post, we will add Redis via a recipe, and make the application available publicly via a gateway. Stay tuned!
One thought on “Giving Microsoft’s Radius a spin”