at 23.11-pre 12 kB view raw
1{ config, lib, options, pkgs, ... }: 2 3with lib; 4 5let 6 top = config.services.kubernetes; 7 otop = options.services.kubernetes; 8 cfg = top.kubelet; 9 10 cniConfig = 11 if cfg.cni.config != [] && cfg.cni.configDir != null then 12 throw "Verbatim CNI-config and CNI configDir cannot both be set." 13 else if cfg.cni.configDir != null then 14 cfg.cni.configDir 15 else 16 (pkgs.buildEnv { 17 name = "kubernetes-cni-config"; 18 paths = imap (i: entry: 19 pkgs.writeTextDir "${toString (10+i)}-${entry.type}.conf" (builtins.toJSON entry) 20 ) cfg.cni.config; 21 }); 22 23 infraContainer = pkgs.dockerTools.buildImage { 24 name = "pause"; 25 tag = "latest"; 26 copyToRoot = pkgs.buildEnv { 27 name = "image-root"; 28 pathsToLink = [ "/bin" ]; 29 paths = [ top.package.pause ]; 30 }; 31 config.Cmd = ["/bin/pause"]; 32 }; 33 34 kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig; 35 36 manifestPath = "kubernetes/manifests"; 37 38 taintOptions = with lib.types; { name, ... }: { 39 options = { 40 key = mkOption { 41 description = lib.mdDoc "Key of taint."; 42 default = name; 43 defaultText = literalMD "Name of this submodule."; 44 type = str; 45 }; 46 value = mkOption { 47 description = lib.mdDoc "Value of taint."; 48 type = str; 49 }; 50 effect = mkOption { 51 description = lib.mdDoc "Effect of taint."; 52 example = "NoSchedule"; 53 type = enum ["NoSchedule" "PreferNoSchedule" "NoExecute"]; 54 }; 55 }; 56 }; 57 58 taints = concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") (mapAttrsToList (n: v: v) cfg.taints); 59in 60{ 61 imports = [ 62 (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "") 63 (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "") 64 (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "") 65 (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "networkPlugin" ] "") 66 (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "containerRuntime" ] "") 67 ]; 68 69 ###### interface 70 options.services.kubernetes.kubelet = with lib.types; { 71 72 address = mkOption { 73 description = lib.mdDoc "Kubernetes kubelet info server listening address."; 74 default = "0.0.0.0"; 75 type = str; 76 }; 77 78 clusterDns = mkOption { 79 description = lib.mdDoc "Use alternative DNS."; 80 default = "10.1.0.1"; 81 type = str; 82 }; 83 84 clusterDomain = mkOption { 85 description = lib.mdDoc "Use alternative domain."; 86 default = config.services.kubernetes.addons.dns.clusterDomain; 87 defaultText = literalExpression "config.${options.services.kubernetes.addons.dns.clusterDomain}"; 88 type = str; 89 }; 90 91 clientCaFile = mkOption { 92 description = lib.mdDoc "Kubernetes apiserver CA file for client authentication."; 93 default = top.caFile; 94 defaultText = literalExpression "config.${otop.caFile}"; 95 type = nullOr path; 96 }; 97 98 cni = { 99 packages = mkOption { 100 description = lib.mdDoc "List of network plugin packages to install."; 101 type = listOf package; 102 default = []; 103 }; 104 105 config = mkOption { 106 description = lib.mdDoc "Kubernetes CNI configuration."; 107 type = listOf attrs; 108 default = []; 109 example = literalExpression '' 110 [{ 111 "cniVersion": "0.3.1", 112 "name": "mynet", 113 "type": "bridge", 114 "bridge": "cni0", 115 "isGateway": true, 116 "ipMasq": true, 117 "ipam": { 118 "type": "host-local", 119 "subnet": "10.22.0.0/16", 120 "routes": [ 121 { "dst": "0.0.0.0/0" } 122 ] 123 } 124 } { 125 "cniVersion": "0.3.1", 126 "type": "loopback" 127 }] 128 ''; 129 }; 130 131 configDir = mkOption { 132 description = lib.mdDoc "Path to Kubernetes CNI configuration directory."; 133 type = nullOr path; 134 default = null; 135 }; 136 }; 137 138 containerRuntimeEndpoint = mkOption { 139 description = lib.mdDoc "Endpoint at which to find the container runtime api interface/socket"; 140 type = str; 141 default = "unix:///run/containerd/containerd.sock"; 142 }; 143 144 enable = mkEnableOption (lib.mdDoc "Kubernetes kubelet"); 145 146 extraOpts = mkOption { 147 description = lib.mdDoc "Kubernetes kubelet extra command line options."; 148 default = ""; 149 type = separatedString " "; 150 }; 151 152 featureGates = mkOption { 153 description = lib.mdDoc "List set of feature gates"; 154 default = top.featureGates; 155 defaultText = literalExpression "config.${otop.featureGates}"; 156 type = listOf str; 157 }; 158 159 healthz = { 160 bind = mkOption { 161 description = lib.mdDoc "Kubernetes kubelet healthz listening address."; 162 default = "127.0.0.1"; 163 type = str; 164 }; 165 166 port = mkOption { 167 description = lib.mdDoc "Kubernetes kubelet healthz port."; 168 default = 10248; 169 type = port; 170 }; 171 }; 172 173 hostname = mkOption { 174 description = lib.mdDoc "Kubernetes kubelet hostname override."; 175 defaultText = literalExpression "config.networking.fqdnOrHostName"; 176 type = str; 177 }; 178 179 kubeconfig = top.lib.mkKubeConfigOptions "Kubelet"; 180 181 manifests = mkOption { 182 description = lib.mdDoc "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)"; 183 type = attrsOf attrs; 184 default = {}; 185 }; 186 187 nodeIp = mkOption { 188 description = lib.mdDoc "IP address of the node. If set, kubelet will use this IP address for the node."; 189 default = null; 190 type = nullOr str; 191 }; 192 193 registerNode = mkOption { 194 description = lib.mdDoc "Whether to auto register kubelet with API server."; 195 default = true; 196 type = bool; 197 }; 198 199 port = mkOption { 200 description = lib.mdDoc "Kubernetes kubelet info server listening port."; 201 default = 10250; 202 type = port; 203 }; 204 205 seedDockerImages = mkOption { 206 description = lib.mdDoc "List of docker images to preload on system"; 207 default = []; 208 type = listOf package; 209 }; 210 211 taints = mkOption { 212 description = lib.mdDoc "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/)."; 213 default = {}; 214 type = attrsOf (submodule [ taintOptions ]); 215 }; 216 217 tlsCertFile = mkOption { 218 description = lib.mdDoc "File containing x509 Certificate for HTTPS."; 219 default = null; 220 type = nullOr path; 221 }; 222 223 tlsKeyFile = mkOption { 224 description = lib.mdDoc "File containing x509 private key matching tlsCertFile."; 225 default = null; 226 type = nullOr path; 227 }; 228 229 unschedulable = mkOption { 230 description = lib.mdDoc "Whether to set node taint to unschedulable=true as it is the case of node that has only master role."; 231 default = false; 232 type = bool; 233 }; 234 235 verbosity = mkOption { 236 description = lib.mdDoc '' 237 Optional glog verbosity level for logging statements. See 238 <https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md> 239 ''; 240 default = null; 241 type = nullOr int; 242 }; 243 244 }; 245 246 ###### implementation 247 config = mkMerge [ 248 (mkIf cfg.enable { 249 250 environment.etc."cni/net.d".source = cniConfig; 251 252 services.kubernetes.kubelet.seedDockerImages = [infraContainer]; 253 254 boot.kernel.sysctl = { 255 "net.bridge.bridge-nf-call-iptables" = 1; 256 "net.ipv4.ip_forward" = 1; 257 "net.bridge.bridge-nf-call-ip6tables" = 1; 258 }; 259 260 systemd.services.kubelet = { 261 description = "Kubernetes Kubelet Service"; 262 wantedBy = [ "kubernetes.target" ]; 263 after = [ "containerd.service" "network.target" "kube-apiserver.service" ]; 264 path = with pkgs; [ 265 gitMinimal 266 openssh 267 util-linux 268 iproute2 269 ethtool 270 thin-provisioning-tools 271 iptables 272 socat 273 ] ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package ++ top.path; 274 preStart = '' 275 ${concatMapStrings (img: '' 276 echo "Seeding container image: ${img}" 277 ${if (lib.hasSuffix "gz" img) then 278 ''${pkgs.gzip}/bin/zcat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import --all-platforms -'' 279 else 280 ''${pkgs.coreutils}/bin/cat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import --all-platforms -'' 281 } 282 '') cfg.seedDockerImages} 283 284 rm /opt/cni/bin/* || true 285 ${concatMapStrings (package: '' 286 echo "Linking cni package: ${package}" 287 ln -fs ${package}/bin/* /opt/cni/bin 288 '') cfg.cni.packages} 289 ''; 290 serviceConfig = { 291 Slice = "kubernetes.slice"; 292 CPUAccounting = true; 293 MemoryAccounting = true; 294 Restart = "on-failure"; 295 RestartSec = "1000ms"; 296 ExecStart = ''${top.package}/bin/kubelet \ 297 --address=${cfg.address} \ 298 --authentication-token-webhook \ 299 --authentication-token-webhook-cache-ttl="10s" \ 300 --authorization-mode=Webhook \ 301 ${optionalString (cfg.clientCaFile != null) 302 "--client-ca-file=${cfg.clientCaFile}"} \ 303 ${optionalString (cfg.clusterDns != "") 304 "--cluster-dns=${cfg.clusterDns}"} \ 305 ${optionalString (cfg.clusterDomain != "") 306 "--cluster-domain=${cfg.clusterDomain}"} \ 307 ${optionalString (cfg.featureGates != []) 308 "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \ 309 --hairpin-mode=hairpin-veth \ 310 --healthz-bind-address=${cfg.healthz.bind} \ 311 --healthz-port=${toString cfg.healthz.port} \ 312 --hostname-override=${cfg.hostname} \ 313 --kubeconfig=${kubeconfig} \ 314 ${optionalString (cfg.nodeIp != null) 315 "--node-ip=${cfg.nodeIp}"} \ 316 --pod-infra-container-image=pause \ 317 ${optionalString (cfg.manifests != {}) 318 "--pod-manifest-path=/etc/${manifestPath}"} \ 319 --port=${toString cfg.port} \ 320 --register-node=${boolToString cfg.registerNode} \ 321 ${optionalString (taints != "") 322 "--register-with-taints=${taints}"} \ 323 --root-dir=${top.dataDir} \ 324 ${optionalString (cfg.tlsCertFile != null) 325 "--tls-cert-file=${cfg.tlsCertFile}"} \ 326 ${optionalString (cfg.tlsKeyFile != null) 327 "--tls-private-key-file=${cfg.tlsKeyFile}"} \ 328 ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \ 329 --container-runtime-endpoint=${cfg.containerRuntimeEndpoint} \ 330 --cgroup-driver=systemd \ 331 ${cfg.extraOpts} 332 ''; 333 WorkingDirectory = top.dataDir; 334 }; 335 unitConfig = { 336 StartLimitIntervalSec = 0; 337 }; 338 }; 339 340 # Always include cni plugins 341 services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins pkgs.cni-plugin-flannel]; 342 343 boot.kernelModules = ["br_netfilter" "overlay"]; 344 345 services.kubernetes.kubelet.hostname = 346 mkDefault config.networking.fqdnOrHostName; 347 348 services.kubernetes.pki.certs = with top.lib; { 349 kubelet = mkCert { 350 name = "kubelet"; 351 CN = top.kubelet.hostname; 352 action = "systemctl restart kubelet.service"; 353 354 }; 355 kubeletClient = mkCert { 356 name = "kubelet-client"; 357 CN = "system:node:${top.kubelet.hostname}"; 358 fields = { 359 O = "system:nodes"; 360 }; 361 action = "systemctl restart kubelet.service"; 362 }; 363 }; 364 365 services.kubernetes.kubelet.kubeconfig.server = mkDefault top.apiserverAddress; 366 }) 367 368 (mkIf (cfg.enable && cfg.manifests != {}) { 369 environment.etc = mapAttrs' (name: manifest: 370 nameValuePair "${manifestPath}/${name}.json" { 371 text = builtins.toJSON manifest; 372 mode = "0755"; 373 } 374 ) cfg.manifests; 375 }) 376 377 (mkIf (cfg.unschedulable && cfg.enable) { 378 services.kubernetes.kubelet.taints.unschedulable = { 379 value = "true"; 380 effect = "NoSchedule"; 381 }; 382 }) 383 384 ]; 385 386 meta.buildDocsInSandbox = false; 387}