Zookeeper: Hostname resolution fails - kubernetes

I am running Zookeeper in an OpenShift/Kubernetes environment. I have setup zookeeper as a StatefulSet in order to reliably persist config data.
I configured three servers in my zoo.cfg by hostname, but on startup, hostname resolution fails. I verified hostnames are indeed resolvable using nslookup inside my cluster.
zoo.cfg:
clientPort=2181
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
tickTime=2000
initLimit=10
syncLimit=2000
maxClientCnxns=60
minSessionTimeout= 4000
maxSessionTimeout= 40000
autopurge.snapRetainCount=3
autopurge.purgeInteval=0
server.1=zookeeper-0.zookeeper-headless:2888:3888
server.2=zookeeper-1.zookeeper-headless:2888:3888
server.3=zookeeper-2.zookeeper-headless:2888:3888
Relevant parts of my OpenShift / Kubernetes configuration:
# StatefulSet
- apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
labels:
app: zookeeper
name: zookeeper
spec:
serviceName: zookeeper-headless
replicas: 3
template:
metadata:
labels:
app: zookeeper
spec:
containers:
- image: 172.30.158.156:5000/os-cloud-platform/zookeeper:latest
name: zookeeper
ports:
- containerPort: 2181
protocol: TCP
name: client
- containerPort: 2888
protocol: TCP
name: server
- containerPort: 3888
protocol: TCP
name: leader-election
dnsPolicy: ClusterFirst
schedulerName: default-scheduler
# Service
- apiVersion: v1
kind: Service
metadata:
labels:
app: zookeeper
name: zookeeper
spec:
ports:
- name: client
port: 2181
protocol: TCP
targetPort: 2181
selector:
app: zookeeper
sessionAffinity: None
type: ClusterIP
- apiVersion: v1
kind: Service
metadata:
name: zookeeper-headless
labels:
app: zookeeper
spec:
ports:
- port: 2888
name: server
- port: 3888
name: leader-election
clusterIP: None
selector:
app: zookeeper
OpenShift logs show UnknownHostExceptions, though:
2017-10-06 10:59:18,289 [myid:] - WARN [main:QuorumPeer$QuorumServer#155] - Failed to resolve address: zookeeper-2.zookeeper-headless
java.net.UnknownHostException: zookeeper-2.zookeeper-headless: No address associated with hostname
at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:928)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1323)
at java.net.InetAddress.getAllByName0(InetAddress.java:1276)
at java.net.InetAddress.getAllByName(InetAddress.java:1192)
at java.net.InetAddress.getAllByName(InetAddress.java:1126)
at java.net.InetAddress.getByName(InetAddress.java:1076)
at org.apache.zookeeper.server.quorum.QuorumPeer$QuorumServer.recreateSocketAddresses(QuorumPeer.java:148)
at org.apache.zookeeper.server.quorum.QuorumPeer$QuorumServer.<init>(QuorumPeer.java:133)
at org.apache.zookeeper.server.quorum.QuorumPeerConfig.parseProperties(QuorumPeerConfig.java:228)
at org.apache.zookeeper.server.quorum.QuorumPeerConfig.parse(QuorumPeerConfig.java:140)
at org.apache.zookeeper.server.quorum.QuorumPeerMain.initializeAndRun(QuorumPeerMain.java:101)
at org.apache.zookeeper.server.quorum.QuorumPeerMain.main(QuorumPeerMain.java:78)
...
What could be the cause? I verified that the hostname (e.g. zookeeper-2.zookeeper-headless) is available from other pods through nslookup.

I found a working solution for this issue. ZooKeeper reads the list of servers in the ensemble on startup and looks for its "own" entry. It then uses this entry to determine which port and interface to listen on.
server.1=zookeeper-0.zookeeper-headless:2888:3888
server.2=zookeeper-1.zookeeper-headless:2888:3888
server.3=zookeeper-2.zookeeper-headless:2888:3888
Since the provided hostname will resolve to 127.0.0.1 on this machine, ZooKeeper will listen on the local loopback interface and therefore does not accept connections from the other ZooKeeper servers.
server.1=0.0.0.0:2888:3888
server.2=zookeeper-1.zookeeper-headless:2888:3888
server.3=zookeeper-2.zookeeper-headless:2888:3888
To automate things in the cluster, I wrote a bash script that will replace the one "own" entry on container startup.
EDIT: As asked in the comments, here is my ENTRYPOINT script that takes care of placing the myid file and setting the appropriate hostname for each zoo.cfg:
#!/bin/bash
# This script extracts the number out of the pod's hostname and sets it as zookeepers id.
# Exact paths may vary according to your setup
MYID_FILE="/var/lib/zookeeper/data/myid"
ZOOCFG_FILE="/conf/zoo.cfg"
# Create myid-file
# Extract only numbers from hostname
id=$(hostname | tr -d -c 0-9)
echo $id > "${MYID_FILE}"
# change own hostname to 0.0.0.0
# otherwise, the own hostname will resolve to 127.0.0.1
# https://stackoverflow.com/a/40750900/5764665
fullHostname="$(hostname).zookeeper-headless"
sed -i -e "s/${fullHostname}/0.0.0.0/g" "${ZOOCFG_FILE}"
echo "Executing $#"
exec "$#"

