Docker:如何轻松获取veth桥接口对?

12

我有两个 Docker 容器,并进行了如下的桥接:

# docker ps
CONTAINER ID        IMAGE                                         COMMAND                CREATED             STATUS              PORTS                      NAMES
ef99087167cb        images.docker.sae.sina.com.cn/ubuntu:latest   /bin/bash -c /home/c   2 days ago          Up 21 minutes       0.0.0.0:49240->22223/tcp   night_leve3         
c8a7b18ec20d        images.docker.sae.sina.com.cn/ubuntu:latest   /bin/bash -c /home/c   2 days ago          Up 54 minutes       0.0.0.0:49239->22223/tcp   night_leve2 

#brctl show cbr0
bridge name bridge id       STP enabled interfaces
docker0     8000.72b675c52895   no      vethRQOy1I
                                        vethjKYWka

我怎样才能获得匹配veth*的容器?

ef99 => vethRQOy1I 或 ef99 => vethjKYWka

//----------------------------------------------------------

我知道可以通过ethtool实现,但是否有更好的方法?


如何使用ethtool实现此操作? - Jiri
3
管道接口索引似乎是顺序的,例如:vethXXX 是19,则容器的 eth 索引必须为 18。使用命令 "ethtool -S vethXXX" 可以获取索引值,然后可以登录容器检查确切的索引或猜测相对应的一对索引。 - user3300984
9个回答

