cni.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cni
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "math"
  19. "sort"
  20. "strings"
  21. "sync"
  22. "time"
  23. "github.com/containernetworking/cni/libcni"
  24. cnitypes "github.com/containernetworking/cni/pkg/types"
  25. "k8s.io/apimachinery/pkg/util/wait"
  26. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  27. "k8s.io/klog"
  28. kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
  29. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  30. "k8s.io/kubernetes/pkg/kubelet/dockershim/network"
  31. "k8s.io/kubernetes/pkg/util/bandwidth"
  32. utilslice "k8s.io/kubernetes/pkg/util/slice"
  33. utilexec "k8s.io/utils/exec"
  34. )
  35. const (
  36. // CNIPluginName is the name of CNI plugin
  37. CNIPluginName = "cni"
  38. // defaultSyncConfigPeriod is the default period to sync CNI config
  39. // TODO: consider making this value configurable or to be a more appropriate value.
  40. defaultSyncConfigPeriod = time.Second * 5
  41. // supported capabilities
  42. // https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md
  43. portMappingsCapability = "portMappings"
  44. ipRangesCapability = "ipRanges"
  45. bandwidthCapability = "bandwidth"
  46. dnsCapability = "dns"
  47. )
  48. type cniNetworkPlugin struct {
  49. network.NoopNetworkPlugin
  50. loNetwork *cniNetwork
  51. sync.RWMutex
  52. defaultNetwork *cniNetwork
  53. host network.Host
  54. execer utilexec.Interface
  55. nsenterPath string
  56. confDir string
  57. binDirs []string
  58. cacheDir string
  59. podCidr string
  60. }
  61. type cniNetwork struct {
  62. name string
  63. NetworkConfig *libcni.NetworkConfigList
  64. CNIConfig libcni.CNI
  65. Capabilities []string
  66. }
  67. // cniPortMapping maps to the standard CNI portmapping Capability
  68. // see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md
  69. type cniPortMapping struct {
  70. HostPort int32 `json:"hostPort"`
  71. ContainerPort int32 `json:"containerPort"`
  72. Protocol string `json:"protocol"`
  73. HostIP string `json:"hostIP"`
  74. }
  75. // cniBandwidthEntry maps to the standard CNI bandwidth Capability
  76. // see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md and
  77. // https://github.com/containernetworking/plugins/blob/master/plugins/meta/bandwidth/README.md
  78. type cniBandwidthEntry struct {
  79. // IngressRate is the bandwidth rate in bits per second for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
  80. IngressRate int `json:"ingressRate,omitempty"`
  81. // IngressBurst is the bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
  82. // NOTE: it's not used for now and default to 0.
  83. IngressBurst int `json:"ingressBurst,omitempty"`
  84. // EgressRate is the bandwidth is the bandwidth rate in bits per second for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
  85. EgressRate int `json:"egressRate,omitempty"`
  86. // EgressBurst is the bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
  87. // NOTE: it's not used for now and default to 0.
  88. EgressBurst int `json:"egressBurst,omitempty"`
  89. }
  90. // cniIPRange maps to the standard CNI ip range Capability
  91. type cniIPRange struct {
  92. Subnet string `json:"subnet"`
  93. }
  94. // cniDNSConfig maps to the windows CNI dns Capability.
  95. // see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md
  96. // Note that dns capability is only used for Windows containers.
  97. type cniDNSConfig struct {
  98. // List of DNS servers of the cluster.
  99. Servers []string `json:"servers,omitempty"`
  100. // List of DNS search domains of the cluster.
  101. Searches []string `json:"searches,omitempty"`
  102. // List of DNS options.
  103. Options []string `json:"options,omitempty"`
  104. }
  105. // SplitDirs : split dirs by ","
  106. func SplitDirs(dirs string) []string {
  107. // Use comma rather than colon to work better with Windows too
  108. return strings.Split(dirs, ",")
  109. }
  110. // ProbeNetworkPlugins : get the network plugin based on cni conf file and bin file
  111. func ProbeNetworkPlugins(confDir, cacheDir string, binDirs []string) []network.NetworkPlugin {
  112. old := binDirs
  113. binDirs = make([]string, 0, len(binDirs))
  114. for _, dir := range old {
  115. if dir != "" {
  116. binDirs = append(binDirs, dir)
  117. }
  118. }
  119. plugin := &cniNetworkPlugin{
  120. defaultNetwork: nil,
  121. loNetwork: getLoNetwork(binDirs),
  122. execer: utilexec.New(),
  123. confDir: confDir,
  124. binDirs: binDirs,
  125. cacheDir: cacheDir,
  126. }
  127. // sync NetworkConfig in best effort during probing.
  128. plugin.syncNetworkConfig()
  129. return []network.NetworkPlugin{plugin}
  130. }
  131. func getDefaultCNINetwork(confDir string, binDirs []string) (*cniNetwork, error) {
  132. files, err := libcni.ConfFiles(confDir, []string{".conf", ".conflist", ".json"})
  133. switch {
  134. case err != nil:
  135. return nil, err
  136. case len(files) == 0:
  137. return nil, fmt.Errorf("no networks found in %s", confDir)
  138. }
  139. cniConfig := &libcni.CNIConfig{Path: binDirs}
  140. sort.Strings(files)
  141. for _, confFile := range files {
  142. var confList *libcni.NetworkConfigList
  143. if strings.HasSuffix(confFile, ".conflist") {
  144. confList, err = libcni.ConfListFromFile(confFile)
  145. if err != nil {
  146. klog.Warningf("Error loading CNI config list file %s: %v", confFile, err)
  147. continue
  148. }
  149. } else {
  150. conf, err := libcni.ConfFromFile(confFile)
  151. if err != nil {
  152. klog.Warningf("Error loading CNI config file %s: %v", confFile, err)
  153. continue
  154. }
  155. // Ensure the config has a "type" so we know what plugin to run.
  156. // Also catches the case where somebody put a conflist into a conf file.
  157. if conf.Network.Type == "" {
  158. klog.Warningf("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", confFile)
  159. continue
  160. }
  161. confList, err = libcni.ConfListFromConf(conf)
  162. if err != nil {
  163. klog.Warningf("Error converting CNI config file %s to list: %v", confFile, err)
  164. continue
  165. }
  166. }
  167. if len(confList.Plugins) == 0 {
  168. klog.Warningf("CNI config list %s has no networks, skipping", string(confList.Bytes[:maxStringLengthInLog(len(confList.Bytes))]))
  169. continue
  170. }
  171. // Before using this CNI config, we have to validate it to make sure that
  172. // all plugins of this config exist on disk
  173. caps, err := cniConfig.ValidateNetworkList(context.TODO(), confList)
  174. if err != nil {
  175. klog.Warningf("Error validating CNI config list %s: %v", string(confList.Bytes[:maxStringLengthInLog(len(confList.Bytes))]), err)
  176. continue
  177. }
  178. klog.V(4).Infof("Using CNI configuration file %s", confFile)
  179. return &cniNetwork{
  180. name: confList.Name,
  181. NetworkConfig: confList,
  182. CNIConfig: cniConfig,
  183. Capabilities: caps,
  184. }, nil
  185. }
  186. return nil, fmt.Errorf("no valid networks found in %s", confDir)
  187. }
  188. func (plugin *cniNetworkPlugin) Init(host network.Host, hairpinMode kubeletconfig.HairpinMode, nonMasqueradeCIDR string, mtu int) error {
  189. err := plugin.platformInit()
  190. if err != nil {
  191. return err
  192. }
  193. plugin.host = host
  194. plugin.syncNetworkConfig()
  195. // start a goroutine to sync network config from confDir periodically to detect network config updates in every 5 seconds
  196. go wait.Forever(plugin.syncNetworkConfig, defaultSyncConfigPeriod)
  197. return nil
  198. }
  199. func (plugin *cniNetworkPlugin) syncNetworkConfig() {
  200. network, err := getDefaultCNINetwork(plugin.confDir, plugin.binDirs)
  201. if err != nil {
  202. klog.Warningf("Unable to update cni config: %s", err)
  203. return
  204. }
  205. plugin.setDefaultNetwork(network)
  206. }
  207. func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {
  208. plugin.RLock()
  209. defer plugin.RUnlock()
  210. return plugin.defaultNetwork
  211. }
  212. func (plugin *cniNetworkPlugin) setDefaultNetwork(n *cniNetwork) {
  213. plugin.Lock()
  214. defer plugin.Unlock()
  215. plugin.defaultNetwork = n
  216. }
  217. func (plugin *cniNetworkPlugin) checkInitialized() error {
  218. if plugin.getDefaultNetwork() == nil {
  219. return fmt.Errorf("cni config uninitialized")
  220. }
  221. if utilslice.ContainsString(plugin.getDefaultNetwork().Capabilities, ipRangesCapability, nil) && plugin.podCidr == "" {
  222. return fmt.Errorf("cni config needs ipRanges but no PodCIDR set")
  223. }
  224. return nil
  225. }
  226. // Event handles any change events. The only event ever sent is the PodCIDR change.
  227. // No network plugins support changing an already-set PodCIDR
  228. func (plugin *cniNetworkPlugin) Event(name string, details map[string]interface{}) {
  229. if name != network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE {
  230. return
  231. }
  232. plugin.Lock()
  233. defer plugin.Unlock()
  234. podCIDR, ok := details[network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR].(string)
  235. if !ok {
  236. klog.Warningf("%s event didn't contain pod CIDR", network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE)
  237. return
  238. }
  239. if plugin.podCidr != "" {
  240. klog.Warningf("Ignoring subsequent pod CIDR update to %s", podCIDR)
  241. return
  242. }
  243. plugin.podCidr = podCIDR
  244. }
  245. func (plugin *cniNetworkPlugin) Name() string {
  246. return CNIPluginName
  247. }
  248. func (plugin *cniNetworkPlugin) Status() error {
  249. // Can't set up pods if we don't have any CNI network configs yet
  250. return plugin.checkInitialized()
  251. }
  252. func (plugin *cniNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.ContainerID, annotations, options map[string]string) error {
  253. if err := plugin.checkInitialized(); err != nil {
  254. return err
  255. }
  256. netnsPath, err := plugin.host.GetNetNS(id.ID)
  257. if err != nil {
  258. return fmt.Errorf("CNI failed to retrieve network namespace path: %v", err)
  259. }
  260. // Todo get the timeout from parent ctx
  261. cniTimeoutCtx, cancelFunc := context.WithTimeout(context.Background(), network.CNITimeoutSec*time.Second)
  262. defer cancelFunc()
  263. // Windows doesn't have loNetwork. It comes only with Linux
  264. if plugin.loNetwork != nil {
  265. if _, err = plugin.addToNetwork(cniTimeoutCtx, plugin.loNetwork, name, namespace, id, netnsPath, annotations, options); err != nil {
  266. return err
  267. }
  268. }
  269. _, err = plugin.addToNetwork(cniTimeoutCtx, plugin.getDefaultNetwork(), name, namespace, id, netnsPath, annotations, options)
  270. return err
  271. }
  272. func (plugin *cniNetworkPlugin) TearDownPod(namespace string, name string, id kubecontainer.ContainerID) error {
  273. if err := plugin.checkInitialized(); err != nil {
  274. return err
  275. }
  276. // Lack of namespace should not be fatal on teardown
  277. netnsPath, err := plugin.host.GetNetNS(id.ID)
  278. if err != nil {
  279. klog.Warningf("CNI failed to retrieve network namespace path: %v", err)
  280. }
  281. // Todo get the timeout from parent ctx
  282. cniTimeoutCtx, cancelFunc := context.WithTimeout(context.Background(), network.CNITimeoutSec*time.Second)
  283. defer cancelFunc()
  284. // Windows doesn't have loNetwork. It comes only with Linux
  285. if plugin.loNetwork != nil {
  286. // Loopback network deletion failure should not be fatal on teardown
  287. if err := plugin.deleteFromNetwork(cniTimeoutCtx, plugin.loNetwork, name, namespace, id, netnsPath, nil); err != nil {
  288. klog.Warningf("CNI failed to delete loopback network: %v", err)
  289. }
  290. }
  291. return plugin.deleteFromNetwork(cniTimeoutCtx, plugin.getDefaultNetwork(), name, namespace, id, netnsPath, nil)
  292. }
  293. func podDesc(namespace, name string, id kubecontainer.ContainerID) string {
  294. return fmt.Sprintf("%s_%s/%s", namespace, name, id.ID)
  295. }
  296. 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) {
  297. rt, err := plugin.buildCNIRuntimeConf(podName, podNamespace, podSandboxID, podNetnsPath, annotations, options)
  298. if err != nil {
  299. klog.Errorf("Error adding network when building cni runtime conf: %v", err)
  300. return nil, err
  301. }
  302. pdesc := podDesc(podNamespace, podName, podSandboxID)
  303. netConf, cniNet := network.NetworkConfig, network.CNIConfig
  304. klog.V(4).Infof("Adding %s to network %s/%s netns %q", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, podNetnsPath)
  305. res, err := cniNet.AddNetworkList(ctx, netConf, rt)
  306. if err != nil {
  307. klog.Errorf("Error adding %s to network %s/%s: %v", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, err)
  308. return nil, err
  309. }
  310. klog.V(4).Infof("Added %s to network %s: %v", pdesc, netConf.Name, res)
  311. return res, nil
  312. }
  313. func (plugin *cniNetworkPlugin) deleteFromNetwork(ctx context.Context, network *cniNetwork, podName string, podNamespace string, podSandboxID kubecontainer.ContainerID, podNetnsPath string, annotations map[string]string) error {
  314. rt, err := plugin.buildCNIRuntimeConf(podName, podNamespace, podSandboxID, podNetnsPath, annotations, nil)
  315. if err != nil {
  316. klog.Errorf("Error deleting network when building cni runtime conf: %v", err)
  317. return err
  318. }
  319. pdesc := podDesc(podNamespace, podName, podSandboxID)
  320. netConf, cniNet := network.NetworkConfig, network.CNIConfig
  321. klog.V(4).Infof("Deleting %s from network %s/%s netns %q", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, podNetnsPath)
  322. err = cniNet.DelNetworkList(ctx, netConf, rt)
  323. // The pod may not get deleted successfully at the first time.
  324. // Ignore "no such file or directory" error in case the network has already been deleted in previous attempts.
  325. if err != nil && !strings.Contains(err.Error(), "no such file or directory") {
  326. klog.Errorf("Error deleting %s from network %s/%s: %v", pdesc, netConf.Plugins[0].Network.Type, netConf.Name, err)
  327. return err
  328. }
  329. klog.V(4).Infof("Deleted %s from network %s/%s", pdesc, netConf.Plugins[0].Network.Type, netConf.Name)
  330. return nil
  331. }
  332. func (plugin *cniNetworkPlugin) buildCNIRuntimeConf(podName string, podNs string, podSandboxID kubecontainer.ContainerID, podNetnsPath string, annotations, options map[string]string) (*libcni.RuntimeConf, error) {
  333. rt := &libcni.RuntimeConf{
  334. ContainerID: podSandboxID.ID,
  335. NetNS: podNetnsPath,
  336. IfName: network.DefaultInterfaceName,
  337. CacheDir: plugin.cacheDir,
  338. Args: [][2]string{
  339. {"IgnoreUnknown", "1"},
  340. {"K8S_POD_NAMESPACE", podNs},
  341. {"K8S_POD_NAME", podName},
  342. {"K8S_POD_INFRA_CONTAINER_ID", podSandboxID.ID},
  343. },
  344. }
  345. // port mappings are a cni capability-based args, rather than parameters
  346. // to a specific plugin
  347. portMappings, err := plugin.host.GetPodPortMappings(podSandboxID.ID)
  348. if err != nil {
  349. return nil, fmt.Errorf("could not retrieve port mappings: %v", err)
  350. }
  351. portMappingsParam := make([]cniPortMapping, 0, len(portMappings))
  352. for _, p := range portMappings {
  353. if p.HostPort <= 0 {
  354. continue
  355. }
  356. portMappingsParam = append(portMappingsParam, cniPortMapping{
  357. HostPort: p.HostPort,
  358. ContainerPort: p.ContainerPort,
  359. Protocol: strings.ToLower(string(p.Protocol)),
  360. HostIP: p.HostIP,
  361. })
  362. }
  363. rt.CapabilityArgs = map[string]interface{}{
  364. portMappingsCapability: portMappingsParam,
  365. }
  366. ingress, egress, err := bandwidth.ExtractPodBandwidthResources(annotations)
  367. if err != nil {
  368. return nil, fmt.Errorf("failed to get pod bandwidth from annotations: %v", err)
  369. }
  370. if ingress != nil || egress != nil {
  371. bandwidthParam := cniBandwidthEntry{}
  372. if ingress != nil {
  373. // see: https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md and
  374. // https://github.com/containernetworking/plugins/blob/master/plugins/meta/bandwidth/README.md
  375. // Rates are in bits per second, burst values are in bits.
  376. bandwidthParam.IngressRate = int(ingress.Value())
  377. bandwidthParam.IngressBurst = math.MaxInt32 // no limit
  378. }
  379. if egress != nil {
  380. bandwidthParam.EgressRate = int(egress.Value())
  381. bandwidthParam.EgressBurst = math.MaxInt32 // no limit
  382. }
  383. rt.CapabilityArgs[bandwidthCapability] = bandwidthParam
  384. }
  385. // Set the PodCIDR
  386. rt.CapabilityArgs[ipRangesCapability] = [][]cniIPRange{{{Subnet: plugin.podCidr}}}
  387. // Set dns capability args.
  388. if dnsOptions, ok := options["dns"]; ok {
  389. dnsConfig := runtimeapi.DNSConfig{}
  390. err := json.Unmarshal([]byte(dnsOptions), &dnsConfig)
  391. if err != nil {
  392. return nil, fmt.Errorf("failed to unmarshal dns config %q: %v", dnsOptions, err)
  393. }
  394. if dnsParam := buildDNSCapabilities(&dnsConfig); dnsParam != nil {
  395. rt.CapabilityArgs[dnsCapability] = *dnsParam
  396. }
  397. }
  398. return rt, nil
  399. }
  400. func maxStringLengthInLog(length int) int {
  401. // we allow no more than 4096-length strings to be logged
  402. const maxStringLength = 4096
  403. if length < maxStringLength {
  404. return length
  405. }
  406. return maxStringLength
  407. }