Related

How to allow for tcp service (not http) on custom port inside kubernetes

I have a container running an OPC-server on port 4840. I am trying to configure my microk8s to allow my OPC-Client to connect to the port 4840. Here are examples of my deployment and service:
(No namespace is defined here but they are deployed through azure pipelines and that is where the namespace is defined, the namespace for the deployment and service is "jawcrusher")
deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jawcrusher
spec:
replicas: 1
selector:
matchLabels:
app: jawcrusher
strategy: {}
template:
metadata:
labels:
app: jawcrusher
spec:
volumes:
- name: jawcrusher-config
configMap:
name: jawcrusher-config
containers:
- image: XXXmicrok8scontainerregistry.azurecr.io/jawcrusher:#{Version}#
name: jawcrusher
ports:
- containerPort: 4840
volumeMounts:
- name: jawcrusher-config
mountPath: "/jawcrusher/config/config.yml"
subPath: "config.yml"
imagePullSecrets:
- name: acrsecret
service.yml
apiVersion: v1
kind: Service
metadata:
name: jawcrusher-service
spec:
ports:
- name: 7070-4840
port: 7070
protocol: TCP
targetPort: 4840
selector:
app: jawcrusher
type: ClusterIP
status:
loadBalancer: {}
I am using a k8s-client called Lens and in this client there is a functionality to forward local ports to the service. If I do this I can connect to the OPC-Server with my OPC-Client using the url localhost:4840. To me that indicates that the service and deployment is set up correctly.
So now I want to tell microk8s to serve my OPC-Server from port 4840 "externally". So for example if my dns to the server is microk8s.xxxx.internal I would like to connect with my OPC-Client to microk8s.xxxx.internal:4840.
I have followed this tutorial as much as I can: https://minikube.sigs.k8s.io/docs/tutorials/nginx_tcp_udp_ingress/.
It says to update the tcp-configuration for the ingress, this is how it looks after I updated it:
nginx-ingress-tcp-microk8s-conf:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-ingress-tcp-microk8s-conf
namespace: ingress
uid: a32690ac-34d2-4441-a5da-a00ec52d308a
resourceVersion: '7649705'
creationTimestamp: '2023-01-12T14:12:07Z'
annotations:
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"annotations":{},"name":"nginx-ingress-tcp-microk8s-conf","namespace":"ingress"}}
managedFields:
- manager: kubectl-client-side-apply
operation: Update
apiVersion: v1
time: '2023-01-12T14:12:07Z'
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
- manager: kubectl-patch
operation: Update
apiVersion: v1
time: '2023-02-14T07:50:30Z'
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:4840: {}
selfLink: /api/v1/namespaces/ingress/configmaps/nginx-ingress-tcp-microk8s-conf
data:
'4840': jawcrusher/jawcrusher-service:7070
binaryData: {}
It also says to update a deployment called ingress-nginx-controller but in microk8s it seems to be a daemonset called nginx-ingress-microk8s-controller. This is what it looks like after adding a new port:
nginx-ingress-microk8s-controller:
spec:
containers:
- name: nginx-ingress-microk8s
image: registry.k8s.io/ingress-nginx/controller:v1.2.0
args:
- /nginx-ingress-controller
- '--configmap=$(POD_NAMESPACE)/nginx-load-balancer-microk8s-conf'
- >-
--tcp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-tcp-microk8s-conf
- >-
--udp-services-configmap=$(POD_NAMESPACE)/nginx-ingress-udp-microk8s-conf
- '--ingress-class=public'
- ' '
- '--publish-status-address=127.0.0.1'
ports:
- name: http
hostPort: 80
containerPort: 80
protocol: TCP
- name: https
hostPort: 443
containerPort: 443
protocol: TCP
- name: health
hostPort: 10254
containerPort: 10254
protocol: TCP
####THIS IS WHAT I ADDED####
- name: jawcrusher
hostPort: 4840
containerPort: 4840
protocol: TCP
After I have updated the daemonset it restarts all the pods. The port seem to be open, if I run this script is outputs:
Test-NetConnection -ComputerName microk8s.xxxx.internal -Port 4840
ComputerName : microk8s.xxxx.internal
RemoteAddress : 10.161.64.124
RemotePort : 4840
InterfaceAlias : Ethernet 2
SourceAddress : 10.53.226.55
TcpTestSucceeded : True
Before I did the changes it said TcpTestSucceeded: False.
But the OPC-Client cannot connect. It just says:
Could not connect to server: BadCommunicationError.
Does anyone see if I made a mistake somewhere or knows how to do this in microk8s.
Update 1:
I see an error message in the ingress-daemonset-pod logs when I try to connect to the server with my OPC-Client:
2023/02/15 09:57:32 [error] 999#999: *63002 connect() failed (111:
Connection refused) while connecting to upstream, client:
10.53.225.232, server: 0.0.0.0:4840, upstream: "10.1.98.125:4840", bytes from/to client:0/0, bytes from/to upstream:0/0
10.53.225.232 is the client machines ip address and 10.1.98.125 is the ip number of the pod running the OPC-server.
So it seems like it has understood that external port 4840 should be proxied/forwarded to my service which in turn points to the OPC-server-pod. But why do I get an error...
Update 2:
Just to clearify, if I run kubectl port forward command and point to my service it works. But not if I try to connect directly to the 4840 port. So for example this works:
kubectl port-forward service/jawcrusher-service 5000:4840 -n jawcrusher --address='0.0.0.0'
This allows me to connect with my OPC-client to the server on port 5000.
You should simply do a port forwarding from your localhost port x to your service/deployment/pod on port y with kubectl command.
Lets say you have a Nats streaming server in your k8s, it's using tcp over port 4222, your command in that case would be:
kubectl port-forward service/nats 4222:4222
In this case it will forward all traffic on localhost over port 4222 to the service named nats inside your cluster on port 4222. Instead of service you could forward to a specific pod or deployment...
Use kubectl port-forward -h to see all your options...
In case you are using k3d to setup a k3s in docker or rancher desktop you could add the port parameter to your k3d command:
k3d cluster create k3s --registry-create registry:5000 -p 8080:80#loadbalancer -p 4222:4222#server:0
The problem was never with microk8s or the ingress configuration. The problem was that my server was bound to the loopback address (127.0.0.1).
When I changed the configuration so the server listened to 0.0.0.0 instead of 127.0.0.1 it started working.

