Jusqu'à maintenant, nous avons créé des pods cloisonnés à l'intérieur du cluster du point de vue réseau. Au cours des exercices suivants, nous allons découvrir les différents moyens d'exposer nos pods sur le réseau.

Ouverture réseau sur le conteneur

Le premier niveau d'accès réseau se fait au niveau du conteneur. Chaque pod se voit attribuer une adresse ip sur le réseau interne du cluster.

  • Nous allons reprendre notre pod basé sur l'image echoserver.
  • Créer un fichier de manifeste nommé container-port.yaml avec le contenu suivant.
apiVersion: v1
kind: Pod
metadata:
  name: <choisir un nom de pod>
  labels:
    app: echo-server
spec:
  containers:
  - name: echo
    image: k8s.gcr.io/echoserver:1.10
  • Ajouter les éléments de manifeste permettant d'exposer le port 8080 du pod
  • Appliquer les changements.

Il n'existe que peu de moyens d'accéder au port ainsi exposé :

  • depuis un autre pod
  • avec un kubectl

Vérifier le bon fonctionnement :

kubectl port-forward <nom du pod> 8080:8080
curl localhost:8080

Les services et leurs différents types

Service ClusterIP

Pour illustrer l'intérêt des services, nous allons commencer par créer un second pod identique au premier, mais avec un nom différent. Un même fichier yaml peut contenir plusieurs manifestes.

apiVersion: v1
kind: Pod
metadata:
    labels:
      app: echo-server
    ...
---
apiVersion: v1
kind: Pod
metadata:
    labels:
      app: echo-server
    ...
  • Écrire le manifeste complet et appliquer les changements
  • On doit maintenant voir deux pods avec des IPs différentes, et portant le label que nous avons définis.
kubectl describe pod -l app=echo-server | grep IP
  • Mettre en place un port-forwarding vers chaque pod et noter le nom d'hôte dans la réponse au curl
kubectl port-forward <nom du premier pod> 8080:8080
curl localhost:8080
kubectl port-forward <nom du second pod> 8081:8080
curl localhost:8081

Nous allons maintenant créer un service pour distribuer les requêtes sur ces deux pods.

apiVersion: v1
kind: Service
metadata:
  name: hello-service
  labels:
    app: echo-server
spec:
  type: ClusterIP
  ports:
    <configuration des ports>
  selector:
    <selecteur de label>
  • Insérer les bonnes valeurs dans le manifeste ci-dessus et l'ajouter au fichier yaml.
  • Appliquer les changements.
  • Vérifier la création du service.
kubectl get services
kubectl describe service <nom du service>

Si le service est bien défini on retrouve deux endpoints qui correspondent aux IPs des deux pods, ainsi qu'une IP pour le service lui-même.

Name:              echo-service
Type:              ClusterIP
IP:                10.43.219.74
Port:              <unset>  8080/TCP
TargetPort:        8080/TCP
Endpoints:         10.42.1.2:8080,10.42.2.3:8080

Pour tester, on peut utiliser un port forward sur le service ou exécuter une session intéractive à l'intérieur d'un pod.

  • Démarrer une session intéractive dans un pod busybox et requêter l'adresse du service.
kubectl run --rm curl --image=radial/busyboxplus:curl -i --tty
[ root@curl-87b54756-kmb6t:/ ]$ curl 10.43.219.74:8080
  • (ou utiliser un port-forward sur le service, nécessite de préfixer le nom du service par svc/)
kubectl port-forward svc/hello-service 8080:8080
  • Éxecuter la requête plusieurs fois, quel est le nom d'hôte dans la réponse de la requête ?

Services NodePort

Le service que nous avons créé distribue les requêtes sur les différents pods mais ne nous ne pouvons toujours pas accéder aux pods depuis l'extérieur du cluster.

Pour résoudre (partiellement) cette problématique, nous allons modifier notre service pour lui ajouter un NodePort

  • Modifier le service précédent, il s'agit d'en changer le type (NodePort) ainsi que d'y ajouter la directive nodePort pour spécifier le port à exposer.
  • Choisir un port dans la tranche autorisée (30000-32767).
  • Appliquer les changements.

