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
Setting up env
To simplify the runtime environment, it is useful to set up necessary variables in a custom .env
file. Below highlights an example .env
file with the necessary field to run the docker samples. It requires the user to provide a base64 of their kc.lic
. Full details on how to do this here.
.env file
KX_DOCKER_REG=registry.dl.kx.com
KX_EUREKA_DISCOVERY_VERSION=1.0.5
KX_DISCOVERY_PROXY_VERSION=0.9.3
KDB_LICENSE_B64=<redacted>
- Create a kx docker bridge network
docker network create kx
Run the Kxi Discovery Microservice
- 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 \
${KX_DOCKER_REG}/kxi-eureka-discovery:${KX_EUREKA_DISCOVERY_VERSION}
This will run the Discovery Registry 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.
echo '{"discoveryProxy":{"adaptor":"discEurekaAdaptor.q","registry":"registry:8761"}}' > config.json
Running the Discovery Proxy will also require a KDB+ CE license to be provided as a base64 string. Info on how to do this here
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 \
${KX_DOCKER_REG}/kxi-discovery-proxy:${KX_DISCOVERY_PROXY_VERSION} -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
Using Docker compose
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: ${KX_DOCKER_REG}/kxi-eureka-discovery:${KX_EUREKA_DISCOVERY_VERSION}
environment:
- JAVA_OPTS=-Deureka.instance.appname=DOCKER_COMPOSE_EX -Deureka.server.responseCacheUpdateIntervalMs=1000
# Expose port 8761 locally
ports:
- 8761:8761
networks:
- kx
proxy:
image: ${KX_DOCKER_REG}/kxi-discovery-proxy:${KX_DISCOVERY_PROXY_VERSION}
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 Registry 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 docker-compose
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 '{"discoveryProxy":{"adaptor":"discEurekaAdaptor.q","registry":"registry-0:8761"}}' > proxyConfig-0.json export KXI_CONFIG_FILE=$(pwd)/proxyConfig-0.json echo '{"discoveryProxy":{"adaptor":"discEurekaAdaptor.q","registry":"registry-1:8761"}}' > proxyConfig-1.json export KXI_CONFIG_FILE=$(pwd)/proxyConfig-1.json echo '{"discoveryProxy":{"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: ${KX_DOCKER_REG}/kxi-eureka-discovery:${KX_EUREKA_DISCOVERY_VERSION} # 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: ${KX_DOCKER_REG}/kxi-discovery-proxy:${KX_DISCOVERY_PROXY_VERSION} 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: ${KX_DOCKER_REG}/kxi-eureka-discovery:${KX_EUREKA_DISCOVERY_VERSION} # 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: ${KX_DOCKER_REG}/kxi-discovery-proxy:${KX_DISCOVERY_PROXY_VERSION} 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: ${KX_DOCKER_REG}/kxi-eureka-discovery:${KX_EUREKA_DISCOVERY_VERSION} # 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: ${KX_DOCKER_REG}/kxi-discovery-proxy:${KX_DISCOVERY_PROXY_VERSION} 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]