-
Notifications
You must be signed in to change notification settings - Fork 1.7k
#2168 A brand-new WatchKube provider for Kubernetes service discovery
#2174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
df406fe
9e1ad79
f53186e
c17beb8
ce666d0
4251741
a106d05
38e427d
6895224
72dfaa8
171b5f3
81d38d2
30ac712
061f018
28a3afe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using Ocelot.Provider.Kubernetes.Interfaces; | ||
|
|
||
| namespace Ocelot.Provider.Kubernetes; | ||
|
|
||
| public static class KubeApiClientExtensions | ||
| { | ||
| public static IEndPointClient EndpointsV1(this IKubeApiClient client) | ||
| => client.ResourceClient<IEndPointClient>(x => new EndPointClientV1(x)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| using System.Reactive.Concurrency; | ||
| using System.Reactive.Linq; | ||
|
|
||
| namespace Ocelot.Provider.Kubernetes; | ||
|
|
||
| public static class ObservableExtensions | ||
| { | ||
| public static IObservable<TSource> RetryAfter<TSource>(this IObservable<TSource> source, | ||
| TimeSpan dueTime, | ||
| IScheduler scheduler) | ||
| { | ||
| return RepeatInfinite(source, dueTime, scheduler).Catch(); | ||
raman-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private static IEnumerable<IObservable<TSource>> RepeatInfinite<TSource>(IObservable<TSource> source, | ||
| TimeSpan dueTime, | ||
| IScheduler scheduler) | ||
| { | ||
| yield return source; | ||
|
|
||
| while (true) | ||
| { | ||
| yield return source.DelaySubscription(dueTime, scheduler); | ||
| } | ||
raman-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| using KubeClient.Models; | ||
| using Ocelot.Logging; | ||
| using Ocelot.Provider.Kubernetes.Interfaces; | ||
| using Ocelot.Values; | ||
| using System.Reactive.Concurrency; | ||
| using System.Reactive.Linq; | ||
|
|
||
| namespace Ocelot.Provider.Kubernetes; | ||
|
|
||
| public class WatchKube : IServiceDiscoveryProvider, IDisposable | ||
| { | ||
| internal const int FailedSubscriptionRetrySeconds = 5; | ||
| internal const int FirstResultsFetchingTimeoutSeconds = 3; | ||
|
||
|
|
||
| private readonly KubeRegistryConfiguration _configuration; | ||
| private readonly IOcelotLogger _logger; | ||
| private readonly IKubeApiClient _kubeApi; | ||
| private readonly IKubeServiceBuilder _serviceBuilder; | ||
| private readonly IScheduler _scheduler; | ||
|
|
||
| private readonly IDisposable _subscription; | ||
| private readonly TaskCompletionSource _firstResultsCompletionSource; | ||
|
|
||
| private List<Service> _services = new(); | ||
|
|
||
| public WatchKube( | ||
| KubeRegistryConfiguration configuration, | ||
| IOcelotLoggerFactory factory, | ||
| IKubeApiClient kubeApi, | ||
| IKubeServiceBuilder serviceBuilder, | ||
| IScheduler scheduler) | ||
| { | ||
| _configuration = configuration; | ||
| _logger = factory.CreateLogger<WatchKube>(); | ||
| _kubeApi = kubeApi; | ||
| _serviceBuilder = serviceBuilder; | ||
| _scheduler = scheduler; | ||
|
|
||
| _firstResultsCompletionSource = new TaskCompletionSource(); | ||
| SetFirstResultsCompletedAfterDelay(); | ||
| _subscription = CreateSubscription(); | ||
| } | ||
|
|
||
| public virtual async Task<List<Service>> GetAsync() | ||
| { | ||
| // wait for first results fetching | ||
| await _firstResultsCompletionSource.Task; | ||
|
|
||
| if (_services is not { Count: > 0 }) | ||
raman-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| _logger.LogWarning(() => GetMessage("Subscription to service endpoints gave no results!")); | ||
| } | ||
|
|
||
| return _services; | ||
| } | ||
|
|
||
| private void SetFirstResultsCompletedAfterDelay() => Observable | ||
| .Timer(TimeSpan.FromSeconds(FirstResultsFetchingTimeoutSeconds), _scheduler) | ||
| .Subscribe(_ => _firstResultsCompletionSource.TrySetResult()); | ||
raman-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| private IDisposable CreateSubscription() => | ||
| _kubeApi | ||
| .EndpointsV1() | ||
| .Watch(_configuration.KeyOfServiceInK8s, _configuration.KubeNamespace) | ||
| .Do(_ => { }, ex => _logger.LogError(() => GetMessage("Endpoints subscription error occured."), ex)) | ||
raman-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .RetryAfter(TimeSpan.FromSeconds(FailedSubscriptionRetrySeconds), _scheduler) | ||
| .Subscribe( | ||
| onNext: endpointEvent => | ||
raman-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| _services = endpointEvent.EventType switch | ||
| { | ||
| ResourceEventType.Deleted or ResourceEventType.Error => new(), | ||
| _ when (endpointEvent.Resource?.Subsets?.Count ?? 0) == 0 => new(), | ||
| _ => _serviceBuilder.BuildServices(_configuration, endpointEvent.Resource).ToList(), | ||
| }; | ||
raman-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _firstResultsCompletionSource.TrySetResult(); | ||
| }, | ||
| onCompleted: () => | ||
| { | ||
| // called only when subscription canceled in Dispose | ||
| _logger.LogInformation(() => GetMessage("Subscription to service endpoints completed")); | ||
| }); | ||
|
|
||
| private string GetMessage(string message) | ||
| => $"{nameof(WatchKube)} provider. Namespace:{_configuration.KubeNamespace}, Service:{_configuration.KeyOfServiceInK8s}; {message}"; | ||
|
|
||
| public void Dispose() => _subscription.Dispose(); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.