二进制部署kubernetes时,大多数使用的是推荐的flannel网络插件,但是部署后我们会发现一个问题,kubelet启动没有指定flannel ,我们也没有配置network-plugin,那么kubelet在创建pod时,究竟flannel如何参与容器的网络创建的。从配置中能看到的是flannel启动时,将本节点的网段传递了docker启动参数。docker启动后会以此设置docker0网桥的网段。
网络上很多没有说明具体的原因,有一篇文章倒是有提到。可以参考,但是没有本质解决我的疑问。
其实我们有以下几个疑问:
- flannel这个插件的工作原理,为什么network-plugin没有体现flannel, flannel最终实现跨节点的pod通信?
- kubelet对network-plugin参数的默认值是多少?针对该配置,对pod的网络流程的影响?
(一)先回答第一个问题:
flannel在cni中是一个相对于calico等第三方cni比较特殊的一个插件,特殊在哪呢?https://github.com/containernetworking/plugins中也有说明,flannel属于Meta: other plugins类,不能独立工作,需要有Main类支持才可以。而flannel本质是调用Main类中的bridge,这个和docker 默认的NetworkMode是一致的。因此二进制部署的时候,kubelet没有指定flannel相关的配置,pod的网络也配好了,肯定是docker自己配置的。
所以二进制部署时,pod的Sandbox的网络不是flannel配置的,即创建infra容器,创建veth pair对,插网桥等动作是docker自己完成的。从docker inspect XX也能看到infra容器的NetworkMode配置为bridge。
所以这种方式部署的pod,其实跨节点通信部署使用了flannel,单节点内的pod网络,并没有flannel的参与!
(其实,flannel也可以使用cni的方式来部署,网络参考:https://github.com/containernetworking/cni, 这种方式,就是flannel-cni来调用bridge,而不是docker-engine调用bridge了)
(二)再来看第二个问题:
既然是docker自己配置的,那么kubelet一定会针对network-plugin这个配置进行区分。
先来看一个结构:
参考之前kubelet的文章https://mp.csdn.net/postedit/97369143
1. kubelet在启动时会初始化 我们重点关注下network-plugin的配置的作用。
KubeletFlags{
ContainerRuntimeOptions {
ContainerRuntime: "docker"
DockerEndpoint: "unix:///var/run/docker.sock"
PodSandboxImage: pauseXX
CNIBinDir: "/opt/cni/bin",
CNIConfDir: "/etc/cni/net.d",
NetworkPluginName string //---------------------注意这个就是network-plugin配置参数
}
RemoteRuntimeEndpoint = "unix:///var/run/dockershim.sock"
}
func NewMainKubelet(...crOptions *config.ContainerRuntimeOptions){
pluginSettings := dockershim.NetworkPluginSettings{
HairpinMode: kubeletconfiginternal.HairpinMode(kubeCfg.HairpinMode),
NonMasqueradeCIDR: nonMasqueradeCIDR,
PluginName: crOptions.NetworkPluginName,//----------这里
PluginConfDir: crOptions.CNIConfDir,
PluginBinDirString: crOptions.CNIBinDir,
MTU: int(crOptions.NetworkPluginMTU),
}
...
//最终传递到了DockerService 即ds中
case kubetypes.DockerContainerRuntime:
// Create and start the CRI shim running as a grpc server.
streamingConfig := getStreamingConfig(kubeCfg, kubeDeps, crOptions)
ds, err := dockershim.NewDockerService(kubeDeps.DockerClientConfig, crOptions.PodSandboxImage, streamingConfig,
&pluginSettings, runtimeCgroups, kubeCfg.CgroupDriver, crOptions.DockershimRootDirectory, !crOptions.RedirectContainerStreaming)
....
}
//在ds的初始化中,
func NewDockerService(){
...
// dockershim currently only supports CNI plugins.
pluginSettings.PluginBinDirs = cni.SplitDirs(pluginSettings.PluginBinDirString)
cniPlugins := cni.ProbeNetworkPlugins(pluginSettings.PluginConfDir, pluginSettings.PluginBinDirs)
cniPlugins = append(cniPlugins, kubenet.NewPlugin(pluginSettings.PluginBinDirs))
netHost := &dockerNetworkHost{
&namespaceGetter{ds},
&portMappingGetter{ds},
}
//划重点,
plug, err := network.InitNetworkPlugin(cniPlugins, pluginSettings.PluginName, netHost, pluginSettings.HairpinMode, pluginSettings.NonMasqueradeCIDR, pluginSettings.MTU)
if err != nil {
return nil, fmt.Errorf("didn't find compatible CNI plugin with given settings %+v: %v", pluginSettings, err)
}
ds.network = network.NewPluginManager(plug)
glog.Infof("Docker cri networking managed by %v", plug.Name()) //在log中能查看的到这句
}
//注意了,这里的NoopNetworkPlugin, 即kubernertes.io/no-op
// InitNetworkPlugin inits the plugin that matches networkPluginName. Plugins must have unique names.
func InitNetworkPlugin(plugins []NetworkPlugin, networkPluginName string, host Host, hairpinMode kubeletconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) (NetworkPlugin, error) {
if networkPluginName == "" {
// default to the no_op plugin
plug := &NoopNetworkPlugin{}
plug.Sysctl = utilsysctl.New()
if err := plug.Init(host, hairpinMode, nonMasqueradeCIDR, mtu); err != nil {
return nil, err
}
return plug, nil
}
pluginMap := map[string]NetworkPlugin{}
...
}
从上面可以看到 network-plugin参数最终因为我们部署的时候没有配置该配置项,即为空,ds.network 就是 no-op
2 ,如果说上一步是配置最终保存在ds.network中,即如果是没有配置network-plugin的话,就会是一个NoopNetworkManager。那么下面就是ds.network如何发挥作用的。
SyncPod
CreateSandbox
RunPodSandbox
createConfig = ds.makeSandboxDockerConfig
ds.applySandboxLinuxOptions
applySandboxSecurityContext
applySandboxSecurityContext
modifySandboxNamespaceOptions
modifyHostOptionsForSandbox
ds.client.CreateContainer(*createConfig)
//network就是ds.network,ds.network是什么呢?基于NoopNetworkPlugin,PluginName为“kubernetes.io/no-op”, 即最终hc.NetworkMode = "default"
func modifyHostOptionsForSandbox(nsOpts *runtimeapi.NamespaceOption, network *knetwork.PluginManager, hc *dockercontainer.HostConfig) {
if nsOpts.GetIpc() == runtimeapi.NamespaceMode_NODE {
hc.IpcMode = namespaceModeHost
}
if nsOpts.GetNetwork() == runtimeapi.NamespaceMode_NODE {
hc.NetworkMode = namespaceModeHost
return
}
if network == nil {
hc.NetworkMode = "default"
return
}
switch network.PluginName() {
case "cni":
fallthrough
case "kubenet":
hc.NetworkMode = "none"
default:
hc.NetworkMode = "default"
}
}
而hc.NetworkMode = "default" 这个参数将会传递给docker-engine,就是NetworkMode参数。
因此我们就有flannel二进制部署的时候,创建的infra的网络模式是bridge。