Aller au contenu
Conteneurs & Orchestration medium

kubectl expose, port-forward et proxy : accéder à vos applications Kubernetes

22 min de lecture

logo kubernetes

Votre application tourne dans le cluster, mais vous ne pouvez pas y accéder. Kubernetes isole les pods du réseau extérieur par défaut, c'est voulu. Pour y accéder, vous disposez de 3 commandes complémentaires : kubectl expose crée un Service permanent, kubectl port-forward ouvre un tunnel temporaire vers un pod ou un service, et kubectl proxy donne accès à l'API Kubernetes via un proxy local. Ce guide vous aide à choisir la bonne commande et à l'utiliser correctement.

  • Choisir entre expose, port-forward et proxy selon votre besoin
  • Créer un Service avec kubectl expose en choisissant le bon type (ClusterIP, NodePort, LoadBalancer)
  • Rediriger un port local vers un pod ou un service pour le dev/debug
  • Accéder à l'API Kubernetes et aux dashboards internes via kubectl proxy
  • Diagnostiquer les problèmes de connectivité et de routage

Le critère de tri n'est pas la difficulté mais la durée de vie de l'accès. expose crée un objet qui survit à votre session et à vos redémarrages de pods, les deux autres commandes disparaissent dès que vous fermez le terminal. Lisez donc la colonne Durée en premier : elle vous dit tout de suite si vous êtes en train de modifier l'état du cluster ou simplement d'ouvrir une fenêtre depuis votre poste.

BesoinCommandeDuréeCe que ça fait
Rendre une app accessible dans le clusterkubectl expose --type=ClusterIPPermanentCrée un Service avec une IP interne stable
Rendre une app accessible hors du clusterkubectl expose --type=NodePort ou LoadBalancerPermanentCrée un Service avec accès externe
Accéder à un pod/service depuis ma machine (dev, debug)kubectl port-forwardTemporaire (durée de la commande)Tunnel direct local → pod/service
Explorer l'API Kubernetes ou accéder à un dashboardkubectl proxyTemporaire (durée de la commande)Proxy HTTP local vers l'API server

kubectl expose crée un objet Service qui devient le point d'entrée réseau permanent vers votre application. Le Service utilise les labels de la ressource ciblée pour trouver les pods à router.

Le premier argument est le type de ressource source, pas le type de Service : on expose un deployment, un replicaset, un pod ou même un Service existant. Kubernetes lit alors les labels de cette ressource pour construire le selector du nouveau Service. C'est ce mécanisme automatique qui explique la panne la plus fréquente, un Service sans endpoints : si vous exposez une ressource dont les labels ne figurent pas sur les pods réellement en cours d'exécution, le Service est créé mais ne route vers rien.

Fenêtre de terminal
kubectl expose <type> <nom> --port=<port-service> --target-port=<port-conteneur> --type=<type-service>
  • --port : le port exposé par le Service (celui que les clients utiliseront)
  • --target-port : le port sur lequel le conteneur écoute (défini dans le pod)
  • --type : le type de Service à créer

Ces trois types forment une pile, pas trois options indépendantes : un NodePort est un ClusterIP auquel s'ajoute un port sur chaque nœud, et un LoadBalancer est un NodePort auquel s'ajoute un équilibreur externe. Vous verrez d'ailleurs dans les sorties ci-dessous qu'un Service NodePort conserve bien une CLUSTER-IP. Retenez la conséquence pratique : passer au type supérieur n'enlève jamais l'accès du type inférieur, cela ne fait qu'élargir l'exposition.

Le Service reçoit une IP interne accessible uniquement depuis l'intérieur du cluster. C'est le choix par défaut et le plus courant pour la communication entre microservices.

Fenêtre de terminal
kubectl expose deploy api-gateway --port=80 --target-port=8080
service/api-gateway exposed

Vérification :

Fenêtre de terminal
kubectl get svc api-gateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api-gateway ClusterIP 10.96.45.123 <none> 80/TCP 5s

Le Service est accessible depuis n'importe quel pod du cluster via api-gateway.default.svc.cluster.local:80 ou simplement api-gateway:80 dans le même namespace.

