Exploitez l'espace de noms réseau Linux avec Go

TL;DR

  1. Go language goroutine change OS Thread qui fonctionne par défaut de manière préventive, il est donc nécessaire de runtime.LockOSThread () '' '' lors de l'exécution d'opérations liées à l'espace de noms Linux qui sont fortement associées à OS Thread. y a-t-il. [^ 1]
  2. Si vous souhaitez utiliser l'espace de noms réseau Linux en langage Go, il est pratique d'utiliser CNI Library.

Pourquoi fais-tu ça?

Si vous préparez une VM pour chaque locataire (200 ~), la gestion et le coût seront importants, j'ai donc décidé de créer un mécanisme pour fournir un proxy inverse HTTP (S) aux locataires avec des espaces d'adressage en conflit.

Proof of Concept

Essayez d'exécuter le code ci-dessous.

package main

import (
        "log"
        "net"
        "net/http"
        "os"
        "runtime"

        "github.com/containernetworking/plugins/pkg/ns"
)

func main() {
        nspath := os.Args[1]
        addr := os.Args[2]
        var err error
        var l net.Listener
        ns.WithNetNSPath(nspath, func(_ ns.NetNS) error {
                l, err = net.Listen("tcp", addr)
                return nil
        })
        runtime.UnlockOSThread()
        if err != nil {
                log.Fatal(err)
        }
        if err := http.Serve(l, nil); err != nil {
                log.Fatal(err)
        }
}

Pour exécuter ce code, préparez un conteneur isolé sur le réseau comme indiqué ci-dessous.

# build binary
go build -o nsproxy nsproxy.go
# setup environment
docker run -d --net none --name pause k8s.gcr.io/pause:3.1
ns=$(docker inspect --format '{{ .NetworkSettings.SandboxKey }}' pause)
# run program
sudo ./nsproxy "$ns" 127.0.0.1:8080 &

Lorsque ce binaire est exécuté, il n'existe pas dans l'espace de noms réseau du conteneur (ci-après dénommé netns) lorsqu'il fonctionne en tant que serveur HTTP.

# ls -l /proc/1/ns/net #Informations réseau initiales pour l'hôte
lrwxrwxrwx 1 root root 0 Dec 24 21:42 /proc/1/ns/net -> 'net:[4026531984]'
# ls -l /proc/$(pgrep nsproxy)/task/*/ns/net #Le processus nsproxy est sur les réseaux hôtes
lrwxrwxrwx 1 root root 0 Dec 24 21:42 /proc/4377/task/4377/ns/net -> 'net:[4026531984]'
lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4378/ns/net -> 'net:[4026531984]'
lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4379/ns/net -> 'net:[4026531984]'
lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4380/ns/net -> 'net:[4026531984]'
lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4381/ns/net -> 'net:[4026531984]'
lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4382/ns/net -> 'net:[4026531984]'
lrwxrwxrwx 1 root root 0 Dec 24 21:47 /proc/4377/task/4393/ns/net -> 'net:[4026531984]'
# ls -l /proc/$(docker inspect --format '{{.State.Pid}}' pause)/task/*/ns/net #informations netns pour le conteneur
lrwxrwxrwx 1 root root 0 Dec 24 21:50 /proc/3867/task/3867/ns/net -> 'net:[4026532117]'

