Category Archives: Eureka

Interacting with Eureka from Swift 5

I wanted to get my little Swift console application (developed in the post Creating a console app. in Swift (on Linux)) working with Eureka.

So in the last post I outlined the simple steps to get Eureka up and running in docker. Now let’s combine these posts along with the information from my the post Interacting with Eureka via it’s REST operations and create ourselves a Swift Eureka client.

Before I get started, I have not found the generation of JSON from types or for that matter generating the Dictionary required for JSONSerialization as intuitive as C# (for example). I’m not sure if there’s a better way to do these things, but this code works so it’ll do for now. But if you know a better way please use it.

To start with we’re just going to manually generate our JSON string to match the POST request that we need to tell EUREKA that our application is starting

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"STARTING\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

Hopefully this is fairly obvious (even with all the escaping characters) what we’re doing, but to use this in JSONSerialization.data method is seems we need to convert this string to a Dictionary or Array, so here’s an extension method to convert to a dictionary

extension String {
  func toDictionary() -> [String:AnyObject]? {
    if let data = self.data(using: .utf8) {
      return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
    }
    return nil
  } 
}

There’s zero error handling here – I leave it to the reader to add that.

So using the extension method toDictionary we’ll convert our JSON string into a dictionary which is then passed into the JSONSerialization.data to create a Data object, i.e.

let data = json.toDictionary()
let httpBody = try JSONSerialization.data(withJSONObject: data!, options: [])

Now we’ll create the URL and URLRequest to call the Eureka service