La confusion entre --port et --target-port est l'erreur la plus fréquente. Voici comment les distinguer :

Client → [Service:port] → [Pod:targetPort]
FlagCe qu'il définitExemple
--portLe port du Service (côté client)80, les autres pods appellent api-gateway:80
--target-portLe port du conteneur (côté application)8080, votre app écoute sur 8080

Si --target-port est omis, il prend la même valeur que --port.

Par défaut, le Service prend le nom de la ressource exposée, ce qui provoque un conflit dès que vous voulez deux Services sur le même Deployment, par exemple un ClusterIP interne et un NodePort de test. L'option --name lève ce blocage. Choisissez un nom explicite : il devient le nom DNS utilisé par les autres pods, et le renommer plus tard casse tous les appels qui le référencent.

Fenêtre de terminal
kubectl expose deploy api-gateway --port=80 --target-port=8080 --name=api-public

Un Service peut exister, avoir une IP, répondre à kubectl get et pourtant ne router vers aucun pod. La vérification tient en trois étapes qui vont du symptôme à la cause : d'abord regarder s'il y a des endpoints, ensuite comparer selector et labels si la liste est vide, enfin confirmer par un appel réel depuis l'intérieur du cluster. Ne sautez pas la troisième : des endpoints présents prouvent que les pods sont sélectionnés, pas que l'application écoute sur le bon port.

  1. Vérifiez que le Service existe et a des endpoints

    Fenêtre de terminal
    kubectl get svc api-gateway
    kubectl get endpointslices -l kubernetes.io/service-name=api-gateway

    La colonne ENDPOINTS doit contenir au moins une adresse IP de pod. Si elle est vide, le Service ne trouve aucun pod correspondant à son selector. La commande historique kubectl get endpoints donne la même information, mais l'API Endpoints est dépréciée depuis Kubernetes 1.33 au profit des EndpointSlices : prenez l'habitude d'utiliser la nouvelle.

  2. Comparez les selectors du Service avec les labels des pods

    Fenêtre de terminal
    # Selector du Service
    kubectl get svc api-gateway -o jsonpath='{.spec.selector}'
    # Labels des pods
    kubectl get pods --show-labels

    Les labels des pods doivent correspondre exactement au selector du Service.

  3. Testez la connectivité depuis un pod du cluster

    Fenêtre de terminal
    kubectl run test-curl --rm -i --restart=Never --image=curlimages/curl:8.19.0 -- \
    curl -s -o /dev/null -w "%{http_code}\n" http://api-gateway:80

    La sortie doit afficher 200. Le pod jetable est supprimé automatiquement grâce à --rm. --restart=Never fixe la restartPolicy du pod à Never : sans cette option, la valeur par défaut Always relance le conteneur en boucle dès que curl a terminé, et la commande ne rend jamais la main.

kubectl port-forward : tunnel temporaire pour le dev

Section intitulée « kubectl port-forward : tunnel temporaire pour le dev »

kubectl port-forward crée un tunnel direct entre votre machine locale et un pod ou un service dans le cluster. Le tunnel reste actif tant que la commande tourne, coupez-la et l'accès disparaît.

Le point commun de ces quatre cas est qu'ils sont individuels et ponctuels : un seul développeur, une seule session, aucune trace laissée dans le cluster. Dès que le besoin devient collectif ou récurrent, port-forward cesse d'être le bon outil, car chaque utilisateur doit relancer sa commande et rien n'est documenté dans les manifests. Le quatrième cas mérite un avertissement : accéder à une base de données de production depuis un client graphique local reste un accès de production, avec les mêmes règles de traçabilité.

  • Débugger une application qui n'est pas exposée à l'extérieur
  • Accéder à un dashboard (Grafana, Prometheus, Kibana) sans créer de Service dédié
  • Tester une API avant de la rendre accessible
  • Accéder à une base de données depuis un outil local (DBeaver, pgAdmin…)

