How to access locally installed postgresql in microk8s cluster - postgresql

I have installed Postgresql and microk8s on Ubuntu 18.
One of my microservice which is inside microk8s single node cluster needs to access postgresql installed on same VM.
Some articles suggesting that I should create service.yml and endpoint.yml like this.
apiVersion: v1
metadata:
name: postgresql
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
---
kind: Endpoints
apiVersion: v1
metadata:
name: postgresql
subsets:
- addresses:
- ip: ?????
ports:
- port: 5432
Now, I am not getting what should I put in subsets.addresses.ip field ?

First you need to configure your Postgresql to listen not only on your vm's localhost. Let's assume you have a network interface with IP address 10.1.2.3, which is configured on your node, on which Postgresql instance is installed.
Add the following entry in your /etc/postgresql/10/main/postgresql.conf:
listen_addresses = 'localhost,10.1.2.3'
and restart your postgres service:
sudo systemctl restart postgresql
You can check if it listens on the desired address by running:
sudo ss -ntlp | grep postgres
From your Pods deployed within your Microk8s cluster you should be able to reach IP addresses of your node e.g. you should be able to ping mentioned 10.1.2.3 from your Pods.
As it doesn't require any loadbalancing you can reach to your Postgresql directly from your Pods without a need of configuring additional Service, that exposes it to your cluster.
If you don't want to refer to your Postgresql instance in your application using it's IP address, you can edit your Deployment (which manages the set of Pods that connect to your postgres db) to modify the default content of /etc/hosts file used by your Pods.
Edit your app Deployment by running:
microk8s.kubectl edit deployment your-app
and add the following section under Pod template spec:
hostAliases: # it should be on the same indentation level as "containers:"
- hostnames:
- postgres
- postgresql
ip: 10.1.2.3
After saving it, all your Pods managed by this Deployment will be recreated according to the new specification. When you exec into your Pod by running:
microk8s.kubectl exec -ti pod-name -- /bin/bash
you should see additional section in your /etc/hosts file:
# Entries added by HostAliases.
10.1.2.3 postgres postgresql
Since now you can refer to your Postgres instance in your app by names postgres:5432 or postgresql:5432 and it will be resolved to your VM's IP address.
I hope it helps.
UPDATE:
I almost forgot that some time ago I've posted an answer on a very similar topic. You can find it here. It describes the usage of a Service without selector, which is basically what you mentioned in your question. And yes, it also can be used for configuring access to your Postgresql instance running on the same host. As this kind of Service doesn't have selectors by its definition, no endpoint is automatically created by kubernetes and you need to create one by yourself. Once you have the IP address of your Postgres instance (in our example it is 10.1.2.3) you can use it in your endpoint definition.
Once you configure everything on the side of kubernetes you may still encounter an issue with Postgres. In your Pod that is trying to connect to the Postgres instance you may see the following error message:
org.postgresql.util.PSQLException: FATAL: no pg_hba.conf entry for host 10.1.7.151
It basically means that your pg_hba.conf file lacks the required entry that would allow your Pod to access your Postgresql database. Authentication is host-based, so in other words only hosts with certain IPs or with IPs within certain IP range are allowed to authenticate.
Client authentication is controlled by a configuration file, which
traditionally is named pg_hba.conf and is stored in the database
cluster's data directory. (HBA stands for host-based authentication.)
So now you probably wonder which network you should allow in your pg_hba.conf. To handle cluster networking Microk8s uses flannel. Take a look at the content of your /var/snap/microk8s/common/run/flannel/subnet.env file. Mine looks as follows:
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.53.1/24
FLANNEL_MTU=1410
FLANNEL_IPMASQ=false
Adding to your pg_hba.conf only flannel subnet should be enough to ensure that all your Pods can connect to Posgresql.

Related

Clean way to connect to services running on the same host as the Kubernetes cluster