let url = URL(string: "http://192.168.1.1:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

The rest of the code is the same as Creating a console app. in Swift (on Linux) but for completeness it’s list below in it’s entirety (don’t forget the extension method toDictionary is in some extension file).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"DOWN\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

let data = json.toDictionary()
let httpBody = try? JSONSerialization.data(withJSONObject: data!, options: [])

let url = URL(string: "http://192.168.0.88:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

session.dataTask(with: request) { (data, response, error) in
    let result = String.init(data: data!, encoding: .utf8)
    print(result!)
    semaphore.signal()
}.resume()

semaphore.wait()

At this time I haven’t got a great solution for using types to define our request as the request has some invalid name keys, such as $ and @enabled (again there may be a solution which I’ve not come across yet). To give the reader a starting point, here’s the code I have for turning my EurekaRequest type into a JSON string.

I’ve not included the EurekaRequest so this really just shows a way to convert a type to a JSON string.

let instance = EurekaRequest()
let encoder = JSONEncoder();
// required to stop // becoming \/
encoder.outputFormatting = .withoutEscapingSlashes
let jsonData = try encoder.encode(instance)

// convert to a string
var json = String(data: jsonData, encoding: String.Encoding.utf8)!;

Eureka in Docker

I’ve actually covered this in a previous post, but because it was part of Eureka Server (using Steeltoe) revisited. It didn’t quite stand out when I was searching for it, so let’s recreate here in it’s own post…

Pull the docker image using

docker pull steeltoeoss/eureka-server

Now to run it

docker run --publish 8761:8761 steeltoeoss/eureka-server

This will now be available locally or remotely on port 8761, i.e.

	
http://locahost:8761

Interacting with Eureka via it’s REST operations

In previous posts we’ve used Steeltoe to interact with Eureka. Obviously Steeltoe gives us good patterns for interacting with Eureka but also has the capability to work with other service discovery registries etc.

Steeltoe’s great, but what if we wanted to just write our own Eureka either for learning how things work or maybe we just want to write our own for .NET or for another language/technology?

Eureka exposes REST operations for everything you’ll need to do with a service registry, let’s take a look.

Note: I’ll give examples using localhost, obviously change to your Eureka host/ip and I’ll also show data based upon the weatherapi we’ve worked on previously.

Query for all applications and their instances

  • GET method:
    http://localhost:8761/eureka/apps
    
  • Response:
    <applications>
      <versions__delta>1</versions__delta>
      <apps__hashcode>UP_1_</apps__hashcode>
      <application>
        <name>WEATHERAPI</name>
        <instance>
          <instanceId>localhost:weatherapi:5001</instanceId>
          <hostName>localhost</hostName>
          <app>WEATHERAPI</app>
          <ipAddr>localhost</ipAddr>
          <status>UP</status>
          <overriddenstatus>UNKNOWN</overriddenstatus>
          <port enabled="false">8080</port>
          <securePort enabled="true">5001</securePort>
          <countryId>1</countryId>
          <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
            <name>MyOwn</name>
          </dataCenterInfo>
          <leaseInfo>
            <renewalIntervalInSecs>30</renewalIntervalInSecs>
            <durationInSecs>90</durationInSecs>
            <registrationTimestamp>1611512017058</registrationTimestamp>
            <lastRenewalTimestamp>1611515916921</lastRenewalTimestamp>
            <evictionTimestamp>0</evictionTimestamp>
            <serviceUpTimestamp>1611512016403</serviceUpTimestamp>
          </leaseInfo>
          <metadata class="java.util.Collections$EmptyMap"/>
          <homePageUrl>https://localhost:5001/</homePageUrl>
          <statusPageUrl>https://localhost:5001/actuator/info</statusPageUrl>
          <healthCheckUrl>https://localhost:5001/actuator/health</healthCheckUrl>
          <vipAddress>weatherapi</vipAddress>
          <secureVipAddress>weatherapi</secureVipAddress>
          <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
          <lastUpdatedTimestamp>1611512017058</lastUpdatedTimestamp>
          <lastDirtyTimestamp>1611512016217</lastDirtyTimestamp>
          <actionType>ADDED</actionType>
        </instance>
      </application>
    </applications>
    

Query for instances for a specific application id

  • GET method:
    http://localhost:8761/eureka/apps/{your app id}
    
    // Example
    http://localhost:8761/eureka/apps/weatherapi
    
  • Response:
    <application>
        <name>WEATHERAPI</name>
        <instance>
            <instanceId>localhost:weatherapi:5001</instanceId>
            <hostName>localhost</hostName>
            <app>WEATHERAPI</app>
            <ipAddr>localhost</ipAddr>
            <status>UP</status>
            <overriddenstatus>UNKNOWN</overriddenstatus>
            <port enabled="false">8080</port>
            <securePort enabled="true">5001</securePort>
            <countryId>1</countryId>
            <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
                <name>MyOwn</name>
            </dataCenterInfo>
            <leaseInfo>
                <renewalIntervalInSecs>30</renewalIntervalInSecs>
                <durationInSecs>90</durationInSecs>
                <registrationTimestamp>1611512017058</registrationTimestamp>
                <lastRenewalTimestamp>1611516936969</lastRenewalTimestamp>
                <evictionTimestamp>0</evictionTimestamp>
                <serviceUpTimestamp>1611512016403</serviceUpTimestamp>
            </leaseInfo>
            <metadata class="java.util.Collections$EmptyMap"/>
            <homePageUrl>https://localhost:5001/</homePageUrl>
            <statusPageUrl>https://localhost:5001/actuator/info</statusPageUrl>
            <healthCheckUrl>https://localhost:5001/actuator/health</healthCheckUrl>
            <vipAddress>weatherapi</vipAddress>
            <secureVipAddress>weatherapi</secureVipAddress>
            <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
            <lastUpdatedTimestamp>1611512017058</lastUpdatedTimestamp>
            <lastDirtyTimestamp>1611512016217</lastDirtyTimestamp>
            <actionType>ADDED</actionType>
        </instance>
    </application>
    

Query for instances by its instance id

  • GET method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}
    
    // Example
    http://localhost:8761/eureka/apps/weatherapi/localhost:weatherapi:5001
    
  • Response:
    <instance>
        <instanceId>localhost:weatherapi:5001</instanceId>
        <hostName>localhost</hostName>
        <app>WEATHERAPI</app>
        <ipAddr>localhost</ipAddr>
        <status>UP</status>
        <overriddenstatus>UNKNOWN</overriddenstatus>
        <port enabled="false">8080</port>
        <securePort enabled="true">5001</securePort>
        <countryId>1</countryId>
        <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
            <name>MyOwn</name>
        </dataCenterInfo>
        <leaseInfo>
            <renewalIntervalInSecs>30</renewalIntervalInSecs>
            <durationInSecs>90</durationInSecs>
            <registrationTimestamp>1611512017058</registrationTimestamp>
            <lastRenewalTimestamp>1611517116997</lastRenewalTimestamp>
            <evictionTimestamp>0</evictionTimestamp>
            <serviceUpTimestamp>1611512016403</serviceUpTimestamp>
        </leaseInfo>
        <metadata class="java.util.Collections$EmptyMap"/>
        <homePageUrl>https://localhost:5001/</homePageUrl>
        <statusPageUrl>https://localhost:5001/actuator/info</statusPageUrl>
        <healthCheckUrl>https://localhost:5001/actuator/health</healthCheckUrl>
        <vipAddress>weatherapi</vipAddress>
        <secureVipAddress>weatherapi</secureVipAddress>
        <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
        <lastUpdatedTimestamp>1611512017058</lastUpdatedTimestamp>
        <lastDirtyTimestamp>1611512016217</lastDirtyTimestamp>
        <actionType>ADDED</actionType>
    </instance>
    

Register an application instance

  • POST method:
    Headers: “Content-Type”: “application/json”, “Accept”: “application/json”

    http://localhost:8761/eureka/apps/{your app id}
    
    // Example
    http://localhost:8761/eureka/apps/myapp
    
  • Body:

    {
      "instance": {
        "hostName": "myhost",
        "app": "myapp",
        "vipAddress": "myservice",
        "secureVipAddress": "myservice",
        "ipAddr": "10.0.0.10",
        "status": "STARTING",
        "port": {"$": "8080", "@enabled": "true"},
        "securePort": {"$": "8443", "@enabled": "true"},
        "healthCheckUrl": "http://myservice:8080/healthcheck",
        "statusPageUrl": "http://myservice:8080/status",
        "homePageUrl": "http://myservice:8080",
        "dataCenterInfo": {
          "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", 
          "name": "MyOwn"
        }
      }
    }
    
  • Response: 204 No Content

As you will see from the status we’ve set this service to STARTING and hence we need set the status to UP when our service is running. Eureka, ofcourse, is just a registry and hence you service need to updated information as required.

Delete an instance

  • DELETE method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost
    
  • Response: 200 OK

Send an application instance heartbeat

  • PUT method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost
    
  • Response: 200 OK, or 404 if the instance doesn’t exist

Take an instance out of service

  • PUT method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}/status?value=OUT_OF_SERVICE
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost/status?value=OUT_OF_SERVICE
    
  • Response: 200 OK, or 500 on failure