GCP health check failing for kubernetes pod

I'm trying to launch an application on GKE and the health checks made by the Ingress always fail.
Here's my full k8s yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: tripvector
labels:
app: tripvector
spec:
replicas: 1
minReadySeconds: 60
selector:
matchLabels:
app: tripvector
template:
metadata:
labels:
app: tripvector
spec:
containers:
- name: tripvector
readinessProbe:
httpGet:
port: 3000
path: /healthz
initialDelaySeconds: 30
timeoutSeconds: 10
periodSeconds: 11
image: us-west1-docker.pkg.dev/triptastic-1542412229773/tripvector/tripvector:healthz2
env:
- name: ROOT_URL
value: https://paymahn.tripvector.io/
- name: MAIL_URL
valueFrom:
secretKeyRef:
key: MAIL_URL
name: startup
- name: METEOR_SETTINGS
valueFrom:
secretKeyRef:
key: METEOR_SETTINGS
name: startup
- name: MONGO_URL
valueFrom:
secretKeyRef:
key: MONGO_URL
name: startup
ports:
- containerPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tripvector
spec:
defaultBackend:
service:
name: tripvector-np
port:
number: 60000
---
apiVersion: v1
kind: Service
metadata:
name: tripvector-np
annotations:
cloud.google.com/neg: '{"ingress": true}'
spec:
type: ClusterIP
selector:
app: tripvector
ports:
- protocol: TCP
port: 60000
targetPort: 3000
This yaml should do the following:
make a deployment with my healthz2 image along with a readiness check at /healthz on port 3000 which is exposed by the image
launch a cluster IP service
launch an ingress
When I check for the status of the service I see it's unhealth:
❯❯❯ gcloud compute backend-services get-health k8s1-07274a01-default-tripvector-np-60000-a912870e --global
---
backend: https://www.googleapis.com/compute/v1/projects/triptastic-1542412229773/zones/us-central1-a/networkEndpointGroups/k8s1-07274a01-default-tripvector-np-60000-a912870e
status:
healthStatus:
- healthState: UNHEALTHY
instance: https://www.googleapis.com/compute/v1/projects/triptastic-1542412229773/zones/us-central1-a/instances/gke-tripvector2-default-pool-78cf58d9-5dgs
ipAddress: 10.12.0.29
port: 3000
kind: compute#backendServiceGroupHealth
It seems that the healthcheck is hitting the right port but this output doesn't confirm if it's hitting the right path. If I look up the health check object in the console I see the following:
Which confirms the GKE health check is hitting the healthz path.
I've verified in the following ways that the health check endpoint I'm using for the readiness probe works but something still isn't working properly:
exec into the pod and run wget
port forward the pod and check /healthz in my browser
port forward the service and check /healthz in my browser
In all three instances above, I can see the /healthz endpoint working. I'll outline each one below.
Here's evidence that running wget from within the pod:
❯❯❯ k exec -it tripvector-65ff4c4dbb-vwvtr /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/tripvector # ls
bundle
/tripvector # wget localhost:3000/healthz
Connecting to localhost:3000 (127.0.0.1:3000)
saving to 'healthz'
healthz 100% |************************************************************************************************************************************************************| 25 0:00:00 ETA
'healthz' saved
/tripvector # cat healthz
[200] Healthcheck passed./tripvector #
Here's what happens when I perform a port forward from the pod to my local machine:
❯❯❯ k port-forward tripvector-65ff4c4dbb-vwvtr 8081:3000
Forwarding from 127.0.0.1:8081 -> 3000
Forwarding from [::1]:8081 -> 3000
Handling connection for 8081
Handling connection for 8081
Handling connection for 8081
Handling connection for 8081
Handling connection for 8081
Handling connection for 8081
Handling connection for 8081
Handling connection for 8081
Handling connection for 8081
And here's what happens when I port forward from the Service object:
2:53PM /Users/paymahn/code/tripvector/tripvector ✘ 1 docker ⬆ ✱
❯❯❯ k port-forward svc/tripvector-np 8082:60000
Forwarding from 127.0.0.1:8082 -> 3000
Forwarding from [::1]:8082 -> 3000
Handling connection for 8082
How can I get the healthcheck for the ingress and network endpoint group to succeed so that I can access my pod from the internet?

