Linkerd es una malla de servicios (service mesh) para Kubernetes centrada en la sencillez: añade a cada pod un micro-proxy escrito en Rust que cifra el tráfico con mTLS, mide latencia y tasa de éxito, y aplica reintentos y reparto de tráfico sin cambiar el código. Consume bastante menos que Istio y se instala en unos minutos. En este tutorial instalarás Linkerd, meterás una aplicación de ejemplo en la malla, consultarás sus métricas y harás un despliegue canario con un HTTPRoute de Gateway API.

Requisitos previos

Para seguir esta guía necesitas:

  • Un clúster de Kubernetes en una versión soportada por Linkerd (consulta la documentación de la release que instales), por ejemplo sobre VPS de CubePath con Ubuntu 24.04.
  • kubectl configurado con permisos de administrador del clúster, en un equipo Linux o macOS.
  • Al menos 1 GB de RAM libre en el clúster para el plano de control y la extensión Viz.

Linkerd frente a Istio

AspectoLinkerdIstio
Proxylinkerd2-proxy (Rust), específico para LinkerdEnvoy (C++), de propósito general
mTLSActivado por defecto al entrar en la mallaActivado por defecto en modo permisivo
Configuración de tráficoGateway API (HTTPRoute) y anotacionesVirtualService, DestinationRule, Gateway API
Consumo y complejidadBajosMayores, a cambio de más funciones

Paso 1: Instalar la CLI de Linkerd

La CLI linkerd genera los manifiestos de instalación y ofrece comandos de diagnóstico. Descarga el script de instalación oficial, revísalo y ejecútalo:

curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install-edge -o linkerd-install.sh
less linkerd-install.sh
sh linkerd-install.sh

El binario se instala en ~/.linkerd2/bin. Añádelo a tu PATH (y a tu ~/.bashrc para que sea permanente):

export PATH="$HOME/.linkerd2/bin:$PATH"
linkerd version --client
Client version: edge-25.9.2

Paso 2: Validar el clúster e instalar las CRD

linkerd check --pre comprueba que el clúster cumple los requisitos antes de instalar nada:

linkerd check --pre

Todas las comprobaciones deben terminar en √. Linkerd usa los recursos HTTPRoute y GRPCRoute de Gateway API para configurar el tráfico. Si la comprobación indica que faltan sus CRD, instala el canal estándar de Gateway API (revisa en la documentación de Linkerd qué versión admite tu release):

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml

Instala ahora las CRD propias de Linkerd:

linkerd install --crds | kubectl apply -f -

Paso 3: Instalar el plano de control

El plano de control incluye el servicio de identidad, que emite los certificados mTLS de cada proxy, el de destino, que informa a los proxies de los endpoints y políticas, y el inyector de proxies. linkerd install genera los certificados raíz automáticamente:

linkerd install | kubectl apply -f -

Espera a que esté listo y valida la instalación:

linkerd check
linkerd-existence
-----------------
√ 'linkerd-config' config map exists
√ control plane pods are ready
...
Status check results are √

Paso 4: Instalar la extensión Viz

La extensión Viz añade Prometheus, un panel web y los comandos linkerd viz para consultar métricas en tiempo real:

linkerd viz install | kubectl apply -f -
linkerd viz check
Status check results are √

Paso 5: Añadir una aplicación a la malla

Emojivoto es una aplicación de ejemplo del proyecto Linkerd con cuatro servicios y un generador de tráfico (vote-bot). Despliégala:

curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/emojivoto.yml | kubectl apply -f -
kubectl -n emojivoto rollout status deploy/web

Para añadirla a la malla, Linkerd necesita la anotación linkerd.io/inject: enabled en los pods o en su namespace. La forma recomendada es anotar el namespace, así cualquier pod nuevo se inyecta automáticamente, y reiniciar los Deployments existentes:

kubectl annotate namespace emojivoto linkerd.io/inject=enabled
kubectl -n emojivoto rollout restart deploy
kubectl -n emojivoto rollout status deploy/web