I have a single node Kubernetes cluster, installed using k3s on bare metal. I also run some services on the host itself, outside the Kubernetes cluster. Currently I use the external IP address of the machine (192.168.200.4) to connect to these services from inside the Kubernetes network.
Is there a cleaner way of doing this? What I want to avoid is having to reconfigure my Kubernetes pods if I decide to change the IP address of my host.
Possible magic I which existed: a Kubernetes service or IP that automagically points to my external IP (192.168.200.4) or a DNS name that points the node's external IP address.
That's what ExternalName services are for (https://kubernetes.io/docs/concepts/services-networking/service/#externalname):
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ExternalName
externalName: ${my-hostname}
ports:
- port: 80
Then you can access the service from withing kubernetes as my-service.${namespace}.svc.cluster.local.
See: https://livebook.manning.com/concept/kubernetes/external-service
After the service is created, pods can connect to the external service
through the external-service.default.svc.cluster.local domain name (or
even external-service) instead of using the service’s actual FQDN.
This hides the actual service name and its location from pods
consuming the service, allowing you to modify the service definition
and point it to a different service any time later, by only changing
the externalName attribute or by changing the type back to ClusterIP
and creating an Endpoints object for the service—either manually or by
specifying a label selector on the service and having it created
automatically.
ExternalName services are implemented solely at the DNS level—a simple
CNAME DNS record is created for the service. Therefore, clients
connecting to the service will connect to the external service
directly, bypassing the service proxy completely. For this reason,
these types of services don’t even get a cluster IP.
This relies on using a resolvable hostname of your machine. On minikube there's a DNS alias host.minikube.internal that is setup to resolve to an IP address that routes to your host machine, I don't know if k3s supports something similar.
Thanks #GeertPt,
With minikube's host.minikube.internal in mind I search around and found that CoreDNS has a DNS entry for each host it's running on. This only seems the case for K3S.
Checking
kubectl -n kube-system get configmap coredns -o yaml
reveals there is the following entry:
NodeHosts: |
192.168.200.4 my-hostname
So if the hostname doesn't change, I can use this instead of the IP.
Also, if you're running plain docker you can use host.docker.internal to access the host.
So to sum up:
from minikube: host.minikube.internal
from docker: host.docker.internal
from k3s: <hostname>

Kubernetes service with external name not discoverable

I'm deploying a nodejs application into a kubernetes cluster. This application needs access to an external database which is public available under db.external-service.com. For this purpose a service of the type ExternalName is created.
kind: Service
apiVersion: v1
metadata:
name: postgres
spec:
type: ExternalName
externalName: db.external-service.com
In the deployment an environment variable which provides the database hostname for the application is set to the name of this service.
env:
- name: DB_HOST
value: postgres
The problem is that when the nodejs application try to connect to the database ends up with this error message.
Error: getaddrinfo ENOTFOUND postgres
Already tried to use the full hostname postgres.<my-namespace>.svc.cluster.local without success.
What cloud be wrong with this setup?
EDIT:
It works if I use directly the plain ip address behind db.external-service.com in my pod configuration
It dose not work if I use the hostname directly in my pod configuration
I can ping the hostname with one of my pods: kubectl exec my-pod-xxx -- ping db.external-service.com has the right ip address
It turns out that the Kubernetes worker nodes are not on the allow list from the database. So the connection timed out.
Seems like your Pod is not able to resolve DNS db.external-service.com to an IP Address.
In Kubernetes, Pods use CoreDNS Pods to resolve Service Names to Service IP Addresses.
https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/
If CoreDNS Pods are not able to resolve the DNS to IP Address it is supposed to redirect the request to the Nameserver configured in the Host/VM/Node resolv.conf because dnsPolicy for CoreDNS Pods is Default. https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
So what is the dnsPolicy of your Pod ?
Are you able to resolve DNS db.external-service.com to an IP Address from the Host/VM/Node on which the CoreDNS Pod is running on ?

access postgres in kubernetes from an application outside the cluster

