Reference

文章目录

  • 1 入口
  • 2 ProxyServer创建及调用
  • 3 ProxyServer 核心调用流程
    • 3.1 func (o *Options) Run() err
    • 3.2 func (o *Options) runLoop() error
    • 3.3 func (s *ProxyServer) Run() error
    • 3.4 func (proxier *Proxier) SyncLoop()
  • 4 资源事件处理流程
    • 4.1 Service事件
    • 4.2 Endpoints事件
    • 4.3 Nodes事件
    • 4.4 func (proxier *Proxier) Sync()
  • 5 代理规则同步处理流程
  • 6 数据包路径及链位置
    • 6.1 PREROUTING
      • 6.1.1 PREROUTING ->KUBE-SERVICES
      • 6.1.2 KUBE-SERVICES
        • 6.1.2.1 ClusterIP
          • 6.1.2.1.1 KUBE-SERVICES->KUBE-MARK-MASQ
          • 6.1.2.1.2 KUBE-SERVICES->KUBE-SVC-HASH
        • 6.1.2.2 External IP
          • 6.1.2.2.1 KUBE-SERVICES->KUBE-MARK-MASQ
          • 6.1.2.2.2 KUBE-SERVICES->KUBE-SVC-HASH
        • 6.1.2.3 LB IP
          • 6.1.2.3.1 KUBE-SERVICES->KUBE-FW-HASH
          • 6.1.2.3.2 KUBE-FW-HASH->KUBE-MARK-MASQ
          • 6.1.2.3.3 KUBE-FW-HASH->KUBE-SVC-HASH
          • 6.1.2.3.4 KUBE-FW-HASH->KUBE-MARK-DROP
        • 6.1.2.4 NodePort
          • 6.1.2.4.1 KUBE-SERVICES->KUBE-NODEPORTS
          • 6.1.2.4.2 KUBE-NODEPORTS->KUBE-MARK-MASQ
          • 6.1.2.4.3 KUBE-NODEPORTS->KUBE-SVC-HASH
        • 6.1.2.5 KUBE-SVC-HASH -> KUBE-SEP-HASH
        • 6.1.2.6 KUBE-SEP-HASH
    • 6.2 INPUT
      • 6.2.1 FILTER INPUT ->KUBE-SERVICES
      • 6.2.2 FILTER INPUT ->KUBE-EXTERNAL-SERVICES
    • 6.3 FORWARD
      • 6.3.1 FORWARD-> KUBE-FORWARD
      • 6.3.2 FORWARD-> KUBE-SERVICES
    • 6.4 OUTPUT
      • 6.4.1 NAT OUTPUT-> KUBE-SERVICES
      • 6.4.2 NAT OUTPUT-> KUBE-SERVICES
    • 6.5 POSTROUTING
      • 6.5.1 NAT POSTROUTING -> KUBE-POSTROUTING
  • 7 总结

1 入口

cmd目录下的proxy.go

 利用Cobra构建CLI接口,并对proxy server进行调用。

kubernetes/cmd/kube-proxy/proxy.gofunc main() {rand.Seed(time.Now().UnixNano())// 核心调用command := app.NewProxyCommand()// TODO: once we switch everything over to Cobra commands, we can go back to calling// utilflag.InitFlags() (by removing its pflag.Parse() call). For now, we have to set the// normalize func and add the go flag set by hand.pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)// utilflag.InitFlags()logs.InitLogs()defer logs.FlushLogs()if err := command.Execute(); err != nil {os.Exit(1)}
}

2 ProxyServer创建及调用

kubernetes/cmd/kube-proxy/app/server.gofunc NewProxyCommand() *cobra.Command {.....cmd := &cobra.Command{......Run:func(cmd *cobra.Command, args []string) {......opts.Run()}}.....opts.config, err = opts.ApplyDefaults(opts.config).....
}

ApplyDefaults中会配置默认的参数。/pkg/proxy/apis中的init方法内,会向SchemeBuilder注册添加默认参数的方法。这里面对于Porxy会注册SetObjectDefaults_KubeProxyConfiguration,最后调用了SetDefaults_KubeProxyConfiguration。其中IPTables.SyncPeriod.Duration,默认值为30S(该值为失败后的尝试retry间隔,可以使用–iptables-sync-period配置,其指定了iptables重刷新的最大间隔)

kubernetes/cmd/kube-proxy/app/server.gofunc (o *Options) Run() error {defer close(o.errCh)if len(o.WriteConfigTo) > 0 {return o.writeConfigFile()}proxyServer, err := NewProxyServer(o)if err != nil {return err}if o.CleanupAndExit {return proxyServer.CleanupAndExit()}o.proxyServer = proxyServerreturn o.runLoop()
}

注意:这里BounedFrequencyRunner的同步方法为proxier.syncProxyRules,最小时间间隔为minSyncPeriod(可以使用参数--iptables-min-sync-period配置,未配置时,应该就是0),最大时间间隔为1小时,突发数为2,失败重试间隔为proxier.syncPeriod(默认为30秒,可以用参数--iptables-sync-period配置,其指定了iptables重刷新的最大间隔,不允许为0)

3 ProxyServer 核心调用流程

opts.Run()->

o.runLoop()->

o.proxyServer.Run()->

s.Proxier.SyncLoop()

3.1 func (o *Options) Run() err

在构建ProxyCommand时,会在其Run方法内调用该方法

  • (1)如kube-proxy命令调用时,传入了–write-config-to参数,则会将kube-proxy的默认配置打印到指定文件,然后退出。
  • (2)调用NewProxyServer方法,构建proxyServer
  • (3)如果kube-proxy命令调用时传入了–cleanup参数,则会清理iptables及ipvs配置,然后退出
  • (4)调用o.runLoop方法。

3.2 func (o *Options) runLoop() error

  • (1)如果o.Watcher(用于监听ConfigFile的更新变化)不为空,则调用o.watcher.Run()(这里如果监听到配置文件的写或者rename事件,会导致kube-proxy的退出)
  • (2)调用o.proxyServer.Run()

