cni.go 14 KB

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