Kubernetes Kubernetes

Services: networking estable para Pods efímeros

Dale a los Pods una dirección estable, exponlos dentro y fuera del clúster y restringe quién puede hablar con quién.

14 min Intermedio

Cada Pod recibe su propia dirección IP, pero los Pods van y vienen, y sus direcciones con ellos. Un Service proporciona una dirección estable delante de un conjunto cambiante de Pods. Esta guía se apoya en el Deployment web de la guía anterior y cubre los Services, cómo exponerlos fuera del clúster, Ingress y las network policies.

El problema: las IP de los Pods cambian

kubectl get pods -l app=web -o wide
kubectl delete pod POD_NAME
kubectl get pods -l app=web -o wide

El Pod de reemplazo tiene una IP distinta y puede estar en otro nodo. Todo lo que tuviera la dirección antigua escrita a mano ahora está roto, y con réplicas que van y vienen, no es posible seguir las direcciones a mano.

Crea un Service ClusterIP

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

El selector elige los Pods que respaldan el Service, usando las mismas etiquetas que viste con los Deployments. port es el puerto en el que escucha el Service y targetPort es el puerto de los Pods al que reenvía, que debe coincidir con el puerto del contenedor. Como no se define type, este es un Service ClusterIP: accesible solo desde dentro del clúster, en una IP virtual estable.

kubectl apply -f service.yaml
kubectl get service web
kubectl get endpointslices -l kubernetes.io/service-name=web

El EndpointSlice lista las IP de los Pods que hay actualmente detrás del Service. Escala el Deployment hacia arriba o hacia abajo y se actualiza automáticamente, mientras que la dirección del propio Service nunca cambia. (Los clústeres y tutoriales más antiguos usan kubectl get endpoints web para la misma información. Las versiones recientes deprecan esa API en favor de EndpointSlices.)

Dentro del clúster, el Service también recibe un nombre DNS: web desde el mismo namespace, o web.NAMESPACE.svc.cluster.local desde cualquier lugar. Pruébalo desde un Pod desechable:

kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web

Deberías ver la página de bienvenida de nginx. En cada nodo, kube-proxy mantiene las reglas que reparten las conexiones entre los Pods listos del Service. En el modo iptables habitual esto es efectivamente aleatorio en lugar de un round-robin estricto, y los Pods que fallan su comprobación de readiness se sacan de la rotación.

Expón un Service fuera del clúster

Hay dos tipos de Service más, y cada uno se construye sobre el anterior:

  • NodePort además abre un puerto (30000–32767) en cada nodo. Todo lo que pueda llegar a la IP de un nodo puede llegar al Service en ese puerto.
  • LoadBalancer además pide al entorno un balanceador de carga externo que apunte a esos node ports. En un proveedor de nube ocurre automáticamente. En bare metal necesitas algo como MetalLB.
apiVersion: v1
kind: Service
metadata:
  name: web-public
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

Si omites nodePort, Kubernetes elige uno libre. En minikube con el driver de Docker, la IP del nodo a menudo no es accesible directamente desde tu portátil, así que deja que minikube lo abra por ti:

minikube service web-public --url

Para un Service LoadBalancer en minikube, ejecuta minikube tunnel en otra terminal.

Enruta HTTP con Ingress

Un LoadBalancer por Service resulta caro y solo maneja tráfico en bruto. Un Ingress te permite enrutar tráfico HTTP(S) a muchos Services por host y ruta, a través de un único punto de entrada, y terminar TLS allí. Un Ingress solo describe las reglas. Necesita un ingress controller ejecutándose en el clúster para actuar sobre ellas (en minikube, minikube addons enable ingress).

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  rules:
    - host: web.example.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Las peticiones para web.example.local ahora van al Service web. Los certificados del host pueden automatizarse con una herramienta como cert-manager.

Restringe el tráfico con NetworkPolicy

Por defecto, cada Pod puede hablar con cualquier otro Pod. Una NetworkPolicy selecciona Pods y lista qué tráfico se permite. Una vez que cualquier política selecciona un Pod para ingress, todo lo que no esté permitido explícitamente se deniega. Empieza denegando todo el tráfico entrante en un namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress

El podSelector vacío selecciona todos los Pods del namespace. Luego permite solo lo que necesites, por ejemplo tráfico hacia los Pods web desde Pods con la etiqueta role: client:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-client-to-web
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: client
      ports:
        - protocol: TCP
          port: 80

Las network policies tienen ámbito de namespace y solo se aplican si el plugin de red del clúster las soporta, por ejemplo Calico o Cilium. Algunas configuraciones locales mínimas aceptan los objetos pero los ignoran en silencio, así que comprueba que una conexión denegada realmente esté bloqueada.

Guías populares