grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / cluster / kubernetes / default.nix
blob208b2a864f02408821c1be44c6a79465a7227db5
1 { config, lib, options, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.kubernetes;
7   opt = options.services.kubernetes;
9   defaultContainerdSettings = {
10     version = 2;
11     root = "/var/lib/containerd";
12     state = "/run/containerd";
13     oom_score = 0;
15     grpc = {
16       address = "/run/containerd/containerd.sock";
17     };
19     plugins."io.containerd.grpc.v1.cri" = {
20       sandbox_image = "pause:latest";
22       cni = {
23         bin_dir = "/opt/cni/bin";
24         max_conf_num = 0;
25       };
27       containerd.runtimes.runc = {
28         runtime_type = "io.containerd.runc.v2";
29         options.SystemdCgroup = true;
30       };
31     };
32   };
34   mkKubeConfig = name: conf: pkgs.writeText "${name}-kubeconfig" (builtins.toJSON {
35     apiVersion = "v1";
36     kind = "Config";
37     clusters = [{
38       name = "local";
39       cluster.certificate-authority = conf.caFile or cfg.caFile;
40       cluster.server = conf.server;
41     }];
42     users = [{
43       inherit name;
44       user = {
45         client-certificate = conf.certFile;
46         client-key = conf.keyFile;
47       };
48     }];
49     contexts = [{
50       context = {
51         cluster = "local";
52         user = name;
53       };
54       name = "local";
55     }];
56     current-context = "local";
57   });
59   caCert = secret "ca";
61   etcdEndpoints = ["https://${cfg.masterAddress}:2379"];
63   mkCert = { name, CN, hosts ? [], fields ? {}, action ? "",
64              privateKeyOwner ? "kubernetes", privateKeyGroup ? "kubernetes" }: rec {
65     inherit name caCert CN hosts fields action;
66     cert = secret name;
67     key = secret "${name}-key";
68     privateKeyOptions = {
69       owner = privateKeyOwner;
70       group = privateKeyGroup;
71       mode = "0600";
72       path = key;
73     };
74   };
76   secret = name: "${cfg.secretsPath}/${name}.pem";
78   mkKubeConfigOptions = prefix: {
79     server = mkOption {
80       description = "${prefix} kube-apiserver server address.";
81       type = types.str;
82     };
84     caFile = mkOption {
85       description = "${prefix} certificate authority file used to connect to kube-apiserver.";
86       type = types.nullOr types.path;
87       default = cfg.caFile;
88       defaultText = literalExpression "config.${opt.caFile}";
89     };
91     certFile = mkOption {
92       description = "${prefix} client certificate file used to connect to kube-apiserver.";
93       type = types.nullOr types.path;
94       default = null;
95     };
97     keyFile = mkOption {
98       description = "${prefix} client key file used to connect to kube-apiserver.";
99       type = types.nullOr types.path;
100       default = null;
101     };
102   };
103 in {
105   imports = [
106     (mkRemovedOptionModule [ "services" "kubernetes" "addons" "dashboard" ] "Removed due to it being an outdated version")
107     (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "")
108   ];
110   ###### interface
112   options.services.kubernetes = {
113     roles = mkOption {
114       description = ''
115         Kubernetes role that this machine should take.
117         Master role will enable etcd, apiserver, scheduler, controller manager
118         addon manager, flannel and proxy services.
119         Node role will enable flannel, docker, kubelet and proxy services.
120       '';
121       default = [];
122       type = types.listOf (types.enum ["master" "node"]);
123     };
125     package = mkPackageOption pkgs "kubernetes" { };
127     kubeconfig = mkKubeConfigOptions "Default kubeconfig";
129     apiserverAddress = mkOption {
130       description = ''
131         Clusterwide accessible address for the kubernetes apiserver,
132         including protocol and optional port.
133       '';
134       example = "https://kubernetes-apiserver.example.com:6443";
135       type = types.str;
136     };
138     caFile = mkOption {
139       description = "Default kubernetes certificate authority";
140       type = types.nullOr types.path;
141       default = null;
142     };
144     dataDir = mkOption {
145       description = "Kubernetes root directory for managing kubelet files.";
146       default = "/var/lib/kubernetes";
147       type = types.path;
148     };
150     easyCerts = mkOption {
151       description = "Automatically setup x509 certificates and keys for the entire cluster.";
152       default = false;
153       type = types.bool;
154     };
156     featureGates = mkOption {
157       description = "List set of feature gates.";
158       default = {};
159       type = types.attrsOf types.bool;
160     };
162     masterAddress = mkOption {
163       description = "Clusterwide available network address or hostname for the kubernetes master server.";
164       example = "master.example.com";
165       type = types.str;
166     };
168     path = mkOption {
169       description = "Packages added to the services' PATH environment variable. Both the bin and sbin subdirectories of each package are added.";
170       type = types.listOf types.package;
171       default = [];
172     };
174     clusterCidr = mkOption {
175       description = "Kubernetes controller manager and proxy CIDR Range for Pods in cluster.";
176       default = "10.1.0.0/16";
177       type = types.nullOr types.str;
178     };
180     lib = mkOption {
181       description = "Common functions for the kubernetes modules.";
182       default = {
183         inherit mkCert;
184         inherit mkKubeConfig;
185         inherit mkKubeConfigOptions;
186       };
187       type = types.attrs;
188     };
190     secretsPath = mkOption {
191       description = "Default location for kubernetes secrets. Not a store location.";
192       type = types.path;
193       default = cfg.dataDir + "/secrets";
194       defaultText = literalExpression ''
195         config.${opt.dataDir} + "/secrets"
196       '';
197     };
198   };
200   ###### implementation
202   config = mkMerge [
204     (mkIf cfg.easyCerts {
205       services.kubernetes.pki.enable = mkDefault true;
206       services.kubernetes.caFile = caCert;
207     })
209     (mkIf (elem "master" cfg.roles) {
210       services.kubernetes.apiserver.enable = mkDefault true;
211       services.kubernetes.scheduler.enable = mkDefault true;
212       services.kubernetes.controllerManager.enable = mkDefault true;
213       services.kubernetes.addonManager.enable = mkDefault true;
214       services.kubernetes.proxy.enable = mkDefault true;
215       services.etcd.enable = true; # Cannot mkDefault because of flannel default options
216       services.kubernetes.kubelet = {
217         enable = mkDefault true;
218         taints = mkIf (!(elem "node" cfg.roles)) {
219           master = {
220             key = "node-role.kubernetes.io/master";
221             value = "true";
222             effect = "NoSchedule";
223           };
224         };
225       };
226     })
229     (mkIf (all (el: el == "master") cfg.roles) {
230       # if this node is only a master make it unschedulable by default
231       services.kubernetes.kubelet.unschedulable = mkDefault true;
232     })
234     (mkIf (elem "node" cfg.roles) {
235       services.kubernetes.kubelet.enable = mkDefault true;
236       services.kubernetes.proxy.enable = mkDefault true;
237     })
239     # Using "services.kubernetes.roles" will automatically enable easyCerts and flannel
240     (mkIf (cfg.roles != []) {
241       services.kubernetes.flannel.enable = mkDefault true;
242       services.flannel.etcd.endpoints = mkDefault etcdEndpoints;
243       services.kubernetes.easyCerts = mkDefault true;
244     })
246     (mkIf cfg.apiserver.enable {
247       services.kubernetes.pki.etcClusterAdminKubeconfig = mkDefault "kubernetes/cluster-admin.kubeconfig";
248       services.kubernetes.apiserver.etcd.servers = mkDefault etcdEndpoints;
249     })
251     (mkIf cfg.kubelet.enable {
252       virtualisation.containerd = {
253         enable = mkDefault true;
254         settings = mapAttrsRecursive (name: mkDefault) defaultContainerdSettings;
255       };
256     })
258     (mkIf (cfg.apiserver.enable || cfg.controllerManager.enable) {
259       services.kubernetes.pki.certs = {
260         serviceAccount = mkCert {
261           name = "service-account";
262           CN = "system:service-account-signer";
263           action = ''
264             systemctl restart \
265               kube-apiserver.service \
266               kube-controller-manager.service
267           '';
268         };
269       };
270     })
272     (mkIf (
273         cfg.apiserver.enable ||
274         cfg.scheduler.enable ||
275         cfg.controllerManager.enable ||
276         cfg.kubelet.enable ||
277         cfg.proxy.enable ||
278         cfg.addonManager.enable
279     ) {
280       systemd.targets.kubernetes = {
281         description = "Kubernetes";
282         wantedBy = [ "multi-user.target" ];
283       };
285       systemd.tmpfiles.rules = [
286         "d /opt/cni/bin 0755 root root -"
287         "d /run/kubernetes 0755 kubernetes kubernetes -"
288         "d ${cfg.dataDir} 0755 kubernetes kubernetes -"
289       ];
291       users.users.kubernetes = {
292         uid = config.ids.uids.kubernetes;
293         description = "Kubernetes user";
294         group = "kubernetes";
295         home = cfg.dataDir;
296         createHome = true;
297         homeMode = "755";
298       };
299       users.groups.kubernetes.gid = config.ids.gids.kubernetes;
301       # dns addon is enabled by default
302       services.kubernetes.addons.dns.enable = mkDefault true;
304       services.kubernetes.apiserverAddress = mkDefault ("https://${if cfg.apiserver.advertiseAddress != null
305                           then cfg.apiserver.advertiseAddress
306                           else "${cfg.masterAddress}:${toString cfg.apiserver.securePort}"}");
307     })
308   ];
310   meta.buildDocsInSandbox = false;