grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / cluster / kubernetes / kubelet.nix
blob8fa820ac44e9e7d2fb071373a57271f7a05d0916
1 { config, lib, options, pkgs, ... }:
3 with lib;
5 let
6   top = config.services.kubernetes;
7   otop = options.services.kubernetes;
8   cfg = top.kubelet;
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       });
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   };
34   kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
36   # Flag based settings are deprecated, use the `--config` flag with a
37   # `KubeletConfiguration` struct.
38   # https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/
39   #
40   # NOTE: registerWithTaints requires a []core/v1.Taint, therefore requires
41   # additional work to be put in config format.
42   #
43   kubeletConfig = pkgs.writeText "kubelet-config" (builtins.toJSON ({
44     apiVersion = "kubelet.config.k8s.io/v1beta1";
45     kind = "KubeletConfiguration";
46     address = cfg.address;
47     port = cfg.port;
48     authentication = {
49       x509 = lib.optionalAttrs (cfg.clientCaFile != null) { clientCAFile = cfg.clientCaFile; };
50       webhook = {
51         enabled = true;
52         cacheTTL = "10s";
53       };
54     };
55     authorization = {
56       mode = "Webhook";
57     };
58     cgroupDriver = "systemd";
59     hairpinMode = "hairpin-veth";
60     registerNode = cfg.registerNode;
61     containerRuntimeEndpoint = cfg.containerRuntimeEndpoint;
62     healthzPort = cfg.healthz.port;
63     healthzBindAddress = cfg.healthz.bind;
64   } // lib.optionalAttrs (cfg.tlsCertFile != null)  { tlsCertFile = cfg.tlsCertFile; }
65     // lib.optionalAttrs (cfg.tlsKeyFile != null)   { tlsPrivateKeyFile = cfg.tlsKeyFile; }
66     // lib.optionalAttrs (cfg.clusterDomain != "")  { clusterDomain = cfg.clusterDomain; }
67     // lib.optionalAttrs (cfg.clusterDns != [])     { clusterDNS = cfg.clusterDns; }
68     // lib.optionalAttrs (cfg.featureGates != {})   { featureGates = cfg.featureGates; }
69     // lib.optionalAttrs (cfg.extraConfig != {})    cfg.extraConfig
70   ));
72   manifestPath = "kubernetes/manifests";
74   taintOptions = with lib.types; { name, ... }: {
75     options = {
76       key = mkOption {
77         description = "Key of taint.";
78         default = name;
79         defaultText = literalMD "Name of this submodule.";
80         type = str;
81       };
82       value = mkOption {
83         description = "Value of taint.";
84         type = str;
85       };
86       effect = mkOption {
87         description = "Effect of taint.";
88         example = "NoSchedule";
89         type = enum ["NoSchedule" "PreferNoSchedule" "NoExecute"];
90       };
91     };
92   };
94   taints = concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") (mapAttrsToList (n: v: v) cfg.taints);
97   imports = [
98     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "")
99     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
100     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "")
101     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "networkPlugin" ] "")
102     (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "containerRuntime" ] "")
103   ];
105   ###### interface
106   options.services.kubernetes.kubelet = with lib.types; {
108     address = mkOption {
109       description = "Kubernetes kubelet info server listening address.";
110       default = "0.0.0.0";
111       type = str;
112     };
114     clusterDns = mkOption {
115       description = "Use alternative DNS.";
116       default = [ "10.1.0.1" ];
117       type = listOf str;
118     };
120     clusterDomain = mkOption {
121       description = "Use alternative domain.";
122       default = config.services.kubernetes.addons.dns.clusterDomain;
123       defaultText = literalExpression "config.${options.services.kubernetes.addons.dns.clusterDomain}";
124       type = str;
125     };
127     clientCaFile = mkOption {
128       description = "Kubernetes apiserver CA file for client authentication.";
129       default = top.caFile;
130       defaultText = literalExpression "config.${otop.caFile}";
131       type = nullOr path;
132     };
134     cni = {
135       packages = mkOption {
136         description = "List of network plugin packages to install.";
137         type = listOf package;
138         default = [];
139       };
141       config = mkOption {
142         description = "Kubernetes CNI configuration.";
143         type = listOf attrs;
144         default = [];
145         example = literalExpression ''
146           [{
147             "cniVersion": "0.3.1",
148             "name": "mynet",
149             "type": "bridge",
150             "bridge": "cni0",
151             "isGateway": true,
152             "ipMasq": true,
153             "ipam": {
154                 "type": "host-local",
155                 "subnet": "10.22.0.0/16",
156                 "routes": [
157                     { "dst": "0.0.0.0/0" }
158                 ]
159             }
160           } {
161             "cniVersion": "0.3.1",
162             "type": "loopback"
163           }]
164         '';
165       };
167       configDir = mkOption {
168         description = "Path to Kubernetes CNI configuration directory.";
169         type = nullOr path;
170         default = null;
171       };
172     };
174     containerRuntimeEndpoint = mkOption {
175       description = "Endpoint at which to find the container runtime api interface/socket";
176       type = str;
177       default = "unix:///run/containerd/containerd.sock";
178     };
180     enable = mkEnableOption "Kubernetes kubelet";
182     extraOpts = mkOption {
183       description = "Kubernetes kubelet extra command line options.";
184       default = "";
185       type = separatedString " ";
186     };
188     extraConfig = mkOption {
189       description = "Kubernetes kubelet extra configuration file entries.";
190       default = {};
191       type = attrsOf attrs;
192     };
194     featureGates = mkOption {
195       description = "Attribute set of feature gate";
196       default = top.featureGates;
197       defaultText = literalExpression "config.${otop.featureGates}";
198       type = attrsOf bool;
199     };
201     healthz = {
202       bind = mkOption {
203         description = "Kubernetes kubelet healthz listening address.";
204         default = "127.0.0.1";
205         type = str;
206       };
208       port = mkOption {
209         description = "Kubernetes kubelet healthz port.";
210         default = 10248;
211         type = port;
212       };
213     };
215     hostname = mkOption {
216       description = "Kubernetes kubelet hostname override.";
217       defaultText = literalExpression "config.networking.fqdnOrHostName";
218       type = str;
219     };
221     kubeconfig = top.lib.mkKubeConfigOptions "Kubelet";
223     manifests = mkOption {
224       description = "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)";
225       type = attrsOf attrs;
226       default = {};
227     };
229     nodeIp = mkOption {
230       description = "IP address of the node. If set, kubelet will use this IP address for the node.";
231       default = null;
232       type = nullOr str;
233     };
235     registerNode = mkOption {
236       description = "Whether to auto register kubelet with API server.";
237       default = true;
238       type = bool;
239     };
241     port = mkOption {
242       description = "Kubernetes kubelet info server listening port.";
243       default = 10250;
244       type = port;
245     };
247     seedDockerImages = mkOption {
248       description = "List of docker images to preload on system";
249       default = [];
250       type = listOf package;
251     };
253     taints = mkOption {
254       description = "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/).";
255       default = {};
256       type = attrsOf (submodule [ taintOptions ]);
257     };
259     tlsCertFile = mkOption {
260       description = "File containing x509 Certificate for HTTPS.";
261       default = null;
262       type = nullOr path;
263     };
265     tlsKeyFile = mkOption {
266       description = "File containing x509 private key matching tlsCertFile.";
267       default = null;
268       type = nullOr path;
269     };
271     unschedulable = mkOption {
272       description = "Whether to set node taint to unschedulable=true as it is the case of node that has only master role.";
273       default = false;
274       type = bool;
275     };
277     verbosity = mkOption {
278       description = ''
279         Optional glog verbosity level for logging statements. See
280         <https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md>
281       '';
282       default = null;
283       type = nullOr int;
284     };
286   };
288   ###### implementation
289   config = mkMerge [
290     (mkIf cfg.enable {
292       environment.etc."cni/net.d".source = cniConfig;
294       services.kubernetes.kubelet.seedDockerImages = [infraContainer];
296       boot.kernel.sysctl = {
297         "net.bridge.bridge-nf-call-iptables"  = 1;
298         "net.ipv4.ip_forward"                 = 1;
299         "net.bridge.bridge-nf-call-ip6tables" = 1;
300       };
302       systemd.services.kubelet = {
303         description = "Kubernetes Kubelet Service";
304         wantedBy = [ "kubernetes.target" ];
305         after = [ "containerd.service" "network.target" "kube-apiserver.service" ];
306         path = with pkgs; [
307           gitMinimal
308           openssh
309           util-linux
310           iproute2
311           ethtool
312           thin-provisioning-tools
313           iptables
314           socat
315         ] ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package ++ top.path;
316         preStart = ''
317           ${concatMapStrings (img: ''
318             echo "Seeding container image: ${img}"
319             ${if (lib.hasSuffix "gz" img) then
320               ''${pkgs.gzip}/bin/zcat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import --all-platforms -''
321             else
322               ''${pkgs.coreutils}/bin/cat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import --all-platforms -''
323             }
324           '') cfg.seedDockerImages}
326           rm /opt/cni/bin/* || true
327           ${concatMapStrings (package: ''
328             echo "Linking cni package: ${package}"
329             ln -fs ${package}/bin/* /opt/cni/bin
330           '') cfg.cni.packages}
331         '';
332         serviceConfig = {
333           Slice = "kubernetes.slice";
334           CPUAccounting = true;
335           MemoryAccounting = true;
336           Restart = "on-failure";
337           RestartSec = "1000ms";
338           ExecStart = ''${top.package}/bin/kubelet \
339             --config=${kubeletConfig} \
340             --hostname-override=${cfg.hostname} \
341             --kubeconfig=${kubeconfig} \
342             ${optionalString (cfg.nodeIp != null)
343               "--node-ip=${cfg.nodeIp}"} \
344             --pod-infra-container-image=pause \
345             ${optionalString (cfg.manifests != {})
346               "--pod-manifest-path=/etc/${manifestPath}"} \
347             ${optionalString (taints != "")
348               "--register-with-taints=${taints}"} \
349             --root-dir=${top.dataDir} \
350             ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
351             ${cfg.extraOpts}
352           '';
353           WorkingDirectory = top.dataDir;
354         };
355         unitConfig = {
356           StartLimitIntervalSec = 0;
357         };
358       };
360       # Always include cni plugins
361       services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins pkgs.cni-plugin-flannel];
363       boot.kernelModules = ["br_netfilter" "overlay"];
365       services.kubernetes.kubelet.hostname =
366         mkDefault (lib.toLower config.networking.fqdnOrHostName);
368       services.kubernetes.pki.certs = with top.lib; {
369         kubelet = mkCert {
370           name = "kubelet";
371           CN = top.kubelet.hostname;
372           action = "systemctl restart kubelet.service";
374         };
375         kubeletClient = mkCert {
376           name = "kubelet-client";
377           CN = "system:node:${top.kubelet.hostname}";
378           fields = {
379             O = "system:nodes";
380           };
381           action = "systemctl restart kubelet.service";
382         };
383       };
385       services.kubernetes.kubelet.kubeconfig.server = mkDefault top.apiserverAddress;
386     })
388     (mkIf (cfg.enable && cfg.manifests != {}) {
389       environment.etc = mapAttrs' (name: manifest:
390         nameValuePair "${manifestPath}/${name}.json" {
391           text = builtins.toJSON manifest;
392           mode = "0755";
393         }
394       ) cfg.manifests;
395     })
397     (mkIf (cfg.unschedulable && cfg.enable) {
398       services.kubernetes.kubelet.taints.unschedulable = {
399         value = "true";
400         effect = "NoSchedule";
401       };
402     })
404   ];
406   meta.buildDocsInSandbox = false;