Kubenix
Kubernetes management with Nix
Usage
A minimal example flake.nix (build with nix build):
{
inputs.kubenix.url = "github:hall/kubenix";
outputs = {self, kubenix, ... }@inputs: let
system = "x86_64-linux";
in {
packages.${system}.default = (kubenix.evalModules.${system} {
module = { kubenix, ... }: {
imports = [ kubenix.modules.k8s ];
kubernetes.resources.pods.example.spec.containers.nginx.image = "nginx";
};
}).config.kubernetes.result;
};
}Either way the JSON manifests will be written to ./result containing everything defined in the Nix Code.
The k8s resources are under the top level key kubernetes.resources.
Submodules
Some things like kubernetes.namespace = "default"; apply globally for your kube config. So in order to have things in multiple namespaces defined, you need submodules. Submodules can also generate new things based on input arguments.
Submodule Definition:
{ config
, kubenix
, lib
, # Name of submodule instance.
name
, # This is a shorthand for config.submodule.args and contains
# final values of the args options.
args
, ...
}: {
imports = with kubenix.modules; [
# This needs to be imported in order to define a submodule.
submodule
# Importing this so that we can set config.kubernetes
# within the context of this submodule.
k8s
];
# Args are used to pass information from the parent context.
options.submodule.args = {
kubernetes = lib.mkOption {
description = "Kubernetes config to be applied to a specific namespace.";
# We are not given a precise type to this since we are using it
# to set kubernetes options from the k8s module which are already
# precisely typed.
type = lib.types.attrs;
default = { };
};
# You could also include whatever else input arguments you need
};
config = {
submodule = {
# Used to uniquely identify a submodule. Used to select submodule
# "prototype" when instantiating.
name = "namespaced";
# Passthru allows a submodule instance to set config of the parent
# context. It's not strictly required but it's useful for combining
# outputs of multiple submodule instances, without having to write
# ad hoc code in the parent context.
# NOTE: passthru has not effect if given options are not defined
# in the parent context. Therefore in this case we are expecting that
# parent imports kubinex.k8s module.
# Here we set kubernetes.objects.
# This is a list so even if distinct instances of the submodule contain
# definitions of identical api resources, these will not be merged or
# cause conflicts. Lists of resources from multiple submodule instances
# will simply be concatenated.
passthru.kubernetes.objects = config.kubernetes.objects;
};
kubernetes = lib.mkMerge [
# Use instance name as namespace
{ namespace = name; }
# Create namespace object
{ resources.namespaces.${name} = { }; }
# All resources defined here will use the above namespace
args.kubernetes
];
};
}To use the submodule:
{ config, lib, pkgs, kubenix, ... }: {
imports = with kubenix.modules; [ submodules k8s ];
# Import submodule.
submodules.imports = [
./namespaced.nix
];
# We can now create multiple submodule instances.
submodules.instances.namespace-http = {
# ~~~~~~~~~~~~~~
# ^
# ╭-----------------------╯
# The submodule instance name is injected as the name attribute to
# the submodule function. In this example, it is used as the namespace
# name.
#
# This needs to match config.submodule.name of an imported submodule.
submodule = "namespaced";
# Now we can set the args options defined in the submodule.
args.kubernetes.resources = {
services.nginx.spec = {
ports = [{
name = "http";
port = 80;
}];
selector.app = "nginx";
};
};
};
submodules.instances.namespace-https = {
submodule = "namespaced";
args.kubernetes.resources = {
services.nginx.spec = {
ports = [{
name = "https";
port = 443;
}];
selector.app = "nginx";
};
};
# Example of how other defaults can be applied to resources
# within a submodule.
args.kubernetes.version = "1.26";
};
# Resources defined in parent context use namespace set at this
# level and are not affected by the above submodules.
kubernetes.namespace = "default";
kubernetes.resources.services.nginx.spec = {
selector.app = "nginx-default";
};
}