Regardless of what you call your architecture or development practice, no doubt it's composed of many services built from many deployed projects and there's some pain in trying to make it all work. Microservice development, technology stack aside, can be a long list of tasks just in itself, including tools to use, how to get services set up, mapping ports, learning terminology, and deployment tasks. The idea of simply writing, running, and checking in the code is the ultimate goal toward which developers strive within the inner loop development cycle and Project Tye looks to push the pain away and get to just coding and debugging your services.
Tye is an experimental developer tool from the .NET team at Microsoft that's meant to make the experience of creating, testing and deploying microservices easier. Specifically, .NET microservices understands how .NET applications are built and work.
Getting Started with Tye
Tye is a .NET global tool, and you'll need to have the .NET Core SDK installed on your computer. Run the following command to install it globally.
dotnet tool install -g Microsoft.Tye
Although you could build, run, and debug your services without Tye, the goals of the project are the reasons you want it for developing many services together: running multiple service with a single command, a service discovery based on convention, and simplicity of deployment to Kubernetes with minimal configuration.
A Simple App
A very basic example of a simple app is a front-end application with a data service back-end. Create a front-end Razor Pages application within a myweatherapp
folder.
mkdir myweatherapp
cd myweatherapp
dotnet new razor -n frontend
The standard dotnet commands could be used to run, build, and debug the application, but instead, you're going to use Tye to run the app.
Type the command for Tye run the front-end application.
tye run frontend
The Tye run command creates the output shown in Figure 1, that builds and starts all services in the application.
The run command identifies the applications and builds them, automatically setting the ports for bindings as these may change due to ports in use/conflicts. Finally, a dashboard is stated to view all of the services running in the application.
Opening the dashboard address reveals the service and some basic information. The Tye Dashboard is shown in Figure 2.
Looking further into the dashboard, the name of the service links to metrics, as shown in Figure 3.
For this application, the Bindings column displays the URIs for the front-end application and the Logs column links to a display for a console-style streaming logs display. This is similar to that typically produced if the dotnet command line were used to start the application, as shown in Figure 4.
Unless port bindings are explicitly defined in the bindings, each service is assigned a random port to avoid conflicts. A common issue when creating multiple services is tracking these ports in configuration files and mapping them from service to service.
Adding Multiple Services
Having a single application is the easy part. With microservices, applications are composed into small components and work together to accomplish the overall application's task. The orchestration of these components when developing code locally can involve starting up multiple debugging instances, IDEs, and/or consoles to get it all working.
Let's add a weather api service and see how Tye helps. First, create the new service with the .NET CLI and call it backend
.
dotnet new webapi -n backend
Tye works with the understanding of .NET projects and solutions, in this case, creating a solution file. Adding the projects to it is the next step.
dotnet new sln -n myweatherapp
dotnet sln add frontend backend
You could use the run command from Tye and see the applications running in the dashboard, but at this point, there are just two applications running and no code has been added to wire them up. Usually, you might put the URI of the api in a configuration file or environment variable, noting that it may change when moving through environments while promoting the application.
As a part of this project, the Tye team has created an extension to the Configuration NuGet package called Microsoft.Tye.Extensions.Configuration
. Adding a reference to this package allows your application to easily access the generated URI and ports needed for service discovery not only during local development, but there's also no need for you to change your code when deploying your applications to Kubernetes.
In the example of the front-end/back-end application, after adding a reference to the new package in the front-end; you can set up a new HttpClient in the ConfigureServices
method, as shown in Listing 1, where the api endpoint is referred to as backend
.
Listing 1: Use GetServiceUri passing the service name
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddHttpClient<WeatherClient>(client =>
{
client.BaseAddress = Configuration.GetServiceUri("backend");
});
}
After adding the necessary code in the Razor page to render the data from the api, run the command to start the applications and view the dashboard.
tye run --dashboard
The dashboard in Figure 5 shows both applications running with the random assigned ports for the front-end and back-end services.
Note that the code didn't define any port or specific URI for the weather api back-end. All you added was the configuration call for the backend
service. Clicking on the URL for the front-end application shows the Web application running and retrieving the data, as shown in Figure 6.
Debugging Services
Adding breakpoints, stepping through code, and debugging statements are crucial processes in the inner development cycle. While Tye is running the services, it also exposes debugging hooks to attach to from editors and IDEs.
Each service can start in debug mode by passing in the --debug
flag with the service name.
tye run --debug backend
This allows an editor like VS Code to attach to the back-end and hit a breakpoint. Alternatively, passing *****
instead of a specific service name exposes all the service's debug hooks, as shown in Figure 7.
Once the debugger is attached and the breakpoint is hit, the experience is the same as you'd expect. Locals, step through processes, call stacks, and other tools are there to use, as shown in Figure 8.
External Dependencies
Adding more .NET Core services to this project, running, and debugging them is pretty easy: The process is the same. However, in the microservice world, applications are built on many existing services, databases, and other processes.
Up to this point, you've depended on Tye's inherent understanding of the .NET project and solution system to build and start the applications along with a new configuration package. When adding in external services or needing more control over what happens for the services, a configuration file, tye.yaml
, is used for these setting.
To generate this file, Tye has an init
command to create the default file based on the projects that exist in the current working directory.
tye init
For the myweatherapp example, the tye.yaml
file created looks like Listing 2.
Listing 2: standard tye.yaml file
# tye application configuration file
# read all about it at https://github.com/dotnet/tye
name: myweatherapp
services:
- name: frontend
project: frontend/frontend.csproj
- name: backend
project: backend/backend.csproj
In the file, each service is listed with configuration specifics. To add a database service like PostgreSQL, you could install that on your local computer and add it to appsettings.json
as a connectionstring setting, or add a service configuration in tye.yaml
and take advantage of the Docker container capabilities of Tye. When running the application, the lifecycle of all services is managed from Tye, starting and stopping the containers along with your application, as shown in Listing 3.
Listing 3: tye.yaml adding postgres service
name: myweatherapp
services:
- name: frontend
project: frontend/frontend.csproj
- name: backend
project: backend/backend.csproj
- name: postgres
image: postgres
env:
- name: POSTGRES_PASSWORD
value: "pass@word1"
bindings:
- port: 5432
connectionString: Server=${host};Port=${port};User Id=postgres;
Password=${env:POSTGRES_PASSWORD};
Adding a new section called “postgres” and defining the image instructs Tye to pull the Docker image when the run command is issued. In this same section, the password for the connection string is defined as a new environment variable. Finally, the port is hard-coded as 5432 as that is the default for PostgreSQL and you don't want a dynamic port set each time.
Note the string interpolation formation for some of the values in the connection string for the database. The ${host}
and ${port)
values substitute for localhost:5432
in local development, but if the port was dynamically assigned; it would properly set as well. This is where using Microsoft.Tye.Extensions.Configuration
to retrieve the values shows its value.
When tye run
is executed and the dashboard is loaded, notice that the front-end and back-end services are noted as “Project” where postgres is a “Container”, as shown in Figure 9. The Tye run command details appear in Figure 10.
Just as in the initial set up for getting the weather api URI, the postgres connection string is available via service discovery using the GetConnectionString
method of the Configuration extension.
Configuration.GetConnectionString("postgres");
To this point, there has been no discussion about Dockerfiles, containers or base images other than for the PostgresSQL image. Yet another benefit in Tye is the ability to continually add dependencies to your application without writing the Docker
files.
Running Apps as Containers
In Figure 11, the dashboard shows the two projects running and the postgres service running as a service. There are opinions in the community stating that development should get as close to the production state during development as possible. In this case, that means containers. Tye offers an option flag, --docker
, where, when the services are started, they're all built in containers and run within Docker. Note that Docker for Windows or your OS is required to be installed and running.
tye run --docker
Tye pulls the base images for the projects based on the project type, builds the project and image, and subsequently runs the image(s), also creating any necessary networks for service discovery.
All of the services are now running in containers on Linux-based images. There was no need to learn the syntax to write a dockerfile or docker-compose nor understand how or where these files should be stored in your structure. It just works with Tye; all you need is a run command with the --docker
flag.
Deploying Your Applications
Tye's deployment target is only to Kubernetes for a few reasons. First, Kubernetes is widely used in production for global companies across an array of industries. Second, it's vendor-neutral with support offered from many cloud providers? Finally, the open source community behind Kubernetes is strong.
Deploying microservice applications isn't always a simple task. Many of the common concerns include creating the containers, pushing the docker images to the specified repositories, understanding Kubernetes manifests, secrets for connection strings, and surely, depending on your app, something else.
Tye is here to help. The deploy
command aims to address each of these items when run. Optionally, there's an interactive flag (-i|--interactive
) that prompts you for your image repository among other detected dependencies such as external database connection string (setup in secrets) and ingress requirements.
Here's an example of the output for the myweatherapp project using the interactive flag, as shown in Figure 12.
tye deploy --interactive
For services to communicate inside of a Kubernetes cluster, Tye sets environment variables for service discovery, ports, and connection strings appropriately. For external services, Kubernetes secrets are used during deployments. There's no changing of configuration files, creating Helm charts, or chasing port numbers; Tye handles these for each deployment locally or when doing the deployment. The application is built and deployed to the Kubernetes cluster based on the .kubeconfig context set on your computer. Post deployment, using the Kubernetes command line tool kubectl
to inspect the pods; the front-end and back-end applications are deployed, as shown in Figure 13.
The connection string for the postgres service is stored in secrets and, using the same utility, calls get secrets
. You can see that the token exists in Figure 14.
The deploy command is an inclusive command, meaning the building and pushing of the containers to the registry happens inside the single gesture. Tye also offers doing this work using the push
command. Or if just building the containers is the desired task, use the build
command.
In short, Tye attempts to simplify the multiple gestures for all the services within an application to a single command.
Continuous Deployment and DevOps
Although using Tye in a “right-click deploy” style fashion, calling deploy for each deployment is great for testing your code in an environment, but it isn't optimal.
Using something like Azure DevOps or GitHub Actions where checking in your code and the services can be deployed is a more likely path to success. Because Tye is a command line tool, setting this process up can be accomplished in any devops system. A recipe for using GitHub actions is documented in the repository at https://aka.ms/tye/recipes/githubaction and is shown in Listing 4.
Listing 4: Github Action for Tye CI/CD
name: Build and Deploy
on: [push]
env:
AZURE_AKS_CLUSTER: myaksclustername
AKS_RESOURCE_GROUP: myaksresourcegroup
ACR_RESOURCE_URI: myregistry.azurecr.io
jobs:
build:
if: github.event_name == 'push' && \
contains(toJson(github.event.commits), '***NO_CI***') == false && \
contains(toJson(github.event.commits), '[ci skip]') == false && \
contains(toJson(github.event.commits), '[skip ci]') == false
name: tye deploy
runs-on: ubuntu-latest
steps:
- name: ? Checkout
uses: actions/checkout@v2
- name: ?? Setup .NET Core
uses: actions/setup-dotnet@v1.5.0
with:
dotnet-version: 3.1.300
- name: ?? Install Tye tools
run: |
dotnet tool install -g Microsoft.Tye \
--version "0.4.0-alpha.20371.1"
- name: ?? Login to ACR
uses: Azure/docker-login@v1
with:
login-server: ${{ env.ACR_RESOURCE_URI }}
username: ${{ secrets.ACR_USER }}
password: ${{ secrets.ACR_PASSWORD }}
- name: ?? Set AKS context
uses: azure/aks-set-context@v1
with:
creds: '${{ secrets.AZURE_CREDENTIALS }}'
cluster-name: ${{ env.AZURE_AKS_CLUSTER }}
resource-group: ${{ env.AKS_RESOURCE_GROUP }}
- name: ?? Install ingress-nginx
run: |
kubectl apply -f https://aka.ms/tye/ingress/deploy
- name: ? tye deploy
run: |
tye deploy -v Debug
Extensions
Beyond the built-in capabilities, there are extensions being added to support well-known logging services, such as Elastic and Seq, distributed tracing support for Zipkin, and an extension for Dapr, the event-driven runtime.
Here's an example of adding support for Seq, a simple addition to the tye.yaml
file.
name: myweatherapp
extensions:
- name: seq
logPath: ./.logs
services:
- name: frontend
project: frontend/frontend.csproj
When tye run
is used, the proper Docker image is pulled and run and now, without any code changes, the logs are pushed to Seq, as shown in Figure 15 and Figure 16.
As the project continues to solve for configuration, logging and diagnostic type services, and other popular microservice patterns and practices; more extensions are bound to surface.
Roadmap
Project Tye is an experimental tool. The goals of the project are solving for or easing the development pain points in service discovery, diagnostics, observability, configuration, and logging when it comes to microservices.
.NET has a rich ecosystem of tools, IDEs and it continues to improve with tools from team and community contributions like this project. All of the source code is available at https://github.com/dotnet/tye, and you'll also find samples, docs, and recipes. Look for monthly release cadence based on your feedback and contributions.