Introduction
I've been working with Civo for a year now, and the truth is that I can't ask for more - it's great working for a cloud company like Civo and with great co-workers. I get to play around with the technology and when I discover something cool, I can write a guide on it. So here is another one! By the end of this you will have a blog site running on Kubernetes that you can continuously deploy without interruptions with each commit to your repository.
What is Hugo?
According to the official site:
The world’s fastest framework for building websites
Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.
The Core
To follow along with this guide, you will need to have a Civo account. You can sign up here and if approved, you will get $250 monthly statement credit for 1 month, meaning following along will be entirely free for you!
You will also need a GitHub account where you can set up a repository, your Civo API key and a Docker Hub account.
We will skip explaining k3s, GitHub Actions, and how they relate together for now. If you want to read about what k3s is, check out this article, or this one on GitHub Actions specifically.
Install Hugo
The first thing will be to download Hugo from their GitHub repository releases page. There are many options for various use cases and operating systems. We will use the hugo_extended
version that has all that's necessary for development without having to install anything else. Download the latest version of hugo_extended
for your operating system, expand it to a directory of your choice, and add that path to our PATH
. Now, to test if everything is fine we execute this command, and you should see output like below:
% hugo version
Hugo Static Site Generator v0.74.3-DA0437B4/extended darwin/amd64 BuildDate: 2020-07-23T16:28:32Z
In my case I have the version v0.74.3
and as I'm on a Mac I use darwin/amd64
. Your output may be different if you use another OS or the version number has changed.
Our first site
Once the installation of hugo
is ready, we can create our first site, and we will do it with this command:
% hugo new site k3s-demo
Congratulations! Your new Hugo site is created in /hugo/k3s-demo.
Just a few more steps and you're ready to go:
1. Download a theme into the same-named folder.
Choose a theme from https://themes.gohugo.io/ or
create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
with "hugo new <SECTIONNAME>/<FILENAME>.<FORMAT>".
3. Start the built-in live server via "hugo server".
Visit https://gohugo.io/ for quickstart guide and full documentation.
As the message says, we can go to the themes page and choose one that we like for our demo. I chose PaperMod and we are going to install it by running the following command inside our project directory:
git submodule add https://github.com/wayjam/hugo-theme-papermod.git k3s-demo/themes/papermod
If it gives an error, it's most likely because your site is not in a git repository, which the command expects. Simply run git init
to initialise a repository in your project's root directory. It is a good idea to create the corresponding GitHub repository now as well. After this we can enter the folder themes/papermod
. Inside will be a directory called exampleSite
, under which we will find a file called config.toml
. Copy this file to the root of our project:
k3s-demo $ cp themes/papermod/exampleSite/config.toml .
Once this is done, standing at the root of our project we are going to run hugo in development mode like this:
% hugo serve
| EN
-------------------+-----
Pages | 6
Paginator pages | 0
Non-page files | 0
Static files | 6
Processed images | 0
Aliases | 1
Sitemaps | 1
Cleaned | 0
Built in 44 ms
Watching for changes in /hugo/k3s-demo/{archetypes,content,data,layouts,static,themes}
Watching for config changes in /hugo/k3s-demo/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
If you visit http://localhost:1313/, you should see something like the following:
So as not to complicate things for this guide, we are going to leave our site as is for now and proceed to the GitHub action. You will be able to add content once that is ready.
GitHub Actions
Now we are going to create everything necessary for our site to run in live Kubernetes. To start, we are going to need to create a repository for our site on GitHub.
Following this we are going to create this directory structure in the root of our site:
├── .github
│ └── workflows
│ └── main.yaml
After this we will create the secrets within our repository's settings on GitHub. You will need the following:
CIVO_TOKEN <-------- | Your Civo API token
DOCKER_EMAIL <------ | Your Docker Hub email
DOCKER_USERNAME <--- | Your Docker Hub username
DOCKER_TOKEN <------ | A token generated in Docker Hub's settings page
Once this is done, we are going to put the following content inside ./github/workflows/main.yaml
, which will form the steps of our GitHub deployment action. It is set to run on changes to the main branch (labeled master
in this case) by installing the Civo action that allows you to create a cluster. It the Dockerizes your Hugo site and applies that Docker container to the cluster.
name: Deploy-Site
on:
push:
branches:
- master
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install civo
uses: civo/action-civo@v1.0.0
with:
token: ${{ secrets.CIVO_TOKEN }}
- name: Create a k3s cluster
run: >
if [[ $(civo k3s show my-site -o custom -f ID) == "" ]]; then
civo k3s create my-site -n 2 --wait
fi
- name: Make config folder
run: mkdir ~/.kube
- name: Save our cluster's authentication details
run: >
civo k3s config my-site --save
--local-path ~/.kube/config -y
- name: Ensure we can connect to the API
run: >
i=0;
while [ $i -le 120 ]; do
kubectl get nodes && break;
((i++));
sleep 1;
done
- name: Authenticate our Kubernetes cluster to Docker Hub
run: >
if ! kubectl get secret regcred | grep "regcred"; then
kubectl create secret docker-registry regcred
--docker-email=${{secrets.DOCKER_EMAIL}}
--docker-server=docker.io
--docker-username=${{ secrets.DOCKER_USERNAME }}
--docker-password=${{ secrets.DOCKER_TOKEN }}
fi
- name: Replace our cluster ID
run: >
sed -i'' -e "s/CLUSTER_ID/`civo k3s show my-site -o custom -f ID`/" k8s.yaml &&
sed -i'' -e "s/CLUSTER_ID/`civo k3s show my-site -o custom -f ID`/" config.toml
- uses: jakejarvis/hugo-build-action@master
with:
args: --minify --buildDrafts
- name: Push to DockerHub
uses: docker/build-push-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
repository: alejandrojnm/my-site
tags: latest
- name: Deploy our app to the cluster
run: kubectl apply -f k8s.yaml
- name: Wait for the deployment to be ready
run: >
i=0;
while [ $i -le 120 ]; do
kubectl rollout status deployment/my-site | grep "successfully rolled out" && break;
((i++));
sleep 1;
done
- name: Update application
run: kubectl patch deployment my-site -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"`date +'%s'`\"}}}}}"
In the above file you will only have to modify repository: alejandrojnm / my-site
to include the repository you are going to use.
Now we are going to create the parts necessary to integrate everything. The first thing will be to create the Dockerfile
file in the root of our project, with the following:
FROM nginx
COPY ./public /usr/share/nginx/html
This simply sets up a nginx
web server to serve our site's public
folder.
Following this we will create another file called k8s.yaml
and inside we will put this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-site
spec:
replicas: 3
selector:
matchLabels:
app: my-site
template:
metadata:
labels:
app: my-site
spec:
containers:
- image: alejandrojnm/my-site:latest
name: my-site
imagePullPolicy: Always
imagePullSecrets:
- name: regcred
---
apiVersion: v1
kind: Service
metadata:
name: my-site
labels:
app: my-site
spec:
ports:
- name: "my-site"
port: 80
selector:
app: my-site
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-site-ingress
annotations:
kubernetes.io/ingress.class: "traefik"
labels:
app: my-site
spec:
rules:
- host: CLUSTER_ID.k8s.civo.com
http:
paths:
- path: /
backend:
serviceName: my-site
servicePort: 80
As with the earlier example, in this file you will also have to modify - image: alejandrojnm/my-site:latest
to use the one that you put in the GitHub action. The other steps this deployment file takes are to create a service operating on port 80 and an ingress allowing routing to your deployed site.
Now our last step, remember the config.toml
of our site that is at the root of our project? Well, we are going to modify it by replacing baseURL
with this baseURL = "http://CLUSTER_ID.k8s.civo.com/"
.
Deploying
Now we only have to do commit
and push
to our repository and if everything went well, within a few minutes we will be able to see our Hugo site in the url CLUSTER_ID.k8s.civo.com
. This site would get updated with new content, themes, or anything else you change in the repository, with each push to the main branch.
Next steps
Of course, this example is very simplistic. It assumes your site should exist in a Kubernetes cluster called "my-site", for one. It also makes the decision of the type and number of Civo Kubernetes nodes to deploy on your behalf. It also does not enable https, but that is best left to another guide.
You're free to play around with the values in the various yaml configuration files to get a deployment that suits your use case. Just make sure that if you are changing your cluster specifications you remove it from your Civo account. If you are simply changing configuration options, each time you commit the files and push to master the GitHub action will simply update rather than rebuild your cluster according to your specifications.
Let us know on Twitter or on the Civo Community Slack if you followed along, and where we can see your blog on the internet!