Monday, January 30, 2023

ElasticSearch and Kibana Deployment on Kubernetes


 


In a post 2 years ago, I've specified steps to deploy ElasticSearch and TLS on kubernetes. It was back then, when ElasticSearch and kubernetes have only started dating, and the integration was not simple. But now ElasticSearch deployment is built-in part of the product, and much simpler.

This post is based mostly on the Elastic Cloud documentation.


CRDs

Like most of the operator based deployments, CRDs are required:


kubectl create -f https://download.elastic.co/downloads/eck/2.6.1/crds.yaml


Operator

Once CRDs are applied we can deploy the Elastic Cloud operator.

kubectl apply -f https://download.elastic.co/downloads/eck/2.6.1/operator.yaml

Kind and Ingress

In case the elastic is used on a local development machine using kind, we will need kind ingress to access the services.


apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: elastic-ingress-kind
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: elastic
http:
paths:
- pathType: ImplementationSpecific
backend:
service:
name: elastic-es-http
port:
number: 9200
- host: kibana
http:
paths:
- pathType: ImplementationSpecific
backend:
service:
name: kibana-kb-http
port:
number: 5601

In addition, we need to add to /etc/hosts the entries:


127.0.0.1 elastic
127.0.0.1 kibana


Elastic

To install ElasticSearch, we apply the elastic custom resource, and let the operator handle it.

apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: elastic
spec:
http:
tls:
selfSignedCertificate:
disabled: true
version: 8.6.0
nodeSets:
- name: default
count: 1
config:
node.store.allow_mmap: false


Notice that we have disabled HTTPs, so we can access the service easily from the kind ingress.

To view the ElasticSearch password, we can use the following:

Password=$(kubectl get secret elastic-es-elastic-user -o go-template='{{.data.elastic | base64decode}}')
echo "Password is ${Password}"

And we can access the ElasticSearch using curl:

curl -k -u "elastic:${Password}" "http://elastic"

We can also view the ElasticSearch status using the command:

kubectl get elasticsearch


A simple script that uses all these to check that the ElasticSearch is ready is below.


#!/bin/bash

set -e
cd "$(dirname "${BASH_SOURCE[0]}")"

function isReady(){
kubectl get elasticsearch
kubectl get pods
count=$(kubectl get elasticsearch | grep green | wc -l)
if [[ "${count}" == "0" ]]; then
return 1
else
return 0
fi
}

function waitForReady(){
until isReady
do
echo "waiting for elastic to be green"
sleep 5
done

echo "elastic is ready"
}

function sendRequest(){
Password=$(kubectl get secret elastic-es-elastic-user -o go-template='{{.data.elastic | base64decode}}')
echo "Password is ${Password}"
set -x
curl -k -u "elastic:${Password}" "http://elastic"
}


waitForReady
sendRequest



Kibana

To install kibana, we apply the following custom resource:

apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
name: kibana
spec:
http:
tls:
selfSignedCertificate:
disabled: true
version: 8.6.0
count: 1
elasticsearchRef:
name: elastic

Here as well, we turn off the HTTPS access to make access though kind easy.

An we can wait for kibana using another simple script:


#!/bin/bash

set -e
cd "$(dirname "${BASH_SOURCE[0]}")"

function isReady(){
kubectl get kibana
kubectl get pods
count=$(kubectl get kibana | grep green | wc -l)
if [[ "${count}" == "0" ]]; then
return 1
else
return 0
fi
}

function waitForReady(){
until isReady
do
echo "waiting for kibana to be green"
sleep 5
done

echo "kibana is ready"
}


waitForReady




Cleanup

To remove the deployments, we use the following:

kubectl delete kibana --all
kubectl delete elastic --all
kubectl delete -f ingress.yaml


Final Note


This progress is great, I really like the operator made by Elastic. Notice that the operator also handles upgrades, and more.

Monday, January 23, 2023

AWS vs. GCP Costs

 



In this post we will review price comparison of a kubernetes deployment on AWS and GCP. 

