Hugo on Kubernetes with Cloudflare Argo Tunnel

February 12, 2019   

Setting up an overengineered self-hosted blog using Hugo on Kubernetes, routing traffic via the Cloudflare Argo Tunnel reverse proxy service. You know, just for fun.

Github Repo

The code for this set of instructions can be found at the following URL: [https://github.com/rshaltry/k8s-hugo]

Build Docker Image

Jumping into the process, we first need to containerize the web server, using the Nginx official Docker image. Start by cloning the k8s-hugo Github repository mentioned above:

git clone [email protected]:rshaltry/k8s-hugo.git

Install Hugo into the source directory:

cd k8s-hugo
hugo new site source

Follow the instructions in the Hugo Quick Start Guide to complete a quick test proof-of-concept Hugo instance.

NOTE: If you already have a Hugo site created, you should copy (or symlink) it to k8s-hugo/source.

Create the Dockerfile:

FROM ubuntu:latest as STAGEONE

# install hugo
ENV HUGO_VERSION=0.54.0
ADD https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz /tmp/
RUN tar -xf /tmp/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz -C /usr/local/bin/

# install syntax highlighting
RUN apt-get update
RUN apt-get install -y python3-pygments

# build site
COPY source /source
RUN hugo --source=/source/ --destination=/public/

FROM nginx:stable-alpine
COPY --from=STAGEONE /public/ /usr/share/nginx/html/
EXPOSE 80

Build the Docker image and push to Docker Hub:

docker build . --tag DOCKERHUBUSER/hugo-blog:0.0.1
docker login
docker push DOCKERHUBUSER/hugo-blog:0.0.1

Where DOCKERHUBUSER is your Docker Hub username.

Deploy Tiller

Create Tiller service account:

$ kubectl create serviceaccount tiller --namespace kube-system

Add Cluster Role Binding:

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: tiller-clusterrolebinding
subjects:
- kind: ServiceAccount
  name: tiller
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: ""
$ kubectl apply -f cluster-role-binding.yml

Deploy Tiller:

helm init --service-account tiller --upgrade

Deploy Cloudflare Ingress Controller

Please note that more detailed instructions can be found at https://developers.cloudflare.com/argo-tunnel/reference/kubernetes/

Add Argo Tunnel secret:

$ kubectl create secret generic mydomain.com --from-file="$HOME/.cloudflared/cert.pem" -n hugo

Deploy Cloudflare Argo Tunnel Kubernetes Ingress Controller:

helm install --name hugo --namespace default \
    --set rbac.create=true \
    --set controller.ingressClass=argo-tunnel \
    --set controller.logLevel=6 \
    cloudflare/argo-tunnel

Deploy Hugo

Create Namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: hugo

Deploy the Hugo app, with 3-pod cluster:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hugo
  namespace: hugo
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: hugo
    spec:
      containers:
      - name: website
        image: DOCKERHUBUSER/hugo-blog:0.0.1
        imagePullPolicy: Always
        ports:
        - containerPort: 80

NOTE: In the manifest above, DOCKERHUBUSER is your Dockerhub username, and the account where the hugo-blog Docker image was pushed.

Add a LoadBalancer Service to distribute requests across 3 pods:

kind: Service
apiVersion: v1
metadata:
  name: hugo
  namespace: hugo
spec:
  selector:
    app: hugo
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer

Add the Ingress with Argo Tunnel:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: argo-tunnel
  labels:
    ingress: argo-tunnel
  name: hugo-argo-tunnel
  namespace: hugo
spec:
  tls:
  - hosts:
    - blog.mydomain.com
    secretName: mydomain.com
  rules:
  - host: blog.mydomain.com
    http:
      paths:
      - backend:
          serviceName: hugo
          servicePort: 80

After a minute or two, you can access your site via the Argo Tunnel connection:

https://blog.mydomain.com