Now in the Eureka dashboard you’ll see the large, red OUT_OF_SERVICE text.

Remove the out of service state

  • DELETE method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}/status?value=UP
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost/status?value=UP
    
  • Response: 200 OK, or 500 on failure

The status UP is optional, it’s a suggestion for the status after the removal of the OUT OF SERVICE override.

Update meta data

  • PUT method:
    http://localhost:8761/eureka/apps/{your app id}/{your instance id}/metadata?key=val
    
    // Example
    http://localhost:8761/eureka/apps/myapp/myhost/metadata?Version=2
    
  • Response: 200 OK, or 500 on failure

Query for all instance of a vip address

  • GET method:
    http://localhost:8761/eureka/vips/{your vipAddress}
    
    // Example
    http://localhost:8761/eureka/vips/myservice
    
  • Response: 200 OK, or 404 if vipAddress does not exist
    <applications>
        <versions__delta>-1</versions__delta>
        <apps__hashcode>UP_1_</apps__hashcode>
        <application>
            <name>MYAPP</name>
            <instance>
                <hostName>myhost</hostName>
                <app>MYAPP</app>
                <ipAddr>10.0.0.10</ipAddr>
                <status>UP</status>
                <overriddenstatus>UNKNOWN</overriddenstatus>
                <port enabled="true">8080</port>
                <securePort enabled="true">8443</securePort>
                <countryId>1</countryId>
                <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
                    <name>MyOwn</name>
                </dataCenterInfo>
                <leaseInfo>
                    <renewalIntervalInSecs>30</renewalIntervalInSecs>
                    <durationInSecs>90</durationInSecs>
                    <registrationTimestamp>1611519061947</registrationTimestamp>
                    <lastRenewalTimestamp>1611519061947</lastRenewalTimestamp>
                    <evictionTimestamp>0</evictionTimestamp>
                    <serviceUpTimestamp>1611518524498</serviceUpTimestamp>
                </leaseInfo>
                <metadata>
                    <Version>2</Version>
                </metadata>
                <homePageUrl>http://myservice:8080</homePageUrl>
                <statusPageUrl>http://myservice:8080/status</statusPageUrl>
                <healthCheckUrl>http://myservice:8080/healthcheck</healthCheckUrl>
                <vipAddress>myservice</vipAddress>
                <secureVipAddress>myservice</secureVipAddress>
                <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
                <lastUpdatedTimestamp>1611519061947</lastUpdatedTimestamp>
                <lastDirtyTimestamp>1611518524498</lastDirtyTimestamp>
                <actionType>ADDED</actionType>
            </instance>
        </application>
    </applications>
    

