Published: Apr 23, 2024 by Isaac Johnson
YAL, or “Yet Another LandingPage” is a simple but functional landing page we can host and configure with some simple JSON. We’ll check that out in Kubernetes by making a quick manifest and applying it.
Trillium is a rich personal note organizer that like YAL can be hosted with Docker or Kubernetes. We’ll set this up and actually try using it for a while to see how it is to use on a daily basis.
Let’s dig in.
YAL
YAL can be downloaded and run locally, or in Docker.
Let’s do it all in Kubernetes instead.
I can create a manifest YAML
builder@LuiGi:~/Workspaces/yal$ cat manifest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: yal-deployment
spec:
replicas: 1
selector:
matchLabels:
app: yal
template:
metadata:
labels:
app: yal
spec:
containers:
- name: yal
image: timoreymann/yal:latest
ports:
- containerPort: 2024
env:
- name: YAL_PORT
value: "2024"
- name: YAL_PAGE_TITLE
value: "freshBrewed"
- name: YAL_CONFIG_FOLDER
value: "/app/config"
- name: YAL_IMAGES_FOLDER
value: "/app/images"
- name: YAL_ICONS_FOLDER
value: "/app/icons"
volumeMounts:
- name: config-volume
mountPath: "/app/config"
- name: icons-volume
mountPath: "/app/icons"
- name: images-volume
mountPath: "/app/images"
# Add other environment variables as needed
volumes:
- name: config-volume
persistentVolumeClaim:
claimName: config-pvc
- name: icons-volume
persistentVolumeClaim:
claimName: icons-pvc
- name: images-volume
persistentVolumeClaim:
claimName: images-pvc
---
apiVersion: v1
kind: Service
metadata:
name: yal-service
spec:
selector:
app: yal
ports:
- protocol: TCP
port: 80
targetPort: 2024
type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: config-pvc
spec:
storageClassName: local-path
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: icons-pvc
spec:
storageClassName: local-path
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: images-pvc
spec:
storageClassName: local-path
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
Then apply
builder@LuiGi:~/Workspaces/yal$ kubectl create ns yal
namespace/yal created
builder@LuiGi:~/Workspaces/yal$ kubectl apply -f ./manifest.yaml -n yal
deployment.apps/yal-deployment created
service/yal-service created
persistentvolumeclaim/config-pvc created
persistentvolumeclaim/icons-pvc created
persistentvolumeclaim/images-pvc created
That crashes because the config.json needs to be in config first.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 44s default-scheduler 0/3 nodes are available: persistentvolumeclaim "config-pvc" not found. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling..
Normal Scheduled 42s default-scheduler Successfully assigned yal/yal-deployment-6764c78884-j8t9q to builder-hp-elitebook-745-g5
Normal Pulled 40s kubelet Successfully pulled image "timoreymann/yal:latest" in 2.216625871s (2.216664703s including waiting)
Normal Pulled 39s kubelet Successfully pulled image "timoreymann/yal:latest" in 465.872652ms (465.911414ms including waiting)
Normal Pulling 22s (x3 over 42s) kubelet Pulling image "timoreymann/yal:latest"
Normal Pulled 22s kubelet Successfully pulled image "timoreymann/yal:latest" in 450.162235ms (450.191638ms including waiting)
Normal Created 22s (x3 over 40s) kubelet Created container yal
Normal Started 22s (x3 over 40s) kubelet Started container yal
Warning BackOff 10s (x4 over 38s) kubelet Back-off restarting failed container yal in pod yal-deployment-6764c78884-j8t9q_yal(4f145bda-166c-4f8d-a401-8677d521c62c)
builder@LuiGi:~/Workspaces/yal$ kubectl logs yal-deployment-6764c78884-j8t9q -n yal
yal 1.5.0 (24-04-05_15:06:17) by Timo Reymann
---------------------------------------------
Build information
GitSha 693c69e
Version 1.5.0
BuildTime 24-04-05_15:06:17
Go-Version go1.22.2
OS/Arch linux/amd64
---------------------------------------------
2024/04/07 22:37:59 Failed to render template: open /app/config/items.json: no such file or directory
Perhaps it is better ot change from a config-pvc to a mounted configmap volume
$ cat manifest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: yal-deployment
spec:
replicas: 1
selector:
matchLabels:
app: yal
template:
metadata:
labels:
app: yal
spec:
containers:
- name: yal
image: timoreymann/yal:latest
ports:
- containerPort: 2024
env:
- name: YAL_PORT
value: "2024"
- name: YAL_PAGE_TITLE
value: "freshBrewed"
- name: YAL_CONFIG_FOLDER
value: "/app/config"
- name: YAL_IMAGES_FOLDER
value: "/app/images"
- name: YAL_ICONS_FOLDER
value: "/app/icons"
- name: YAL_LOGO
value: "http://freshbrewed-test.s3-website-us-east-1.amazonaws.com/img/happy_me_emoji.png"
- name: YAL_MASCOT
value: "http://freshbrewed-test.s3-website-us-east-1.amazonaws.com/img/happy_me_emoji.png"
- name: YAL_BACKGROUND
value: "http://freshbrewed-test.s3-website-us-east-1.amazonaws.com/content/images/2019/02/image-15.png"
- name: YAL_FAVICON
value: "https://freshbrewed.science/favicon.png"
volumeMounts:
- name: icons-volume
mountPath: "/app/icons"
- name: images-volume
mountPath: "/app/images"
- name: config-volume
mountPath: "/app/config"
# Add other environment variables as needed
volumes:
- name: icons-volume
persistentVolumeClaim:
claimName: icons-pvc
- name: images-volume
persistentVolumeClaim:
claimName: images-pvc
- name: config-volume
projected:
sources:
- configMap:
name: yaml-configjson-configmap
items:
- key: items.json
path: items.json
- configMap:
name: yaml-searchenginejson-configmap
items:
- key: searchEngines.json
path: searchEngines.json
---
apiVersion: v1
kind: Service
metadata:
name: yal-service
spec:
selector:
app: yal
ports:
- protocol: TCP
port: 80
targetPort: 2024
type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: config-pvc
spec:
storageClassName: managed-nfs-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: icons-pvc
spec:
storageClassName: managed-nfs-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: images-pvc
spec:
storageClassName: managed-nfs-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: yaml-configjson-configmap
data:
items.json: |
[
{
"title": "My Section Title",
"entries": [
{
"text": "Freshbrewed Blog",
"link": "https://freshbrewed.science",
"description": "The freshbrewed blog",
"icon": "https://microsoft.github.io/garnet/img/garnet-logo-diamond.png"
}
]
}
]
---
apiVersion: v1
kind: ConfigMap
metadata:
name: yaml-searchenginejson-configmap
data:
searchEngines.json: |
[
{
"title": "MySearch",
"urlPrefix": "https://my.search?text=whatever"
}
]
Then I can apply
builder@LuiGi:~/Workspaces/yal$ kubectl apply -f ./manifest.yaml -n yal
deployment.apps/yal-deployment configured
service/yal-service unchanged
persistentvolumeclaim/config-pvc unchanged
persistentvolumeclaim/icons-pvc unchanged
persistentvolumeclaim/images-pvc unchanged
configmap/yaml-configjson-configmap created
configmap/yaml-searchenginejson-configmap created
Now I can port-forward and see a very ugly page as I chose some random images with which to test
$ kubectl port-forward svc/yal-service -n yal 8888:80
Forwarding from 127.0.0.1:8888 -> 2024
Forwarding from [::1]:8888 -> 2024
Handling connection for 8888
Handling connection for 8888
That is really bad.
I made a white pixel with https://onlinepngtools.com/generate-1x1-png and copied it to https://freshbrewed.science/whitepixel.png.
I updated the manifest to use that as a background
- name: YAL_LOGO
value: "https://freshbrewed.science/favicon.png"
- name: YAL_MASCOT
value: "http://freshbrewed-test.s3-website-us-east-1.amazonaws.com/img/happy_me_emoji.png"
- name: YAL_BACKGROUND
value: "https://freshbrewed.science/whitepixel.png"
- name: YAL_FAVICON
value: "https://freshbrewed.science/favicon.png"
and updated
$ kubectl apply -f manifest.yaml -n yal
deployment.apps/yal-deployment configured
service/yal-service unchanged
persistentvolumeclaim/config-pvc unchanged
persistentvolumeclaim/icons-pvc unchanged
persistentvolumeclaim/images-pvc unchanged
configmap/yaml-configjson-configmap unchanged
configmap/yaml-searchenginejson-configmap unchanged
That’s almost worse
I think the more I mess with this, the uglier I make it.
I’ll just smile and say “thanks, but no” on this one.
Trillium
Trillium is a note taking app we can host in Docker or Kubernetes.
Since the Docker approach is pretty easy, let’s instead pivot to doing this all in Kubernetes.
We can create a manifest with a PVC:
$ cat trillium.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: trilium-deployment
spec:
replicas: 1
selector:
matchLabels:
app: trilium
template:
metadata:
labels:
app: trilium
spec:
containers:
- name: trilium-container
image: zadam/trilium
ports:
- containerPort: 8080
env:
- name: TRILIUM_DATA_DIR
value: "/home/node/trilium-data"
volumes:
- name: trilium-data-volume
persistentVolumeClaim:
claimName: trilium-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: trilium-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-path
---
apiVersion: v1
kind: Service
metadata:
name: trilium-service
spec:
selector:
app: trilium
ports:
- protocol: TCP
port: 8080
targetPort: 8080
Then create a namespace and apply
$ kubectl create ns trillium
namespace/trillium created
$ kubectl apply -f ./trillium.yaml -n trillium
deployment.apps/trilium-deployment created
persistentvolumeclaim/trilium-pvc created
service/trilium-service created
We can see it launched
$ kubectl get pods -n trillium
NAME READY STATUS RESTARTS AGE
trilium-deployment-59778d4dbb-pf5l7 1/1 Running 0 73s
$ kubectl get svc -n trillium
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
trilium-service ClusterIP 10.43.244.185 <none> 8080/TCP 78s
I can now hit the service
$ kubectl port-forward svc/trilium-service -n trillium 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
We are presented with a setup page
Then I just set a password
And then I can use it
It’s suprisingly complete
We can play around with it
I now want to setup an ingress
$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n trillium
{
"ARecords": [
{
"ipv4Address": "75.73.224.240"
}
],
"TTL": 3600,
"etag": "ce9e21ac-f620-4209-bcaa-1a762c4c4eac",
"fqdn": "trillium.tpk.pw.",
"id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/trillium",
"name": "trillium",
"provisioningState": "Succeeded",
"resourceGroup": "idjdnsrg",
"targetResource": {},
"trafficManagementProfile": {},
"type": "Microsoft.Network/dnszones/A"
}
I then create an Ingress yaml
$ cat trillium.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: azuredns-tpkpw
ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.org/websocket-services: trilium-service
name: trillium-ingress
namespace: trillium
spec:
rules:
- host: trillium.tpk.pw
http:
paths:
- backend:
service:
name: trilium-service
port:
number: 8080
path: /
pathType: Prefix
tls:
- hosts:
- trillium.tpk.pw
secretName: trillium-tls
$ kubectl apply -f ./trillium.yaml -n trillium
ingress.networking.k8s.io/trillium-ingress created
I can now login with a proper URL
One of the killer features (to me) is exporting out as markdown.
This means we have portability with anything that does markdown. I often write my daily status updates in Google Docs, but its a pain to maintain formatting and export. However, I’ve often avoiding moving to other tools because I didn’t want lock in. This would allow me to dump reports for managers/end of month backup.
Real Usage
I wanted to try living with Trillium for a few days.
To do so meant creating a new project
We start by making a plain old note
When I click the +
sign, it will create child notes turning the first into a folder icon
I couldn’t figure out how to add a Journal easily so I instead cloned from the Demo area
and saved it to my new folder
Then I just deleted the demo dates out (which were a few years old at this point and easy to identify)
I typically break a day down to Meetings, Actions, Pings and Plans. Here I’ll try just adding Plans and Actions
What I liked is that I could look at an overall Day-view that covered both my plans and actions
Summary
We checked out two projects today. The first was YAL. It’s not bad, per se, but it really didn’t fit my needs. I likely won’t be exposing that endpoint.
The second was Trillium which is a Personal Notes Organizer. It’s surprisingly rich for something with no commercial backend. Though they encourage one to donate to Ukraine if one likes the software.