3.3 func (s *ProxyServer) Run() error

  • (1)如果启动参数中配置了–oom-score-adj(oom打分调整,OOM killer 会给进程打分,把 oom_score 最大的进程先杀死,如果这个值设置成负数,则表明不优先级杀死它,如果位正数则优先杀死,如果设置为0表明与用户不调整)。则为kube-proxy配置该值
  • (2)如果Broadcaster及EventClient不为空,则开启事件记录服务
  • (3)如果健康检查服务不会空,则启动
  • (4)如果指标绑定地址大于0,则启动服务
    • 1)curl 10.19.188.3:10249/proxyMode, 可以获得proxyMode运行模式
    • 2)curl curl 10.19.188.3:10249/metrics, 可以获得运行指标信息
  • (5)如过contracker会为空,则根据启动参数,配置系统contracker配置。(这里我们会把所有的contracker相关配置设置为0, 即不修改系统原先的conntrack配置)
    • 1)首先调用getConntrackMax,如果–conntrack-max-per-core参数不为空,且值大于0(不配置时,默认值为32768),则用其乘以cpu数,获取上限值,如果该值大于配置中的–conntrack-min,则返回该值,否则返回–conntrack-min。否则返回0
    • 2)如果max值大于0,则更新系统配置
    • 3)如果配置了–conntrack-tcp-timeout-close-wait duration,且其值大于0, 则更新系统值
    • 4)如果配置了–conntrack-tcp-timeout-established,且其值大于0,则更新系统中的该值。
  • (6)构建kube-proxy监听的server的选择器,及Informer(对于具有service.kubernetes.io/service-proxy-name及service.kubernetes.io/headless label的service不处理),注册service事件处理器,并启动
  • (7)如果使用EndpointSlices,则构建endpointsSlice事件处理器,并启动。否则构建endpoints事件处理器,并启动。
  • (8)启动informer
  • (9)如果使用了ServiceTopology门特性,则增加nodeInformer,及相关事件处理器,并启动
  • (10)记录启动事件
  • (11)调用s.Proxier.SyncLoop

3.4 func (proxier *Proxier) SyncLoop()

  • (1)如果proxier的健康检查服务不为空,则调用其update函数
  • (2)调用proxier.syncRunner.Loop(wait.NeverStop)

4 资源事件处理流程

在NewProxier时,会为其构建syncRunner,其绑定的处理函数为syncProxyRules。同时,调用ipt.Monitor(Monitor通过创建canary chain和轮询来检测给定的iptables表是否已被外部工具(例如,重新加载防火墙)刷新。 (具体来说,它每隔一段时间轮询[0]表,直到canary chain从表中删除,然后再等待一段额外的时间,以便从其余的表中也删除canary chain。 您可以通过在表[0]中列出一个相对空的表来优化轮询。 当检测到刷新时,将调用reloadFunc,以便调用者可以重新加载自己的iptables规则。 如果它无法创建检测链(无论是最初还是重新加载后),它将记录一个错误并停止监视。 (这个函数应该从goroutine中调用。))

4.1 Service事件

service Add\Update\Delete事件,会记录service变化,加入到proxier的serviceChanges中,并会调用proxier.Sync()方法

4.2 Endpoints事件

Endpoints Add\Update\Delete事件,会记录endpoints变化,加入到proxier的endpointsChanges中,会调用proxier.Sync()方法

4.3 Nodes事件

Nodes的Add\Update\Delete事件, 会调用proxier.syncProxyRules()

4.4 func (proxier *Proxier) Sync()

  • (1)如果proxier.healthzServer 不为空,则调用QueuedUpdate方法。会存储,事件时间。
  • (2)调用proxier.syncRunner.Run(),该方法会导致尝试调用syncProxyRules

5 代理规则同步处理流程

service/endpoints/node事件,最后都会导致syncProxyRules函数的调用

  • (1)如果proxier还没有初始化,则直接打印日志信息,返回.即只有在informer收到services和endpoints以后,才会进行真正的配置同步。
  • (2)构建defer func,记录其观察到的指标信息
  • (3)调用UpdateServiceMap,基于给定的ServiceChangeTracker,返回更新结果UpdateServiceMapResult,其包含了HCServiceNodePorts(HCServiceNodePorts是服务名称到节点端口号的映射,该映射指示该节点上该服务的健康状况)和UDPStaleClusterIP(持有UDP端口的过期的(不再分配给服务)Service IPs.调用者可以使用此信息中止超时等待或清除连接跟踪信息。)
    • 1)构建空Set集合赋值给UDPStaleClusterIP
    • 2)将ServiceChangeTracker中每个change的Current ServiceMap merge到proxier.ServiceMap中。用Curent ServiceMap 过滤change的Previous ServiceMap(即change的Previous ServiceMap中移除svcPortName中还在Curent中的项,剩下的就是无效的port),从proxier.ServiceMap中unmerge这些变化,同时将这些port中协议类型为TCP的ClusterIP加入到UDPStaleClusterIP集合中(svcPortName对象由ns,servicePort.Name,servicePort.Protocol组成。只有LB类型和NoePort类型Service才有可能会有HealthCheckNodePort–我们没有使用到这个字段)
    • 构建空HCServiceNodePorts集合
    • 遍历Merge以后的ServiceMap,如果其健康检查端口不为空,则将其加入到HCServiceNodePorts中。
  • (4)调用endpointsMap,基于endpoints变化,更新proxy.EndpointsMap,返回UpdateEndpointMapResult(包含HCEndpointsLocalIPSize–与kube-proxy同节点的endpoints的集合,StaleEndpoints–过期的endpoint service对,StaleServiceNames过期的service名字集合,LastChangeTriggerTimes所有endpoints变化的触发时间)
    • 1)构建空StaleEndpoints、StaleServiceNames、LastChangeTriggerTimes map
    • 2)调用apply方法,更新EndpointsMap,并用staleEndpoints存储不新鲜的 udp endpoints,用staleServiceNames 用于存储不新鲜的udp service .
    • 3)基于合并后的endpointsMap,获取本地的endpoints,填充HCEndpointsLocalIPSize。
  • (5)将endpointUpdateResult中的StaleServiceNames和serviceUpdateResult中的UDPStaleClusterIP进行合并,存入到staleServices中(里面记录了clusterIP以及ExternalIP)
  • (6)遍历iptablesJumpChains(kube-proxy在iptables原有链上,新建的Jump链)。确保每条目标Jump链,在表上存在(尝试去创建,如果创建失败,则根据返回值判断是否已经存在,如果创建失败,直接return).确保在源链上插入Jump规则,即kube-chanin(首先判断规则是否存在,如果不存在,则插入,这里应该是插入到表头位置)
  • (7)构建existingFilterChains map,清理proxier的existingFilterChainsData。
  • (8)调用SaveInto方法,将Filter表内的信息写入到proxier的existingFilterChainsData。(底层调用的命令:iptables-save -t filter)
