about summary refs log tree commit diff
path: root/nixos/modules/services/cluster/kubernetes.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/cluster/kubernetes.nix')
-rw-r--r--nixos/modules/services/cluster/kubernetes.nix598
1 files changed, 409 insertions, 189 deletions
diff --git a/nixos/modules/services/cluster/kubernetes.nix b/nixos/modules/services/cluster/kubernetes.nix
index 19303e97a706..4bdfa6d1ff58 100644
--- a/nixos/modules/services/cluster/kubernetes.nix
+++ b/nixos/modules/services/cluster/kubernetes.nix
@@ -5,14 +5,77 @@ with lib;
 let
   cfg = config.services.kubernetes;
 
+  skipAttrs = attrs: map (filterAttrs (k: v: k != "enable"))
+    (filter (v: !(hasAttr "enable" v) || v.enable) attrs);
+
+  infraContainer = pkgs.dockerTools.buildImage {
+    name = "pause";
+    tag = "latest";
+    contents = cfg.package.pause;
+    config.Cmd = "/bin/pause";
+  };
+
+  kubeconfig = pkgs.writeText "kubeconfig" (builtins.toJSON {
+    apiVersion = "v1";
+    kind = "Config";
+    clusters = [{
+      name = "local";
+      cluster.certificate-authority = cfg.kubeconfig.caFile;
+      cluster.server = cfg.kubeconfig.server;
+    }];
+    users = [{
+      name = "kubelet";
+      user = {
+        client-certificate = cfg.kubeconfig.certFile;
+        client-key = cfg.kubeconfig.keyFile;
+      };
+    }];
+    contexts = [{
+      context = {
+        cluster = "local";
+        user = "kubelet";
+      };
+      current-context = "kubelet-context";
+    }];
+  });
+
+  policyFile = pkgs.writeText "kube-policy"
+    concatStringsSep "\n" (map (builtins.toJSON cfg.apiserver.authorizationPolicy));
+
+  cniConfig = pkgs.buildEnv {
+    name = "kubernetes-cni-config";
+    paths = imap (i: entry:
+      pkgs.writeTextDir "${10+i}-${entry.type}.conf" (builtins.toJSON entry)
+    ) cfg.kubelet.cni.config;
+  };
+
+  manifests = pkgs.buildEnv {
+    name = "kubernetes-manifests";
+    paths = mapAttrsToList (name: manifest:
+      pkgs.writeTextDir "${name}.json" (builtins.toJSON manifest)
+    ) cfg.kubelet.manifests;
+  };
+
 in {
 
   ###### interface
 
   options.services.kubernetes = {
+    roles = mkOption {
+      description = ''
+        Kubernetes role that this machine should take.
+
+        Master role will enable etcd, apiserver, scheduler and controller manager
+        services. Node role will enable etcd, docker, kubelet and proxy services.
+      '';
+      default = [];
+      type = types.listOf (types.enum ["master" "node"]);
+    };
+
     package = mkOption {
       description = "Kubernetes package to use.";
       type = types.package;
+      default = pkgs.kubernetes;
     };
 
     verbose = mkOption {
@@ -21,21 +84,56 @@ in {
       type = types.bool;
     };
 
-    etcdServers = mkOption {
-      description = "Kubernetes list of etcd servers to watch.";
-      default = [ "127.0.0.1:2379" ];
-      type = types.listOf types.str;
+    etcd = {
+      servers = mkOption {
+        description = "List of etcd servers. By default etcd is started, except if this option is changed.";
+        default = ["http://127.0.0.1:2379"];
+        type = types.listOf types.str;
+      };
+
+      keyFile = mkOption {
+        description = "Etcd key file";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      certFile = mkOption {
+        description = "Etcd cert file";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      caFile = mkOption {
+        description = "Etcd ca file";
+        default = null;
+        type = types.nullOr types.path;
+      };
     };
 
-    roles = mkOption {
-      description = ''
-        Kubernetes role that this machine should take.
+    kubeconfig = {
+      server = mkOption {
+        description = "Kubernetes apiserver server address";
+        default = "http://${cfg.apiserver.address}:${toString cfg.apiserver.port}";
+        type = types.str;
+      };
 
-        Master role will enable etcd, apiserver, scheduler and controller manager
-        services. Node role will enable etcd, docker, kubelet and proxy services.
-      '';
-      default = [];
-      type = types.listOf (types.enum ["master" "node"]);
+      caFile = mkOption {
+        description = "Certificate authrority file to use to connect to kuberentes apiserver";
+        type = types.nullOr types.path;
+        default = null;
+      };
+
+      certFile = mkOption {
+        description = "Client certificate file to use to connect to kubernetes";
+        type = types.nullOr types.path;
+        default = null;
+      };
+
+      keyFile = mkOption {
+        description = "Client key file to use to connect to kubernetes";
+        type = types.nullOr types.path;
+        default = null;
+      };
     };
 
     dataDir = mkOption {
@@ -44,12 +142,6 @@ in {
       type = types.path;
     };
 
-    dockerCfg = mkOption {
-      description = "Kubernetes contents of dockercfg file.";
-      default = "";
-      type = types.lines;
-    };
-
     apiserver = {
       enable = mkOption {
         description = "Whether to enable kubernetes apiserver.";
@@ -72,6 +164,16 @@ in {
         type = types.str;
       };
 
+      advertiseAddress = mkOption {
+        description = ''
+          Kubernetes apiserver IP address on which to advertise the apiserver
+          to members of the cluster. This address must be reachable by the rest
+          of the cluster.
+        '';
+        default = null;
+        type = types.nullOr types.str;
+      };
+
       port = mkOption {
         description = "Kubernetes apiserver listening port.";
         default = 8080;
@@ -80,41 +182,36 @@ in {
 
       securePort = mkOption {
         description = "Kubernetes apiserver secure port.";
-        default = 6443;
+        default = 443;
         type = types.int;
       };
 
       tlsCertFile = mkOption {
         description = "Kubernetes apiserver certificate file.";
-        default = "";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.path;
       };
 
-      tlsPrivateKeyFile = mkOption {
+      tlsKeyFile = mkOption {
         description = "Kubernetes apiserver private key file.";
-        default = "";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.path;
       };
 
       clientCaFile = mkOption {
         description = "Kubernetes apiserver CA file for client auth.";
-        default = "";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.path;
       };
 
       tokenAuth = mkOption {
         description = ''
           Kubernetes apiserver token authentication file. See
-          <link xlink:href="http://kubernetes.io/v1.0/docs/admin/authentication.html"/>
+          <link xlink:href="http://kubernetes.io/docs/admin/authentication.html"/>
         '';
-        default = {};
-        example = literalExample ''
-          {
-            alice = "abc123";
-            bob = "xyz987";
-          }
-        '';
-        type = types.attrsOf types.str;
+        default = null;
+        example = ''token,user,uid,"group1,group2,group3"'';
+        type = types.nullOr types.lines;
       };
 
       authorizationMode = mkOption {
@@ -148,13 +245,13 @@ in {
 
       allowPrivileged = mkOption {
         description = "Whether to allow privileged containers on kubernetes.";
-        default = false;
+        default = true;
         type = types.bool;
       };
 
       portalNet = mkOption {
         description = "Kubernetes CIDR notation IP range from which to assign portal IPs";
-        default = "10.10.10.10/16";
+        default = "10.10.10.10/24";
         type = types.str;
       };
 
@@ -171,9 +268,9 @@ in {
       admissionControl = mkOption {
         description = ''
           Kubernetes admission control plugins to use. See
-          <link xlink:href="http://kubernetes.io/v1.0/docs/admin/admission-controllers.html"/>
+          <link xlink:href="http://kubernetes.io/docs/admin/admission-controllers/"/>
         '';
-        default = ["AlwaysAdmit"];
+        default = ["NamespaceLifecycle" "LimitRanger" "ServiceAccount" "ResourceQuota"];
         example = [
           "NamespaceLifecycle" "NamespaceExists" "LimitRanger"
           "SecurityContextDeny" "ServiceAccount" "ResourceQuota"
@@ -181,15 +278,40 @@ in {
         type = types.listOf types.str;
       };
 
-      serviceAccountKey = mkOption {
+      serviceAccountKeyFile = mkOption {
         description = ''
           Kubernetes apiserver PEM-encoded x509 RSA private or public key file,
-          used to verify ServiceAccount tokens.
+          used to verify ServiceAccount tokens. By default tls private key file
+          is used.
         '';
         default = null;
         type = types.nullOr types.path;
       };
 
+      kubeletClientCaFile = mkOption {
+        description = "Path to a cert file for connecting to kubelet";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      kubeletClientCertFile = mkOption {
+        description = "Client certificate to use for connections to kubelet";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      kubeletClientKeyFile = mkOption {
+        description = "Key to use for connections to kubelet";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      kubeletHttps = mkOption {
+        description = "Whether to use https for connections to kubelet";
+        default = true;
+        type = types.bool;
+      };
+
       extraOpts = mkOption {
         description = "Kubernetes apiserver extra command line options.";
         default = "";
@@ -216,10 +338,10 @@ in {
         type = types.int;
       };
 
-      master = mkOption {
-        description = "Kubernetes apiserver address";
-        default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}";
-        type = types.str;
+      leaderElect = mkOption {
+        description = "Whether to start leader election before executing main loop";
+        type = types.bool;
+        default = false;
       };
 
       extraOpts = mkOption {
@@ -248,13 +370,13 @@ in {
         type = types.int;
       };
 
-      master = mkOption {
-        description = "Kubernetes apiserver address";
-        default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}";
-        type = types.str;
+      leaderElect = mkOption {
+        description = "Whether to start leader election before executing main loop";
+        type = types.bool;
+        default = false;
       };
 
-      serviceAccountPrivateKey = mkOption {
+      serviceAccountKeyFile = mkOption {
         description = ''
           Kubernetes controller manager PEM-encoded private RSA key file used to
           sign service account tokens
@@ -272,6 +394,12 @@ in {
         type = types.nullOr types.path;
       };
 
+      clusterCidr = mkOption {
+        description = "Kubernetes controller manager CIDR Range for Pods in cluster";
+        default = "10.10.0.0/16";
+        type = types.str;
+      };
+
       extraOpts = mkOption {
         description = "Kubernetes controller manager extra command line options.";
         default = "";
@@ -292,6 +420,12 @@ in {
         type = types.bool;
       };
 
+      registerSchedulable = mkOption {
+        description = "Register the node as schedulable. No-op if register-node is false.";
+        default = true;
+        type = types.bool;
+      };
+
       address = mkOption {
         description = "Kubernetes kubelet info server listening address.";
         default = "0.0.0.0";
@@ -304,6 +438,18 @@ in {
         type = types.int;
       };
 
+      tlsCertFile = mkOption {
+        description = "File containing x509 Certificate for HTTPS.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      tlsKeyFile = mkOption {
+        description = "File containing x509 private key matching tlsCertFile.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
       healthz = {
         bind = mkOption {
           description = "Kubernetes kubelet healthz listening address.";
@@ -326,19 +472,10 @@ in {
 
       allowPrivileged = mkOption {
         description = "Whether to allow kubernetes containers to request privileged mode.";
-        default = false;
+        default = true;
         type = types.bool;
       };
 
-      apiServers = mkOption {
-        description = ''
-          Kubernetes kubelet list of Kubernetes API servers for publishing events,
-          and reading pods and services.
-        '';
-        default = ["${cfg.apiserver.address}:${toString cfg.apiserver.port}"];
-        type = types.listOf types.str;
-      };
-
       cadvisorPort = mkOption {
         description = "Kubernetes kubelet local cadvisor port.";
         default = 4194;
@@ -347,16 +484,62 @@ in {
 
       clusterDns = mkOption {
         description = "Use alternative dns.";
-        default = "";
+        default = "10.10.1.1";
         type = types.str;
       };
 
       clusterDomain = mkOption {
         description = "Use alternative domain.";
-        default = "kubernetes.io";
+        default = "cluster.local";
         type = types.str;
       };
 
+      networkPlugin = mkOption {
+        description = "Network plugin to use by kubernetes";
+        type = types.nullOr (types.enum ["cni" "kubenet"]);
+        default = "kubenet";
+      };
+
+      cni = {
+        packages = mkOption {
+          description = "List of network plugin packages to install";
+          type = types.listOf types.package;
+          default = [];
+        };
+
+        config = mkOption {
+          description = "Kubernetes CNI configuration";
+          type = types.listOf types.attrs;
+          default = [];
+          example = literalExample ''
+            [{
+              "cniVersion": "0.2.0",
+              "name": "mynet",
+              "type": "bridge",
+              "bridge": "cni0",
+              "isGateway": true,
+              "ipMasq": true,
+              "ipam": {
+                  "type": "host-local",
+                  "subnet": "10.22.0.0/16",
+                  "routes": [
+                      { "dst": "0.0.0.0/0" }
+                  ]
+              }
+            } {
+              "cniVersion": "0.2.0",
+              "type": "loopback"
+            }]
+          '';
+        };
+      };
+
+      manifests = mkOption {
+        description = "List of manifests to bootstrap with kubelet";
+        type = types.attrsOf types.attrs;
+        default = {};
+      };
+
       extraOpts = mkOption {
         description = "Kubernetes kubelet extra command line options.";
         default = "";
@@ -377,12 +560,6 @@ in {
         type = types.str;
       };
 
-      master = mkOption {
-        description = "Kubernetes apiserver address";
-        default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}";
-        type = types.str;
-      };
-
       extraOpts = mkOption {
         description = "Kubernetes proxy extra command line options.";
         default = "";
@@ -390,23 +567,23 @@ in {
       };
     };
 
-    kube2sky = {
-      enable = mkEnableOption "Whether to enable kube2sky dns service.";
+    dns = {
+      enable = mkEnableOption "kubernetes dns service.";
 
-      domain = mkOption  {
-        description = "Kuberntes kube2sky domain under which all DNS names will be hosted.";
-        default = cfg.kubelet.clusterDomain;
-        type = types.str;
+      port = mkOption {
+        description = "Kubernetes dns listening port";
+        default = 53;
+        type = types.int;
       };
 
-      master = mkOption {
-        description = "Kubernetes apiserver address";
-        default = "${cfg.apiserver.address}:${toString cfg.apiserver.port}";
+      domain = mkOption  {
+        description = "Kuberntes dns domain under which to create names.";
+        default = cfg.kubelet.clusterDomain;
         type = types.str;
       };
 
       extraOpts = mkOption {
-        description = "Kubernetes kube2sky extra command line options.";
+        description = "Kubernetes dns extra command line options.";
         default = "";
         type = types.str;
       };
@@ -416,50 +593,118 @@ in {
   ###### implementation
 
   config = mkMerge [
+    (mkIf cfg.kubelet.enable {
+      systemd.services.kubelet = {
+        description = "Kubernetes Kubelet Service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" "docker.service" "kube-apiserver.service" ];
+        path = with pkgs; [ gitMinimal openssh docker utillinux iproute ethtool thin-provisioning-tools iptables ];
+        preStart = ''
+          docker load < ${infraContainer}
+          rm /opt/cni/bin/* || true
+          ${concatMapStringsSep "\n" (p: "ln -fs ${p.plugins}/* /opt/cni/bin") cfg.kubelet.cni.packages}
+        '';
+        serviceConfig = {
+          ExecStart = ''${cfg.package}/bin/kubelet \
+            --pod-manifest-path=${manifests} \
+            --kubeconfig=${kubeconfig} \
+            --require-kubeconfig \
+            --address=${cfg.kubelet.address} \
+            --port=${toString cfg.kubelet.port} \
+            --register-node=${if cfg.kubelet.registerNode then "true" else "false"} \
+            --register-schedulable=${if cfg.kubelet.registerSchedulable then "true" else "false"} \
+            ${optionalString (cfg.kubelet.tlsCertFile != null)
+              "--tls-cert-file=${cfg.kubelet.tlsCertFile}"} \
+            ${optionalString (cfg.kubelet.tlsKeyFile != null)
+              "--tls-private-key-file=${cfg.kubelet.tlsKeyFile}"} \
+            --healthz-bind-address=${cfg.kubelet.healthz.bind} \
+            --healthz-port=${toString cfg.kubelet.healthz.port} \
+            --hostname-override=${cfg.kubelet.hostname} \
+            --allow-privileged=${if cfg.kubelet.allowPrivileged then "true" else "false"} \
+            --root-dir=${cfg.dataDir} \
+            --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \
+            ${optionalString (cfg.kubelet.clusterDns != "")
+              "--cluster-dns=${cfg.kubelet.clusterDns}"} \
+            ${optionalString (cfg.kubelet.clusterDomain != "")
+              "--cluster-domain=${cfg.kubelet.clusterDomain}"} \
+            --pod-infra-container-image=pause \
+            ${optionalString (cfg.kubelet.networkPlugin != null)
+              "--network-plugin=${cfg.kubelet.networkPlugin}"} \
+            --cni-conf-dir=${cniConfig} \
+            --reconcile-cidr \
+            --hairpin-mode=hairpin-veth \
+            ${optionalString cfg.verbose "--v=6 --log_flush_frequency=1s"} \
+            ${cfg.kubelet.extraOpts}
+          '';
+          WorkingDirectory = cfg.dataDir;
+        };
+      };
+
+      environment.etc = mapAttrs' (name: manifest:
+        nameValuePair "kubernetes/manifests/${name}.json" {
+          text = builtins.toJSON manifest;
+          mode = "0755";
+        }
+      ) cfg.kubelet.manifests;
+
+      # Allways include cni plugins
+      services.kubernetes.kubelet.cni.packages = [pkgs.cni];
+    })
+
     (mkIf cfg.apiserver.enable {
       systemd.services.kube-apiserver = {
-        description = "Kubernetes Api Server";
+        description = "Kubernetes Kubelet Service";
         wantedBy = [ "multi-user.target" ];
-        requires = ["kubernetes-setup.service"];
-        after = [ "network.target" "etcd.service" "kubernetes-setup.service" ];
+        after = [ "network.target" "docker.service" ];
         serviceConfig = {
-          ExecStart = let
-            authorizationPolicyFile =
-              pkgs.writeText "kubernetes-policy"
-                (builtins.toJSON cfg.apiserver.authorizationPolicy);
-            tokenAuthFile =
-              pkgs.writeText "kubernetes-auth"
-                (concatImapStringsSep "\n" (i: v: v + "," + (toString i))
-                    (mapAttrsToList (name: token: token + "," + name) cfg.apiserver.tokenAuth));
-          in ''${cfg.package}/bin/kube-apiserver \
-            --etcd-servers=${concatMapStringsSep "," (f: "http://${f}") cfg.etcdServers} \
-            --insecure-bind-address=${cfg.apiserver.address} \
+          ExecStart = ''${cfg.package}/bin/kube-apiserver \
+            --etcd-servers=${concatStringsSep "," cfg.etcd.servers} \
+            ${optionalString (cfg.etcd.caFile != null)
+              "--etcd-cafile=${cfg.etcd.caFile}"} \
+            ${optionalString (cfg.etcd.certFile != null)
+              "--etcd-certfile=${cfg.etcd.certFile}"} \
+            ${optionalString (cfg.etcd.keyFile != null)
+              "--etcd-keyfile=${cfg.etcd.keyFile}"} \
             --insecure-port=${toString cfg.apiserver.port} \
-            --bind-address=${cfg.apiserver.publicAddress} \
+            --bind-address=0.0.0.0 \
+            ${optionalString (cfg.apiserver.advertiseAddress != null)
+              "--advertise-address=${cfg.apiserver.advertiseAddress}"} \
             --allow-privileged=${if cfg.apiserver.allowPrivileged then "true" else "false"} \
-            ${optionalString (cfg.apiserver.tlsCertFile!="")
+            ${optionalString (cfg.apiserver.tlsCertFile != null)
               "--tls-cert-file=${cfg.apiserver.tlsCertFile}"} \
-            ${optionalString (cfg.apiserver.tlsPrivateKeyFile!="")
-              "--tls-private-key-file=${cfg.apiserver.tlsPrivateKeyFile}"} \
-            ${optionalString (cfg.apiserver.tokenAuth!=[])
-              "--token-auth-file=${tokenAuthFile}"} \
-            ${optionalString (cfg.apiserver.clientCaFile!="")
+            ${optionalString (cfg.apiserver.tlsKeyFile != null)
+              "--tls-private-key-file=${cfg.apiserver.tlsKeyFile}"} \
+            ${optionalString (cfg.apiserver.tokenAuth != null)
+              "--token-auth-file=${cfg.apiserver.tokenAuth}"} \
+            --kubelet-https=${if cfg.apiserver.kubeletHttps then "true" else "false"} \
+            ${optionalString (cfg.apiserver.kubeletClientCaFile != null)
+              "--kubelet-certificate-authority=${cfg.apiserver.kubeletClientCaFile}"} \
+            ${optionalString (cfg.apiserver.kubeletClientCertFile != null)
+              "--kubelet-client-certificate=${cfg.apiserver.kubeletClientCertFile}"} \
+            ${optionalString (cfg.apiserver.kubeletClientKeyFile != null)
+              "--kubelet-client-key=${cfg.apiserver.kubeletClientKeyFile}"} \
+            ${optionalString (cfg.apiserver.clientCaFile != null)
               "--client-ca-file=${cfg.apiserver.clientCaFile}"} \
             --authorization-mode=${cfg.apiserver.authorizationMode} \
             ${optionalString (cfg.apiserver.authorizationMode == "ABAC")
-              "--authorization-policy-file=${authorizationPolicyFile}"} \
+              "--authorization-policy-file=${policyFile}"} \
             --secure-port=${toString cfg.apiserver.securePort} \
             --service-cluster-ip-range=${cfg.apiserver.portalNet} \
-            ${optionalString (cfg.apiserver.runtimeConfig!="")
+            ${optionalString (cfg.apiserver.runtimeConfig != "")
               "--runtime-config=${cfg.apiserver.runtimeConfig}"} \
             --admission_control=${concatStringsSep "," cfg.apiserver.admissionControl} \
-            ${optionalString (cfg.apiserver.serviceAccountKey!=null)
-              "--service-account-key-file=${cfg.apiserver.serviceAccountKey}"} \
-            --logtostderr=true \
-            ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \
+            ${optionalString (cfg.apiserver.serviceAccountKeyFile!=null)
+              "--service-account-key-file=${cfg.apiserver.serviceAccountKeyFile}"} \
+            ${optionalString cfg.verbose "--v=6"} \
+            ${optionalString cfg.verbose "--log-flush-frequency=1s"} \
             ${cfg.apiserver.extraOpts}
           '';
+          WorkingDirectory = cfg.dataDir;
           User = "kubernetes";
+          Group = "kubernetes";
+          AmbientCapabilities = "cap_net_bind_service";
+          Restart = "on-failure";
+          RestartSec = 5;
         };
       };
     })
@@ -468,17 +713,20 @@ in {
       systemd.services.kube-scheduler = {
         description = "Kubernetes Scheduler Service";
         wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" "kubernetes-apiserver.service" ];
+        after = [ "kube-apiserver.service" ];
         serviceConfig = {
           ExecStart = ''${cfg.package}/bin/kube-scheduler \
             --address=${cfg.scheduler.address} \
             --port=${toString cfg.scheduler.port} \
-            --master=${cfg.scheduler.master} \
-            --logtostderr=true \
-            ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \
+            --leader-elect=${if cfg.scheduler.leaderElect then "true" else "false"} \
+            --kubeconfig=${kubeconfig} \
+            ${optionalString cfg.verbose "--v=6"} \
+            ${optionalString cfg.verbose "--log-flush-frequency=1s"} \
             ${cfg.scheduler.extraOpts}
           '';
+          WorkingDirectory = cfg.dataDir;
           User = "kubernetes";
+          Group = "kubernetes";
         };
       };
     })
@@ -487,113 +735,94 @@ in {
       systemd.services.kube-controller-manager = {
         description = "Kubernetes Controller Manager Service";
         wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" "kubernetes-apiserver.service" ];
+        after = [ "kube-apiserver.service" ];
         serviceConfig = {
           ExecStart = ''${cfg.package}/bin/kube-controller-manager \
             --address=${cfg.controllerManager.address} \
             --port=${toString cfg.controllerManager.port} \
-            --master=${cfg.controllerManager.master} \
-            ${optionalString (cfg.controllerManager.serviceAccountPrivateKey!=null)
-              "--service-account-private-key-file=${cfg.controllerManager.serviceAccountPrivateKey}"} \
+            --kubeconfig=${kubeconfig} \
+            --leader-elect=${if cfg.controllerManager.leaderElect then "true" else "false"} \
+            ${if (cfg.controllerManager.serviceAccountKeyFile!=null)
+              then "--service-account-private-key-file=${cfg.controllerManager.serviceAccountKeyFile}"
+              else "--service-account-private-key-file=/var/run/kubernetes/apiserver.key"} \
             ${optionalString (cfg.controllerManager.rootCaFile!=null)
               "--root-ca-file=${cfg.controllerManager.rootCaFile}"} \
-            --logtostderr=true \
-            ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \
+            ${optionalString (cfg.controllerManager.clusterCidr!=null)
+              "--cluster-cidr=${cfg.controllerManager.clusterCidr}"} \
+            --allocate-node-cidrs=true \
+            ${optionalString cfg.verbose "--v=6"} \
+            ${optionalString cfg.verbose "--log-flush-frequency=1s"} \
             ${cfg.controllerManager.extraOpts}
           '';
+          WorkingDirectory = cfg.dataDir;
           User = "kubernetes";
+          Group = "kubernetes";
         };
       };
     })
 
-    (mkIf cfg.kubelet.enable {
-      systemd.services.kubelet = {
-        description = "Kubernetes Kubelet Service";
-        wantedBy = [ "multi-user.target" ];
-        requires = ["kubernetes-setup.service"];
-        after = [ "network.target" "etcd.service" "docker.service" "kubernetes-setup.service" ];
-        path = [ pkgs.gitMinimal pkgs.openssh ];
-        script = ''
-          export PATH="/bin:/sbin:/usr/bin:/usr/sbin:$PATH"
-          exec ${cfg.package}/bin/kubelet \
-            --api-servers=${concatMapStringsSep "," (f: "http://${f}") cfg.kubelet.apiServers}  \
-            --register-node=${if cfg.kubelet.registerNode then "true" else "false"} \
-            --address=${cfg.kubelet.address} \
-            --port=${toString cfg.kubelet.port} \
-            --healthz-bind-address=${cfg.kubelet.healthz.bind} \
-            --healthz-port=${toString cfg.kubelet.healthz.port} \
-            --hostname-override=${cfg.kubelet.hostname} \
-            --allow-privileged=${if cfg.kubelet.allowPrivileged then "true" else "false"} \
-            --root-dir=${cfg.dataDir} \
-            --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \
-            ${optionalString (cfg.kubelet.clusterDns != "")
-                ''--cluster-dns=${cfg.kubelet.clusterDns}''} \
-            ${optionalString (cfg.kubelet.clusterDomain != "")
-                ''--cluster-domain=${cfg.kubelet.clusterDomain}''} \
-            --logtostderr=true \
-            ${optionalString cfg.verbose "--v=6 --log_flush_frequency=1s"} \
-            ${cfg.kubelet.extraOpts}
-          '';
-        serviceConfig.WorkingDirectory = cfg.dataDir;
-      };
-    })
-
     (mkIf cfg.proxy.enable {
       systemd.services.kube-proxy = {
         description = "Kubernetes Proxy Service";
         wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" "etcd.service" ];
+        after = [ "kube-apiserver.service" ];
+        path = [pkgs.iptables];
         serviceConfig = {
           ExecStart = ''${cfg.package}/bin/kube-proxy \
-            --master=${cfg.proxy.master} \
+            --kubeconfig=${kubeconfig} \
             --bind-address=${cfg.proxy.address} \
-            --logtostderr=true \
-            ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \
-            ${cfg.proxy.extraOpts}
+            ${optionalString cfg.verbose "--v=6"} \
+            ${optionalString cfg.verbose "--log-flush-frequency=1s"} \
+            ${cfg.controllerManager.extraOpts}
           '';
-          Restart = "always"; # Retry connection
-          RestartSec = "5s";
+          WorkingDirectory = cfg.dataDir;
         };
       };
     })
 
-    (mkIf cfg.kube2sky.enable {
-      systemd.services.kube2sky = {
-        description = "Kubernetes Dns Bridge Service";
+    (mkIf cfg.dns.enable {
+      systemd.services.kube-dns = {
+        description = "Kubernetes Dns Service";
         wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" "skydns.service" "etcd.service" "kubernetes-apiserver.service" ];
+        after = [ "kube-apiserver.service" ];
         serviceConfig = {
-          ExecStart = ''${cfg.package}/bin/kube2sky \
-            -etcd-server=http://${head cfg.etcdServers} \
-            -domain=${cfg.kube2sky.domain} \
-            -kube_master_url=http://${cfg.kube2sky.master} \
-            -logtostderr=true \
-            ${optionalString cfg.verbose "--v=6 --log-flush-frequency=1s"} \
-            ${cfg.kube2sky.extraOpts}
+          ExecStart = ''${cfg.package}/bin/kube-dns \
+            --kubecfg-file=${kubeconfig} \
+            --dns-port=${toString cfg.dns.port} \
+            --domain=${cfg.dns.domain} \
+            ${optionalString cfg.verbose "--v=6"} \
+            ${optionalString cfg.verbose "--log-flush-frequency=1s"} \
+            ${cfg.dns.extraOpts}
           '';
+          WorkingDirectory = cfg.dataDir;
           User = "kubernetes";
+          Group = "kubernetes";
+          AmbientCapabilities = "cap_net_bind_service";
+          SendSIGHUP = true;
         };
       };
     })
 
+    (mkIf cfg.kubelet.enable {
+      boot.kernelModules = ["br_netfilter"];
+    })
+
     (mkIf (any (el: el == "master") cfg.roles) {
+      virtualisation.docker.enable = mkDefault true;
+      services.kubernetes.kubelet.enable = mkDefault true;
+      services.kubernetes.kubelet.allowPrivileged = mkDefault true;
       services.kubernetes.apiserver.enable = mkDefault true;
       services.kubernetes.scheduler.enable = mkDefault true;
       services.kubernetes.controllerManager.enable = mkDefault true;
-      services.kubernetes.kube2sky.enable = mkDefault true;
+      services.etcd.enable = mkDefault (cfg.etcd.servers == ["http://127.0.0.1:2379"]);
     })
 
     (mkIf (any (el: el == "node") cfg.roles) {
       virtualisation.docker.enable = mkDefault true;
+      virtualisation.docker.logDriver = mkDefault "json-file";
       services.kubernetes.kubelet.enable = mkDefault true;
       services.kubernetes.proxy.enable = mkDefault true;
-    })
-
-    (mkIf (any (el: el == "node" || el == "master") cfg.roles) {
-      services.etcd.enable = mkDefault true;
-
-      services.skydns.enable = mkDefault true;
-      services.skydns.domain = mkDefault cfg.kubelet.clusterDomain;
+      services.kubernetes.dns.enable = mkDefault true;
     })
 
     (mkIf (
@@ -601,24 +830,16 @@ in {
         cfg.scheduler.enable ||
         cfg.controllerManager.enable ||
         cfg.kubelet.enable ||
-        cfg.proxy.enable
+        cfg.proxy.enable ||
+        cfg.dns.enable
     ) {
-      systemd.services.kubernetes-setup = {
-        description = "Kubernetes setup.";
-        serviceConfig.Type = "oneshot";
-        script = ''
-          mkdir -p /var/run/kubernetes
-          chown kubernetes /var/lib/kubernetes
-
-          rm ${cfg.dataDir}/.dockercfg || true
-          ln -fs ${pkgs.writeText "kubernetes-dockercfg" cfg.dockerCfg} ${cfg.dataDir}/.dockercfg
-        '';
-      };
-
-      services.kubernetes.package = mkDefault pkgs.kubernetes;
+      systemd.tmpfiles.rules = [
+        "d /opt/cni/bin 0755 root root -"
+        "d /var/run/kubernetes 0755 kubernetes kubernetes -"
+        "d /var/lib/kubernetes 0755 kubernetes kubernetes -"
+      ];
 
       environment.systemPackages = [ cfg.package ];
-
       users.extraUsers = singleton {
         name = "kubernetes";
         uid = config.ids.uids.kubernetes;
@@ -630,6 +851,5 @@ in {
       };
       users.extraGroups.kubernetes.gid = config.ids.gids.kubernetes;
     })
-
   ];
 }