Streamlit application reloads every 30secs when deployed on kubernetes - kubernetes-ingress

Hi I have deployed a streamlit application which acts as a UI for downloading data from our platform. After deploying on kubernetes I observe that application keeps reloading after every 30 seconds, which is very annoying.
If I access the app by port forwarding the service it works ok but somehow using via nginx, it has the above mentioned problem.
Has anyone faced this issue ?
I was looking into the streamlit forum and saw same issue like mine but there is no clear solution.
Streamlit reruns all 30 seconds
I do see a websocket time of ~30s in developer tool under network section in browser.
My k8s manifest file is as follows
apiVersion: apps/v1
kind: Deployment
metadata:
name: streamlit-deployment
labels:
app: streamlit
spec:
replicas: 1
selector:
matchLabels:
app: streamlit
template:
metadata:
labels:
app: streamlit
spec:
containers:
- name: streamlit
image: <image>:<tag>
imagePullPolicy: Always
ports:
- containerPort: 8501
livenessProbe:
httpGet:
path: /healthz
port: 8501
scheme: HTTP
timeoutSeconds: 1
readinessProbe:
httpGet:
path: /healthz
port: 8501
scheme: HTTP
timeoutSeconds: 1
resources:
limits:
cpu: 1
memory: 2Gi
requests:
cpu: 100m
memory: 745Mi
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data
---
apiVersion: v1
kind: Service
metadata:
labels:
app: streamlit
name: streamlit-service
spec:
ports:
- nodePort: 32640
port: 8501
protocol: TCP
targetPort: 8501
selector:
app: streamlit
sessionAffinity: None
type: NodePort
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: webapp
labels:
app: streamlit
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /webapp(/|$)(.*)
pathType: Prefix
backend:
service:
name: streamlit-service
port:
number: 8501
`
I tried to add following annotation in nginx definition but it didnt work
nginx.ingress.kubernetes.io/connection-draining: "true" nginx.ingress.kubernetes.io/connection-draining-timeout: "3000"
Also I looked into the source code and was wondering if its because of tornado settings in
lib/streamlit/web/server/server.py which has web socket ping timeout set as 30 seconds.
"websocket_ping_timeout": 30,
I tried to set this to a higher value and create a new image for my deployment, but unfortunately it didnt help.
I'd really appreciate any leads.

Finally, we found out that issue was due to the default time out of the GCP load balancer, which was causing the web socket connection to re-initialize every 30 secs. All we needed to do was change the time out in the backend configuration of the load balancer. I hope it helps.

Related

Exposing AKS cluster application using ingress

I am trying to expose my application inside the AKS cluster using ingress:
It creates a service and an ingress but somehow does not assign an address to the ingress. What could be a possible reason for this?
apiVersion: apps/v1
kind: Deployment
metadata:
name: dockerdemo
spec:
replicas: 1
selector:
matchLabels:
app: dockerdemo
template:
metadata:
labels:
app: dockerdemo
spec:
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: dockerdemo
image: devsecopsacademy/dockerapp:v3
env:
- name: ALLOW_EMPTY_PASSWORD
value: "yes"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
ports:
- containerPort: 80
name: redis
apiVersion: v1
kind: Service
metadata:
name: dockerdemo-service
spec:
type: ClusterIP
ports:
port: 80
selector:
app: dockerdemo
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress15
annotations:
kubernetes.io/ingress.class: addon-http-application-rounting
spec:
rules:
host: curefirsttestapp.cluster15-dns-c42b65ee.hcp.westeurope.azmk8s.io
http:
paths:
path: /
pathType: Prefix
backend:
service:
name: dockerdemo-service
port:
number: 80
Well, first make sure your application is up and functionning inside your K8s Cluster using a port-forword to your localhost
kubectl -n $NAMESPACE port-forward svc/$SERVICE :$PORT
if app is reachable and your call are getting back 200 Status, you can now move to the ingress part:
Make sure ingress controller is well installed under your services
kubectl -n $NAMESPACE get svc
Add a DNS record in your DNS zone which maps your domain.com to ingress controller $EXTERNAL_IP
Take a look at the ingress you created for your $SERVICE
kubectl -n $NAMESPACE get ingress
At this stage, if you application is well running and also the the ingress is well set, the app should be reachable trough domain.com, otherwise we'll need further debugging.
Make sure you have an ingress controller deployed. This is a load balancer service which can have either a public or private ip depending on your situation.
Make sure you have an ingress definition which has a rule to point to your service. This is the metadata which will tell your ingress controller how to route requests to its ip address. These routing rules can contain how to handle paths like strip, exact, etc....

How to use nginx ingress to route traffic based on port

I'm currently working on deploying ELK stack on kubernetes cluster, i was successfully able to use ClusterIP service and nginx-ingress on minikube to route inbound http traffic to kibana (5601 port), need inputs on how i can route traffic based on inbound port rather than path?
Using below Ingress object declaration, i was successfully able to connect to my kibana deployment, but how can i access other tools stack exposed on different ports (9200, 5044, 9600)?
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
name: ingress-service
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: kibana-service
servicePort: 5601
CUrl'ing minikube ip on default 80 port returns valid response
# curl http://<minikube-ip>/api/status
{"name":"kibana",....}
Note: i would not want to use NodePort, but would like to know if nodeport is the only way we can achieve the above?
As you already have minikube and minikube ingress addon enabled:
$ minikube addons list | grep ingress
| ingress | minikube | enabled ✅ |
| ingress-dns | minikube | enabled ✅ |
Just as reminder:
targetPort: is the port the container accepts traffic on (port where application runs inside the pod).
port: is the abstracted Service port, which can be any port other pods use to access the Service.
Please keep in mind that if your container will not be listening port specified in targetPort you will not be able to connect to the pod.
Also remember about firewall configuration to allow traffic.
As for example I've used this yamls:
apiVersion: v1
kind: Service
metadata:
name: service-one
spec:
selector:
key: application-1
ports:
- port: 81
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-1
spec:
replicas: 1
selector:
matchLabels:
key: application-1
template:
metadata:
labels:
key: application-1
spec:
containers:
- name: hello1
image: gcr.io/google-samples/hello-app:1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: service-two
spec:
selector:
key: application-2
ports:
- port: 82
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-2
spec:
replicas: 1
selector:
matchLabels:
key: application-2
template:
metadata:
labels:
key: application-2
spec:
containers:
- name: hello2
image: gcr.io/google-samples/hello-app:2.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- http:
paths:
- path: /hello
backend:
serviceName: service-one
servicePort: 81
- path: /hello2
backend:
serviceName: service-two
servicePort: 82
service/service-one created
deployment.apps/deployment-1 created
service/service-two created
deployment.apps/deployment-2 created
Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.networking.k8s.io/ingress created
Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
Please keep in mind that soon Minikube will change apiVersion as per warning above.
Below output of this configuration:
$ curl http://172.17.0.3/hello
Hello, world!
Version: 1.0.0
Hostname: deployment-1-77ddb77d56-2l4cp
minikube-ubuntu18:~$ curl http://172.17.0.3/hello2
Hello, world!
Version: 2.0.0
Hostname: deployment-2-fb984955c-5dvbx
You could use:
paths:
- path: /elasticsearch
backend:
serviceName: elasticsearch-service
servicePort: 100
- path: /anotherservice
backend:
serviceName: another-service
servicePort: 101
Where service would looks like:
name: elasticsearch-service
...
ports:
- port: 100
targetPort: 9200
---
name: another-service
...
ports:
- port: 101
targetPort: 5044
However, if you would need more advanced path configuration you can also use rewrite. Also you can use default backend to redirect to specific service.
More information about accessing Minikube you can find in Minikube documentation.
Is it what you were looking for or something different?

Kubernetes ingress same path multiple ports

After much googling and searching (even here), I'm not able to find a definitive answer to my question. So I hope someone here might be able to point me in the right direction.
I have a Kube Service definition that's already working for me, but right now I've simply exposed it with just a LoadBalancer. Here's my current Service yaml:
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: namespace1
labels:
app: my-service
spec:
type: LoadBalancer
selector:
app: my-service
tier: web
ports:
- name: proxy-port
port: 8080
targetPort: 8080
- name: metrics-port
port: 8082
targetPort: 8082
- name: admin-port
port: 8092
targetPort: 8092
- name: grpc-port
port: 50051
targetPort: 50051
This is obviously only TCP load-balanced. What I want to do is secure this with Mutual TLS, so that the server will only accept connections from my client with the authorized certificate.
From all I can tell in Kube land, what I need to do that is an Ingress definition. I've been researching all the docs I can find on kind:Ingress and I can't seem to find anything where it allows me to create a single Ingress with multiple ports on the same path!
Am I missing something here? Is there no way to create a K8s Ingress that simply has the same functionality as the above Service definition?
To my knowledge you cannot use custom ports (e.g 8080) for HTTPS LoadBalancer backed with Ingress Controller (e.g. NGINX HTTP(S) Proxy), as
currently the port of an Ingress is implicitly :80 for http and :443 for https, as official doc reference for IngressRule explains.
I think the workaround would be to use different host per service, like with this example of Ingress resource:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: name-virtual-host-ingress
spec:
rules:
- host: proxy.foo.com
http:
paths:
- backend:
serviceName: proxy-svc
servicePort: 8080
- host: metrics.foo.com
http:
paths:
- backend:
serviceName: metrics-svc
servicePort: 8082
- host: admin.foo.com
http:
paths:
- backend:
serviceName: admin-svc
servicePort: 8092
- host: grpc.foo.com
http:
paths:
- backend:
serviceName: grpc-svc
servicePort: 50051
I faced the same situation where we had to expose port 80,443 and 50051 on the same host. Using traefik v2+ on K3S, this is how I solved it:
Apply this file to modify traefik config; this is with K3S. If you have installed traefik directly with the chart, add a values file with the same config as below.
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
grpc:
port: 50051
protocol: TCP
expose: true
exposedPort: 50051
After it is done, watch the traefik service be updated with the new config.
If its not working, make sure the port youve set are free and not used by another service.
When this is done, you can create IngressRoute object. Here I got one for grpc (50051) and one for web (80/443).
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: db
spec:
entryPoints:
- web
- websecure
routes:
- kind: Rule
match: Host(`lc1.nebula.global`)
services:
- name: db
port: 80
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: db-grpc
spec:
entryPoints:
- grpc
routes:
- kind: Rule
match: Host(`lc1.nebula.global`)
services:
- name: db-grpc
port: 50051
EDIT:::
if running with your own instance of k3s with the community helm chart (not the one provided by k3s). Here is the equivalent config I have:
traefik:
rbac:
enabled: true
ports:
web:
redirectTo: websecure
websecure:
tls:
enabled: true
grpc:
port: 50051
protocol: TCP
expose: true
exposedPort: 50051
podAnnotations:
prometheus.io/port: "8082"
prometheus.io/scrape: "true"
providers:
kubernetesIngress:
publishedService:
enabled: true
priorityClassName: "system-cluster-critical"
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
additionalArguments:
- "--entrypoints.grpc.http2.maxconcurrentstreams=10000"
ingress:
enabled: false
host: traefik.local
annotations: {}
In my case, I also increase the max number of concurrent http2 streams for grpc.

Restoring wordpress and mysql data to kubernetes volume

I am currently running mysql, wordpress and my custom node.js + express application on kubernetes pods in the same cluster. Everything is working quite well but my problem is that all the data will be reset if I have to rerun the deployments, services and persistent volumes.
I have configured wordpress quite extensively and would like to save all the data and insert it again after redeploying everything. How is this possible to do or am I thinking something wrong? I am using the mysql:5.6 and wordpress:4.8-apache images.
I also want to transfer my configuration to my other team members so they don't have to configure wordpress again.
This is my mysql-deploy.yaml
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
app: wordpress
tier: mysql
clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: hidden
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
This the wordpress-deploy.yaml
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: NodePort
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: wordpress:4.8-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_PASSWORD
value: hidden
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim
How is this possible to do or am I thinking something wrong?
It might be better to move configuration mindset from working directly on base container instances to configuring container images/manifests. You have several approaches there, just some pointers:
Create own Dockerfile based on images you referenced and bundle configuration files inside them. This is viable approach if configuration is more or less static and can be handled with env vars or infrequent builds of docker images, but require docker registry handling to work with k8s. In this approach you would add all changed files to build context of docker and then COPY them to appropriate places.
Create ConfigMaps and mount them on container filesystem as config files where change is required. This way you can still use base images you reference directly but changes are limited to kubernetes manifests instead of rebuilding docker images. Approach in this case would be to identify all changed files on container, then create kubernetes ConfigMaps out of them and finally mount appropriately. I don't know which exactly things you are changing but here is example of how you can place nginx config in ConfigMap:
kind: ConfigMap
apiVersion: v1
metadata:
name: cm-nginx-example
data:
nginx.conf: |
server {
listen 80;
...
# actual config here
...
}
and then mount it in container in appropriate place like so:
...
containers:
- name: nginx-example
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: nginx-conf
volumes:
- name: nginx-conf
configMap:
name: cm-nginx-example
items:
- key: nginx.conf
path: nginx.conf
...
Mount persistent volumes (subpaths) on places where you need configs and keep configuration on persistent volumes.
Personally, I'd probably opt for ConfigMaps since you can easily share/edit those with k8s deployments and configuration details are not lost as some mystical 'extensive work' but can be reviewed, tweaked and stored to some code versioning system for version tracking...

Different ingress in different Namespace in kubernetes

I have created two different namespaces for different environment. one is devops-qa and another is devops-dev. I created two ingress in different namespaces. So while creating ingress of qa env in devops-qa namespace, the rules written inside ingress of qa is working fine. Means I am able to access the webpage of qa env. The moment I will create the ingress of dev env in devops-dev namespace, I will be able to access the webpage of dev env but wont be able to access the webpage of qa. And when I delete the dev ingress then again I will be able to access the qa env website
Below is the ingree of both dev and qa env.
Dev Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
name: cafe-ingress-dev
namespace: devops-dev
spec:
tls:
- hosts:
- cafe-dev.example.com
secretName: default-token-drk6n
rules:
- host: cafe-dev.example.com
http:
paths:
- path: /
backend:
serviceName: miqpdev-svc
servicePort: 80
QA Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
name: cafe-ingress-qa
namespace: devops-qa
spec:
tls:
- hosts:
- cafe-qa.example.com
secretName: default-token-jdnqf
rules:
- host: cafe-qa.example.com
http:
paths:
- path: /greentea
backend:
serviceName: greentea-svc
servicePort: 80
- path: /blackcoffee
backend:
serviceName: blackcoffee-svc
servicePort: 80
The token mentioned in the ingress file is of each namespace. And the nginx ingress controller is running in QA namespace
How can i run both the ingress and will be able to get all the websites deployed in both dev and qa env ?
I actually Solved my problem. I did everything correct. But only thing I did not do is to map the hostname with the same ip in Route53. And instead of accessing the website with hostname, I was accessing it from IP. Now after accessing the website from hostname, I was able to access it :)
Seems like you posted here and got your answer. The solution is to deploy a different Ingress for each namespace. However, deploying 2 Ingresses complicates matters because one instance has to run on a non-standard port (eg. 8080, 8443).
I think this is better solved using DNS. Create the CNAME records cafe-qa.example.com and cafe-dev.example.com both pointing to cafe.example.com. Update each Ingress manifest accordingly. Using DNS is somewhat the standard way to separate the Dev/QA/Prod environments.
Had the same issue, found a way to resolve it:
you just need to add the "--watch-namespace" argument to the ingress controller that sits under the ingress service that you've linked to your ingress resource. Then it will be bound only to the services within the same namespace as the ingress service and its pods belong to.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
namespace: my-namespace
name: nginx-ingress-controller
spec:
replicas: 1
selector:
matchLabels:
name: nginx-ingress-lb
template:
metadata:
labels:
name: nginx-ingress-lb
spec:
serviceAccountName: ingress-account
containers:
- args:
- /nginx-ingress-controller
- "--default-backend-service=$(POD_NAMESPACE)/default-http-backend"
- "--default-ssl-certificate=$(POD_NAMESPACE)/secret-tls"
- "--watch-namespace=$(POD_NAMESPACE)"
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
name: nginx-ingress-controller
image: "quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.24.1"
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
namespace: my-namespace
name: nginx-ingress
spec:
type: LoadBalancer
ports:
- name: https
port: 443
targetPort: https
selector:
name: nginx-ingress-lb
You can create nginx ingress cotroller in kube-system namespace instead of creating it in QA namespace.