Query for all instance of a secure vip address

  • GET method:
    http://localhost:8761/eureka/svips/{your svipAddress}
    
    // Example
    http://localhost:8761/eureka/svips/myservice
    
  • Response: 200 OK, or 404 is svipAddress does not exist
    <applications>
        <versions__delta>-1</versions__delta>
        <apps__hashcode>UP_1_</apps__hashcode>
        <application>
            <name>MYAPP</name>
            <instance>
                <hostName>myhost</hostName>
                <app>MYAPP</app>
                <ipAddr>10.0.0.10</ipAddr>
                <status>UP</status>
                <overriddenstatus>UNKNOWN</overriddenstatus>
                <port enabled="true">8080</port>
                <securePort enabled="true">8443</securePort>
                <countryId>1</countryId>
                <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
                    <name>MyOwn</name>
                </dataCenterInfo>
                <leaseInfo>
                    <renewalIntervalInSecs>30</renewalIntervalInSecs>
                    <durationInSecs>90</durationInSecs>
                    <registrationTimestamp>1611519061947</registrationTimestamp>
                    <lastRenewalTimestamp>1611519061947</lastRenewalTimestamp>
                    <evictionTimestamp>0</evictionTimestamp>
                    <serviceUpTimestamp>1611518524498</serviceUpTimestamp>
                </leaseInfo>
                <metadata>
                    <Version>2</Version>
                </metadata>
                <homePageUrl>http://myservice:8080</homePageUrl>
                <statusPageUrl>http://myservice:8080/status</statusPageUrl>
                <healthCheckUrl>http://myservice:8080/healthcheck</healthCheckUrl>
                <vipAddress>myservice</vipAddress>
                <secureVipAddress>myservice</secureVipAddress>
                <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
                <lastUpdatedTimestamp>1611519061947</lastUpdatedTimestamp>
                <lastDirtyTimestamp>1611518524498</lastDirtyTimestamp>
                <actionType>ADDED</actionType>
            </instance>
        </application>
    </applications>
    

Eureka Discovery with Steeltoe outside of ASP.NET

Whilst the previous post showed how we use Steeltoe in an ASP.NET application, sometimes we might not want to (or be able) to use all the nice methods for automatic service discovery etc.

In this post we’re going to create a simple .NET core console application and interact with the discovery service at a slightly lower level.

So…

  • Create yourself a .NET core console application
  • Add the NuGet package Steeltoe.Discovery.Eureka

We need to configure the discovery client. You can use your own configuration method but for this example we’ll hard code parameters into a EurekaClientConfig object.

