at 18.03-beta 39 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.kubernetes; 7 8 skipAttrs = attrs: map (filterAttrs (k: v: k != "enable")) 9 (filter (v: !(hasAttr "enable" v) || v.enable) attrs); 10 11 infraContainer = pkgs.dockerTools.buildImage { 12 name = "pause"; 13 tag = "latest"; 14 contents = cfg.package.pause; 15 config.Cmd = "/bin/pause"; 16 }; 17 18 mkKubeConfig = name: cfg: pkgs.writeText "${name}-kubeconfig" (builtins.toJSON { 19 apiVersion = "v1"; 20 kind = "Config"; 21 clusters = [{ 22 name = "local"; 23 cluster.certificate-authority = cfg.caFile; 24 cluster.server = cfg.server; 25 }]; 26 users = [{ 27 name = "kubelet"; 28 user = { 29 client-certificate = cfg.certFile; 30 client-key = cfg.keyFile; 31 }; 32 }]; 33 contexts = [{ 34 context = { 35 cluster = "local"; 36 user = "kubelet"; 37 }; 38 current-context = "kubelet-context"; 39 }]; 40 }); 41 42 mkKubeConfigOptions = prefix: { 43 server = mkOption { 44 description = "${prefix} kube-apiserver server address."; 45 default = "http://${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 46 type = types.str; 47 }; 48 49 caFile = mkOption { 50 description = "${prefix} certificate authrority file used to connect to kube-apiserver."; 51 type = types.nullOr types.path; 52 default = cfg.caFile; 53 }; 54 55 certFile = mkOption { 56 description = "${prefix} client certificate file used to connect to kube-apiserver."; 57 type = types.nullOr types.path; 58 default = null; 59 }; 60 61 keyFile = mkOption { 62 description = "${prefix} client key file used to connect to kube-apiserver."; 63 type = types.nullOr types.path; 64 default = null; 65 }; 66 }; 67 68 kubeConfigDefaults = { 69 server = mkDefault cfg.kubeconfig.server; 70 caFile = mkDefault cfg.kubeconfig.caFile; 71 certFile = mkDefault cfg.kubeconfig.certFile; 72 keyFile = mkDefault cfg.kubeconfig.keyFile; 73 }; 74 75 cniConfig = pkgs.buildEnv { 76 name = "kubernetes-cni-config"; 77 paths = imap (i: entry: 78 pkgs.writeTextDir "${toString (10+i)}-${entry.type}.conf" (builtins.toJSON entry) 79 ) cfg.kubelet.cni.config; 80 }; 81 82 manifests = pkgs.buildEnv { 83 name = "kubernetes-manifests"; 84 paths = mapAttrsToList (name: manifest: 85 pkgs.writeTextDir "${name}.json" (builtins.toJSON manifest) 86 ) cfg.kubelet.manifests; 87 }; 88 89 addons = pkgs.runCommand "kubernetes-addons" { } '' 90 mkdir -p $out 91 # since we are mounting the addons to the addon manager, they need to be copied 92 ${concatMapStringsSep ";" (a: "cp -v ${a}/* $out/") (mapAttrsToList (name: addon: 93 pkgs.writeTextDir "${name}.json" (builtins.toJSON addon) 94 ) (cfg.addonManager.addons))} 95 ''; 96 97 taintOptions = { name, ... }: { 98 options = { 99 key = mkOption { 100 description = "Key of taint."; 101 default = name; 102 type = types.str; 103 }; 104 value = mkOption { 105 description = "Value of taint."; 106 type = types.str; 107 }; 108 effect = mkOption { 109 description = "Effect of taint."; 110 example = "NoSchedule"; 111 type = types.enum ["NoSchedule" "PreferNoSchedule" "NoExecute"]; 112 }; 113 }; 114 }; 115 116 taints = concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") (mapAttrsToList (n: v: v) cfg.kubelet.taints); 117 118 # needed for flannel to pass options to docker 119 mkDockerOpts = pkgs.runCommand "mk-docker-opts" { 120 buildInputs = [ pkgs.makeWrapper ]; 121 } '' 122 mkdir -p $out 123 cp ${pkgs.kubernetes.src}/cluster/centos/node/bin/mk-docker-opts.sh $out/mk-docker-opts.sh 124 125 # bashInteractive needed for `compgen` 126 makeWrapper ${pkgs.bashInteractive}/bin/bash $out/mk-docker-opts --add-flags "$out/mk-docker-opts.sh" 127 ''; 128in { 129 130 ###### interface 131 132 options.services.kubernetes = { 133 roles = mkOption { 134 description = '' 135 Kubernetes role that this machine should take. 136 137 Master role will enable etcd, apiserver, scheduler and controller manager 138 services. Node role will enable etcd, docker, kubelet and proxy services. 139 ''; 140 default = []; 141 type = types.listOf (types.enum ["master" "node"]); 142 }; 143 144 package = mkOption { 145 description = "Kubernetes package to use."; 146 type = types.package; 147 default = pkgs.kubernetes; 148 defaultText = "pkgs.kubernetes"; 149 }; 150 151 verbose = mkOption { 152 description = "Kubernetes enable verbose mode for debugging."; 153 default = false; 154 type = types.bool; 155 }; 156 157 etcd = { 158 servers = mkOption { 159 description = "List of etcd servers. By default etcd is started, except if this option is changed."; 160 default = ["http://127.0.0.1:2379"]; 161 type = types.listOf types.str; 162 }; 163 164 keyFile = mkOption { 165 description = "Etcd key file."; 166 default = null; 167 type = types.nullOr types.path; 168 }; 169 170 certFile = mkOption { 171 description = "Etcd cert file."; 172 default = null; 173 type = types.nullOr types.path; 174 }; 175 176 caFile = mkOption { 177 description = "Etcd ca file."; 178 default = cfg.caFile; 179 type = types.nullOr types.path; 180 }; 181 }; 182 183 kubeconfig = mkKubeConfigOptions "Default kubeconfig"; 184 185 caFile = mkOption { 186 description = "Default kubernetes certificate authority"; 187 type = types.nullOr types.path; 188 default = null; 189 }; 190 191 dataDir = mkOption { 192 description = "Kubernetes root directory for managing kubelet files."; 193 default = "/var/lib/kubernetes"; 194 type = types.path; 195 }; 196 197 featureGates = mkOption { 198 description = "List set of feature gates"; 199 default = []; 200 type = types.listOf types.str; 201 }; 202 203 apiserver = { 204 enable = mkOption { 205 description = "Whether to enable Kubernetes apiserver."; 206 default = false; 207 type = types.bool; 208 }; 209 210 featureGates = mkOption { 211 description = "List set of feature gates"; 212 default = cfg.featureGates; 213 type = types.listOf types.str; 214 }; 215 216 address = mkOption { 217 description = "Kubernetes apiserver listening address."; 218 default = "127.0.0.1"; 219 type = types.str; 220 }; 221 222 publicAddress = mkOption { 223 description = '' 224 Kubernetes apiserver public listening address used for read only and 225 secure port. 226 ''; 227 default = cfg.apiserver.address; 228 type = types.str; 229 }; 230 231 advertiseAddress = mkOption { 232 description = '' 233 Kubernetes apiserver IP address on which to advertise the apiserver 234 to members of the cluster. This address must be reachable by the rest 235 of the cluster. 236 ''; 237 default = null; 238 type = types.nullOr types.str; 239 }; 240 241 storageBackend = mkOption { 242 description = '' 243 Kubernetes apiserver storage backend. 244 ''; 245 default = "etcd3"; 246 type = types.enum ["etcd2" "etcd3"]; 247 }; 248 249 port = mkOption { 250 description = "Kubernetes apiserver listening port."; 251 default = 8080; 252 type = types.int; 253 }; 254 255 securePort = mkOption { 256 description = "Kubernetes apiserver secure port."; 257 default = 443; 258 type = types.int; 259 }; 260 261 tlsCertFile = mkOption { 262 description = "Kubernetes apiserver certificate file."; 263 default = null; 264 type = types.nullOr types.path; 265 }; 266 267 tlsKeyFile = mkOption { 268 description = "Kubernetes apiserver private key file."; 269 default = null; 270 type = types.nullOr types.path; 271 }; 272 273 clientCaFile = mkOption { 274 description = "Kubernetes apiserver CA file for client auth."; 275 default = cfg.caFile; 276 type = types.nullOr types.path; 277 }; 278 279 tokenAuthFile = mkOption { 280 description = '' 281 Kubernetes apiserver token authentication file. See 282 <link xlink:href="http://kubernetes.io/docs/admin/authentication.html"/> 283 ''; 284 default = null; 285 type = types.nullOr types.path; 286 }; 287 288 basicAuthFile = mkOption { 289 description = '' 290 Kubernetes apiserver basic authentication file. See 291 <link xlink:href="http://kubernetes.io/docs/admin/authentication.html"/> 292 ''; 293 default = pkgs.writeText "users" '' 294 kubernetes,admin,0 295 ''; 296 type = types.nullOr types.path; 297 }; 298 299 authorizationMode = mkOption { 300 description = '' 301 Kubernetes apiserver authorization mode (AlwaysAllow/AlwaysDeny/ABAC/RBAC). See 302 <link xlink:href="http://kubernetes.io/docs/admin/authorization.html"/> 303 ''; 304 default = ["RBAC" "Node"]; 305 type = types.listOf (types.enum ["AlwaysAllow" "AlwaysDeny" "ABAC" "RBAC" "Node"]); 306 }; 307 308 authorizationPolicy = mkOption { 309 description = '' 310 Kubernetes apiserver authorization policy file. See 311 <link xlink:href="http://kubernetes.io/docs/admin/authorization.html"/> 312 ''; 313 default = []; 314 type = types.listOf types.attrs; 315 }; 316 317 allowPrivileged = mkOption { 318 description = "Whether to allow privileged containers on Kubernetes."; 319 default = true; 320 type = types.bool; 321 }; 322 323 serviceClusterIpRange = mkOption { 324 description = '' 325 A CIDR notation IP range from which to assign service cluster IPs. 326 This must not overlap with any IP ranges assigned to nodes for pods. 327 ''; 328 default = "10.0.0.0/24"; 329 type = types.str; 330 }; 331 332 runtimeConfig = mkOption { 333 description = '' 334 Api runtime configuration. See 335 <link xlink:href="http://kubernetes.io/docs/admin/cluster-management.html"/> 336 ''; 337 default = "authentication.k8s.io/v1beta1=true"; 338 example = "api/all=false,api/v1=true"; 339 type = types.str; 340 }; 341 342 admissionControl = mkOption { 343 description = '' 344 Kubernetes admission control plugins to use. See 345 <link xlink:href="http://kubernetes.io/docs/admin/admission-controllers/"/> 346 ''; 347 default = ["NamespaceLifecycle" "LimitRanger" "ServiceAccount" "ResourceQuota" "DefaultStorageClass" "DefaultTolerationSeconds" "NodeRestriction"]; 348 example = [ 349 "NamespaceLifecycle" "NamespaceExists" "LimitRanger" 350 "SecurityContextDeny" "ServiceAccount" "ResourceQuota" 351 "PodSecurityPolicy" "NodeRestriction" "DefaultStorageClass" 352 ]; 353 type = types.listOf types.str; 354 }; 355 356 serviceAccountKeyFile = mkOption { 357 description = '' 358 Kubernetes apiserver PEM-encoded x509 RSA private or public key file, 359 used to verify ServiceAccount tokens. By default tls private key file 360 is used. 361 ''; 362 default = null; 363 type = types.nullOr types.path; 364 }; 365 366 kubeletClientCaFile = mkOption { 367 description = "Path to a cert file for connecting to kubelet."; 368 default = cfg.caFile; 369 type = types.nullOr types.path; 370 }; 371 372 kubeletClientCertFile = mkOption { 373 description = "Client certificate to use for connections to kubelet."; 374 default = null; 375 type = types.nullOr types.path; 376 }; 377 378 kubeletClientKeyFile = mkOption { 379 description = "Key to use for connections to kubelet."; 380 default = null; 381 type = types.nullOr types.path; 382 }; 383 384 kubeletHttps = mkOption { 385 description = "Whether to use https for connections to kubelet."; 386 default = true; 387 type = types.bool; 388 }; 389 390 extraOpts = mkOption { 391 description = "Kubernetes apiserver extra command line options."; 392 default = ""; 393 type = types.str; 394 }; 395 }; 396 397 scheduler = { 398 enable = mkOption { 399 description = "Whether to enable Kubernetes scheduler."; 400 default = false; 401 type = types.bool; 402 }; 403 404 featureGates = mkOption { 405 description = "List set of feature gates"; 406 default = cfg.featureGates; 407 type = types.listOf types.str; 408 }; 409 410 address = mkOption { 411 description = "Kubernetes scheduler listening address."; 412 default = "127.0.0.1"; 413 type = types.str; 414 }; 415 416 port = mkOption { 417 description = "Kubernetes scheduler listening port."; 418 default = 10251; 419 type = types.int; 420 }; 421 422 leaderElect = mkOption { 423 description = "Whether to start leader election before executing main loop."; 424 type = types.bool; 425 default = true; 426 }; 427 428 kubeconfig = mkKubeConfigOptions "Kubernetes scheduler"; 429 430 extraOpts = mkOption { 431 description = "Kubernetes scheduler extra command line options."; 432 default = ""; 433 type = types.str; 434 }; 435 }; 436 437 controllerManager = { 438 enable = mkOption { 439 description = "Whether to enable Kubernetes controller manager."; 440 default = false; 441 type = types.bool; 442 }; 443 444 featureGates = mkOption { 445 description = "List set of feature gates"; 446 default = cfg.featureGates; 447 type = types.listOf types.str; 448 }; 449 450 address = mkOption { 451 description = "Kubernetes controller manager listening address."; 452 default = "127.0.0.1"; 453 type = types.str; 454 }; 455 456 port = mkOption { 457 description = "Kubernetes controller manager listening port."; 458 default = 10252; 459 type = types.int; 460 }; 461 462 leaderElect = mkOption { 463 description = "Whether to start leader election before executing main loop."; 464 type = types.bool; 465 default = true; 466 }; 467 468 serviceAccountKeyFile = mkOption { 469 description = '' 470 Kubernetes controller manager PEM-encoded private RSA key file used to 471 sign service account tokens 472 ''; 473 default = null; 474 type = types.nullOr types.path; 475 }; 476 477 rootCaFile = mkOption { 478 description = '' 479 Kubernetes controller manager certificate authority file included in 480 service account's token secret. 481 ''; 482 default = cfg.caFile; 483 type = types.nullOr types.path; 484 }; 485 486 kubeconfig = mkKubeConfigOptions "Kubernetes controller manager"; 487 488 extraOpts = mkOption { 489 description = "Kubernetes controller manager extra command line options."; 490 default = ""; 491 type = types.str; 492 }; 493 }; 494 495 kubelet = { 496 enable = mkOption { 497 description = "Whether to enable Kubernetes kubelet."; 498 default = false; 499 type = types.bool; 500 }; 501 502 featureGates = mkOption { 503 description = "List set of feature gates"; 504 default = cfg.featureGates; 505 type = types.listOf types.str; 506 }; 507 508 seedDockerImages = mkOption { 509 description = "List of docker images to preload on system"; 510 default = []; 511 type = types.listOf types.package; 512 }; 513 514 registerNode = mkOption { 515 description = "Whether to auto register kubelet with API server."; 516 default = true; 517 type = types.bool; 518 }; 519 520 address = mkOption { 521 description = "Kubernetes kubelet info server listening address."; 522 default = "0.0.0.0"; 523 type = types.str; 524 }; 525 526 port = mkOption { 527 description = "Kubernetes kubelet info server listening port."; 528 default = 10250; 529 type = types.int; 530 }; 531 532 tlsCertFile = mkOption { 533 description = "File containing x509 Certificate for HTTPS."; 534 default = null; 535 type = types.nullOr types.path; 536 }; 537 538 tlsKeyFile = mkOption { 539 description = "File containing x509 private key matching tlsCertFile."; 540 default = null; 541 type = types.nullOr types.path; 542 }; 543 544 clientCaFile = mkOption { 545 description = "Kubernetes apiserver CA file for client authentication."; 546 default = cfg.caFile; 547 type = types.nullOr types.path; 548 }; 549 550 healthz = { 551 bind = mkOption { 552 description = "Kubernetes kubelet healthz listening address."; 553 default = "127.0.0.1"; 554 type = types.str; 555 }; 556 557 port = mkOption { 558 description = "Kubernetes kubelet healthz port."; 559 default = 10248; 560 type = types.int; 561 }; 562 }; 563 564 hostname = mkOption { 565 description = "Kubernetes kubelet hostname override."; 566 default = config.networking.hostName; 567 type = types.str; 568 }; 569 570 allowPrivileged = mkOption { 571 description = "Whether to allow Kubernetes containers to request privileged mode."; 572 default = true; 573 type = types.bool; 574 }; 575 576 cadvisorPort = mkOption { 577 description = "Kubernetes kubelet local cadvisor port."; 578 default = 4194; 579 type = types.int; 580 }; 581 582 clusterDns = mkOption { 583 description = "Use alternative DNS."; 584 default = "10.1.0.1"; 585 type = types.str; 586 }; 587 588 clusterDomain = mkOption { 589 description = "Use alternative domain."; 590 default = config.services.kubernetes.addons.dns.clusterDomain; 591 type = types.str; 592 }; 593 594 networkPlugin = mkOption { 595 description = "Network plugin to use by Kubernetes."; 596 type = types.nullOr (types.enum ["cni" "kubenet"]); 597 default = "kubenet"; 598 }; 599 600 cni = { 601 packages = mkOption { 602 description = "List of network plugin packages to install."; 603 type = types.listOf types.package; 604 default = []; 605 }; 606 607 config = mkOption { 608 description = "Kubernetes CNI configuration."; 609 type = types.listOf types.attrs; 610 default = []; 611 example = literalExample '' 612 [{ 613 "cniVersion": "0.2.0", 614 "name": "mynet", 615 "type": "bridge", 616 "bridge": "cni0", 617 "isGateway": true, 618 "ipMasq": true, 619 "ipam": { 620 "type": "host-local", 621 "subnet": "10.22.0.0/16", 622 "routes": [ 623 { "dst": "0.0.0.0/0" } 624 ] 625 } 626 } { 627 "cniVersion": "0.2.0", 628 "type": "loopback" 629 }] 630 ''; 631 }; 632 }; 633 634 manifests = mkOption { 635 description = "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)"; 636 type = types.attrsOf types.attrs; 637 default = {}; 638 }; 639 640 applyManifests = mkOption { 641 description = "Whether to apply manifests (this is true for master node)."; 642 default = false; 643 type = types.bool; 644 }; 645 646 unschedulable = mkOption { 647 description = "Whether to set node taint to unschedulable=true as it is the case of node that has only master role."; 648 default = false; 649 type = types.bool; 650 }; 651 652 taints = mkOption { 653 description = "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/)."; 654 default = {}; 655 type = types.attrsOf (types.submodule [ taintOptions ]); 656 }; 657 658 nodeIp = mkOption { 659 description = "IP address of the node. If set, kubelet will use this IP address for the node."; 660 default = null; 661 type = types.nullOr types.str; 662 }; 663 664 kubeconfig = mkKubeConfigOptions "Kubelet"; 665 666 extraOpts = mkOption { 667 description = "Kubernetes kubelet extra command line options."; 668 default = ""; 669 type = types.str; 670 }; 671 }; 672 673 proxy = { 674 enable = mkOption { 675 description = "Whether to enable Kubernetes proxy."; 676 default = false; 677 type = types.bool; 678 }; 679 680 featureGates = mkOption { 681 description = "List set of feature gates"; 682 default = cfg.featureGates; 683 type = types.listOf types.str; 684 }; 685 686 address = mkOption { 687 description = "Kubernetes proxy listening address."; 688 default = "0.0.0.0"; 689 type = types.str; 690 }; 691 692 kubeconfig = mkKubeConfigOptions "Kubernetes proxy"; 693 694 extraOpts = mkOption { 695 description = "Kubernetes proxy extra command line options."; 696 default = ""; 697 type = types.str; 698 }; 699 }; 700 701 addonManager = { 702 enable = mkOption { 703 description = "Whether to enable Kubernetes addon manager."; 704 default = false; 705 type = types.bool; 706 }; 707 708 addons = mkOption { 709 description = "Kubernetes addons (any kind of Kubernetes resource can be an addon)."; 710 default = { }; 711 type = types.attrsOf (types.either types.attrs (types.listOf types.attrs)); 712 example = literalExample '' 713 { 714 "my-service" = { 715 "apiVersion" = "v1"; 716 "kind" = "Service"; 717 "metadata" = { 718 "name" = "my-service"; 719 "namespace" = "default"; 720 }; 721 "spec" = { ... }; 722 }; 723 } 724 // import <nixpkgs/nixos/modules/services/cluster/kubernetes/dashboard.nix> { cfg = config.services.kubernetes; }; 725 ''; 726 }; 727 }; 728 729 path = mkOption { 730 description = "Packages added to the services' PATH environment variable. Both the bin and sbin subdirectories of each package are added."; 731 type = types.listOf types.package; 732 default = []; 733 }; 734 735 clusterCidr = mkOption { 736 description = "Kubernetes controller manager and proxy CIDR Range for Pods in cluster."; 737 default = "10.1.0.0/16"; 738 type = types.str; 739 }; 740 741 flannel.enable = mkOption { 742 description = "Whether to enable flannel networking"; 743 default = false; 744 type = types.bool; 745 }; 746 747 }; 748 749 ###### implementation 750 751 config = mkMerge [ 752 (mkIf cfg.kubelet.enable { 753 services.kubernetes.kubelet.seedDockerImages = [infraContainer]; 754 755 systemd.services.kubelet-bootstrap = { 756 description = "Boostrap Kubelet"; 757 wantedBy = ["kubernetes.target"]; 758 after = ["docker.service" "network.target"]; 759 path = with pkgs; [ docker ]; 760 script = '' 761 ${concatMapStrings (img: '' 762 echo "Seeding docker image: ${img}" 763 docker load <${img} 764 '') cfg.kubelet.seedDockerImages} 765 766 rm /opt/cni/bin/* || true 767 ${concatMapStrings (package: '' 768 echo "Linking cni package: ${package}" 769 ln -fs ${package.plugins}/* /opt/cni/bin 770 '') cfg.kubelet.cni.packages} 771 ''; 772 serviceConfig = { 773 Slice = "kubernetes.slice"; 774 Type = "oneshot"; 775 }; 776 }; 777 778 systemd.services.kubelet = { 779 description = "Kubernetes Kubelet Service"; 780 wantedBy = [ "kubernetes.target" ]; 781 after = [ "network.target" "docker.service" "kube-apiserver.service" "kubelet-bootstrap.service" ]; 782 path = with pkgs; [ gitMinimal openssh docker utillinux iproute ethtool thin-provisioning-tools iptables socat ] ++ cfg.path; 783 serviceConfig = { 784 Slice = "kubernetes.slice"; 785 ExecStart = ''${cfg.package}/bin/kubelet \ 786 ${optionalString cfg.kubelet.applyManifests 787 "--pod-manifest-path=${manifests}"} \ 788 ${optionalString (taints != "") 789 "--register-with-taints=${taints}"} \ 790 --kubeconfig=${mkKubeConfig "kubelet" cfg.kubelet.kubeconfig} \ 791 --require-kubeconfig \ 792 --address=${cfg.kubelet.address} \ 793 --port=${toString cfg.kubelet.port} \ 794 --register-node=${boolToString cfg.kubelet.registerNode} \ 795 ${optionalString (cfg.kubelet.tlsCertFile != null) 796 "--tls-cert-file=${cfg.kubelet.tlsCertFile}"} \ 797 ${optionalString (cfg.kubelet.tlsKeyFile != null) 798 "--tls-private-key-file=${cfg.kubelet.tlsKeyFile}"} \ 799 ${optionalString (cfg.kubelet.clientCaFile != null) 800 "--client-ca-file=${cfg.kubelet.clientCaFile}"} \ 801 --authentication-token-webhook \ 802 --authentication-token-webhook-cache-ttl="10s" \ 803 --authorization-mode=Webhook \ 804 --healthz-bind-address=${cfg.kubelet.healthz.bind} \ 805 --healthz-port=${toString cfg.kubelet.healthz.port} \ 806 --hostname-override=${cfg.kubelet.hostname} \ 807 --allow-privileged=${boolToString cfg.kubelet.allowPrivileged} \ 808 --root-dir=${cfg.dataDir} \ 809 --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \ 810 ${optionalString (cfg.kubelet.clusterDns != "") 811 "--cluster-dns=${cfg.kubelet.clusterDns}"} \ 812 ${optionalString (cfg.kubelet.clusterDomain != "") 813 "--cluster-domain=${cfg.kubelet.clusterDomain}"} \ 814 --pod-infra-container-image=pause \ 815 ${optionalString (cfg.kubelet.networkPlugin != null) 816 "--network-plugin=${cfg.kubelet.networkPlugin}"} \ 817 --cni-conf-dir=${cniConfig} \ 818 --hairpin-mode=hairpin-veth \ 819 ${optionalString (cfg.kubelet.nodeIp != null) 820 "--node-ip=${cfg.kubelet.nodeIp}"} \ 821 ${optionalString (cfg.kubelet.featureGates != []) 822 "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.kubelet.featureGates}"} \ 823 ${optionalString cfg.verbose "--v=6 --log_flush_frequency=1s"} \ 824 ${cfg.kubelet.extraOpts} 825 ''; 826 WorkingDirectory = cfg.dataDir; 827 }; 828 }; 829 830 # Allways include cni plugins 831 services.kubernetes.kubelet.cni.packages = [pkgs.cni]; 832 833 boot.kernelModules = ["br_netfilter"]; 834 835 services.kubernetes.kubelet.kubeconfig = kubeConfigDefaults; 836 }) 837 838 (mkIf (cfg.kubelet.applyManifests && cfg.kubelet.enable) { 839 environment.etc = mapAttrs' (name: manifest: 840 nameValuePair "kubernetes/manifests/${name}.json" { 841 text = builtins.toJSON manifest; 842 mode = "0755"; 843 } 844 ) cfg.kubelet.manifests; 845 }) 846 847 (mkIf (cfg.kubelet.unschedulable && cfg.kubelet.enable) { 848 services.kubernetes.kubelet.taints.unschedulable = { 849 value = "true"; 850 effect = "NoSchedule"; 851 }; 852 }) 853 854 (mkIf cfg.apiserver.enable { 855 systemd.services.kube-apiserver = { 856 description = "Kubernetes Kubelet Service"; 857 wantedBy = [ "kubernetes.target" ]; 858 after = [ "network.target" "docker.service" ]; 859 serviceConfig = { 860 Slice = "kubernetes.slice"; 861 ExecStart = ''${cfg.package}/bin/kube-apiserver \ 862 --etcd-servers=${concatStringsSep "," cfg.etcd.servers} \ 863 ${optionalString (cfg.etcd.caFile != null) 864 "--etcd-cafile=${cfg.etcd.caFile}"} \ 865 ${optionalString (cfg.etcd.certFile != null) 866 "--etcd-certfile=${cfg.etcd.certFile}"} \ 867 ${optionalString (cfg.etcd.keyFile != null) 868 "--etcd-keyfile=${cfg.etcd.keyFile}"} \ 869 --insecure-port=${toString cfg.apiserver.port} \ 870 --bind-address=0.0.0.0 \ 871 ${optionalString (cfg.apiserver.advertiseAddress != null) 872 "--advertise-address=${cfg.apiserver.advertiseAddress}"} \ 873 --allow-privileged=${boolToString cfg.apiserver.allowPrivileged}\ 874 ${optionalString (cfg.apiserver.tlsCertFile != null) 875 "--tls-cert-file=${cfg.apiserver.tlsCertFile}"} \ 876 ${optionalString (cfg.apiserver.tlsKeyFile != null) 877 "--tls-private-key-file=${cfg.apiserver.tlsKeyFile}"} \ 878 ${optionalString (cfg.apiserver.tokenAuthFile != null) 879 "--token-auth-file=${cfg.apiserver.tokenAuthFile}"} \ 880 ${optionalString (cfg.apiserver.basicAuthFile != null) 881 "--basic-auth-file=${cfg.apiserver.basicAuthFile}"} \ 882 --kubelet-https=${if cfg.apiserver.kubeletHttps then "true" else "false"} \ 883 ${optionalString (cfg.apiserver.kubeletClientCaFile != null) 884 "--kubelet-certificate-authority=${cfg.apiserver.kubeletClientCaFile}"} \ 885 ${optionalString (cfg.apiserver.kubeletClientCertFile != null) 886 "--kubelet-client-certificate=${cfg.apiserver.kubeletClientCertFile}"} \ 887 ${optionalString (cfg.apiserver.kubeletClientKeyFile != null) 888 "--kubelet-client-key=${cfg.apiserver.kubeletClientKeyFile}"} \ 889 ${optionalString (cfg.apiserver.clientCaFile != null) 890 "--client-ca-file=${cfg.apiserver.clientCaFile}"} \ 891 --authorization-mode=${concatStringsSep "," cfg.apiserver.authorizationMode} \ 892 ${optionalString (elem "ABAC" cfg.apiserver.authorizationMode) 893 "--authorization-policy-file=${ 894 pkgs.writeText "kube-auth-policy.jsonl" 895 (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.apiserver.authorizationPolicy) 896 }" 897 } \ 898 --secure-port=${toString cfg.apiserver.securePort} \ 899 --service-cluster-ip-range=${cfg.apiserver.serviceClusterIpRange} \ 900 ${optionalString (cfg.apiserver.runtimeConfig != "") 901 "--runtime-config=${cfg.apiserver.runtimeConfig}"} \ 902 --admission_control=${concatStringsSep "," cfg.apiserver.admissionControl} \ 903 ${optionalString (cfg.apiserver.serviceAccountKeyFile!=null) 904 "--service-account-key-file=${cfg.apiserver.serviceAccountKeyFile}"} \ 905 ${optionalString cfg.verbose "--v=6"} \ 906 ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 907 --storage-backend=${cfg.apiserver.storageBackend} \ 908 ${optionalString (cfg.kubelet.featureGates != []) 909 "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.kubelet.featureGates}"} \ 910 ${cfg.apiserver.extraOpts} 911 ''; 912 WorkingDirectory = cfg.dataDir; 913 User = "kubernetes"; 914 Group = "kubernetes"; 915 AmbientCapabilities = "cap_net_bind_service"; 916 Restart = "on-failure"; 917 RestartSec = 5; 918 }; 919 }; 920 }) 921 922 (mkIf cfg.scheduler.enable { 923 systemd.services.kube-scheduler = { 924 description = "Kubernetes Scheduler Service"; 925 wantedBy = [ "kubernetes.target" ]; 926 after = [ "kube-apiserver.service" ]; 927 serviceConfig = { 928 Slice = "kubernetes.slice"; 929 ExecStart = ''${cfg.package}/bin/kube-scheduler \ 930 --address=${cfg.scheduler.address} \ 931 --port=${toString cfg.scheduler.port} \ 932 --leader-elect=${boolToString cfg.scheduler.leaderElect} \ 933 --kubeconfig=${mkKubeConfig "kube-scheduler" cfg.scheduler.kubeconfig} \ 934 ${optionalString cfg.verbose "--v=6"} \ 935 ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 936 ${optionalString (cfg.scheduler.featureGates != []) 937 "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.scheduler.featureGates}"} \ 938 ${cfg.scheduler.extraOpts} 939 ''; 940 WorkingDirectory = cfg.dataDir; 941 User = "kubernetes"; 942 Group = "kubernetes"; 943 }; 944 }; 945 946 services.kubernetes.scheduler.kubeconfig = kubeConfigDefaults; 947 }) 948 949 (mkIf cfg.controllerManager.enable { 950 systemd.services.kube-controller-manager = { 951 description = "Kubernetes Controller Manager Service"; 952 wantedBy = [ "kubernetes.target" ]; 953 after = [ "kube-apiserver.service" ]; 954 serviceConfig = { 955 RestartSec = "30s"; 956 Restart = "on-failure"; 957 Slice = "kubernetes.slice"; 958 ExecStart = ''${cfg.package}/bin/kube-controller-manager \ 959 --address=${cfg.controllerManager.address} \ 960 --port=${toString cfg.controllerManager.port} \ 961 --kubeconfig=${mkKubeConfig "kube-controller-manager" cfg.controllerManager.kubeconfig} \ 962 --leader-elect=${boolToString cfg.controllerManager.leaderElect} \ 963 ${if (cfg.controllerManager.serviceAccountKeyFile!=null) 964 then "--service-account-private-key-file=${cfg.controllerManager.serviceAccountKeyFile}" 965 else "--service-account-private-key-file=/var/run/kubernetes/apiserver.key"} \ 966 ${if (cfg.controllerManager.rootCaFile!=null) 967 then "--root-ca-file=${cfg.controllerManager.rootCaFile}" 968 else "--root-ca-file=/var/run/kubernetes/apiserver.crt"} \ 969 ${optionalString (cfg.clusterCidr!=null) 970 "--cluster-cidr=${cfg.clusterCidr}"} \ 971 --allocate-node-cidrs=true \ 972 ${optionalString (cfg.controllerManager.featureGates != []) 973 "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.controllerManager.featureGates}"} \ 974 ${optionalString cfg.verbose "--v=6"} \ 975 ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 976 ${cfg.controllerManager.extraOpts} 977 ''; 978 WorkingDirectory = cfg.dataDir; 979 User = "kubernetes"; 980 Group = "kubernetes"; 981 }; 982 path = cfg.path; 983 }; 984 985 services.kubernetes.controllerManager.kubeconfig = kubeConfigDefaults; 986 }) 987 988 (mkIf cfg.proxy.enable { 989 systemd.services.kube-proxy = { 990 description = "Kubernetes Proxy Service"; 991 wantedBy = [ "kubernetes.target" ]; 992 after = [ "kube-apiserver.service" ]; 993 path = [pkgs.iptables pkgs.conntrack_tools]; 994 serviceConfig = { 995 Slice = "kubernetes.slice"; 996 ExecStart = ''${cfg.package}/bin/kube-proxy \ 997 --kubeconfig=${mkKubeConfig "kube-proxy" cfg.proxy.kubeconfig} \ 998 --bind-address=${cfg.proxy.address} \ 999 ${optionalString (cfg.proxy.featureGates != []) 1000 "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.proxy.featureGates}"} \ 1001 ${optionalString cfg.verbose "--v=6"} \ 1002 ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 1003 ${optionalString (cfg.clusterCidr!=null) 1004 "--cluster-cidr=${cfg.clusterCidr}"} \ 1005 ${cfg.proxy.extraOpts} 1006 ''; 1007 WorkingDirectory = cfg.dataDir; 1008 }; 1009 }; 1010 1011 # kube-proxy needs iptables 1012 networking.firewall.enable = mkDefault true; 1013 1014 services.kubernetes.proxy.kubeconfig = kubeConfigDefaults; 1015 }) 1016 1017 (mkIf (any (el: el == "master") cfg.roles) { 1018 virtualisation.docker.enable = mkDefault true; 1019 services.kubernetes.kubelet.enable = mkDefault true; 1020 services.kubernetes.kubelet.allowPrivileged = mkDefault true; 1021 services.kubernetes.kubelet.applyManifests = mkDefault true; 1022 services.kubernetes.apiserver.enable = mkDefault true; 1023 services.kubernetes.scheduler.enable = mkDefault true; 1024 services.kubernetes.controllerManager.enable = mkDefault true; 1025 services.etcd.enable = mkDefault (cfg.etcd.servers == ["http://127.0.0.1:2379"]); 1026 services.kubernetes.addonManager.enable = mkDefault true; 1027 services.kubernetes.proxy.enable = mkDefault true; 1028 }) 1029 1030 # if this node is only a master make it unschedulable by default 1031 (mkIf (all (el: el == "master") cfg.roles) { 1032 services.kubernetes.kubelet.unschedulable = mkDefault true; 1033 }) 1034 1035 (mkIf (any (el: el == "node") cfg.roles) { 1036 virtualisation.docker = { 1037 enable = mkDefault true; 1038 1039 # kubernetes needs access to logs 1040 logDriver = mkDefault "json-file"; 1041 1042 # iptables must be disabled for kubernetes 1043 extraOptions = "--iptables=false --ip-masq=false"; 1044 }; 1045 1046 services.kubernetes.kubelet.enable = mkDefault true; 1047 services.kubernetes.proxy.enable = mkDefault true; 1048 }) 1049 1050 (mkIf cfg.addonManager.enable { 1051 environment.etc."kubernetes/addons".source = "${addons}/"; 1052 1053 systemd.services.kube-addon-manager = { 1054 description = "Kubernetes addon manager"; 1055 wantedBy = [ "kubernetes.target" ]; 1056 after = [ "kube-apiserver.service" ]; 1057 environment.ADDON_PATH = "/etc/kubernetes/addons/"; 1058 serviceConfig = { 1059 Slice = "kubernetes.slice"; 1060 ExecStart = "${cfg.package}/bin/kube-addons"; 1061 WorkingDirectory = cfg.dataDir; 1062 User = "kubernetes"; 1063 Group = "kubernetes"; 1064 }; 1065 }; 1066 }) 1067 1068 (mkIf ( 1069 cfg.apiserver.enable || 1070 cfg.scheduler.enable || 1071 cfg.controllerManager.enable || 1072 cfg.kubelet.enable || 1073 cfg.proxy.enable 1074 ) { 1075 systemd.targets.kubernetes = { 1076 description = "Kubernetes"; 1077 wantedBy = [ "multi-user.target" ]; 1078 }; 1079 1080 systemd.tmpfiles.rules = [ 1081 "d /opt/cni/bin 0755 root root -" 1082 "d /var/run/kubernetes 0755 kubernetes kubernetes -" 1083 "d /var/lib/kubernetes 0755 kubernetes kubernetes -" 1084 ]; 1085 1086 environment.systemPackages = [ cfg.package ]; 1087 users.extraUsers = singleton { 1088 name = "kubernetes"; 1089 uid = config.ids.uids.kubernetes; 1090 description = "Kubernetes user"; 1091 extraGroups = [ "docker" ]; 1092 group = "kubernetes"; 1093 home = cfg.dataDir; 1094 createHome = true; 1095 }; 1096 users.extraGroups.kubernetes.gid = config.ids.gids.kubernetes; 1097 1098 # dns addon is enabled by default 1099 services.kubernetes.addons.dns.enable = mkDefault true; 1100 }) 1101 1102 (mkIf cfg.flannel.enable { 1103 services.flannel = { 1104 enable = mkDefault true; 1105 network = mkDefault cfg.clusterCidr; 1106 etcd = mkDefault { 1107 endpoints = cfg.etcd.servers; 1108 inherit (cfg.etcd) caFile certFile keyFile; 1109 }; 1110 }; 1111 1112 services.kubernetes.kubelet = { 1113 networkPlugin = mkDefault "cni"; 1114 cni.config = mkDefault [{ 1115 name = "mynet"; 1116 type = "flannel"; 1117 delegate = { 1118 isDefaultGateway = true; 1119 bridge = "docker0"; 1120 }; 1121 }]; 1122 }; 1123 1124 systemd.services."mk-docker-opts" = { 1125 description = "Pre-Docker Actions"; 1126 wantedBy = [ "flannel.service" ]; 1127 before = [ "docker.service" ]; 1128 after = [ "flannel.service" ]; 1129 path = [ pkgs.gawk pkgs.gnugrep ]; 1130 script = '' 1131 mkdir -p /run/flannel 1132 ${mkDockerOpts}/mk-docker-opts -d /run/flannel/docker 1133 ''; 1134 serviceConfig.Type = "oneshot"; 1135 }; 1136 systemd.services.docker.serviceConfig.EnvironmentFile = "/run/flannel/docker"; 1137 1138 # read environment variables generated by mk-docker-opts 1139 virtualisation.docker.extraOptions = "$DOCKER_OPTS"; 1140 1141 networking.firewall.allowedUDPPorts = [ 1142 8285 # flannel udp 1143 8472 # flannel vxlan 1144 ]; 1145 }) 1146 ]; 1147}