L'ordre des ports se lit local d'abord, distant ensuite, et l'inverser est l'erreur la plus fréquente : 8080:3000 ouvre le port 8080 sur votre machine vers le port 3000 du pod. Le tunnel passe par l'API server du cluster, ce qui a deux conséquences : vous n'avez besoin d'aucune route réseau vers les nœuds, mais chaque tunnel consomme une connexion sur l'API server, d'où l'intérêt de fermer ceux que vous n'utilisez plus.

Fenêtre de terminal
kubectl port-forward <type>/<nom> <port-local>:<port-distant> [-n namespace]

Cibler un pod nommément est le cas le plus direct, mais aussi le plus fragile : le nom contient un identifiant généré qui change à chaque recréation du pod.

Fenêtre de terminal
kubectl port-forward pod/api-server-7d4b8c 8080:8080 -n prod
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

Les deux lignes correspondent aux écoutes IPv4 et IPv6 ; l'absence de la seconde sur certains systèmes est normale. Ouvrez ensuite http://localhost:8080 dans votre navigateur.

Cibler un Service plutôt qu'un pod permet au port-forward de suivre automatiquement si le pod redémarre :

Fenêtre de terminal
kubectl port-forward svc/grafana 3000:3000 -n monitoring

Une seule commande peut ouvrir plusieurs redirections vers la même cible : il suffit d'enchaîner les couples local:distant. C'est utile quand une application expose son trafic applicatif et ses métriques sur deux ports distincts, ici 8080 et 9090. Un seul processus les tient tous : l'interrompre les ferme tous en même temps.

Fenêtre de terminal
kubectl port-forward pod/mon-app-7d4b8c 8080:8080 9090:9090 -n prod

Par défaut, le tunnel n'écoute que sur 127.0.0.1. Pour le rendre accessible depuis d'autres machines de votre réseau :

Fenêtre de terminal
kubectl port-forward --address 0.0.0.0 svc/grafana 3000:3000 -n monitoring

Si le port 8080 est déjà pris sur votre machine :

Fenêtre de terminal
kubectl port-forward svc/api-gateway :8080 -n prod

Le :8080 (sans port local) laisse le système attribuer un port libre. La commande affiche le port choisi.

Workflow typique : accéder à un dashboard Prometheus

Section intitulée « Workflow typique : accéder à un dashboard Prometheus »

Cet enchaînement se transpose à n'importe quel outil interne : Grafana, Kibana, Argo CD, l'interface d'un opérateur. La seule étape qui demande de la réflexion est la première, car les distributions nomment leurs Services différemment ; le grep sert précisément à découvrir le nom réel plutôt qu'à le deviner. Notez aussi le port répété à l'identique, 9090:9090 : garder le même numéro des deux côtés évite les confusions quand plusieurs tunnels tournent en parallèle.

  1. Identifiez le Service Prometheus

    Fenêtre de terminal
    kubectl get svc -n monitoring | grep prometheus
    prometheus-server ClusterIP 10.96.12.45 <none> 9090/TCP 30d
  2. Ouvrez le tunnel

    Fenêtre de terminal
    kubectl port-forward svc/prometheus-server 9090:9090 -n monitoring
  3. Accédez au dashboard

    Ouvrez http://localhost:9090 dans votre navigateur.

  4. Coupez le tunnel quand vous avez terminé avec Ctrl+C.

kubectl proxy démarre un serveur proxy HTTP local qui authentifie automatiquement vos requêtes vers l'API server Kubernetes. Vous pouvez ensuite explorer l'API REST avec curl, un navigateur, ou n'importe quel outil HTTP.

Ces trois usages ont un point commun : ils passent par l'API server plutôt que par le réseau des pods. C'est ce qui distingue proxy de port-forward, et cela impose une limite nette, seul le trafic HTTP est possible. Pour une base de données PostgreSQL ou un service gRPC en TCP brut, kubectl proxy ne convient pas.

  • Explorer l'API Kubernetes (lister les ressources, vérifier les autorisations)
  • Accéder aux dashboards exposés via l'API server (Kubernetes Dashboard)
  • Développer un outil qui interagit avec l'API Kubernetes