# iptables-save -t filter 查询得到的结果中间省略了部分
*filter
:INPUT ACCEPT [272947:113632328]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [269953:157912430]
:KUBE-EXTERNAL-SERVICES - [0:0]
:KUBE-FIREWALL - [0:0]
:KUBE-FORWARD - [0:0]
:KUBE-KUBELET-CANARY - [0:0]
:KUBE-PROXY-CANARY - [0:0]
:KUBE-SERVICES - [0:0]
-A INPUT -j KUBE-FIREWALL
  • (9)如果获取失败则打印日志信息。如果获取成功,则从proxier.existingFilterChainsData中获取ChainLine,存入到existingFilterChains中,其中key值为Chain名字,value为该行的值(比如:KUBE-SERVICES - [0:0])
  • (10)构建existingNATChains map,清理proxier的iptablesData。
  • (11)调用SaveInto方法,将NAT表内的信息写入到proxier的iptablesData。(底层调用的命令:iptables-save -t nat)
  • (12)如果获取失败则打印日志信息。如果获取成功,则从proxier.iptablesData中获取ChainLine,存入到existingNATChains中,其中key值为Chain名字,value为该行的值(比如:KUBE-SERVICES - [0:0])
  • (13)清理proxier.filterChains、proxier.filterRules、proxier.natChains及natRules.
  • (14)往proxier.filterChains写入表头即*filter
  • (15)往proxier.natChains写入表头即*nat
  • (16)遍历filter表中的Jump链名字(KUBE-SERVICES、KUBE-EXTERNAL-SERVICES、KUBE-FORWARD),如果existingFilterChains中存在该名字,则将其对应的行信息写入到proxier.filterChains,如果不存在(考虑获取proxier.existingFilterChainsData失败),则构建对应的ChainLine写入到proxier.filterChains中。(此时,proxier.filterChains包含了KUBE-SERVICES、KUBE-EXTERNAL-SERVICES以及KUBE-FORWARD链行信息)
  • (17)遍历nat表中的Jump链名字(KUBE-SERVICES、KUBE-NODEPORTS、KUBE-POSTROUTING、KUBE-MARK-MASQ),如果existingNatChains中存在,则将其对应的行信息写入到proxier.natChains,如果不存在(考虑获取proxier.existingNatChainsData失败),则构建对应的ChainLine写入到proxier.natChains中。(此时,proxier.natChains包含了KUBE-SERVICES、KUBE-NODEPORTS、KUBE-POSTROUTING以及KUBE-MARK-MASQ链信息)
  • (18)构建masqRule(该规则负责匹配带有0x4000/0x4000 mark的数据包,将其交给MASQUERADE处理)
masqRule := []string{"-A", string(kubePostroutingChain),"-m", "comment", "--comment", `"kubernetes service traffic requiring SNAT"`,"-m", "mark", "--mark", proxier.masqueradeMark,"-j", "MASQUERADE",}
# 其中:
# kubePostroutingChain:KUBE-POSTROUTING
# masqueradeMark:由--iptables-masquerade-bit决定,
masqueradeValue = 1 << uint(14)
masqueradeMark = fmt.Printf("%#08x/%#08x", masqueradeValue, masqueradeValue)
--iptables-masquerade-bit默认为14,这则masqueradeMark默认为0x4000/0x4000# iptables nat表中对应的规则
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
# 可以通过该命令查看
iptables -t nat -L KUBE-POSTROUTING
  • (19)如果iptables支持RandomFully(iptables版本大于1.6.2,参考iptables.go)。则在masqRule中添加–random-fully参数(避免linux内核问题,多个流映射到相同的IP:PORT)
  • (20)将masqRule加入到proxier.natRules中。
  • (21)向proxier.natRules中,加入set-xmark规则