We will check a deployment that :

  • Includes 10 nodes of 8 CPUs and 16GB RAM
  • Serving 1000 requests per second 
  • The size of the request and the size of response is 1KB

General Calculations

The egress traffic bandwidth per month is:
1000 RPS * 86400 seconds in a day * 30 days * 1 KB per response = 2592000 GB


GCP Monthly Costs

Kubernetes cluster up-time:
0.1$ per hour = 72$ per month

Kubernetes cluster nodes:
10 nodes of 8 CPUs 16GB machine (c2d-highcpu-8) = 10 * 137$ = 1370$

Egress:
2592000 GB * 0.01 per GB = 25920$

Load balancer up-time:
0.025 per hour = 18$

Load balancer inbound traffic cost:
0.008$ per GB * 2592000 GB = 20736$

Load balancer outbound traffic cost:
0.008$ per GB * 2592000 GB = 20736$

Total monthly cost: 72834$ 


AWS Monthly Costs

Kubernetes cluster up-time:
0.1$ per hour = 73$ per month

Kubernetes cluster nodes:
10 nodes of 8 CPUs 16GB machine (c2d-highcpu-8) = 10 * 149$ = 1490$

Egress:
2592000 GB * 0.09 per GB = 233280$

Load balancer up-time:
0.0225 per hour = 16$

Load balancer outbound traffic cost:
0.00864$ per GB * 2592000 GB = 22394$

Total monthly cost: 257253$ 


Final Note

We can see that the pricing of the cloud providers is similar differs most regarding the traffic ingress and egress. The major cost depends on the traffic, and not on the compute resources, as the traffic cost is high, and it also affects the high costs of the load balancer.


Monday, January 16, 2023

How to work in Ubuntu Desktop while the Organization uses Microsoft



This post is quite personal...

I really do not like Microsoft products. Their PMs had built their products for the lowest possible common user, the products are full of bugs, they keep changing the products behavior in a zig-zag manner, and they allow full control on your desktop when you're working in an organization.

Several years ago, when all my colleagues were using Microsoft Windows, I've switched to Ubuntu desktop. It was a great relief, it was faster, it was simpler, and it was intended for a human who uses his brain. I like it.

But then, as years went by, the organization had restricted more and more of the IT infrastructure and services to be limited to Microsoft products. And throughout this, I had to survive with my Ubuntu.

In this post I will present show of the tricks I've used to survive. This is not the final word though. I might lose the ability to work at all, and will be forced to use Microsoft Windows, but for now I am still using an OS intended to be used by thinking humans.


Outlook

The first thing that went off is outlook. Microsoft office is not supported for Ubuntu in an easy way, and mail is a must have requirement to live in an organization. The bypass is to use Office 365 on the web. It is more basic than the fat Outlook client for windows, but it supplies all the functionality you need. It also has less bugs than the fat client, though it is still much more buggier than a KISS gmail web client.

The other issue for outlook, is that the organization uses both MFA and timeouts session every 10 minutes (come on? IT team? are you real??). So I had to re-login to the web interface and authenticate using my phone all day long. This is very very annoying. The bypass here is to keep the session alive using the Firefox extension mSession Keeper. To configure it use the following settings:

  • Start session URL: https://outlook.office.com/mail/
  • Stop session URL: https://login.microsoftonline.com/common/oauth2/logout
  • Ping URL: https://outlook.office.com/owa/
  • Ping interval: 1 to 3 minutes

Microsoft Teams

Communication through Microsoft Teams is required for some meetings. But for some wonder reason, Microsoft had made an effort to support it directly on Ubuntu. Just follow the instructions in this link.

In recent time, the organization IT had explicitly blocked usage of Teams on Ubuntu :(

To bypass this, I now use Zoom, which is still not blocked :)


Microsoft Word, Excel, PowerPoint

There are some alternatives for these Microsoft office products.

1. Use these products on Office 365 on the web (if not blocked by IT)

2. Use LibreOffice which is a builtin Office suite for Ubuntu.

3. Use Google docs, sheets, and slides.


VPN

In most cases VPN clients are available for Ubuntu machines as well. For example Pulse VPN client is available.


