Merge pull request #19023 from offlinehacker/kube-update

WIP: kubernetes update package and module

authored by Jaka Hudoklin and committed by GitHub 3b500d37 e29e2467

+830 -349
+409 -189
nixos/modules/services/cluster/kubernetes.nix
··· 5 5 let 6 6 cfg = config.services.kubernetes; 7 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 + kubeconfig = pkgs.writeText "kubeconfig" (builtins.toJSON { 19 + apiVersion = "v1"; 20 + kind = "Config"; 21 + clusters = [{ 22 + name = "local"; 23 + cluster.certificate-authority = cfg.kubeconfig.caFile; 24 + cluster.server = cfg.kubeconfig.server; 25 + }]; 26 + users = [{ 27 + name = "kubelet"; 28 + user = { 29 + client-certificate = cfg.kubeconfig.certFile; 30 + client-key = cfg.kubeconfig.keyFile; 31 + }; 32 + }]; 33 + contexts = [{ 34 + context = { 35 + cluster = "local"; 36 + user = "kubelet"; 37 + }; 38 + current-context = "kubelet-context"; 39 + }]; 40 + }); 41 + 42 + policyFile = pkgs.writeText "kube-policy" 43 + concatStringsSep "\n" (map (builtins.toJSON cfg.apiserver.authorizationPolicy)); 44 + 45 + cniConfig = pkgs.buildEnv { 46 + name = "kubernetes-cni-config"; 47 + paths = imap (i: entry: 48 + pkgs.writeTextDir "${10+i}-${entry.type}.conf" (builtins.toJSON entry) 49 + ) cfg.kubelet.cni.config; 50 + }; 51 + 52 + manifests = pkgs.buildEnv { 53 + name = "kubernetes-manifests"; 54 + paths = mapAttrsToList (name: manifest: 55 + pkgs.writeTextDir "${name}.json" (builtins.toJSON manifest) 56 + ) cfg.kubelet.manifests; 57 + }; 58 + 8 59 in { 9 60 10 61 ###### interface 11 62 12 63 options.services.kubernetes = { 64 + roles = mkOption { 65 + description = '' 66 + Kubernetes role that this machine should take. 67 + 68 + Master role will enable etcd, apiserver, scheduler and controller manager 69 + services. Node role will enable etcd, docker, kubelet and proxy services. 70 + ''; 71 + default = []; 72 + type = types.listOf (types.enum ["master" "node"]); 73 + }; 74 + 13 75 package = mkOption { 14 76 description = "Kubernetes package to use."; 15 77 type = types.package; 78 + default = pkgs.kubernetes; 16 79 }; 17 80 18 81 verbose = mkOption { ··· 21 84 type = types.bool; 22 85 }; 23 86 24 - etcdServers = mkOption { 25 - description = "Kubernetes list of etcd servers to watch."; 26 - default = [ "127.0.0.1:2379" ]; 27 - type = types.listOf types.str; 87 + etcd = { 88 + servers = mkOption { 89 + description = "List of etcd servers. By default etcd is started, except if this option is changed."; 90 + default = ["http://127.0.0.1:2379"]; 91 + type = types.listOf types.str; 92 + }; 93 + 94 + keyFile = mkOption { 95 + description = "Etcd key file"; 96 + default = null; 97 + type = types.nullOr types.path; 98 + }; 99 + 100 + certFile = mkOption { 101 + description = "Etcd cert file"; 102 + default = null; 103 + type = types.nullOr types.path; 104 + }; 105 + 106 + caFile = mkOption { 107 + description = "Etcd ca file"; 108 + default = null; 109 + type = types.nullOr types.path; 110 + }; 28 111 }; 29 112 30 - roles = mkOption { 31 - description = '' 32 - Kubernetes role that this machine should take. 113 + kubeconfig = { 114 + server = mkOption { 115 + description = "Kubernetes apiserver server address"; 116 + default = "http://${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 117 + type = types.str; 118 + }; 33 119 34 - Master role will enable etcd, apiserver, scheduler and controller manager 35 - services. Node role will enable etcd, docker, kubelet and proxy services. 36 - ''; 37 - default = []; 38 - type = types.listOf (types.enum ["master" "node"]); 120 + caFile = mkOption { 121 + description = "Certificate authrority file to use to connect to kuberentes apiserver"; 122 + type = types.nullOr types.path; 123 + default = null; 124 + }; 125 + 126 + certFile = mkOption { 127 + description = "Client certificate file to use to connect to kubernetes"; 128 + type = types.nullOr types.path; 129 + default = null; 130 + }; 131 + 132 + keyFile = mkOption { 133 + description = "Client key file to use to connect to kubernetes"; 134 + type = types.nullOr types.path; 135 + default = null; 136 + }; 39 137 }; 40 138 41 139 dataDir = mkOption { 42 140 description = "Kubernetes root directory for managing kubelet files."; 43 141 default = "/var/lib/kubernetes"; 44 142 type = types.path; 45 - }; 46 - 47 - dockerCfg = mkOption { 48 - description = "Kubernetes contents of dockercfg file."; 49 - default = ""; 50 - type = types.lines; 51 143 }; 52 144 53 145 apiserver = { ··· 72 164 type = types.str; 73 165 }; 74 166 167 + advertiseAddress = mkOption { 168 + description = '' 169 + Kubernetes apiserver IP address on which to advertise the apiserver 170 + to members of the cluster. This address must be reachable by the rest 171 + of the cluster. 172 + ''; 173 + default = null; 174 + type = types.nullOr types.str; 175 + }; 176 + 75 177 port = mkOption { 76 178 description = "Kubernetes apiserver listening port."; 77 179 default = 8080; ··· 80 182 81 183 securePort = mkOption { 82 184 description = "Kubernetes apiserver secure port."; 83 - default = 6443; 185 + default = 443; 84 186 type = types.int; 85 187 }; 86 188 87 189 tlsCertFile = mkOption { 88 190 description = "Kubernetes apiserver certificate file."; 89 - default = ""; 90 - type = types.str; 191 + default = null; 192 + type = types.nullOr types.path; 91 193 }; 92 194 93 - tlsPrivateKeyFile = mkOption { 195 + tlsKeyFile = mkOption { 94 196 description = "Kubernetes apiserver private key file."; 95 - default = ""; 96 - type = types.str; 197 + default = null; 198 + type = types.nullOr types.path; 97 199 }; 98 200 99 201 clientCaFile = mkOption { 100 202 description = "Kubernetes apiserver CA file for client auth."; 101 - default = ""; 102 - type = types.str; 203 + default = null; 204 + type = types.nullOr types.path; 103 205 }; 104 206 105 207 tokenAuth = mkOption { 106 208 description = '' 107 209 Kubernetes apiserver token authentication file. See 108 - <link xlink:href="http://kubernetes.io/v1.0/docs/admin/authentication.html"/> 210 + <link xlink:href="http://kubernetes.io/docs/admin/authentication.html"/> 109 211 ''; 110 - default = {}; 111 - example = literalExample '' 112 - { 113 - alice = "abc123"; 114 - bob = "xyz987"; 115 - } 116 - ''; 117 - type = types.attrsOf types.str; 212 + default = null; 213 + example = ''token,user,uid,"group1,group2,group3"''; 214 + type = types.nullOr types.lines; 118 215 }; 119 216 120 217 authorizationMode = mkOption { ··· 148 245 149 246 allowPrivileged = mkOption { 150 247 description = "Whether to allow privileged containers on kubernetes."; 151 - default = false; 248 + default = true; 152 249 type = types.bool; 153 250 }; 154 251 155 252 portalNet = mkOption { 156 253 description = "Kubernetes CIDR notation IP range from which to assign portal IPs"; 157 - default = "10.10.10.10/16"; 254 + default = "10.10.10.10/24"; 158 255 type = types.str; 159 256 }; 160 257 ··· 171 268 admissionControl = mkOption { 172 269 description = '' 173 270 Kubernetes admission control plugins to use. See 174 - <link xlink:href="http://kubernetes.io/v1.0/docs/admin/admission-controllers.html"/> 271 + <link xlink:href="http://kubernetes.io/docs/admin/admission-controllers/"/> 175 272 ''; 176 - default = ["AlwaysAdmit"]; 273 + default = ["NamespaceLifecycle" "LimitRanger" "ServiceAccount" "ResourceQuota"]; 177 274 example = [ 178 275 "NamespaceLifecycle" "NamespaceExists" "LimitRanger" 179 276 "SecurityContextDeny" "ServiceAccount" "ResourceQuota" ··· 181 278 type = types.listOf types.str; 182 279 }; 183 280 184 - serviceAccountKey = mkOption { 281 + serviceAccountKeyFile = mkOption { 185 282 description = '' 186 283 Kubernetes apiserver PEM-encoded x509 RSA private or public key file, 187 - used to verify ServiceAccount tokens. 284 + used to verify ServiceAccount tokens. By default tls private key file 285 + is used. 188 286 ''; 189 287 default = null; 190 288 type = types.nullOr types.path; 191 289 }; 192 290 291 + kubeletClientCaFile = mkOption { 292 + description = "Path to a cert file for connecting to kubelet"; 293 + default = null; 294 + type = types.nullOr types.path; 295 + }; 296 + 297 + kubeletClientCertFile = mkOption { 298 + description = "Client certificate to use for connections to kubelet"; 299 + default = null; 300 + type = types.nullOr types.path; 301 + }; 302 + 303 + kubeletClientKeyFile = mkOption { 304 + description = "Key to use for connections to kubelet"; 305 + default = null; 306 + type = types.nullOr types.path; 307 + }; 308 + 309 + kubeletHttps = mkOption { 310 + description = "Whether to use https for connections to kubelet"; 311 + default = true; 312 + type = types.bool; 313 + }; 314 + 193 315 extraOpts = mkOption { 194 316 description = "Kubernetes apiserver extra command line options."; 195 317 default = ""; ··· 216 338 type = types.int; 217 339 }; 218 340 219 - master = mkOption { 220 - description = "Kubernetes apiserver address"; 221 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 222 - type = types.str; 341 + leaderElect = mkOption { 342 + description = "Whether to start leader election before executing main loop"; 343 + type = types.bool; 344 + default = false; 223 345 }; 224 346 225 347 extraOpts = mkOption { ··· 248 370 type = types.int; 249 371 }; 250 372 251 - master = mkOption { 252 - description = "Kubernetes apiserver address"; 253 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 254 - type = types.str; 373 + leaderElect = mkOption { 374 + description = "Whether to start leader election before executing main loop"; 375 + type = types.bool; 376 + default = false; 255 377 }; 256 378 257 - serviceAccountPrivateKey = mkOption { 379 + serviceAccountKeyFile = mkOption { 258 380 description = '' 259 381 Kubernetes controller manager PEM-encoded private RSA key file used to 260 382 sign service account tokens ··· 272 394 type = types.nullOr types.path; 273 395 }; 274 396 397 + clusterCidr = mkOption { 398 + description = "Kubernetes controller manager CIDR Range for Pods in cluster"; 399 + default = "10.10.0.0/16"; 400 + type = types.str; 401 + }; 402 + 275 403 extraOpts = mkOption { 276 404 description = "Kubernetes controller manager extra command line options."; 277 405 default = ""; ··· 292 420 type = types.bool; 293 421 }; 294 422 423 + registerSchedulable = mkOption { 424 + description = "Register the node as schedulable. No-op if register-node is false."; 425 + default = true; 426 + type = types.bool; 427 + }; 428 + 295 429 address = mkOption { 296 430 description = "Kubernetes kubelet info server listening address."; 297 431 default = "0.0.0.0"; ··· 304 438 type = types.int; 305 439 }; 306 440 441 + tlsCertFile = mkOption { 442 + description = "File containing x509 Certificate for HTTPS."; 443 + default = null; 444 + type = types.nullOr types.path; 445 + }; 446 + 447 + tlsKeyFile = mkOption { 448 + description = "File containing x509 private key matching tlsCertFile."; 449 + default = null; 450 + type = types.nullOr types.path; 451 + }; 452 + 307 453 healthz = { 308 454 bind = mkOption { 309 455 description = "Kubernetes kubelet healthz listening address."; ··· 326 472 327 473 allowPrivileged = mkOption { 328 474 description = "Whether to allow kubernetes containers to request privileged mode."; 329 - default = false; 475 + default = true; 330 476 type = types.bool; 331 477 }; 332 478 333 - apiServers = mkOption { 334 - description = '' 335 - Kubernetes kubelet list of Kubernetes API servers for publishing events, 336 - and reading pods and services. 337 - ''; 338 - default = ["${cfg.apiserver.address}:${toString cfg.apiserver.port}"]; 339 - type = types.listOf types.str; 340 - }; 341 - 342 479 cadvisorPort = mkOption { 343 480 description = "Kubernetes kubelet local cadvisor port."; 344 481 default = 4194; ··· 347 484 348 485 clusterDns = mkOption { 349 486 description = "Use alternative dns."; 350 - default = ""; 487 + default = "10.10.1.1"; 351 488 type = types.str; 352 489 }; 353 490 354 491 clusterDomain = mkOption { 355 492 description = "Use alternative domain."; 356 - default = "kubernetes.io"; 493 + default = "cluster.local"; 357 494 type = types.str; 358 495 }; 359 496 497 + networkPlugin = mkOption { 498 + description = "Network plugin to use by kubernetes"; 499 + type = types.nullOr (types.enum ["cni" "kubenet"]); 500 + default = "kubenet"; 501 + }; 502 + 503 + cni = { 504 + packages = mkOption { 505 + description = "List of network plugin packages to install"; 506 + type = types.listOf types.package; 507 + default = []; 508 + }; 509 + 510 + config = mkOption { 511 + description = "Kubernetes CNI configuration"; 512 + type = types.listOf types.attrs; 513 + default = []; 514 + example = literalExample '' 515 + [{ 516 + "cniVersion": "0.2.0", 517 + "name": "mynet", 518 + "type": "bridge", 519 + "bridge": "cni0", 520 + "isGateway": true, 521 + "ipMasq": true, 522 + "ipam": { 523 + "type": "host-local", 524 + "subnet": "10.22.0.0/16", 525 + "routes": [ 526 + { "dst": "0.0.0.0/0" } 527 + ] 528 + } 529 + } { 530 + "cniVersion": "0.2.0", 531 + "type": "loopback" 532 + }] 533 + ''; 534 + }; 535 + }; 536 + 537 + manifests = mkOption { 538 + description = "List of manifests to bootstrap with kubelet"; 539 + type = types.attrsOf types.attrs; 540 + default = {}; 541 + }; 542 + 360 543 extraOpts = mkOption { 361 544 description = "Kubernetes kubelet extra command line options."; 362 545 default = ""; ··· 377 560 type = types.str; 378 561 }; 379 562 380 - master = mkOption { 381 - description = "Kubernetes apiserver address"; 382 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 383 - type = types.str; 384 - }; 385 - 386 563 extraOpts = mkOption { 387 564 description = "Kubernetes proxy extra command line options."; 388 565 default = ""; ··· 390 567 }; 391 568 }; 392 569 393 - kube2sky = { 394 - enable = mkEnableOption "Whether to enable kube2sky dns service."; 570 + dns = { 571 + enable = mkEnableOption "kubernetes dns service."; 395 572 396 - domain = mkOption { 397 - description = "Kuberntes kube2sky domain under which all DNS names will be hosted."; 398 - default = cfg.kubelet.clusterDomain; 399 - type = types.str; 573 + port = mkOption { 574 + description = "Kubernetes dns listening port"; 575 + default = 53; 576 + type = types.int; 400 577 }; 401 578 402 - master = mkOption { 403 - description = "Kubernetes apiserver address"; 404 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 579 + domain = mkOption { 580 + description = "Kuberntes dns domain under which to create names."; 581 + default = cfg.kubelet.clusterDomain; 405 582 type = types.str; 406 583 }; 407 584 408 585 extraOpts = mkOption { 409 - description = "Kubernetes kube2sky extra command line options."; 586 + description = "Kubernetes dns extra command line options."; 410 587 default = ""; 411 588 type = types.str; 412 589 }; ··· 416 593 ###### implementation 417 594 418 595 config = mkMerge [ 596 + (mkIf cfg.kubelet.enable { 597 + systemd.services.kubelet = { 598 + description = "Kubernetes Kubelet Service"; 599 + wantedBy = [ "multi-user.target" ]; 600 + after = [ "network.target" "docker.service" "kube-apiserver.service" ]; 601 + path = with pkgs; [ gitMinimal openssh docker utillinux iproute ethtool thin-provisioning-tools iptables ]; 602 + preStart = '' 603 + docker load < ${infraContainer} 604 + rm /opt/cni/bin/* || true 605 + ${concatMapStringsSep "\n" (p: "ln -fs ${p.plugins}/* /opt/cni/bin") cfg.kubelet.cni.packages} 606 + ''; 607 + serviceConfig = { 608 + ExecStart = ''${cfg.package}/bin/kubelet \ 609 + --pod-manifest-path=${manifests} \ 610 + --kubeconfig=${kubeconfig} \ 611 + --require-kubeconfig \ 612 + --address=${cfg.kubelet.address} \ 613 + --port=${toString cfg.kubelet.port} \ 614 + --register-node=${if cfg.kubelet.registerNode then "true" else "false"} \ 615 + --register-schedulable=${if cfg.kubelet.registerSchedulable then "true" else "false"} \ 616 + ${optionalString (cfg.kubelet.tlsCertFile != null) 617 + "--tls-cert-file=${cfg.kubelet.tlsCertFile}"} \ 618 + ${optionalString (cfg.kubelet.tlsKeyFile != null) 619 + "--tls-private-key-file=${cfg.kubelet.tlsKeyFile}"} \ 620 + --healthz-bind-address=${cfg.kubelet.healthz.bind} \ 621 + --healthz-port=${toString cfg.kubelet.healthz.port} \ 622 + --hostname-override=${cfg.kubelet.hostname} \ 623 + --allow-privileged=${if cfg.kubelet.allowPrivileged then "true" else "false"} \ 624 + --root-dir=${cfg.dataDir} \ 625 + --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \ 626 + ${optionalString (cfg.kubelet.clusterDns != "") 627 + "--cluster-dns=${cfg.kubelet.clusterDns}"} \ 628 + ${optionalString (cfg.kubelet.clusterDomain != "") 629 + "--cluster-domain=${cfg.kubelet.clusterDomain}"} \ 630 + --pod-infra-container-image=pause \ 631 + ${optionalString (cfg.kubelet.networkPlugin != null) 632 + "--network-plugin=${cfg.kubelet.networkPlugin}"} \ 633 + --cni-conf-dir=${cniConfig} \ 634 + --reconcile-cidr \ 635 + --hairpin-mode=hairpin-veth \ 636 + ${optionalString cfg.verbose "--v=6 --log_flush_frequency=1s"} \ 637 + ${cfg.kubelet.extraOpts} 638 + ''; 639 + WorkingDirectory = cfg.dataDir; 640 + }; 641 + }; 642 + 643 + environment.etc = mapAttrs' (name: manifest: 644 + nameValuePair "kubernetes/manifests/${name}.json" { 645 + text = builtins.toJSON manifest; 646 + mode = "0755"; 647 + } 648 + ) cfg.kubelet.manifests; 649 + 650 + # Allways include cni plugins 651 + services.kubernetes.kubelet.cni.packages = [pkgs.cni]; 652 + }) 653 + 419 654 (mkIf cfg.apiserver.enable { 420 655 systemd.services.kube-apiserver = { 421 - description = "Kubernetes Api Server"; 656 + description = "Kubernetes Kubelet Service"; 422 657 wantedBy = [ "multi-user.target" ]; 423 - requires = ["kubernetes-setup.service"]; 424 - after = [ "network.target" "etcd.service" "kubernetes-setup.service" ]; 658 + after = [ "network.target" "docker.service" ]; 425 659 serviceConfig = { 426 - ExecStart = let 427 - authorizationPolicyFile = 428 - pkgs.writeText "kubernetes-policy" 429 - (builtins.toJSON cfg.apiserver.authorizationPolicy); 430 - tokenAuthFile = 431 - pkgs.writeText "kubernetes-auth" 432 - (concatImapStringsSep "\n" (i: v: v + "," + (toString i)) 433 - (mapAttrsToList (name: token: token + "," + name) cfg.apiserver.tokenAuth)); 434 - in ''${cfg.package}/bin/kube-apiserver \ 435 - --etcd-servers=${concatMapStringsSep "," (f: "http://${f}") cfg.etcdServers} \ 436 - --insecure-bind-address=${cfg.apiserver.address} \ 660 + ExecStart = ''${cfg.package}/bin/kube-apiserver \ 661 + --etcd-servers=${concatStringsSep "," cfg.etcd.servers} \ 662 + ${optionalString (cfg.etcd.caFile != null) 663 + "--etcd-cafile=${cfg.etcd.caFile}"} \ 664 + ${optionalString (cfg.etcd.certFile != null) 665 + "--etcd-certfile=${cfg.etcd.certFile}"} \ 666 + ${optionalString (cfg.etcd.keyFile != null) 667 + "--etcd-keyfile=${cfg.etcd.keyFile}"} \ 437 668 --insecure-port=${toString cfg.apiserver.port} \ 438 - --bind-address=${cfg.apiserver.publicAddress} \ 669 + --bind-address=0.0.0.0 \ 670 + ${optionalString (cfg.apiserver.advertiseAddress != null) 671 + "--advertise-address=${cfg.apiserver.advertiseAddress}"} \ 439 672 --allow-privileged=${if cfg.apiserver.allowPrivileged then "true" else "false"} \ 440 - ${optionalString (cfg.apiserver.tlsCertFile!="") 673 + ${optionalString (cfg.apiserver.tlsCertFile != null) 441 674 "--tls-cert-file=${cfg.apiserver.tlsCertFile}"} \ 442 - ${optionalString (cfg.apiserver.tlsPrivateKeyFile!="") 443 - "--tls-private-key-file=${cfg.apiserver.tlsPrivateKeyFile}"} \ 444 - ${optionalString (cfg.apiserver.tokenAuth!=[]) 445 - "--token-auth-file=${tokenAuthFile}"} \ 446 - ${optionalString (cfg.apiserver.clientCaFile!="") 675 + ${optionalString (cfg.apiserver.tlsKeyFile != null) 676 + "--tls-private-key-file=${cfg.apiserver.tlsKeyFile}"} \ 677 + ${optionalString (cfg.apiserver.tokenAuth != null) 678 + "--token-auth-file=${cfg.apiserver.tokenAuth}"} \ 679 + --kubelet-https=${if cfg.apiserver.kubeletHttps then "true" else "false"} \ 680 + ${optionalString (cfg.apiserver.kubeletClientCaFile != null) 681 + "--kubelet-certificate-authority=${cfg.apiserver.kubeletClientCaFile}"} \ 682 + ${optionalString (cfg.apiserver.kubeletClientCertFile != null) 683 + "--kubelet-client-certificate=${cfg.apiserver.kubeletClientCertFile}"} \ 684 + ${optionalString (cfg.apiserver.kubeletClientKeyFile != null) 685 + "--kubelet-client-key=${cfg.apiserver.kubeletClientKeyFile}"} \ 686 + ${optionalString (cfg.apiserver.clientCaFile != null) 447 687 "--client-ca-file=${cfg.apiserver.clientCaFile}"} \ 448 688 --authorization-mode=${cfg.apiserver.authorizationMode} \ 449 689 ${optionalString (cfg.apiserver.authorizationMode == "ABAC") 450 - "--authorization-policy-file=${authorizationPolicyFile}"} \ 690 + "--authorization-policy-file=${policyFile}"} \ 451 691 --secure-port=${toString cfg.apiserver.securePort} \ 452 692 --service-cluster-ip-range=${cfg.apiserver.portalNet} \ 453 - ${optionalString (cfg.apiserver.runtimeConfig!="") 693 + ${optionalString (cfg.apiserver.runtimeConfig != "") 454 694 "--runtime-config=${cfg.apiserver.runtimeConfig}"} \ 455 695 --admission_control=${concatStringsSep "," cfg.apiserver.admissionControl} \ 456 - ${optionalString (cfg.apiserver.serviceAccountKey!=null) 457 - "--service-account-key-file=${cfg.apiserver.serviceAccountKey}"} \ 458 - --logtostderr=true \ 459 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 696 + ${optionalString (cfg.apiserver.serviceAccountKeyFile!=null) 697 + "--service-account-key-file=${cfg.apiserver.serviceAccountKeyFile}"} \ 698 + ${optionalString cfg.verbose "--v=6"} \ 699 + ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 460 700 ${cfg.apiserver.extraOpts} 461 701 ''; 702 + WorkingDirectory = cfg.dataDir; 462 703 User = "kubernetes"; 704 + Group = "kubernetes"; 705 + AmbientCapabilities = "cap_net_bind_service"; 706 + Restart = "on-failure"; 707 + RestartSec = 5; 463 708 }; 464 709 }; 465 710 }) ··· 468 713 systemd.services.kube-scheduler = { 469 714 description = "Kubernetes Scheduler Service"; 470 715 wantedBy = [ "multi-user.target" ]; 471 - after = [ "network.target" "kubernetes-apiserver.service" ]; 716 + after = [ "kube-apiserver.service" ]; 472 717 serviceConfig = { 473 718 ExecStart = ''${cfg.package}/bin/kube-scheduler \ 474 719 --address=${cfg.scheduler.address} \ 475 720 --port=${toString cfg.scheduler.port} \ 476 - --master=${cfg.scheduler.master} \ 477 - --logtostderr=true \ 478 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 721 + --leader-elect=${if cfg.scheduler.leaderElect then "true" else "false"} \ 722 + --kubeconfig=${kubeconfig} \ 723 + ${optionalString cfg.verbose "--v=6"} \ 724 + ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 479 725 ${cfg.scheduler.extraOpts} 480 726 ''; 727 + WorkingDirectory = cfg.dataDir; 481 728 User = "kubernetes"; 729 + Group = "kubernetes"; 482 730 }; 483 731 }; 484 732 }) ··· 487 735 systemd.services.kube-controller-manager = { 488 736 description = "Kubernetes Controller Manager Service"; 489 737 wantedBy = [ "multi-user.target" ]; 490 - after = [ "network.target" "kubernetes-apiserver.service" ]; 738 + after = [ "kube-apiserver.service" ]; 491 739 serviceConfig = { 492 740 ExecStart = ''${cfg.package}/bin/kube-controller-manager \ 493 741 --address=${cfg.controllerManager.address} \ 494 742 --port=${toString cfg.controllerManager.port} \ 495 - --master=${cfg.controllerManager.master} \ 496 - ${optionalString (cfg.controllerManager.serviceAccountPrivateKey!=null) 497 - "--service-account-private-key-file=${cfg.controllerManager.serviceAccountPrivateKey}"} \ 743 + --kubeconfig=${kubeconfig} \ 744 + --leader-elect=${if cfg.controllerManager.leaderElect then "true" else "false"} \ 745 + ${if (cfg.controllerManager.serviceAccountKeyFile!=null) 746 + then "--service-account-private-key-file=${cfg.controllerManager.serviceAccountKeyFile}" 747 + else "--service-account-private-key-file=/var/run/kubernetes/apiserver.key"} \ 498 748 ${optionalString (cfg.controllerManager.rootCaFile!=null) 499 749 "--root-ca-file=${cfg.controllerManager.rootCaFile}"} \ 500 - --logtostderr=true \ 501 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 750 + ${optionalString (cfg.controllerManager.clusterCidr!=null) 751 + "--cluster-cidr=${cfg.controllerManager.clusterCidr}"} \ 752 + --allocate-node-cidrs=true \ 753 + ${optionalString cfg.verbose "--v=6"} \ 754 + ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 502 755 ${cfg.controllerManager.extraOpts} 503 756 ''; 757 + WorkingDirectory = cfg.dataDir; 504 758 User = "kubernetes"; 759 + Group = "kubernetes"; 505 760 }; 506 761 }; 507 762 }) 508 763 509 - (mkIf cfg.kubelet.enable { 510 - systemd.services.kubelet = { 511 - description = "Kubernetes Kubelet Service"; 512 - wantedBy = [ "multi-user.target" ]; 513 - requires = ["kubernetes-setup.service"]; 514 - after = [ "network.target" "etcd.service" "docker.service" "kubernetes-setup.service" ]; 515 - path = [ pkgs.gitMinimal pkgs.openssh ]; 516 - script = '' 517 - export PATH="/bin:/sbin:/usr/bin:/usr/sbin:$PATH" 518 - exec ${cfg.package}/bin/kubelet \ 519 - --api-servers=${concatMapStringsSep "," (f: "http://${f}") cfg.kubelet.apiServers} \ 520 - --register-node=${if cfg.kubelet.registerNode then "true" else "false"} \ 521 - --address=${cfg.kubelet.address} \ 522 - --port=${toString cfg.kubelet.port} \ 523 - --healthz-bind-address=${cfg.kubelet.healthz.bind} \ 524 - --healthz-port=${toString cfg.kubelet.healthz.port} \ 525 - --hostname-override=${cfg.kubelet.hostname} \ 526 - --allow-privileged=${if cfg.kubelet.allowPrivileged then "true" else "false"} \ 527 - --root-dir=${cfg.dataDir} \ 528 - --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \ 529 - ${optionalString (cfg.kubelet.clusterDns != "") 530 - ''--cluster-dns=${cfg.kubelet.clusterDns}''} \ 531 - ${optionalString (cfg.kubelet.clusterDomain != "") 532 - ''--cluster-domain=${cfg.kubelet.clusterDomain}''} \ 533 - --logtostderr=true \ 534 - ${optionalString cfg.verbose "--v=6 --log_flush_frequency=1s"} \ 535 - ${cfg.kubelet.extraOpts} 536 - ''; 537 - serviceConfig.WorkingDirectory = cfg.dataDir; 538 - }; 539 - }) 540 - 541 764 (mkIf cfg.proxy.enable { 542 765 systemd.services.kube-proxy = { 543 766 description = "Kubernetes Proxy Service"; 544 767 wantedBy = [ "multi-user.target" ]; 545 - after = [ "network.target" "etcd.service" ]; 768 + after = [ "kube-apiserver.service" ]; 769 + path = [pkgs.iptables]; 546 770 serviceConfig = { 547 771 ExecStart = ''${cfg.package}/bin/kube-proxy \ 548 - --master=${cfg.proxy.master} \ 772 + --kubeconfig=${kubeconfig} \ 549 773 --bind-address=${cfg.proxy.address} \ 550 - --logtostderr=true \ 551 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 552 - ${cfg.proxy.extraOpts} 774 + ${optionalString cfg.verbose "--v=6"} \ 775 + ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 776 + ${cfg.controllerManager.extraOpts} 553 777 ''; 554 - Restart = "always"; # Retry connection 555 - RestartSec = "5s"; 778 + WorkingDirectory = cfg.dataDir; 556 779 }; 557 780 }; 558 781 }) 559 782 560 - (mkIf cfg.kube2sky.enable { 561 - systemd.services.kube2sky = { 562 - description = "Kubernetes Dns Bridge Service"; 783 + (mkIf cfg.dns.enable { 784 + systemd.services.kube-dns = { 785 + description = "Kubernetes Dns Service"; 563 786 wantedBy = [ "multi-user.target" ]; 564 - after = [ "network.target" "skydns.service" "etcd.service" "kubernetes-apiserver.service" ]; 787 + after = [ "kube-apiserver.service" ]; 565 788 serviceConfig = { 566 - ExecStart = ''${cfg.package}/bin/kube2sky \ 567 - -etcd-server=http://${head cfg.etcdServers} \ 568 - -domain=${cfg.kube2sky.domain} \ 569 - -kube_master_url=http://${cfg.kube2sky.master} \ 570 - -logtostderr=true \ 571 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 572 - ${cfg.kube2sky.extraOpts} 789 + ExecStart = ''${cfg.package}/bin/kube-dns \ 790 + --kubecfg-file=${kubeconfig} \ 791 + --dns-port=${toString cfg.dns.port} \ 792 + --domain=${cfg.dns.domain} \ 793 + ${optionalString cfg.verbose "--v=6"} \ 794 + ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 795 + ${cfg.dns.extraOpts} 573 796 ''; 797 + WorkingDirectory = cfg.dataDir; 574 798 User = "kubernetes"; 799 + Group = "kubernetes"; 800 + AmbientCapabilities = "cap_net_bind_service"; 801 + SendSIGHUP = true; 575 802 }; 576 803 }; 804 + }) 805 + 806 + (mkIf cfg.kubelet.enable { 807 + boot.kernelModules = ["br_netfilter"]; 577 808 }) 578 809 579 810 (mkIf (any (el: el == "master") cfg.roles) { 811 + virtualisation.docker.enable = mkDefault true; 812 + services.kubernetes.kubelet.enable = mkDefault true; 813 + services.kubernetes.kubelet.allowPrivileged = mkDefault true; 580 814 services.kubernetes.apiserver.enable = mkDefault true; 581 815 services.kubernetes.scheduler.enable = mkDefault true; 582 816 services.kubernetes.controllerManager.enable = mkDefault true; 583 - services.kubernetes.kube2sky.enable = mkDefault true; 817 + services.etcd.enable = mkDefault (cfg.etcd.servers == ["http://127.0.0.1:2379"]); 584 818 }) 585 819 586 820 (mkIf (any (el: el == "node") cfg.roles) { 587 821 virtualisation.docker.enable = mkDefault true; 822 + virtualisation.docker.logDriver = mkDefault "json-file"; 588 823 services.kubernetes.kubelet.enable = mkDefault true; 589 824 services.kubernetes.proxy.enable = mkDefault true; 590 - }) 591 - 592 - (mkIf (any (el: el == "node" || el == "master") cfg.roles) { 593 - services.etcd.enable = mkDefault true; 594 - 595 - services.skydns.enable = mkDefault true; 596 - services.skydns.domain = mkDefault cfg.kubelet.clusterDomain; 825 + services.kubernetes.dns.enable = mkDefault true; 597 826 }) 598 827 599 828 (mkIf ( ··· 601 830 cfg.scheduler.enable || 602 831 cfg.controllerManager.enable || 603 832 cfg.kubelet.enable || 604 - cfg.proxy.enable 833 + cfg.proxy.enable || 834 + cfg.dns.enable 605 835 ) { 606 - systemd.services.kubernetes-setup = { 607 - description = "Kubernetes setup."; 608 - serviceConfig.Type = "oneshot"; 609 - script = '' 610 - mkdir -p /var/run/kubernetes 611 - chown kubernetes /var/lib/kubernetes 612 - 613 - rm ${cfg.dataDir}/.dockercfg || true 614 - ln -fs ${pkgs.writeText "kubernetes-dockercfg" cfg.dockerCfg} ${cfg.dataDir}/.dockercfg 615 - ''; 616 - }; 617 - 618 - services.kubernetes.package = mkDefault pkgs.kubernetes; 836 + systemd.tmpfiles.rules = [ 837 + "d /opt/cni/bin 0755 root root -" 838 + "d /var/run/kubernetes 0755 kubernetes kubernetes -" 839 + "d /var/lib/kubernetes 0755 kubernetes kubernetes -" 840 + ]; 619 841 620 842 environment.systemPackages = [ cfg.package ]; 621 - 622 843 users.extraUsers = singleton { 623 844 name = "kubernetes"; 624 845 uid = config.ids.uids.kubernetes; ··· 630 851 }; 631 852 users.extraGroups.kubernetes.gid = config.ids.gids.kubernetes; 632 853 }) 633 - 634 854 ]; 635 855 }
+383 -157
nixos/tests/kubernetes.nix
··· 1 - # This test runs two node kubernetes cluster and checks if simple redis pod works 1 + { system ? builtins.currentSystem }: 2 + 3 + with import ../lib/testing.nix { inherit system; }; 4 + with import ../lib/qemu-flags.nix; 5 + with pkgs.lib; 2 6 3 - import ./make-test.nix ({ pkgs, ...} : rec { 4 - name = "kubernetes"; 5 - meta = with pkgs.stdenv.lib.maintainers; { 6 - maintainers = [ offline ]; 7 + let 8 + redisPod = pkgs.writeText "redis-master-pod.json" (builtins.toJSON { 9 + kind = "Pod"; 10 + apiVersion = "v1"; 11 + metadata.name = "redis"; 12 + metadata.labels.name = "redis"; 13 + spec.containers = [{ 14 + name = "redis"; 15 + image = "redis"; 16 + args = ["--bind" "0.0.0.0"]; 17 + imagePullPolicy = "Never"; 18 + ports = [{ 19 + name = "redis-server"; 20 + containerPort = 6379; 21 + }]; 22 + }]; 23 + }); 24 + 25 + redisService = pkgs.writeText "redis-service.json" (builtins.toJSON { 26 + kind = "Service"; 27 + apiVersion = "v1"; 28 + metadata.name = "redis"; 29 + spec = { 30 + ports = [{port = 6379; targetPort = 6379;}]; 31 + selector = {name = "redis";}; 32 + }; 33 + }); 34 + 35 + redisImage = pkgs.dockerTools.buildImage { 36 + name = "redis"; 37 + tag = "latest"; 38 + contents = pkgs.redis; 39 + config.Entrypoint = "/bin/redis-server"; 7 40 }; 8 41 9 - redisMaster = builtins.toFile "redis-master-pod.yaml" '' 10 - id: redis-master-pod 11 - kind: Pod 12 - apiVersion: v1beta1 13 - desiredState: 14 - manifest: 15 - version: v1beta1 16 - id: redis-master-pod 17 - containers: 18 - - name: master 19 - image: master:5000/nix 20 - cpu: 100 21 - ports: 22 - - name: redis-server 23 - containerPort: 6379 24 - hostPort: 6379 25 - volumeMounts: 26 - - name: nix-store 27 - mountPath: /nix/store 28 - readOnly: true 29 - volumeMounts: 30 - - name: system-profile 31 - mountPath: /bin 32 - readOnly: true 33 - command: 34 - - /bin/redis-server 35 - volumes: 36 - - name: nix-store 37 - source: 38 - hostDir: 39 - path: /nix/store 40 - - name: system-profile 41 - source: 42 - hostDir: 43 - path: /run/current-system/sw/bin 44 - labels: 45 - name: redis 46 - role: master 42 + testSimplePod = '' 43 + $kubernetes->execute("docker load < ${redisImage}"); 44 + $kubernetes->waitUntilSucceeds("kubectl create -f ${redisPod}"); 45 + $kubernetes->succeed("kubectl create -f ${redisService}"); 46 + $kubernetes->waitUntilSucceeds("kubectl get pod redis | grep Running"); 47 + $kubernetes->succeed("nc -z \$\(dig \@10.10.0.1 redis.default.svc.cluster.local +short\) 6379"); 47 48 ''; 49 + in { 50 + # This test runs kubernetes on a single node 51 + trivial = makeTest { 52 + name = "kubernetes-trivial"; 48 53 49 - nodes = { 50 - master = 51 - { config, pkgs, lib, nodes, ... }: 52 - { 53 - virtualisation.memorySize = 768; 54 - services.kubernetes = { 55 - roles = ["master" "node"]; 56 - dockerCfg = ''{"master:5000":{}}''; 57 - controllerManager.machines = ["master" "node"]; 58 - apiserver.address = "0.0.0.0"; 59 - verbose = true; 60 - }; 61 - virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false -b cbr0 --insecure-registry master:5000"; 54 + nodes = { 55 + kubernetes = 56 + { config, pkgs, lib, nodes, ... }: 57 + { 58 + virtualisation.memorySize = 768; 59 + virtualisation.diskSize = 2048; 60 + 61 + programs.bash.enableCompletion = true; 62 62 63 - services.etcd = { 64 - listenPeerUrls = ["http://0.0.0.0:7001"]; 65 - initialAdvertisePeerUrls = ["http://master:7001"]; 66 - initialCluster = ["master=http://master:7001" "node=http://node:7001"]; 67 - }; 68 - services.dockerRegistry.enable = true; 69 - services.dockerRegistry.host = "0.0.0.0"; 70 - services.dockerRegistry.port = 5000; 63 + services.kubernetes.roles = ["master" "node"]; 64 + virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false -b cbr0"; 71 65 72 - virtualisation.vlans = [ 1 2 ]; 73 - networking.bridges = { 74 - cbr0.interfaces = [ "eth2" ]; 75 - }; 76 - networking.interfaces = { 77 - cbr0 = { 78 - ipAddress = "10.10.0.1"; 79 - prefixLength = 24; 80 - }; 81 - eth2.ip4 = lib.mkOverride 0 [ ]; 66 + networking.bridges.cbr0.interfaces = []; 67 + networking.interfaces.cbr0 = {}; 82 68 }; 83 - networking.localCommands = '' 84 - ip route add 10.10.0.0/16 dev cbr0 85 - ip route flush cache 86 - ''; 87 - networking.extraHosts = "127.0.0.1 master"; 69 + }; 70 + 71 + testScript = '' 72 + startAll; 73 + 74 + $kubernetes->waitUntilSucceeds("kubectl get nodes | grep kubernetes | grep Ready"); 75 + 76 + ${testSimplePod} 77 + ''; 78 + }; 79 + 80 + cluster = let 81 + runWithOpenSSL = file: cmd: pkgs.runCommand file { 82 + buildInputs = [ pkgs.openssl ]; 83 + } cmd; 84 + 85 + ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048"; 86 + ca_pem = runWithOpenSSL "ca.pem" '' 87 + openssl req \ 88 + -x509 -new -nodes -key ${ca_key} \ 89 + -days 10000 -out $out -subj "/CN=etcd-ca" 90 + ''; 91 + etcd_key = runWithOpenSSL "etcd-key.pem" "openssl genrsa -out $out 2048"; 92 + etcd_csr = runWithOpenSSL "etcd.csr" '' 93 + openssl req \ 94 + -new -key ${etcd_key} \ 95 + -out $out -subj "/CN=etcd" \ 96 + -config ${openssl_cnf} 97 + ''; 98 + etcd_cert = runWithOpenSSL "etcd.pem" '' 99 + openssl x509 \ 100 + -req -in ${etcd_csr} \ 101 + -CA ${ca_pem} -CAkey ${ca_key} \ 102 + -CAcreateserial -out $out \ 103 + -days 365 -extensions v3_req \ 104 + -extfile ${openssl_cnf} 105 + ''; 106 + 107 + etcd_client_key = runWithOpenSSL "etcd-client-key.pem" 108 + "openssl genrsa -out $out 2048"; 109 + 110 + etcd_client_csr = runWithOpenSSL "etcd-client-key.pem" '' 111 + openssl req \ 112 + -new -key ${etcd_client_key} \ 113 + -out $out -subj "/CN=etcd-client" \ 114 + -config ${client_openssl_cnf} 115 + ''; 116 + 117 + etcd_client_cert = runWithOpenSSL "etcd-client.crt" '' 118 + openssl x509 \ 119 + -req -in ${etcd_client_csr} \ 120 + -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \ 121 + -out $out -days 365 -extensions v3_req \ 122 + -extfile ${client_openssl_cnf} 123 + ''; 124 + 125 + apiserver_key = runWithOpenSSL "apiserver-key.pem" "openssl genrsa -out $out 2048"; 126 + 127 + apiserver_csr = runWithOpenSSL "apiserver.csr" '' 128 + openssl req \ 129 + -new -key ${apiserver_key} \ 130 + -out $out -subj "/CN=kube-apiserver" \ 131 + -config ${apiserver_cnf} 132 + ''; 133 + 134 + apiserver_cert = runWithOpenSSL "apiserver.pem" '' 135 + openssl x509 \ 136 + -req -in ${apiserver_csr} \ 137 + -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \ 138 + -out $out -days 365 -extensions v3_req \ 139 + -extfile ${apiserver_cnf} 140 + ''; 141 + 142 + worker_key = runWithOpenSSL "worker-key.pem" "openssl genrsa -out $out 2048"; 143 + 144 + worker_csr = runWithOpenSSL "worker.csr" '' 145 + openssl req \ 146 + -new -key ${worker_key} \ 147 + -out $out -subj "/CN=kube-worker" \ 148 + -config ${worker_cnf} 149 + ''; 150 + 151 + worker_cert = runWithOpenSSL "worker.pem" '' 152 + openssl x509 \ 153 + -req -in ${worker_csr} \ 154 + -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \ 155 + -out $out -days 365 -extensions v3_req \ 156 + -extfile ${worker_cnf} 157 + ''; 158 + 159 + openssl_cnf = pkgs.writeText "openssl.cnf" '' 160 + [req] 161 + req_extensions = v3_req 162 + distinguished_name = req_distinguished_name 163 + [req_distinguished_name] 164 + [ v3_req ] 165 + basicConstraints = CA:FALSE 166 + keyUsage = digitalSignature, keyEncipherment 167 + extendedKeyUsage = serverAuth 168 + subjectAltName = @alt_names 169 + [alt_names] 170 + DNS.1 = etcd1 171 + DNS.2 = etcd2 172 + DNS.3 = etcd3 173 + IP.1 = 127.0.0.1 174 + ''; 88 175 89 - networking.firewall.enable = false; 90 - #networking.firewall.allowedTCPPorts = [ 4001 7001 ]; 176 + client_openssl_cnf = pkgs.writeText "client-openssl.cnf" '' 177 + [req] 178 + req_extensions = v3_req 179 + distinguished_name = req_distinguished_name 180 + [req_distinguished_name] 181 + [ v3_req ] 182 + basicConstraints = CA:FALSE 183 + keyUsage = digitalSignature, keyEncipherment 184 + extendedKeyUsage = clientAuth 185 + ''; 91 186 92 - environment.systemPackages = [ pkgs.redis ]; 187 + apiserver_cnf = pkgs.writeText "apiserver-openssl.cnf" '' 188 + [req] 189 + req_extensions = v3_req 190 + distinguished_name = req_distinguished_name 191 + [req_distinguished_name] 192 + [ v3_req ] 193 + basicConstraints = CA:FALSE 194 + keyUsage = nonRepudiation, digitalSignature, keyEncipherment 195 + subjectAltName = @alt_names 196 + [alt_names] 197 + DNS.1 = kubernetes 198 + DNS.2 = kubernetes.default 199 + DNS.3 = kubernetes.default.svc 200 + DNS.4 = kubernetes.default.svc.cluster.local 201 + IP.1 = 10.10.10.1 202 + ''; 203 + 204 + worker_cnf = pkgs.writeText "worker-openssl.cnf" '' 205 + [req] 206 + req_extensions = v3_req 207 + distinguished_name = req_distinguished_name 208 + [req_distinguished_name] 209 + [ v3_req ] 210 + basicConstraints = CA:FALSE 211 + keyUsage = nonRepudiation, digitalSignature, keyEncipherment 212 + subjectAltName = @alt_names 213 + [alt_names] 214 + DNS.1 = kubeWorker1 215 + DNS.2 = kubeWorker2 216 + ''; 217 + 218 + etcdNodeConfig = { 219 + virtualisation.memorySize = 128; 220 + 221 + services = { 222 + etcd = { 223 + enable = true; 224 + keyFile = etcd_key; 225 + certFile = etcd_cert; 226 + trustedCaFile = ca_pem; 227 + peerClientCertAuth = true; 228 + listenClientUrls = ["https://0.0.0.0:2379"]; 229 + listenPeerUrls = ["https://0.0.0.0:2380"]; 93 230 }; 231 + }; 94 232 95 - node = 96 - { config, pkgs, lib, nodes, ... }: 97 - { 98 - services.kubernetes = { 99 - roles = ["node"]; 100 - dockerCfg = ''{"master:5000":{}}''; 101 - kubelet.apiServers = ["master:8080"]; 102 - verbose = true; 103 - }; 104 - virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false -b cbr0 --insecure-registry master:5000"; 105 - services.etcd = { 106 - listenPeerUrls = ["http://0.0.0.0:7001"]; 107 - initialAdvertisePeerUrls = ["http://node:7001"]; 108 - initialCluster = ["master=http://master:7001" "node=http://node:7001"]; 109 - }; 233 + environment.variables = { 234 + ETCDCTL_CERT_FILE = "${etcd_client_cert}"; 235 + ETCDCTL_KEY_FILE = "${etcd_client_key}"; 236 + ETCDCTL_CA_FILE = "${ca_pem}"; 237 + ETCDCTL_PEERS = "https://127.0.0.1:2379"; 238 + }; 110 239 111 - virtualisation.vlans = [ 1 2 ]; 112 - networking.bridges = { 113 - cbr0.interfaces = [ "eth2" ]; 114 - }; 115 - networking.interfaces = { 116 - cbr0 = { 117 - ipAddress = "10.10.1.1"; 118 - prefixLength = 24; 119 - }; 120 - eth2.ip4 = lib.mkOverride 0 [ ]; 121 - }; 122 - networking.localCommands = '' 123 - ip route add 10.10.0.0/16 dev cbr0 124 - ip route flush cache 125 - ''; 126 - networking.extraHosts = "127.0.0.1 node"; 240 + networking.firewall.allowedTCPPorts = [ 2379 2380 ]; 241 + }; 127 242 128 - networking.firewall.enable = false; 129 - #networking.firewall.allowedTCPPorts = [ 4001 7001 ]; 243 + kubeConfig = { 244 + virtualisation.diskSize = 2048; 245 + programs.bash.enableCompletion = true; 130 246 131 - environment.systemPackages = [ pkgs.redis ]; 247 + services.flannel = { 248 + enable = true; 249 + network = "10.10.0.0/16"; 250 + iface = "eth1"; 251 + etcd = { 252 + endpoints = ["https://etcd1:2379" "https://etcd2:2379" "https://etcd3:2379"]; 253 + keyFile = etcd_client_key; 254 + certFile = etcd_client_cert; 255 + caFile = ca_pem; 132 256 }; 257 + }; 258 + 259 + # vxlan 260 + networking.firewall.allowedUDPPorts = [ 8472 ]; 261 + 262 + systemd.services.docker.after = ["flannel.service"]; 263 + systemd.services.docker.serviceConfig.EnvironmentFile = "/run/flannel/subnet.env"; 264 + virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false --bip $FLANNEL_SUBNET"; 265 + 266 + services.kubernetes.verbose = true; 267 + services.kubernetes.etcd = { 268 + servers = ["https://etcd1:2379" "https://etcd2:2379" "https://etcd3:2379"]; 269 + keyFile = etcd_client_key; 270 + certFile = etcd_client_cert; 271 + caFile = ca_pem; 272 + }; 133 273 134 - client = 135 - { config, pkgs, nodes, ... }: 136 - { 137 - virtualisation.docker.enable = true; 138 - virtualisation.docker.extraOptions = "--insecure-registry master:5000"; 139 - environment.systemPackages = [ pkgs.kubernetes ]; 140 - environment.etc."test/redis-master-pod.yaml".source = redisMaster; 141 - environment.etc."test/pause".source = "${pkgs.kubernetes}/bin/kube-pause"; 142 - environment.etc."test/Dockerfile".source = pkgs.writeText "Dockerfile" '' 143 - FROM scratch 144 - ADD pause / 145 - ENTRYPOINT ["/pause"] 146 - ''; 274 + environment.systemPackages = [ pkgs.bind pkgs.tcpdump pkgs.utillinux ]; 275 + }; 276 + 277 + kubeMasterConfig = {pkgs, ...}: { 278 + require = [kubeConfig]; 279 + 280 + # kube apiserver 281 + networking.firewall.allowedTCPPorts = [ 443 ]; 282 + 283 + virtualisation.memorySize = 512; 284 + 285 + services.kubernetes = { 286 + roles = ["master"]; 287 + scheduler.leaderElect = true; 288 + controllerManager.leaderElect = true; 289 + 290 + apiserver = { 291 + publicAddress = "0.0.0.0"; 292 + advertiseAddress = "192.168.1.8"; 293 + tlsKeyFile = apiserver_key; 294 + tlsCertFile = apiserver_cert; 295 + clientCaFile = ca_pem; 296 + kubeletClientCaFile = ca_pem; 297 + kubeletClientKeyFile = worker_key; 298 + kubeletClientCertFile = worker_cert; 147 299 }; 148 - }; 300 + }; 301 + }; 302 + 303 + kubeWorkerConfig = { pkgs, ... }: { 304 + require = [kubeConfig]; 305 + 306 + virtualisation.memorySize = 512; 307 + 308 + # kubelet 309 + networking.firewall.allowedTCPPorts = [ 10250 ]; 310 + 311 + services.kubernetes = { 312 + roles = ["node"]; 313 + kubeconfig = { 314 + server = "https://kubernetes:443"; 315 + caFile = ca_pem; 316 + certFile = worker_cert; 317 + keyFile = worker_key; 318 + }; 319 + kubelet = { 320 + tlsKeyFile = worker_key; 321 + tlsCertFile = worker_cert; 322 + }; 323 + }; 324 + }; 325 + in makeTest { 326 + name = "kubernetes-cluster"; 327 + 328 + nodes = { 329 + etcd1 = { config, pkgs, nodes, ... }: { 330 + require = [etcdNodeConfig]; 331 + services.etcd = { 332 + advertiseClientUrls = ["https://etcd1:2379"]; 333 + initialCluster = ["etcd1=https://etcd1:2380" "etcd2=https://etcd2:2380" "etcd3=https://etcd3:2380"]; 334 + initialAdvertisePeerUrls = ["https://etcd1:2380"]; 335 + }; 336 + }; 337 + 338 + etcd2 = { config, pkgs, ... }: { 339 + require = [etcdNodeConfig]; 340 + services.etcd = { 341 + advertiseClientUrls = ["https://etcd2:2379"]; 342 + initialCluster = ["etcd1=https://etcd1:2380" "etcd2=https://etcd2:2380" "etcd3=https://etcd3:2380"]; 343 + initialAdvertisePeerUrls = ["https://etcd2:2380"]; 344 + }; 345 + }; 346 + 347 + etcd3 = { config, pkgs, ... }: { 348 + require = [etcdNodeConfig]; 349 + services.etcd = { 350 + advertiseClientUrls = ["https://etcd3:2379"]; 351 + initialCluster = ["etcd1=https://etcd1:2380" "etcd2=https://etcd2:2380" "etcd3=https://etcd3:2380"]; 352 + initialAdvertisePeerUrls = ["https://etcd3:2380"]; 353 + }; 354 + }; 355 + 356 + kubeMaster1 = { config, pkgs, lib, nodes, ... }: { 357 + require = [kubeMasterConfig]; 358 + }; 359 + 360 + kubeMaster2 = { config, pkgs, lib, nodes, ... }: { 361 + require = [kubeMasterConfig]; 362 + }; 149 363 150 - testScript = '' 151 - startAll; 364 + # Kubernetes TCP load balancer 365 + kubernetes = { config, pkgs, ... }: { 366 + # kubernetes 367 + networking.firewall.allowedTCPPorts = [ 443 ]; 152 368 153 - $master->waitForUnit("kubernetes-apiserver.service"); 154 - $master->waitForUnit("kubernetes-scheduler.service"); 155 - $master->waitForUnit("kubernetes-controller-manager.service"); 156 - $master->waitForUnit("kubernetes-kubelet.service"); 157 - $master->waitForUnit("kubernetes-proxy.service"); 369 + services.haproxy.enable = true; 370 + services.haproxy.config = '' 371 + global 372 + log 127.0.0.1 local0 notice 373 + user haproxy 374 + group haproxy 158 375 159 - $node->waitForUnit("kubernetes-kubelet.service"); 160 - $node->waitForUnit("kubernetes-proxy.service"); 376 + defaults 377 + log global 378 + retries 2 379 + timeout connect 3000 380 + timeout server 5000 381 + timeout client 5000 382 + 383 + listen kubernetes 384 + bind 0.0.0.0:443 385 + mode tcp 386 + option ssl-hello-chk 387 + balance roundrobin 388 + server kube-master-1 kubeMaster1:443 check 389 + server kube-master-2 kubeMaster2:443 check 390 + ''; 391 + }; 161 392 162 - $master->waitUntilSucceeds("kubectl get minions | grep master"); 163 - $master->waitUntilSucceeds("kubectl get minions | grep node"); 393 + kubeWorker1 = { config, pkgs, lib, nodes, ... }: { 394 + require = [kubeWorkerConfig]; 395 + }; 164 396 165 - $client->waitForUnit("docker.service"); 166 - $client->succeed("tar cv --files-from /dev/null | docker import - nix"); 167 - $client->succeed("docker tag nix master:5000/nix"); 168 - $master->waitForUnit("docker-registry.service"); 169 - $client->succeed("docker push master:5000/nix"); 170 - $client->succeed("mkdir -p /root/pause"); 171 - $client->succeed("cp /etc/test/pause /root/pause/"); 172 - $client->succeed("cp /etc/test/Dockerfile /root/pause/"); 173 - $client->succeed("cd /root/pause && docker build -t master:5000/pause ."); 174 - $client->succeed("docker push master:5000/pause"); 397 + kubeWorker2 = { config, pkgs, lib, nodes, ... }: { 398 + require = [kubeWorkerConfig]; 399 + }; 400 + }; 175 401 176 - subtest "simple pod", sub { 177 - $client->succeed("kubectl create -f ${redisMaster} -s http://master:8080"); 178 - $client->waitUntilSucceeds("kubectl get pods -s http://master:8080 | grep redis-master | grep -i running"); 179 - } 402 + testScript = '' 403 + startAll; 180 404 181 - ''; 182 - }) 405 + ${testSimplePod} 406 + ''; 407 + }; 408 + }
+36
pkgs/applications/networking/cluster/cni/default.nix
··· 1 + { stdenv, fetchFromGitHub, go }: 2 + 3 + stdenv.mkDerivation rec { 4 + name = "cni-${version}"; 5 + version = "0.3.0"; 6 + 7 + src = fetchFromGitHub { 8 + owner = "containernetworking"; 9 + repo = "cni"; 10 + rev = "v${version}"; 11 + sha256 = "1nvixvf5slnsdrfpfs2km64x680wf83jbyp7il12bcim37q2az7m"; 12 + }; 13 + 14 + buildInputs = [ go ]; 15 + 16 + outputs = ["out" "plugins"]; 17 + 18 + buildPhase = '' 19 + patchShebangs build 20 + ./build 21 + ''; 22 + 23 + installPhase = '' 24 + mkdir -p $out/bin $plugins 25 + mv bin/cnitool $out/bin 26 + mv bin/* $plugins/ 27 + ''; 28 + 29 + meta = with stdenv.lib; { 30 + description = "Container Network Interface - networking for Linux containers"; 31 + license = licenses.asl20; 32 + homepage = https://github.com/containernetworking/cni; 33 + maintainers = with maintainers; [offline]; 34 + platforms = [ "x86_64-linux" ]; 35 + }; 36 + }
-3
pkgs/applications/networking/cluster/kubernetes/default.nix
··· 48 48 ''; 49 49 50 50 preFixup = '' 51 - wrapProgram "$out/bin/kube-proxy" --prefix PATH : "${iptables}/bin" 52 - wrapProgram "$out/bin/kubelet" --prefix PATH : "${coreutils}/bin" 53 - 54 51 # Remove references to go compiler 55 52 while read file; do 56 53 cat $file | sed "s,${go},$(echo "${go}" | sed "s,$NIX_STORE/[^-]*,$NIX_STORE/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee,"),g" > $file.tmp
+2
pkgs/top-level/all-packages.nix
··· 12402 12402 pulseaudioSupport = config.pulseaudio or false; 12403 12403 }; 12404 12404 12405 + cni = callPackage ../applications/networking/cluster/cni {}; 12406 + 12405 12407 communi = qt5.callPackage ../applications/networking/irc/communi { }; 12406 12408 12407 12409 compiz = callPackage ../applications/window-managers/compiz {