¿Un pod de OpenVPN en Kubernetes no reenvía nada? Revise ip_forward
Los clientes hacen ping al pod de la VPN y nada más allá. El espacio de nombres tiene el reenvío desactivado, y el arreglo debe sobrevivir a la admisión, a los reinicios y a la ruta de vuelta.
Su portátil muestra un túnel verde, el registro del cliente informa de que se han enviado rutas, y la única dirección que responde es la IP del túnel del propio servidor VPN. Los pings más allá mueren sin importar a qué apunte, ya sea un ClusterIP, la dirección de otro pod o un nombre de host público. Esa combinación es diagnóstica, no vaga: la desencapsulación funciona y el segundo salto no.
Me lo encontré en un nodo Debian con una imagen de OpenVPN instalada por chart, y el túnel era la parte fácil. El cliente tomó una concesión 10.8.0.x, 10.8.0.1 respondió y 10.96.0.10, el servicio DNS del clúster, agotó el tiempo. Nada del registro del servidor parecía estar mal, y por eso mismo la gente empieza a reescribir reglas de iptables que nunca necesitó tocar.
Un pod de VPN es un enrutador, y merece decirse sin rodeos. Los paquetes cifrados llegan, se desenvuelven en el espacio de nombres de red del propio pod y luego tienen que salir por otra interfaz. Linux rechaza ese segundo paso salvo que el reenvío esté activado dentro de ese mismo espacio de nombres.
A qué puede y a qué no puede llegar el cliente
La dirección que responde pertenece al extremo del túnel, y el proceso de la VPN la posee localmente, así que el reenvío no interviene en absoluto. Todo lo demás, incluido otro pod, un VIP de servicio o internet, necesita que el núcleo mueva un paquete de tun0 a eth0. Cuando ese movimiento se rechaza, el síntoma es un silencio uniforme y no un rechazo, porque nada genera un error ICMP de vuelta por el túnel. Obtiene tiempos de espera, no mensajes de inalcanzable, y esa es la pista de que nadie está enrutando en absoluto.
Dos minutos de clasificación estrechan la capa. Si los nombres DNS fallan mientras los pings crudos a una IP de servicio funcionan, el problema es la fontanería de DNS, no el reenvío. Si todo destino más allá de la dirección del par agota el tiempo, incluida una IP desnuda, empiece por el espacio de nombres.
Lea el ajuste dentro del espacio de nombres
Cada pod recibe su propio espacio de nombres de red, y net.ipv4.ip_forward es un sysctl por espacio de nombres. Cambiarlo en el nodo no le dice nada sobre el contenedor, y es una forma fiable de pasar una tarde en la máquina equivocada.
kubectl exec -it deploy/openvpn -- sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 0
Un cero confirma el diagnóstico. El valor tampoco se hereda de la configuración de ejecución del nodo: parte del valor predeterminado de la imagen cada vez que se crea un contenedor. Por eso un arreglo aplicado a mano con kubectl exec ... sysctl -w desaparece en el siguiente reinicio y vuelve como una avería que parece intermitente.
Activarlo para que el manifiesto lo conserve
La opción duradera es un contenedor init que se ejecuta en el mismo pod sandbox y escribe el valor antes de que arranque el contenedor de la VPN. Los contenedores de un pod comparten un espacio de nombres de red, así que el ajuste pasa al contenedor principal.
initContainers:
- name: enable-forwarding
image: busybox:1.36
command: ["sysctl", "-w", "net.ipv4.ip_forward=1"]
securityContext:
privileged: true
Ese privileged: true es la línea a la que los revisores se oponen, y tienen razón. Escribir un sysctl requiere privilegio o una capacidad de alcance estrecho, y un clúster que aplica el Pod Security Standard restringido puede rechazar de plano un contenedor init privilegiado. Si la admisión rechaza el pod, lo sabe en el momento de crearlo, lo que al menos es honesto y fácil de leer.
La alternativa más limpia es la lista securityContext.sysctls a nivel de pod, que permite al kubelet aplicar sysctls con espacio de nombres sin conceder privilegios a ningún contenedor. Solo cubre nombres de la lista de permitidos del clúster. Que net.ipv4.ip_forward esté en esa lista depende de cómo se configuró el kubelet, y un pod que pide un sysctl fuera de ella se rechaza en la admisión o se queda atascado en CreateContainerConfigError mientras se reintenta el sandbox. Compruebe la lista de permitidos antes de suponer que el campo está roto.
Antes de construir a mano cualquiera de los dos enfoques, mire si el chart ya expone el interruptor, a menudo como algo parecido a ipForwardInitContainer: true. Usar el valor del chart mantiene pequeño su archivo de anulación y sobrevive a la próxima actualización, cosa que las ediciones de Deployment parcheadas a mano no hacen.
La segunda mitad: ruta de vuelta y política
El reenvío activado es necesario, no suficiente. Si los clientes usan un grupo privado para el que la red del clúster no tiene ruta, las respuestas no encuentran el camino de vuelta al túnel, y el pod tiene que enmascarar ese tráfico hacia su destino.
La política se apoya encima. Los objetos NetworkPolicy y las reglas de cortafuegos de la nube se evalúan contra la dirección de origen que lleva el paquete tras la traducción, así que una regla escrita para el grupo de clientes puede dejar de coincidir en silencio en cuanto se activa el enmascaramiento.
Cuando los clientes solo necesitan llegar a servicios dentro del clúster, que la VPN termine en el VIP del servicio es un diseño más pequeño que enrutar hacia la red de pods. También mantiene el grupo de clientes completamente fuera de la tabla de rutas del CNI.
Aislar el salto que falla del cliente
Trabaje hacia fuera y deténgase en el primer paso silencioso. Empiece por el par de la VPN, luego pruebe la dirección de algún pod, después un VIP de servicio y solo después un destino público. Cada éxito mueve la frontera un salto, y cada fallo nombra la capa que merece inspección. Tras cualquier despliegue, entre con exec y lea el sysctl otra vez, porque un pod reemplazado parte del valor predeterminado de la imagen y un arreglo que nunca estuvo en el manifiesto ya se ha ido.
La documentación de Kubernetes sobre el uso de sysctls en un clúster enumera qué nombres pueden fijarse mediante el contexto de seguridad del pod, y es el primer lugar donde mirar cuando la admisión rechaza el cambio.