Cada pod tiene ahora dos contenedores, la aplicación y linkerd-proxy:

kubectl -n emojivoto get pods
NAME                        READY   STATUS    RESTARTS   AGE
emoji-6d66d87995-9xk2b      2/2     Running   0          40s
vote-bot-b98dc7c55-lhq8m    2/2     Running   0          40s
voting-5f5b555dff-r4wqn     2/2     Running   0          40s
web-64cb5b6f7b-2hc6v        2/2     Running   0          40s

Comprueba que los proxies funcionan correctamente:

linkerd check --proxy -n emojivoto

Paso 6: Consultar métricas y comprobar mTLS

Linkerd mide para cada workload las métricas clave: tasa de éxito, peticiones por segundo y latencia. Consúltalas por Deployment:

linkerd viz stat deploy -n emojivoto
NAME       MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
emoji         1/1   100.00%   1.9rps           1ms           2ms           3ms          3
vote-bot      1/1         -        -             -             -             -          -
voting        1/1    87.50%   0.9rps           1ms           2ms           4ms          3
web           1/1    91.91%   1.9rps           2ms          10ms          28ms          3

La tasa de éxito de voting no es del 100 % a propósito: emojivoto devuelve un error al votar un emoji concreto, precisamente para que puedas practicar el diagnóstico. Para ver las peticiones en tiempo real, incluidas las que fallan, usa tap:

linkerd viz tap deploy/web -n emojivoto --to deploy/voting

Pulsa Ctrl+C para salir. Para comprobar que el tráfico entre servicios va cifrado, muestra las conexiones entre workloads:

linkerd viz edges deploy -n emojivoto
SRC        DST      SRC_NS      DST_NS      SECURED
vote-bot   web      emojivoto   emojivoto   √
web        emoji    emojivoto   emojivoto   √
web        voting   emojivoto   emojivoto   √

La columna SECURED confirma que cada conexión usa mTLS, sin haber configurado ningún certificado a mano.

Para abrir el panel web de Viz, que muestra lo mismo de forma gráfica:

linkerd viz dashboard

El comando crea un reenvío de puertos y abre el navegador en http://localhost:50750.

Paso 7: Repartir tráfico entre versiones con HTTPRoute

Linkerd implementa la especificación GAMMA de Gateway API: un HTTPRoute cuyo parentRef es un Service modifica el tráfico que los clientes de la malla envían a ese Service. Así se hace un despliegue canario.

Crea un namespace anotado para la malla y dos versiones de un servicio sencillo basado en http-echo, que responde con un texto fijo:

nano echo.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: demo
  annotations:
    linkerd.io/inject: enabled
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-v1
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels: {app: echo, version: v1}
  template:
    metadata:
      labels: {app: echo, version: v1}
    spec:
      containers:
        - name: echo
          image: hashicorp/http-echo:1.0
          args: ["-text=v1"]
          ports:
            - containerPort: 5678
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-v2
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels: {app: echo, version: v2}
  template:
    metadata:
      labels: {app: echo, version: v2}
    spec:
      containers:
        - name: echo
          image: hashicorp/http-echo:1.0
          args: ["-text=v2"]
          ports:
            - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: echo
  namespace: demo
spec:
  selector: {app: echo, version: v1}
  ports:
    - port: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: echo-v1
  namespace: demo
spec:
  selector: {app: echo, version: v1}
  ports:
    - port: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: echo-v2
  namespace: demo
spec:
  selector: {app: echo, version: v2}
  ports:
    - port: 5678

Los clientes siempre llaman al Service echo. Los Services echo-v1 y echo-v2 son los destinos del reparto.

Aplica el manifiesto y crea un pod cliente en el mismo namespace. Al estar el namespace anotado, el cliente también entra en la malla, algo imprescindible porque el reparto lo aplica el proxy del cliente:

kubectl apply -f echo.yaml
kubectl -n demo rollout status deploy/echo-v2
kubectl run curl -n demo --image=curlimages/curl --restart=Never --command -- sleep 3600
kubectl -n demo wait --for=condition=Ready pod/curl --timeout=120s

