当前位置:网站首页>flannel 原理 之 子网划分

flannel 原理 之 子网划分

2022-04-23 14:11:00 Mrpre

flannel 原理 之 子网划分

Docker

通常情况下, 2台物理机上分别安装Docker,Docker分别在2台物理机上,docker0的网桥,其IP地址属于私网网段,例如物理机地址是10.x.x.x,docker0地址通常是192.x.x.x。 任何在物理机上启动的container,其分配的eth0地址都是 192.x.x.x。 但是2台独立的物理机上的container之间是无法通信的,因为物理网络里是没有192.x.x.x网段的信息,无法对其进行路由,更何况物理网络作为基础设施,也不可能动态的去添加容器网络相关的路由。

flannel 解决的问题,其实就是 部署在多台不同物理机(ECS)上的容器,如何进行网络通信。

flannel

子网划分

flannel无论哪种运行模式,都会对物理机的docker配置信息进行修改,例如 flannel启动之后会修改 /usr/lib/systemd/system/docker.service 注入 DOCKER_OPT_BIP="–bip=xx.xx.xx.xx/24" 使得当前机器上启动的的容器以此网段作为容器的地址。而具体网段信息是保存在etcd中的,每台node上面的flannel都监听了这个,flannel读取etcd解析,然后确定自己网段,修改docker配置。

ETCD中提前存储了flannel的网段,例如ETCDCTL_API=2 etcdctl mk /atomic.io/network/config '{ "Network": "182.48.0.0/16" }' 其中配置格式是json格式,需要和flannel解析格式匹配。该配置告诉所有flannel进程,flannel所在的node上申请的容器ip,必须在这个网段之内。

type Config struct {
  Network     ip.IP4Net
  SubnetMin   ip.IP4
  SubnetMax   ip.IP4
  SubnetLen   uint
  BackendType string          `json:"-"`
  Backend     json.RawMessage `json:",omitempty"`
}

flannel读取网段配置以及解析流程

  // Fetch the network config (i.e. what backend to use etc..).
  config, err := getConfig(ctx, sm)
  if err == errCanceled {
    
    wg.Wait()
    os.Exit(0)
  }

  func getConfig(ctx context.Context, sm subnet.Manager) (*subnet.Config, error) {
    
  // Retry every second until it succeeds
  for {
    
    config, err := sm.GetNetworkConfig(ctx)
    if err != nil {
    
      log.Errorf("Couldn't fetch network config: %s", err)
    } else if config == nil {
    
      log.Warningf("Couldn't find network config: %s", err)
    } else {
    
      log.Infof("Found network config - Backend type: %s", config.BackendType)
      return config, nil
    }
    select {
    
    case <-ctx.Done():
      return nil, errCanceled
    case <-time.After(1 * time.Second):
      fmt.Println("timed out")
    }
  }
}

# subnet/etcdv2/local_manager.go 读取etcd中的网段信息
func (m *LocalManager) GetNetworkConfig(ctx context.Context) (*Config, error) {
    
  cfg, err := m.registry.getNetworkConfig(ctx)
  if err != nil {
    
    return nil, err
  }


  //获取到 { "Network": "182.48.0.0/16" },然后解析,解析的例如,182.48.0.0/16这个网段,解析后 SubnetMin是182.48.1.0,SubnetMax是182.48.255.0
  return ParseConfig(cfg)
}

# subnet/etcdv2/registry.go
func (esr *etcdSubnetRegistry) getNetworkConfig(ctx context.Context) (string, error) {
    
  key := path.Join(esr.etcdCfg.Prefix, "config")
  resp, err := esr.client().Get(ctx, key, &etcd.GetOptions{
    Quorum: true})
  if err != nil {
    
    return "", err
  }

  //通过etcd获取到 { "Network": "182.48.0.0/16" }
  return resp.Node.Value, nil
}