writeLine(proxier.natRules, []string{"-A", string(KubeMarkMasqChain),"-j", "MARK", "--set-xmark", proxier.masqueradeMark,}...)
其中:
KubeMarkMasqChain:KUBE-MARK-MASQ
proxier.masqueradeMark:默认为0x4000/0x4000
# 可以通过该命令查询
iptables -t nat -L KUBE-MARK-MASQ
  • (22)构建activeNATChains map,其累积需要保存的NAT链
  • (23)构建replacementPortsMap,累积一旦更新完成,我们将保持打开的本地端口集
  • (24)构建空endpoints信息及endpointChains slice。
  • (25)置proxier.endpointChainsNumber为0, 通过遍历proxier.serviceMap,累加所有serviceName对应的endpointsMap长度,获取proxier.endpointChainsNumber值
  • (26)遍历service,为每个service构建规则
    • 1)获取svcInfo(包括了svc的基础信息,以及serviceNameString、servicePortChainName、serviceFirewallChainName、serviceLBChainName )

      • a)servicePortChainName: KUBE-SVC-svc端口名字(namespace + name + portname)及协议Hash
      • b)serviceFirewallChainName:KUBE-FW-svc端口名字及协议Hash
      • c)serviceLBChainName: KUBE-XLB–svc端口名字及协议Hash
    • 2)判读clusterIP类型
    • 3)获取svcInfo协议类型(TCP or UDP or SCP)
    • 4)获取svcName对应的endpoints,记为allEndpoints
    • 5)基于allEndpoings,获取hasEndpoints属性
    • 6)如果有endpoints,则判断当前svcInfo对应的svcChain是否在existingNATChains中,如果时,将对应行信息写入到proxier.natChains中。否则构建对应svcChainLine.并在activeNATChains中记录该chain( existingNATChains中的行信息基于iptables-save -t nat生成,比如:KUBE-SVC-5SLFW5ITLOQQHB35 - [0:0])
    • 7)如果service是LB或NodePort类型Service,且service.Spec.ExternalTrafficPolicy为Local,即转发时,只允许转发给本地容器,保证源IP地址不变,则判断svcInfo对应serviceLBChainName是否已经存在,如果存在则将行信息写入到proxier.natChains,否则构建新的写入到proxier.natChains,同时在activeNATChains中记录该chain。
    • 8)如果有endpoints,构建该svc的masquerad规则(追加到KUBE-SERVICES链,匹配规则为目的IP为ClusterIP,协议为svc协议,端口为svc端口,查询命令iptables -t nat -L KUBE-SERVICES)及跳转SVC链规则.
      • a) 如果proxier.masqueradeAll为真,则在proxier.natRules中加入该svc的masquerad规则,目的链为KUBE-MARK-MASQ.
      • b) 如果不为真,且kube-proxy在启动时配置了启动参数–cluster-cidr(cluster-cidr主要用于帮助 kube-proxy 区分内外流量。当值为空时,kube-proxy认为所有流量都是内部流量,不做SNAT(MASQ)即c情况。当值非空时,来自 cluster-cidr 网络(即 Pod网络)的流量被当成内部流量,访问 Service 时不做 SNAT(MASQ),来自其他网络的流量被当成外部流量,访问 Service 时需要做 SNAT(MASQ),如果配置了masqueradeAll,则所有流量都要做SNAT),则在匹配规则中额外添加源地址不允许是proxier.clusterCIDR(该规则将非cluster IP流量masquerades到一个service VIP。这么做可以通过为Sevice Ranage建立静态路由,将流量路由到任意节点,而该节点将会为你连接到Service内。因为可能会跳离节点,因此需要在这里masquerade。当我们支持"Local"策略时,应该更新它)。目的链为KUBE-MARK-MASQ**
      • c)往proxier.natRules追加KUBE-SVC-HASH(hash值基于NamespacedSVCName、PortName、Protocol计算得到)
      • d)如果不为真,且kube-proxy在启动时没有配置启动参数–cluster-cidr,则目的链为svcChain(这种情况认为所有的流量都是内部流量,不做MASQ)
    • 9)如果没有endpoints,则往proxier.filterRules的KUBE-SERVICES链中追加REJECT规则
    • 10)遍历svcInfo中的外部IP地址。(目前我们没有使用这个字段)
      • A)如果该IP地址不是本地系统设备上的某个地址,则打印日志信息。
      • B)如果该IP地址是本地系统设备上的某个地址,且协议类型不为SCTP。
        • a)构建LocalPort lb(IP、Port、Protocol)
        • b)如果proxier.portsMap中存在该端口,更新replacementPortsMap[lb] = proxier.portsMap[lp]
        • c)如果不存在,则在本地打开端口,返回得到socket,replacementPortsMap[lb] = socket
      • C)如果有endpoints。则构建nat规则,追加到KUBE-SERVICES链表,匹配协议为svc协议,目的端口为svc端口,目的链为KUBE-MARK-MASQ。该规则负责SNAT到external IPs。构建externalTrafficOnly规则,其在上一个规则基础上增加了匹配规则,其允许非来自bridge或本地进程的流量z转发到service,即外部来的流量。构建dstLocalOnlyArgs规则,其在第一个规则基础上,增加匹配规则,允许被识别为本地ip的外部ip绑定的流量留在本地。
      • D)如果没有,则往proxier.filterRules的KUBE-SERVICES链中追加REJECT规则。
    • 11)遍历svcInfo中的LoadBalancerIPStrings,对ingress不为空的svcInfo做处理。
      • A)如果有后端。则判断对应FirewallChain名字是否在existingNATChains中存在,如果存在则写入到proxier.natChains中。否则则新建后写入(:KUBE-FW-5SQY75UPE7NMO5UB - [0:0]).并更新activeNATChains[fwChain] = true

        • b)构建nat规则,在KUBE-SERVICE链表中,追加匹配LB协议、目的IP、目的端口时,跳转入fwChain的规则。
        • c)chosenChain := svcXlbChain
        • d)构建nat规则,在fwChain中追加。如果svcInfo不是OnlyNodeLocalEndpoints,则目标链为KUBE-MARK-MASQ。chosenChain = svcChain。
        • e)如果svcInfo.LoadBalancerSourceRanges长度为0(该字段确定了可以访问LB的客户端IP),则目标链为choseChine
        • f)如果svcInfo.LoadBalancerSourceRanges长度不为0,则匹配规则为源来自oadBalancerSourceRanges,目标地址为chosenChain.
        • g)添加默认丢弃规则,KUBE-MARK-DROP
      • B)如果没有,则往proxier.filterRules的KUBE-SERVICES链中追加REJECT规则。
      1. 如果svc的NodePort不为0
      • A)基于配置,在本机设备上找到所有在proxier.nodePortAddresses(–nodeport-addresses启动参数决定)内的address。
      • B)遍历address,基于每个Address生成LocalPort对象(IP=address,Port=svcInfo.NodePort,protocol=svcInfo.protocol)。
      • C)遍历这些LocalPort对象,
        • a)如果其存在与proxier.portsMap[lp],则replacementPortsMap[lp] = proxier.portsMap[lp]。
        • b)否则当协议类型不是ProtocolSCTP时,构建对应socket,同时对于udp清理链接状态.更新replacementPortsMap[lp] = socket
        • c)如果具有endpoints,则往KUBE-NODEPORTS链添加规则。如果svcInfo不是OnlyNodeLocalEndpoints,则往proxier.natRules添加跳转到KUBE-MARK-MASQ及svcChain.否则对于源IP地址为回环地址的数据包,配置JUMP为KUBE-MARK-MASQ,其它jump到svcXlbChain.
        • c)如果没有,则往proxier.filterRules的KUBE-SERVICES链中追加REJECT规则。
      • 13)如果没有endpoints,则处理下一个svcInfo
      • 14)刷新endpoints及endpointChain链表
      • 15)遍历当前svcInfo对应的endpoints
        • A)获取epInfo(协议,chainName=KUBE-SEP-svcName+protocol+endpoint的哈希值)
        • B)将epInfo加入到endpoints slice中
        • C)构建endpointChain,并将其加入到endpointChains slice中
        • D)如果它已经存在于existingNATChains中则将其对应行信息写入到proxier.natChains中,否则构建新Chain,写入其中(:KUBE-SEP-ZM2K5WAG2QZ6VDLK - [0:0]).置activeNATChains[endpointChain] = true
      • 16)如果svcInfo.SessionAffinityType == ServiceAffinityClientIP(基于客户端源IP的亲和性)。遍历endpointChains slice,为每个endpointsChain在svcChain中添加会话保持规则。
      • 17)遍历endpointChains
        • A) 如果svcInfo.OnlyNodeLocalEndpoints为true,且当前endpoint在本机器上。则将其加入到localEndpoints,并将对应endpoinChain加入到localEndpointChains
        • B)获取EP.IP, 如果IP不存在,则跳过处理
        • C)基于endpointChain当前索引,计算其概率,构建nat规则,加入到endpointChain中(-A KUBE-SVC-PRPXIF4GQR5KDAOQ -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-N66C6CFW2S6AAC7V)。如果是最后一个endpoint,则不需要-m statistic --mode random --probability 0.50000000000配置。
        • D)往endpointChain中添加nat规则,对于源IP地址是epIP地址的数据包,跳转进入KUBE-MARK-MASQ。其用于用SNAT处理返回到发起者的通讯
        • E)如果具有客户端亲和性,则更新参数,加入亲和性配置
        • F)更新参数加入DNAT动作,往endpointChain中加入该DNAT规则(如果有客户端IP亲和性,相当于在这个规则里多加了-m recent --name endpointChain --set参数。不加情况下,规则可能为DNAT tcp – anywhere anywhere tcp to:172.20.0.22:8443)。
        • G)如果!svcInfo.OnlyNodeLocalEndpoints(),则处理下一个svcInfo
        • H)clusterCIDR大于0时,svcXlbChain中加入nat规则,对于源来自clusterCIDR的,跳入svcChain处理。(重定向尝试访问LB IP的pod,访问ClusterIP)
        • I)构建并添加重定向规则,对于externalTrafficPolicy=Local的svc,将所有src-type=Local->LB IP的数据包重顶下到svcChain.
        • J)如果本地的localEndpointChains等于0,则往svcXlbChain中添加规则,跳转到KUBE-MARK-DROP,即该机器上没有这个svc的pod,而这个pod又不允许转发到别的节点,因此就丢弃了。
        • K)否则,在svcXlbChain 为local endpoints添加robability filter rules
  • 27)遍历existingNATChains,对于非激活链,且由kube-proxy构建的,往proxier.natRules中添加删除规则
  • 28)如果在本机设备存在NodePort地址,则为这些地址在KUBE-SERVICES链中添加nat规则,对于目的地址类型为本地的地址,跳入KUBE-NODEPORTS处理。
  • 29)构建filter规则,往KUBE-FORWARD链中增加状态为INALID的数据包的丢包处理规则
  • 30)构建filter规则,往KUBE-FORWARD链中增加mark为proxier.masqueradeMark的数据包的接收处理规则。
  • 31)如果proxier.clusterCIDR长度大于0。
    • A)构建filter规则,往KUBE-FORWARD链中添加,对于源IP是proxier.clusterCIDR,或者与其相关的,数据包Accept规则。
    • B)构建filter规则,往KUBE-FORWARD链中添加,对于目的IP是proixer.clusterCIDR,或者与其相关的,数据包Accept规则。
  • 32)清理proxier.iptablesData,往其中写入proxier.filterChains.Bytes()、proxier.filterRules.Bytes()、proxier.natChains.Bytes()、proxier.natRules.Bytes()。
  • 33)重新存储iptables data
  • 34)更新指标及健康检查信息
  • 35)清理无效UDP链接信息。

