Logrotate工具

logrotate是一个用于管理日志文件的工具,它可以定期轮转(rotate)和压缩日志文件,以便节省磁盘空间。它通常在Linux系统中使用。

logrotate的主要功能包括:

  1. 轮转日志文件:logrotate可以按照预设的时间间隔(如每天、每周、每月等)对指定的日志文件进行轮转。轮转意味着将当前的日志文件重命名为一个新的文件,并创建一个新的空的日志文件。

  2. 压缩日志文件:logrotate还可以自动对轮转后的旧日志文件进行压缩,以节省磁盘空间。它支持多种压缩格式,如gzip、bzip2等。

  3. 删除旧的日志文件:logrotate可以根据设置的保留时间,自动删除过期的日志文件,以防止占用过多的磁盘空间。

  4. 执行脚本或命令:logrotate允许在轮转日志文件之前或之后执行自定义的脚本或命令。这使得用户能够在日志轮转过程中执行额外的操作,如发送邮件通知、重新启动相关服务等。

logrotate的配置文件通常位于/etc/logrotate.conf或/etc/logrotate.d/目录下,每个日志文件都有一个对应的配置文件。配置文件中可以指定轮转的时间间隔、保留时间、压缩格式等参数。

Centos7示例

logrotate通过cron触发,执行脚本在/etc/cron.daily/logrotate

/usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf

logrotate.status是其保存状态的文件,/etc/logrotate.conf是主配置,主配置中可以设置全局轮转配置

# see "man logrotate" for details
# rotate log files weekly
weekly

# keep 4 weeks worth of backlogs
rotate 4

# create new (empty) log files after rotating old ones
create

# use date as a suffix of the rotated file
dateext

# uncomment this if you want your log files compressed
#compress

# RPM packages drop log rotation information into this directory
include /etc/logrotate.d

以/etc/logrotate.d/syslog为例,子配置继承主配置的属性,在block内可以添加配置或者覆盖配置,大括号block之前表示匹配的日志文件。

/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
{
    missingok
    sharedscripts
    postrotate
    /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}

结合起来syslog的日志轮转逻辑为:

  • 每周轮转(weekly)
  • 保留最近4份(rotate 4)
  • 轮转掉旧文件创建一个新的(create)
  • 旧文件添加日期后缀(dateext)
  • 没有配置到日志文件不报错(missingok)
  • 所有的文件轮转仅执行一次postrotate/endscript之间的脚本(sharedscripts),相反没有此配置每处理一个文件都会执行一次。

更多属性

compress
默认情况下,旧版本的日志文件会使用gzip(1)进行压缩。也可参见nocompress选项。

compresscmd
指定用于压缩日志文件的命令。默认为gzip(1)。也可参见compress选项。

uncompresscmd
指定用于解压缩日志文件的命令。默认为gunzip(1)。

compressext
指定在启用压缩时要使用的压缩日志文件的扩展名。默认扩展名与配置的压缩命令保持一致。

compressoptions
如果使用了压缩程序,可以通过该选项传递命令行参数给压缩程序。默认情况下,对于gzip(1)来说,参数是"-6"(以牺牲速度换取更高的压缩率)。如果使用其他压缩命令,可能需要修改compressoptions以匹配。

copy
复制日志文件,但不更改原始文件。例如,可以用于创建当前日志文件的快照,或者当其他工具需要截断或解析文件时使用。当使用此选项时,create选项将不起作用,因为旧的日志文件保持在原地。

copytruncate
复制日志文件后,将原始日志文件截断为零大小,而不是移动旧日志文件并可选地创建一个新文件。如果某个程序无法关闭其日志文件,并且可能会继续写入(追加)到前一个日志文件中,可以使用此选项。请注意,在复制文件和截断文件之间有一个非常小的时间间隔,因此可能会丢失一些日志数据。当使用此选项时,create选项将不起作用,因为旧的日志文件保持在原地。

create mode owner group, create owner group
在轮转日志文件后立即创建日志文件(与刚轮转的日志文件相同的名称)。mode指定日志文件的权限(以八进制表示,与chmod(2)相同),owner指定将拥有日志文件的用户,group指定日志文件所属的组。可以省略任何日志文件属性,省略的属性将使用与原始日志文件相同的值。可以使用nocreate选项禁用此功能。

createolddir mode owner group
如果oldir指令指定的目录不存在,则创建它。mode指定olddir目录的权限(以八进制表示,与chmod(2)相同),owner指定将拥有olddir目录的用户,group指定olddir目录所属的组。可以使用nocreateolddir选项禁用此功能。

