Home Replace wordpress with jekyll on RKE2 K8s
Post
Cancel

Replace wordpress with jekyll on RKE2 K8s

I have recently been redeploying all my services running on a mixture of docker and LXD with Kubernetes based deployments. This post will take you through replacing your wordpress install with Jekyll. Compared to wordpress, my blog is now much simpler to maintain, easier to backup, faster and looks better.

You should end up with:

  • Your blog will be stored within git
  • Commit new posts to the git project
  • Within the kubernetes deployment
    • A container that checks for new commits in the git project and pulls them down
    • A container running jekyll that checks for changes and rebuilds the site
    • A container running nginx that serves the built directory

Install Jekyll

The first step is to install Jekyll; the official documentation is pretty good. This can be found here for Ubuntu.

Basically:

1
sudo apt-get install ruby-full build-essential zlib1g-dev
1
2
3
4
echo '# Install Ruby Gems to ~/gems' >> ~/.bashrc
echo 'export GEM_HOME="$HOME/gems"' >> ~/.bashrc
echo 'export PATH="$HOME/gems/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
1
gem install jekyll bundler

Chirpy Theme

I really wanted to use the chirpy theme github. The documentation is focused on using github actions with the developers other project chirpy-starter. I wanted to deploy my blog locally on my own Kubernetes cluster which wasn’t as obvious.

Firstly fork and clone the main github project.

Note: I am mirring it to my local git instance so my URL’s will be different to yours.

1
git clone https://hamgit.monotok.org/hammer/blog-chirpy-jekyll.git -b master --single-branch

I would create a new branch, this is not necessary but it makes it a bit easier to keep in sync with upstream.

1
git checkout -b myblog

Now run the initialise script to remove some directories and files, ready for your own content.

1
bash tools/init.sh

Now we want to update some settings in the _config.ym. It should be obvious which things you need to change. For example the URL. After you make any changes you want, you can run the blog locally.

1
bash tools/run.sh

This will build the site which generates the static content and copies it to the _site directory, this is then served on port 4000. While the script is running it will detect any changes to the files and rebuild for you. This is useful while writing new content.

Wordpress Exporter Plugin

Now we need to install the additional Gems for the official wordpress exporter (Official Docs).

The mysql2 gem needs client package to be installed.

1
sudo apt install mariadb-client

Open the Gemfile and add at the bottom.

1
2
3
4
5
6
#For import from wordpress
gem "safe_yaml"
gem "sequel"
gem "unidecode"
gem "jekyll-import"
gem "mysql2"

To actually install the official wordpress exporter plugin.

1
bundle install

This plugin connects to the database and creates the posts as html pages. The images are not downloaded, however they are saved as links, referencing the original wordpress blog.

So to actually generate the posts, run the import command. You might want to clear your cache before running the below command, see ‘Exporter Issues’ below.

1
jekyll import wordpress --dbname <database name> --host <host ip or dns name> --port <port> --user <username> --password <password> --comments --categories --tags

Exporter Issues

Oembed Cache

The export ran successfully except I got quite a few oembed_cache pages generated in the _posts folder. I cleared the cache from wordpress via the DB. See clearing oembed_cache. I then ran the above command again which fixed the problem.

Images not downloaded

The exporter plugin does not download the images but the images will still display when you run the blog locally as they are referenced back to the original wordpress site.

Unfortunately it was a manual process to fix, if I had more posts then I would have automated it. Basically download the images into a folder within this project, I chose assets/img/posts/date/. Then replace the references to the original wordpress site with the local image; for example in html:

1
<p><a href="/assets/img/posts/2017/Screenshot_C_Pointers.png"><img class="size-large wp-image-612" src="/assets/img/posts/2017/Screenshot_C_Pointers.png" alt="" width="1024" height="751"></a></p>

Now you should commit the changes in git. Next we need to look at deployment.

Deployment via Kubernetes

This wasn’t as straightforward as I had hoped, although it works pretty well now. The deployment will use multiple containers with a pod:

  • Git-sync will check git for changes and sync to a shared volume
  • A custom jekyll container will continuously watch for changes on the shared volume and rebuild if detected
  • The last container is standard nginx and will serve the site from the generated content

git-sync

Github

Unfortunately I couldn’t see a prebuilt container for git-sync, so I had to build and publish the image to my own repository. This is pretty simple to do.

You need docker-buildx (You could probably build it without but their standard build uses it). Docker buildx github. I downloaded the binary and placed it in my ~/.docker/cli-plugins directory.

Clone git-sync project and checkout the latest tag eg: git checkout v4.0.0-rc5. You then tag the image with that specific version.

1
git clone https://github.com/kubernetes/git-sync.git

Inside the project run.

The registry can be anything that your K8’s cluster is able to access. Eg: Docker Registry or something like gittea.

1
2
make container REGISTRY=<docker public or your own registry> VERSION=4.0.0-rc5
make push REGISTRY=<same as above> VERSION=4.0.0-rc5

Custom Jekyll Container

Now we need a container for jeykll to watch for changes and rebuild the site. Once again this needs to be pushed to a repository that your K8’s cluster can reach. Create a new file in the chirpy git project at the top level.

touch Dockerfile

