Skip to content

Running the Discovery Service

This section highlights how to run up the full KXI Discovery Service including the registry and the API proxy layers. This will allow any service in any language to interact with the Discovery Service over a openAPI defined specification; register themselves and get information about other running processes.

This sections outlines how to run the Service in both Docker and Kubernetes (with Helm). There are basic prerequisites necessary to go through to ensure the Service can be run depending on the tool (Docker/Kubernetes) running the service.

Docker example

Running the KXI Discovery Service can be achieved using Docker. This requires running both the Discovery Registry and the Discovery Proxy

  • Create a kx docker bridge network
docker network create kx
  • Run the Discovery Registry
docker run -it --rm \
    --network=kx \
    --name=registry \
    -e JAVA_OPTS="-Deureka.instance.appname=DOCKER_EX -Deureka.server.responseCacheUpdateIntervalMs=1000" \
    -p 8761:8761 \
    registry.dl.kx.com/kxi-eureka-discovery:1.0.0

This will run the Discovery Regsitry with the embedded configuration detailed here. To provide a custom configuration with advanced settings, please review here for details in how to provide a custom configuration or JAVA_OPTS.

  • Run the Discovery Proxy

The proxy requires a configuration file in order to know what adaptor to load and where to find the registry. This file should be JSON and the location provided to the service using an environment variable. It also requires a KDB+ CE license to be provided as a base64 string. Info on how to do this here

echo '{"discovery":{"adaptor":"discEurekaAdaptor.q","registry":"registry:8761"}}' > config.json
# Run
docker run -it --rm \
    --name=discproxy \
    --network=kx \
    --env KXI_CONFIG_FILE=/opt/kx/config/config.json \
    -e "KDB_LICENSE_B64=$KDB_LICENSE_B64" \
    -p 5000:5000 \
    -v $(pwd):/opt/kx/config \
    registry.dl.kx.com/kxi-discovery-proxy:0.8.2 -p 5000

This will build and run a local eureka registry which can be accessed at http://localhost:8761.

The Discovery Proxy is connected to the Registry and is available at: - REST: http://localhost:5000 - qIPC: `:localhost:5000

Docker compose example

The same can be achieved via docker-compose which allows for a structured approach to the KXI Discovery Service runtime.

As per the docker example, the proxy requires the same configuration file config.json intialised there and a KDB+ CE license to be provided as a base64 string.

docker-compose.yml

version: "3.7"
# All our processes will run on a dedicated network 'kx'
networks:
  kx:
    name: kx
    driver: bridge
# Here we define the services we wish to run
services:
  registry:
    image: registry.dl.kx.com/kxi-eureka-discovery:1.0.0
    environment:
       - JAVA_OPTS=-Deureka.instance.appname=DOCKER_COMPOSE_EX -Deureka.server.responseCacheUpdateIntervalMs=1000
    # Expose port 8761 locally
    ports:
      - 8761:8761
    networks:
      - kx

  proxy:
    image: registry.dl.kx.com/kxi-discovery-proxy:0.8.2
    ports:
      - 5000:5000
    environment:
      - KXI_CONFIG_FILE=/opt/kx/config/config.json
      - KDB_LICENSE_B64=$KDB_LICENSE_B64
    volumes:
      - ./:/opt/kx/config
    command: -p 5000
    networks:
      - kx
docker-compose -f docker-compose.yml up

Like the Docker example this will run the Discovery Regsitry with the embedded configuration detailed here though advanced configurations can be provided through an environment CUSTOM_CONFIG or JAVA_OPTS. Full details here.

Multi-node example

For a fully distributed fault tolerant service it is necessary to run the KXI Discovery Service in multi-node mode. This requires more configuration for each of the components and docker-compose file

  • Set up a custom Discovery Registry configuration -

application_3nodeRegistry.yaml

spring:
  profiles: default
  application:
    name: KXI-DISCOVERY

server:
  port: 8761
  tomcat:
    accesslog:
      enabled: true
      suffix: .log
      prefix: access_${POD_HOST}
      directory: /data/logs

eureka:
  server:
    enableSelfPreservation: false
    evictionIntervalTimerInMs: 5000
    renewalPercentThreshold: 0.850000
    responseCacheUpdateIntervalMs: 1000
  instance:
    hostname: ${POD_HOST}
    preferIpAddress: false
    leaseRenewalIntervalInSeconds: 30
    leaseExpirationDurationInSeconds: 90
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://registry-0:8761/eureka/,http://registry-1:8761/eureka/,http://registry-2:8761/eureka/
  • Set up a Discovery Proxy configuration file for each node