daily
每天轮转日志文件。

dateext
在归档旧版本的日志文件时,添加一个日期扩展名,如YYYYMMDD,而不仅仅是添加一个数字。可以使用dateformat和dateyesterday选项配置扩展名。

dateformat format_string
使用与strftime(3)函数类似的格式指定dateext的扩展名。只允许使用%Y %m %d %H和%s这些格式化符号。默认值为-"%Y%m%d",每小时轮转的情况下,默认值为-"%Y%m%d%H"。请注意,将日志文件名与扩展名分隔的字符也是dateformat字符串的一部分。系统时钟必须设置在2001年9月9日之后,%s才能正确工作。请注意,此格式生成的日期戳必须按字母顺序排序(即先年份,然后月份,再日期。例如,2001/12/01没问题,但01/12/2001不行,因为01/11/2002排序较低但实际上更晚)。这是因为在使用rotate选项时,logrotate会对所有旋转过的文件进行排序,以确定哪些日志文件是更旧的,应该被删除。

dateyesterday
使用昨天的日期而不是今天的日期来创建dateext扩展名,以便旋转的日志文件的名称中包含一个与其中的时间戳相同的日期。

delaycompress
将前一个日志文件的压缩推迟到下一次轮转周期。只有与compress一起使用时才有效。当某个程序无法关闭其日志文件并且可能会继续写入前一个日志文件一段时间时,可以使用此选项。

extension ext
保留扩展名为ext的日志文件,不进行轮转。如果使用了压缩,则压缩扩展名(通常为.gz)会出现在ext之后。例如,如果有一个名为mylog.foo的日志文件,希望将其旋转到mylog.1.foo.gz而不是mylog.foo.1.gz。

hourly
每小时轮转日志文件。请注意,通常配置logrotate以每天由cron运行。您需要更改此配置并每小时运行logrotate,才能真正每小时轮转日志。

ifempty
即使日志文件为空,也要轮转该日志文件,覆盖notifempty选项(ifempty是默认值)。

include file_or_directory
按照所给定的文件名或目录名包含文件,就好像它是行内包含的一样。如果给定一个目录,将按字母顺序读取该目录中的大多数文件,然后再继续处理包含文件。忽略非普通文件(如目录和命名管道)和文件名以禁止扩展指令指定的禁止扩展名结尾的文件。

mail address
当日志被轮转时,将其发送到指定的地址。如果某个日志文件不应生成邮件,则可以使用nomail指令。

mailfirst
使用mail命令时,发送刚刚轮转的文件,而不是即将过期的文件。

maillast
使用mail命令时,发送即将过期的文件,而不是刚刚轮转的文件(这是默认值)。

maxage count
删除早于<count>天的旋转日志。只有在要轮转日志文件时才检查年龄。如果配置了maillast和mail指令,则将文件发送到配置的地址。

maxsize size
日志文件的大小超过指定的大小(以字节为单位),即使没有达到额外指定的时间间隔(每天、每周、每月或每年)也会进行轮转。相关的size选项类似,但与时间间隔选项互斥,它导致日志文件在不考虑上次轮转时间的情况下进行轮转。当使用maxsize时,同时考虑日志文件的大小和时间戳。

minsize size
只有当日志文件的大小超过指定的大小(以字节为单位),并且尚未达到额外指定的时间间隔(每天、每周、每月或每年)时,才进行日志文件的轮转。相关的size选项类似,但与时间间隔选项互斥,它导致日志文件在不考虑上次轮转时间的情况下进行轮转。当使用minsize时,同时考虑日志文件的大小和时间戳。

missingok
如果日志文件不存在,则继续处理下一个日志文件,而不发出错误消息。参见nomissingok选项。

monthly
每月的第一次运行logrotate时进行日志文件轮转(通常在每月的第一天)。

nocompress
不压缩旧版本的日志文件。参见compress选项。

nocopy
不复制原始日志文件并保留在原地(这会覆盖copy选项)。

nocopytruncate
在创建副本后,不截断原始日志文件(这会覆盖copytruncate选项)。

nocreate
不创建新的日志文件(这会覆盖create选项)。

nocreateolddir
当不存在oldir指令指定的目录时,logrotate不会创建它。

nodelaycompress
不推迟前一个日志文件的压缩到下一个轮转周期(这会覆盖delaycompress选项)。

nodateext
不使用日期扩展名对旧版本的日志文件进行归档(这会覆盖dateext选项)。