Sin ninguna regla, todo el tráfico va a v1:

kubectl -n demo exec curl -c curl -- sh -c 'for i in $(seq 1 50); do curl -s http://echo:5678; done | sort | uniq -c'
     50 v1

Crea el HTTPRoute que manda el 90 % a v1 y el 10 % a v2. Las anotaciones añaden hasta dos reintentos ante errores 5xx y un timeout de 2 segundos por petición:

nano echo-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: echo-canary
  namespace: demo
  annotations:
    retry.linkerd.io/http: 5xx
    retry.linkerd.io/limit: "2"
    timeout.linkerd.io/request: 2s
spec:
  parentRefs:
    - name: echo
      kind: Service
      group: ""
      port: 5678
  rules:
    - backendRefs:
        - name: echo-v1
          port: 5678
          weight: 90
        - name: echo-v2
          port: 5678
          weight: 10
kubectl apply -f echo-route.yaml
kubectl -n demo exec curl -c curl -- sh -c 'for i in $(seq 1 50); do curl -s http://echo:5678; done | sort | uniq -c'
     46 v1
      4 v2

Para avanzar en el despliegue, ajusta los pesos (50/50 y después 0/100) y vuelve a aplicar el archivo. Para deshacerlo, borra el HTTPRoute y el tráfico volverá al Service echo.

Paso 8: Exigir clientes de la malla

Por defecto, un pod de la malla acepta tráfico de cualquier cliente, esté o no en la malla. La anotación config.linkerd.io/default-inbound-policy: all-authenticated hace que los pods solo acepten conexiones con mTLS de otros proxies de Linkerd. Aplícala al namespace demo y reinicia los pods para que la recojan:

kubectl annotate namespace demo config.linkerd.io/default-inbound-policy=all-authenticated
kubectl -n demo rollout restart deploy
kubectl -n demo rollout status deploy/echo-v1

Crea un cliente fuera de la malla, en el namespace default, que no tiene la anotación de inyección:

kubectl run curl-outside --image=curlimages/curl --restart=Never --command -- sleep 3600
kubectl wait --for=condition=Ready pod/curl-outside --timeout=120s
kubectl exec curl-outside -- curl -s -o /dev/null -w "%{http_code}\n" http://echo.demo:5678
403

El cliente de la malla sigue funcionando, porque presenta su identidad mTLS:

kubectl -n demo exec curl -c curl -- curl -s http://echo:5678

Para reglas más finas (por ejemplo, permitir solo a una ServiceAccount concreta), Linkerd ofrece los recursos Server, AuthorizationPolicy y MeshTLSAuthentication del grupo policy.linkerd.io.

Solución de problemas

Los pods muestran 1/1 tras anotar el namespace. La inyección solo ocurre al crear el pod. Reinicia los Deployments con kubectl rollout restart deploy -n <namespace> y comprueba la anotación con kubectl get namespace <namespace> -o yaml.

El reparto de tráfico no tiene efecto. El cliente debe estar en la malla, y el parentRef debe apuntar al Service y puerto que usa el cliente. Revisa el estado del HTTPRoute con kubectl describe httproute echo-canary -n demo: la condición Accepted debe ser True.

linkerd check avisa de que un certificado va a caducar. Rota el trust anchor y el certificado del emisor siguiendo la guía oficial antes de la fecha indicada; si caducan, los proxies dejarán de poder comunicarse.

Un pod con proxy no arranca. Consulta los registros del proxy con kubectl logs <pod> -c linkerd-proxy -n <namespace> y los del contenedor de inicialización linkerd-init, que configura las reglas de iptables.

Conclusión

Has instalado Linkerd con su extensión Viz, has metido una aplicación en la malla con mTLS automático, has consultado su tasa de éxito y latencia, has hecho un despliegue canario con HTTPRoute y has exigido que solo los clientes de la malla lleguen a tus pods. Como siguientes pasos puedes gestionar los certificados de Linkerd con cert-manager, conectar las métricas a tu Prometheus y Grafana existentes o definir políticas de autorización por ServiceAccount.