Today, I would like to show you how my infrastructure is deployed and managed. Spoiler alert, I’m using Kubernetes to do that.
I know… What a twist!
Let’s get to it.
What services am I running exactly? Here is a list I’m running at the time of this writing:
- Athens Go Proxy
- The Lounge (IRC bouncer)
- Two CronJobs
- Fork Updater
- IDLE RPG online checker
- My WebSite (gergelybrautigam.com)
And it’s really simple to add more.
My cluster is deployed at DigitalOcean using two droplets each 1vCPU and 2GB RAM.
This isn’t going to be a production grade cluster. What I don’t include in here:
RBAC for various services and users
Since I’m the only user of my cluster I didn’t create any kind of access limits / users or such. You are free to create them though. The only role based auth that’s going on is for Prometheus.
I’m not using any third party things which require access to the API.
I’m the sole user of my things. I’m not really scaling my gitea up or down based on usage and as such, I did not define things like:
- Resource limits
- Nodes with certain capabilities
- Affinities and Taints – which means, everything can run anywhere
Most of by deploys and services don’t have these except for Athens.
Okay, with that out of the way, let’s get into the hows of things…
The most important thing that you need to do in order to use Kubernetes is Containerizing all the things.
Since Kubernetes is a container orchestration tool, without containers it’s pretty useless.
As a driver, I’m going to use Docker. Kubernetes can use anything that’s OCI compatible, which means if you would like to use runc as a container engine, you can do that. I’d like to keep my sanity though.
To show you what I mean… I have a cronjob which is running every month. It gathers all my forks on github and updates them with the latest from their parents. This a small ruby script located here: Fork Updater. How do we run this? It requires two things. First, a token. We pass that currently as an environment property. It could be in a file in a vault or a secret mounted in as a file it doesn’t matter. Currently, it’s an environment property. The second thing is more subtle.
I’m pushing the changes back into my remote forks. I’m doing this via SSH. So, we need a key in there too. How we’ll get that in there, I’ll talk about later when we are talking about how to set this cron job up. For now though, the container needs to look for a key in a specific location because we don’t want to over-mount
/root/.ssh/ and we also don’t want to use an initContainer to copy over an SSH key (because it’s mounted in as a symlink (but that’s a different issue all together)). Also, we certianly do NOT want to have a key in the container.
To achieve this, we simply set up a
config file for SSH like this one:
Host github.com IdentityFile /etc/secret/id_rsa
/etc/secret will be the destination of the ssh key we create.
And we also need to have a known_hosts file, otherwise git clone will complain. We also bake this into the container. Why? Why not generate that on the fly? Because I want it to fail in case there is something wrong or there is a MIMA going on etc.
All this translated into a Dockerfile looks like this:
# We are using alpine for a minimalistic image FROM alpine:latest RUN apk --no-cache add ca-certificates RUN apk update # Openssh is needed for the SSH command RUN apk --no-cache add ruby vim curl git build-base ruby-dev openssh openssh-client # Setup dependencies for the fork ruby file RUN gem install octokit logger multi_json json --no-ri --no-rdoc RUN mkdir /data WORKDIR /data # Setup some data about the committer RUN git config --global user.name "Fork Updater" RUN git config --global user.email "email@example.com" RUN mkdir -p /root/.ssh # Get the host config for github.com RUN ssh-keyscan github.com >> /root/.ssh/known_hosts # Setup the SSH config COPY ./config /root/.ssh COPY ./fork_updater.rb . CMD ["ruby", "/data/fork_updater.rb"]
That’s it. Now our updater is containerized and ready to be deployed as a cronjob on a kube cluster. Oh, and we also need to create the SSH key like this:
kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa
Before we Begin
There are two things we will need though to set up for our cluster before we even begin adding the first service. And that’s an ingress with a load-balancer and cert-manager.
Now, you have the option of installing cert-manager via helm, or via the provided kube config yaml file. I STRONGLY recommend using the config yaml file because the upgrading process with helm is a hell of a lot dirtier / failure prone than simply applying a new yaml file with a different version in it.
Either way, to install cert-manager follow this simple guide: Cert-manager Install Manual.
An Ingress is a must. This is the component which manages external access to the services which we will define. Like a proxy before your http server. This component will handle the hostname based routing between our services.
I’m using nginx ingress, although there are a couple of implementations out there.
To install nginx ingress, follow their guide here: Installing Nginx-Ingress.
From Easy to Complicated
Alright. Now that we have the prereqs out of the way, let’s get our hands dirty. I’ll start with the easiest of them all, my web site, and then will progress towards the more complicated ones, like Gitea and Athens, which require a lot more fiddling and have more moving parts.
It’s powered / served by an nginx instance running on port 9090 define by a very simple Dockerfile:
FROM golang:latest as builder RUN apt-get update && apt install -y git make vim hugo RUN mkdir -p /opt/website RUN git clone https://github.com/Skarlso/gergelybrautigam /opt/website WORKDIR /opt/website RUN make FROM nginx:latest RUN mkdir -p /var/www/html/gergelybrautigam WORKDIR /var/www/html/gergelybrautigam COPY --from=builder /opt/website/public . COPY 01_gergelybrautigam /etc/nginx/sites-available/ RUN mkdir -p /etc/nginx/sites-enabled/ RUN ln -s /etc/nginx/sites-available/01_gergelybrautigam /etc/nginx/sites-enabled/01_gergelybrautigam
Easy as goblin pie. Nginx has a command set like this
CMD ["nginx", "-g", "daemon off;"] and exposes port 80.
In order to deploy this in the cluster, I created a deployment as follows:
apiVersion: apps/v1 kind: Deployment metadata: name: gb-deployment namespace: gergely-brautigam labels: app: gergelybrautigam annotations: prometheus.io/scrape: 'true' prometheus.io/port: '9090' spec: replicas: 1 selector: matchLabels: app: gergelybrautigam template: metadata: labels: app: gergelybrautigam annotations: prometheus.io/scrape: 'true' prometheus.io/port: '9090' spec: containers: - name: gergelybrautigam image: skarlso/gergelybrautigam:v0.0.26 ports: - containerPort: 9090
The metadata section defines information about the deployment. It’s name is gb-deployment. The namespace in which this sits is called gergely-brautigam and it has some labels to it so Prometheus monitoring tool can discover the pod.
It’s running a single replica, has a bunch of more metadata and template settings, and finally the container spec which defines the image, and the exposed container port on which the application is running.
Now we need a service to expose this deployment.
The service is also simple. It looks like this:
kind: Service apiVersion: v1 metadata: namespace: gergely-brautigam name: gb-service spec: selector: app: gergelybrautigam ports: - port: 80 targetPort: 9090
Again, nothing fancy here, just a simple service exposing a port to a different port on the front-end side. This service will be picked up by our previously created routing facility.
Now that we have the service we need to expose it to the domain. I have the domain gergelybrautigam.com and I already pointed it at the LoadBalancer’s IP which was created by the nginx ingress controller.
We only want one LoadBalancer, but we have multiple hostnames. We can achieve that by creating an Ingress resource in the namespace our service is in like this:
apiVersion: extensions/v1beta1 kind: Ingress metadata: namespace: gergely-brautigam name: gergely-brautigam-ingress annotations: kubernetes.io/ingress.class: "nginx" certmanager.k8s.io/cluster-issuer: "letsencrypt-prod" certmanager.k8s.io/acme-challenge-type: http01 nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - gergelybrautigam.com secretName: gergelybrautigam-tls rules: - host: gergelybrautigam.com http: paths: - backend: serviceName: gb-service servicePort: 80
Remember, we already have the nginx ingress resource in the default namespace when we installed the controller previously. That is the main entrypoint. We are taking advantage of the rewrite-target annotation. That is our key to success
nginx.ingress.kubernetes.io/rewrite-target: /. The rest is basic routing. We’ll have something like this in the other namespace to.
And with that, our website is done and it should be working under HTTPS. Cert-manager should have picked it up and generated a certificate for it. Let’s check.
k get certs -n gergely-brautigam you should see something like this:
$ k get certs -n gergely-brautigam NAME READY SECRET AGE gergelybrautigam-tls True gergelybrautigam-tls 86d
If there is a problem, just describe the cert resource and look for the generated challenge and if it was successful or not. The challenge contains mostly good error messages.
That wasn’t too bad, right? Let’s do something a bit more complex this time. We are going to deploy The lounge irc bouncer.
It’s actually quite easy to do but can be daunting to look at at first.
Lucky for us, the bouncer already provides a container located here: The Lounge Docker Hub.
We just need two things. To expose the port 9000 and to give it something called a PersistentVolume. What’s a persistent volume? Well, look it up here: Kubernetes Persistent Volumes.
TL;DR: We need to preserve data. Containers are ephemeral in nature. Meaning if there is a problem we usually just delete the pod. Which means that all data will be lost. But we need persistence in this case because we’ll have user data and user information which we would like to persist between pods. That’s what a volume is for.
It will be mounted into the pod so we can point the bouncer to use that location for data management.
With that, this is how our PersistentVolumeClaim will look like:
apiVersion: v1 kind: PersistentVolumeClaim metadata: namespace: powerhouse name: do-storage-irc spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: do-block-storage
DigitalOcean provides a block storage implementation for this claim so we use that storage class
With that, this is how the deployment will look like:
apiVersion: apps/v1 kind: Deployment metadata: namespace: powerhouse name: irc-app labels: app: irc spec: replicas: 1 selector: matchLabels: app: irc template: metadata: labels: app: irc app.kubernetes.io/name: irc app.kubernetes.io/instance: irc spec: containers: - name: irc image: thelounge/thelounge:3.1.1 ports: - containerPort: 9000 name: irc-http volumeMounts: - mountPath: /var/opt/thelounge subPath: thelounge name: irc-data readOnly: false volumes: - name: irc-data persistentVolumeClaim: claimName: do-storage-irc
Short and sweet. The important bits are the labels, those are used by cert-manager and ingress to find the right deployment, and the
volumeMounts. We mount into the /var/opt/thelounge folder because that’s the main configuration location. The subPath is important for a correct mounting.
Alright, with the deployment in place, let’s take a look at the service.
kind: Service apiVersion: v1 metadata: namespace: powerhouse name: irc labels: app: irc app.kubernetes.io/name: irc app.kubernetes.io/instance: irc spec: selector: app: irc app.kubernetes.io/name: irc app.kubernetes.io/instance: irc ports: - name: http port: 9000 targetPort: irc-http
Again, very boring stuff. Boring is good. Boring is predictable. We expose port 9000 to the named targetPort called irc-http which we defined in the above deployment.
Now, I have a domain in which these things are running, let’s call it
powerhouse.com (because I’m tired of example.com). And I have multiple services in this namespace too, so I’ll call the namespace here, powerhouse and put this irc service in there. This also means that the ingress resource for this namespace will contain a couple more routings, because my powerhouse namespace will also contain my gitea and Athens proxy installation.
We can, however, take a peak at the ingress resource here and now… because I hate suspense.
apiVersion: extensions/v1beta1 kind: Ingress metadata: namespace: powerhouse name: powerhouse-ingress annotations: kubernetes.io/ingress.class: "nginx" certmanager.k8s.io/cluster-issuer: "letsencrypt-prod" certmanager.k8s.io/acme-challenge-type: http01 nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - irc.powerhouse.com secretName: irc-powerhouse-tls - hosts: - gitea.powerhouse.com secretName: gitea-powerhouse-tls - hosts: - athens.powerhouse.com secretName: athens-powerhouse-tls rules: - host: irc.powerhouse.com http: paths: - backend: serviceName: irc servicePort: 9000 path: / - host: gitea.powerhouse.com http: paths: - backend: serviceName: gitea servicePort: 3000 path: / - host: athens.powerhouse.com http: paths: - backend: serviceName: athens-service servicePort: 80 path: /
We can see that I have multiple paths pointing to three different subdomains with different ports. These ports will be routed to by nginx ingress. Meaning you DO NOT OPEN THESE ON YOUR LOADBALANCER. These will all be accessible from 443/HTTPS. Expect for gitea’s SSH port later on.
With these in place, cert-manager should pick it up and provide a certificate for it.
Side track – debugging
If there is a problem and we can’t reach TheLounge we need to debug. I use the following tool to access Kubernetes resources: K9S. It’s a neat CLI tool to look at kube resources in an interactive way and not having to type in a bunch of commands. Never the less, I will also paste those in here.
To look at the pods that should have been created, type:
k get pods -n powerhouse
Should see something like this:
NAME READY STATUS RESTARTS AGE athens-app-857749c59c-lmjnb 1/1 Running 0 6d3h gitea-app-6974fb995b-pn2vv 1/1 Running 0 9d gitea-db-59758fbcd9-4562c 1/1 Running 0 9d irc-app-5f87688f98-dqsvb 1/1 Running 0 9d
You can see that my other services are running fine. And there is IRC as well. Now if there would be any kind of problem we could access the Pods information be describing the pod with:
k describe pod/irc-app-5f87688f98-dqsvb -n powerhouse
Which will provide a bunch of information about the pod. But the pod could be absolutely fine, yet our service could be down. (We didn’t define any liveliness or readiness probs after all).
We can verify that by taking a peak in the container (also, check if our mounting was successful). Since this is just a container, exec works similar to docker exec.
$ k exec -it irc-app-5f87688f98-dqsvb -n powerhouse /bin/bash root@irc-app-5f87688f98-dqsvb:/#
Should give us a prompt. We can now look at logs, check out the configuration folder etc.
In k9s you would simply select the right namespace, select the pod, hit
d for describe or
s for shell. Done.
Now, we have IRC running. Let’s try deploying Gitea. This takes a tiny bit more fiddling though.
Gitea requires the following things to be present:
- The gitea app configuration file (this can be done via environment properties though)
- A DB
- A PersistentVolume
- SSH Port for SSH based git clones instead of simple https
We shall begin with the simplest of them, the DB. At this point we could go with the DigitalOcean managed Postgres installation, but I didn’t want to put that on the bill as well. So I choose to simply put my DB into a container and deploy it within the cluster.
This is actually quite simple. The DB will be a separate deployment / application in the same namespace as the Gitea app. It will also contain a network policy, since the DB doesn’t need internet access and the internet shouldn’t be able to access it.
In fact the only thing that should be able to access the DB is the Gitea application itself which we will be able to restrict via the usage of… Labels!
But first, take a look at the deployment of a Postgres 11 pod:
apiVersion: apps/v1 kind: Deployment metadata: namespace: powerhouse name: gitea-db spec: replicas: 1 selector: matchLabels: app: gitea-db template: metadata: name: gitea-db labels: app: gitea-db spec: containers: - name: postgres image: postgres:11 env: - name: POSTGRES_USER value: gitea - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: gitea-db-password key: password - name: POSTGRES_DB value: gitea volumeMounts: - mountPath: /var/lib/postgresql/data subPath: data # important so it gets mounted properly name: git-db-data volumes: - name: git-db-data persistentVolumeClaim: claimName: do-storage-gitea-db
Okay, there are a lot of things going on here, but the three things we need to note are the following:
labels: app: gitea-db
Our network policy will look for this label to identify the pods which fell under its rule.
- name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: gitea-db-password key: password
The database password will come from a secret.
volumeMounts: - mountPath: /var/lib/postgresql/data subPath: data # important so it gets mounted properly name: git-db-data volumes: - name: git-db-data persistentVolumeClaim: claimName: do-storage-gitea-db
We also need a persistent volume otherwise the data will be lost on each pod restart.
--- apiVersion: v1 kind: PersistentVolumeClaim metadata: namespace: powerhouse name: do-storage-gitea-db spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi storageClassName: do-block-storage
We also need a Service so Gitea will be able to reach it. This isn’t public though so a NodePort is enough with no clusterIP.
kind: Service apiVersion: v1 metadata: namespace: powerhouse name: gitea-db-service spec: ports: - port: 5432 selector: app: gitea-db clusterIP: None
In order to reach this DB we can use a URL like this now from the Gitea app:
We want the Gitea app to be able to reach it. Which means in-out to the Gitea app and nothing else.
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: gitea-db-network-policy namespace: powehouse spec: podSelector: matchLabels: app: gitea-db policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: gitea ports: - protocol: TCP port: 5432 egress: - to: - podSelector: matchLabels: app: gitea ports: - protocol: TCP port: 5432
We can test this now by exec-ing into the Pod of the DB deployment and trying to ping google.com for example. It should be denied. Yet later, when we deploy our Gitea app, that should be able to talk to the DB instance.
Finally, we have a Secret which contains our db password base64 encoded.
apiVersion: v1 kind: Secret metadata: namespace: cronohub name: gitea-db-password type: Opaque data: password: Z2l0ZWE=
That says password123. To get it, you can run something like
echo -n "password123" | base64.
Gitea App ini
Huh, with that done, we can go on with the application ini file. This can be configured via environment properties but once you get over a dozen configuration entries, it’s just easier to use an app.ini. My app ini is large, so I won’t post it here. I could mount it in as a file, but that proved to be difficult or not work at all properly because Gitea is running under a different user than root. Also, once the mount happened the fact the gitea was trying to write into it caused problems. Mounting as a different user didn’t work out either, so I’m using an InitContainer to do the job. They are there for that reason. And it was actually a hell of a lot simpler than doing file mounting.
The app.ini is defined as a ConfigMap like this:
kubectl create configmap gitea-app-ini --from-file=app.ini --namespace powerhouse
This was done from the folder where my app.ini was residing.
Now comes the big gun. The Gitea deployment file. This is how it looks like in all its glory:
apiVersion: apps/v1 kind: Deployment metadata: namespace: cronohub name: gitea-app labels: app: gitea spec: replicas: 1 selector: matchLabels: app: gitea template: metadata: labels: app: gitea app.kubernetes.io/name: gitea app.kubernetes.io/instance: gitea spec: initContainers: - name: init-disk image: busybox:latest command: - /bin/chown - 1000:1000 # we set the gid and uid of the user for gitea. - /data volumeMounts: - name: git-data mountPath: "/data" readOnly: false - name: init-app-ini image: busybox:latest command: ['sh', '-c', 'mkdir -p /data/gitea/conf/; cp /data/app.ini /data/gitea/conf'] volumeMounts: - name: git-data mountPath: "/data" readOnly: false - name: gitea-app-ini-conf mountPath: /data/app.ini subPath: app.ini readOnly: false containers: - name: gitea image: gitea/gitea:1.9.2 env: - name: DB_PASSWD valueFrom: secretKeyRef: name: gitea-db-password key: password - name: DB_TYPE valueFrom: configMapKeyRef: name: gitea-config-map key: DB_TYPE - name: DB_HOST valueFrom: configMapKeyRef: name: gitea-config-map key: DB_HOST - name: DB_NAME valueFrom: configMapKeyRef: name: gitea-config-map key: DB_NAME - name: DB_USER valueFrom: configMapKeyRef: name: gitea-config-map key: DB_USER ports: - containerPort: 3000 name: gitea-http - containerPort: 22 name: gitea-ssh volumeMounts: - mountPath: /data name: git-data readOnly: false volumes: - name: git-data persistentVolumeClaim: claimName: do-storage-gitea - name: gitea-app-ini-conf configMap: name: gitea-app-ini
The important bit is the initContainer section. What’s happening here? We mount the app.ini file to the init container under /data. The awesome part about the initContainer is that the real container will have access to the file system the init container created.
So we take that file, fix the permissions on it and copy it to the right location under
/data/gitea/conf for the Gitea app to work with.
And the configMap is simple:
apiVersion: v1 kind: ConfigMap metadata: namespace: powerhouse name: gitea-config-map data: APP_COLOR: blue APP_MOD: prod DB_TYPE: postgres DB_HOST: "gitea-db-service.cronohub.svc.cluster.local:5432" DB_NAME: gitea DB_USER: gitea
Normally, Ingress only allows HTTP based traffic control. But what would an ingress be without also regular TCP based routing?
But it’s not trivial. Nginx Ingress provides a documentation for this here: Exposing TCP and UDP services. What does that mean in practice?
You see we are also exposing port 22 on the container:
- containerPort: 22 name: gitea-ssh
I choose to differentiate my SSH port for Gitea from port 22 because that’s just cumbersome to get done right. Gitea provides an explanation on how to do port 22 forwarding in a docker container with a custom git command which forwards commands to the container itself. This is all just plain too much to worry about.
I have this in the app.ini:
SSH_PORT = <port of my choosing>
And then this in the Service definition:
kind: Service apiVersion: v1 metadata: namespace: powerhouse name: gitea labels: app: gitea app.kubernetes.io/name: gitea app.kubernetes.io/instance: gitea spec: selector: app: gitea app.kubernetes.io/name: gitea app.kubernetes.io/instance: gitea ports: - name: http port: 3000 targetPort: gitea-http - name: ssh port: <port of my choosing> targetPort: gitea-ssh protocol: TCP
And then, we edit the nginx-controller deployment like this:
kubectl edit deployment.apps/nginx-ingress-controller
And add this line
--tcp-services-configmap=cronohub/gitea-ssh-service to the container’s args field:
containers: - args: - /nginx-ingress-controller - --default-backend-service=default/nginx-ingress-default-backend - --election-id=ingress-controller-leader - --ingress-class=nginx - --configmap=default/nginx-ingress-controller - --tcp-services-configmap=powerhouse/gitea-ssh-service
One more thing is that we have to open that port on the load balancer as well to get traffic to it. To that end, edit the nginx ingress service as well:
kubectl edit services/nginx-ingress-controller
And add the exposed port:
- name: ssh port: <port of my choosing> protocol: TCP targetPort: <port of my choosing>
There will probably be a nodePort section in there on the other ports. Ignore that for your change.
Also, if you are doing the nginx installation by hand, just add this or save the yaml file from those deployments like this:
kubectl get service/nginx-ingress-controller -o yaml > nginx-ingress-controller.yaml
So you can deploy / modify it later on.
And with that, visit
gitea.powerhouse.com and it should work including HTTPS and SSH!
You can now clone repositories like this:
git clone ssh://firstname.lastname@example.org:1234/user/awesome_project.git after you created your user.
User creation is done by using the gitea admin CLI tool described here: Gitea Documentation.
It is important to note that we don’t use
latest anywhere. It’s just not good if you are trying to update a service later on. We could set ImagePolicy to AlwaysPull but that’s just not a good thing to do if you have a 2 gig image. Always use version and policy
imagePullPolicy: IfNotPresent to save yourself some bandwidth.
Let’s create a last resource, then we’ll call it a day.
The idle RPG is a cool little game that you play by… not playing. At all. If you play, you get penalties. Here is a cool resource to start: Idle RPG. It looks something like this:
21:56 <@IdleBot> Verily I say unto thee, the Heavens have burst forth, and the blessed hand of God carried ganome 0 days, 03:52:11 toward level 45. 21:56 <@IdleBot> ganome reaches next level in 0 days, 01:49:16. 22:02 <@IdleBot> himuraken, the level 77 Mage Of BitFlips, is now online from nickname himuraken. Next level in 11 days, 10:35:53. 22:14 <@IdleBot> Nechayev, Sundance, and simple [2011/2347] have team battled HeavyPodda, Sixbierehomme, and L [1417/2717] and won! 0 days, 06:14:54 is removed from their clocks. 22:18 <@IdleBot> canton7 saw an episode of Ally McBeal. This terrible calamity has slowed them 0 days, 05:10:53 from level 85. 22:18 <@IdleBot> canton7 reaches next level in 2 days, 00:21:36. 22:26 <@IdleBot> Tor [4/1142] has challenged Brainiac [232/817] in combat and lost! 3 days, 23:06:05 is added to Tor's clock. 22:26 <@IdleBot> Tor reaches next level in 39 days, 23:39:35.
It could happen that by some misfortune the bouncer gets restarted and it doesn’t log you back in. Or you simply just lose connection and you don’t re-connect. That is unacceptable because the point is to be present. Otherwise you don’t play. So you need an early warning in case you are offline. Luckily, IdleRPG provides an XML based endpoint to get which contains your status.
From there, I’m using mailgun with a registered domain to send me an email in case my status is offline. For that, here is a small Go program IdleRPG Checker Go Code.
To put that into a Docker container, here is a Dockerfile:
FROM golang:latest as build RUN go get -v github.com/sirupsen/logrus && \ go get -v github.com/mailgun/mailgun-go COPY ./main.go /code/ WORKDIR /code RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /idlerpg-checker . FROM alpine:latest RUN apk --no-cache add ca-certificates COPY --from=build /idlerpg-checker /idlerpg-checker RUN echo "v0.0.1" >> .version ENTRYPOINT ["/idlerpg-checker"]
And the corresponding cronjob resource definition:
apiVersion: batch/v1beta1 kind: CronJob metadata: name: idle-checker namespace: idle-checker spec: schedule: "*/20 * * * *" jobTemplate: spec: template: spec: containers: - name: idle-checker image: skarlso/idle-checker imagePullPolicy: IfNotPresent env: - name: MG_API_KEY valueFrom: secretKeyRef: name: idle-rpg-secret key: MG_API_KEY - name: MG_DOMAIN valueFrom: secretKeyRef: name: idle-rpg-secret key: MG_DOMAIN args: ['-username', 'username', '-email', 'email@example.com'] restartPolicy: OnFailure
Aaaand, the secret for the API key:
apiVersion: v1 kind: Secret metadata: namespace: idle-checker name: idle-rpg-secret type: Opaque data: MG_API_KEY: asdf= MG_DOMAIN: asdf==
Done. Huh. This will run every 20 minutes and check if the user with username
username is online. If not, it will send an email to the given email address. Your levels are safe.
Phew. This has been quite the ride. The post is now really long, so I will split the rest out into a Part 2. That is, Athens and Monitoring.
Thank you for reading this far!