nomail
不将旧日志文件发送到任何地址。

nomissingok
如果日志文件不存在,则发出错误。这是默认值。

noolddir
日志在它们通常所在的目录中进行轮转(这会覆盖olddir选项)。

nosharedscripts
对于每个要轮转的日志文件,运行prerotate和postrotate脚本(这是默认值,并覆盖了sharedscripts选项)。将绝对路径传递给脚本作为第一个参数。如果脚本退出时出现错误,则对受影响的日志文件不执行剩余操作。

noshred
删除旧日志文件时不使用shred命令。参见shred选项。

notifempty
如果日志文件为空,则不进行轮转(这会覆盖ifempty选项)。

olddir directory
将日志移动到指定的目录进行轮转。该目录必须与要轮转的日志文件位于同一物理设备上,除非使用了copy、copytruncate或renamecopy选项。假设该目录相对于包含日志文件的目录,除非指定了绝对路径名。当使用此选项时,所有旧版本的日志文件都会放在目录中。这个选项可以被noolddir选项覆盖。

postrotate/endscript
在日志文件轮转后(在运行postrotate脚本之前),以/bin/sh执行postrotate和endscript之间的行。这些指令只能出现在日志文件定义内部。通常,将绝对路径传递给脚本作为第一个参数。如果指定了sharedscripts,则整个模式将传递给脚本。参见prerotate。参见sharedscripts和nosharedscripts用于错误处理。

prerotate/endscript
在日志文件轮转之前(当实际上要轮转日志时)以/bin/sh执行prerotate和endscript之间的行。这些指令只能出现在日志文件定义内部。通常,将绝对路径传递给脚本作为第一个参数。如果指定了sharedscripts,则整个模式将传递给脚本。参见postrotate。参见sharedscripts和nosharedscripts用于错误处理。

firstaction/endscript
在满足通配符模式的所有日志文件轮转之前,执行firstaction和endscript之间的行(只有在至少有一个日志实际上会被轮转时才执行prerotate脚本)。这些指令只能出现在日志文件定义内部。将整个模式作为第一个参数传递给脚本。如果脚本退出时出现错误,将不执行任何进一步处理。参见lastaction。

lastaction/endscript
在满足通配符模式的所有日志文件轮转之后,执行lastaction和endscript之间的行(在运行postrotate脚本之后),只有至少有一个日志被轮转时才执行。这些指令只能出现在日志文件定义内部。将整个模式作为第一个参数传递给脚本。如果脚本退出时出现错误,则仅显示错误消息(因为这是最后一个动作)。参见firstaction。

preremove/endscript
在删除日志文件之前,执行preremove和endscript之间的行。logrotate将传递即将被删除的文件的名称。参见firstaction。

rotate count
在删除或发送到mail指令中配置的地址之前,对日志文件进行count次轮转。如果count为0,则删除旧版本而不进行轮转。

size size
只有当日志文件大小超过指定的大小(以字节为单位)时,才进行轮转。如果size后面跟着k,则认为大小是以千字节为单位。如果使用M,则大小以兆字节为单位;如果使用G,则大小以千兆字节为单位。因此,size 100、size 100k、size 100M和size 100G都是有效的。

sharedscripts
通常,prerotate和postrotate脚本对每个要轮转的日志文件都会运行,并将绝对路径作为第一个参数传递给脚本。这意味着对于匹配多个文件的日志文件条目,可能会多次运行单个脚本。如果指定了sharedscripts,则只运行一次脚本,不管有多少日志与通配符模式匹配,并且整个模式都传递给它们。但是,如果模式中的所有日志都不需要进行轮转,则根本不会运行脚本。如果脚本退出时出现错误,将不对任何日志执行剩余操作。此选项覆盖了nosharedscripts选项,并暗示了create选项。

shred
在删除旧日志文件时使用shred -u而不是unlink()。默认情况下,此选项关闭。参见noshred选项。

shredcycles count
要求GNU shred(1)在删除之前覆盖旧日志文件count次。如果没有使用此选项,将使用shred的默认值。

start count
这是用作轮转基础的数字。例如,如果您指定0,则在从原始日志文件轮转时,日志将以.0扩展名创建。如果指定9,则日志文件将以.9创建,跳过0-8。文件仍将按照rotate指令指定的次数进行轮转。

su user group
以指定的用户和组而不是使用默认的用户/组(通常为root)进行日志文件轮转。user指定用于轮转的用户名,group指定用于轮转的组名。如果在create指令中指定了无法拥有指定权限的用户/组创建文件,将导致错误。