Cependant, si vous utilisez nsenter pour entrer les réseaux du conteneur, vous pouvez voir que le serveur http fonctionne à 127.0.0.1: 8080```.

# nsenter --net=$(docker inspect --format '{{ .NetworkSettings.SandboxKey }}' pause) bash
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# ss -ltn
State           Recv-Q          Send-Q                   Local Address:Port                     Peer Address:Port
LISTEN          0               128                          127.0.0.1:8080                          0.0.0.0:*
# curl http://127.0.0.1:8080 -v
* Expire in 0 ms for 6 (transfer 0x5627619e7f50)
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x5627619e7f50)
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Tue, 24 Dec 2019 12:58:10 GMT
< Content-Length: 19
<
404 page not found
* Connection #0 to host 127.0.0.1 left intact

Essayez de le rendre accessible à partir de nombreux conteneurs

Voyons à quel point cette méthode évolue. Développez pour avoir plusieurs ports d'écoute.

package main

import (
        "log"
        "net"
        "net/http"
        "os"
        "runtime"
        "sync"

        "github.com/containernetworking/plugins/pkg/ns"
)

func main() {
        addr := os.Args[1]
        var ls []net.Listener
        for _, nspath := range os.Args[2:] {
                ns.WithNetNSPath(nspath, func(_ ns.NetNS) error {
                        l, err := net.Listen("tcp", addr)
                        if err != nil {
                                log.Fatal(err)
                        }
                        ls = append(ls, l)
                        return nil
                })
        }
        runtime.UnlockOSThread()
        var wg sync.WaitGroup
        for _, l := range ls {
                wg.Add(1)
                go func(l net.Listener){
                        err := http.Serve(l, nil)
                        if err != nil {
                                log.Print(err)
                        }
                        wg.Done()
                }(l)
        }
        wg.Wait()
}

Préparez environ 100 conteneurs comme indiqué ci-dessous

#Créer 100 conteneurs
seq 1000 1999 | xargs -I '{}' -exec docker run -d --net none --name 'pause{}' k8s.gcr.io/pause:3.1
#Écoutez 100 conteneurs
sudo ./nsproxy 127.0.0.1:8080 $(docker inspect --format '{{.NetworkSettings.SandboxKey}}' pause{100..199} ) &

État immédiatement après le démarrage du processus

$ sudo cat /proc/$(pgrep nsproxy)/status
Name:   nsproxy
Umask:  0022
State:  S (sleeping)
Tgid:   17082
Ngid:   0
Pid:    17082
PPid:   17068
TracerPid:      0
Uid:    0       0       0       0
Gid:    0       0       0       0
FDSize: 128
Groups: 0
NStgid: 17082
NSpid:  17082
NSpgid: 17068
NSsid:  3567
VmPeak:   618548 kB
VmSize:   561720 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:     10980 kB
VmRSS:     10980 kB
RssAnon:            6608 kB
RssFile:            4372 kB
RssShmem:              0 kB
VmData:   161968 kB
VmStk:       140 kB
VmExe:      2444 kB
VmLib:      1500 kB
VmPTE:       140 kB
VmSwap:        0 kB
HugetlbPages:          0 kB
CoreDumping:    0
Threads:        7
SigQ:   0/15453
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: ffffffffffc1feff
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
NoNewPrivs:     0
Seccomp:        0
Speculation_Store_Bypass:       thread vulnerable
Cpus_allowed:   ffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list:      0-239
Mems_allowed:   00000000,00000001
Mems_allowed_list:      0
voluntary_ctxt_switches:        6
nonvoluntary_ctxt_switches:     0

Immédiatement après le démarrage, on constate que le RSS est assez léger, environ 10980 ko.

Résumé

Ce n'est pas effrayant de toucher l'espace de noms du réseau, alors essayez-le. La bibliothèque CNI elle-même est légère, alors assurez-vous de jeter un œil à l'implémentation elle-même.

Recommended Posts

Exploitez l'espace de noms réseau Linux avec Go
Espace de noms réseau Linux
Faire fonctionner le conteneur Db2 avec Go
Commandes réseau Linux
Routage de l'espace de noms réseau
J'ai essayé d'utiliser Linux avec Discord Bot
Linux (Lubuntu) avec OneMix3S
Faire fonctionner Blender avec Python
Utiliser Excel avec Python (1)
Mémo réseau (principalement Linux)
Utiliser Excel avec Python (2)
Exploitez Excel avec Python open pyxl
Dessinez la courbe de Bézier avec Go
Exploitez TwitterBot avec Lambda, Python
Premiers pas avec Go Assembly
Noyau Linux auto-construit avec clang
Réseau de neurones avec Python (scikit-learn)
Jouez avec les partitions Linux
3. Distribution normale avec un réseau neuronal!
Recherche de bits complète avec Go
Connectez-vous à Postgresql avec GO
[Note] Faites fonctionner MongoDB avec Python
Travailler avec des sites Web à l'aide de Python_Webbrowser
L'apprentissage le plus rapide sous Linux avec AWS
Essayez d'implémenter le parfum avec Go
Utilisez WDC-433SU2M2 avec Manjaro Linux
[Python] [SQLite3] Exploiter SQLite avec Python (basique)
4. Entourez les paramètres avec un réseau neuronal!
Cours ROS 105 Fonctionnement toio avec ROS
Programmation réseau avec Python Scapy
Mesure de la performance du réseau avec iperf