Files
kubernetes/ingress.md
2025-08-17 12:56:18 +02:00

15 KiB
Raw Blame History

1. Cert-Manager: Gestión automática de certificados TLS en

Kubernetes

cert-manager es un componente esencial en entornos Kubernetes modernos. Se encarga de emitir y renovar automáticamente certificados TLS, eliminando la necesidad de gestionarlos manualmente. Esto es especialmente útil cuando los servicios se exponen a través de un Ingress Controller y se quiere TLS mediante Let's Encrypt.

¿Cómo funciona?

  • Se integra con el API de Kubernetes.
  • Usa recursos personalizados (Issuer y ClusterIssuer) para definir cómo se emitirán los certificados.
  • Funciona con ACME (como Let's Encrypt) para obtener certificados automáticamente.
  • Trabaja junto con los objetos Ingress para asegurar las rutas públicas de nuestros servicios.
  • Puede usar solvers http01 (habitualmente expuestos por el Ingress Controller) para verificar la propiedad del dominio.

2. Despliegue de cert-manager

Paso 1: Crear el namespace

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager
kubectl apply -f namespace.yaml

Paso 2: Instalar cert-manager desde el repositorio oficial

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml

Esto instalará los CRDs necesarios, así como los controladores en el namespace cert-manager.


3. Configurar los emisores de certificados

Issuer vs ClusterIssuer

  • Issuer: solo disponible en un namespace concreto.
  • ClusterIssuer: disponible en todo el clú ster.

Aquí usamos ClusterIssuer para que cualquier Ingress de cualquier namespace pueda solicitar certificados.

Paso 3: Crear los ClusterIssuers

# clusterissuer-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    email: xavor@hotmail.es
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik
# clusterissuer-prod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: xavor@hotmail.es
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik

⚠️ Asegúrate de que ingressClassName coincida con el del Ingress Controller instalado (como nginx, traefik, haproxy).

kubectl apply -f clusterissuer-staging.yaml
kubectl apply -f clusterissuer-prod.yaml

4. Uso de kustomize para aplicar en bloque

Puedes usar kustomize para aplicar todo desde un solo punto:

# kustomization.yaml
namespace: cert-manager
resources:
  - clusterissuer-prod.yaml
  - clusterissuer-staging.yaml

Y aplicarlo con:

kubectl apply -k .

5. Ingress-NGINX: Controlador de entrada HTTP/S para Kubernetes

Ingress-NGINX es uno de los controladores de entrada (Ingress Controller) más usados en Kubernetes. Se encarga de recibir peticiones HTTP y HTTPS desde el exterior del clú ster y redirigirlas al servicio correspondiente dentro del clú ster.

Es altamente configurable y permite gestionar el enrutamiento, certificados TLS, cabeceras, tiempos de espera, redirecciones, etc.

Arquitectura básica

  • Se despliega como un DaemonSet en los nodos del clú ster.
  • Expone los puertos 80 y 443 mediante un Service tipo NodePort o LoadBalancer (cuando se usa con MetalLB).
  • Requiere una configuración RBAC, una IngressClass, y un ConfigMap para parámetros adicionales.

Archivos usados en este despliegue

Estructura del repositorio:

.
├── configmap/configmap.yaml         # Configuración del controlador
├── deployments/deployment.yaml     # Despliegue como DaemonSet
├── ingressclass/ingressclass.yaml  # Define la clase Ingress "nginx"
├── namespace.yaml                  # Namespace "ingress-nginx"
├── rbac/                           # Roles y bindings necesarios
└── services/service.yaml           # Service tipo NodePort

Este despliegue usa un DaemonSet para que el Ingress Controller se ejecute en todos los nodos. Esto es ideal cuando se usa con MetalLB para exponer los puertos 80/443 desde cada nodo.

Comando de despliegue

kubectl apply -k .

Esto desplegará el controlador en el namespace ingress-nginx y lo dejará listo para enrutar peticiones entrantes hacia los servicios del clúster.

Separacion del trafico en dos redes

Para separar el tráfico externo (Internet) del interno (VPN), el controlador ingress-nginx se publica con dos Services de tipo LoadBalancer apuntando al mismo DaemonSet de NGINX:

Estructura (repo kubernetes/ingress-nginx/):

configmap/configmap.yaml
deployments/deployment.yaml        # DaemonSet del controller
ingressclass/ingressclass.yaml     # IngressClass: nginx (controller: k8s.io/ingress-nginx)
namespace.yaml
rbac/*
services/
  ├─ service.yaml                  # LB público (IP en red 192.168.0.0/24)
  └─ service-200.yaml              # LB VPN (IP en red 192.168.200.0/24)
  • service.yaml → IP pública (ej.: 192.168.0.100). Aquí entra ACME/Lets Encrypt y tráfico externo normal.
  • service-200.yaml → IP VPN (ej.: 192.168.200.10). Solo accesible desde la VPN/CoreDNS interno.

Ambos Services seleccionan los mismos Pods del controller (selector: app.kubernetes.io/name: ingress-nginx). Kubernetes permite múltiples Services por el mismo backend.

Ejemplo de services/service-200.yaml

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller-200
  namespace: ingress-nginx
spec:
  type: LoadBalancer
  loadBalancerIP: 192.168.200.10
  # Preserva la IP de cliente en NGINX y evita hairpinning innecesario.
  externalTrafficPolicy: Local
  selector:
    app.kubernetes.io/name: ingress-nginx
  ports:
    - name: http
      port: 80
      targetPort: 80
    - name: https
      port: 443
      targetPort: 443

Uso desde los Ingress de las apps:

  • Todos los Ingress usan la misma ingressClassName: nginx.
  • El aislamiento se consigue con la lista blanca de orígenes, por ejemplo:
    metadata:
      annotations:
        nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.200.0/24,10.244.0.0/16,192.168.4.0/24"
    
  • ACME/Lets Encrypt validará por el LB público (service.yaml).
  • El LB VPN (service-200.yaml) sirve a clientes internos (WireGuard) y al CoreDNS interno, que resuelve los FQDN a 192.168.200.10.

Notas:

  • Si dependes de la IP real del cliente en logs/reglas, usa externalTrafficPolicy: Local en ambos Services (o al menos en el interno). Ajusta el firewall si procede.

6. Ingress-Traefik: Controlador ligero, moderno y compatible con CRDs

Traefik es un Ingress Controller muy versátil y fácil de extender. Además del soporte nativo para Ingress, también soporta su propio sistema basado en CRDs (IngressRoute, Middleware, etc.) y se integra muy bien con cert-manager.

Arquitectura básica

  • Se despliega como un Deployment, normalmente con un solo pod.
  • Expone los puertos HTTP/HTTPS mediante un Service tipo NodePort o LoadBalancer.
  • Lee su configuración desde un ConfigMap que define el comportamiento del proxy.

Archivos usados en este despliegue

Estructura del repositorio:

.
├── configmaps/configmap.yaml        # Configuración traefik.yml
├── deployments/deployment.yaml      # Deployment con volumen del configmap
├── ingressclass.yaml                # IngressClass "traefik"
├── namespace.yaml                   # Namespace "traefik"
├── rbac/                            # Roles y ServiceAccount
└── services/service.yaml            # Service tipo NodePort

Fragmento clave del deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
  namespace: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik
      containers:
        - name: traefik
          image: traefik:v3.0
          args:
            - --configFile=/config/traefik.yml
          volumeMounts:
            - name: config
              mountPath: /config
      volumes:
        - name: config
          configMap:
            name: traefik-config

Comando de despliegue

kubectl apply -k .

Esto desplegará el controlador en el namespace traefik y lo dejará operativo como proxy HTTP/HTTPS para tus servicios.


7. Ingress-HAProxy: Controlador HTTP/S y TCP/UDP con soporte avanzado

HAProxy Ingress Controller es una alternativa robusta y flexible a NGINX y Traefik. Permite manejar no solo tráfico HTTP/S, sino también servicios TCP y UDP mediante recursos adicionales como TCPIngress y UDPIngress.

Instalación con Helm

Para instalarlo correctamente, es necesario tener Helm instalado:

# Agregar el repositorio oficial de HAProxy Ingress
helm repo add haproxy-ingress https://haproxy-ingress.github.io/charts
helm repo update

Luego, se puede instalar el controlador con los siguientes archivos:

helm install haproxy haproxy-ingress/haproxy-ingress \
  --namespace haproxy-controller \
  --create-namespace \
  --values values.yaml

⚠️ A veces es necesario definir manualmente la IngressClass, aunque se haya especificado en values.yaml.

Archivo ingressclass.yaml

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: haproxy
spec:
  controller: haproxy.org/ingress-controller
kubectl apply -f ingressclass.yaml

Fragmento del archivo values.yaml

controller:
  name: haproxy
  ingressClass:
    create: true
    name: haproxy
    default: false
  service:
    enabled: true
    type: LoadBalancer
    loadBalancerIP: 192.168.1.100
    ports:
      http:
        port: 80
      https:
        port: 443
  metrics:
    enabled: true
  extraArgs:
    watch-ingress-classes: "true"
    watch-ingress-without-class: "true"
    disable-tcp-services: "false"
    disable-udp-services: "false"
  createCustomResources: true
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 500m
      memory: 512Mi

Esto genera un Service tipo LoadBalancer que puede integrarse con MetalLB para asignar una IP fija (en este caso, 192.168.1.100).


8. Ejemplo completo: VPN WireGuard con panel web y tráfico UDP con HAProxy

Este ejemplo demuestra el despliegue de un servicio WireGuard VPN con un panel de administración web (wg-easy), certificado TLS automático, y exposición de tráfico UDP mediante UDPIngress de HAProxy. El panel web podría estar expuesto con cualquier Ingress Controller, pero el tráfico UDP requiere el uso de HAProxy o un mecanismo equivalente.

La parte de almacenamiento (PVC) usa una StorageClass llamada nfs-temp, cuya configuración está explicada en la guía de almacenamiento.

Estructura de manifiestos (namespace: vpn)

1 namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: vpn

2 pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wg-easy-config
  namespace: vpn
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: nfs-temp
  resources:
    requests:
      storage: 1Gi

3 deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wg-easy
  namespace: vpn
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wg-easy
  template:
    metadata:
      labels:
        app: wg-easy
    spec:
      containers:
        - name: wg-easy
          image: ghcr.io/wg-easy/wg-easy
          securityContext:
            capabilities:
              add: ["NET_ADMIN", "SYS_MODULE"]
          env:
            - name: WG_HOST
              value: "c2et.com"
            - name: PASSWORD_HASH
              value: "$2y$10$PqXdePb8yMvOsEgiuhS4KON8Thy541uAPOjV6nwczNaCcy9bdA9Jq"
            - name: WG_PORT
              value: "51820"
            - name: WG_DEFAULT_ADDRESS
              value: "192.168.200.x"
            - name: WG_DEFAULT_DNS
              value: "192.168.0.80"
          ports:
            - containerPort: 51820
              protocol: UDP
            - containerPort: 51821
              protocol: TCP
          volumeMounts:
            - mountPath: /etc/wireguard
              name: config
      volumes:
        - name: config
          persistentVolumeClaim:
            claimName: wg-easy-config

4 service.yaml

apiVersion: v1
kind: Service
metadata:
  name: wg-easy
  namespace: vpn
spec:
  selector:
    app: wg-easy
  ports:
    - name: vpn
      port: 51820
      protocol: UDP
      targetPort: 51820
    - name: panel
      port: 51821
      protocol: TCP
      targetPort: 51821

5 udp-ingress.yaml

apiVersion: configuration.haproxy.org/v1alpha1
kind: UDPIngress
metadata:
  name: wg-easy-udp
  namespace: vpn
spec:
  rules:
    - port: 51820
      backend:
        serviceName: wg-easy
        servicePort: 51820

6 ingress.yaml (panel web con TLS)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wg-easy-panel
  namespace: vpn
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    haproxy-ingress.github.io/ssl-redirect: "true"
spec:
  ingressClassName: haproxy
  tls:
    - hosts:
        - wireguard.manabo.org
      secretName: wg-easy-tls
  rules:
    - host: wireguard.manabo.org
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wg-easy
                port:
                  number: 51821

Aplicación en orden

kubectl apply -f namespace.yaml
kubectl apply -f pvc.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f udp-ingress.yaml
kubectl apply -f ingress.yaml

🧪 Comprobaciones

Componente Verificación
Web panel https://wireguard.manabo.org
VPN UDP Conexión desde cliente a c2et.com:51820
Persistencia Archivos de peers en /etc/wireguard se mantienen tras reinicio

Próxima sección: ejemplos comunes para exponer servicios con Ingress y cert-manager.