Encontramos un comportamiento bastante interesante al trabajar con el servicio Headless en Kubernetes. En nuestro caso, el problema ocurrió con mongos, pero es relevante para cualquier servicio Headless. Los invito a leer nuestra historia y tratar de jugar con este problema localmente.
En uno de los proyectos, usamos MongoDB y Kubernetes. MongoDB tiene un componente: mongos. A través de él, las consultas se ejecutan en un clúster MongoDB fragmentado (puede asumir que esto es solo un proxy complicado). Antes de pasar a Kubernetes, los servicios de mongos se instalaron directamente en cada host.
Al mover los servicios a Kubernetes, establecimos el grupo de mongos en un servicio Headless con escalado automático de implementación a través de HPA (Horizontal Pod Autoscaler).
Después de un tiempo, resultó que la aplicación no estaba funcionando muy bien con una disminución en la cantidad de Pods de mongos.
A través de la depuración, resultó que la aplicación se cuelga exactamente cuando intenta establecer una conexión con mongos ( net.Dial
en términos de Go) y coincide en el tiempo con detener cualquier Pod.
, Headless-: , IP- (ClusterIP: None
). DNS- IP Pod, .
Headless- , , Pod , :
mongodb- IP , , , ( «» mongos).
ClusterIP
«» .
gRPC- , .
ClusterIP
Pod .
, Pod , , IP- Pod. :
Pod DNS, DNS ;
DNS .
, Pod?
. , .
, Pod Out of Memory, , “connection refused” . , .
, .
SIGTERM
Pod mongos. 45 DNS ( Pod ). mongos 15 ( IP “connection refused”, ).
terminationGracePeriodSeconds
, Pod .
minReadySeconds
Pod .
, , IP- ( Pod , ).
minReadySeconds
. , : IP Pod.
minReadySeconds
- , Pod Terminating
. x2 Pod.
, IP- , DNS minReadySeconds
.
, minReadySeconds
gRPC-: , .
?
MiniKube nginx.
headless Service (service.yml
):
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
clusterIP: None
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
(dialer.go
):
package main
import (
"fmt"
"net"
"os"
"time"
)
const timeFormat = "15:04:05.999"
func main() {
address := os.Args[1]
last := ""
ticker := time.NewTicker(time.Millisecond * 100)
t := time.Now()
fmt.Printf("%s: === %s\n", t.Format(timeFormat), address)
for {
conn, err := net.DialTimeout("tcp", address, time.Millisecond*100)
var msg string
if conn != nil {
msg = fmt.Sprintf("connected (%s)", conn.RemoteAddr())
_ = conn.Close()
}
if err != nil {
msg = err.Error()
}
if last != msg {
now := time.Now()
if last != "" {
fmt.Printf("%s: --- %s: %v\n", now.Format(timeFormat), last, now.Sub(t))
}
last = msg
fmt.Printf("%s: +++ %s\n", now.Format(timeFormat), last)
t = now
}
<-ticker.C
}
}
nginx 80- . ( , ):
#!/bin/bash
echo "
tee dialer.go << EEOF
$(cat dialer.go)
EEOF
go run dialer.go nginx:80
" | kubectl --context=minikube run -i --rm "debug-$(date +'%s')" \
--image=golang:1.16 --restart=Never --
- :
16:57:19.986: === nginx:80
16:57:19.988: +++ dial tcp: lookup nginx on 10.96.0.10:53: server misbehaving
.
Deployment
Deployment (nginx.yml
):
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
replicas
, IP-.
Deployment livenessProbe
readinessProbe
. .
«» Deployment:
#!/bin/bash
kubectl --context minikube rollout restart deployment/nginx
Deployment. , : Pod Pod. Pod.
( ):
# Deployment
#
17:04:08.288: +++ connected (172.17.0.10:80)
17:07:32.187: --- connected (172.17.0.10:80): 3m23.899438044s
# nginx Pod,
# IP.
# Pod , "connection refused"
17:07:32.187: +++ dial tcp 172.17.0.10:80: connect: connection refused
17:07:32.488: --- dial tcp 172.17.0.10:80: connect: connection refused: 301.155902ms
# Pod , IP.
# IP- , .
17:07:32.488: +++ dial tcp 172.17.0.10:80: i/o timeout
17:07:38.448: --- dial tcp 172.17.0.10:80: i/o timeout: 5.960150161s
# IP Pod.
17:07:38.448: +++ connected (172.17.0.7:80)
Pod
Deployment , “connection refused”:
#!/bin/bash
kubectl --context minikube patch deployment nginx --output yaml --patch '
---
spec:
template:
spec:
containers:
- name: nginx
command: [ "sh" ]
# nginx
args:
- "-c"
- "nginx -g \"daemon off;\" && sleep 60"
# , sh SIGTERM
lifecycle:
preStop:
exec:
command: ["sh", "-c", "nginx -s stop"]
# , Pod-
#
terminationGracePeriodSeconds: 180
'
Pod ( SIGTERM
). , , Out Of Memory Segmentation fault, .
«» Deployment:
#!/bin/bash
kubectl --context minikube rollout restart deployment/nginx
( ):
# Deployment
#
17:58:10.389: +++ connected (172.17.0.7:80)
18:00:53.687: --- connected (172.17.0.7:80): 2m43.29763747s
# nginx Pod,
# IP.
# Pod , "connection refused".
# Pod sleep nginx.
18:00:53.687: +++ dial tcp 172.17.0.7:80: connect: connection refused
18:01:10.491: --- dial tcp 172.17.0.7:80: connect: connection refused: 16.804114254s
# IP Pod.
18:01:10.491: +++ connected (172.17.0.10:80)
Pod
Deployment , , Pod :
#!/bin/bash
kubectl --context minikube patch deployment nginx --output yaml --patch '
---
spec:
template:
spec:
containers:
- name: nginx
# nginx
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 60 && nginx -s stop"]
# , Pod
#
terminationGracePeriodSeconds: 180
'
«» Deployment:
#!/bin/bash
kubectl --context minikube rollout restart deployment/nginx
( ):
# Deployment
#
18:05:10.589: +++ connected (172.17.0.7:80)
18:07:10.689: --- connected (172.17.0.7:80): 2m0.099149168s
# IP Pod.
# Pod - .
18:07:10.689: +++ connected (172.17.0.10:80)
?
: .
SIGTERM
— DNS- Pod .
, DNS-.
, DNS- .
IP- ,
SIGTERM
minReadySeconds
.
Pod, / Pod “connection refused”, .
,
SIGTERM
Pod DNS .
, , .
.