var config = new EurekaClientConfig
{
   EurekaServerServiceUrls = "http://localhost:8761/eureka/",
   ShouldFetchRegistry = true,
   ShouldRegisterWithEureka = false
};

We’re just writing a client, hence we don’t want to register the application with Eureka, but we do want to fetch registry data.

Next we will create the discovery client, access the applications and get our weatherapi application…

var discoveryClient = new DiscoveryClient(config);
var applications = discoveryClient.Applications;
var service = applications?.GetRegisteredApplication("weatherapi");

Finally we want to find instances of the services associated with the application which are UP and get the URL associated with one of the instances.

var instance = service?.Instances.FirstOrDefault(info => info.Status == InstanceStatus.UP);
if(instance != null)                    
{
   var client = new HttpClient
   {
      BaseAddress = new Uri(instance.HomePageUrl)
   };
   var response = await client.GetAsync("weatherforecast");
   Console.WriteLine(await response.Content.ReadAsStringAsync());
}

If an instance is found we just simply use HttpClient to invoke a GET method on the service and output the response to the console, just to prove everything worked.

This code doesn’t use any load balancing strategies, I’ll leave that to the reader to look into as in a real world scenario we wouldn’t want all clients to use the first instance only.

Creating an ASP.NET client using Eureka and Steeltoe

In the previous post Eureka Server (using Steeltoe) revisited I went through the process of running a Eureka instance and creating a default template based ASP.NET Web API which registers itself with the Eureka server as the application weatherapi.

Let’s now create a basic ASP.NET MVC project to interact with the Eureka server, get an instance of the API service and use it.

Note: This post is almost 100% based on the Channel 9 video Service Discovery with Steeltoe, so credit should go to Tim Hess for the sample code.

  • Create an ASP.NET Core application and then select Web Application (Model View Controller)
  • Add the following NuGet packages, Steeltoe.Discovery.ClientCore, Steeltoe.Discovery.Eureka and System.Net.Http.Json
  • Update the appsettings.json with the following
      "eureka": {
        "client": {
          "serviceUrl": "http://locahost:8761/eureka/",
          "shouldFetchRegistry": "true",
          "shouldRegisterWithEureka": false,
          "validateCertificates": false
        }
      }
    

    Notice we set shouldFetchRegistry to true as we want to get the latest registry information, and we set shouldRegisterWidthEureka to false as, in this case, we don’t want to register this client. Ofcourse change this is your client also exposes services.

  • Within Startup.cs, ConfigureServices add the following
    services.AddDiscoveryClient(Configuration);
    services.AddHttpClient("weather", 
       client => client.BaseAddress = new Uri("http://weatherapi/"))
          .AddServiceDiscovery();
    

    The interesting thing here is that we associate the name “weather” with an address which looks like a URL but really is the name of the service within Eureka that we want to access. Then, by using the AddServiceDiscovery this will be converted to an instance URL representing the instance of the service associated with the app name.

    Note: we can also use load balancing strategies, such as round robin or random.

  • Finally within the Startup.cs, method Configure, add the following
    app.UseDiscoveryClient();
    
  • We’re going to simply copy the WeatherForecast class from the service and add to the client, here it is

    public class WeatherForecast
    {
       public DateTime Date { get; set; }
       public int TemperatureC { get; set; }
       public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
       public string Summary { get; set; }
    }
    
  • Within HomeController.cs we need to add a variable of type IHttpClientFactory which will be injected into the constructor and then uses to call defined HTTP client, this will then used service discovery to return the URL of an instance (as already discussed) and then we’re use that to call the weather API to get a list of values. Here’s the changes required to HomeController.cs
    private readonly IHttpClientFactory _httpClientFactory;
    
    public HomeController(IHttpClientFactory httpClientFactory, ILogger<HomeController> logger)
    {
       _httpClientFactory = httpClientFactory;
       _logger = logger;
    }
    
    public async Task<IActionResult> Index()
    {
       var client = _httpClientFactory.CreateClient("weather");
       var weather = await client.GetFromJsonAsync<IList<WeatherForecast>>("weatherforecast");
       return View(weather);
    }
    
  • Finally, let’s change the Index.cshtml to display the weather forecast data returned in the Index method. Firstly we declare the @model and then simply create a table to output the items from that model in columns and rows, so here’s the change to be added to the top of the file
    @model IList<EurekaWebClient.Controllers.WeatherForecast>
    

    and within the main div, just add the following

    <table class="table">
       <tr><th>Day of Week</th><th>Summary</th><th>Temp</th></tr>
       @foreach (var weather in Model)
       {
          <tr><td>@weather.Date.DayOfWeek</td><td>@weather.Summary</td><td>@weather.TemperatureF</td></tr>
       }
    </table>
    