Can't connect Mosquitto Broker on Public Domain

I have a Mosquitto Broker on my Kubernetes. I can connect to Mosquitto Broker in Private Network. It works well.
But when We use a Public Domain ( We use Sophos UTM 9 ), The client can't connect to Mosquitto Broker.
I'm a new with Kubernetes. This is mosquitto.yaml file:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mosquitto
spec:
selector:
matchLabels:
app: mosquitto
replicas: 1
template:
metadata:
labels:
app: mosquitto
spec:
containers:
- name: mosquitto
image: eclipse-mosquitto:v1.16.10
resources:
limits:
cpu: "1"
memory: 2Gi
requests:
cpu: "1"
memory: 2Gi
imagePullPolicy: Always
ports:
- containerPort: 1883
---
apiVersion: v1
kind: Service
metadata:
name: mosquitto
spec:
externalIPs:
- xxx.xxx.xxx.xxx
type: ClusterIP
ports:
- name: mqtt
port: 1883
targetPort: 1883
protocol: TCP
selector:
app: mosquitto
I use NodeJS to connect with public domain. This NodeJS code is:
var mqtt = require('mqtt');
var client = mqtt.connect('mqtt://mydomain.com:1883');
client.on('connect', function () {
client.subscribe(topic)
console.log("Subscribed topic " + topic);
})
I wonder what the problem is kubernetes or Sophos UTM 9. Do I miss anything?
What do I have to do for Mosquitto on Kubernetes to use the Public Domain ?
I am most grateful.
After test your yaml file, I've concluded that you configuration is almost correct, I mean that because:
The image you are using eclipse-mosquitto:v1.16.10 does not exists. You can check all tag available here.
So, the most probable issue, is that your pod might not be running. You can check it by running the command below and checking the column STATUS.
$ kubectl get pods -l=app=mosquitto
NAME READY STATUS RESTARTS AGE
mosquitto-c9dc57d59-98l8r 1/1 Running 0 5m53s
Here the yaml that worked for me. Note: I've removed the externalIP and resource limits from service and deployment for tests purposes and replaced the image for eclipse-mosquitto:1.6.10:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mosquitto
spec:
selector:
matchLabels:
app: mosquitto
replicas: 1
template:
metadata:
labels:
app: mosquitto
spec:
containers:
- name: mosquitto
image: eclipse-mosquitto:1.6.10
imagePullPolicy: Always
ports:
- containerPort: 1883
---
apiVersion: v1
kind: Service
metadata:
name: mosquitto
spec:
type: ClusterIP
ports:
- name: mqtt
port: 1883
targetPort: 1883
protocol: TCP
selector:
app: mosquitto
After deployed I've tested using a dnsutil container (you can find the spec here):
kubectl exec dnsutils -- sh -c 'apk update && apk add mosquitto-clients'
kubectl exec dnsutils -- mosquitto_pub -h mosquitto -t 'test/topic' -m 'upvoteIt'
Check the logs in mosquitto pod:
kubectl logs mosquitto-xxxxx
1597829622: New client connected from 172.17.0.4 as mosqpub|88-dnsutils (p1, c1, k60).
1597829622: Client mosqpub|88-dnsutils disconnected.
If you want to see the message before test, open a second terminal and run this command to see the message being received by mosquitto server:
$ kubectl exec mosquitto-xxxxx -- mosquitto_sub -v -t 'test/topic'
test/topic upvoteIt
Where mosquitto-xxxxx is the name of your pod.