echo '{"discovery":{"adaptor":"discEurekaAdaptor.q","registry":"registry-0:8761"}}' > proxyConfig-0.json
export KXI_CONFIG_FILE=$(pwd)/proxyConfig-0.json
echo '{"discovery":{"adaptor":"discEurekaAdaptor.q","registry":"registry-1:8761"}}' > proxyConfig-1.json
export KXI_CONFIG_FILE=$(pwd)/proxyConfig-1.json
echo '{"discovery":{"adaptor":"discEurekaAdaptor.q","registry":"registry-2:8761"}}' > proxyConfig-2.json
export KXI_CONFIG_FILE=$(pwd)/proxyConfig-2.json
  • Set up a docker-compose3.yml
version: "3.7"
# Volume to load config file
volumes:
  config:
# All processes will run on a dedicated network 'kx'
networks:
  kx:
    name: kx
    driver: bridge
# Here we define the services we wish to run
services:
  registry-0:
    image: registry.dl.kx.com/kxi-eureka-discovery:1.0.0
    # Expose port 8761 (default reigstry port)
    ports:
      - 9000:8761
    environment:
      # - JAVA_OPTS=-Dlogging.level.org.springframework=DEBUG
      - CUSTOM_CONFIG=/data/config/application_3nodeRegistry.yaml
      - POD_HOST=registry-0
    networks:
      - kx
    volumes:
      - ./:/data/config
      - ./logs:/data/logs

  proxy-0:
    image: registry.dl.kx.com/kxi-discovery-proxy:0.8.2
    ports:
      - 5000:5000
    environment:
      - KXI_CONFIG_FILE=/opt/kx/config/proxyConfig-0.json
      - KDB_LICENSE_B64=$KDB_LICENSE_B64
    volumes:
      - ./:/opt/kx/config
    command: -p 5000
    networks:
      - kx

  registry-1:
    image: registry.dl.kx.com/kxi-eureka-discovery:1.0.0
    # Expose port 8761 (default reigstry port)
    ports:
      - 9001:8761
    environment:
      #- JAVA_OPTS=-Dlogging.level.org.springframework=DEBUG
      - CUSTOM_CONFIG=/data/config/application_3nodeRegistry.yaml
      - POD_HOST=registry-1
    networks:
      - kx
    volumes:
      - ./:/data/config
      - ./logs:/data/logs

  proxy-1:
    image: registry.dl.kx.com/kxi-discovery-proxy:0.8.2
    ports:
      - 5001:5000
    environment:
      - KXI_CONFIG_FILE=/opt/kx/config/proxyConfig-1.json
      - KDB_LICENSE_B64=$KDB_LICENSE_B64
    volumes:
      - ./:/opt/kx/config
    command: -p 5000
    networks:
      - kx

  registry-2:
    image: registry.dl.kx.com/kxi-eureka-discovery:1.0.0
    # Expose port 8761 (default reigstry port)
    ports:
      - 9002:8761
    environment:
      #- JAVA_OPTS=-Dlogging.level.org.springframework=DEBUG
      - CUSTOM_CONFIG=/data/config/application_3nodeRegistry.yaml
      - POD_HOST=registry-2
    networks:
     - kx
    volumes:
      - ./:/data/config
      - ./logs:/data/logs

  proxy-2:
    image: registry.dl.kx.com/kxi-discovery-proxy:0.8.2
    ports:
      - 5002:5000
    environment:
      - KXI_CONFIG_FILE=/opt/kx/config/proxyConfig-2.json
      - KDB_LICENSE_B64=$KDB_LICENSE_B64
    volumes:
      - ./:/opt/kx/config
    command: -p 5000
    networks:
      - kx  
  • Run the Discovery service
docker-compose -f docker-compose3.yml up

Kubernetes

A Helm chart for the KXI Discovery Service has been provided to enable the use of the service within a Kubernetes orchastrated system. To deploy this chart, it will be necessary to ensure a number of pre-requisites are set up. Once set up deploying this chart is straight forward.

  • Create a Kx License secret in the Kubernetes cluster

Here we created a base64 string of the KDB+ license, here we will initialise a secret within our KDB+ cluster to store that license which can be used by all our services.

# Retrieve B64 representation of license
$ base64 -w 0 $QLIC/kc.lic
<b64String>
  • Create Kubernetes secret resource
$ cat kx-license-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: kx-license-info
type: Opaque
stringData:
  license: <b64String>

  • Apply secret to the cluster
$ kubectl apply -f  kx-license-secret.yaml
secret/license-info configured
  • Create a simple configuration yaml

install-config.yml

global:
    image:
        repository: registry.dl.kx.com
    imagePullSecrets: 
        - name: kx-repo-access

    license:
        secretName: kx-license-info
  • Install helm chart