Am trying to access postgres db deployed in kubernetes(kubeadm) on centos vms from another application running on another centos vm. I have deployed postgres service as 'NodePort' type. My understanding is we can deploy it as LoadBalancer type only on cloud providers like AWS/Azure and not on baremetal vm. So now am trying to configure 'ingress' with NodePort type service. But am still unable to access my db other than using kubectl exec $Pod-Name on kubernetes master.
My ingress.yaml is
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: postgres-ingress
spec:
backend:
serviceName: postgres
servicePort: 5432
which does not show up any address as below
NAME HOSTS ADDRESS PORTS AGE
postgres-ingress * 80 4m19s
am not even able to access it from pgadmin on my local mac. Am I missing something?
Any help is highly appreciated.
Ingress won't work, it's only designed for HTTP traffic, and the Postgres protocol is not HTTP. You want solutions that deal with just raw TCP traffic:
A NodePort service alone should be enough. It's probably the simplest solution. Find out the port by doing kubectl describe on the service, and then connect your Postgres client to the IP of the node VM (not the pod or service) on that port.
You can use port-forwarding: kubectl port-forward pod/your-postgres-pod 5432:5432, and then connect your Postgres client to localhost:5432. This is my preferred way for accessing the database from your local machine (it's very handy and secure) but I wouldn't use it for production workloads (kubectl must be always running so it's somewhat fragile and you don't get the best performance).
If you do special networking configuration, it is possible to directly access the service or pod IPs from outside the cluster. You have to route traffic for the pod and service CIDR ranges to the k8s nodes, this will probably involve configuring your VM hypervisors, routers and firewalls, and is highly dependent on what networking (CNI) plugin are you using for your Kubernetes cluster.

How can a Kubernetes pod connect to database which is running in the same local network (outside the cluster) as the host?

I have a Kubernetes cluster (K8s) running in a physical server A (internal network IP 192.168.200.10) and a PostgreSQL database running in another physical server B (internal network IP 192.168.200.20). How can my Java app container (pod) running in the K8s be able to connect to the PostgreSQL DB in server B?
OS: Ubuntu v16.04
Docker 18.09.7
Kubernetes v1.15.4
Calico v3.8.2
Pod base image: openjdk:8-jre-alpine
I have tried following this example to create a service and endpoint
kind: Service
apiVersion: v1
metadata:
name: external-postgres
spec:
ports:
- port: 5432
targetPort: 5432
---
kind: Endpoints
apiVersion: v1
metadata:
name: external-postgres
subsets:
- addresses:
- ip: 192.168.200.20
ports:
- port: 5432
And had my JDBC connection string as: jdbc:postgresql://external-postgres/MY_APPDB , but it doesn't work. The pod cannot ping server B or telnet the DB using the said internal IP or ping external-postgres service name. I do not wish to use "hostNetwork: true" or connect server B via a public IP.
Any advice is much appreciated. Thanks.
I just found out the issue is due to the K8s network conflict with the server local network (192.168.200.x)
subnet.
During the K8s cluster initialization
kubadmin init --pod-network-cidr=192.168.0.0/16
The CIDR 192.168.0.0/16 IP range must be change to something else eg. 10.123.0.0/16
And this IP range must be also changed in the calico.yaml file before applying the Calico plugin:
# The default IPv4 pool to create on startup if none exists. Pod IPs will be
# chosen from this range. Changing this value after installation will have
# no effect. This should fall within `--cluster-cidr`.
- name: CALICO_IPV4POOL_CIDR
value: "10.123.0.0/16"
Can now ping and telnet server B after reset and re-init the K8s cluster with the different CIDR.
I guess you can replace CALICO_IPV4POOL_CIDR without re-spawning K8s cluster via kubeadm builder tool, maybe it can be useful in some circumstances.
Remove current Calico CNI plugin installation, eg.:
$ kubectl delete -f https://docs.projectcalico.org/v3.8/manifests/calico.yaml
Install Calico CNI addon, supplying CALICO_IPV4POOL_CIDR parameter with a desired value:
$ curl -k https://docs.projectcalico.org/v3.8/manifests/calico.yaml --output some_file.yaml && sed -i "s~$old_ip~$new_ip~" some_file.yaml && kubectl apply -f some_file.yaml
Re-spin CoreDNS pods:
$ kubectl delete pod --selector=k8s-app=kube-dns -n kube-system
Wait until CoreDNS pods obtain IP address from a new network CIDR pool.

Accessing Postgres RDS from Kubernetes cluster in AWS

My Kubernetes cluster setup has n-tier web application running in dev and test environments on AWS. For the production environment, postgres RDS was chosen, to ensure periodic backup. While creating a postgres RDS instance, kubernetes-vpc was selected for db-subnet to keep networking stuff simple during pilot run.
Also, security group selected is the same as kubernetes-minions.
Following is the service and endpoint yaml:
apiVersion: v1
kind: Service
metadata:
labels:
name: pgsql-rds
name: pgsql-rds
spec:
ports:
- port: 5432
protocol: TCP
targetPort: 5432
--
apiVersion: v1
kind: Endpoints
metadata:
name: pgsql-rds
subsets:
- addresses:
- ip: 52.123.44.55
ports:
- port: 5432
name: pgsql-rds
protocol: TCP
When web-app service and deployment is created, it's unable to connect to RDS instance.
The log is as follows:
java.sql.SQLException: Error in allocating a connection. Cause: Connection could not be allocated because: Connection to pgsql-rds:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
What am I missing? any pointers to resolve the issue appreciated.
This has to do with DNS resolving. When you use the RDS dns name INSIDE the same VPC it will be resolved to a private ip. When you use the same dns name on the internet or another VPC you will get the public ip of the RDS instance.
This is a problem because from another VPC you can not make use of the load balancing feature unless you expose the RDS instance to the public internet.
It's been a while the issue was resolved.
Don't exactly remember now, which step I missed that caused connection problem.
But, below are the steps that did work for me.
Pre-requisite: kubernetes cluster is set up with vpc ('k8s-vpc')
Create VPC SUBNET
Go to vpc dashboard, ensure same aws region as k8s minion. (you will see existing 'k8s-vpc')
Create subnet with each availability zone.
Select 'k8s-vpc' as vpc from drop-down.
CIDR could be 172.20.16.0/24 or 172.20.32.0/24
Create DB SUBNET and SUBNET GROUP FOR VPC of k8s minion if not already available.
Go to RDS Dashboard.
Create subnet group (e.g. my-db-subnet-group) for DB and add all subnet from step 1 to create subnet group.
From RDS Dashboard create Parameter Group
(e.g. my-db-param-group) for Postgres (version 9.5 in this example)
Copy value for max_connections to the max_prepared_transactions field and save
Create RDS instance for Postgres DB
Launch DB instance -> select Engine Postgres -> Choose stage (Production or Dev/Test)
-> Give instance spec.s (instance type & disk space etc.) and specify DB settings (user/password)
-> Configure Advanced settings
vpc selection as 'k8s-vpc'
DB subnet should be one created in previous step (my-db-subnet-group)
VPC security group should be from Kubernetes minions - so that no additional config. required for access from minions
Select Publicly Accessible - to connect to postgres from internet
Select Parameter Group as 'my-db-param-group'.
Specify Database options, backup and maintenance options and finally launch the instance
Also check security group of VPC and add inbound rule to allow connection to postgres port.
You can test connection from one of the k8s pod (kubectl exec -it) where postgres client is installed.
Make sure to change user to postgres.
Connect to RDS using psql as shown below:
$ psql --host=my-rds-dev.cyhi3va0y2or.ap-northeast-1.rds.amazonaws.com --port=5432 --username=<masterUserName> --password --dbname=<masterDB>
If everything is set up correctly, it should prompt you for password of db user.
Providing correct password will finally connect to RDS.
This article was of great help.
Your IP is of the form: 52.123.44.55. This is a public IP. See the official RFC
Since you said both are in the same VPC, you could have used the internal IP address instead.
That said, the error "Connection to pgsql-rds:5432 refused" means that the address was resolved, otherwise you would get "psql: error: could not translate host name "psql-rds" to address: Name or service not known". Therefore, it is not a DNS issue as cited on another answer.
The cause of the block is likely that the security group was not configured to accept requests from the EC2 instance external IP address. This if the official AWS documentation on connecting to RDS scenarios.
You might have already whitelisted all connections from VPC, however double check the security groups. I would not recommend using a whitelist for the external IP address, however it works if you put the external IP address there. It is a security concern when you don't have an elastic IP address and there are data transfer costs unless you have a more complex setup.
That said, you could have avoided the Kubernetes resources and used the DNS address of the RDS instance.
If you had to avoid using the DNS address of the RDS instance directly, you could have used the following:
apiVersion: v1
kind: Service
metadata:
name: psql-rds
spec:
externalName: my-rds-dev.cyhi3va0y2or.ap-northeast-1.rds.amazonaws.com
ports:
- port: 5432
protocol: TCP
targetPort: 5432
sessionAffinity: None
type: ExternalName
With the setup above, you don't need a Kubernetes Endpoint. You can just use psql-rds, or any variation using the namespace as the domain or the fully qualified version, such as psql-rds.default. This is the documentation for the ExternalName
I see that the original poster mentioned the problem was solved, however it is not clear or well documented that the problem was a combination of using the external IP address and checking the security group rules.