Homelab

First, some explaination! Homelab is what I usually call my experimentation lab, where I try out new technologies, host some fun sites, and test things. I learned about virtual machines, networking, containerization, kubernetes, gitops, and more.

History

Minecraft servers are what got me into running and maintaining virtual machines, and they’ve stayed an important goal with any setup I have. Originally, I used Azure to learn about the cloud, using it to host Minecraft servers and a few other game servers as well.

Eventually I got a used HP ProLiant DL360P on Amazon. It has 64GB of Memory, a raid array with 8TB of storage, and 2 CPUs. This server was a really fun project to work on, since I got to use my novice woodworking skills to build a server cabinet. I used a set of computer fans and wired the power so that air will flow in from the bottom, and out the top.

A wooden server case

I started out just writing bash scripts, running modded and vanilla Minecraft servers, and then I started messing around with Docker. This was my first real interaction with Docker, and I ended up setting up a pretty nice Docker compose project. I used Traefik as a reverse proxy to make TLS and subdomains work nicely, since everything was hosted on the same IP. This way, I could use outline.reeve.dev for my notes, wiki.reeve.dev for Bookstack (an older notes site I used) and a few more.

Eventually, the heat it produced was too much for my room. I moved it out to the garage, but it was still using a lot of power. After this I considered moving to EKS since I had experience with that by this time at work, but that was going to be expensive to run. I ended up learning that Oracle has a cloud platform with quite a generous free tier, and since then I’ve set up a pretty nice cluster with Traefik (again).

Pods (57)
My current pods, successful and not

Oracle Cloud

In my cluster, I use the app of apps pattern. This means, once ArgoCD is set up, I have a root app that tracks itself and a others. The other apps includes ArgoCD, meaning it will manage and update itself.

Below you can see the root application:

---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
    name: root
    namespace: argocd
spec:
    project: default
    source:
        path: apps/
        repoURL: https://gitlab.com/reeve567-k8s-homelab/io-argocd-environment.git
        targetRevision: HEAD
    destination:
        server: https://kubernetes.default.svc
        namespace: argocd
    syncPolicy:
        automated:
            prune: true
            selfHeal: true

ArgoCD:

---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
    name: argo-cd
    namespace: argocd
spec:
    destination:
        namespace: argocd
        server: https://kubernetes.default.svc
    project: default
    source:
        path: charts/argo-cd
        repoURL: https://gitlab.com/reeve567-k8s-homelab/io-argocd-environment.git
        targetRevision: HEAD
    syncPolicy:
        automated:
            prune: true
            selfHeal: true
        syncOptions:
            - CreateNamespace=true

And the main ApplicationSet for everything else:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
    name: root-appset
    namespace: argocd
spec:
    goTemplate: true
    generators:
        - git:
            repoURL: https://gitlab.com/reeve567-k8s-homelab/io-argocd-environment.git
            revision: HEAD
            directories:
                - path: charts/*
                - path: charts/argo-cd
                  exclude: true
    template:
        metadata:
            name: "{{ .path.basename }}"
            namespace: argocd
            annotations:
                argocd-image-updater.argoproj.io/image-list: registry.gitlab.com/reeve567/zaylith,registry.gitlab.com/reeve567/strapi,registry.gitlab.com/reeve567/portfolio-hugo
                argocd-image-updater.argoproj.io/write-back-method: git
                argocd-image-updater.argoproj.io/git-branch: master
                argocd-image-updater.argoproj.io/registry.gitlab.com.pullsecret: argocd/gitlab-secret
        spec:
            project: default
            source:
                repoURL: https://gitlab.com/reeve567-k8s-homelab/io-argocd-environment.git
                targetRevision: HEAD
                path: "{{ .path.path }}"
            destination:
                server: https://kubernetes.default.svc
                namespace: "{{ .path.basename }}"
            syncPolicy:
                syncOptions:
                    - CreateNamespace=true
                automated:
                    prune: true
                    selfHeal: true

Project Structure

If you look closely here, you’ll notice that these are all pointing to the same repo, which is just how I’ve set it up. You could easily have each Application in it’s own repo, but that didn’t really make sense for my setup.

My folder structure is like this:

apps/
    kustomization.yaml
    resources/
        argo-cd.yaml
        root-set.yaml
        root.yaml
charts/
    .coder/
    .couchdb/
    .kubernetes-dashboard/
    .outline/
    ...
    argo-cd/
    cert-manager/
    cnpg/
    ...
    portfolio/

Where anything that starts with a . is ignored by the root-set. These are effectively my disabled applications. What’s also neat, is I’m using a mix of kustomization and Helm. For my Applications and ApplicationSets, kustomization, and then all the actual Applications are just Helm.

So for example my portfolio:

/charts
    ...
    portfolio/
        templates/
            deployment.yaml
            ingress.yaml
            service.yaml
        Chart.yaml
        values.yaml

Using Traefik and cert-manager

In order to avoid my cluster costing anything, I need to use the a specific kind of Loadbalancer on Oracle that doesn’t cost anything. This Loadbalancer basically just sends the traffic to my cluster rather than terminating it and acting as a middle man. This means that Oracle won’t deal with TLS, or directing traffic for me. That’s where these two come in. The initial setup isn’t too difficult, it just means making sure you have all the right settings for ports in Traefik, and making sure those services get tied to the right kind of Loadbalancer.

Continuing my portfolio project for example, after the initial setup, I just have to use the CustomResources that Traefik and cert-manager provide. And then I just have some settings for the ingress.yaml that I use in the values.yaml. Here’s a little show of that since it’s a little different.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
    name: {{ .Chart.Name }}-domain-tls
spec:
    dnsNames:
        - reeve.dev
    secretName: {{ .Chart.Name }}-domain-tls
    issuerRef:
        name: letsencrypt-http
        kind: ClusterIssuer
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
    name: {{ .Chart.Name }}-domain-https
spec:
    entryPoints:
        - websecure
    routes:
        - match: Host(`reeve.dev`)
    kind: Rule
    services:
        - name: {{ .Values.service.name }}
          port: {{ .Values.service.port }}
    tls:
        secretName: {{ .Chart.Name }}-domain-tls