{ config, lib, pkgs, ... }: let cfg = config.services.kubernetes; in { options.services.kubernetes = with lib; with types; let mkCertOptions = name: { key = mkOption { description = "${name} key file."; type = path; }; crt = mkOption { description = "${name} certificate file."; type = path; }; }; in { enable = mkEnableOption "kubernetes"; lib = mkOption { description = "Kubernetes utility functions."; type = raw; readOnly = true; default = { mkKubeConfig = name: ca: cert: key: (pkgs.formats.json { }).generate "${name}-kubeconfig.json" { apiVersion = "v1"; kind = "Config"; clusters = [ { name = "local"; cluster = { server = cfg.apiserver._address; "certificate-authority" = ca; }; } ]; users = [ { inherit name; user = { "client-certificate" = cert; "client-key" = key; }; } ]; contexts = [ { name = "local"; context = { cluster = "local"; user = name; }; } ]; current-context = "local"; }; }; }; roles = mkOption { description = "Kubernetes role that this machine should take."; type = listOf (enum [ "master" "node" ]); default = [ "master" "node" ]; }; address = mkOption { description = "Kubernetes master server address."; type = str; default = "localhost"; }; cidr = mkOption { description = "Kubernetes cluster CIDR."; type = str; default = "10.0.0.0/24"; }; cas = { kubernetes = mkCertOptions "Kubernetes CA"; frontProxy = mkCertOptions "Front Proxy CA"; etcd = mkCertOptions "ETCD CA"; }; certs = { apiserver = { server = mkCertOptions "Kubernetes API Server"; kubeletClient = mkCertOptions "Kubernetes API Server Kubelet Client"; etcdClient = mkCertOptions "Kubernetes API Server ETCD Client"; }; etcd = { server = mkCertOptions "ETCD Server"; peer = mkCertOptions "ETCD Peer"; }; frontProxy = mkCertOptions "Front Proxy Client"; serviceAccount = { public = mkOption { description = "Service account public key file."; type = path; }; private = mkOption { description = "Service account private key file."; type = path; }; }; accounts = { scheduler = mkCertOptions "Kubernetes Scheduler"; controllerManager = mkCertOptions "Kubernetes Controller Manager"; addonManager = mkCertOptions "Kubernetes Addon Manager"; proxy = mkCertOptions "Kubernetes Proxy"; admin = mkCertOptions "Kubernetes Admin"; }; }; kubeconfigs = mkOption { description = "Kubernetes kubeconfigs."; type = attrsOf path; default = { }; }; apiserver = { _address = mkOption { description = "Kubernetes API server address."; internal = true; type = str; }; address = mkOption { description = "Kubernetes API server listening address."; type = str; readOnly = true; default = "0.0.0.0"; }; port = mkOption { description = "Kubernetes API server listening port."; type = port; readOnly = true; default = 6443; }; bootstrapTokenFile = mkOption { description = "Kubernetes API server bootstrap token file."; type = path; }; }; kubelet = { address = mkOption { description = "Kubernetes kubelet listening address."; type = str; readOnly = true; default = "0.0.0.0"; }; port = mkOption { description = "Kubernetes kubelet listening port."; type = port; readOnly = true; default = 10250; }; taints = let taintOptions = { name, ... }: { key = mkOption { description = "Taint key."; type = str; default = name; }; value = mkOption { description = "Taint value."; type = str; }; effect = mkOption { description = "Taint effect."; type = enum [ "NoSchedule" "PreferNoSchedule" "NoExecute" ]; }; }; in mkOption { description = "Taints to apply to the node."; type = attrsOf (submodule taintOptions); default = { }; }; bootstrapToken = mkOption { description = "Kubelet bootstrap token file."; type = path; }; seedImages = mkOption { description = "Container images to preload on the system."; type = listOf package; default = [ ]; }; cidr = mkOption { description = "Kubernetes pod CIDR."; type = str; default = "10.1.0.0/16"; }; }; scheduler = { address = mkOption { description = "Kubernetes scheduler listening address."; type = str; readOnly = true; default = "127.0.0.1"; }; port = mkOption { description = "Kubernetes scheduler listening port."; type = port; readOnly = true; default = 10251; }; }; controllerManager = { address = mkOption { description = "Kubernetes controller manager listening address."; type = str; readOnly = true; default = "127.0.0.1"; }; port = mkOption { description = "Kubernetes controller manager listening port."; type = port; readOnly = true; default = 10252; }; }; proxy = { address = mkOption { description = "Kubernetes proxy listening address."; type = str; readOnly = true; default = "0.0.0.0"; }; }; addonManager = { addons = mkOption { description = "Kubernetes addons."; type = attrsOf (coercedTo (attrs) (a: [ a ]) (listOf attrs)); default = { }; }; bootstrapAddons = mkOption { description = "Kubernetes addons applied with cluster-admin permissions."; type = attrsOf (coercedTo (attrs) (a: [ a ]) (listOf attrs)); default = { }; }; }; }; config = lib.mkIf cfg.enable ( lib.mkMerge [ # master or node { services.kubernetes = { apiserver._address = "https://${cfg.address}:${toString cfg.apiserver.port}"; kubeconfigs.admin = cfg.lib.mkKubeConfig "admin" cfg.cas.kubernetes.crt cfg.certs.accounts.admin.crt cfg.certs.accounts.admin.key; addonManager.bootstrapAddons = { addonManager = import ./addons/addon-manager { }; bootstrap = import ./addons/bootstrap { inherit config; }; kubeletApiAdmin = import ./addons/kubelet-api-admin { }; metricsServer = import ./addons/metrics-server { }; }; }; boot = { kernel.sysctl = { "net.bridge.bridge-nf-call-iptables" = 1; "net.ipv4.ip_forward" = 1; "net.bridge.bridge-nf-call-ip6tables" = 1; }; kernelModules = [ "br_netfilter" "overlay" ]; }; users = { users.kubernetes = { uid = config.ids.uids.kubernetes; group = "kubernetes"; home = "/var/lib/kubernetes"; homeMode = "755"; createHome = true; description = "Kubernetes user"; }; groups.kubernetes.gid = config.ids.gids.kubernetes; }; systemd = { targets.kubernetes = { description = "Kubernetes"; wantedBy = [ "multi-user.target" ]; }; tmpfiles.rules = [ "d /opt/cni/bin 0755 root root -" "d /run/kubernetes 0755 kubernetes kubernetes -" ]; services = { kubelet = let kubeletConfig = (pkgs.formats.json { }).generate "config.json" ({ apiVersion = "kubelet.config.k8s.io/v1beta1"; kind = "KubeletConfiguration"; address = cfg.kubelet.address; port = cfg.kubelet.port; authentication = { x509.clientCAFile = cfg.cas.kubernetes.crt; webhook = { enabled = true; cacheTTL = "10s"; }; }; authorization.mode = "Webhook"; cgroupDriver = "systemd"; hairpinMode = "hairpin-veth"; registerNode = true; containerRuntimeEndpoint = "unix:///run/containerd/containerd.sock"; failSwapOn = false; memorySwap.swapBehavior = "LimitedSwap"; rotateCertificates = true; serverTLSBootstrap = true; featureGates = { RotateKubeletServerCertificate = true; NodeSwap = true; }; healthzBindAddress = "127.0.0.1"; healthzPort = 10248; }); taints = lib.strings.concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") ( lib.attrsets.mapAttrsToList (n: v: v) cfg.kubelet.taints ); generateKubeletBootstrapKubeconfig = lib.meta.getExe ( pkgs.writeShellApplication { name = "kubelet-bootstrap-kubeconfig"; runtimeInputs = with pkgs; [ coreutils ]; text = '' mkdir -p /etc/kubernetes cat > /etc/kubernetes/bootstrap-kubeconfig <