blob: f475b5bac08af1ba57c212d24d4b1617b82125f9 [file] [log] [blame]
Sergiusz Bazanskic78cc132020-02-02 22:31:53 +01001# Same as upstream kubelet.nix module from nixpkgs, but with the following
2# changes:
3# - cni tunables nuked and replaced with static host dirs, so that calico
4# running on k8s can drop CNI plugins there itself
5# - package configurable separately from rest of kubernetes
6
7{ config, lib, pkgs, ... }:
8
9with lib;
10
11let
12 top = config.services.kubernetes;
13 cfg = top.kubelet;
14
15 infraContainer = pkgs.dockerTools.buildImage {
16 name = "pause";
17 tag = "latest";
18 contents = top.package.pause;
19 config.Cmd = "/bin/pause";
20 };
21
22 kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
23
24 manifestPath = "kubernetes/manifests";
25
26 taintOptions = with lib.types; { name, ... }: {
27 options = {
28 key = mkOption {
29 description = "Key of taint.";
30 default = name;
31 type = str;
32 };
33 value = mkOption {
34 description = "Value of taint.";
35 type = str;
36 };
37 effect = mkOption {
38 description = "Effect of taint.";
39 example = "NoSchedule";
40 type = enum ["NoSchedule" "PreferNoSchedule" "NoExecute"];
41 };
42 };
43 };
44
45 taints = concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") (mapAttrsToList (n: v: v) cfg.taints);
46in
47{
48 imports = [
49 #(mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "")
50 #(mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
51 #(mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "")
52 ];
53
54 # services/cluster/kubernetes/default.nix still wants to poke flannel,
55 # but since we nuke that module we have to add a fake tunable for it.
56 options.services.kubernetes.flannel = {
57 enable = mkEnableOption "enable flannel networking";
58 };
59
60 ###### interface
61 options.services.kubernetes.kubelet = with lib.types; {
62
63 address = mkOption {
64 description = "Kubernetes kubelet info server listening address.";
65 default = "0.0.0.0";
66 type = str;
67 };
68
69 clusterDns = mkOption {
70 description = "Use alternative DNS.";
71 default = "10.1.0.1";
72 type = str;
73 };
74
75 clusterDomain = mkOption {
76 description = "Use alternative domain.";
77 default = config.services.kubernetes.addons.dns.clusterDomain;
78 type = str;
79 };
80
81 clientCaFile = mkOption {
82 description = "Kubernetes apiserver CA file for client authentication.";
83 default = top.caFile;
84 type = nullOr path;
85 };
86
87 enable = mkEnableOption "Kubernetes kubelet.";
88
89 extraOpts = mkOption {
90 description = "Kubernetes kubelet extra command line options.";
91 default = "";
92 type = str;
93 };
94
95 featureGates = mkOption {
96 description = "List set of feature gates";
97 default = top.featureGates;
98 type = listOf str;
99 };
100
101 healthz = {
102 bind = mkOption {
103 description = "Kubernetes kubelet healthz listening address.";
104 default = "127.0.0.1";
105 type = str;
106 };
107
108 port = mkOption {
109 description = "Kubernetes kubelet healthz port.";
110 default = 10248;
111 type = int;
112 };
113 };
114
115 hostname = mkOption {
116 description = "Kubernetes kubelet hostname override.";
117 default = config.networking.hostName;
118 type = str;
119 };
120
121 kubeconfig = top.lib.mkKubeConfigOptions "Kubelet";
122
123 manifests = mkOption {
124 description = "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)";
125 type = attrsOf attrs;
126 default = {};
127 };
128
129 networkPlugin = mkOption {
130 description = "Network plugin to use by Kubernetes.";
131 type = nullOr (enum ["cni" "kubenet"]);
132 default = "kubenet";
133 };
134
135 nodeIp = mkOption {
136 description = "IP address of the node. If set, kubelet will use this IP address for the node.";
137 default = null;
138 type = nullOr str;
139 };
140
141 registerNode = mkOption {
142 description = "Whether to auto register kubelet with API server.";
143 default = true;
144 type = bool;
145 };
146
147 package = mkOption {
148 description = "Kubernetes package to use.";
149 type = types.package;
150 default = pkgs.kubernetes;
151 defaultText = "pkgs.kubernetes";
152 };
153
154 port = mkOption {
155 description = "Kubernetes kubelet info server listening port.";
156 default = 10250;
157 type = int;
158 };
159
160 seedDockerImages = mkOption {
161 description = "List of docker images to preload on system";
162 default = [];
163 type = listOf package;
164 };
165
166 taints = mkOption {
167 description = "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/).";
168 default = {};
169 type = attrsOf (submodule [ taintOptions ]);
170 };
171
172 tlsCertFile = mkOption {
173 description = "File containing x509 Certificate for HTTPS.";
174 default = null;
175 type = nullOr path;
176 };
177
178 tlsKeyFile = mkOption {
179 description = "File containing x509 private key matching tlsCertFile.";
180 default = null;
181 type = nullOr path;
182 };
183
184 unschedulable = mkOption {
185 description = "Whether to set node taint to unschedulable=true as it is the case of node that has only master role.";
186 default = false;
187 type = bool;
188 };
189
190 verbosity = mkOption {
191 description = ''
192 Optional glog verbosity level for logging statements. See
193 <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
194 '';
195 default = null;
196 type = nullOr int;
197 };
198
199 };
200
201 ###### implementation
202 config = mkMerge [
203 (mkIf cfg.enable {
204 services.kubernetes.kubelet.seedDockerImages = [infraContainer];
205
206 systemd.services.kubelet = {
207 description = "Kubernetes Kubelet Service";
208 wantedBy = [ "kubernetes.target" ];
209 after = [ "network.target" "docker.service" "kube-apiserver.service" ];
210 path = with pkgs; [ gitMinimal openssh docker utillinux iproute ethtool thin-provisioning-tools iptables socat ] ++ top.path;
211 preStart = ''
212 ${concatMapStrings (img: ''
213 echo "Seeding docker image: ${img}"
214 docker load <${img}
215 '') cfg.seedDockerImages}
216 '';
217 serviceConfig = {
218 Slice = "kubernetes.slice";
219 CPUAccounting = true;
220 MemoryAccounting = true;
221 Restart = "on-failure";
222 RestartSec = "1000ms";
223 ExecStart = ''${cfg.package}/bin/kubelet \
224 --address=${cfg.address} \
225 --authentication-token-webhook \
226 --authentication-token-webhook-cache-ttl="10s" \
227 --authorization-mode=Webhook \
228 ${optionalString (cfg.clientCaFile != null)
229 "--client-ca-file=${cfg.clientCaFile}"} \
230 ${optionalString (cfg.clusterDns != "")
231 "--cluster-dns=${cfg.clusterDns}"} \
232 ${optionalString (cfg.clusterDomain != "")
233 "--cluster-domain=${cfg.clusterDomain}"} \
234 --cni-conf-dir=/opt/cni/conf \
235 --cni-bin-dir=/opt/cni/bin \
236 ${optionalString (cfg.featureGates != [])
237 "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
238 --hairpin-mode=hairpin-veth \
239 --healthz-bind-address=${cfg.healthz.bind} \
240 --healthz-port=${toString cfg.healthz.port} \
241 --hostname-override=${cfg.hostname} \
242 --kubeconfig=${kubeconfig} \
243 ${optionalString (cfg.networkPlugin != null)
244 "--network-plugin=${cfg.networkPlugin}"} \
245 ${optionalString (cfg.nodeIp != null)
246 "--node-ip=${cfg.nodeIp}"} \
247 --pod-infra-container-image=pause \
248 ${optionalString (cfg.manifests != {})
249 "--pod-manifest-path=/etc/${manifestPath}"} \
250 --port=${toString cfg.port} \
251 --register-node=${boolToString cfg.registerNode} \
252 ${optionalString (taints != "")
253 "--register-with-taints=${taints}"} \
254 --root-dir=${top.dataDir} \
255 ${optionalString (cfg.tlsCertFile != null)
256 "--tls-cert-file=${cfg.tlsCertFile}"} \
257 ${optionalString (cfg.tlsKeyFile != null)
258 "--tls-private-key-file=${cfg.tlsKeyFile}"} \
259 ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
260 ${cfg.extraOpts}
261 '';
262 WorkingDirectory = top.dataDir;
263 };
264 };
265
266 boot.kernelModules = ["br_netfilter"];
267
268 services.kubernetes.kubelet.hostname = with config.networking;
269 mkDefault (hostName + optionalString (domain != null) ".${domain}");
270
271 services.kubernetes.pki.certs = with top.lib; {
272 kubelet = mkCert {
273 name = "kubelet";
274 CN = top.kubelet.hostname;
275 action = "systemctl restart kubelet.service";
276
277 };
278 kubeletClient = mkCert {
279 name = "kubelet-client";
280 CN = "system:node:${top.kubelet.hostname}";
281 fields = {
282 O = "system:nodes";
283 };
284 action = "systemctl restart kubelet.service";
285 };
286 };
287
288 services.kubernetes.kubelet.kubeconfig.server = mkDefault top.apiserverAddress;
289 })
290
291 (mkIf (cfg.enable && cfg.manifests != {}) {
292 environment.etc = mapAttrs' (name: manifest:
293 nameValuePair "${manifestPath}/${name}.json" {
294 text = builtins.toJSON manifest;
295 mode = "0755";
296 }
297 ) cfg.manifests;
298 })
299
300 (mkIf (cfg.unschedulable && cfg.enable) {
301 services.kubernetes.kubelet.taints.unschedulable = {
302 value = "true";
303 effect = "NoSchedule";
304 };
305 })
306
307 ];
308}