weekly [weekday]
每个工作日都进行一次日志文件轮转,或者如果日期推进了至少7天,自上次轮转开始以来忽略具体时间。工作日的解释如下:0表示周日,1表示周一,...,6表示周六;特殊值7表示每7天,与工作日无关。如果省略weekday参数,默认为0。

yearly
如果当前年份与上次轮转的年份不同,则对日志文件进行轮转。
阅读全文

Centos7安装高版本PHP

在centos7上通过remi源安装高版本php

添加源

sudo yum install epel-release yum-utils
sudo yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm

安装php

sudo yum install -y php82-php-fpm php82-php-cli php82-php-bcmath php82-php-gd php82-php-json php82-php-mbstring php82-php-mcrypt php82-php-mysqlnd php82-php-pecl-redis5 php82-php-opcache php82-php-pdo php82-php-pecl-crypto php82-php-pecl-mcrypt php82-php-pecl-geoip php82-php-pecl-msgpack php82-php-pecl-swoole5 php82-php-pecl-uuid php82-php-pecl-zip php82-php-xz php82-php-xml

添加到PATH

sudo ln -s /opt/remi/php82/root/usr/bin/php /usr/local/bin/php
阅读全文

Linux网络-Macvlan

macvlan是什么

macvlan 是 Linux 内核 3.0 版本引入的一种虚拟网卡,它让用户可以在一块物理网卡上,再创建若干个依附于它的虚拟网卡,这些网卡拥有不同的 MAC 地址,如此就完成了网卡的虚拟化。

macvlan的模式

bridge模式

bridge模式下,物理网卡可以作为桥供macvlan虚拟网卡通信。但需要注意,macvlan虚拟网卡与物理网卡不能通信( macvlan 所有模式均不支持),不能通信意味着网卡上方的协议栈不能互通,即你不能从容器ping通宿主机,反之也一样。

VEPA模式

VEPA模式与bridge模式相比,macvlan虚拟网卡之间也不能在宿主机内部互通了!但它们还可以借助外部工作在 Hairpin 模式(发夹弯模式)的交换机完成通信, 工作在发夹弯模式的交换机在收到广播报文或者未知单播报文时,会向所有端口(不会排除报文入接口)发送报文。不仅是 macvlan 虚拟网卡之间, macvlan 虚拟网卡与物理网卡之间也能通过外部 Hairpin 交换机通信。

private模式

private 模式正如其名字,意味着该模式是”私密”的,在这种模式下,即使外部有工作在 Hairpin模式的交换机,macvlan虚拟网卡之间也不能通信。此时,从macvlan 虚拟网卡发出的广播报文经过外部 Hairpin模式的交换机echo回来,只是会送回自己。

passthrough模式

passthrough即直通,此模式下,某一个 macvlan 虚拟网卡会接管物理网卡,它将独占物理网卡。

macvlan的实现

作为一种虚拟网卡实现, macvlan以驱动模块的形式存在于内核源码中,具体请看/driver/net/macvlan.c。

创建macvlan虚拟网卡

用户通过输入ip link add link eth0 name macv1 type macvlan mode bridge 添加 macvlan 虚拟网卡。iproute 将名字、模式等信息打包成Netlink消息下发到内核,内核调用macvlan_common_newlink()创建。

static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    ......
    ops = rtnl_link_ops_get(kind); // 会得到 macvlan_link_ops
    // 创建 macvlan 虚拟网卡的 net_device, 私有部分为 struct macvlan_dev
    dev = rtnl_create_link(link_net ? : dest_net, ifname, name_assign_type, ops, tb);
     |
     |- dev = alloc_netdev_mqs(ops->priv_size, ifname, name_assign_type,
			       ops->setup, num_tx_queues, num_rx_queues);
    ops->newlink(link_net ? : net, dev, tb, data);
      |
      |- macvlan_common_newlink(src_net, dev, tb, data);
         |
         |  // lowerdev 是依附的物理网卡 eth0
         |- lowerdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
         |
         |  // 随机分配 macvlan 网卡的 MAC 地址
         |- if (...) eth_hw_addr_random(dev);
         |
         |  // 如果 eth 还没有创建过 macvlan 网卡, 就创建
         |- if (...) macvlan_port_create(lowerdev)
            |
            | // 初始化保存 macvlan 网卡MAC地址的 HASH 表
            |- INIT_HLIST_HEAD(&port->vlan_hash[i]);
            |
            |  // 设置物理网卡 eth0 的 rx_handler 为 macvlan_handle_frame
            |- netdev_rx_handler_register(dev, macvlan_handle_frame, port);
         |
         |   // 将 macvlan 设备注册到系统
         |-  register_netdevice(dev);
}

