Currently, I run Unraid and have all of my services’ setup there as docker containers. While this is nice and easy to setup initially, it has some major downsides:

  • It’s fragile. Unraid is prone to bugs/crashes with docker that take down my containers. It’s also not resilient so when things break I have to log in and fiddle.
  • It’s mutable. I can’t use any infrastructure-as-code tools like terraform, and configuration sort of just exist in the UI. I can’t really roll back or recover easily.
  • It’s single-node. Everything is tied to my one big server that runs the NAS, but I’d rather have the NAS as a separate fairly low-power appliance and then have a separate machine to handle things like VMs and containers.

So I’m looking ahead and thinking about what the next iteration of my homelab will look like. While I like unraid for the storage stuff, I’m a little tired of wrangling it into a container orchestrator and hypervisor, and I think this year I’ll split that job out to a dedicated machine. I’m comfortable with, and in fact prefer, IaC over fancy UIs and so would love to be able to use terraform or Pulumi or something like that. I would prefer something multi-node, as I want to be able to tie multiple machines together. And I want something that is fault-tolerant, as I host services for friends and family that currently require a lot of manual intervention to fix when they go down.

So the question is: how do you all do this? Kubernetes, docker-compose, Hashicorp Nomad? Do you run k3s, Harvester, or what? I’d love to get an idea of what people are doing and why, so I can get some ideas as to what I might do.

  • @nico@r.dcotta.eu
    link
    fedilink
    English
    4
    edit-2
    10 months ago

    I see no one else commented my stack, so I suggest:

    Nomad for managing containers if you want something high availability. Essentially the same as k8s but much much much simpler to deploy, learn, and maintain. Perfect for homelabs imo. Most of the concepts of Nomad translate well to k8s if you do want to learn it later. It integrates really well with Terraform too if you are also hoping to learn that, but it’s not a requirement.

    NixOS for managing the bare metal. It’s a lot more work to learn than say, Debian, but it is just as stable, and all configuration will be defined as code, down to the bootloader config (no bash scripts!). This makes it super robust. You can also deploy it remotely. Once you grow beyond a handful of nodes it’s important to use a config management tool, and Nix has been by far my favourite so far.

    If you really want everything to be infra-as-code, you can manage cloud providers via Terraform too.

    For networking I use wireguard, and configure it with NixOS. Specifically, I have a mesh network where every node can reach every node without extra hops. This is a requirement if you don’t want a single point of failure (hub and spoke) to disconnect your entire cluster.

    Everything in my setup is defined ‘as-code’, immutable, and multi-node (I have 7 machines) which seems to be what you want, from what you say in your post. I’ll leave my repo here, and I’m happy to answer questions!

    My opinions on the alternatives:

    Docker compose is great but doesn’t scale if you want high availability (ie, have a container be rescheduled on node failure). If you don’t want higher availability, anything more than docker might be overkill.

    Ansible and Puppet are alright but are super stateful, and require scripting. If you want immutability you will love Nix/NixOS

    k8s works (I use it at work) but is extremely hard to get right, even for well-resourced infra teams. Nomad achieves the same but with the leanings of having come afterwards, and without the history.

    • @nopersonalspace@lemmy.worldOP
      link
      fedilink
      English
      210 months ago

      Thanks for this, I’ve been sort if interested in both Nomad and NixOS for the exact reasons it seems like you use them. Thanks for linking that repo, I’ll check it out for inspiration!

      Do you find that you sometimes struggle to get things working in Nomad? My one worry is that, because it’s not as well established as kubernetes or docker, there won’t be good compatibility or documentation. For example most services in their docs will show how to deploy with kubernetes or docker, but rarely Nomad. Do you find that it’s easy enough to translate these instructions that it doesn’t matter?

      • @nico@r.dcotta.eu
        link
        fedilink
        English
        2
        edit-2
        10 months ago

        Good question! So it depends, but TLDR: imo it’s worth it, or it’s fine, but it’s easy to try yourself and see

        most services in their docs will show how to deploy with kubernetes or docker, but rarely Nomad

        You are absolutely correct, but I do find that for the large large majority of things, either you can find an online Nomad config, or the Nomad config is easy enough to translate from Docker compose. Only some complicated larger deployments (think Immich) are harder to translate, but even then it just takes some trial and error. I really do think that extra trouble of translating is very much worth the pain you save yourself in terms of deploying k8s though. You might spend a bit longer typing out the Nomad job file yourself, but in exchange you are thankfully not maintaining the k8s cluster.

        As far Nomad-specific documentation goes, I think it the official one is more than good enough.

        You mentioned compatibility. So far I have not found anything I really wanted that was not possible to set up in Nomad. Nomad does CNI and CSI, which is the same API k8s uses, so thinkgs working there will work for Nomad. Other things you would use with docker compose or k8s don’t work with Nomad, but you don’t need them (for example: portainer or metrics exporters) because Nomad has them natively already (this blog discusses that).

        As you can see I am pretty opinionated towards Nomad - I have been using it in my previous job in prod, and in my home-lab for a year now, and I am very happy with it. If you would like to read more I recommend this blog post. For Nomad on NixOS I wrote this one.

        For now my advice is: just try nomad yourself (as simple as running nomad agent -dev on your laptop), run the tutorial, and see if it was easy enough that you see yourself using it for the rest of your containers. If you need more help you are welcome to DM me :)

    • @jkrtn@lemmy.ml
      link
      fedilink
      English
      111 months ago

      Could you give a quick example of using NixOS configuration to launch a machine or deploying something remotely? I’m just starting to move beyond a single machine at home. I’d really like to get transition to infra as code.

      • @nico@r.dcotta.eu
        link
        fedilink
        English
        310 months ago

        I recommend starting with ZeroToNix’s docs and then moving on to nixos.wiki, but here is a minimal, working example that I could deploy to a hetzner VPS that only has nix and ssh installed:

        { config, pkgs, ... }: {
          # generated, this will set up partitions and bootloader in a separate file
          imports = [ ./hardware-configuration.nix ];
          zramSwap.enable = true;
          networking.hostName = "miki";
          # configures SSH daemon with a public key so we can ssh in again
          services.openssh.enable = true;
          users.users.root.openssh.authorizedKeys.keys = [ ''ssh-ed25519 AAAAC3NzaC1lNDI1NTE5AAAAIPJ7FM3wEuWoVuxRkWnh9PNEtG+HOcwcZIt6Qg/Y1jka'' ];
          # creates a timmy user with sudo access and wget installed
          users.users.timmy = {
            isNormalUser = true;
            extraGroups = [ "networkmanager" "wheel" "sudo" ];
            packages = with pkgs; [ wget ];
          };
          # open up SSH port
          networking.firewall.allowedTCPPorts = [ 22 ];
          # start nginx, assumes HTML is present at `/var/www`
          services.nginx = {
            enable = true;
            virtualHosts."default" = {
              forceSSL = true;            # Redirect HTTP clients to an HTTPs connection
              default = true;             # Always use this host, no matter the host name
              root = /var/www;        # Set the web root to ser
            };
          };
          system.stateVersion = "22.11";
        }
        

        This sets up a machine, configures the usual stuff like the ssh daemon, creates a user, and sets up an nginx server. To deploy it you would run nixos-rebuild --target-host root@10.0.0.1 switch. Other tools exist (I use colmena but the idea is the same). Note how easy it was to set up nginx! If I was setting Nomad up, I would just do services.nomad.enable = true.

        As you can see some things you will have to learn (the nix language, what the configs are…) but I think it is worth it.

        • @jkrtn@lemmy.ml
          link
          fedilink
          English
          210 months ago

          This is such a wealth of information, thank you! I’m really excited to try this out.

        • @nopersonalspace@lemmy.worldOP
          link
          fedilink
          English
          210 months ago

          This is awesome, ZeroToNix is exactly what I was looking for. I’ve been interested in trying NixOS for a while but I always found the documentation obtuse (extensive, which is great, but not super beginner friendly). I’ll give it a try!

          • @nico@r.dcotta.eu
            link
            fedilink
            English
            210 months ago

            Good luck on your Nix journey! Happy to help if you have questions.

            Of all the tech I use, I think Nix is the most ‘avant-garde’ in that it is super different from the usual methods (scripting, stateful things), but works very well once past the paradigm shift and the learning curve that entails.

    • @johntash@eviltoast.org
      link
      fedilink
      English
      011 months ago

      Hey, your stack is pretty similar to mine. One thing I recently started testing is Seaweedfs. I saw it listed in your repo too, how are you liking it so far? And do you use it on all of your nodes?

      • @nico@r.dcotta.eu
        link
        fedilink
        English
        211 months ago

        I struggled a bit to get it up and running well, but now I am happy with it. It’s not too hard to deploy (at least easier than the alternatives), it has CSI which for me was big, and it has erasure coding. The dev that maintains it (yes, the one dev) is very responsive.

        It has trade offs, so depending on your needs, I recommend it. Backing store for stateful workloads like postgres DBs? Absolutely not. Large S3 store (with an option for filesystem mount) for storing lots of files? Yes! In that regard it’s good for stuff like Lemmy’s pictrs or immich. I use it as my own Google drive. You can easily replicate in your own cluster, or back it up to an external cloud provider. You can mount it via FUSE on your personal machine too.

        Feel free to browse through my setup - if you have specific questions I am happy to answer them.

        • @johntash@eviltoast.org
          link
          fedilink
          English
          110 months ago

          Thanks! I’ll do some testing over the weekend and see how it goes.

          While I’d love to be able to use it for postgres, I figured that wouldn’t work out well so probably won’t try it any time soon. I do have several apps that use sqlite databases though, do you think those would have any issues? e.g. trilium, ntfy, ghost

          The main downside to most of the distributed/clustered storage that I’ve tried is they always seem to corrupt sqlite db files due to not supporting locking or some other posix feature. Reading through some older github issues, it looks like that is something the dev of seaweedfs fixed hopefully.

          • @nico@r.dcotta.eu
            link
            fedilink
            English
            210 months ago

            The problem with using seaweedfs to a back your DBs is more on the filesystem than the implementations of POSIX features. When you are writing to a file, and the connection to seaweedfs breaks (container restart, wifi, you name it), then you might end up with a half-written file. If you upload pictures, this is unlikely, but DBs are doing several writes per second usually. So it is more likely one of those gets interrupted. In my case, my grafana sqlite DB would get corrupted every other week.

            What I recommend is using DBs natively in your node’s filesystem, and backing them up to seaweedfs periodically instead. That way your DBs ‘work’ but you can get them running again, and the backup is replicated in the distributed filesystem.

            • @johntash@eviltoast.org
              link
              fedilink
              English
              110 months ago

              What I do right now is I have a rclone sidecar container that uploads files in a directory every few seconds, and I also have another init sidecar that runs before the main application and downloads those files (incl sqlite dbs) to the normal disk. This works okay but feels pretty clunky and can still result in stuff getting corrupted because I’m just backing up the db files and not using any sqlite commands to actually back up the db to another file that isn’t in-use first.

              How do you handle a job going from one nomad node to another? Or do you pin jobs like grafana to specific hosts?

              • @nico@r.dcotta.eu
                link
                fedilink
                English
                110 months ago

                Nomad has host volumes - so you can tell it to mount a folder from the machine on the container, and it will only schedule that container on machines that have that folder. So yes, effectively you pin the workload, thus introducing a SPOF - I do not love it but Grafana only supports sqlite and postgres, so making those HA would require failover setups which is a bit much for a homelab :')

                For backing up, you can use the sqlite command periodically (do cron job or Nomad periodic job) and then upload the backup to some external, safe storage (could be seaweedfs or S3!). For postgres you can use something like this.

            • @nopersonalspace@lemmy.worldOP
              link
              fedilink
              English
              110 months ago

              That’s an interesting issue. Do you think the problem would be the same for any CSI plugin? I’m thinking of using my NAS as the storage brains of the operation and hooking it up with NFS or something, but would that have issues with stateful stuff like DB’s too?

              • @nico@r.dcotta.eu
                link
                fedilink
                English
                210 months ago

                I have never used NFS, but I think it would fare much better than seaweedfs because it uses Fuse to implement CSI. So for NFS I am sure the protocol would consider half-assed writes

                would be the same for any CSI plugin

                No, it would depend on the CSI plugin and how it is implemented. Ceph for example I know it has several, and cloud providers offer CSI volumes for their block storage (AWS EBS, GCP PD), and they will all perform differently. See this comment from a seaweedfs issue:

                […] It is always better to run databases on host volumes if you can (or on volumes provided by AWS EBS or similar). But with Seaweedfs especially if you are running postgres with seaweedfs-csi volume be prepared for data corruption. Seaweefs-csi uses FUSE, if anything happens to seaweedfs-csi (Nomad client restart, docker restart, OOM) mount will be lost and data corruption will happen.

                Running on CEPH (since CEPH CSI using Kernel driver not FUSE) is acceptable if you fine with low TPS.

                I found it was easier to make recoverable, backed up, host volumes than to make DBs run on high availability filesystems like seaweedfs (I admit I have not tried Ceph - the deployment looked a bit complicated/overkill for a homelab).

                Postgres and sqlite are just not made for that environment. To run a high-availability DB, it is better to run a distributed DB made for that (think etcd, cassandra) than to run a non-distributed DB on top of a distributed filesystem.

                Good luck! :)