Comment Service Mesh réalise-t-il un proxy transparent qui n'envahit pas le code métier? Interception du trafic via iptables dans Istio

table des matières

1 Le problème du microservice traditionnel MicroService: découverte intrusive de services côté client + LoadBalance

1.1 Découverte de service côté client + équilibrage de charge

2 Comment Istio parvient-il à détourner le trafic?

2.1 Que faut-il faire?

2.2 Proxy transparent

2.3 Sidecar

2.4 iptables

2.5 Conteneur d'initialisation

3 Question: Comment juger du type de service cible?

3.1 IP du cluster


1 Le problème du microservice traditionnel MicroService: découverte intrusive de services côté client + LoadBalance

1.1 Découverte de service côté client + équilibrage de charge

Les microservices traditionnels, la découverte de services + le code d'équilibrage de charge, sont associés au code métier et s'exécutent dans le même processus que l'entreprise pendant l'exploitation.

Par exemple, le service Tomcat démarré par le projet Springboot, la logique métier s'exécute dans ce tomcat, et le code de découverte de service et le code d'équilibrage de charge après la découverte de service s'exécutent également dans ce tomcat.

Alors, pouvez-vous découpler le code métier et le code cadre?

Peut-on réaliser que seul le code métier est exécuté sur le serveur Tomcat et que la découverte de service + l'équilibrage de charge est transférée à d'autres processus?

La réponse est oui. Placez la découverte de service + équilibrage de charge dans un processus side-car séparé, dissociez-le du code métier et, en même temps, créez un proxy pour le trafic de service via le détournement de trafic.

L'un des points forts du projet d'Istio est que les anciennes applications peuvent être connectées de manière transparente à la plate-forme Service Mesh sans modifier une ligne de code. Pour réaliser cette fonction, actuellement, le trafic est intercepté et transmis au proxy via iptables.

2 Comment Istio parvient-il à détourner le trafic?

En référence à l'implémentation d'Istio, nous pouvons concevoir nous-mêmes un schéma de détournement de trafic simple.

2.1 Que faut-il faire?

  • Tout d'abord, il doit y avoir un proxy qui prend en charge le proxy transparent, qui peut gérer le trafic détourné et être en mesure d'obtenir l'adresse de destination d'origine lorsque la connexion est établie. Dans k8s, ce proxy est déployé dans un pod à l'aide de la méthode side-car et du service pour détourner le trafic.
  • Détourner le trafic que nous voulons détourner vers un proxy via iptables. Le propre trafic du proxy doit être exclu.
  • Pour éviter toute intrusion, il est préférable de ne pas modifier l'image du service. Dans k8s, le conteneur Init peut être utilisé pour modifier iptables avant le démarrage du conteneur d'application.

2.2 Proxy transparent

En tant que proxy transparent, le trafic qu'il peut gérer subira une série de logique de traitement, y compris une nouvelle tentative, un délai d'attente, un équilibrage de charge, etc., puis le transmettra au service homologue. Pour le trafic qui ne peut pas être traité par lui-même, il sera directement transmis de manière transparente sans traitement.

Une fois que le trafic est transmis au proxy via iptables, le proxy doit pouvoir obtenir l'adresse de destination d'origine lorsque la connexion est établie. L'implémentation dans Go est un peu plus gênante et doit être obtenue en  syscall appelant,

Exemple de code:

package redirect

import (
    "errors"
    "fmt"
    "net"
    "os"
    "syscall"
)

const SO_ORIGINAL_DST = 80

var (
    ErrGetSocketoptIPv6 = errors.New("get socketopt ipv6 error")
    ErrResolveTCPAddr   = errors.New("resolve tcp address error")
    ErrTCPConn          = errors.New("not a valid TCPConn")
)