macvlan虚拟网卡接收报文

从宿主机外发给macvlan虚拟网卡的网卡的报文总是先经过物理网卡eth0,在eth0的接收过程中,会调用到创建macvlan虚拟网卡时注册的macvlan_handle_frame,这里面将报文分为了多播(广播)和单播报文分别处理,先来看多播报文的处理:

static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
{
    port = macvlan_port_get_rcu(skb->dev);
	if (is_multicast_ether_addr(eth->h_dest)) {		
		*pskb = skb;
		eth = eth_hdr(skb);

        // 根据报文的 源MAC 查找是否是某个虚拟网卡的 MAC 地址
		src = macvlan_hash_lookup(port, eth->h_source);
		if (src && src->mode != MACVLAN_MODE_VEPA &&
		    src->mode != MACVLAN_MODE_BRIDGE) {
            // 如果找到且它是 private 或者 passthrou 模式,则将其送给该端口 
			vlan = src;
			ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: netif_rx(skb);
            // 返回 CONSUMED 是告诉外面该报文已经"处理过"了
			handle_res = RX_HANDLER_CONSUMED;
			goto out;
		}

        // 如果没有找到对应的虚拟网卡 或者 其模式为 bridge 或者 vepa,则将该报文广播给所有虚拟网卡
		MACVLAN_SKB_CB(skb)->src = src;
		macvlan_broadcast_enqueue(port, skb);

        // 返回 PASS 是告诉外面 这个包还可以继续处理,也就是还可以给 eth0 上方的协议栈
		return RX_HANDLER_PASS;
	}
    ......
}

单播的情况:

static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
{
    ...
    // 单播报文的处理
    if (port->passthru)
		vlan = list_first_or_null_rcu(&port->vlans,
					      struct macvlan_dev, list);
	else
        // 根据报文的 目的MAC 看是否是应该某个 macvlan 虚拟网卡接收
		vlan = macvlan_hash_lookup(port, eth->h_dest);
	if (vlan == NULL)
        // 不是的话 返回 PASS,让 eth0 的协议栈处理
		return RX_HANDLER_PASS;

    // 如果是,则修改报文的入设备为 macvlan 虚拟网卡
    dev = vlan->dev;
    skb->dev = dev;

    // 返回 ANOTHER 让调用者重新走一次接收匹配过程
    handle_res = RX_HANDLER_ANOTHER;
    return handle_res;
}

macvlan虚拟网卡发送报文

macvlan虚拟网卡的发送比较简单,只有bridge模式有一个特别处理:广播报文需要发送给其他“兄弟”虚拟网卡,当然它们也必须是 bridge模式,单播报文如果能找到确定的目标,就直接回路到接收过程。只要不是单播并且找到了目标的情况,该报文最终都会修改报文出设备为物理网卡eth0后,由物理网卡发送出去。

static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
{
	const struct macvlan_dev *vlan = netdev_priv(dev);
	const struct macvlan_port *port = vlan->port;
	const struct macvlan_dev *dest;

	if (vlan->mode == MACVLAN_MODE_BRIDGE) {
		const struct ethhdr *eth = (void *)skb->data;

		// 广播报文: 广播给其他同样依附于 eth0 且是 bridge 模式的 macvlan 虚拟网卡 
		if (is_multicast_ether_addr(eth->h_dest)) {
			macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
			goto xmit_world;
		}

        // 单播报文如果能找到确切的 macvlan 虚拟网卡 就直接走接收过程
		dest = macvlan_hash_lookup(port, eth->h_dest);
		if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {
			dev_forward_skb(vlan->lowerdev, skb);
			return NET_XMIT_SUCCESS;
		}
	}

xmit_world:
	skb->dev = vlan->lowerdev;
	return dev_queue_xmit(skb);
}
阅读全文

Flannel网络模式之host-gw

所有节点在二层网络互通的情况下,直接配置节点的三层路由即可互通,不需要使用VXLAN隧道。

使用host-gw模式,需要修改flannel的ConfigMap将Backend.Type 从vxlan改为host-gw:

net-conf.json: |
  {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "host-gw" // <- 改成host-gw
      }
  }