If All Fails

Sometimes, nothing works, and you must use windows. For this keep another machine with windows, and use remmina RDP to connect to it from the Ubuntu.








Monday, January 9, 2023

Cost Estimation For New Cloud Based Projects



Recently I've was working on several new projects, planned to run on a kubernetes cluster on a public cloud. Some of the projects were on their day 1 start of implementation, while others had a year of implementation behind them. So the PM pops the question: 


"Let me know how much will this cost"

As much as the implementation is more mature, providing a precise answer to this question is more straight forward. In case the project is nearly ready, we can use a stress test, along with prometheus and grafana monitoring the cluster to provide an answer. However, there are some items that should be handled properly to fully estimate the cost.

Existing Implementation Estimation

For an existing project, we can use the following methods.

Stress Test Include Production Data

In case the solution should supply service to multiple customers, we should simulate data that we expect to get for both the amount of requests for each customer, and the amount of customers. Notice that in many cases the load by each customer is not even, hence the stress test should simulate an expected distribution of loads, for example: 10% of the simulated customers provide heavy load, 80% of the simulated customers provide medium load, and 10% of the customers provide low load.

Stress Test Compute

The stress test simulating tools require both high compute and high bandwidth, hence we should use the public cloud infrastructure to run them. A nice cloud service for this purpose is the cloud batches service which enable running containers on demand.

Estimation Sheet

To get a good estimation, we need to test the deployment using several configurations. For example, start with cost per customer for a deployment with 10 customers, and then check 100 customers, 1000 customers, and so on. We usually expect the price per customer to drop as the amount of customers increases. The final result is a graph whose X axis is the amount of customers, and its Y axis is the cost per customer.

Cost Per Service

A cost estimation process is not a one time task. It usually includes running the stress tests, getting an estimation sheet, and then trying to reduce the cost by changing the highest compute demanding service, and re-running the cost again and again. To provide an understanding of the cost, we need to supply cost details for each of the services. This includes: amount of pods, average memory per pod, average CPU per pod.

Total Costs

Note that while the tests are mostly toward compute resources (CPU and memory), there are other costs which play a big part in the total sum. These include the following.

Network traffic cost, which is usually free for incoming traffic, and paid for egress traffic. Notice that in case we use VPN between regions, there is cost for ingress traffic as well.

Storage cost, which in most cases is low, due to relatively low cost in the public cloud providers.

API cost, which much be high in case of public cloud. Notice that we might have a low cost on storage, but high cost on API access to store and retrieve the data.

Other cloud services costs, that include for example the kubernetes cluster itself, and might include other services such as secure keys management, and databases. 

A special notice should be taken for Ingress, which is actually implemented as a load-balancer, and hence there is an up time cost as well as a per API cost.

Estimation Before Implementation

The previous sections discussed of stress test to an existing deployment, but what do we do if we just started the development, and we do not have an implementation ready?

The answer is guess!
But...

Do not guess in the wild, but instead use guidelines from other implementations. Try estimating the amount of incoming APIs calls to the services, and the database accesses amount you'll need.
In general, a good guideline for a 2G RAM and 2 CPU pod with minor database updates is being able to handle about 500 requests per second. This is a very rough estimation, but unless we have other information, we can use this guess baseline.

Final Note

While cost estimation is a long process, we should make our best effort to automate and document it, as it will probably be used not only once, but also later in the product lifetime, and maybe by another team...

Also, once results are shown, make sure to back them with all the data that led to the results, as questions will pop up by the PM or by other teams. For example: which public cloud did you used? which compute instances? what was the stress requests per second? Why did you choose this load? 


Monday, January 2, 2023

ChatGPT - Are Engineers Still Required

 



Recently there is a lot of buzz around ChatGPT, a trained language model that has answer to anything! This prevails many domains, including the programming domain, and causes some software engineers to ask whether this is the beginning of the end of this profession for human.

I've played with this bot, and checked its abilities. It is very impressive, and is probably the best of breed so far. I t can provide useful information for many questions, and the answers appear to be comprehensive.