6 数据包路径及链位置

 kube-proxy在开始下发svc\ep相关规则前,会现往filter表追加KUBE-SERVICES、KUBE-EXTERNAL-SERVICES、KUBE-FORWARD链跳转规则,会往nat表追加入KUBE-SERVICES、KUBE-NODEPORTS、KUBE-POSTROUTING、KUBE-MARK-MASQ链跳转规则。并为KUBE-POSTROUTING及KUBE-MARK-MASQ链添加默认规则。

# KUBE-POSTROUTING
# Add Rule
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING# Show Rule
iptables -t nat -L KUBE-POSTROUTING# Show Result
Chain KUBE-POSTROUTING (1 references)
target     prot opt source               destination
MASQUERADE  all  --  anywhere             anywhere             /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000-----------------------------------------------------------------------------------# KUBE-MARK-MASQ
# Add Rule
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE# Show Rule
iptables -t nat -L KUBE-MARK-MASQ# Show Result
Chain KUBE-MARK-MASQ (63 references)
target     prot opt source               destination
MARK       all  --  anywhere             anywhere             MARK or 0x4000

6.1 PREROUTING

6.1.1 PREROUTING ->KUBE-SERVICES

NAT表的PREROUTING链中加入了,KUBE-SERVICES链跳转规则。对于所有的数据包,跳转入KUBE-SERVICES链处理

# Add Rule
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES# Show Rule
iptables -t nat -L PREROUTING# Show Result
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */

6.1.2 KUBE-SERVICES

数据包经过NAT表PREROUTING链中规则匹配,进入KUBE-SERVICE链处理

6.1.2.1 ClusterIP

6.1.2.1.1 KUBE-SERVICES->KUBE-MARK-MASQ

kube-proxy会遍历每个svcInfo,根据clusterIP、启动参数–cluster-cidr及–masquerade-all来配置, jump到KUBE-MARK-MASQ链的匹配规则。

  • (1) 配置了–masquerade-all,认为所有流量都是外部流量,都要打标签
# Add Rule
-A KUBE-SERVICES -d 10.68.81.30/32 -p tcp -m comment --comment "demo/s-test-mq:tcp-5672 cluster IP" -m tcp --dport 5672 -j KUBE-MARK-MASQ
  • (2)–cluster-cidr不为空,认为–cluster-cidr指定网段为内部网段不需要打标签,其它流量为外部流量需要打标签