That should be it. Ensure Eureka is running, your service is up and you should now see the weather service data in a nice little table.

Eureka Server (using Steeltoe) revisited

The Eureka server is used as a registry for maintaining lists of services and their endpoints. It’s used a lot in the microservice world by way of a microservice registering it’s existence somewhere (in this case in Eureka). When a client (whether it’s another service or anything else for that matter) want to access a service it asks the registry for an instance, in this case we connect to the Eureka server and find the services instance(s) that are available for a specific application name and are ofcourse UP.

Installing and running Eureka Server

In a previous post Spring boot Eureka server we wrote a Java application to run a Eureka server. In this post we’re going to use Docker to host the server and then use Steeltoe with .NET to interact with the instance.

To get an image of Eureka server, let’s use the Steeltoe docker image (Spring and others exist, the Steeltoe image is not intended for production, but is fine for what we want to do)

docker pull steeltoeoss/eureka-server

Now run up the docker image using

docker run --publish 8761:8761 steeltoeoss/eureka-server

If all goes well, connect to the Spring Eureka dashboard using

http://locahost:8761

change localhost to the ip/host you’re running the Eureka server from, now you should now see the Spring Eureka web page.

Registering a .NET core client with Eureka

I’ve a post on this topic A .NET service registered with Eureka, but let’s go through the process again with the current version of Steeltoe (as the NuGet packages have changed somewhat).

  • Create an ASP.NET Core Web Application, as this will represent a REST service that we want to interact with. My project is called RegisterExample and is an API project.
  • Add the NuGet package Steeltoe.Discovery.ClientCore and Steeltoe.Discovery.Eureka
  • In Startup.cs within ConfigureServices add the following
    services.AddDiscoveryClient(Configuration);
    
  • Within the Configure method add the following
    app.UseDiscoveryClient();
    
  • Finally add the following to the appsettings.json file
    // Eureka info
      "eureka": {
        "client": {
          "serviceUrl": "http://localhost:8761/eureka/",
          "shouldFetchRegistry": "false",
          "shouldRegisterWithEureka": true,
          "validateCertificates": false
        },
        "instance": {
          "appName": "weatherapi",
          "port": "8080",
          "ipAddress": "localhost",
          "preferIpAddress": true
        }
      }
    

We’re going to leave the API with the default weatherapi, hence the appName in the appsettings.

Notice we set the value for “shouldFetchRegistry” to false as this service will not be acting as a client to any other services. Obviously change this is you also need to discovery other services. “shouldRegisterWithEureka” is set to true as we want this service to automatically register itself with Eureka.

Now navigate to the URL of your Eureka server again (or refresh) and you should see a new Application. In my case I have an application with the name weatherapi. This name comes from our appsettings.json configuration application/name.

Info and Health

If you click on the instance link within the Eureka server dashboard, you will navigate to

https://localhost:5001/info

(or whatever ip/hostname and port your service is running on) you get a 404, so let’s fix that in our project.

