Sunday, 7 May 2017

Kubernetes Service And Their EndPoints.

Its very necessary that we specify a proper selector while creating a kubernetes service. The selectors should be unique. So, that they can easily discover the pods.

If you specify a common selectors for all the services, then it may occur that it will point to a multiple pods. And then it will be very difficult to identify the real cause. I was getting a connection-refused error. Sometimes, when i was accessing pod using a node port service.

In my case i was having two pods (named backend-manager and the other one was named engine).
And i have created two node port services for it (bm and engine).

And here are the service yamls :

1. Engine Service :

"engine": { //Engine /
"apiVersion":"v1",
"kind":"Service",
"metadata":{
"name":"engine",
"namespace":`${tennantId}-${salt}`,
"labels":{
"app":"backend",
"tier":"engine"
}
},
"spec":{
"type":"NodePort",
"selector":{
"app":"backend"
},
"ports":[{
"port":3000,
"targetPort": 3000
}]
}
}


2. BM SERVICE :

"bm": { //Backend Manager
"apiVersion":"v1",
"kind":"Service",
"metadata":{
"name":"bm",
"namespace":`${tennantId}-${salt}`,
"labels":{
"app":"backend",
"tier":"bm"
}
},
"spec":{
"type":"NodePort",
"selector":{
"app":"backend"
},
"ports":[{
"port":3001,
"targetPort": 3001
}]
}
}


As, you can see i have mistakenly mentioned the common selector in both the service.

And lets see, the endpoints for the service, using the below url.

http://localhost:8000/api/v1/namespaces/myapps-fv92n/endpoints/bm

And here is the output :





As, you can clearly see that different pods (both BM and Engine) are listing under the service endpoint (under subsets->addresses highlighted above ). Actually, only BM should be listed.

I have then modified both the service yaml and added one more custom selector:

a. "tier:bm" in BM service.
b. "tier:engine" in Engine Service.

And here is the output for the service endpoint, Only BM is listing under the bm service endpoint.



Sometimes, i was getting the connection refused error while accessing the pods using the node port service. And after updating the service yaml's this issue has been resolved.


Below is the steps for creating the end-point URL :

We can get the endpoints by sending a GET request to the following URL.

The URL contains the following parts :

1. localhost:8000 : As, i have created the proxy (kubectl proxy --port=8000).

2. api/v1 : version

3. namespaces : keyword

4. {{namespace-name}} : Your namespace name, if you have created services under the default namespace, then mentioned it default.

5. endpoints : keyword.

6. {{service-name}} : In my case it is bm

URL :  http://localhost:8000/api/v1/namespaces/myapps-fv92n/endpoints/bm

Monday, 1 May 2017

Promisifying Redis Client With Bluebird Example (With Pub-Sub Also)

Days before, i was struggling handling callbacks in redis-client. And code was getting more complex, with callbacks.

I have research around, whether is there any thing like promises in redis. And i came to know, that now redis support promises by promisifying node_redis with bluebird.

I have written a simple example code for promisify the node_redis client.

I have written a wrapper around the out of the box node_redis functions (get,set,exist, etc) and that will return you a promise. You can just directly call the wrapper function and can write your all callback code in the "then()" function.

In this example, i am first initializing the redis client and then using the exist function to check, whether the key exist in redis or not, if not then creating the key and publishing it to subscriber, and if the key already exist, then updating the old key value by increment it with 1 and then publishing it to subscribers.

Here is the code for it :

Git hub repository : https://github.com/UtkarshYeolekar/promisify-redis-client

1. redis.js : Which contains all the wrapper functions.

You can remove the logger and can use console.log directly. Also, you can remove the redis-config and can directly specify the port and host in the create Client().


let bluebird = require("bluebird"),
    redis = require('redis'),
    logger = require("./logger"),
    redisConfig = require("./config.js").redisConfig,
    maxReconnectingTry = 4,
    tryReconnecting  = 0,
    // subscriber will pass a callback function and when the redis client
    // will recieve a message, it will call that callback function.
    callback  
bluebird.promisifyAll(redis.RedisClient.prototype);

