linux namespace

namespace简介

  • linux操作系统的namespace用于资源的隔离,是容器功能实现的底层支持
  • 不同namespace内看到的东西不一样。namespace的类型有pid,uts,cgroup,ipc,network,user,mount,分别实现对不同资源的隔离
  • 这篇文章介绍namespace相关的系统调用和命令行工具nsenter。

namespaces相关的系统调用:

  • clone创建一个新的namespace
  • setns加入一个namespace
  • unshare将进程迁移到一个新的namespace

clone创建新的namespace

[root@k8snode1 namespace]# cat cloneuts.c 
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>



#define STACK_SIZE  (1024*1024)
static char  container_stack[STACK_SIZE];

char * const container_args[] = {
  "/bin/bash",
  NULL
};

//容器进程设置hostname为utstest,启动一个/bin/bash
int container_main(void *arg){
  printf("container - inside the container\n");
  sethostname("utstest",7);
  execv(container_args[0], container_args);
  printf("Something wrong\n");
  return 1;
}

int main(){
  printf("parent - start a container\n");
  //创建一个子进程,子进程使用新的UTS namespace
  int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD|CLONE_NEWUTS, NULL);
  waitpid(container_pid, NULL, 0);
  printf("parent - container stopped\n");
  return 0;
}

[root@k8snode1 namespace]# gcc cloneuts.c  -o uts
[root@k8snode1 utsns]# ./uts
parent - start a container           
container - inside the container
#容器内打印hostname
[root@utstest utsns]# hostname
utstest
[root@utstest utsns]# exit
exit
parent - container stopped
#容器外打印hostname
[root@k8snode1 utsns]# hostname
k8snode1

  • 因为给clone传递了参数CLONE_NEWUTS,所以clone创建的进程会被放入一个新建的uts namespace。
  • 不同的uts namespace中的进程执行sethostname不会互相影响。

setns加入ns

[root@k8snode1 namespace]# cat setnsuts.c 
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

//使用系统调用setns进入指定的namespace
int main(int argc, char *argv[]){
   int k,fd;
   if(argc!=2) {
     printf("usage: command namespacefile\n");
         return -1;
   }
   fd = open(argv[1],O_RDONLY);
   if(fd == -1){
     printf("open namespace file failed\n");
         return -1;
   }
   //加入指定的namespace
   if(setns(fd,0)==-1){
     printf("setns failed\n");
         return -1;
   }
   char buff[1024];
   k=gethostname(buff,1023);
   printf("hostname:%s\n",buff);
   
   return 1;
}

[root@k8snode1 namespace]# gcc setnsuts.c -o setuts
#加入指定进程(上面的uts命令启动的进程)的uts并打印hostname
[root@k8snode1 namespace]# ./setuts /proc/3893548/ns/uts 
hostname:utstest

  • 可以看到setuts打印的hostname与uts进程的相同,说明setns成功了。
  • 文件/proc/3893548/ns/uts代表了进程3893548的uts namspace,这里验证了linux上万物皆文件的概念。

命令行工具nsenter

#nsenter可以进入某个进程的namespaces

#进入指定进程的namespace
[root@testdsq namespace]# nsenter -a -t 3893548

#根据namespace file进入到某个namespace
[root@testdsq namespace]# nsenter --uts=/proc/3893548/ns/uts

排查容器网络

[root@testdsq ~]# docker ps -f  name=nginx
CONTAINER ID   IMAGE                          COMMAND                  CREATED        STATUS                  PORTS                                       NAMES
42e8e1e7cf6c   goharbor/nginx-photon:v2.8.2   "nginx -g 'daemon of…"   2 months ago   Up 2 months (healthy)   0.0.0.0:8898->8080/tcp, :::8898->8080/tcp   nginx

#获取容器进程pid
[root@testdsq ~]# docker inspect -f {{.State.Pid}} nginx
1015456

#进入容器net namespace
[root@testdsq ~]# nsenter -n -t 1015456

#查看容器网络信息
[root@testdsq ~]# ifconfig -a
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.9  netmask 255.255.0.0  broadcast 172.18.255.255
        ether 02:42:ac:12:00:09  txqueuelen 0  (Ethernet)
        RX packets 1621358  bytes 1376204886 (1.2 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1645975  bytes 1178811498 (1.0 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 2203964  bytes 386354434 (368.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2203964  bytes 386354434 (368.4 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
[root@testdsq ~]# netstat -ntl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.11:35319        0.0.0.0:*               LISTEN 

#抓取容器网络包
[root@testdsq ~]# tcpdump -i eth0 tcp port 8080
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
15:43:04.572612 IP testdsq.43842 > testdsq.webcache: Flags [S], seq 2355546266, win 43690, options [mss 65495,sackOK,TS val 3576319139 ecr 0,nop,wscale 7], length 0
15:43:04.572650 IP testdsq.webcache > testdsq.43842: Flags [S.], seq 2468828826, ack 2355546267, win 28960, options [mss 1460,sackOK,TS val 2491864504 ecr 3576319139,nop,wscale 7], length 0
15:43:04.572669 IP testdsq.43842 > testdsq.webcache: Flags [.], ack 1, win 342, options [nop,nop,TS val 3576319139 ecr 2491864504], length 0

#容器外通过curl访问容器服务
[root@testdsq ~]# curl http://88.88.88.22:8898

基本思路就是用nsenter进入到容器的网络命名空间,然后就可以像常规一样使用tcpdump抓包分析了。

参考

  • man namespaces
posted @ 2024-04-19 16:50  董少奇  阅读(212)  评论(0)    收藏  举报