# Add Rule
-A KUBE-SERVICES ! -s 172.20.0.0/16 -d 10.68.81.30/32 -p tcp -m comment --comment "demo/s-test-mq:tcp-5672 cluster IP" -m tcp --dport 5672 -j KUBE-MARK-MASQ
  • (3)–cluster-cidr为空,认为所有流量都是内部流量,都不打标签,在POSTROUTING时,不需要SNAT

KUBE-MARK-MASQ链内规则会为进入链的所有数据包打上0x4000/0x4000标签(进行完此处理动作后,将会继续比对其它规则)

# Show Rule
iptables -t nat -L KUBE-MARK-MASQ# Show Result
Chain KUBE-MARK-MASQ (63 references)
target     prot opt source               destination
MARK       all  --  anywhere             anywhere             MARK or 0x4000
6.1.2.1.2 KUBE-SERVICES->KUBE-SVC-HASH

kube-proxy会在KUBE-SERVICES中为每个具有clusterIP的 svcPort构建KUBE-SVC-HASH跳转规则,将访问svcPort的数据包,导入到KUBE-SVC-HASH链中

# Add Rule
-A KUBE-SERVICES -d 10.68.249.16/32 -p tcp -m comment --comment "demo/netutil-2:tcp-8081 cluster IP" -m tcp --dport 8081 -j KUBE-SVC-QUZXUNUIPD3MZETI# Show Rule
KUBE-SVC-QUZXUNUIPD3MZETI  tcp  --  anywhere             10.68.249.16         /* demo/netutil-2:tcp-8081 cluster IP */ tcp dpt:tproxy

6.1.2.2 External IP

对于External IP,只有当前物理机上设备有这个地址时,kube-proxy才会下发规则.kube-proxy首先在本地使用External IP及svcPort、Protocol打开一个端口,然后下发规则。

6.1.2.2.1 KUBE-SERVICES->KUBE-MARK-MASQ

kube-proxy会在KUBE-SERVICES内,为每个External IP添加KUBE-MARK-MASQ跳转规则,对于目的地址为External IP的数据包丢入KUBE-MARK-MASQ打mark

# 查询
iptables-save -t nat | grep external # Add rule
-A KUBE-SERVICES -d 公网IP地址 /32 -p tcp -m comment --comment "demo/hk-nginx-hello:tcp-80 external IP" -m tcp --dport 80 -j KUBE-MARK-MASQ# 查询
iptables -t nat  -L KUBE-SERVICES | grep "external IP" | grep MARK# 结果
KUBE-MARK-MASQ  tcp  --  anywhere  地址这里显示的主机名  /* demo/hk-nginx-hello:tcp-80 external IP */ tcp dpt:http
6.1.2.2.2 KUBE-SERVICES->KUBE-SVC-HASH

kube-proxy会在KUBE-SERVICES内,为每个External IP添加KUBE-SVC-HASH跳转规则

# Add rule
-A KUBE-SERVICES -d 公网IP地址 /32 -p tcp -m comment --comment "demo/hk-nginx-hello:tcp-80 external IP" -m tcp --dport 80 -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-UEOQSLEZ4LUM4H7G
-A KUBE-SERVICES -d 公网IP地址 /32 -p tcp -m comment --comment "demo/hk-nginx-hello:tcp-80 external IP" -m tcp --dport 80 -m addrtype --dst-type LOCAL -j KUBE-SVC-UEOQSLEZ4LUM4H7G# show ruleiptables -t nat  -L KUBE-SERVICES | grep "external IP" | grep -v MARK# 非本地请求,转发到svc
KUBE-SVC-UEOQSLEZ4LUM4H7G  tcp  --  anywhere  地址这里显示的主机名  /* demo/hk-nginx-hello:tcp-80 external IP */ tcp dpt:http PHYSDEV match ! --physdev-is-in ADDRTYPE match src-type !LOCAL# 本地请求转发到svc
KUBE-SVC-UEOQSLEZ4LUM4H7G  tcp  --  anywhere  地址这里显示的主机名  /* demo/hk-nginx-hello:tcp-80 external IP */ tcp dpt:http ADDRTYPE match dst-type LOCAL

6.1.2.3 LB IP

6.1.2.3.1 KUBE-SERVICES->KUBE-FW-HASH

kube-proxy会为每个具有LB IP的svcPort构建KUBE-FW-HASH跳转规则,将访问LBIP:svcPort(协议+端口)的数据包,导入到KUBE-FW-HASH链中

# Add Rule
-A KUBE-SERVICES -d 公网IP地址 /32 -p tcp -m comment --comment "demo/netutil-2:tcp-8081 loadbalancer IP" -m tcp --dport 8081 -j KUBE-FW-QUZXUNUIPD3MZETI# Show Rule
KUBE-FW-QUZXUNUIPD3MZETI  tcp  --  anywhere             公网IP地址       /* demo/netutil-2:tcp-8081 loadbalancer IP */ tcp dpt:tproxy
6.1.2.3.2 KUBE-FW-HASH->KUBE-MARK-MASQ

对于进入KUBE-FW-HASH链的所有数据包进入KUBE-MARK-MASQ,打上0x4000/0x4000标记

6.1.2.3.3 KUBE-FW-HASH->KUBE-SVC-HASH

打完MASQ的数据包,进入链表KUBE-SVC-QUZXUNUIPD3MZETI处理(如果这个时候有后端,会把数据包按概率丢给链KUBE-SVC-QUZXUNUIPD3MZETI-HASH处理)

