Hello there, welcome to my talk, thanks for coming to hear about the state of Steeltoe in the year 2020.
My name is Tim Hess, I work for VMware as an engineer on Steeltoe, I was with Pivotal before the acquisition, for a total of 3 years on this project now.
As a friendly .NET developer speaking at a Java conference, I’ll try to avoid confirming whether or not I am a member of the Rebel alliance
Here we have this disclaimer that says we might change or not ship anything that I describe later, but the good news is that all of Steeltoe is open source, so if we do end up going a direction you don’t like you’re always welcome to fork the code and do your own thing.
Before I get into the details of where Steeltoe is at now and where we are planning to go next, I want to tell a brief, fairly incomplete story that resembles what I’ve personally experienced as history in software development over the last 15 or so years, in order to paint the picture of why Steeltoe was started and explain some of the problems we’re trying to solve.
For what it’s worth, this little journey we’re about to go on could also apply to present-day startups that build on an MVP basis and only add complexity as necessary.
Here we are at the beginning, with a simple website.
We have almost no traffic, so it’s fine to use a single instance of the site, since it’s primarily serving static content, maybe a little JavaScript…
To make this story a little more interesting and relevant, this one runs some .NET code to run a shopping cart system and has some generic database to store data.
Because our product is just the best thing ever, we’ve grown our customer base, and that original server isn’t enough anymore.
So, we beef up the server as a band-aid.
We know that’ll work for a little while, but obviously there are limits to how big you can reasonably make a server.
Not only that, you start running into availability concerns: how do we patch the server OS without taking the app offline? How do we update the app without it going offline?
So we add a couple more servers and call it a cluster, or server farm, whatever name you want to use, the idea is that we now have a few instances of our application running, and they don’t have to be as big (or expensive) anymore.
But wait, what is going manage routing requests to a specific instance of the application?
Not to worry, we can add this magical thing called a load balancer.
That’s great - something, that I as a developer don’t have to manage, can do some kind of magic to figure out which application instance gets each request, so I don’t even have to put it on this slide.
But wait - that shopping cart system we built to process orders uses session state and session state doesn’t work if requests don’t always go to the same server because that’s held in-memory!
Eh, why worry about it – let’s just use sticky sessions so load balancer will just make sure the session sticks to a single server.
Oh, wait, that leaves the availability problems we were just fighting with a single server. Fine we’ll just use that database we already have to share session state between the web servers.
How does the load balancer know which instances of the application are healthy? Why are(n’t) they healthy?
What happens when a configuration change is needed? Sure, an administrator can change a config file on disk if there’s a small handful of servers, but at a certain point that doesn’t scale anymore…
This architecture isn’t particularly complex, and can be used to solve many problems, but you can see we’re already adding complexity that isn’t essential to what the application is really needing to do (… use case… sell widgets??)
Well, it turns out at this point, we also may have started to experience pains of monolithic architecture, where the entire app is a single executable.
Now certain areas of the application may need to evolve at different rates, or maybe need more processing power, or memory, or very likely at this point we’re splitting work across multiple teams of people – so we split them out in to their own little app stacks.
Over time, these little app stacks may even independently follow the same evolution that we just walked through, but the immediate problem was resolved.
But now there’s another series of questions raised:
How do instances of the original/front end application know where to find the new services?
Can they talk directly to service instances, or is there another load balancer or… ?
This view is a bit simplified of what I’m really talking about, so if we expand it out…
We can see a bit more complete version of the picture now, since we’ve probably also added at least one more separate interface to these systems (such as an interface for handling of orders)
At this point, beyond the business logic we need to be concerned with, we have to worry about
Centralizing configuration
Managing addresses of backing services
How do we communicate with these services? HTTP, gRPC, some form of message queue?
How do we handle security here?
- Do we pass user credentials forward (how do we know they’re valid?!)
- Is there a separate system for authN/authZ?
- Are we using circuit breakers or retry mechanisms to handle failure without creating internal denial of service situations?
- How are we keeping track of the health of these applications?
- Is there some pattern or practice for performing app/data management tasks?
Are we using functions for any of this? Are we tied to a platform-specific Function tech
What about data processing, warehousing, analytics and all of that? How does data move out of operational databases
Backing up for a minute here, there are additional questions involved in taking any application to production, but that are amplified with this complicated of an environment
What should we do when we have to start a new project? What’s the baseline for dependencies my app can use,?
What source control mechanism should it use?
Is there an IDE my whole team should use? Does it have any useful tooling to help out with this complexity?
How do we continuously test these services to make sure they’re all speaking the same dialect of the same language and that they’re not breaking any contracts?
On top of all that, how do we actually ship these things?
There are a lot of questions that need to be answered to run complex, modern application architectures.
Some, like source control have hopefully been answered by most organizations some time ago.
For us in the .NET space, Microsoft does have solutions to many of these problems, but sometimes they cost too much or don’t work the way we want them to or they just aren’t the right tool for the job.
Throw all these problems together, and you start to understand the need for a broad, open source project to tackle these kinds of problems.
So, in this environment, Steeltoe was started. And back then in 2015, there weren’t many solutions to these problems, particularly for .NET. .NET Core wasn’t even really a thing yet.
Steeltoe aims to provide or prescribe solutions to these kinds of problems for .NET environments, and also, because many companies have .NET and Java (Oh, hey that’s right we’re at SpringOne) provide interoperability with Spring.
So, what’s in Steeltoe today? More than a couple things.
Some of the components in this list provide their own abstractions, like Service Discovery, Connectors, Messaging, Management.
Others plug into or extend Microsoft tooling, like our configuration providers and most of the security functionality.
Everything on this list with an asterisk is either new, or has seen significant changes recently.
If you were to refer back to the list of problems I just raised, you’ll find answers for a lot of them here:
Centralized Configuration – provider for Spring Cloud Config Server
Locating services – variety of service discovery options
Service to service – security packages, though CF specific today… Messaging & Streams
Resilience – Hystrix
Health – Actuators, specifically health
Tasks – management project
Functions - … not yet
Data/ETL/ETC – very soon with Data Flow
Templates – Initializr
IDE Tooling ~ .NET Dev X
Testing ~ SC Contract ?
CI/CD ~ buildpacks? Other VMW projects
No significant changes with Circuit Breaker or most of security…
if not mentioned, it’s still there unless autofac or net4 specific
OK, we’re going to treat this slide as the roadmap for the rest of the talk today
Steeltoe has grown up as a collection of packages with varying levels of interconnectedness.
Sometimes that resulted in apparent ties to a certain platform that weren’t necessarily necessary.
As a result, we landed in a situation where it may have seemed like Steeltoe was only for Cloud Foundry based platforms, but that was more of a side affect of our focus than anything intentional.
So, we took many of the defining aspects of our components and moved them to separate, extremely small packages.
You could say these packages are intended to form a sort of blueprint for the foundation of what Steeltoe libraries can/should do.
Additionally, with these changes, we were able to reduce the interlinking between Steeltoe packages and the specific implementations that make up our components.
With loosened coupling, you should have an easier time building onto and extending the behaviors we have in Steeltoe today.
So, what does that actually look like? From a packing perspective, that means 10 new packages. Ideally, these are also our best-documented packages too, so please let us know if you notice gaps.
So with this abstraction split, this foundation we’re defining is taking us towards better and broader platform support.
By nature of the history of this project, with most of the contributions coming from Pivotal, now VMware employees, there’s been a lot of attention on support for Cloud Foundry, specifically Pivotal Cloud Foundry and now VMware Tanzu.
That’s not been a bad thing for us, there’s a lot there to love that really makes development simple and straightforward, and that’s helped identify some guiding principles and priorities for us over time.
However, with all that being said, Steeltoe was always destined for more, and wasn’t intended to only run on one cloud (even if that cloud runs on other clouds…).
Our work leading up to last year’s SpringOne was the beginning of the work in running a single app on many platforms, and since then we’ve been working on this (re)building this ground layer in order to more naturally support that end goal.
With this release, we’re beginning to add support for Kubernetes, in the form of packages that directly interact with the Kubernetes management API.
As a result of the abstractions work, we’ve been able to do this without taking dependencies on our Cloud Foundry-related packages and without introducing any new k8s related dependencies to components where it isn’t required. More on that later though.
One other general change we made that I wanted to sneak into this section is that for Steeltoe 3.0, we shifted .NET Framework out focus, and we’re really only targeting .NET Core.
Packages in 3.0 do still target .NET Standard where it makes sense, but please be aware that IF aspects of Steeltoe 3.0 work with .NET Framework, it’s not necessarily by design, and that runtime is not included in our CI testing.
We are still supporting .NET Framework in the 2.x line of code, if that’s you, please don’t feel like we’re leaving you behind – there will be a 2.5 release due out in September.
Next up is Configuration
On the theme of forming a solid foundation, we took some of the ideas previously presented in our Cloud Foundry configuration provider and extracted them.
Specially I’m talking about attributes that have been provided by VCAP variables on Cloud Foundry.
These properties include things like the application’s name (as viewed by the platform), IP addresses, port, URIs and service credentials.
Not all of these properties make sense on all platforms, so there may be some further evolution here, but the point is that we’ve been working on building this generic foundation that (in this case) defines the basics of an application instance on any platform.
We’ve added support for using mutual TLS with spring cloud config server.
The new configuration-related Kubernetes support we have is built on top of the official Kubernetes client for .NET, which is a bit of a heavyweight dependency, but it should get the job done for now.
We built configuration providers for configmaps and secrets and used some familiar conventions as defaults for finding resources.
By default, we search the default namespace, you can change that in configuration.
You can also add other resource names you’d like to use
Any time you’re dealing with externalized configuration, it’s a good idea to have a plan for how to update the version of it your app is holding, so…
There are several ways to handle updating the configuration data that is mapped by these providers.
Don’t do anything… pretty self-explanatory – this is the default here, so if you only want app instances to get configuration data when they first start, you’re ready to go
Then we have polling, where every x seconds the config provider makes an api call for each resource and, if there’s data to update, updates the internal dictionary accordingly
And finally, there’s event-based reloading. With this method, the Kubernetes client holds open a web socket connection with the API server, and any events or changes on these resources is communicated to the app instance, triggering a data update.
Which method should you use? It may depend on the environment... Event-based is pretty cool for local development scenarios, where you probably aren’t operating at scale, but you might not need or want that level of real-time commitment in production.
Up next is Connectors
Historically, this feature has been pretty Cloud Foundry or Tanzu specific, being most useful in a VCAP environment.
But with this release, again, we’ve worked to pull apart the Cloud Foundry dependency and the underlying abstractions, so you only depend on the code you actually need.
We don’t have any other platform-specific implementations yet, as, at least as far as I’m aware, there aren’t really any other platforms out there with similar functionality for us to adapt to.
And Heroku doesn’t count here because they don’t support .NET.
There are a couple of projects for Kubernetes that are promising, and I think we’ll be looking more closely at k8s-service-bindings as it gets closer to being ready for consumption
The way we separated the Cloud Foundry bits was by introducing some new extensibility points. I won’t go too far in depth here, because at least in my opinion, this is one of the areas we hope you don’t generally have to know much about and can rely on it just working.
At the basic level, most backing resources can be represented by a ServiceInfo, which can be built from some raw credential format by a specific ServiceInfoFactory, and all of that stuff is managed by a ServiceInfoCreator.
If you need or want to know more about how this all works, check out the link on this slide later, or find me on github, Slack or Twitter.
We also added a couple of new assembly-level attributes so we don’t have to blindly scan all assemblies.
There’s also some new shareable logic in Steeltoe.Common that can be used to easily find assemblies containing types of class, sometimes pointing to the specific class for even quicker discovery. Again, details available on demand
One big side affect of these changes though is that if you’re migrating from some v2 of Steeltoe, you’ll need to add an additional reference to get VCAP_SERVICES support. Because this functionality is reflection-based, you should only need the additional reference.
We’ve also added support for CosmosDB and a couple of new ways to interact with Connectors.
We’ve also added a couple new ways of interacting with connectors for more flexibility
The ConnectionStringManager takes in your app configuration and can return typed connection info.
Providing a service binding name is generally optional, but required if there are multiple bindings of the same type.
The ConnectionInfo that gets returned here will include the connection string and some additional metadata that may or may not be required for your use case.
The certificate information we’re referring to here under Postgres is specific to the GCP service broker. If you’re using that, you need to configure the client to use client certificates and you’ll find these properties quite helpful.
The ConnectionString Configuration provider is very new, but it builds on top of all the existing pieces and is really just a shim to use Connectors similar to the way you’d normally use connection strings (like in all the ASP.NET docs).
We do have a little extra power here in the form of retrieving the connection by either its name or type.
It’s worth pointing out here that when you use this method, you won’t get automatic injection of health contributors the same way you would if you use the Steeltoe service collection extensions that add configured clients for the given technology.
OK, next up is Service discovery
In this release, we’ve rerouted the plumbing that’s used to set up Service Discovery for your application.
This change has some pretty big impact, but I’ll get into detail on the next slide.
We’ve either added or improved mTLS support for Eureka, depending on your perspective… I don’t have a whole lot to say on this other than its not extremely hard to do now.
We’ve had this configuration-based service “discovery” option for a while, I added it for unit testing with the load balancer functionality some time ago.
There’s obviously no registration capability here, but it’s now a fully integrated option for managing a mapping of service names to host name/port combinations, so t might be useful when you still want to use the discovery infrastructure in a relatively static environment or if you’re trying to work out some fringe issue with a specific service instance somewhere
We’ve also added a client for discovering service instances with the Kubernetes management API, and a no-op client for when you’re deploying to a platform such as Kubernetes that can do its own load balancing internally and uses something like DNS so that all you needed in the first place is the service name.
On the subject of inter-package relationships, the core of discovery now lives in Discovery.ClientBase, which doesn’t need references to any of the complex clients anymore.
We wanted to make sure this client didn’t have any dependencies on ASP.NET Core, so as a consequence that means we have additional packages now extending outward in a couple different directions. If you’re looking for WebHostBuilder extensions, this is where you’ll find them
This model is kind of similar to how you’d work with Entity Framework core, in that you add the abstraction to your application and use an extension to specify the specific implementation you’d like to use.
In this case you’d say “.AddServiceDiscovery” and in your options you’d .UseEureka or .UseConsul or .UseKubernetes.
Not pictured on this slide is, of course, the new Discovery Abstractions library
So that’s a lot on metadata-type concerns, what does this actually mean for the code?
These options are the extensions on the Generic host builder, but there’s identical code available for WebHostBuilder, and very similar options for adding directly to the Service Collection, though you’ll likely also need to activate the client if you use those. See the documentation for more detail here.
If you prefer the style we offered before, where your code just says “Hey, add a discovery client and let the runtime configuration figure it out”, you’re actually all set with the same method signature you’ve been using. You’ll still need to add or change your nuget references so the right clients are there for us to find, and under the covers, we’ll use reflection to load up those assemblies and used what’s configured.
If you’re not a fan of the reflection here, or know you’re only ever going to use one option, you now have the opportunity to declare outright that when you AddServiceDiscovery, it should only ever be for that one client.
These options, particularly the new variation on AddDiscoveryClient, are closer to how we’ve always wanted this functionality work, but the timing here was driven the Kubernetes discovery work
Speaking of Kubernetes discovery…
When you deploy an app to Kubernetes and have it listening on a port, a service is automatically registered for you, so there’s no need for a discovery client to do any work here. Our client is able to find your service applications using the built-in service and endpoint objects that represent them.
Like the configuration providers, this functionality is also built on top of the official Kubernetes client for .NET. That client is quite large, due to the way its code is auto-generated.
As such, we needed to rework Steeltoe discovery if we wanted to use it. since nobody using eureka or consul wants this extra, huge dependency pulled into their project that they’re not going to use.
This will also open doors for us with next steps in this space, as there’s now a way to potentially use a different Kubernetes client, like dotnet-kube-client, or use code that’s in our incubator right now that brings the informers concept from go over to .NET.
Since Kubernetes has built-in service registration and discovery capability, we’ve also included a no-op client for a way to seamlessly change between using the management api to find and talk directly between app instances and using the built-in infrastructure.
Let’s talk about app management updates
My teammate Hananiel delivered a talk yesterday that was focused on the finer details of some of these updates, so be sure to check out the recording of that talk if you missed it as I’ll only be mentioning the stuff he went in depth on.
OpenTelemetry has replaced OpenCensus ~ this is an evolution of the dependency we’ve had for a while now
Prometheus export support added for broader usage
Spring Boot Admin was obviously built for Spring boot apps, but our actuators are compatible (enough) and now have a registration mechanism
A bit of an internal change, but our actuators are now using ASP.NET Core’s built-in endpoint routing instead of being a completely custom middleware that did path
We finally have an AddAllActuators method, so if you’re not using a cloud foundry based platform you have a one-liner available for adding actuators
AddKubernetesActuators builds on AddAllActuators, but adds Kubernetes Info Contributor with pod/node info
Heap dumps available on Linux
The one feature I do want to spend a bit more time on is Health groups
Another feature we added, largely for supporting deployments to Kubernetes, is health groups.
Kubernetes uses the concepts of readiness and liveness probes to determine whether or not to route traffic to an application instance, and if an instance is healthy.
For Steeltoe, both of these are powered by this new Application Availability class, which is essentially a dictionary of state…
We added hooks that fire at application startup to initialize this state, so they’re a pretty basic implementation, but you’re free to push additional state change events to them based on your own requirements.
With this functionality, you also get the ability to define your own health groups, so if you have a variety of health contributors, you can group them with some basic configuration, and now you have the ability to evaluate application health based on a subset of contributors.
These groups do also work with the Microsoft health checks and anything that’s compatible with those abstractions, so you’re free to mix and match there.
Messaging is brand new
This might be the single largest feature we’ve ever built… Steeltoe Messaging
The big update for Security in 3.0 is Service to Service mutual TLS, specifically with support for rotating certificates and including authorization policies SameOrg, SameSpace
With this functionality, especially on platforms like Cloud Foundry or Tanzu, you now have a very low-effort way to use certificates that are provisioned and rotated by the platform for securing inter-service communications.
This mechanism can validate that the certificates are recognized, and even apply policies that require the caller to be running in the same org or even the same space on the platform.
Lastly, we’ve got some other general updates
Initializr is in final stages of rebuild, with the guts to be delivered via nuget package so it is easier to run your own version in your own environment
CLI project on hold after Microsoft kicked up project Tye, which has pretty serious momentum.
We did some things they still aren’t doing, so we’ll look at contributing back and/or doing some kind of extension for an even better experience.
Added/improved HostBuilder Extensions for simpler experience, getting-started or otherwise
Very small update, but color is now automatically disabled when running on CloudFoundry so that color codes aren’t cluttering up the logs. We’ll probably update this soon to be on any cloud platform that we can detect.
Also we’re now making sure that if you use Steeltoe’s dynamic console logger we’re removing Microsoft’s console logger so that you don’t get duplicate log entries
There’s also been improvements in the logger actuator setup methods that can automatically add dynamic logging so that our dynamic Serilog and console loggers can co-exist a bit more naturally and peacefully without as many concerns about ordering.
We’ve updated most of our HttpClient usage so that clients are kept around longer and aren’t being disposed after a single usage.
Also, they will be using a user-agent so that services like Eureka and Config Server can know they’re being called by Steeltoe and which version
We’ve also reduced our usage of Newtonsoft, on the path towards eliminating it in favor of System.Text.Json. We’ve not used much of the capability in Newtonsoft, so it was a bit bigger of a tool than we really needed.
We’ve also made some general improvements to add more comments and improve some naming so it’s more consistent across the board and also in some cases clearer what’s happening. For example, we’ve been shipping a hostbuilder extension named AddCloudFoundry, but that doesn’t really tell you much, so it’s been replaced with AddCloudFoundryConfiguration
Lastly, we’ve got some other general updates
Get Involved or at least follow along
GH Stars help us something something
GH watch for releases