16
这是一种变体的ethtool技巧,不需要实际使用ethtool工具:
function veth_interface_for_container() {
  # Get the process ID for the container named ${1}:
  local pid=$(docker inspect -f '{{.State.Pid}}' "${1}")

  # Make the container's network namespace available to the ip-netns command:
  mkdir -p /var/run/netns
  ln -sf /proc/$pid/ns/net "/var/run/netns/${1}"

  # Get the interface index of the container's eth0:
  local index=$(ip netns exec "${1}" ip link show eth0 | head -n1 | sed s/:.*//)
  # Increment the index to determine the veth index, which we assume is
  # always one greater than the container's index:
  let index=index+1

  # Write the name of the veth interface to stdout:
  ip link show | grep "^${index}:" | sed "s/${index}: \(.*\):.*/\1/"

  # Clean up the netns symlink, since we don't need it anymore
  rm -f "/var/run/netns/${1}"
}

3
我好像没有足够的声望在“lxc.network.veth.pair”答案下发表评论,但我想指出最近版本的Docker默认使用libcontainer而不是LXC,在这种情况下,通过“--lxc-conf”传递的任何选项都将被静默忽略。 - Joel Dice
这个答案在2020年肯定不适用于GKE :-D。我将发布另一个答案。 - Nick

6
有多种“hackish”方法来实现:
  • 扫描内核日志,如Jiri所提到的(但必须在启动容器后立即执行,否则会变得混乱);
  • 检查容器中的接口计数器(已发送/已接收的数据包/字节数),然后与主机中的接口进行比较,并找到完全匹配的一对(但发送和接收方向相反);
  • 使用 iptables LOG 规则。

最后一个选项是我认为更可靠(也更易于使用)的,但仍然非常“hackish”。这个想法非常简单:

  1. 添加一个iptables规则,以记录到达Docker桥的ICMP流量:

    sudo iptables -I INPUT -i docker0 -p icmp -j LOG

  2. 向要识别的容器发送ping请求:

    IPADDR=$(docker inspect -format='{{.NetworkSettings.IPAddress}}' 0c33)

    ping -c 1 $IPADDR

  3. 检查内核日志:

    dmesg | grep $IPADDR

    你将看到一行看起来像这样的内容:

    […] IN=docker0 OUT= PHYSIN=vethv94jPK MAC=fe:2c:7f:2c:ab:3f:42:83:95:74:0b:8f:08:00 SRC=172.17.0.79 …

    如果你想要高级点,只需使用awk或sed提取PHYSIN=…

  4. 删除iptables记录规则(除非你想保留它,因为你将定期ping容器来识别它们)。

如果你需要一个无懈可击的版本,可以安装ulogd并使用ULOG目标。它不仅会将数据包头写入内核日志,而且还会通过netlink套接字将它们发送出去,然后用户空间程序可以正确处理它们。


3
尝试这个脚本:

请查看以下代码:

get_network_mode() {
    docker inspect --format='{{.HostConfig.NetworkMode}}' "$1"
}


created_by_kubelet() {
    [[ $(docker inspect --format='{{.Name}}' "$1") =~ ^/k8s_ ]]
}


for container_id in $(docker ps -q); do
    network_mode=$(get_network_mode "${container_id}")
    # skip the containers whose network_mode is 'host' or 'none',
    # but do NOT skip the container created by kubelet.
    if [[ "${network_mode}" == "host" || \
          $(! created_by_kubelet "${container_id}") && "${network_mode}" == "none" ]]; then
        echo "${container_id} => ${network_mode}"
        continue
    fi

    # if one container's network_mode is 'other container',
    # then get its root parent container's network_mode.
    while grep container <<< "${network_mode}" -q; do
        network_mode=$(get_network_mode "${network_mode/container:/}")
        # skip the containers whose network_mode is 'host' or 'none',
        # but do NOT skip the container created by kubelet.
        if [[ "${network_mode}" == "host" || \
              $(! created_by_kubelet "${container_id}") && "${network_mode}" == "none" ]]; then
            echo "${container_id} => ${network_mode}"
            continue 2
        fi
    done

    # get current container's 'container_id'.
    pid=$(docker inspect --format='{{.State.Pid}}' "${container_id}")

    # get the 'id' of veth device in the container.
    veth_id=$(nsenter -t "${pid}" -n ip link show eth0 |grep -oP '(?<=eth0@if)\d+(?=:)')

    # get the 'name' of veth device in the 'docker0' bridge (or other name),
    # which is the peer of veth device in the container.
    veth_name=$(ip link show |sed -nr "s/^${veth_id}: *([^ ]*)@if.*/\1/p")

    echo "${container_id} => ${veth_name}"
done

解释:

  • 避免在容器中执行命令。
  • 避免创建临时文件和文件夹。
  • 最重要的是,避免获取以下容器的不正确答案:NetworkModehostnonecontainer:<name|id>(与另一个容器共享网络栈。例如:Kubernetes中的一个pod中的用户容器与暂停 pod容器的网络栈共享)

1
我制作了一个工具来说明桥接的veth对。

https://github.com/t1anz0ng/iftree

输出可以是树形结构表格图片和Graphviz dot语言,例如:

sudo iftree

╭─  cni_bridge0    up
│  ╰─  /var/run/netns/123456
│     ╰─  veth57e09f05    eth13
├─  cni_br    up
│  ├─  /var/run/netns/321
│  │  ╰─  veth6328d76d    eth1
...                                       

sudo table --table


├───┬───────────────┬────────────────────────────────────┬──────────────┬───────────────────┤
│   │ BRIDGE        │ NETNS                              │ VETH         │ IFNAME(CONTAINER) │
├───┼───────────────┼────────────────────────────────────┼──────────────┼───────────────────┤
│ 1 │  cni_bridge0  │ /var/run/netns/123456              │ veth57e09f05 │ eth13             │
├───┼───────────────┼────────────────────────────────────┼──────────────┼───────────────────┤
│ 2 │  cni_br       │ /var/run/netns/123                 │ veth5e41415a │ eth1              │
├───┤               │                                    ├──────────────┼───────────────────┤
│ 3 │               │                                    │ veth90c9f5fa │ eth2              │
├───┤               │                                    ├──────────────┼───────────────────┤
│ 4 │               │                                    │ veth385ac3bb │ eth3              │
├───┤               ├────────────────────────────────────┼──────────────┼───────────────────┤
│ 5 │               │ /var/run/netns/321                 │ veth6328d76d │ eth1              │
├───┤               ├────────────────────────────────────┼──────────────┼───────────────────┤
│ 6 │               │ /var/run/docker/netns/415d70663520 │ veth319e1bda │ eth22             │
├───┼───────────────┼────────────────────────────────────┼──────────────┼───────────────────┤
│ 7 │  br0          │ /var/run/netns/netns0              │ veth0        │ ceth0             │


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

1
据我所理解,您需要与容器相关联的虚拟网络设备?
您可以通过以下方式获取它:
1:
docker exec -it <container> cat /sys/class/net/<physical-device>/iflink
# the output looks like this -> 20  

然后

2:

# ip ad | grep <the output, like 20>:
 ip ad | grep 20:
# the output looks, like this:
# 20: vetha5531eb@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-0595ab5d7c95 state UP group default qlen 1000
# where vetha5531eb is what I think you're looking for.

1
如果有人仍然对此感兴趣。我在docker邮件列表上找到了这个: http://permalink.gmane.org/gmane.comp.sysutils.docker.user/3182 您可以通过传递lxc-conf参数“lxc.network.veth.pair”来自定义veth的名称。例如: docker run -rm -i -t --lxc-conf="lxc.network.veth.pair=foobar" ubuntu /bin/bash 创建一个名为“foobar”的veth接口的容器。 有关更多方便的lxc-conf参数,请参见此页面: http://manpages.ubuntu.com/manpages/precise/man5/lxc.conf.5.html

2
这些标志已被弃用。 - diyoda_

0

我不知道如何正确获取它,但您可以使用一个技巧:在运行容器后,您可以扫描系统日志以查找添加的接口:

#!/bin/sh

JOB=$(sudo docker run -d ...)
sleep 1s
INTERFACE=$(grep "docker0: port" /var/log/syslog | tail -n 1 |  sed -r s/^.*\(veth[^\)]+\).*$/\\1/)
echo "job: $JOB interface: $INTERFACE"