Add the following contents.

Note: You will need to change the git clone URL to reflect your fork.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM ubuntu:22.04

RUN apt update && \
    apt-get install -y ruby-full build-essential zlib1g-dev git libmariadb-dev

RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu
USER ubuntu
WORKDIR /home/ubuntu

ENV GEM_HOME="/home/ubuntu/gems"
ENV PATH="/home/ubuntu/gems/bin:$PATH"

RUN gem install jekyll bundler

RUN git clone https://hamgit.monotok.org/hammer/blog-chirpy-jekyll.git -b myblog --single-branch

WORKDIR /home/ubuntu/blog-chirpy-jekyll

RUN bundle install

RUN rm -rf /home/ubuntu/blog-chirpy-jekyll

Now build the dockerfile. Once again you will need to change the tag to your repository.

1
2
docker build -t <docker repo>blog-chirpy-jekyll:u2204 .
docker push -t <docker repo>blog-chirpy-jekyll:u2204 .

Kubernetes Deployment Files

I am going to assume you know how to deploy things to Kubernetes etc. This can be in the same git project if you wish, but I have my deployments for K8s in it’s own project.

I did try to get this working with a ‘emptyDir’ storage type however I couldn’t reliably get the second container to read the data git-sync populated it with.

The deployment file.

Apply as usual with kubectl apply -f file.

You might have to run the git-sync container by itself the first time by commenting out the others to allow it to do an initial git sync. If you don’t then the jekyll container will probably crash as the directories don’t exist.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
apiVersion: apps/v1
kind: Deployment
metadata:
  name: blog
spec:
  replicas: 1
  selector:
    matchLabels:
      name: blog
  template:
    metadata:
      labels:
        name: blog
    spec:
      containers:
        - name: git-sync
          image: <your repo>git-sync/git-sync:4.0.0-rc5__linux_amd64
          volumeMounts:
          - name: tmp-git
            mountPath: /git
          args:
            - --repo=https://<your fork>blog-chirpy-jekyll.git
            - --depth=1
            - --period=60s
            - --link=current
            - --root=/git
            - --http-metrics
            - --http-bind=:9090
            - --ref=myblog #your branch you created for your blog
          env:
            - name: GITSYNC_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: PASSWORD
                  name: gitsync-secrets
            - name: GITSYNC_USERNAME
              valueFrom:
                secretKeyRef:
                  key: USERNAME
                  name: gitsync-secrets
          securityContext:
            runAsUser: 1000
            runAsGroup: 1000
        - name: jekyll
          image: <your repo>/blog-chirpy-jekyll:u2204
          workingDir: /git/current
          volumeMounts:
          - name: tmp-git
            mountPath: /git
          - name: html
            mountPath: /dest
          env:
            - name: JEKYLL_ENV
              value: production
          command:
            - "bundle"
            - "exec"
            - "jekyll"
            - "build"
          args:
            - --destination=/dest
            - --watch
          securityContext:
            runAsUser: 1000
            runAsGroup: 1000
        - name: nginx
          image: nginx:1.25.1
          volumeMounts:
          - name: html
            mountPath: /usr/share/nginx/html
            readOnly: true
          readinessProbe:
            initialDelaySeconds: 10
            periodSeconds: 10
            exec:
              command:
                - ls
                - /usr/share/nginx/html/index.html
      volumes:
      - name: tmp-git
        persistentVolumeClaim:
          claimName: blog-git-sync
      - name: html
        emptyDir: {}

The secret:

Encode a string into base64 like this: echo 'my string' | openssl base64 -A

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
  name: gitsync-secrets
type: Opaque
data:
  PASSWORD: base64 encoded password
  USERNAME: base64 encoded password

The PVC. The storage-class depends on your cluster distribution. I am running RKE2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  labels:
    application.name: blog
  name: blog-git-sync
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 2G
status: {}

The service.

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
  name: blog-service
spec:
  type: ClusterIP
  ports:
  - port: 80
  selector:
    name: blog

The ingress. You will need to adjust accordingly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    ingress.cert-manager.io/issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
    nginx.ingress.kubernetes.io/proxy-body-size: "30M"
  name: blog
spec:
  ingressClassName: nginx
  rules:
  - host: blog.monotok.org
    http:
      paths:
      - backend:
          service:
            name: blog-service
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - blog.monotok.org
    - monotok.org
    secretName: blog-tls-cert
status:
  loadBalancer: {}

Publish a new post

You can publish new posts by simply following the documentation here. Once you have made your changes, commit them in git and push. It should then magically update.

Final Notes

As you have forked the project then you can easily make modifications to the default theme. For example I have:

  • Added extra pages down the left side
  • Added links to the right side

Hopefully this has been useful.

References

Tranglc Getting Started - Chirpy

Wordpress Exporter Plugin

Jekyll Documentation - Deployment

This post is licensed under CC BY 4.0 by the author.

If you have found this site useful, please consider buying me a coffee :)

Proud supporter of the Gnome Foundation

Become a Friend of GNOME

Contents

weather Station in C++ on Raspberry Pi

Configure RKE2 Cluster with MetalLB and NGINX Ingress Controller

Comments powered by Disqus.