容器網路除錯怎麼辦?一條命令就搞定

Linksla發表於2023-02-27

nsenter 命令是一個可以在指定程式的命令空間下執行指定程式的命令。它位於 util-linux 包中。

用途

一個最典型的用途就是進入容器的網路命令空間。相當多的容器為了輕量級,是不包含較為基礎的命令的,比如說 ip address ping telnet ss tcpdump 等等命令,這就給除錯容器網路帶來相當大的困擾:只能透過  docker inspect ContainerID  命令獲取到容器 IP,以及無法測試和其他網路的連通性。這時就可以使用 nsenter 命令僅進入該容器的網路名稱空間,使用宿主機的命令除錯容器網路。

此外,nsenter 也可以進入 mnt uts ipc pid user 命令空間,以及指定根目錄和工作目錄。

使用

首先看下 nsenter 命令的語法:

    
    nsenter [options] [program [arguments]]
    
    
    
    options: -t, --target pid:指定被進入名稱空間的目標程式的pid -m, --mount[=file]:進入mount命令空間。如果指定了file,則進入file的命令空間 -u, --uts[=file]:進入uts命令空間。如果指定了file,則進入file的命令空間 -i, --ipc[=file]:進入ipc命令空間。如果指定了file,則進入file的命令空間 -n, --net[=file]:進入net命令空間。如果指定了file,則進入file的命令空間 -p, --pid[=file]:進入pid命令空間。如果指定了file,則進入file的命令空間 -U, --user[=file]:進入user命令空間。如果指定了file,則進入file的命令空間 -G, --setgid gid:設定執行程式的gid -S, --setuid uid:設定執行程式的uid -r, --root[=directory]:設定根目錄 -w, --wd[=directory]:設定工作目錄
    如果沒有給出program,則預設執行$SHELL。

    示例:

    執行一個 nginx 容器,檢視該容器的 pid:

    [root@staight ~]# docker inspect -f {{.State.Pid}} nginx5645

    然後,使用 nsenter 命令進入該容器的網路命令空間:

    [root@staight ~]# nsenter -n -t5645[root@staight ~]# ip addr1: 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 forever18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default     link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0       valid_lft forever preferred_lft forever

    進入成功~

    在 Kubernetes 中,在得到容器 pid 之前還需獲取容器的 ID,可以使用如下命令獲取:

    [root@node1 test]# kubectl get pod test -oyaml|grep containerID  - containerID: docker://cf0873782d587dbca6aa32f49605229da3748600a9926e85b36916141597ec85

    或者更為精確地獲取 containerID :

    [root@node1 test]# kubectl get pod test -o template --template='{{range .status.containerStatuses}}{{.containerID}}{{end}}'docker://cf0873782d587dbca6aa32f49605229da3748600a9926e85b36916141597ec85

    原理

    namespace

    namespace 是 Linux 中一些程式的屬性的作用域,使用名稱空間,可以隔離不同的程式。

    Linux在不斷的新增名稱空間,目前有:

    • mount 掛載名稱空間,使程式有一個獨立的掛載檔案系統,始於Linux 2.4.19

    • ipc ipc名稱空間,使程式有一個獨立的ipc,包括訊息佇列,共享記憶體和訊號量,始於Linux 2.6.19

    • uts uts名稱空間,使程式有一個獨立的hostname和domainname,始於Linux 2.6.19

    • net network命令空間,使程式有一個獨立的網路棧,始於Linux 2.6.24

    • pid pid名稱空間,使程式有一個獨立的pid空間,始於Linux 2.6.24

    • user user名稱空間,是程式有一個獨立的user空間,始於Linux 2.6.23,結束於Linux 3.8

    • cgroup cgroup名稱空間,使程式有一個獨立的cgroup控制組,始於Linux 4.6

    Linux 的每個程式都具有名稱空間,可以在 /proc/PID/ns 目錄中看到名稱空間的檔案描述符。

    [root@staight ns]# pwd/proc/1/ns[root@staight ns]# lltotal 0lrwxrwxrwx 1 root root 0 Sep 23 19:53 ipc -> ipc:[4026531839]lrwxrwxrwx 1 root root 0 Sep 23 19:53 mnt -> mnt:[4026531840]lrwxrwxrwx 1 root root 0 Sep 23 19:53 net -> net:[4026531956]lrwxrwxrwx 1 root root 0 Sep 23 19:53 pid -> pid:[4026531836]lrwxrwxrwx 1 root root 0 Sep 23 19:53 user -> user:[4026531837]lrwxrwxrwx 1 root root 0 Sep 23 19:53 uts -> uts:[4026531838]

    clone

    clone 是 Linux 的系統呼叫函式,用於建立一個新的程式。

    clone 和 fork 比較類似,但更為精細化,比如說使用 clone 建立出的子程式可以共享父程式的虛擬地址空間,檔案描述符表,訊號處理表等等。不過這裡要強調的是,clone 函式還能為新程式指定名稱空間。

    clone的語法:

      
      #define _GNU_SOURCE
      
      #include <sched.h>
      
      
      
      int clone(int (*fn)(void *), void *child_stack,        int flags, void *arg, ...        /* pid_t *ptid, void *newtls, pid_t *ctid */ );

      其中 flags 即可指定名稱空間,包括:

      • CLONE_NEWCGROUP: cgroup

      • CLONE_NEWIPC: ipc

      • CLONE_NEWNET: net

      • CLONE_NEWNS: mount

      • CLONE_NEWPID: pid

      • CLONE_NEWUSER: user

      • CLONE_NEWUTS: uts

      使用示例:

      pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);

      setns

      clone 用於建立新的命令空間,而 setns 則用來讓當前執行緒(單執行緒即程式)加入一個名稱空間。
      語法:

        
        
        #
        define _GNU_SOURCE             
        /* See feature_test_macros(7) */
        
        
        #
        include 
        <sched.h>
        
        
        
        int setns ( int fd, int nstype);
        fd引數是一個指向一個名稱空間的檔案描述符,位於/proc/PID/ns/目錄。
        nstype指定了允許進入的名稱空間,一般可設定為 0,表示允許進入所有名稱空間。

        因此,往往該函式的用法為:

        1. 呼叫setns函式: 指定該執行緒的名稱空間。

        2. 呼叫execvp函式: 執行指定路徑的程式,建立子程式並替換父程式。

        這樣,就可以指定名稱空間執行新的程式了。

        程式碼示例:

          
          #define _GNU_SOURCE
          
          #include <fcntl.h>
          
          #include <sched.h>
          
          #include <unistd.h>
          
          #include <stdlib.h>
          
          #include <stdio.h>
          
          
          
          #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \                        } while (0)
          int main(int argc, char *argv[]) {    int fd;
             if (argc < 3) {        fprintf(stderr, "%s /proc/PID/ns/FILE cmd args...\n", argv[0]);        exit(EXIT_FAILURE);    }
             fd = open(argv[1], O_RDONLY); /* Get file descriptor for namespace */    if (fd == -1)        errExit("open");
             if (setns(fd, 0) == -1)       /* Join that namespace */        errExit("setns");
             execvp(argv[2], &argv[2]);    /* Execute a command in namespace */    errExit("execvp"); }

          使用示例:

          ./ns_exec /proc/3550/ns/uts /bin/bash

          nsenter

          那麼,最後就是 nsenter 了,nsenter 相當於在setns的示例程式之上做了一層封裝,使我們無需指定名稱空間的檔案描述符,而是指定程式號即可。

          指定程式號PID以及需要進入的名稱空間後,nsenter會幫我們找到對應的名稱空間檔案描述符/proc/PID/ns/FD,然後使用該名稱空間執行新的程式。

          參考文件

          • 容器內抓包定位網路問題:

          • man-page:nsenter:

          • man-page:clone:

          • man-page:setns:

          來源:%E5%91%BD%E4%BB%A4%E7%AE%80%E4%BB%8B/


          來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70013542/viewspace-2937078/,如需轉載,請註明出處,否則將追究法律責任。

          相關文章