在host-gw模式下,由于不涉及VXLAN的封包解包,不再需要flannel.1虚机网卡。 flanneld 负责为各节点设置路由 ,将对应节点Pod子网的下一跳地址指向对应的节点的IP,如图中路由表①所示。

[root@Node1 ~]# ip r
...
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 192.168.50.3 dev eth0 # Node2子网的下一跳地址指向Node2的public ip。
...

由于没有封包解包带来的消耗,host-gw性能要好。不过一般在云环境下,二层互通的条件可能不好满足,不能支持使用host-gw的模式,在私有化部署的场景下,可以考虑。

阅读全文

Flannel网络模式之VXLAN

VXLAN简介

VXLAN (Virtual Extensible LAN)是一种网络虚拟化技术,它使用一种隧道协议,将二层以太网帧封装在四层UDP报文中,通过三层网络传输,组成一个虚拟大二层网络。VXLAN的报文格式如下:

使用VTEP(VXLAN Tunnel Endpoint)来进行封包和解包,它是VXLAN隧道的起点或终点:
在发送端,源VTEP将原始报文封装成VXLAN报文,通过UDP发送到对端VTEP。
在接收端,VTEP将解开VXLAN报文,将原始的2层数据帧转发给目的的接收方。
VTEP可以是独立的网络设备,例如交换机,也可以是部署在服务器上的虚拟设备。例如使用置顶交换机(TOR)作为VTEP时,VXLAN的网络模型如下图:

Flannel的VXLAN模式

VXLAN是Flannel默认和推荐的模式。当我们使用默认配置安装Flannel时,它会为每个节点分配一个24位子网,并在每个节点上创建两张虚机网卡: cni0 和 flannel.1 。 cni0 是一个网桥设备,类似于 docker0 ,节点上所有的Pod都通过veth pair的形式与 cni0 相连。 flannel.1 则是一个VXLAN类型的设备,充当VTEP的角色,实现对VXLAN报文的封包解包。

节点内通信

节点内的容器间通信通过 cni0 网桥就能完成,不涉及任何VXLAN报文的封包解包。例如在下面的图例中,Node1的子网为10.244.0.1/24, PodA 10.244.0.20 和 PodB 10.224.0.21通过 cni0 网桥实现互通。

跨节点通信

flanneld 从 etcd 中可以获取所有节点的子网情况,以此为依据为各节点配置路由,将属于非本节点的子网IP都路由到 flannel.1 处理,本节点的子网路由到 cni0 网桥处理。

[root@Node1 ~]# ip r
...
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 # Node1子网为10.224.0.0/24, 本机PodIP都交由cni0处理
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink # Node2子网为10.224.1.0/24,Node2的PodID都交由flannel.1处理
...

VXLAN的封包是将二层以太网帧封装到四层UDP报文中的过程。要生成原始的L2帧, flannel.1 需要得知:

内层源/目的IP地址
内层源/目的MAC地址

内层的源/目的IP地址是已知的,即为PodA/PodB的PodIP,在图例中,分别为10.224.0.20和10.224.1.20。 内层源/目的MAC地址要结合路由表和ARP表来获取。根据路由表①得知:

下一跳地址是10.224.1.0,关联ARP表②,得到下一跳的MAC地址,也就是目的MAC地址:Node2_flannel.1_MAC;
报文要从 flannel.1 虚拟网卡发出,因此源MAC地址为 flannel.1 的MAC地址。

要注意的是,这里ARP表的表项②并不是通过ARP学习得到的,而是 flanneld 预先为每个节点设置好的,由 flanneld负责维护,没有过期时间。

要将原始L2帧封装成VXLAN UDP报文, flannel.1 还需要填充源/目的IP地址。前面提到,VTEP是VXLAN隧道的起点或终点。因此,目的IP地址即为对端VTEP的IP地址,通过FDB表获取。在FDB表③中,dst字段表示的即为VXLAN隧道目的端点(对端VTEP)的IP地址,也就是VXLAN DUP报文的目的IP地址。FDB表也是由 flanneld 在每个节点上预设并负责维护的。

[root@Node1 ~]# bridge fdb show | grep flannel.1
ba:74:f9:db:69:c1 dev flannel.1 dst 192.168.50.3 self permanent

Flannel的VXLAN模式通过静态配置路由表,ARP表和FDB表的信息,结合VXLAN虚拟网卡 flannel.1 ,实现了一个所有Pod同属一个大二层网络的VXLAN网络模型。

阅读全文