let redisClient = null;
module.exports = {

        initRedisClient : () =>{
            redisClient =  redis.createClient(redisConfig().port,redisConfig().host)
            logger.debug("Initalizing Redis Client");

            redisClient.on('ready',function() {
            logger.debug(" subs Redis is ready");
            });

            redisClient.on('connect',function(){
                logger.debug('subs connected to redis');
                isRedisConnected = true;
            });

            redisClient.on("message", function(channel, message) {
                logger.info("message recieved on channel :", channel);
                callback(channel,message);
            });

            redisClient.on("error", function (err) {
                logger.debug("Error occurred while connecting to redis " + err);
                isRedisConnected = false;
            });

            redisClient.on('reconnecting',function(err){
                    tryReconnecting++;
                    logger.warn('reconnecting');
                    if(tryReconnecting >= maxReconnectingTry)
                    {
                        logger.error(err);
                        redisClient.quit();
                    }
            });
        },
        getKeyValue: (key) => {
            return redisClient.getAsync(key)
                .then((res, err) => err ? Promise.reject("getKeyValue : "+err) : Promise.resolve(res));
        },
        setKeyValue: (key, value) => {
            return redisClient.setAsync(key, value)
                .then((res, err) => err ? Promise.reject("setkeyvalue : "+ err) : Promise.resolve(res));
        },
        doesKeyExist: key => {
            return redisClient.existsAsync(key)
                .then((res, err) => !res || err ? Promise.resolve(false) : Promise.resolve(res));
        },
        deleteKey: key => {
            return redisClient.delAsync(key)
                .then((res, err) => res ? Promise.resolve(res) : Promise.reject("deleteKey :"+err));
        },
        publishMessage: (channel,message) => redisClient.publish(channel,message),
        endConnection: () => redisClient.quit(),
        subscribeChannel: (channel,cb) => {
             redisClient.subscribe(channel)
             callback = cb;
        }
    }


2. update-redis.js : By using wrapper functions update the redis key and publish the message to the channel.

In this code :
1. I am checking whether the key exist in the redis.
2. if key doesn't exist than create the key and publish the message to the channel(subscriber).
3. if key exist, then get the old value of the key and increment it by 1 and publish it to the channel(subscriber).

I am using "winston" for logging. So, also in this code, you can remove the logger and can use console.log().

let redis = require("./redis.js"),
    logger = require("./logger.js"),
    _baseVersion = 1,
    _currentVersion , _deploymentVersion , _previousDeployedVersion = null;

const versionLabel = "v";
const key = "_deploymentVersion";
const channel = "deployment";


/*redis().deleteKey(key)
.then((res) => redis().doesKeyExist(key))*/
module.exports = {
    updateRedis: () => {
    redis.initRedisClient();
      return redis.doesKeyExist(key)
        .then((res) => res ? redis.getKeyValue(key) : null)
        .then((res) => {
            if (res != null) {
                logger.info("Current Deployed Version", res);
                _previousDeployedVersion = res;
                _currentVersion = parseInt(_previousDeployedVersion.split(versionLabel)[1]) + 1;
                _deploymentVersion = versionLabel + _currentVersion;
            }
            else
                _deploymentVersion = versionLabel + _baseVersion;
            redis.setKeyValue(key, _deploymentVersion)
        })
        .then((res) => {
            logger.info("version updated to : ", _deploymentVersion);
            let message = JSON.stringify({ "_deploymentVersion": _deploymentVersion });
            return redis.publishMessage(channel, message);
        }).then((res) => {
            logger.info("message published to channel :", channel);
            redis.endConnection();
            return Promise.resolve("Redis Updated and message published");
        })
        .catch((res) => {
            logger.error("catch block :->", res);
            redis.endConnection();
            return Promise.reject("Error in updating redis",res);
        });

    }
}

3. subsriber.js : Here we will initialize one more client and will subscribe to the above deployment channel. So, whenever the message is published in channel, we will get notified.


let redis = require("./redis.js");

redis.initRedisClient();
redis.subscribeChannel('deployment',(channel,message)=>{
             console.log(message);
});


Lets, require both the JS (update-redis and subscriber into one js file and named it app.js).

//Updating redis key and publishing it to a channel
let redis = require('./update-redis.js');

//subscribing the channel.
let subs = require('./subscriber.js');

redis.updateRedis().then((res)=>{
    console.log("res",res);
})
.catch((err)=>{
    console.log("eree",err);
});


Now, we can directly run the code by typing node .

You can download the full code from here. It contains the logger and the config file also.

Steps :

1. Download the code.
2. npm install
3. Make sure your redis service is up and running.
4. node app.js

Note : Publisher and Subscriber cannot work on the same client, We require two separate clients for that.


Do provide your valuable feedback 😊