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 let 6 cfg = config.services.kubernetes; 7 8 in { 9 10 ###### interface 11 12 options.services.kubernetes = { 13 package = mkOption { 14 description = "Kubernetes package to use."; 15 type = types.package; 16 }; 17 18 verbose = mkOption { ··· 21 type = types.bool; 22 }; 23 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; 28 }; 29 30 - roles = mkOption { 31 - description = '' 32 - Kubernetes role that this machine should take. 33 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"]); 39 }; 40 41 dataDir = mkOption { 42 description = "Kubernetes root directory for managing kubelet files."; 43 default = "/var/lib/kubernetes"; 44 type = types.path; 45 - }; 46 - 47 - dockerCfg = mkOption { 48 - description = "Kubernetes contents of dockercfg file."; 49 - default = ""; 50 - type = types.lines; 51 }; 52 53 apiserver = { ··· 72 type = types.str; 73 }; 74 75 port = mkOption { 76 description = "Kubernetes apiserver listening port."; 77 default = 8080; ··· 80 81 securePort = mkOption { 82 description = "Kubernetes apiserver secure port."; 83 - default = 6443; 84 type = types.int; 85 }; 86 87 tlsCertFile = mkOption { 88 description = "Kubernetes apiserver certificate file."; 89 - default = ""; 90 - type = types.str; 91 }; 92 93 - tlsPrivateKeyFile = mkOption { 94 description = "Kubernetes apiserver private key file."; 95 - default = ""; 96 - type = types.str; 97 }; 98 99 clientCaFile = mkOption { 100 description = "Kubernetes apiserver CA file for client auth."; 101 - default = ""; 102 - type = types.str; 103 }; 104 105 tokenAuth = mkOption { 106 description = '' 107 Kubernetes apiserver token authentication file. See 108 - <link xlink:href="http://kubernetes.io/v1.0/docs/admin/authentication.html"/> 109 ''; 110 - default = {}; 111 - example = literalExample '' 112 - { 113 - alice = "abc123"; 114 - bob = "xyz987"; 115 - } 116 - ''; 117 - type = types.attrsOf types.str; 118 }; 119 120 authorizationMode = mkOption { ··· 148 149 allowPrivileged = mkOption { 150 description = "Whether to allow privileged containers on kubernetes."; 151 - default = false; 152 type = types.bool; 153 }; 154 155 portalNet = mkOption { 156 description = "Kubernetes CIDR notation IP range from which to assign portal IPs"; 157 - default = "10.10.10.10/16"; 158 type = types.str; 159 }; 160 ··· 171 admissionControl = mkOption { 172 description = '' 173 Kubernetes admission control plugins to use. See 174 - <link xlink:href="http://kubernetes.io/v1.0/docs/admin/admission-controllers.html"/> 175 ''; 176 - default = ["AlwaysAdmit"]; 177 example = [ 178 "NamespaceLifecycle" "NamespaceExists" "LimitRanger" 179 "SecurityContextDeny" "ServiceAccount" "ResourceQuota" ··· 181 type = types.listOf types.str; 182 }; 183 184 - serviceAccountKey = mkOption { 185 description = '' 186 Kubernetes apiserver PEM-encoded x509 RSA private or public key file, 187 - used to verify ServiceAccount tokens. 188 ''; 189 default = null; 190 type = types.nullOr types.path; 191 }; 192 193 extraOpts = mkOption { 194 description = "Kubernetes apiserver extra command line options."; 195 default = ""; ··· 216 type = types.int; 217 }; 218 219 - master = mkOption { 220 - description = "Kubernetes apiserver address"; 221 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 222 - type = types.str; 223 }; 224 225 extraOpts = mkOption { ··· 248 type = types.int; 249 }; 250 251 - master = mkOption { 252 - description = "Kubernetes apiserver address"; 253 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 254 - type = types.str; 255 }; 256 257 - serviceAccountPrivateKey = mkOption { 258 description = '' 259 Kubernetes controller manager PEM-encoded private RSA key file used to 260 sign service account tokens ··· 272 type = types.nullOr types.path; 273 }; 274 275 extraOpts = mkOption { 276 description = "Kubernetes controller manager extra command line options."; 277 default = ""; ··· 292 type = types.bool; 293 }; 294 295 address = mkOption { 296 description = "Kubernetes kubelet info server listening address."; 297 default = "0.0.0.0"; ··· 304 type = types.int; 305 }; 306 307 healthz = { 308 bind = mkOption { 309 description = "Kubernetes kubelet healthz listening address."; ··· 326 327 allowPrivileged = mkOption { 328 description = "Whether to allow kubernetes containers to request privileged mode."; 329 - default = false; 330 type = types.bool; 331 }; 332 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 cadvisorPort = mkOption { 343 description = "Kubernetes kubelet local cadvisor port."; 344 default = 4194; ··· 347 348 clusterDns = mkOption { 349 description = "Use alternative dns."; 350 - default = ""; 351 type = types.str; 352 }; 353 354 clusterDomain = mkOption { 355 description = "Use alternative domain."; 356 - default = "kubernetes.io"; 357 type = types.str; 358 }; 359 360 extraOpts = mkOption { 361 description = "Kubernetes kubelet extra command line options."; 362 default = ""; ··· 377 type = types.str; 378 }; 379 380 - master = mkOption { 381 - description = "Kubernetes apiserver address"; 382 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 383 - type = types.str; 384 - }; 385 - 386 extraOpts = mkOption { 387 description = "Kubernetes proxy extra command line options."; 388 default = ""; ··· 390 }; 391 }; 392 393 - kube2sky = { 394 - enable = mkEnableOption "Whether to enable kube2sky dns service."; 395 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; 400 }; 401 402 - master = mkOption { 403 - description = "Kubernetes apiserver address"; 404 - default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}"; 405 type = types.str; 406 }; 407 408 extraOpts = mkOption { 409 - description = "Kubernetes kube2sky extra command line options."; 410 default = ""; 411 type = types.str; 412 }; ··· 416 ###### implementation 417 418 config = mkMerge [ 419 (mkIf cfg.apiserver.enable { 420 systemd.services.kube-apiserver = { 421 - description = "Kubernetes Api Server"; 422 wantedBy = [ "multi-user.target" ]; 423 - requires = ["kubernetes-setup.service"]; 424 - after = [ "network.target" "etcd.service" "kubernetes-setup.service" ]; 425 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} \ 437 --insecure-port=${toString cfg.apiserver.port} \ 438 - --bind-address=${cfg.apiserver.publicAddress} \ 439 --allow-privileged=${if cfg.apiserver.allowPrivileged then "true" else "false"} \ 440 - ${optionalString (cfg.apiserver.tlsCertFile!="") 441 "--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!="") 447 "--client-ca-file=${cfg.apiserver.clientCaFile}"} \ 448 --authorization-mode=${cfg.apiserver.authorizationMode} \ 449 ${optionalString (cfg.apiserver.authorizationMode == "ABAC") 450 - "--authorization-policy-file=${authorizationPolicyFile}"} \ 451 --secure-port=${toString cfg.apiserver.securePort} \ 452 --service-cluster-ip-range=${cfg.apiserver.portalNet} \ 453 - ${optionalString (cfg.apiserver.runtimeConfig!="") 454 "--runtime-config=${cfg.apiserver.runtimeConfig}"} \ 455 --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"} \ 460 ${cfg.apiserver.extraOpts} 461 ''; 462 User = "kubernetes"; 463 }; 464 }; 465 }) ··· 468 systemd.services.kube-scheduler = { 469 description = "Kubernetes Scheduler Service"; 470 wantedBy = [ "multi-user.target" ]; 471 - after = [ "network.target" "kubernetes-apiserver.service" ]; 472 serviceConfig = { 473 ExecStart = ''${cfg.package}/bin/kube-scheduler \ 474 --address=${cfg.scheduler.address} \ 475 --port=${toString cfg.scheduler.port} \ 476 - --master=${cfg.scheduler.master} \ 477 - --logtostderr=true \ 478 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 479 ${cfg.scheduler.extraOpts} 480 ''; 481 User = "kubernetes"; 482 }; 483 }; 484 }) ··· 487 systemd.services.kube-controller-manager = { 488 description = "Kubernetes Controller Manager Service"; 489 wantedBy = [ "multi-user.target" ]; 490 - after = [ "network.target" "kubernetes-apiserver.service" ]; 491 serviceConfig = { 492 ExecStart = ''${cfg.package}/bin/kube-controller-manager \ 493 --address=${cfg.controllerManager.address} \ 494 --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}"} \ 498 ${optionalString (cfg.controllerManager.rootCaFile!=null) 499 "--root-ca-file=${cfg.controllerManager.rootCaFile}"} \ 500 - --logtostderr=true \ 501 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 502 ${cfg.controllerManager.extraOpts} 503 ''; 504 User = "kubernetes"; 505 }; 506 }; 507 }) 508 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 (mkIf cfg.proxy.enable { 542 systemd.services.kube-proxy = { 543 description = "Kubernetes Proxy Service"; 544 wantedBy = [ "multi-user.target" ]; 545 - after = [ "network.target" "etcd.service" ]; 546 serviceConfig = { 547 ExecStart = ''${cfg.package}/bin/kube-proxy \ 548 - --master=${cfg.proxy.master} \ 549 --bind-address=${cfg.proxy.address} \ 550 - --logtostderr=true \ 551 - ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \ 552 - ${cfg.proxy.extraOpts} 553 ''; 554 - Restart = "always"; # Retry connection 555 - RestartSec = "5s"; 556 }; 557 }; 558 }) 559 560 - (mkIf cfg.kube2sky.enable { 561 - systemd.services.kube2sky = { 562 - description = "Kubernetes Dns Bridge Service"; 563 wantedBy = [ "multi-user.target" ]; 564 - after = [ "network.target" "skydns.service" "etcd.service" "kubernetes-apiserver.service" ]; 565 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} 573 ''; 574 User = "kubernetes"; 575 }; 576 }; 577 }) 578 579 (mkIf (any (el: el == "master") cfg.roles) { 580 services.kubernetes.apiserver.enable = mkDefault true; 581 services.kubernetes.scheduler.enable = mkDefault true; 582 services.kubernetes.controllerManager.enable = mkDefault true; 583 - services.kubernetes.kube2sky.enable = mkDefault true; 584 }) 585 586 (mkIf (any (el: el == "node") cfg.roles) { 587 virtualisation.docker.enable = mkDefault true; 588 services.kubernetes.kubelet.enable = mkDefault true; 589 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; 597 }) 598 599 (mkIf ( ··· 601 cfg.scheduler.enable || 602 cfg.controllerManager.enable || 603 cfg.kubelet.enable || 604 - cfg.proxy.enable 605 ) { 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; 619 620 environment.systemPackages = [ cfg.package ]; 621 - 622 users.extraUsers = singleton { 623 name = "kubernetes"; 624 uid = config.ids.uids.kubernetes; ··· 630 }; 631 users.extraGroups.kubernetes.gid = config.ids.gids.kubernetes; 632 }) 633 - 634 ]; 635 }
··· 5 let 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 + 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 + 59 in { 60 61 ###### interface 62 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 + 75 package = mkOption { 76 description = "Kubernetes package to use."; 77 type = types.package; 78 + default = pkgs.kubernetes; 79 }; 80 81 verbose = mkOption { ··· 84 type = types.bool; 85 }; 86 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 + }; 111 }; 112 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 + }; 119 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 + }; 137 }; 138 139 dataDir = mkOption { 140 description = "Kubernetes root directory for managing kubelet files."; 141 default = "/var/lib/kubernetes"; 142 type = types.path; 143 }; 144 145 apiserver = { ··· 164 type = types.str; 165 }; 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 + 177 port = mkOption { 178 description = "Kubernetes apiserver listening port."; 179 default = 8080; ··· 182 183 securePort = mkOption { 184 description = "Kubernetes apiserver secure port."; 185 + default = 443; 186 type = types.int; 187 }; 188 189 tlsCertFile = mkOption { 190 description = "Kubernetes apiserver certificate file."; 191 + default = null; 192 + type = types.nullOr types.path; 193 }; 194 195 + tlsKeyFile = mkOption { 196 description = "Kubernetes apiserver private key file."; 197 + default = null; 198 + type = types.nullOr types.path; 199 }; 200 201 clientCaFile = mkOption { 202 description = "Kubernetes apiserver CA file for client auth."; 203 + default = null; 204 + type = types.nullOr types.path; 205 }; 206 207 tokenAuth = mkOption { 208 description = '' 209 Kubernetes apiserver token authentication file. See 210 + <link xlink:href="http://kubernetes.io/docs/admin/authentication.html"/> 211 ''; 212 + default = null; 213 + example = ''token,user,uid,"group1,group2,group3"''; 214 + type = types.nullOr types.lines; 215 }; 216 217 authorizationMode = mkOption { ··· 245 246 allowPrivileged = mkOption { 247 description = "Whether to allow privileged containers on kubernetes."; 248 + default = true; 249 type = types.bool; 250 }; 251 252 portalNet = mkOption { 253 description = "Kubernetes CIDR notation IP range from which to assign portal IPs"; 254 + default = "10.10.10.10/24"; 255 type = types.str; 256 }; 257 ··· 268 admissionControl = mkOption { 269 description = '' 270 Kubernetes admission control plugins to use. See 271 + <link xlink:href="http://kubernetes.io/docs/admin/admission-controllers/"/> 272 ''; 273 + default = ["NamespaceLifecycle" "LimitRanger" "ServiceAccount" "ResourceQuota"]; 274 example = [ 275 "NamespaceLifecycle" "NamespaceExists" "LimitRanger" 276 "SecurityContextDeny" "ServiceAccount" "ResourceQuota" ··· 278 type = types.listOf types.str; 279 }; 280 281 + serviceAccountKeyFile = mkOption { 282 description = '' 283 Kubernetes apiserver PEM-encoded x509 RSA private or public key file, 284 + used to verify ServiceAccount tokens. By default tls private key file 285 + is used. 286 ''; 287 default = null; 288 type = types.nullOr types.path; 289 }; 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 + 315 extraOpts = mkOption { 316 description = "Kubernetes apiserver extra command line options."; 317 default = ""; ··· 338 type = types.int; 339 }; 340 341 + leaderElect = mkOption { 342 + description = "Whether to start leader election before executing main loop"; 343 + type = types.bool; 344 + default = false; 345 }; 346 347 extraOpts = mkOption { ··· 370 type = types.int; 371 }; 372 373 + leaderElect = mkOption { 374 + description = "Whether to start leader election before executing main loop"; 375 + type = types.bool; 376 + default = false; 377 }; 378 379 + serviceAccountKeyFile = mkOption { 380 description = '' 381 Kubernetes controller manager PEM-encoded private RSA key file used to 382 sign service account tokens ··· 394 type = types.nullOr types.path; 395 }; 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 + 403 extraOpts = mkOption { 404 description = "Kubernetes controller manager extra command line options."; 405 default = ""; ··· 420 type = types.bool; 421 }; 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 + 429 address = mkOption { 430 description = "Kubernetes kubelet info server listening address."; 431 default = "0.0.0.0"; ··· 438 type = types.int; 439 }; 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 + 453 healthz = { 454 bind = mkOption { 455 description = "Kubernetes kubelet healthz listening address."; ··· 472 473 allowPrivileged = mkOption { 474 description = "Whether to allow kubernetes containers to request privileged mode."; 475 + default = true; 476 type = types.bool; 477 }; 478 479 cadvisorPort = mkOption { 480 description = "Kubernetes kubelet local cadvisor port."; 481 default = 4194; ··· 484 485 clusterDns = mkOption { 486 description = "Use alternative dns."; 487 + default = "10.10.1.1"; 488 type = types.str; 489 }; 490 491 clusterDomain = mkOption { 492 description = "Use alternative domain."; 493 + default = "cluster.local"; 494 type = types.str; 495 }; 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 + 543 extraOpts = mkOption { 544 description = "Kubernetes kubelet extra command line options."; 545 default = ""; ··· 560 type = types.str; 561 }; 562 563 extraOpts = mkOption { 564 description = "Kubernetes proxy extra command line options."; 565 default = ""; ··· 567 }; 568 }; 569 570 + dns = { 571 + enable = mkEnableOption "kubernetes dns service."; 572 573 + port = mkOption { 574 + description = "Kubernetes dns listening port"; 575 + default = 53; 576 + type = types.int; 577 }; 578 579 + domain = mkOption { 580 + description = "Kuberntes dns domain under which to create names."; 581 + default = cfg.kubelet.clusterDomain; 582 type = types.str; 583 }; 584 585 extraOpts = mkOption { 586 + description = "Kubernetes dns extra command line options."; 587 default = ""; 588 type = types.str; 589 }; ··· 593 ###### implementation 594 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 + 654 (mkIf cfg.apiserver.enable { 655 systemd.services.kube-apiserver = { 656 + description = "Kubernetes Kubelet Service"; 657 wantedBy = [ "multi-user.target" ]; 658 + after = [ "network.target" "docker.service" ]; 659 serviceConfig = { 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}"} \ 668 --insecure-port=${toString cfg.apiserver.port} \ 669 + --bind-address=0.0.0.0 \ 670 + ${optionalString (cfg.apiserver.advertiseAddress != null) 671 + "--advertise-address=${cfg.apiserver.advertiseAddress}"} \ 672 --allow-privileged=${if cfg.apiserver.allowPrivileged then "true" else "false"} \ 673 + ${optionalString (cfg.apiserver.tlsCertFile != null) 674 "--tls-cert-file=${cfg.apiserver.tlsCertFile}"} \ 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) 687 "--client-ca-file=${cfg.apiserver.clientCaFile}"} \ 688 --authorization-mode=${cfg.apiserver.authorizationMode} \ 689 ${optionalString (cfg.apiserver.authorizationMode == "ABAC") 690 + "--authorization-policy-file=${policyFile}"} \ 691 --secure-port=${toString cfg.apiserver.securePort} \ 692 --service-cluster-ip-range=${cfg.apiserver.portalNet} \ 693 + ${optionalString (cfg.apiserver.runtimeConfig != "") 694 "--runtime-config=${cfg.apiserver.runtimeConfig}"} \ 695 --admission_control=${concatStringsSep "," cfg.apiserver.admissionControl} \ 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"} \ 700 ${cfg.apiserver.extraOpts} 701 ''; 702 + WorkingDirectory = cfg.dataDir; 703 User = "kubernetes"; 704 + Group = "kubernetes"; 705 + AmbientCapabilities = "cap_net_bind_service"; 706 + Restart = "on-failure"; 707 + RestartSec = 5; 708 }; 709 }; 710 }) ··· 713 systemd.services.kube-scheduler = { 714 description = "Kubernetes Scheduler Service"; 715 wantedBy = [ "multi-user.target" ]; 716 + after = [ "kube-apiserver.service" ]; 717 serviceConfig = { 718 ExecStart = ''${cfg.package}/bin/kube-scheduler \ 719 --address=${cfg.scheduler.address} \ 720 --port=${toString cfg.scheduler.port} \ 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"} \ 725 ${cfg.scheduler.extraOpts} 726 ''; 727 + WorkingDirectory = cfg.dataDir; 728 User = "kubernetes"; 729 + Group = "kubernetes"; 730 }; 731 }; 732 }) ··· 735 systemd.services.kube-controller-manager = { 736 description = "Kubernetes Controller Manager Service"; 737 wantedBy = [ "multi-user.target" ]; 738 + after = [ "kube-apiserver.service" ]; 739 serviceConfig = { 740 ExecStart = ''${cfg.package}/bin/kube-controller-manager \ 741 --address=${cfg.controllerManager.address} \ 742 --port=${toString cfg.controllerManager.port} \ 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"} \ 748 ${optionalString (cfg.controllerManager.rootCaFile!=null) 749 "--root-ca-file=${cfg.controllerManager.rootCaFile}"} \ 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"} \ 755 ${cfg.controllerManager.extraOpts} 756 ''; 757 + WorkingDirectory = cfg.dataDir; 758 User = "kubernetes"; 759 + Group = "kubernetes"; 760 }; 761 }; 762 }) 763 764 (mkIf cfg.proxy.enable { 765 systemd.services.kube-proxy = { 766 description = "Kubernetes Proxy Service"; 767 wantedBy = [ "multi-user.target" ]; 768 + after = [ "kube-apiserver.service" ]; 769 + path = [pkgs.iptables]; 770 serviceConfig = { 771 ExecStart = ''${cfg.package}/bin/kube-proxy \ 772 + --kubeconfig=${kubeconfig} \ 773 --bind-address=${cfg.proxy.address} \ 774 + ${optionalString cfg.verbose "--v=6"} \ 775 + ${optionalString cfg.verbose "--log-flush-frequency=1s"} \ 776 + ${cfg.controllerManager.extraOpts} 777 ''; 778 + WorkingDirectory = cfg.dataDir; 779 }; 780 }; 781 }) 782 783 + (mkIf cfg.dns.enable { 784 + systemd.services.kube-dns = { 785 + description = "Kubernetes Dns Service"; 786 wantedBy = [ "multi-user.target" ]; 787 + after = [ "kube-apiserver.service" ]; 788 serviceConfig = { 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} 796 ''; 797 + WorkingDirectory = cfg.dataDir; 798 User = "kubernetes"; 799 + Group = "kubernetes"; 800 + AmbientCapabilities = "cap_net_bind_service"; 801 + SendSIGHUP = true; 802 }; 803 }; 804 + }) 805 + 806 + (mkIf cfg.kubelet.enable { 807 + boot.kernelModules = ["br_netfilter"]; 808 }) 809 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; 814 services.kubernetes.apiserver.enable = mkDefault true; 815 services.kubernetes.scheduler.enable = mkDefault true; 816 services.kubernetes.controllerManager.enable = mkDefault true; 817 + services.etcd.enable = mkDefault (cfg.etcd.servers == ["http://127.0.0.1:2379"]); 818 }) 819 820 (mkIf (any (el: el == "node") cfg.roles) { 821 virtualisation.docker.enable = mkDefault true; 822 + virtualisation.docker.logDriver = mkDefault "json-file"; 823 services.kubernetes.kubelet.enable = mkDefault true; 824 services.kubernetes.proxy.enable = mkDefault true; 825 + services.kubernetes.dns.enable = mkDefault true; 826 }) 827 828 (mkIf ( ··· 830 cfg.scheduler.enable || 831 cfg.controllerManager.enable || 832 cfg.kubelet.enable || 833 + cfg.proxy.enable || 834 + cfg.dns.enable 835 ) { 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 + ]; 841 842 environment.systemPackages = [ cfg.package ]; 843 users.extraUsers = singleton { 844 name = "kubernetes"; 845 uid = config.ids.uids.kubernetes; ··· 851 }; 852 users.extraGroups.kubernetes.gid = config.ids.gids.kubernetes; 853 }) 854 ]; 855 }
+383 -157
nixos/tests/kubernetes.nix
··· 1 - # This test runs two node kubernetes cluster and checks if simple redis pod works 2 3 - import ./make-test.nix ({ pkgs, ...} : rec { 4 - name = "kubernetes"; 5 - meta = with pkgs.stdenv.lib.maintainers; { 6 - maintainers = [ offline ]; 7 }; 8 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 47 ''; 48 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"; 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; 71 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 [ ]; 82 }; 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"; 88 89 - networking.firewall.enable = false; 90 - #networking.firewall.allowedTCPPorts = [ 4001 7001 ]; 91 92 - environment.systemPackages = [ pkgs.redis ]; 93 }; 94 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 - }; 110 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"; 127 128 - networking.firewall.enable = false; 129 - #networking.firewall.allowedTCPPorts = [ 4001 7001 ]; 130 131 - environment.systemPackages = [ pkgs.redis ]; 132 }; 133 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 - ''; 147 }; 148 - }; 149 150 - testScript = '' 151 - startAll; 152 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"); 158 159 - $node->waitForUnit("kubernetes-kubelet.service"); 160 - $node->waitForUnit("kubernetes-proxy.service"); 161 162 - $master->waitUntilSucceeds("kubectl get minions | grep master"); 163 - $master->waitUntilSucceeds("kubectl get minions | grep node"); 164 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"); 175 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 - } 180 181 - ''; 182 - })
··· 1 + { system ? builtins.currentSystem }: 2 + 3 + with import ../lib/testing.nix { inherit system; }; 4 + with import ../lib/qemu-flags.nix; 5 + with pkgs.lib; 6 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"; 40 }; 41 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"); 48 ''; 49 + in { 50 + # This test runs kubernetes on a single node 51 + trivial = makeTest { 52 + name = "kubernetes-trivial"; 53 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 63 + services.kubernetes.roles = ["master" "node"]; 64 + virtualisation.docker.extraOptions = "--iptables=false --ip-masq=false -b cbr0"; 65 66 + networking.bridges.cbr0.interfaces = []; 67 + networking.interfaces.cbr0 = {}; 68 }; 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 + ''; 175 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 + ''; 186 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"]; 230 }; 231 + }; 232 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 + }; 239 240 + networking.firewall.allowedTCPPorts = [ 2379 2380 ]; 241 + }; 242 243 + kubeConfig = { 244 + virtualisation.diskSize = 2048; 245 + programs.bash.enableCompletion = true; 246 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; 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 + }; 273 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; 299 }; 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 + }; 363 364 + # Kubernetes TCP load balancer 365 + kubernetes = { config, pkgs, ... }: { 366 + # kubernetes 367 + networking.firewall.allowedTCPPorts = [ 443 ]; 368 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 375 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 + }; 392 393 + kubeWorker1 = { config, pkgs, lib, nodes, ... }: { 394 + require = [kubeWorkerConfig]; 395 + }; 396 397 + kubeWorker2 = { config, pkgs, lib, nodes, ... }: { 398 + require = [kubeWorkerConfig]; 399 + }; 400 + }; 401 402 + testScript = '' 403 + startAll; 404 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 ''; 49 50 preFixup = '' 51 - wrapProgram "$out/bin/kube-proxy" --prefix PATH : "${iptables}/bin" 52 - wrapProgram "$out/bin/kubelet" --prefix PATH : "${coreutils}/bin" 53 - 54 # Remove references to go compiler 55 while read file; do 56 cat $file | sed "s,${go},$(echo "${go}" | sed "s,$NIX_STORE/[^-]*,$NIX_STORE/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee,"),g" > $file.tmp
··· 48 ''; 49 50 preFixup = '' 51 # Remove references to go compiler 52 while read file; do 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 pulseaudioSupport = config.pulseaudio or false; 12403 }; 12404 12405 communi = qt5.callPackage ../applications/networking/irc/communi { }; 12406 12407 compiz = callPackage ../applications/window-managers/compiz {
··· 12402 pulseaudioSupport = config.pulseaudio or false; 12403 }; 12404 12405 + cni = callPackage ../applications/networking/cluster/cni {}; 12406 + 12407 communi = qt5.callPackage ../applications/networking/irc/communi { }; 12408 12409 compiz = callPackage ../applications/window-managers/compiz {