关联博客:kubernetes/k8s CSI 分析-容器存储接口分析https://xie.infoq.cn/article/5cd26c1b24c5665820411bb5a
kubernetes/k8s CRI 分析-容器运行时接口分析https://xie.infoq.cn/article/0d2bc16c22af9fc710b9f9c30
概述
kubernetes 的设计初衷是支持可插拔架构,从而利于扩展 kubernetes 的功能。在此架构思想下,kubernetes 提供了 3 个特定功能的接口,分别是容器网络接口 CNI、容器运行时接口 CRI 和容器存储接口 CSI。kubernetes 通过调用这几个接口,来完成相应的功能。
下面我们来对容器运行时接口 CNI 来做一下介绍与分析。
CNI 是什么
CNI,全称是 Container Network Interface,即容器网络接口。
CNI 是 K8s 中标准的调用网络实现的接口。Kubelet 通过这个标准的接口来调用不同的网络插件以实现不同的网络配置方式。
CNI 网络插件是一个可执行文件,是遵守容器网络接口(CNI)规范的网络插件。常见的 CNI 网络插件包括 Calico、flannel、Terway、Weave Net 等。
当 kubelet 选择使用 CNI 类型的网络插件时(通过 kubelet 启动参数指定),kubelet 在创建 pod、删除 pod 的时候,会调用 CNI 网络插件来做 pod 的构建网络和销毁网络等操作。
kubelet 的网络插件
kubelet 的网络插件有以下 3 种类型:
(1)CNI;
(2)kubenet;
(3)Noop,代表不配置网络插件。
这里主要对 kubelet 中 CNI 相关的源码进行分析。
CNI 架构
kubelet 创建/删除 pod 时,会调用 CRI,然后 CRI 会调用 CNI 来进行 pod 网络的构建/删除。
kubelet 构建 pod 网络的大致过程
(1)kubelet 先通过 CRI 创建 pause 容器(pod sandbox),生成 network namespace;
(2)kubelet 根据启动参数配置调用具体的网络插件如 CNI 网络插件;
(3)网络插件给 pause 容器(pod sandbox)配置网络;
(4)pod 中其他的容器都与 pause 容器(pod sandbox)共享网络。
kubelet 中 cni 相关的源码分析
kubelet 的 cni 源码分析包括如下几部分:
(1)cni 相关启动参数分析;
(2)关键 struct/interface 分析;
(3)cni 初始化分析;
(4)cni 构建 pod 网络分析;
(5)cni 销毁 pod 网络分析。
基于 tag v1.17.4
https://github.com/kubernetes/kubernetes/releases/tag/v1.17.4
1.kubelet 组件 cni 相关启动参数分析
kubelet 组件 cni 相关启动参数相关代码如下:
// pkg/kubelet/config/flags.go
func (s *ContainerRuntimeOptions) AddFlags(fs *pflag.FlagSet) {
...
// Network plugin settings for Docker.
fs.StringVar(&s.NetworkPluginName, "network-plugin", s.NetworkPluginName, fmt.Sprintf("<Warning: Alpha feature> The name of the network plugin to be invoked for various events in kubelet/pod lifecycle. %s", dockerOnlyWarning))
fs.StringVar(&s.CNIConfDir, "cni-conf-dir", s.CNIConfDir, fmt.Sprintf("<Warning: Alpha feature> The full path of the directory in which to search for CNI config files. %s", dockerOnlyWarning))
fs.StringVar(&s.CNIBinDir, "cni-bin-dir", s.CNIBinDir, fmt.Sprintf("<Warning: Alpha feature> A comma-separated list of full paths of directories in which to search for CNI plugin binaries. %s", dockerOnlyWarning))
fs.StringVar(&s.CNICacheDir, "cni-cache-dir", s.CNICacheDir, fmt.Sprintf("<Warning: Alpha feature> The full path of the directory in which CNI should store cache files. %s", dockerOnlyWarning))
fs.Int32Var(&s.NetworkPluginMTU, "network-plugin-mtu", s.NetworkPluginMTU, fmt.Sprintf("<Warning: Alpha feature> The MTU to be passed to the network plugin, to override the default. Set to 0 to use the default 1460 MTU. %s", dockerOnlyWarning))
...
}
复制代码
cni 相关启动参数的默认值在NewContainerRuntimeOptions
函数中设置。
// cmd/kubelet/app/options/container_runtime.go
// NewContainerRuntimeOptions will create a new ContainerRuntimeOptions with
// default values.
func NewContainerRuntimeOptions() *config.ContainerRuntimeOptions {
dockerEndpoint := ""
if runtime.GOOS != "windows" {
dockerEndpoint = "unix:///var/run/docker.sock"
}
return &config.ContainerRuntimeOptions{
ContainerRuntime: kubetypes.DockerContainerRuntime,
RedirectContainerStreaming: false,
DockerEndpoint: dockerEndpoint,
DockershimRootDirectory: "/var/lib/dockershim",
PodSandboxImage: defaultPodSandboxImage,
ImagePullProgressDeadline: metav1.Duration{Duration: 1 * time.Minute},
ExperimentalDockershim: false,
//Alpha feature
CNIBinDir: "/opt/cni/bin",
CNIConfDir: "/etc/cni/net.d",
CNICacheDir: "/var/lib/cni/cache",
}
}
复制代码
下面来简单分析几个比较重要的 cni 相关启动参数:
(1)--network-plugin
:指定要使用的网络插件类型,可选值cni
、kubenet
、""
,默认为空串,代表 Noop,即不配置网络插件(不构建 pod 网络)。此处配置值为cni
时,即指定 kubelet 使用的网络插件类型为cni
。
(2)--cni-conf-dir
:CNI 配置文件所在路径。默认值:/etc/cni/net.d
。
(3)--cni-bin-dir
:CNI 插件的可执行文件所在路径,kubelet 将在此路径中查找 CNI 插件的可执行文件来执行 pod 的网络操作。默认值:/opt/cni/bin
。
2.关键 struct/interface 分析
interface NetworkPlugin
先来看下关键的 interface:NetworkPlugin
。
NetworkPlugin interface 声明了 kubelet 网络插件的一些操作方法,不同类型的网络插件只需要实现这些方法即可,其中最关键的就是SetUpPod
与TearDownPod
方法,作用分别是构建 pod 网络与销毁 pod 网络,cniNetworkPlugin
实现了该 interface。
// pkg/kubelet/dockershim/network/plugins.go
// NetworkPlugin is an interface to network plugins for the kubelet
type NetworkPlugin interface {
// Init initializes the plugin. This will be called exactly once
// before any other methods are called.
Init(host Host, hairpinMode kubeletconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) error
// Called on various events like:
// NET_PLUGIN_EVENT_POD_CIDR_CHANGE
Event(name string, details map[string]interface{})
// Name returns the plugin's name. This will be used when searching
// for a plugin by name, e.g.
Name() string
// Returns a set of NET_PLUGIN_CAPABILITY_*
Capabilities() utilsets.Int
// SetUpPod is the method called after the infra container of
// the pod has been created but before the other containers of the
// pod are launched.
SetUpPod(namespace string, name string, podSandboxID kubecontainer.ContainerID, annotations, options map[string]string) error
// TearDownPod is the method called before a pod's infra container will be deleted
TearDownPod(namespace string, name string, podSandboxID kubecontainer.ContainerID) error
// GetPodNetworkStatus is the method called to obtain the ipv4 or ipv6 addresses of the container
GetPodNetworkStatus(namespace string, name string, podSandboxID kubecontainer.ContainerID) (*PodNetworkStatus, error)
// Status returns error if the network plugin is in error state
Status() error
}
复制代码
struct cniNetworkPlugin
cniNetworkPlugin struct 实现了NetworkPlugin interface
,实现了SetUpPod
与TearDownPod
等方法。
// pkg/kubelet/dockershim/network/cni/cni.go
type cniNetworkPlugin struct {
network.NoopNetworkPlugin
loNetwork *cniNetwork
sync.RWMutex
defaultNetwork *cniNetwork
host network.Host
execer utilexec.Interface
nsenterPath string
confDir string
binDirs []string
cacheDir string
podCidr string
}
复制代码
struct PluginManager
struct PluginManager 中的plugin
属性是interface NetworkPlugin
类型,可以传入具体的网络插件实现,如cniNetworkPlugin struct
。
// pkg/kubelet/dockershim/network/plugins.go
// The PluginManager wraps a kubelet network plugin and provides synchronization
// for a given pod's network operations. Each pod's setup/teardown/status operations
// are synchronized against each other, but network operations of other pods can
// proceed in parallel.
type PluginManager struct {
// Network plugin being wrapped
plugin NetworkPlugin
// Pod list and lock
podsLock sync.Mutex
pods map[string]*podLock
}
复制代码
struct dockerService
struct dockerService 其实在 CRI 分析的博文部分有做过详细分析,可以去回顾一下,下面再简单做一下介绍。
struct dockerService 实现了 CRI shim 服务端的容器运行时接口以及容器镜像接口,所以其代表了 dockershim(kubelet 内置的 CRI shim)的服务端。
struct dockerService 中的network
属性是struct PluginManager
类型,在该结构体初始化时会将具体的网络插件结构体如struct cniNetworkPlugin
存储进该属性。
创建 pod、删除 pod 时会根据dockerService
结构体的network
属性里面存储的具体的网络插件结构体,去调用某个具体网络插件(如cniNetworkPlugin
)的SetUpPod
、TearDownPod
方法来构建 pod 的网络、销毁 pod 的网络。
// pkg/kubelet/dockershim/docker_service.go
type dockerService struct {
client libdocker.Interface
os kubecontainer.OSInterface
podSandboxImage string
streamingRuntime *streamingRuntime
streamingServer streaming.Server
network *network.PluginManager
// Map of podSandboxID :: network-is-ready
networkReady map[string]bool
networkReadyLock sync.Mutex
containerManager cm.ContainerManager
// cgroup driver used by Docker runtime.
cgroupDriver string
checkpointManager checkpointmanager.CheckpointManager
// caches the version of the runtime.
// To be compatible with multiple docker versions, we need to perform
// version checking for some operations. Use this cache to avoid querying
// the docker daemon every time we need to do such checks.
versionCache *cache.ObjectCache
// startLocalStreamingServer indicates whether dockershim should start a
// streaming server on localhost.
startLocalStreamingServer bool
// containerCleanupInfos maps container IDs to the `containerCleanupInfo` structs
// needed to clean up after containers have been removed.
// (see `applyPlatformSpecificDockerConfig` and `performPlatformSpecificContainerCleanup`
// methods for more info).
containerCleanupInfos map[string]*containerCleanupInfo
}
复制代码
3.cni 初始化分析
Kubelet 启动过程中针对网络主要做以下步骤,分别是探针获取当前环境的网络插件以及初始化网络插件(只有当容器运行时选择为内置 dockershim 时,才会做 CNI 的初始化操作,将 CNI 初始化完成后交给 dockershim 使用)。
cni 初始化的调用链:
main (cmd/kubelet/kubelet.go)-> NewKubeletCommand (cmd/kubelet/app/server.go)-> Run (cmd/kubelet/app/server.go)
-> run (cmd/kubelet/app/server.go)
-> RunKubelet (cmd/kubelet/app/server.go)-> CreateAndInitKubelet(cmd/kubelet/app/server.go)-> kubelet.NewMainKubelet(pkg/kubelet/kubelet.go)-> cni.ProbeNetworkPlugins & network.InitNetworkPlugin(pkg/kubelet/network/plugins.go)
调用链很长,这里直接进入关键的函数NewMainKubelet
进行分析。
NewMainKubelet
NewMainKubelet 函数中主要看到dockershim.NewDockerService
调用。
// pkg/kubelet/kubelet.go
// NewMainKubelet instantiates a new Kubelet object along with all the required internal modules.
// No initialization of Kubelet and its modules should happen here.
func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,...) {
...
switch containerRuntime {
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)
...
}
复制代码
这里对变量containerRuntime
值等于docker
时做分析,即 kubelet 启动参数--container-runtime
值为docker
,这时 kubelet 会使用内置的CRI shim
即dockershim
作为容器运行时,初始化并启动dockershim
。
其中,调用dockershim.NewDockerService
的作用是:新建并初始化dockershim
服务端,包括初始化 docker client、初始化 cni 网络配置等操作。
而其中 CNI 部分的主要逻辑为:
(1)调用cni.ProbeNetworkPlugins
:根据 kubelet 启动参数 cni 相关配置,获取 cni 配置文件、cni 网络插件可执行文件等信息,根据这些 cni 的相关信息来初始化cniNetworkPlugin
结构体并返回;
(2)调用network.InitNetworkPlugin
:根据 networkPluginName 的值(对应 kubelet 启动参数--network-plugin
),选择相应的网络插件,调用其Init()
方法,做网络插件的初始化操作(初始化操作主要是起了一个 goroutine,定时探测 cni 的配置文件以及可执行文件,让其可以热更新);
(3)将上面步骤中获取到的cniNetworkPlugin
结构体,赋值给dockerService struct
的network
属性,待后续创建 pod、删除 pod 时可以调用cniNetworkPlugin
的SetUpPod
、TearDownPod
方法来构建 pod 的网络、销毁 pod 的网络。
kubelet 对 CNI 的实现的主要代码:pkg/kubelet/network/cni/cni.go-SetUpPod/TearDownPod
(构建 Pod 网络和销毁 Pod 网络)
其中函数入参pluginSettings *NetworkPluginSettings
的参数值,其实是从 kubelet 启动参数配置而来,kubelet cni 相关启动参数在前面已经做了分析了,忘记的可以回头看一下。
// pkg/kubelet/dockershim/docker_service.go
// NewDockerService creates a new `DockerService` struct.
// NOTE: Anything passed to DockerService should be eventually handled in another way when we switch to running the shim as a different process.
func NewDockerService(config *ClientConfig, podSandboxImage string, streamingConfig *streaming.Config, pluginSettings *NetworkPluginSettings,
cgroupsName string, kubeCgroupDriver string, dockershimRootDir string, startLocalStreamingServer bool, noJsonLogPath string) (DockerService, error) {
...
ds := &dockerService{
client: c,
os: kubecontainer.RealOS{},
podSandboxImage: podSandboxImage,
streamingRuntime: &streamingRuntime{
client: client,
execHandler: &NativeExecHandler{},
},
containerManager: cm.NewContainerManager(cgroupsName, client),
checkpointManager: checkpointManager,
startLocalStreamingServer: startLocalStreamingServer,
networkReady: make(map[string]bool),
containerCleanupInfos: make(map[string]*containerCleanupInfo),
noJsonLogPath: noJsonLogPath,
}
...
// dockershim currently only supports CNI plugins.
pluginSettings.PluginBinDirs = cni.SplitDirs(pluginSettings.PluginBinDirString)
// (1)根据kubelet启动参数cni相关配置,获取cni配置文件、cni网络插件可执行文件等信息,根据这些cni的相关信息来初始化```cniNetworkPlugin```结构体并返回
cniPlugins := cni.ProbeNetworkPlugins(pluginSettings.PluginConfDir, pluginSettings.PluginCacheDir, pluginSettings.PluginBinDirs)
cniPlugins = append(cniPlugins, kubenet.NewPlugin(pluginSettings.PluginBinDirs, pluginSettings.PluginCacheDir))
netHost := &dockerNetworkHost{
&namespaceGetter{ds},
&portMappingGetter{ds},
}
// (2)根据networkPluginName的值(对应kubelet启动参数```--network-plugin```),选择相应的网络插件,调用其```Init()```方法,做网络插件的初始化操作(初始化操作主要是起了一个goroutine,定时探测cni的配置文件以及可执行文件,让其可以热更新)
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)
}
// (3)将上面步骤中获取到的```cniNetworkPlugin```结构体,赋值给```dockerService struct```的```network```属性,待后续创建pod、删除pod时可以调用```cniNetworkPlugin```的```SetUpPod```、```TearDownPod```方法来构建pod的网络、销毁pod的网络。
ds.network = network.NewPluginManager(plug)
klog.Infof("Docker cri networking managed by %v", plug.Name())
...
}
复制代码
先来看下pluginSettings
长什么样,其实是struct NetworkPluginSettings
,包含了网络插件名称、网络插件可执行文件所在目录、网络插件配置文件所在目录等属性,代码如下:
// pkg/kubelet/dockershim/docker_service.go
type NetworkPluginSettings struct {
// HairpinMode is best described by comments surrounding the kubelet arg
HairpinMode kubeletconfig.HairpinMode
// NonMasqueradeCIDR is the range of ips which should *not* be included
// in any MASQUERADE rules applied by the plugin
NonMasqueradeCIDR string
// PluginName is the name of the plugin, runtime shim probes for
PluginName string
// PluginBinDirString is a list of directiores delimited by commas, in
// which the binaries for the plugin with PluginName may be found.
PluginBinDirString string
// PluginBinDirs is an array of directories in which the binaries for
// the plugin with PluginName may be found. The admin is responsible for
// provisioning these binaries before-hand.
PluginBinDirs []string
// PluginConfDir is the directory in which the admin places a CNI conf.
// Depending on the plugin, this may be an optional field, eg: kubenet
// generates its own plugin conf.
PluginConfDir string
// PluginCacheDir is the directory in which CNI should store cache files.
PluginCacheDir string
// MTU is the desired MTU for network devices created by the plugin.
MTU int
}
复制代码
3.1 cni.ProbeNetworkPlugins
cni.ProbeNetworkPlugins 中主要作用为:根据 kubelet 启动参数 cni 相关配置,获取 cni 配置文件、cni 网络插件可执行文件等信息,根据这些 cni 的相关信息来初始化cniNetworkPlugin
结构体并返回。
其中看到plugin.syncNetworkConfig()
调用,主要作用是给cniNetworkPlugin
结构体的defaultNetwork
属性赋值。
// pkg/kubelet/dockershim/network/cni/cni.go
// ProbeNetworkPlugins : get the network plugin based on cni conf file and bin file
func ProbeNetworkPlugins(confDir, cacheDir string, binDirs []string) []network.NetworkPlugin {
old := binDirs
binDirs = make([]string, 0, len(binDirs))
for _, dir := range old {
if dir != "" {
binDirs = append(binDirs, dir)
}
}
plugin := &cniNetworkPlugin{
defaultNetwork: nil,
loNetwork: getLoNetwork(binDirs),
execer: utilexec.New(),
confDir: confDir,
binDirs: binDirs,
cacheDir: cacheDir,
}
// sync NetworkConfig in best effort during probing.
plugin.syncNetworkConfig()
return []network.NetworkPlugin{plugin}
}
复制代码
plugin.syncNetworkConfig()
主要逻辑:
(1)getDefaultCNINetwork()
:根据 kubelet 启动参数配置,去对应的 cni conf 文件夹下寻找 cni 配置文件,返回包含 cni 信息的 cniNetwork 结构体;
(2)plugin.setDefaultNetwork()
:根据上一步获取到的 cniNetwork 结构体,赋值给cniNetworkPlugin
结构体的defaultNetwork
属性。
// pkg/kubelet/dockershim/network/cni/cni.go
func (plugin *cniNetworkPlugin) syncNetworkConfig() {
network, err := getDefaultCNINetwork(plugin.confDir, plugin.binDirs)
if err != nil {
klog.Warningf("Unable to update cni config: %s", err)
return
}
plugin.setDefaultNetwork(network)
}
复制代码
getDefaultCNINetwork()
主要逻辑:
(1)在 cni 配置文件所在目录下,可以识别 3 种 cni 配置文件,分别是.conf
, .conflist
, .json
。
(2)调用sort.Strings()
将 cni 配置文件所在目录下的所有 cni 配置文件按照字典顺序升序排序。
(3)只取第一个读取到的 cni 配置文件,然后直接 return。所以就算在 cni 配置文件目录下配置了多个 cni 配置文件,也只会有其中一个最终生效。
(4)调用cniConfig.ValidateNetworkList()
,校验 cni 可执行文件目录下是否存在对应的可执行文件。
// pkg/kubelet/dockershim/network/cni/cni.go
func getDefaultCNINetwork(confDir string, binDirs []string) (*cniNetwork, error) {
files, err := libcni.ConfFiles(confDir, []string{".conf", ".conflist", ".json"})
switch {
case err != nil:
return nil, err
case len(files) == 0:
return nil, fmt.Errorf("no networks found in %s", confDir)
}
cniConfig := &libcni.CNIConfig{Path: binDirs}
sort.Strings(files)
for _, confFile := range files {
var confList *libcni.NetworkConfigList
if strings.HasSuffix(confFile, ".conflist") {
confList, err = libcni.ConfListFromFile(confFile)
if err != nil {
klog.Warningf("Error loading CNI config list file %s: %v", confFile, err)
continue
}
} else {
conf, err := libcni.ConfFromFile(confFile)
if err != nil {
klog.Warningf("Error loading CNI config file %s: %v", confFile, err)
continue
}
// Ensure the config has a "type" so we know what plugin to run.
// Also catches the case where somebody put a conflist into a conf file.
if conf.Network.Type == "" {
klog.Warningf("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", confFile)
continue
}
confList, err = libcni.ConfListFromConf(conf)
if err != nil {
klog.Warningf("Error converting CNI config file %s to list: %v", confFile, err)
continue
}
}
if len(confList.Plugins) == 0 {
klog.Warningf("CNI config list %s has no networks, skipping", string(confList.Bytes[:maxStringLengthInLog(len(confList.Bytes))]))
continue
}
// Before using this CNI config, we have to validate it to make sure that
// all plugins of this config exist on disk
caps, err := cniConfig.ValidateNetworkList(context.TODO(), confList)
if err != nil {
klog.Warningf("Error validating CNI config list %s: %v", string(confList.Bytes[:maxStringLengthInLog(len(confList.Bytes))]), err)
continue
}
klog.V(4).Infof("Using CNI configuration file %s", confFile)
return &cniNetwork{
name: confList.Name,
NetworkConfig: confList,
CNIConfig: cniConfig,
Capabilities: caps,
}, nil
}
return nil, fmt.Errorf("no valid networks found in %s", confDir)
}
复制代码
plugin.setDefaultNetwork
将上面获取到的cniNetwork
结构体赋值给cniNetworkPlugin
结构体的defaultNetwork
属性。
// pkg/kubelet/dockershim/network/cni/cni.go
func (plugin *cniNetworkPlugin) setDefaultNetwork(n *cniNetwork) {
plugin.Lock()
defer plugin.Unlock()
plugin.defaultNetwork = n
}
复制代码
3.2 network.InitNetworkPlugin
network.InitNetworkPlugin()主要作用:根据 networkPluginName 的值(对应 kubelet 启动参数--network-plugin
),选择相应的网络插件,调用其Init()
方法,做网络插件的初始化操作。
// pkg/kubelet/dockershim/network/plugins.go
// 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{}
allErrs := []error{}
for _, plugin := range plugins {
name := plugin.Name()
if errs := validation.IsQualifiedName(name); len(errs) != 0 {
allErrs = append(allErrs, fmt.Errorf("network plugin has invalid name: %q: %s", name, strings.Join(errs, ";")))
continue
}
if _, found := pluginMap[name]; found {
allErrs = append(allErrs, fmt.Errorf("network plugin %q was registered more than once", name))
continue
}
pluginMap[name] = plugin
}
chosenPlugin := pluginMap[networkPluginName]
if chosenPlugin != nil {
err := chosenPlugin.Init(host, hairpinMode, nonMasqueradeCIDR, mtu)
if err != nil {
allErrs = append(allErrs, fmt.Errorf("network plugin %q failed init: %v", networkPluginName, err))
} else {
klog.V(1).Infof("Loaded network plugin %q", networkPluginName)
}
} else {
allErrs = append(allErrs, fmt.Errorf("network plugin %q not found", networkPluginName))
}
return chosenPlugin, utilerrors.NewAggregate(allErrs)
}
复制代码
chosenPlugin.Init()
当 kubelet 启动参数--network-plugin
的值配置为cni
时,会调用到cniNetworkPlugin
的Init()
方法,代码如下。
启动一个 goroutine,每隔 5 秒,调用一次plugin.syncNetworkConfig
。再来回忆一下plugin.syncNetworkConfig()
的作用:根据 kubelet 启动参数配置,去对应的 cni conf 文件夹下寻找 cni 配置文件,返回包含 cni 信息的 cniNetwork 结构体,赋值给cniNetworkPlugin
结构体的defaultNetwork
属性,从而达到 cni conf 以及 bin 更新后,kubelet 也能感知并更新cniNetworkPlugin
结构体的效果。
此处也可以看出该 goroutine 存在的意义,让 cni 的配置文件以及可执行文件等可以热更新,而无需重启 kubelet。
// pkg/kubelet/dockershim/network/cni/cni.go
func (plugin *cniNetworkPlugin) Init(host network.Host, hairpinMode kubeletconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) error {
err := plugin.platformInit()
if err != nil {
return err
}
plugin.host = host
plugin.syncNetworkConfig()
// start a goroutine to sync network config from confDir periodically to detect network config updates in every 5 seconds
go wait.Forever(plugin.syncNetworkConfig, defaultSyncConfigPeriod)
return nil
}
复制代码
plugin.platformInit()只是检查了下是否有nsenter
,没有做其他操作。
// pkg/kubelet/dockershim/network/cni/cni_others.go
func (plugin *cniNetworkPlugin) platformInit() error {
var err error
plugin.nsenterPath, err = plugin.execer.LookPath("nsenter")
if err != nil {
return err
}
return nil
}
复制代码
4.CNI 构建 pod 网络分析
kubelet 创建 pod 时,通过 CRI 创建并启动 pod sandbox,然后 CRI 会调用 CNI 网络插件构建 pod 网络。
kubelet 中 CNI 构建 pod 网络的方法是:pkg/kubelet/network/cni/cni.go-SetUpPod
。
其中SetUpPod
方法的调用链如下(只列出了关键部分):
main (cmd/kubelet/kubelet.go)...-> klet.syncPod(pkg/kubelet/kubelet.go)-> kl.containerRuntime.SyncPod(pkg/kubelet/kubelet.go)-> m.createPodSandbox(pkg/kubelet/kuberuntime/kuberuntime_manager.go)-> m.runtimeService.RunPodSandbox (pkg/kubelet/kuberuntime/kuberuntime_sandbox.go)-> ds.network.SetUpPod(pkg/kubelet/dockershim/docker_sandbox.go)-> pm.plugin.SetUpPod(pkg/kubelet/dockershim/network/plugins.go)-> SetUpPod(pkg/kubelet/dockershim/network/cni/cni.go)
下面的代码只是列出来看一下关键方法cniNetworkPlugin.SetUpPod()
的调用链,不做具体分析。
// pkg/kubelet/kuberuntime/kuberuntime_manager.go
func (m *kubeGenericRuntimeManager) SyncPod(pod *v1.Pod, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) {
...
podSandboxID, msg, err = m.createPodSandbox(pod, podContainerChanges.Attempt)
...
}
复制代码
// pkg/kubelet/kuberuntime/kuberuntime_sandbox.go
// createPodSandbox creates a pod sandbox and returns (podSandBoxID, message, error).
func (m *kubeGenericRuntimeManager) createPodSandbox(pod *v1.Pod, attempt uint32) (string, string, error) {
...
podSandBoxID, err := m.runtimeService.RunPodSandbox(podSandboxConfig, runtimeHandler)
...
}
复制代码
在RunPodSandbox
方法中可以看到,是先创建 pod sandbox,然后启动 pod sandbox,然后才是给该 pod sandbox 构建网络。
// pkg/kubelet/dockershim/docker_sandbox.go
func (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {
...
createResp, err := ds.client.CreateContainer(*createConfig)
...
err = ds.client.StartContainer(createResp.ID)
...
err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations, networkOptions)
...
}
复制代码
在PluginManager.SetUpPod
方法中可以看到,调用了pm.plugin.SetUpPod
,前面介绍 cni 初始化的时候讲过相关赋值初始化操作,这里会调用到cniNetworkPlugin
的SetUpPod
方法。
// pkg/kubelet/dockershim/network/plugins.go
func (pm *PluginManager) SetUpPod(podNamespace, podName string, id kubecontainer.ContainerID, annotations, options map[string]string) error {
defer recordOperation("set_up_pod", time.Now())
fullPodName := kubecontainer.BuildPodFullName(podName, podNamespace)
pm.podLock(fullPodName).Lock()
defer pm.podUnlock(fullPodName)
klog.V(3).Infof("Calling network plugin %s to set up pod %q", pm.plugin.Name(), fullPodName)
if err := pm.plugin.SetUpPod(podNamespace, podName, id, annotations, options); err != nil {
return fmt.Errorf("networkPlugin %s failed to set up pod %q network: %v", pm.plugin.Name(), fullPodName, err)
}
return nil
}
复制代码
cniNetworkPlugin.SetUpPod
cniNetworkPlugin.SetUpPod 方法作用 cni 网络插件构建 pod 网络的调用入口。其主要逻辑为:
(1)调用plugin.checkInitialized()
:检查网络插件是否已经初始化完成;
(2)调用plugin.host.GetNetNS()
:获取容器网络命名空间路径,格式/proc/${容器PID}/ns/net
;
(3)调用context.WithTimeout()
:设置调用 cni 网络插件的超时时间;
(3)调用plugin.addToNetwork()
:如果是 linux 环境,则调用 cni 网络插件,给 pod 构建回环网络;
(4)调用plugin.addToNetwork()
:调用 cni 网络插件,给 pod 构建默认网络。
// pkg/kubelet/dockershim/network/cni/cni.go
func (plugin *cniNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.ContainerID, annotations, options map[string]string) error {
if err := plugin.checkInitialized(); err != nil {
return err
}
netnsPath, err := plugin.host.GetNetNS(id.ID)
if err != nil {
return fmt.Errorf("CNI failed to retrieve network namespace path: %v", err)
}
// Todo get the timeout from parent ctx
cniTimeoutCtx, cancelFunc := context.WithTimeout(context.Background(), network.CNITimeoutSec*time.Second)
defer cancelFunc()
// Windows doesn't have loNetwork. It comes only with Linux
if plugin.loNetwork != nil {
if _, err = plugin.addToNetwork(cniTimeoutCtx, plugin.loNetwork, name, namespace, id, netnsPath, annotations, options); err != nil {
return err
}
}
_, err = plugin.addToNetwork(cniTimeoutCtx, plugin.getDefaultNetwork(), name, namespace, id, netnsPath, annotations, options)
return err
}
复制代码
plugin.addToNetwork
plugin.addToNetwork 方法的作用就是调用 cni 网络插件,给 pod 构建指定类型的网络,其主要逻辑为:
(1)调用 plugin.buildCNIRuntimeConf():构建调用 cni 网络插件的配置;
(2)调用 cniNet.AddNetworkList():调用 cni 网络插件,进行网络构建。
// pkg/kubelet/dockershim/network/cni/cni.go
func (plugin *cniNetworkPlugin) addToNetwork(ctx context.Context, network *cniNetwork, podName string, podNamespace string, podSandboxID kubecontainer.ContainerID, podNetnsPath string, annotations, options map[string]string) (cnitypes.Result, error) {
rt, err := plugin.buildCNIRuntimeConf(podName, podNamespace, podSandboxID, podNetnsPath, annotations, options)
if err != nil {
klog.Errorf("Error adding network when building cni runtime conf: %v", err)
return nil, err
}
pdesc := podDesc(podNamespace, podName, podSandboxID)
netConf, cniNet := network.NetworkConfig, network.CNIConfig
klog.V(4).Infof("Adding %s to network %s/%s netns %q", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, podNetnsPath)
res, err := cniNet.AddNetworkList(ctx, netConf, rt)
if err != nil {
klog.Errorf("Error adding %s to network %s/%s: %v", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, err)
return nil, err
}
klog.V(4).Infof("Added %s to network %s: %v", pdesc, netConf.Name, res)
return res, nil
}
复制代码
cniNet.AddNetworkList
AddNetworkList 方法中主要是调用了 addNetwork 方法,所以来看下 addNetwork 方法的逻辑:
(1)调用c.exec.FindInPath()
:拼接出 cni 网络插件可执行文件的绝对路径;
(2)调用buildOneConfig()
:构建配置;
(3)调用c.args()
:构建调用 cni 网络插件的参数;(4)调用invoke.ExecPluginWithResult()
:调用 cni 网络插件进行 pod 网络的构建操作。
// vendor/github.com/containernetworking/cni/libcni/api.go
func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
var err error
var result types.Result
for _, net := range list.Plugins {
result, err = c.addNetwork(ctx, list.Name, list.CNIVersion, net, result, rt)
if err != nil {
return nil, err
}
}
if err = setCachedResult(result, list.Name, rt); err != nil {
return nil, fmt.Errorf("failed to set network %q cached result: %v", list.Name, err)
}
return result, nil
}
func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
return nil, err
}
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
if err != nil {
return nil, err
}
return invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args("ADD", rt), c.exec)
}
复制代码
c.args
c.args 方法作用是构建调用 cni 网络插件可执行文件时的参数。
从代码中可以看出,参数有Command
(命令,Add
代表构建网络,Del
代表销毁网络)、ContainerID
(容器 ID)、NetNS
(容器网络命名空间路径)、IfName
(Interface Name 即网络接口名称)、PluginArgs
(其他参数如 pod 名称、pod 命名空间等)等。
// vendor/github.com/containernetworking/cni/libcni/api.go
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
return &invoke.Args{
Command: action,
ContainerID: rt.ContainerID,
NetNS: rt.NetNS,
PluginArgs: rt.Args,
IfName: rt.IfName,
Path: strings.Join(c.Path, string(os.PathListSeparator)),
}
}
复制代码
invoke.ExecPluginWithResult
invoke.ExecPluginWithResult 主要是将调用参数变成 env,然后调用 cni 网络插件可执行文件,并获取返回结果。
func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
if exec == nil {
exec = defaultExec
}
stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
if err != nil {
return nil, err
}
// Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{}
confVersion, err := versionDecoder.Decode(netconf)
if err != nil {
return nil, err
}
return version.NewResult(confVersion, stdoutBytes)
}
复制代码
5.CNI 销毁 pod 网络分析
kubelet 删除 pod 时,CRI 会调用 CNI 网络插件销毁 pod 网络。
kubelet 中 CNI 销毁 pod 网络的方法是:pkg/kubelet/network/cni/cni.go-TearDownPod
。
其中TearDownPod
方法的调用链如下(只列出了关键部分):
main (cmd/kubelet/kubelet.go)...-> m.runtimeService.StopPodSandbox (pkg/kubelet/kuberuntime/kuberuntime_sandbox.go)-> ds.network.TearDownPod(pkg/kubelet/dockershim/docker_sandbox.go)-> pm.plugin.TearDownPod(pkg/kubelet/dockershim/network/plugins.go)-> TearDownPod(pkg/kubelet/dockershim/network/cni/cni.go)
下面的代码只是列出来看一下关键方法cniNetworkPlugin.TearDownPod()
的调用链,不做具体分析。
在StopPodSandbox
方法中可以看到,会先销毁 pod 网络,然后停止 pod sandbox 的运行,但是这两个操作中的任何一个发生错误,kubelet 都会继续进行重试,直到成功为止,所以对这两个操作成功的顺序并没有严格的要求(删除 pod sandbox 的操作由 kubelet gc 去完成)。
// pkg/kubelet/dockershim/docker_sandbox.go
func (ds *dockerService) StopPodSandbox(ctx context.Context, r *runtimeapi.StopPodSandboxRequest) (*runtimeapi.StopPodSandboxResponse, error) {
...
// WARNING: The following operations made the following assumption:
// 1. kubelet will retry on any error returned by StopPodSandbox.
// 2. tearing down network and stopping sandbox container can succeed in any sequence.
// This depends on the implementation detail of network plugin and proper error handling.
// For kubenet, if tearing down network failed and sandbox container is stopped, kubelet
// will retry. On retry, kubenet will not be able to retrieve network namespace of the sandbox
// since it is stopped. With empty network namespcae, CNI bridge plugin will conduct best
// effort clean up and will not return error.
errList := []error{}
ready, ok := ds.getNetworkReady(podSandboxID)
if !hostNetwork && (ready || !ok) {
// Only tear down the pod network if we haven't done so already
cID := kubecontainer.BuildContainerID(runtimeName, podSandboxID)
err := ds.network.TearDownPod(namespace, name, cID)
if err == nil {
ds.setNetworkReady(podSandboxID, false)
} else {
errList = append(errList, err)
}
}
if err := ds.client.StopContainer(podSandboxID, defaultSandboxGracePeriod); err != nil {
// Do not return error if the container does not exist
if !libdocker.IsContainerNotFoundError(err) {
klog.Errorf("Failed to stop sandbox %q: %v", podSandboxID, err)
errList = append(errList, err)
} else {
// remove the checkpoint for any sandbox that is not found in the runtime
ds.checkpointManager.RemoveCheckpoint(podSandboxID)
}
}
...
}
复制代码
在PluginManager.TearDownPod
方法中可以看到,调用了pm.plugin.TearDownPod
,前面介绍 cni 初始化的时候讲过相关赋值初始化操作,这里会调用到cniNetworkPlugin
的TearDownPod
方法。
// pkg/kubelet/dockershim/network/plugins.go
func (pm *PluginManager) TearDownPod(podNamespace, podName string, id kubecontainer.ContainerID) error {
defer recordOperation("tear_down_pod", time.Now())
fullPodName := kubecontainer.BuildPodFullName(podName, podNamespace)
pm.podLock(fullPodName).Lock()
defer pm.podUnlock(fullPodName)
klog.V(3).Infof("Calling network plugin %s to tear down pod %q", pm.plugin.Name(), fullPodName)
if err := pm.plugin.TearDownPod(podNamespace, podName, id); err != nil {
return fmt.Errorf("networkPlugin %s failed to teardown pod %q network: %v", pm.plugin.Name(), fullPodName, err)
}
return nil
}
复制代码
cniNetworkPlugin.TearDownPod
cniNetworkPlugin.TearDownPod 方法作用 cni 网络插件销毁 pod 网络的调用入口。其主要逻辑为:
(1)调用plugin.checkInitialized()
:检查网络插件是否已经初始化完成;
(2)调用plugin.host.GetNetNS()
:获取容器网络命名空间路径,格式/proc/${容器PID}/ns/net
;
(3)调用context.WithTimeout()
:设置调用 cni 网络插件的超时时间;
(3)调用plugin.deleteFromNetwork()
:如果是 linux 环境,则调用 cni 网络插件,销毁 pod 的回环网络;
(4)调用plugin.deleteFromNetwork()
:调用 cni 网络插件,销毁 pod 的默认网络。
// pkg/kubelet/dockershim/network/cni/cni.go
func (plugin *cniNetworkPlugin) TearDownPod(namespace string, name string, id kubecontainer.ContainerID) error {
if err := plugin.checkInitialized(); err != nil {
return err
}
// Lack of namespace should not be fatal on teardown
netnsPath, err := plugin.host.GetNetNS(id.ID)
if err != nil {
klog.Warningf("CNI failed to retrieve network namespace path: %v", err)
}
// Todo get the timeout from parent ctx
cniTimeoutCtx, cancelFunc := context.WithTimeout(context.Background(), network.CNITimeoutSec*time.Second)
defer cancelFunc()
// Windows doesn't have loNetwork. It comes only with Linux
if plugin.loNetwork != nil {
// Loopback network deletion failure should not be fatal on teardown
if err := plugin.deleteFromNetwork(cniTimeoutCtx, plugin.loNetwork, name, namespace, id, netnsPath, nil); err != nil {
klog.Warningf("CNI failed to delete loopback network: %v", err)
}
}
return plugin.deleteFromNetwork(cniTimeoutCtx, plugin.getDefaultNetwork(), name, namespace, id, netnsPath, nil)
}
复制代码
plugin.deleteFromNetwork
plugin.deleteFromNetwork 方法的作用就是调用 cni 网络插件,销毁 pod 指定类型的网络,其主要逻辑为:
(1)调用 plugin.buildCNIRuntimeConf():构建调用 cni 网络插件的配置;
(2)调用 cniNet.DelNetworkList():调用 cni 网络插件,进行 pod 网络销毁。
// pkg/kubelet/dockershim/network/cni/cni.go
func (plugin *cniNetworkPlugin) deleteFromNetwork(ctx context.Context, network *cniNetwork, podName string, podNamespace string, podSandboxID kubecontainer.ContainerID, podNetnsPath string, annotations map[string]string) error {
rt, err := plugin.buildCNIRuntimeConf(podName, podNamespace, podSandboxID, podNetnsPath, annotations, nil)
if err != nil {
klog.Errorf("Error deleting network when building cni runtime conf: %v", err)
return err
}
pdesc := podDesc(podNamespace, podName, podSandboxID)
netConf, cniNet := network.NetworkConfig, network.CNIConfig
klog.V(4).Infof("Deleting %s from network %s/%s netns %q", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, podNetnsPath)
err = cniNet.DelNetworkList(ctx, netConf, rt)
// The pod may not get deleted successfully at the first time.
// Ignore "no such file or directory" error in case the network has already been deleted in previous attempts.
if err != nil && !strings.Contains(err.Error(), "no such file or directory") {
klog.Errorf("Error deleting %s from network %s/%s: %v", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, err)
return err
}
klog.V(4).Infof("Deleted %s from network %s/%s", pdesc, netConf.Plugins[0].Network.Type, netConf.Name)
return nil
}
复制代码
cniNet.DelNetworkList
DelNetworkList 方法中主要是调用了 addNetwork 方法,所以来看下 addNetwork 方法的逻辑:
(1)调用c.exec.FindInPath()
:拼接出 cni 网络插件可执行文件的绝对路径;
(2)调用buildOneConfig()
:构建配置;
(3)调用c.args()
:构建调用 cni 网络插件的参数;(4)调用invoke.ExecPluginWithResult()
:调用 cni 网络插件进行 pod 网络的销毁操作。
// vendor/github.com/containernetworking/cni/libcni/api.go
// DelNetworkList executes a sequence of plugins with the DEL command
func (c *CNIConfig) DelNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) error {
var cachedResult types.Result
// Cached result on DEL was added in CNI spec version 0.4.0 and higher
if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil {
return err
} else if gtet {
cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt)
if err != nil {
return fmt.Errorf("failed to get network %q cached result: %v", list.Name, err)
}
}
for i := len(list.Plugins) - 1; i >= 0; i-- {
net := list.Plugins[i]
if err := c.delNetwork(ctx, list.Name, list.CNIVersion, net, cachedResult, rt); err != nil {
return err
}
}
_ = delCachedResult(list.Name, rt)
return nil
}
func (c *CNIConfig) delNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error {
c.ensureExec()
pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)
if err != nil {
return err
}
newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt)
if err != nil {
return err
}
return invoke.ExecPluginWithoutResult(ctx, pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec)
}
复制代码
c.args
c.args 方法作用是构建调用 cni 网络插件可执行文件时的参数。
从代码中可以看出,参数有Command
(命令,Add
代表构建网络,Del
代表销毁网络)、ContainerID
(容器 ID)、NetNS
(容器网络命名空间路径)、IfName
(Interface Name 即网络接口名称)、PluginArgs
(其他参数如 pod 名称、pod 命名空间等)等。
// vendor/github.com/containernetworking/cni/libcni/api.go
func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args {
return &invoke.Args{
Command: action,
ContainerID: rt.ContainerID,
NetNS: rt.NetNS,
PluginArgs: rt.Args,
IfName: rt.IfName,
Path: strings.Join(c.Path, string(os.PathListSeparator)),
}
}
复制代码
invoke.ExecPluginWithResult
invoke.ExecPluginWithResult 主要是将调用参数变成 env,然后调用 cni 网络插件可执行文件,并获取返回结果。
func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) {
if exec == nil {
exec = defaultExec
}
stdoutBytes, err := exec.ExecPlugin(ctx, pluginPath, netconf, args.AsEnv())
if err != nil {
return nil, err
}
// Plugin must return result in same version as specified in netconf
versionDecoder := &version.ConfigDecoder{}
confVersion, err := versionDecoder.Decode(netconf)
if err != nil {
return nil, err
}
return version.NewResult(confVersion, stdoutBytes)
}
复制代码
总结
CNI
CNI,全称是 Container Network Interface,即容器网络接口。
CNI 是 K8s 中标准的调用网络实现的接口。Kubelet 通过这个标准的接口来调用不同的网络插件以实现不同的网络配置方式。
CNI 网络插件是一个可执行文件,是遵守容器网络接口(CNI)规范的网络插件。常见的 CNI 网络插件包括 Calico、flannel、Terway、Weave Net 等。
当 kubelet 选择使用 CNI 类型的网络插件时(通过 kubelet 启动参数指定),kubelet 在创建 pod、删除 pod 的时候,通过 CRI 调用 CNI 网络插件来做 pod 的构建网络和销毁网络等操作。
kubelet 构建 pod 网络的大致过程
(1)kubelet 先通过 CRI 创建 pause 容器(pod sandbox),生成 network namespace;
(2)kubelet 根据启动参数配置调用具体的网络插件如 CNI 网络插件;
(3)网络插件给 pause 容器(pod sandbox)配置网络;
(4)pod 中其他的容器都与 pause 容器(pod sandbox)共享网络。
kubelet 组件 CNI 相关启动参数分析
(1)--network-plugin
:指定要使用的网络插件类型,可选值cni
、kubenet
、""
,默认为空串,代表 Noop,即不配置网络插件(不构建 pod 网络)。此处配置值为cni
时,即指定 kubelet 使用的网络插件类型为cni
。
(2)--cni-conf-dir
:CNI 配置文件所在路径。默认值:/etc/cni/net.d
。
(3)--cni-bin-dir
:CNI 插件的可执行文件所在路径,kubelet 将在此路径中查找 CNI 插件的可执行文件来执行 pod 的网络操作。默认值:/opt/cni/bin
。
kubelet 中的 CNI 初始化
kubelet 启动后,会根据启动参数中 cni 的相关参数,获取 cni 配置文件并初始化 cni 网络插件,待后续创建 pod、删除 pod 时会调用SetUpPod
、TearDownPod
方法来构建 pod 的网络、销毁 pod 的网络。同时,初始化时起了一个 goroutine,定时探测 cni 的配置文件以及可执行文件,让其可以热更新。
CNI 构建 pod 网络
kubelet 创建 pod 时,通过 CRI 创建并启动 pod sandbox,然后 CRI 会调用 CNI 网络插件构建 pod 网络。
kubelet 中 CNI 构建 pod 网络的代码方法是:pkg/kubelet/network/cni/cni.go-SetUpPod
。
CNI 销毁 pod 网络
kubelet 删除 pod 时,CRI 会调用 CNI 网络插件销毁 pod 网络。
kubelet 中 CNI 销毁 pod 网络的方法是:pkg/kubelet/network/cni/cni.go-TearDownPod
。
评论