# Show Rule
iptables -t nat -L KUBE-FW-QUZXUNUIPD3MZETI# Show Rule
Chain KUBE-FW-QUZXUNUIPD3MZETI (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  anywhere             anywhere             /* demo/netutil-2:tcp-8081 loadbalancer IP */
KUBE-SVC-QUZXUNUIPD3MZETI  all  --  anywhere             anywhere             /* demo/netutil-2:tcp-8081 loadbalancer IP */
KUBE-MARK-DROP  all  --  anywhere             anywhere             /* demo/netutil-2:tcp-8081 loadbalancer IP */
6.1.2.3.4 KUBE-FW-HASH->KUBE-MARK-DROP

对于没有endpoint的数据包,不会在KUBE-SVC-HASH中匹配,进入KUBE-MARK-DROP链表处理,会打上0x8000/0x8000标记(这些数据包会在filter被过滤丢弃)

6.1.2.4 NodePort

6.1.2.4.1 KUBE-SERVICES->KUBE-NODEPORTS

kube-proxy会在KUBE-SERVICES链中构建KUBE-NODEPORTS链,将不匹配ClusterIP、LB规则的数据包导入到KUBE-NODEPORTS链中处理

iptables -t nat -L KUBE-SERVICES| grep KUBE-NODEPORTS
6.1.2.4.2 KUBE-NODEPORTS->KUBE-MARK-MASQ

kube-proxy在KUBE-NODEPORTS中添加规则,匹配目标端口是否为NodePort,如果是,将数据包导入KUBE-NODEPORTS,打上MASQ标记

6.1.2.4.3 KUBE-NODEPORTS->KUBE-SVC-HASH

kub-proxy在KUBE-NODEPORTS中添加规则,匹配目标端口是否为NodePort,如果是将数据包导入到KUBE-SVC-HASH链处理

iptables -t nat -L KUBE-NODEPORTS

6.1.2.5 KUBE-SVC-HASH -> KUBE-SEP-HASH

kube-proxy会在KUBE-SVC-HASH链内为svcPort的每个endpoint构建基于概率的 KUBE-SEP-HASH跳转规则,并为每个endpoint构建KUBE-SEP-HASH规则

# Add Rule
-A KUBE-SVC-QUZXUNUIPD3MZETI -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-AMWXJCMBQ4RG26Q5
-A KUBE-SVC-QUZXUNUIPD3MZETI -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-2OQUDCMHKG5JUGZU
-A KUBE-SVC-QUZXUNUIPD3MZETI -j KUBE-SEP-Z2UJWNX76VNMX7FW# Rule show
iptables -t nat -L KUBE-SVC-QUZXUNUIPD3MZETI# Show Result
Chain KUBE-SVC-QUZXUNUIPD3MZETI (2 references)
target     prot opt source               destination
KUBE-SEP-AMWXJCMBQ4RG26Q5  all  --  anywhere             anywhere             statistic mode random probability 0.33333333349
KUBE-SEP-2OQUDCMHKG5JUGZU  all  --  anywhere             anywhere             statistic mode random probability 0.50000000000
KUBE-SEP-Z2UJWNX76VNMX7FW  all  --  anywhere             anywhere

6.1.2.6 KUBE-SEP-HASH

  • (1)对于源IP地址为172.20.4.10的数据包进行MARK。endPoint 访问SVC的情况。
  • (2)对数据包进行DNAT,将目的IP地址从Service CluterIP NAT为Pod IP
# Add Rule
-A KUBE-SEP-AMWXJCMBQ4RG26Q5 -s 172.20.4.10/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-AMWXJCMBQ4RG26Q5 -p tcp -m tcp -j DNAT --to-destination 172.20.4.10:8091# Show Rule
iptables -t nat -L KUBE-SEP-AMWXJCMBQ4RG26Q5# Show Result
Chain KUBE-SEP-AMWXJCMBQ4RG26Q5 (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  172.20.4.10          anywhere
DNAT       tcp  --  anywhere             anywhere             tcp to:172.20.4.10:8091

6.2 INPUT

FILTER 表的INPUT链中配置了KUBE-SERVICES, KUBE-EXTERNAL-SERVICES 跳转规则(KUBE-SERVICES未命中,再匹配KUBE-EXTERNAL-SERVICES)。

iptables -t filter -L INPUT

6.2.1 FILTER INPUT ->KUBE-SERVICES

拒绝目的地址为指定IP的数据包(这些IP对应的Service没有endpoints,这里的目的IP可能是Cluter IP、LB IP)

# Show Rules
iptables -t filter -L KUBE-SERVICES# Show Result
Chain KUBE-SERVICES (3 references)
target     prot opt source               destination
REJECT     tcp  --  anywhere             10.68.236.66         /* default/myservice: has no endpoints */ tcp dpt:ssh reject-with icmp-port-unreachable
REJECT     tcp  --  anywhere             10.68.121.211        /* demo/test-lb:tcp-80 has no endpoints */ tcp dpt:http reject-with icmp-port-unreachable
REJECT     tcp  --  anywhere             公网IP地址      /* demo/test-lb:tcp-80 has no endpoints */ tcp dpt:http reject-with icmp-port-unreachable
REJECT     tcp  --  anywhere             10.68.220.136        /* demo/test3:tcp-80 has no endpoints */ tcp dpt:http reject-with icmp-port-unreachable
REJECT     tcp  --  anywhere             公网IP地址       /* demo/test3:tcp-80 has no endpoints */ tcp dpt:http reject-with icmp-port-unreachable

6.2.2 FILTER INPUT ->KUBE-EXTERNAL-SERVICES

拒绝目的地址为指定External IP的数据包(这些IP对应的Service没有endpoints,这里的IP可能是External IP、Node IP)

iptables -t filter -L KUBE-EXTERNAL-SERVICES

6.3 FORWARD

FILTER 表的FORWARD链中配置了KUBE-FORWARD, KUBE-SERVICES 跳转规则(KUBE-FORWARD未命中,再匹配KUBE-SERVICES)。

iptables -t filter -L FORWARD

6.3.1 FORWARD-> KUBE-FORWARD

  • (1)丢弃处于状态处于INVALID的数据包,因为它可能会潜在导致不期望的链接重置
  • (2)转发所有打了0x4000/0x4000标记的数据包
  • (3)后两条规则确保在“kubernetes kubernetes forwarding rules"”接受的初始数据包之后的流量将被接受,尽可能具体地说,流量必须来源于或指向clusterCIDR(到/从一个pod), 即保证Pod流量可以被转发(该规则再配置了–cluster-cidr时配置)。
# Show Rules
iptables -t filter -L KUBE-FORWARD# Show Result
Chain KUBE-FORWARD (1 references)
target     prot opt source               destination
DROP       all  --  anywhere             anywhere             ctstate INVALID
ACCEPT     all  --  anywhere             anywhere             /* kubernetes forwarding rules */ mark match 0x4000/0x4000
ACCEPT     all  --  172.20.0.0/16        anywhere             /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             172.20.0.0/16        /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHE

6.3.2 FORWARD-> KUBE-SERVICES

拒绝目的地址为指定IP的数据包(这些IP对应的Service没有endpoints,这里的目的IP可能是Cluter IP、LB IP)

6.4 OUTPUT

NAT 表及Filter表中添加了KUBE-SERVICE链表跳转规则。数据包先过NAT链,再过Filter链。

6.4.1 NAT OUTPUT-> KUBE-SERVICES

所有数据包进入NAT表的KUBE-SERVICES链处理,KUBE-SERVICES链处理流程和6.1从NAT 表PreRouting 链进入NAT表一致。

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */

6.4.2 NAT OUTPUT-> KUBE-SERVICES

数据包进入Filter表的KUBE-SERVICES,KUBE-SERVICES链处理流程和6.2.1 Filter INPUT进入KUBE-SERVICES一致。

6.5 POSTROUTING

kube-proxy在NAT 表的POSTROUTING链中加入了KUBE-POSTROUTING跳转链表规则

-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING

6.5.1 NAT POSTROUTING -> KUBE-POSTROUTING

KUBE-POSTROUTING 链表中对于打了0x4000/0x4000标签的数据包,进行MASQUERADE,进行SNAT。

# Add Rule
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE# Show Rule
iptables -t nat -L KUBE-POSTROUTING# Show Result
Chain KUBE-POSTROUTING (1 references)
target     prot opt source               destination
MASQUERADE  all  --  anywhere             anywhere             /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000

7 总结

入包处理整体流程(进入主机)

  • (1)Nat表PreRouting、OutPut拦截所有数据包进入KUBE-SERVICE链处理
  • (2)对出包时需要SNAT的数据包打mark
  • (3)对数据包基于目的地址及目的端口进行匹配,进入KUBE-SVC-HASH链处理
  • (4)进入KUBE-SVC-HASH链,基于概率进入KUBE-SEP-HASH链,进行DNAT,将目的地址转换为RIP:RPORT

出包处理整体流程(出主机)

  • (1)Nat表PostRouting拦截所有进入KUBE-POSTROUTING链处理
  • (2)KUBE-POSTROUTING链对于打了标记的数据包,进行SNAT处理

kube-proxy源码阅读(iptables实现)相关推荐

  1. mybatis源码阅读

    说下mybatis执行一个sql语句的流程 执行语句,事务等SqlSession都交给了excutor,excutor又委托给statementHandler SimpleExecutor:每执行一次 ...

  2. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  3. koa源码阅读之koa-compose/application.js

    koa源码阅读之koa-compose/application.js koa-Compose 为了理解方便特地把注释也粘进来 //这英语.我也来翻译一波 //大概就是把所有的中间件组合返回一个完整大块 ...

  4. DotText源码阅读(7) --Pingback/TrackBack

    DotText源码阅读(7) --Pingback/TrackBack 博客这种服务的区别于论坛和所谓文集网站,很大程度上我认为是由于pingback/trackback的存在,使得博客这种自媒体有可 ...

  5. mybatis源码阅读(八) ---Interceptor了解一下

    转载自  mybatis源码阅读(八) ---Interceptor了解一下 1 Intercetor MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis允许 ...

  6. mybatis源码阅读(四):mapper(dao)实例化

    转载自   mybatis源码阅读(四):mapper(dao)实例化 在开始分析之前,先来了解一下这个模块中的核心组件之间的关系,如图: 1.MapperRegistry&MapperPro ...

  7. mybatis源码阅读(一):SqlSession和SqlSessionFactory

    转载自  mybatis源码阅读(一):SqlSession和SqlSessionFactory 一.接口定义 听名字就知道这里使用了工厂方法模式,SqlSessionFactory负责创建SqlSe ...

  8. 基于lis3dh的简易倾角仪c源码_开源网关apisix源码阅读和最佳实践

    大家应该都接手过这种项目,前人找一个开源软件改一改,发上线. 我这里便曾经遇到过类似的问题. 随着需求的增加,各种维护人员东改改西改改,原来的开源项目被改的面目全非,再也无法和上游合并. 甚至TLS协 ...

  9. Rpc框架dubbo-client(v2.6.3) 源码阅读(二)

    接上一篇 dubbo-server 之后,再来看一下 dubbo-client 是如何工作的. dubbo提供者服务示例, 其结构是这样的! dubbo://192.168.11.6:20880/co ...

最新文章

  1. 关于中台的思考与尝试
  2. R语言_基本统计分析
  3. android FloatingActionButton
  4. hdu5247找连续数(打表)
  5. Clipboard还能玩出花
  6. SAP内部订单使用实例
  7. linux用户命令权限管理,Linux命令-用户、权限管理
  8. 批量获取域名解析地址socketthread
  9. java contains 通配符_java删除文件支持通配符
  10. js控制语句练习(回顾)
  11. 微信公众平台消息接口开发(6)电话号码链接与网址链接
  12. 【Demo 0015】坐标系
  13. mysql2005中文版_SQL Server 2005 sp4 简体中文完整免费版下载
  14. python爬虫入门——爬取淘宝商品评论信息
  15. 详解Unity中的摄像机及实例应用
  16. JMeter下载及使用
  17. serialVersionUID快捷键
  18. 微信直接下载app的解决方案
  19. CoreOS在儿童节发布了自己的分布式存储Torus
  20. 云时代,最好用的MySQL客户端工具推荐

热门文章

  1. 计算机应用能力PPT,计算机应用能力的技术全集.ppt
  2. Gmail一登录就显示“您浏览器的 cookie 功能被禁用,请启用此功能”
  3. 将不可复制、不可打印的电子书批量截图生成PDF
  4. 1.4 SIG MESH协议各个层的作用
  5. 网线传输速度测试_如何测试网线的优劣
  6. 分享一个仿ios风格的AlertDialog的实现
  7. python 捕获鼠标点击事件_Python捕捉和模拟鼠标事件的方法
  8. 解决两张表关联,一张表插入一条新数据,另外一张表也跟着插入一条新数据问题
  9. linux gdb打印内存命令,linux下gdb常用的调试命令--by流氓兔斯基
  10. 全国研究生数学建模比赛经验