0
dmesg --clear
for i in $(docker inspect $(docker ps -a -q) | grep IPAddress | cut -d\" -f4); do ping -c 1 -w 1 $i >/dev/null; done
while read line
do
IPADDRESS=$(docker inspect $line | grep IPAddress | cut -d\" -f4)
NAME=$(docker inspect $line | grep Name | cut -d/ -f2 | cut -d\" -f1)
FOUND=$(dmesg | grep $IPADDRESS | grep -Po 'vet[A-Za-z0-9]+' | sort -u)
echo "GEVONDEN $IPADDRESS MET NAAM : $NAME en INTERFACE: $FOUND" | grep NAAM
done < <(docker ps -a -q)

0

这只是对Joel Dice在“2015年2月19日”提供的答案的更新。

原始代码(适用于2015年)

# Get the interface index of the container's eth0:
  local index=$(ip netns exec "${1}" ip link show eth0 | head -n1 | sed s/:.*//)
  # Increment the index to determine the veth index, which we assume is
  # always one greater than the container's index:
  let index=index+1

  # Write the name of the veth interface to stdout:
  ip link show | grep "^${index}:" | sed "s/${index}: \(.*\):.*/\1/"

结果为:

$ index=$(sudo ip netns exec "ns-4012085" ip link show eth0 | head -n1 | sed s/:.*//)
$ echo $index
3

$ let index=index+1
$ echo $index
4

$ sudo ip link show | grep "^${index}:" | sed "s/${index}: \(.*\):.*/\1/"
cbr0

鉴于:

index=$(sudo ip netns exec "ns-4012085" ip link show type veth | grep eth0 | sed s/.*@if// | sed s/:.*// )
$ echo $index
14

$ ip link show | grep "^${index}:" | sed "s/${index}: \(.*\):.*/\1/"
veth240a8f81@if3

希望这能帮到某些人。 :)
P.S. 我从这个帖子来到这里。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接