// For transparent proxy.
// Get REDIRECT package's originial dst address.
// Note: it may be only support linux.
func GetOriginalDstAddr(conn *net.TCPConn) (addr net.Addr, c *net.TCPConn, err error) {
    fc, errRet := conn.File()
    if errRet != nil {
        conn.Close()
        err = ErrTCPConn
        return
    } else {
        conn.Close()
    }
    defer fc.Close()

    mreq, errRet := syscall.GetsockoptIPv6Mreq(int(fc.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
    if errRet != nil {
        err = ErrGetSocketoptIPv6
        c, _ = getTCPConnFromFile(fc)
        return
    }

    // only support ipv4
    ip := net.IPv4(mreq.Multiaddr[4], mreq.Multiaddr[5], mreq.Multiaddr[6], mreq.Multiaddr[7])
    port := uint16(mreq.Multiaddr[2])<<8 + uint16(mreq.Multiaddr[3])
    addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", ip.String(), port))
    if err != nil {
        err = ErrResolveTCPAddr
        return
    }

    c, errRet = getTCPConnFromFile(fc)
    if errRet != nil {
        err = ErrTCPConn
        return
    }
    return
}

func getTCPConnFromFile(f *os.File) (*net.TCPConn, error) {
    newConn, err := net.FileConn(f)
    if err != nil {
        return nil, ErrTCPConn
    }

    c, ok := newConn.(*net.TCPConn)
    if !ok {
        return nil, ErrTCPConn
    }
    return c, nil
}

L' GetOriginalDstAddr adresse de destination d'origine de la connexion peut être obtenue via la  fonction.

Il est important de noter ici que lorsque le transfert iptables est activé, si le proxy reçoit une connexion qui accède directement à lui-même, il reconnaîtra qu'il ne peut pas la gérer, puis se connectera à l'adresse de destination (c'est-à-dire l'adresse liée à elle-même) , de sorte qu'il mènera à une boucle sans fin. Par conséquent, lorsque le service est démarré, la connexion dont l'adresse de destination est sa propre adresse IP doit être déconnectée directement.

2.3 Sidecar

Lorsque vous utilisez le mode Sidecar pour déployer la grille de service, un proxy supplémentaire sera ouvert à côté de chaque service pour prendre en charge une partie du trafic du conteneur. Dans kubernetes, un pod peut avoir plusieurs conteneurs. Ces multiples conteneurs peuvent partager des ressources telles que le réseau et le stockage. Déployez conceptuellement le conteneur de services et le conteneur proxy dans un pod, et le conteneur proxy équivaut à un conteneur side-car.

Nous faisons une démonstration à travers un déploiement. La configuration yaml de ce déploiement comprend deux conteneurs de test et de proxy, qui partagent le réseau, donc après vous être connecté au conteneur de test,  127.0.0.1:30000 vous pouvez accéder au conteneur de proxy via.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
  namespace: default
  labels:
    app: test
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - name: test
        image: {test-image}
        ports:
          - containerPort: 9100
      - name: proxy
        image: {proxy-image}
        ports:
          - containerPort: 30000    

Ecrire la configuration du conteneur side-car pour chaque service est une tâche fastidieuse. Lorsque l'architecture est mature, nous pouvons utiliser les MutatingAdmissionWebhook fonctions de kubernetes pour  injecter activement des configurations liées au side-car lorsque l'utilisateur crée un déploiement.

Par exemple, nous ajoutons les champs suivants aux annotations du déploiement:

annotations:
  xxx.com/sidecar.enable: "true"
  xxx.com/sidecar.version: "v1"

Indique que la version v1 du side-car doit être injectée dans ce déploiement. Lorsque notre service reçoit ce webhook, il peut vérifier le champ d'annotations pertinent, et décider s'il faut injecter la configuration du side-car et quelle version de la configuration en fonction de la configuration du champ. Si certains paramètres doivent être modifiés en fonction du service, vous pouvez également l'utiliser Le mode de livraison améliore considérablement la flexibilité.

2.4 iptables

Grâce à iptables, nous pouvons détourner le trafic spécifié vers le proxy et exclure une partie du trafic.

iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner 9527 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d 172.17.0.0/16 -j REDIRECT --to-port 30000

La commande ci-dessus signifie de transférer 172.17.0.0/16 le trafic avec  l'adresse cible  REDIRECT vers le port 30000 (le port que le proxy surveille). Mais le processus commencé avec l'UID 9527 est une exception. 172.17.0.0/16 Cette adresse est le segment IP à l'intérieur du cluster k8s. Il suffit de détourner cette partie du trafic. Pour le trafic accédant à l'extérieur du cluster, nous ne le détournerons pas temporairement. Si tout le trafic est détourné, le le proxy ne peut pas gérer doit être exclu par les règles iptables.

2.5 Conteneur d'initialisation

Comme mentionné précédemment, afin de parvenir à zéro intrusion, nous devons utiliser le conteneur Init pour modifier iptables avant de démarrer le conteneur de service utilisateur. Cette partie de la configuration peut également être MutatingAdmissionWebhook injectée dans la configuration de déploiement de l'utilisateur via la fonction de kubernetes  .

Ajoutez la configuration du conteneur Init à la configuration du side-car précédent:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
  namespace: default
  labels:
    app: test
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: test
    spec:
      initContainers:
      - name: iptables-init
        image: {iptables-image}
        imagePullPolicy: IfNotPresent
        command: ['sh', '-c', 'iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner 9527 -j RETURN && iptables -t nat -A OUTPUT -p tcp -d 172.17.0.0/16 -j REDIRECT --to-port 30000']
        securityContext:
          capabilities:
            add:
            - NET_ADMIN
          privileged: true
      containers:
      - name: test
        image: {test-image}
        ports:
          - containerPort: 9100
      - name: proxy
        image: {proxy-image}
        ports:
          - containerPort: 30000    

Ce conteneur Init doit installer iptables et la commande iptables que nous avons configurée sera exécutée au démarrage.

Besoin d'une attention particulière est  securityContext cet élément de configuration, nous avons ajouté des  NET_ADMIN autorisations. Il est utilisé pour définir les autorisations du pod ou du conteneur. S'il n'est pas configuré, iptables affichera une erreur lors de l'exécution de la commande.

3 Question: Comment juger du type de service cible?

Nous avons  172.17.0.0/16 détourné tout le trafic vers le proxy, alors comment déterminer le type de protocole du service cible? Si vous ne connaissez pas le type de protocole, vous ne pouvez pas savoir comment analyser les requêtes suivantes.

Dans le service kubernetes, nous pouvons spécifier un nom pour le port de chaque service. Le format de ce nom peut être fixé pour  {name}-{protocol}, par exemple  {test-http}, qu'un certain port de ce service est un protocole http.

kind: Service
apiVersion: v1
metadata:
  name: test
  namespace: default
spec:
  selector:
    app: test
  ports:
    - name: test-http
      port: 9100
      targetPort: 9100
      protocol: TCP

Le proxy obtient l'adresse IP du cluster et le nom de port correspondant au service via le service de découverte, de sorte que le type de protocole de communication de la connexion peut être connu via l'adresse IP et le port du service cible, qui peuvent ensuite être transférés au gestionnaire correspondant. pour traitement.

3.1 IP du cluster

Créez un service dans kubernetes. S'il n'est pas spécifié, l'adresse IP du cluster est utilisée par défaut pour accéder. Kube-proxy créera une règle iptables pour cela, convertira l'adresse IP du cluster en équilibrage de charge et la transmettra à l'adresse IP du pod.

Lorsqu'il existe une adresse IP de cluster, la résolution DNS du service pointera vers l'adresse IP du cluster et l'équilibrage de charge est effectué par iptables. S'il n'existe pas, le résultat de la résolution DNS pointera directement vers l'adresse IP du pod.

Le proxy s'appuie sur l'adresse IP du cluster du service pour déterminer à quel service l'utilisateur accède, il ne peut donc pas être défini sur  clusterIP: None. Étant donné que l'adresse IP du pod est susceptible de changer fréquemment, lors de l'ajout ou de la soustraction d'instances, l'ensemble des adresses IP du pod changera et le proxy ne pourra pas obtenir ces modifications en temps réel.

Je suppose que tu aimes

Origine blog.csdn.net/hugo_lei/article/details/106941516
conseillé
Classement