{ pkgs, lib, config, ...}: with lib; let cfg = config.cluster; pki = import ./pki.nix { inherit pkgs; ca = cfg.initca; }; masterAddress = cfg.k8s.master.address; apiserverAddress = "https://${masterAddress}:4443"; cfssl-apitoken = let apitoken = pkgs.stdenv.mkDerivation { name = "apitoken"; buildCommand = '' head -c ${toString (32 / 2)} /dev/urandom | \ od -An -t x | tr -d ' ' > $out chmod 400 $out ''; }; in # make ca derivation sha depend on initca cfssl output pkgs.stdenv.mkDerivation { name = "cfssl-apitoken"; src = apitoken; buildCommand = '' cp $src $out ''; }; cluster-scripts = pkgs.stdenv.mkDerivation { name = "cluster-scripts"; src = ../scripts; buildCommand = '' mkdir -p $out/bin cp $src/* $out/bin ''; }; kubernetes-charts = pkgs.stdenv.mkDerivation rec { name = "kubernetes-charts"; src = ../charts; buildCommand = '' mkdir -p $out/share/${name} cp -r $src/* $out/share${name} ''; }; show-kubernetes-charts-config = let ingressNodes = builtins.foldl' (a: x: a + ", ${x}") "${cfg.k8s.master.name}" cfg.k8s.extraIngressNodes; ingressReplicaCount = builtins.toString (1 + builtins.length cfg.k8s.extraIngressNodes); in pkgs.writeScriptBin "show-kubernetes-charts-config" '' #!${pkgs.stdenv.shell} cat << EOF # Generated by show-kubernetes-charts-config # $(date) # Charts in ${kubernetes-charts} vars=( initca="${pki.initca}" apiserver="${cfg.k8s.master.name}" cluster="${cfg.clusterName}" ingress_nodes="[ ${ingressNodes} ]" ingress_replica_count="${ingressReplicaCount}" filseserver="${cfg.k8s.fileserver}" acme_email="${cfg.k8s.charts.acme_email}" grafana_smtp_user="$(echo -n ${cfg.k8s.charts.grafana_smtp_user} | base64 -w0)" grafana_smtp_password="$(echo -n ${cfg.k8s.charts.grafana_smtp_password} | base64 -w0)" ) EOF cat << 'EOF' substitute_all () { read x subs=("$@") for i in "''${subs[@]}"; do k=$(echo "$i" | cut -d= -f1) v=$(echo "$i" | cut -d= -f2) echo "$x" | sed "s/@$k@/$v/g" done } substitute_defaults () { substitute_all "''${vars[@]}" } kubectl_apply () { read x namespace=$1; shift kubectl get ns $namespace 2>&1 >/dev/null || kubectl create ns $namespace cat $x | substitute_defaults | kubectl -n $namespace apply -f - } kubectl_apply_files () { namespace=$1; shift charts=("$@") for i in "''${charts[@]}"; do cat $i | kubectl_apply $namespace done } EOF ''; install-apitoken = '' #!${pkgs.bash}/bin/bash set -e if [ -d /var/lib/cfssl ]; then cp ${cfssl-apitoken} /var/lib/cfssl/apitoken.secret chown cfssl /var/lib/cfssl/apitoken.secret chmod 640 /var/lib/cfssl/apitoken.secret else mkdir -p /var/lib/kubernetes/secrets cp ${cfssl-apitoken} /var/lib/kubernetes/secrets/apitoken.secret chown root /var/lib/kubernetes/secrets/apitoken.secret chmod 600 /var/lib/kubernetes/secrets/apitoken.secret fi ''; kubeMaster = { services.cfssl.ca = pki.ca.cert; services.cfssl.caKey = pki.ca.key; services.kubernetes = { roles = [ "master" ]; inherit apiserverAddress; masterAddress = cfg.k8s.master.name; clusterCidr = cfg.k8s.cidr; pki.genCfsslCACert = false; pki.genCfsslAPIToken = false; pki.caCertPathPrefix = "${pki.initca}/ca"; kubelet = { clusterDomain = "${cfg.clusterName}.local"; }; apiserver = { advertiseAddress = masterAddress; authorizationMode = [ "Node" "RBAC" ]; allowPrivileged = true; securePort = 4443; insecurePort = 8080; extraOpts = "--requestheader-client-ca-file ${pki.ca.cert}"; extraSANs = cfg.k8s.master.extraSANs; # verbosity = 4; }; controllerManager = { bindAddress = masterAddress; extraOpts = "--authorization-always-allow-paths=/healthz,/metrics"; }; scheduler.address = masterAddress; addonManager.enable = true; addons = { dns = { enable = true; clusterDomain = "${cfg.clusterName}.local"; reconcileMode = "EnsureExists"; }; }; }; services.etcd = { listenClientUrls = [ "https://${masterAddress}:2379" ]; }; networking.firewall = { allowedTCPPorts = [ 53 5000 8080 4443 4001 2379 2380 10250 10251 10252 ]; allowedUDPPorts = [ 53 4053 ]; }; environment.systemPackages = [ pkgs.kubernetes-helm pkgs.kubectl cluster-scripts kubernetes-charts show-kubernetes-charts-config ]; systemd.services.kube-certmgr-apitoken-bootstrap = { description = "Kubernetes certmgr bootstrapper"; wantedBy = [ "cfssl.service" ]; before = [ "cfssl.target" ]; script = install-apitoken; serviceConfig = { RestartSec = "10s"; Restart = "on-failure"; }; }; systemd.services.cfssl-restart = { enable = true; startAt = "00/6:00"; description = "Restrart cfssl which regularly locks up"; script = "systemctl restart cfssl.service"; }; }; kubeNode = { services.kubernetes = rec { roles = [ "node" ]; inherit apiserverAddress; masterAddress = cfg.k8s.master.name; clusterCidr = cfg.k8s.cidr; kubelet.clusterDomain = "${cfg.clusterName}.local"; }; networking = { firewall = { enable = true; allowedTCPPorts = [ 4194 10250 ]; allowedUDPPorts = [ 53 ]; extraCommands = ''iptables -m comment --comment "pod external access" -t nat -A POSTROUTING ! -d 10.10.0.0/16 -m addrtype ! --dst-type LOCAL -j MASQUERADE''; }; }; virtualisation.docker.extraOptions = "--insecure-registry 10.0.0.0/8"; virtualisation.docker.autoPrune.enable = true; systemd.services.kube-certmgr-apitoken-bootstrap = { description = "Kubernetes certmgr bootstrapper"; wantedBy = [ "certmgr.service" ]; before = [ "certmgr.service" ]; script = install-apitoken; serviceConfig = { RestartSec = "10s"; Restart = "on-failure"; }; }; }; in { options.cluster.k8s = { enable = mkEnableOption "Enable kubernetes"; nodes = mkOption { type = types.listOf types.attrs; default = []; }; fileserver = mkOption { type = types.str; default = null; }; cidr = mkOption { type = types.str; default = "10.0.0.0/16"; }; extraIngressNodes = mkOption { type = types.listOf types.str; default = []; }; master = { enable = mkEnableOption "Enable kubernetes master node"; address = mkOption { type = types.str; default = null; }; name = mkOption { type = types.str; default = null; }; extraSANs = mkOption { type = types.listOf types.str; default = []; }; hw = mkOption { type = types.path; default = null; }; }; node = { enable = mkEnableOption "Enable kubernetes"; }; charts = { acme_email = mkOption { type = types.str; default = ""; }; grafana_smtp_user = mkOption { type = types.str; default = ""; }; grafana_smtp_password = mkOption { type = types.str; default = ""; }; }; }; config = mkIf cfg.k8s.enable ( mkMerge [ (mkIf cfg.k8s.master.enable kubeMaster) (mkIf cfg.k8s.node.enable kubeNode) ] ); imports = [ ./os.nix ]; }