diff --git a/manifests/cluster-issuer.yaml b/manifests/cluster-issuer.yaml new file mode 100644 index 0000000..62e4eb5 --- /dev/null +++ b/manifests/cluster-issuer.yaml @@ -0,0 +1,14 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: adam.carr@hey.com + privateKeySecretRef: + name: letsencrypt-prod-account-key + solvers: + - http01: + ingress: + class: traefik diff --git a/manifests/monitoring/ddns.yaml b/manifests/monitoring/ddns.yaml new file mode 100644 index 0000000..b229ffa --- /dev/null +++ b/manifests/monitoring/ddns.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ddns-script + namespace: monitoring +data: + ddns.sh: | + #!/bin/sh + # Keeps the carr.pub apex A record pointing at this network's public IP. + RECORD_ID=84723206 + ZONE="carr.pub" + ACCOUNT=6300 + API="https://api.dnsimple.com/v2" + AUTH="Authorization: Bearer ${DNSIMPLE_TOKEN}" + + IP=$(curl -s -m 15 https://ifconfig.me) + case "$IP" in + *.*.*.*) : ;; + *) echo "no valid public ip: '$IP'"; exit 0 ;; + esac + + CUR=$(curl -s -m 15 -H "$AUTH" "$API/$ACCOUNT/zones/$ZONE/records/$RECORD_ID" \ + | sed -n 's/.*"content":"\([^"]*\)".*/\1/p') + if [ "$IP" = "$CUR" ]; then + echo "$(date -Is) up to date ($IP)" + exit 0 + fi + + RES=$(curl -s -m 15 -X PATCH -H "$AUTH" -H "Content-Type: application/json" \ + "$API/$ACCOUNT/zones/$ZONE/records/$RECORD_ID" \ + -d "{\"content\":\"$IP\"}") + echo "$(date -Is) updated carr.pub -> $IP" + case "$RES" in + *'"id"'*) exit 0 ;; + *) echo "API error: $RES"; exit 1 ;; + esac +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: ddns-carr-pub + namespace: monitoring +spec: + schedule: "*/5 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 1 + template: + spec: + restartPolicy: Never + containers: + - name: ddns + image: curlimages/curl:8.11.1 + command: ["/bin/sh", "/scripts/ddns.sh"] + env: + - name: DNSIMPLE_TOKEN + valueFrom: + secretKeyRef: {name: dnsimple-token, key: token} + resources: + requests: {cpu: 10m, memory: 16Mi} + limits: {memory: 32Mi} + volumeMounts: + - {name: scripts, mountPath: /scripts} + volumes: + - name: scripts + configMap: {name: ddns-script, defaultMode: 0744} diff --git a/manifests/web/hello-carr-pub.yaml b/manifests/web/hello-carr-pub.yaml new file mode 100644 index 0000000..f76fac3 --- /dev/null +++ b/manifests/web/hello-carr-pub.yaml @@ -0,0 +1,88 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: web +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: hello-index + namespace: web +data: + index.html: | + + + carr.pub + + + +
+

carr.pub

+

Served by the planck cluster — 20 Raspberry Pis that finally earn their rack space.

+
+ + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hello-web + namespace: web +spec: + replicas: 6 + selector: + matchLabels: {app: hello-web} + template: + metadata: + labels: {app: hello-web} + spec: + containers: + - name: web + image: nginx:1.29-alpine + ports: [{containerPort: 80}] + resources: + requests: {cpu: 10m, memory: 16Mi} + limits: {memory: 64Mi} + volumeMounts: + - {name: html, mountPath: /usr/share/nginx/html} + volumes: + - name: html + configMap: {name: hello-index} +--- +apiVersion: v1 +kind: Service +metadata: + name: hello-web + namespace: web +spec: + selector: {app: hello-web} + ports: + - port: 80 + targetPort: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: carr-pub + namespace: web + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + tls: + - hosts: [carr.pub] + secretName: carr-pub-tls + rules: + - host: carr.pub + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: hello-web + port: {number: 80}