Allow two pods to communicate with each other

First time using Kubernetes. I have an API and a database, and I want the two pods to communicate with each other.
Based on the docs, I should create a service.
I have created a service for each of the two pods, though still not able to connect to the pod using the services IP address.
For example if the MySQL service that is created has an IP address of 11.22.33.44, I can run the following command to try to connect to the pod of that service:
mysql -h11.22.33.44 -uuser -ppassword foo
...and it will hang and eventually the connection will time out.
I create the pod and service like so:
kubectl create -f ./mysql.yaml
mysql.yaml:
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
selector:
app: mysql
ports:
- protocol: TCP
port: 80
targetPort: 3306
---
apiVersion: v1
kind: Pod
metadata:
name: mysql
spec:
containers:
- name: mysql
image: my-custom-mysql-image:latest
ports:
- containerPort: 3306
protocol: TCP
name: mysql
env:
- name: MYSQL_DATABASE
value: "foo"
- name: MYSQL_USER
value: "user"
- name: MYSQL_ROOT_PASSWORD
value: "password"
- name: MYSQL_HOST
value: "127.0.0.1"
your service has a selector defined
selector:
app: mysql
yet your Pod has no labels whatsoever, hence the service can not identify it as its backend and has no endpoint to direct traffic for ClusterIP. You should also stick to standard port number on service as well, so like this :
ports:
- protocol: TCP
port: 3306
targetPort: 3306

Tunnelling via pod

I have multiple Kubernetes pods running on a server. One of the pods contains a database application that only accepts connections from a specific subnet (i.e. other Kubernetes pods).
I'm trying to connect to the DB application from the server itself, but the connection is refused because the server's IP is not part of the allowed subnet.
Is there a way to create a simple pod that accepts connections from the server and forwards them to the pod containing the DB app?
Unfortunately, the DB app cannot be reconfigured to accept other connections.
Thank you
The easiest solution is probably to add another container to your pod running socat or something similar and make it listen and connect to your local pod's IP (important: connect to the pod ip, not 127.0.0.1 if your database program is configured to only accept connections from the overlay network).
Then modify the service you have for these pods and add the extra port.
The example below assumes port 2000 is running your program and 2001 will be the port that is forwarded to 2000 inside the pod.
Example (the example is running netcat simulating your database program):
apiVersion: v1
kind: Pod
metadata:
name: database
labels:
app: database
spec:
containers:
- name: alpine
image: alpine
command: ["nc","-v","-n","-l","-p","2000"]
ports:
- containerPort: 2000
- name: socat
image: toughiq/socat
ports:
- containerPort: 2001
env:
- name: LISTEN_PROTO
value: "TCP4"
- name: LISTEN_PORT
value: "2001"
- name: TARGET_PROTO
value: "TCP4"
- name: TARGET_HOST
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: TARGET_PORT
value: "2000"
---
apiVersion: v1
kind: Service
metadata:
name: database
spec:
selector:
app: database
ports:
- name: myport
port: 2000
targetPort: 2000
protocol: TCP
- name: socat
port: 2001
targetPort: 2001
protocol: TCP
externalIPs: [xxxxxx]