Let's examine some good examples. I've asked the bot:

"create a go program that reads numbers from a file, and prints their average"

 The result code is:


import (
"bufio"
"fmt"
"os"
"strconv"
)

func main() {
// Open the file for reading
file, err := os.Open("numbers.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()

// Initialize a scanner to read the file line by line
scanner := bufio.NewScanner(file)

// Initialize a variable to hold the sum of the numbers
var sum float64

// Initialize a variable to keep track of the number of numbers
var count int

// Read the file line by line
for scanner.Scan() {
// Convert the line to a float
number, err := strconv.ParseFloat(scanner.Text(), 64)
if err != nil {
fmt.Println(err)
continue
}

// Add the number to the sum
sum += number

// Increment the count
count++
}

// Check for any errors that occurred during the scan
if err := scanner.Err(); err != nil {
fmt.Println(err)
return
}

// Calculate the average
avg := sum / float64(count)

// Print the average
fmt.Printf("Average: %.2f\n", avg)
}



Too good, to be true!

A full program, including comments, and it is working (yes, I've test it). 

But wait...

What about an empty file? The Average will be printed as NaN, and no errors. Still this is a good answer that will pass you through an interview.


Let's make it harder: 

"create a go program that draw a 4X4 board allowing a player to move in the board up, down, left, and right"


package main

import (
"bufio"
"fmt"
"os"
)

const boardSize = 4

// Coord represents a coordinate on the board
type Coord struct {
x, y int
}

func main() {
board := make([][]bool, boardSize)
for i := range board {
board[i] = make([]bool, boardSize)
}

pos := Coord{0, 0}
board[pos.x][pos.y] = true

// Set up input reader
reader := bufio.NewReader(os.Stdin)

for {
fmt.Println("Move around the board using the arrow keys. Press 'q' to quit.")
drawBoard(board)

// Read input
input, _, err := reader.ReadRune()
if err != nil {
fmt.Println(err)
continue
}

// Clear current position on board
board[pos.x][pos.y] = false

// Update position based on input
switch input {
case 'q':
fmt.Println("Quitting...")
return
case 'w':
pos.x--
case 's':
pos.x++
case 'a':
pos.y--
case 'd':
pos.y++
default:
fmt.Println("Invalid input. Try again.")
continue
}

// Check for out-of-bounds error
if pos.x < 0 || pos.x >= boardSize || pos.y < 0 || pos.y >= boardSize {
fmt.Println("Invalid move. Try again.")
pos.x, pos.y = 0, 0
continue
}

// Update current position on board
board[pos.x][pos.y] = true
}
}

func drawBoard(board [][]bool) {
fmt.Println("-------------------------")
for i := 0; i < boardSize; i++ {
fmt.Print("|")
for j := 0; j < boardSize; j++ {
if board[i][j] {
fmt.Print(" X ")
} else {
fmt.Print(" ")
}
}
fmt.Println("|")
fmt.Println("-------------------------")
}
}



WOW!! I mean WOW!! I did not expect that to work. It looks amazing!!

But... wait, it is almost working. The input reading reads both the key and the enter, while it actually asks for arrow, and expects wasd keys. Also the printout of the board looks very bad. Still it is a good start that a programmer can start with a fix to work properly. It might save an hour of work.


Hey, this is a chat bot, right? So let's chat about the last question:

"now, add obstacles to the board that the play must not move into"


So the bot changed the board type from bool to string, and added this:


// Check for obstacle
if board[pos.x][pos.y] == "O" {
fmt.Println("You can't move there. There's an obstacle in the way.")
pos.x, pos.y = 0, 0
continue
}



But the draw board function was cut. Looks like it has some limit on the program length (Though I asked the bot and he denied it...)


Final Note

ChatGPT is a nice tool, for simple programming issues, it can be used as an advanced stackoverflow site, but it is limited by the complexity and length of the answers. While it is a step toward a programmer, it is a very small step for a long marathon run. Still, we might see a breakthrough in several years that will enhance it abilities. For now, while it might pass a very simple junior programmer interview, it cannot do a junior programmer work.