flannel读取到etcd数据后,由于有多台node的存在,每台node需要再次根据16位掩码,确定自己的子网掩码,这样,每台node上容器启动的ip都不会冲突,在ParseConfig中,已经有逻辑确定了16位掩码情况下子网网段为24掩码,其他情况下是 为 n+2。24位掩码的意思值,当前node生成容器的ip是 182.48.{y}.x,x为 0~254。那么就存在一个问题,就是每台node上面{y}肯定不能配置一样。


# main.go
bn, err := be.RegisterNetwork(ctx, &wg, config)
if err != nil {
  log.Errorf("Error registering network: %s", err)
  cancel()
  wg.Wait()
  os.Exit(1)
}

# 路径和架构有关
func (be *UdpBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGroup, config *subnet.Config) (backend.Network, error) {

    l, err := be.sm.AcquireLease(ctx, &attrs)
    switch err {
    case nil:
    ......
    }
}

func (m *LocalManager) AcquireLease(ctx context.Context, attrs *LeaseAttrs) (*Lease, error) {
  config, err := m.GetNetworkConfig(ctx)
  if err != nil {
    return nil, err
  }

  for i := 0; i < raceRetries; i++ {
    l, err := m.tryAcquireLease(ctx, config, attrs.PublicIP, attrs)
    switch err {
    case nil:
      return l, nil
    case errTryAgain:
      continue
    default:
      return nil, err
    }
  }

  return nil, errors.New("Max retries reached trying to acquire a subnet")
}

tryAcquireLease函数中,先从etcd读取老的配置,查看是否当前node存在子网配置信息,通常如果只是flannel重启并且之前启动成功过,那么此时都会读到配置

flannel中,存入etcd的格式的路径是 $PREFIX/subnets/,key为网段,value为node的ip。

$ETCDCTL_API=2 etcdctl ls /coreos.com/network/subnets
/coreos.com/network/subnets/182.48.56.0-24
/coreos.com/network/subnets/182.48.17.0-24


$ETCDCTL_API=2 etcdctl get /coreos.com/network/subnets/182.48.56.0-24
{
    "PublicIP":"11.238.116.75"}

func (m *LocalManager) tryAcquireLease(ctx context.Context, config *Config, extIaddr ip.IP4, attrs *LeaseAttrs) (*Lease, error) {
    
  leases, _, err := m.registry.getSubnets(ctx)
  if err != nil {
    
    return nil, err
  }

  // Try to reuse a subnet if there's one that matches our IP
  //遍历获取到的所有key,判断其value是否是自己node的ip,是表示已经有配置信息的,使用之即可
  if l := findLeaseByIP(leases, extIaddr); l != nil {
    
    // Make sure the existing subnet is still within the configured network
    if isSubnetConfigCompat(config, l.Subnet) {
    
      log.Infof("Found lease (%v) for current IP (%v), reusing", l.Subnet, extIaddr)
    }
}

tryAcquireLease函数中,如果没有当前node的配置,就创建一个配置。防止多个node的flannel并发创建相同的网段的手段也很简单,使用了参数prevExist=false防止同时写成功;失败后,外部循环再次进行创建子网的操作,直到成功。


  if sn.Empty() {
    
    // no existing match, grab a new one
    sn, err = m.allocateSubnet(config, leases)
    if err != nil {
    
      return nil, err
    }
  }

  exp, err := m.registry.createSubnet(ctx, sn, attrs, subnetTTL)
  switch {
    
  case err == nil:
    log.Infof("Allocated lease (%v) to current node (%v) ", sn, extIaddr)
    return &Lease{
    
      Subnet:     sn,
      Attrs:      *attrs,
      Expiration: exp,
    }, nil
  case isErrEtcdNodeExist(err):
    return nil, errTryAgain
  default:
    return nil, err
  }

至此,多个物理机(Node)上的flannel进程,从182.48.0.0/16这个配置的网段中,生成了自己的子网,例如 182.48.1.0/24,182.48.2.0/24,并且将配置保存在etcd上面,以保证其他节点不占用。并且以此子网,修改了Docker启动参数,是的Docker启动的容器以子网为准。

版权声明
本文为[Mrpre]所创,转载请带上原文链接,感谢
https://wonderful.blog.csdn.net/article/details/115180077