# Assumes the helm repo has been set up to `kxi-repo`
helm install kxi-discovery-service kxi-repo/kxi-discovery-service -f install-config.yml

Sample client

The KXI Discovery Service requires services to register with the Discovery Service; provide information on what and who they are including metadata necessary within the application. In addition to this, the client will be required to on a timer to heartbeat with the Discovery Service to ensure it remains active within the Discovery Service. Simple KX and KXCE client code have been included below to demonstrate the use of each API. Each of the clients use the register, service, update, status and deregister APIs.

KX

\c 20 200
discProxyH:@[hopen;`::5000;{[e]0N!"Could not connect to proxy: ",e;exit[1];}]
uid:"ex_svc_12ab";
service:"ex_svc"
host:"hostA"
port:5050
ip:"0.0.0.0"

// register
args:`uid`service`hostname`port`ip`status`metadata!(uid;service; host; port;ip;"UP"; enlist[`connectivity]!enlist `aeron)
registerResp:registerResp:discProxyH(`.sd.register; args)
if[200 <> first registerResp; ' last registerResp]

// get services
getServiceStatus:{[h;p_uid]
        getServicesResp:h(`.sd.getServices; ()!());
        if[200 <> first getServicesResp; ' last getServicesResp];
        -1 .Q.s resp:select from last getServicesResp where uid like p_uid;
        resp
        }
while[1<>count getServiceStatus[discProxyH;uid];system"sleep 1"];

// heartbeat - check the proxy 
.z.ts:{[]args:`uid`service`hostname!(uid;service; host);heartbeatResp:discProxyH(`.sd.heartbeat; args);}

// update status
args:`uid`service`hostname`port`ip`status`metadata!(uid;service; host; port; ip; "DOWN"; enlist[`connectivity]!enlist`aeron)
updateResp:discProxyH(`.sd.updateStatus; args)
getServiceStatus[discProxyH;uid];

// update details
args:`uid`service`hostname`port`ip`status`metadata!(uid;service; host; port; ip; "UP"; `connectivity`data!`aeron`quotes);
updateDetailsResp:discProxyH(`.sd.updateDetails;args)
getServiceStatus[discProxyH;uid];

// deregister
\t 0
args:`uid`service`hostname!(uid;service;host);
deregisterResp:discProxyH(`.sd.deregister; args);
while[1=count getServiceStatus[discProxyH;uid];system"sleep 1"];
exit[0]

KXCE

\c 20 200
uid:"ex_svc_12ab";
service:"ex_svc"
host:"hostA"
port:5050
ip:"0.0.0.0"

// add use of qlog
id:.com_kx_log.init[`:fd://stdout; ()];
.qlog:.com_kx_log.new[`qlog; ()];

// register
opts:()!();
headers:("Accept";"Content-Type")!2#enlist "application/json"
body:.j.j `uid`service`hostname`port`ip`status`metadata!(uid;service; host; port; ip; "UP"; enlist[`connectivity]!enlist `aeron)
opts:``headers`body!(::;headers;body);
resp:.kurl.sync("http://localhost:5000/register";`POST;opts)
if[200 <> first resp; ' last resp]
.qlog.info last resp;

// get services
getServiceStatus:{[p_uid]
  resp:.kurl.sync("http://localhost:5000/services";`GET;``headers!(::;("Accept";"Content-Type")!2#enlist "application/json"));
  if[200 <> first resp; ' last resp];
  .qlog.info lr:last resp;
  select from .j.k[lr]where uid like p_uid
  }
while[0=count getServiceStatus[uid];system"sleep 1"]
// heartbeat
.z.ts:{[]body:.j.j`uid`service`hostname!(uid; service; host);opts:`headers`body!(headers;body);.kurl.sync("http://localhost:5000/heartbeat";`POST;opts);}
\t 2000

// update details
body:.j.j`uid`service`hostname`port`ip`status`metadata!(uid;service; host; port; ip; "UP"; `connectivity`data!`aeron`quotes);
opts:`headers`body!(headers;body);
resp:.kurl.sync("http://localhost:5000/update";`POST;opts)
if[200 <> first resp; ' last resp]

// update status
body:.j.j`uid`service`hostname`status!(uid; service; host; "DOWN");
opts:`headers`body!(headers;body);
resp:.kurl.sync("http://localhost:5000/status";`POST;opts)
if[200 <> first resp; ' last resp]

// deregister
\t 0
body:.j.j`uid`service`hostname!(uid; service; host);
opts:`headers`body!(headers;body);
resp:.kurl.sync("http://localhost:5000/deregister";`POST;opts)
if[200 <> first resp; ' last resp]
while[1=count getServiceStatus[uid];system"sleep 1"]

exit[0]