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