Les deux options gouvernent uniquement le côté local du proxy. L'authentification vers le cluster, elle, est reprise telle quelle de votre kubeconfig : le proxy agit avec vos droits RBAC, ni plus ni moins. C'est la raison pour laquelle il ne faut jamais l'ouvrir au-delà de 127.0.0.1, quiconque atteint le port obtient l'intégralité de vos permissions sans avoir à présenter le moindre identifiant.

Fenêtre de terminal
kubectl proxy [--port=<port>] [--address=<adresse>]
Fenêtre de terminal
kubectl proxy --port=8001
Starting to serve on 127.0.0.1:8001

Le proxy reste actif tant que la commande tourne. Arrêtez-le avec Ctrl+C.

Une fois le proxy démarré, l'API Kubernetes est accessible sans jeton ni certificat dans la requête, puisque le proxy les ajoute pour vous. Le passage par python3 -m json.tool sert uniquement à rendre la sortie lisible ; sur une liste de pods d'un namespace chargé, la réponse brute dépasse facilement plusieurs milliers de lignes. Pour un usage régulier, jq permet en plus de filtrer les champs qui vous intéressent.

Fenêtre de terminal
# Lister les API disponibles
curl -s http://localhost:8001/api | python3 -m json.tool
# Lister les nœuds
curl -s http://localhost:8001/api/v1/nodes | python3 -m json.tool
# Lister les pods d'un namespace
curl -s http://localhost:8001/api/v1/namespaces/prod/pods | python3 -m json.tool
# Obtenir un pod spécifique
curl -s http://localhost:8001/api/v1/namespaces/prod/pods/api-server-7d4b8c | python3 -m json.tool

Le proxy expose aussi les Services de votre cluster via un chemin formaté. Deux détails de syntaxe piègent systématiquement. Le préfixe https: devant le nom du Service indique que le proxy doit contacter le backend en HTTPS, il ne s'agit pas du protocole de votre requête locale. Et la barre oblique finale de /proxy/ est obligatoire : sans elle, l'API server répond une redirection que la plupart des clients ne suivent pas correctement.

http://localhost:8001/api/v1/namespaces/<namespace>/services/<service>[:<port>]/proxy/
Fenêtre de terminal
# Accéder au dashboard Kubernetes
curl http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
# Accéder à Grafana
curl http://localhost:8001/api/v1/namespaces/monitoring/services/grafana:3000/proxy/

La ligne décisive est la dernière. kubectl proxy ne transporte que du HTTP parce qu'il traduit vos requêtes en appels d'API, alors que port-forward établit un tunnel TCP neutre au contenu. Concrètement, si vous devez brancher un client PostgreSQL, Redis ou gRPC, la question ne se pose pas, c'est port-forward. Le proxy garde l'avantage quand la cible est l'API elle-même ou une interface web servie à travers elle.