Notre service doit maintenant présenter le NodePort choisi dans sa description.

Name:              echo-service
Type:              NodePort
IP:                10.43.219.74
Port:              <unset>  8080/TCP
TargetPort:        8080/TCP
NodePort:          <unset>  30808/TCP
Endpoints:         10.42.1.2:8080,10.42.2.3:8080

Il est désormais possible d'atteindre ce service sur n'importe quel noeud du cluster, vérifier le bon fonctionnement est donc très simple.

curl <ip noeud 1>:<nodePort choisi>
curl <ip noeud 2>:<nodePort choisi>
curl <ip noeud 3>:<nodePort choisi>

Attention, les services NodePort permettent d'exposer un port sur tous les noeuds du cluster, ce qui signifie que le service sera accessible sur toutes les IPs de chaque noeud. Ceci est source d'un certain nombre d'incertitudes : - Rien ne garanti que les IPs en question soient accessibles dans le périmètre requis. On risque par exemple d'exposer un service sur un réseau où il ne devrait pas l'être, ce qui peut être source de vulnérabilités. - Il est nécessaire de connaître et de maintenir la disponibilité des IPs sur le cluster.

Services LoadBalancer

En plus d'introduire de potentielles vulnérabilités, les NodePorts présentent la complexité de nous obliger à gérer manuellement l'attribution et le suivi des IPs sur le cluster.

Pour pallier à ce problème, une solution est d'utiliser les services de type LoadBalancer proposés par Kubernetes.

Dès la création d'un service LoadBalancer, une application externe au cluster aura la responsabilité d'attribuer une IP au service. Chez les grands cloud providers, ceci entraîne généralement un coût supplémentaire.

Nous allons modifier notre service pour le transformer en service de type LoadBalancer.

  • Modifier le manifeste de définition du service, changer le type (utiliser LoadBalancer)
  • Retirer l'attribut nodePort s'il est présent
  • Appliquer les changements

La description du service doit présenter une adresse IP sous l'attribut LoadBalancer Ingress

Name:              echo-service
Type:              LoadBalancer
IP:                10.43.219.74
LoadBalancer Ingress:     37.61.243.92
Port:              <unset>  8080/TCP
TargetPort:        8080/TCP
NodePort:          <unset>  30808/TCP
Endpoints:         10.42.1.2:8080,10.42.2.3:8080

Il est désormais possible d'atteindre le service sur cette IP :

curl 37.61.243.92

Accès réseau avec les Ingress

Pour l'instant, la manière la plus souple que nous avons vu pour exposer un service sont les LoadBalancers. Malheureusement ces derniers souffrent d'un défaut conséquent : leur prix. Gérer une multiplicité de service avec des LoadBalancers n'est pas une stratégie durable, aussi allons-nous nous intéresser aux Ingress qui vont nous permettre de créer des règles de reverse-proxy pour limiter le nombre d'IPs consommées.

Dès leur création, les ressources de type Ingress sont interpretées par un Ingress Controller dont le rôle est l'exécution des règles de reverse-proxy. Le principe du reverse proxy est simple, pour un nom de domaine donné, on redirige toutes les requêtes vers un backend. Les backend des ingress sont généralement des services.

Nous allons créer un ingress avec les caractéristiques suivantes :

  • Un nom de domaine de votre choix
  • Un service ClusterIP pointant vers nos pods comme backend
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
  labels:
    app: echo-server
spec:
  rules:
    - host: <url choisie>
      http:
        paths:
          - path: <chemin sur le backend>
            pathType: ImplementationSpecific
            backend:
              service:
                name: <nom du service>
                port: 
                  number: <port du service>
  • Ajouter les valeurs dans le modèle ci-dessus
  • Appliquer le manifeste pour créer l'Ingress

Il doit maintenant être possible d'accéder au service via le nom de domaine choisi

curl <nom de domaine>