Skip to content

Swift Service Discovery implementation for Kubernetes on Swiftkube

License

Notifications You must be signed in to change notification settings

swiftkube/servicediscovery

Repository files navigation

Swiftkube:ServiceDiscovery

Kubernetes 1.32.0 Swift Package Manager CI Status

An implementation of the Swift Service Discovery API for Kubernetes based on SwiftkubeClient.

Table of contents

#Overview

  • Auto-configuration for different environments
  • Support for all Kubernetes ListOptions
  • Discovery for any listable Kubernetes resource
  • Support for reconnect and retry
  • Complete documentation
  • End-to-end tests

Usage

Creating a service discovery

To use this service discovery import SwiftkubeServiceDiscovery and init an instance.

SwiftkubeServiceDiscovery is generic. Thus, instances must be specialized and are therefore bound to a specific KubernetesResouce type and its GroupVersionResource.

import SwiftkubeServiceDiscovery

let podDiscovery = SwiftkubeServiceDiscovery<core.v1.Pod>()
let serviceDiscovery = SwiftkubeServiceDiscovery<core.v1.Service>()

Underneath, SwiftkubeServiceDiscovery uses SwiftkubeClient for all Kubernetes communication, which configures itself automatically for the environment it runs in.

However, you can also pass an existing client instance to the service discovery:

let config = KubernetesClientConfig(
   masterURL: "https://kubernetesmaster",
   namespace: "default",
   authentication: authentication,
   trustRoots: NIOSSLTrustRoots.certificates(caCert),
   insecureSkipTLSVerify: false,
   timeout: HTTPClient.Configuration.Timeout.init(connect: .seconds(1), read: .seconds(10)),
   redirectConfiguration: HTTPClient.Configuration.RedirectConfiguration.follow(max: 5, allowCycles: false)
)
let client = KubernetesClient(config: config)

let discovery = SwiftkubeServiceDiscovery<core.v1.Service>(client: client)

You should shut down the SwiftkubeServiceDiscovery instance when you're done using it, which in turn shuts down the underlying SwiftkubeClient. Thus, you shouldn't call discovery.shutdown() before all requests have finished.

You can also shut down the client asynchronously in an async/await context or by providing a DispatchQueue for the completion callback.

// when finished close the client
 try discovery.syncShutdown()
 
// async/await
try await discovery.shutdown()

// DispatchQueue
let queue: DispatchQueue = ...
discovery.shutdown(queue: queue) { (error: Error?) in 
    print(error)
}

Configuration

You can configure the service discovery by passing a SwiftkubeDiscoveryConfiguration instance.

Currently, the following configuration options are supported:

  • RetryStrategy: A retry strategy to control the reconnect-behaviour for the underlying client in case of non-recoverable errors when serving service discovery subscriptions.
let strategy = RetryStrategy(
    policy: .maxAttemtps(20),
    backoff: .exponentiaBackoff(maxDelay: 60, multiplier: 2.0),
    initialDelay = 5.0,
    jitter = 0.2
)

let config = Configuration(retryStrategy: strategy)
let discovery = SwiftkubeServiceDiscovery<core.v1.Service>(config: config)

Lookup resources

To lookup Kubernetes resources you have to pass an instance of LookupObject. You can specify the namespaces to search and provide a list of selectors to filter the desired objects.

SwiftkubeServiceDiscovery lookups return a list of resources of the type specified by the generic specialization:

let podDiscovery = SwiftkubeServiceDiscovery<core.v1.Pod>()

let object = LookupObject(
    namespace: .allNamespaces,
    options: [
        .labelSelector(.exists(["app", "env"])),
        .labelSelector(.eq(["app": "nginx"])),
        .labelSelector(.notIn(["env": ["dev", "staging"]])),    
    ]
)

discovery.lookup(object) { result in
    switch result {
    case let .success(pods):
         pods.forEach { pod in
             print("\(pod.name), \(pod.namespace): \(pod.status?.podIP)")
         }
    case let .failure(error):
        // handle error
    }
}

Subscribtions

You can subscribe to service lookups the same way, by providing a LookupObject:

let serviceDiscovery = SwiftkubeServiceDiscovery<core.v1.Service>()

let token = discovery.subscribe(to: object) { result in
    switch result {
    case let .success(service):
        pods.forEach {
            print("\(service.name), \(service.namespace): \(service.spec?.ports)")
        }
    case let .failure(error):
        print(error)
    }
} onComplete: { reason in
    print(reason)
}

token.cancel()

The client will try to reconnect to the API server according to the configured RetryStrategy and will serve updates until the subscription token is cancelled.

Custom Resources

TBD

Strict Concurrency

TBD

RBAC

TBD

Installation

To use the SwiftkubeServiceDiscovery in a SwiftPM project, add the following line to the dependencies in your Package.swift file:

.package(name: "SwiftkubeServiceDiscovery", url: "https://github.com/swiftkube/servicediscovery.git", from: "0.3.0")

then include it as a dependency in your target:

import PackageDescription

let package = Package(
    // ...
    dependencies: [
        .package(name: "SwiftkubeServiceDiscovery", url: "https://github.com/swiftkube/servicediscovery.git", from: "0.2.0")
    ],
    targets: [
        .target(name: "<your-target>", dependencies: [
            .product(name: "SwiftkubeServiceDiscovery", package: "servicediscovery"),
        ])
    ]
)

Then run swift build.

License

Swiftkube project is licensed under version 2.0 of the Apache License. See LICENSE for more details.