Critèrekubectl proxykubectl port-forward
Ce qui est exposéL'API Kubernetes complèteUn pod ou service spécifique
ProtocoleHTTP uniquementTCP (n'importe quel protocole)
AuthAutomatique (le proxy s'authentifie)Automatique (kubectl s'authentifie)
UsageExplorer l'API, accéder aux dashboards via l'APIDebug/dev, accéder à une app spécifique
URL d'accèshttp://localhost:8001/api/v1/...http://localhost:<port-local>
TCP brut (BD, gRPC…)❌ Non (HTTP only)✅ Oui

La dernière ligne est celle qui compte en production réelle. Multiplier les Services LoadBalancer revient à multiplier les adresses IP publiques facturées par le cloud, une par service exposé. Un Ingress Controller placé devant des Services ClusterIP mutualise un seul point d'entrée pour toutes vos applications, avec en prime la terminaison TLS et le routage par nom d'hôte ou par chemin.

EnvironnementType recommandéPourquoi
Communication entre microservicesClusterIPPas besoin d'accès externe, routage interne
Cluster de dev/test, accès depuis le réseau localNodePortSimple, pas de dépendance cloud
Production, accès publicLoadBalancerIP stable, health checks, intégration cloud
Production avec routing avancé (TLS, path-based)ClusterIP + IngressPlus flexible qu'un LB par Service

Ces quatre règles visent le même écueil : un port-forward est une dette d'exploitation invisible. Rien dans le cluster n'indique qu'un accès existe, rien ne le documente, et il disparaît au premier redémarrage du poste. La deuxième règle est celle qu'on oublie le plus, un tunnel laissé ouvert des jours durant maintient une connexion sur l'API server et échoue silencieusement dès que le pod cible est recréé.

  • Utilisez svc/ plutôt que pod/, le Service survit aux redémarrages de pods
  • Ne laissez pas tourner un port-forward oublié, il consomme une connexion API server
  • Pour un accès régulier, créez un Service ou un Ingress plutôt que de relancer un port-forward chaque jour
  • Documentez les commandes port-forward fréquentes dans le README du projet

Le deuxième point est le plus sous-estimé. kubectl proxy n'accorde pas un accès à une application, il accorde un accès à l'API du cluster avec l'ensemble de vos droits RBAC. Si vous êtes cluster-admin, toute personne capable d'atteindre le port du proxy l'est aussi, sans authentification. Traitez ce port comme vous traiteriez votre fichier kubeconfig.

  • Jamais --address 0.0.0.0 sur un réseau public (ni pour proxy, ni pour port-forward)
  • kubectl proxy = accès complet au cluster avec vos droits. Traitez-le comme un accès admin
  • En production, préférez un Ingress Controller avec TLS plutôt qu'un NodePort ou proxy

Une méthode avant le tableau : ces neuf symptômes se répartissent en deux familles qui ne se diagnostiquent pas de la même façon. Les erreurs de création (no resources found, already exists) sont explicites et se corrigent en relisant la commande. Les erreurs de routage (pas d'endpoints, 503, tunnel qui se coupe) sont muettes du côté de kubectl et demandent de remonter la chaîne : Service, puis EndpointSlice, puis état du pod, puis port réellement écouté par l'application.

SymptômeCause probableSolution
error: no resources found (expose)La ressource n'existe pas ou mauvais namespaceVérifiez avec kubectl get <type> -n <ns>
service already exists (expose)Un Service du même nom existe déjàSupprimez-le avec kubectl delete svc <nom> ou choisissez un autre nom (--name)
Service créé mais pas d'endpointsLe selector du Service ne matche aucun podComparez kubectl get svc <nom> -o jsonpath='{.spec.selector}' avec les labels des pods
unable to listen on port (port-forward)Le port local est déjà utiliséChangez le port local : 9090:8080 ou utilisez :8080 pour un port aléatoire
error forwarding port (port-forward)Le pod n'est pas en état RunningVérifiez kubectl get pod <nom> -n <ns>, attendez qu'il soit prêt
Port-forward se coupe tout seulLe pod a redémarré ou a été suppriméCiblez le Service (svc/) plutôt que le pod. Relancez la commande
address already in use (proxy)Un autre processus utilise le port 8001Changez de port : kubectl proxy --port=8002
unauthorized (proxy)Kubeconfig invalide ou expiréVérifiez avec kubectl cluster-info. Renouvelez vos credentials si nécessaire
Proxy : accès au Service retourne 503Le Service n'a pas d'endpoints ou le pod n'est pas prêtVérifiez kubectl get endpoints <svc> -n <ns>
  • kubectl expose crée un Service permanent, choisissez le bon type : ClusterIP (interne), NodePort (dev/test), LoadBalancer (prod cloud).
  • kubectl port-forward crée un tunnel temporaire local → pod/service, parfait pour le dev et le debug, mais pas pour la production.
  • kubectl proxy expose l'API Kubernetes en HTTP local, puissant pour l'exploration, mais dangereux si exposé sur le réseau.
  • --port = port du Service (côté client), --target-port = port du conteneur (côté application).
  • Ciblez un Service (svc/) plutôt qu'un pod pour un port-forward résilient aux redémarrages.
  • En production, préférez un Ingress Controller avec TLS plutôt que des NodePort ou des proxy.
  • Pour un accès régulier, créez un Service ou un Ingress, ne compensez pas avec un port-forward quotidien.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn