Skip to content

Commit

Permalink
[#1527 #1529] Breaking change to the class renaming of Kube service…
Browse files Browse the repository at this point in the history
… discovery provider (#1954)

* Update kubernetes.rst

The `Type` field for the kubernetes example is outdated

* Rename the class back to `Kube` making it as default K8s provider

* Endpoint mocking for `GetAsync()` of `Kube` provider

* Switch off timer

* Update K8s docs

* IDE1006: Naming rule violation

* Follow .NET conventions for test class names

---------

Co-authored-by: Raman Maksimchuk <dotnet044@gmail.com>
  • Loading branch information
ZisisTsatsas and raman-m committed Feb 12, 2024
1 parent c9510b1 commit a1607f5
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 180 deletions.
61 changes: 39 additions & 22 deletions docs/features/kubernetes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@
:alt: K8s Logo
:width: 40

|K8s Logo| Kubernetes
=====================
|K8s Logo| Kubernetes [#f1]_ aka K8s
====================================

Feature: :doc:`../features/servicediscovery`
A part of feature: :doc:`../features/servicediscovery`

This feature was requested as part of `issue 345 <https://github.com/ThreeMammals/Ocelot/issues/345>`_ to add support for `Kubernetes <https://kubernetes.io/>`_ service discovery provider.
About [#f2]_
------------

Ocelot will call the K8s endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them.
Ocelot used to use the services API to send requests to the K8s service but this was changed in `PR 1134 <https://github.com/ThreeMammals/Ocelot/pull/1134>`_ because the service did not load balance as expected.
Ocelot will call the `K8s <https://kubernetes.io/>`_ endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them.
Ocelot used to use the services API to send requests to the `K8s <https://kubernetes.io/>`__ service but this was changed in `PR 1134 <https://github.com/ThreeMammals/Ocelot/pull/1134>`_ because the service did not load balance as expected.

The first thing you need to do is install the `NuGet package <https://www.nuget.org/packages/Ocelot.Provider.Kubernetes>`_ that provides Kubernetes support in Ocelot:
Install
-------

The first thing you need to do is install the `NuGet package <https://www.nuget.org/packages/Ocelot.Provider.Kubernetes>`_ that provides **Kubernetes** [#f1]_ support in Ocelot:

.. code-block:: powershell
Install-Package Ocelot.Provider.Kubernetes
Then add the following to your ConfigureServices method:
Then add the following to your ``ConfigureServices`` method:

.. code-block:: csharp
Expand All @@ -41,47 +45,53 @@ K8s API server and token will read from pod.
kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts
The following example shows how to set up a Route that will work in Kubernetes.
Configuration
-------------

The following examples show how to set up a Route that will work in Kubernetes.
The most important thing is the **ServiceName** which is made up of the Kubernetes service name.
We also need to set up the **ServiceDiscoveryProvider** in **GlobalConfiguration**.

Kube default provider
^^^^^^^^^^^^^^^^^^^^^

The example here shows a typical configuration:

.. code-block:: json
{
"Routes": [
{
"DownstreamPathTemplate": "/api/values",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/values",
"ServiceName": "downstreamservice",
"UpstreamHttpMethod": [ "Get" ]
// ...
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Host": "192.168.0.13",
"Port": 443,
"Token": "txpc696iUhbVoudg164r93CxDTrKRVWG",
"Namespace": "dev",
"Type": "kube"
"Namespace": "Dev",
"Type": "Kube"
}
}
}
Service deployment in **Namespace** ``dev``, **ServiceDiscoveryProvider** type is ``kube``, you also can set ``pollkube`` **ServiceDiscoveryProvider** type.
Service deployment in **Namespace** ``Dev``, **ServiceDiscoveryProvider** type is ``Kube``, you also can set :ref:`kubernetes-pollkube-provider` type.
Note: **Host**, **Port** and **Token** are no longer in use.

.. kubernetes-pollkube-provider:
PollKube provider
^^^^^^^^^^^^^^^^^

You use Ocelot to poll Kubernetes for latest service information rather than per request.
If you want to poll Kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration:

.. code-block:: json
"ServiceDiscoveryProvider": {
// ...
"Namespace": "dev",
"Type": "pollkube",
"PollingInterval": 100
"Type": "PollKube",
"PollingInterval": 100 // ms
}
The polling interval is in milliseconds and tells Ocelot how often to call Kubernetes for changes in service configuration.
Expand All @@ -92,14 +102,21 @@ This really depends on how volatile your services are.
We doubt it will matter for most people and polling may give a tiny performance improvement over calling Kubernetes per request.
There is no way for Ocelot to work these out for you.

Global vs Route levels
^^^^^^^^^^^^^^^^^^^^^^

If your downstream service resides in a different namespace, you can override the global setting at the Route-level by specifying a **ServiceNamespace**:

.. code-block:: json
"Routes": [
{
// ...
"ServiceName": "downstreamservice",
"ServiceNamespace": "downstream-namespace"
}
]
""""

.. [#f1] `Wikipedia <https://en.wikipedia.org/wiki/Kubernetes>`_ | `K8s Website <https://kubernetes.io/>`_ | `K8s Documentation <https://kubernetes.io/docs/>`_ | `K8s GitHub <https://github.com/kubernetes/kubernetes>`_
.. [#f2] This feature was requested as part of `issue 345 <https://github.com/ThreeMammals/Ocelot/issues/345>`_ to add support for `Kubernetes <https://kubernetes.io/>`_ :doc:`../features/servicediscovery` provider.
4 changes: 2 additions & 2 deletions src/Ocelot.Provider.Kubernetes/EndPointClientV1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Ocelot.Provider.Kubernetes
{
public class EndPointClientV1 : KubeResourceClient
public class EndPointClientV1 : KubeResourceClient, IEndPointClient
{
private readonly HttpRequest _collection;

Expand All @@ -13,7 +13,7 @@ public EndPointClientV1(IKubeApiClient client) : base(client)
_collection = KubeRequest.Create("api/v1/namespaces/{Namespace}/endpoints/{ServiceName}");
}

public async Task<EndpointsV1> Get(string serviceName, string kubeNamespace = null, CancellationToken cancellationToken = default)
public async Task<EndpointsV1> GetAsync(string serviceName, string kubeNamespace = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(serviceName))
{
Expand Down
9 changes: 9 additions & 0 deletions src/Ocelot.Provider.Kubernetes/IEndPointClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using KubeClient.Models;
using KubeClient.ResourceClients;

namespace Ocelot.Provider.Kubernetes;

public interface IEndPointClient : IKubeResourceClient
{
Task<EndpointsV1> GetAsync(string serviceName, string kubeNamespace = null, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@

namespace Ocelot.Provider.Kubernetes;

public class KubernetesServiceDiscoveryProvider : IServiceDiscoveryProvider
/// <summary>
/// Default Kubernetes service discovery provider.
/// </summary>
public class Kube : IServiceDiscoveryProvider
{
private readonly KubeRegistryConfiguration _kubeRegistryConfiguration;
private readonly IOcelotLogger _logger;
private readonly IKubeApiClient _kubeApi;

public KubernetesServiceDiscoveryProvider(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClient kubeApi)
public Kube(KubeRegistryConfiguration kubeRegistryConfiguration, IOcelotLoggerFactory factory, IKubeApiClient kubeApi)
{
_kubeRegistryConfiguration = kubeRegistryConfiguration;
_logger = factory.CreateLogger<KubernetesServiceDiscoveryProvider>();
_logger = factory.CreateLogger<Kube>();
_kubeApi = kubeApi;
}

public async Task<List<Service>> GetAsync()
{
var endpoint = await _kubeApi
.ResourceClient(client => new EndPointClientV1(client))
.Get(_kubeRegistryConfiguration.KeyOfServiceInK8s, _kubeRegistryConfiguration.KubeNamespace);
.GetAsync(_kubeRegistryConfiguration.KeyOfServiceInK8s, _kubeRegistryConfiguration.KubeNamespace);

var services = new List<Service>();
if (endpoint != null && endpoint.Subsets.Any())
Expand Down
8 changes: 4 additions & 4 deletions src/Ocelot.Provider.Kubernetes/KubernetesProviderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ private static IServiceDiscoveryProvider CreateProvider(IServiceProvider provide
var factory = provider.GetService<IOcelotLoggerFactory>();
var kubeClient = provider.GetService<IKubeApiClient>();

var k8SRegistryConfiguration = new KubeRegistryConfiguration
var configuration = new KubeRegistryConfiguration
{
KeyOfServiceInK8s = route.ServiceName,
KubeNamespace = string.IsNullOrEmpty(route.ServiceNamespace) ? config.Namespace : route.ServiceNamespace,
};

var k8SServiceDiscoveryProvider = new KubernetesServiceDiscoveryProvider(k8SRegistryConfiguration, factory, kubeClient);
var defaultK8sProvider = new Kube(configuration, factory, kubeClient);

return PollKube.Equals(config.Type, StringComparison.OrdinalIgnoreCase)
? new PollKube(config.PollingInterval, factory, k8SServiceDiscoveryProvider)
: k8SServiceDiscoveryProvider;
? new PollKube(config.PollingInterval, factory, defaultK8sProvider)
: defaultK8sProvider;
}
}
}

0 comments on commit a1607f5

Please sign in to comment.