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网络模型。

阅读全文