Info, by default will display some basic information about the application, product version etc. However you can also add further custom information if you want, but example the git build SHA1 hash or just some general info.

  • Add NuGet package Steeltoe.Management.EndpointCore
  • In Startup.cs ConfigureServices, add the following
    services.AddSingleton<IInfoContributor, MyInfoContributor>();
    services.AddInfoActuator(Configuration);
    services.AddHealthActuator(Configuration);
    

    The first of these lines will add our implementation of an IInfoContributor to allow for custom info.

  • Still in Startup.cs, but now the method Configure, add the following to the UseEndpoints endpoint routes

    endpoints.Map<InfoEndpoint>();
    endpoints.Map<HealthEndpoint>();
    
  • Now we’ll create a simple implementation of and IInfoContributor which allows us to add our own info, so add the following class

    public class MyInfoContributor : IInfoContributor
    {
       public void Contribute(IInfoBuilder builder)
       {
          builder.WithInfo("MyInfo", new { SomeName = "Scooby Doo" });
       }
    }
    

Now when we run our service we hope to see our info data, however by default Steeltoe seems to set the info and health endpoint to /actuator/info and /actuator/health respectively. Eureka seems to expect /info. So go to the appsettings.json and add the following to the Instance section

"StatusPageUrlPath": "/actuator/info",
"HealthCheckUrlPath": "/actuator/health" 

Note: I’m not sure what I’m missing here and why the defaults don’t match up, but these configuration changes will tell the Eureka server (when we register our service with it) that it should use these paths for info and health.

Now, if you run the service again for /actuator/info you should see something like this

{"MyInfo":{"someName":"Scooby Doo"},
"applicationVersionInfo":{"ProductName":"RegisterExample",
"FileVersion":"1.0.0.0","ProductVersion":"1.0.0"},
"steeltoeVersionInfo":"ProductName":"Steeltoe.Management.Endpoint",
"FileVersion":"3.0.2.0","ProductVersion":
"3.0.2\u002B4089779c66d127f40325a3be9b613149b3b090f2"}}

and health something like

{"status":"UP","details":{"liveness":{},"diskSpace":{"total":4000769372160,
"free":3889734168576,"threshold":10485760,"status":"UP"},"eurekaServer":
{"remoteInstStatus":"UNKNOWN","fetchStatus":"Not fetching","heartbeat":
"Successful","heartbeatStatus":"UP","heartbeatTime":"2021-01-23T20:40:44",
"status":"UP","applications":"NONE"},"readiness":{}}}

A .NET service registered with Eureka

To create a .NET service which is registered with Eureka we’ll use the Steeltoe libraries…

Create an ASP.NET Core Web Application and select WebApi. Add the Steeltoe.Discovery.Client NuGet package, now in the Startup.cs within ConfigureServices add the following

services.AddDiscoveryClient(Configuration);

and within the Configure method add

app.UseDiscoveryClient();

We’re not going to create any other code to implement a service, but we will need some configuration added to the appsettings.json file, so add the following

"spring": {
    "application": {
      "name": "eureka-test-service"
    }
  },
  "eureka": {
    "client": {
      "serviceUrl": "http://localhost:8761/eureka/",
      "shouldRegisterWithEureka": true,
      "shouldFetchRegistry": false
    },
    "instance": {
      "hostname": "localhost",
      "port": 5000
    }
  }

The application name is what’s going to be displayed within Eureka and visible via http://localhost:8761/eureka/apps (obviously replacing the host name and ip with your Eureka server’s).

Spring boot Eureka server

Using Spring boot, we can very easily run up a Eureka server.

In IntelliJ create a new project using the Spring Initializr, simply select the Eureka Server dependency.

Once the application is create, I found I needed to add the @EnableEurekaServer annotation to the application class, so here’s my EurekatesApplication.java

package com.putridparrot.eurekatest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekatestApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekatestApplication.class, args);
    }
}

Next we need to add the following properties to the resources/application.properties

server.port=8761

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

logging.level.com.netflix.eureka=OFF
logging.level.com.netflix.discovery=OFF

Now when you run the application and view the web page http://localhost:8761/, you should see the Spring Eureka web page. Running http://localhost:8761/eureka/apps from your preferred browser will list any applications registered with the server.

At this point we’ve not application registered with the server, so let’s add a .NET server in the next post.