20200302-hugo_and_gitlab_ci_cd.draft (10514B)
1 --- 2 title: "hugo and gitlab ci cd" 3 date: 2020-03-02T18:50:01Z 4 summary: How I set up a Hugo website and deployed with Gitlab's CI/CD pipeline 5 categories: [workshop] 6 tags: [website, hugo, devops, gitlab, automation] 7 draft: false 8 --- 9 10 It is about time I get this weblog started again, it has been too long... 11 12 As I had decided to migrate my code repositories off Github it felt right I should move my weblog from being hosted on Github Pages. At first I was going to move to Gitlab Pages and keep everything the same, but **_where_** is the fun in that! 13 14 Instead of Jekyll, the static site generator [Hugo](https://gohugo.io) was recommended to me. I also wanted to play around with Gitlab's CI/CD pipeline tool, so thought the weblog migration would be a great project. I have migrated all my old posts to the new platform as well, so you won't miss out on anything! 15 16 ### new projects 17 18 Generate a new Hugo site, for this example I will be calling mine 'pyratelog' 19 ``` 20 hugo new site pyratelog 21 ``` 22 23 Navigate into the new directory and initialise it as a git repository 24 ``` 25 cd pyratelog 26 git init 27 ``` 28 29 Create a new project in Gitlab 30 31 ![new_gitlab_project](/img/20200302-hugo_blog-01-new_project.png#fitwidth) 32 33 Add your new Gitlab project as a remote repo to your Hugo site and make an initial commit if you want 34 ``` 35 git remote add origin git@gitlab.com:pyratebeard/pyratelog.git 36 git add . 37 git commit -m "initial commit" 38 git push -u origin master 39 ``` 40 41 Your Gitlab project should now be populated with a `config.toml` file and the 'archetypes' directory. 42 43 ![initial_commit](/img/20200302-hugo_blog-02-initial_commit.png#fitwidth) 44 45 I won't keep mentioning when to commit changes to git as we all work differently. We will come to it a bit later when we configure our CI/CD pipeline. 46 47 ### configure hugo 48 49 Let us add a theme to our Hugo project, in this case I will use my own 'futuremyth' theme 50 ``` 51 git submodule add https://gitlab.com/pyratebeard/hugo-futuremyth.git themes/futuremyth 52 echo 'theme = "futuremyth"' >> config.toml 53 ``` 54 55 I have added in the 'paginate' variable to change the default of 10 items to 5, and also set a static directory for use with images in my log entries 56 ``` 57 cat >> config.toml << EOF 58 paginate = "5" 59 staticDir = ["static"] 60 EOF 61 ``` 62 63 I found it is a good idea to change some of the cache directories. There was an issue I had in my Gitlab CI/CD pipeline with root permissions being set on a directory, causing the pipeline to fail 64 ``` 65 cat >> config.toml << EOF 66 [caches.images] 67 dir = ":cacheDir/_gen" 68 [caches.assets] 69 dir = ":cacheDir/_gen" 70 EOF 71 ``` 72 73 You should also edit the 'baseURL' and 'title' variables in your `config.toml`. 74 75 You can start Hugo on your local machine in development mode using 76 ``` 77 hugo server -D 78 ``` 79 80 If you navigate to http://localhost:1313 you should see a fairly empty page. To add new content you run 81 ``` 82 hugo new posts/hello_world.md 83 ``` 84 You change the path to whatever you want, and it will be created under the 'content' directory. 85 86 If you left your deployment server running you should see that in your browser the site should automatically updates. You first entry should show the title of your post and the date. You can open the markdown file in your favourite editor and start writing below the second set of hyphens (`---`). Everything between the hyphens is metadata for the page. You can add more if you like, I add a 'summary', 'categories', and 'tags' in the following way 87 ``` 88 summary: How I set up a Hugo website and deployed with Gitlab's CI/CD pipeline 89 categories: [tech] 90 tags: [website, hugo, devops, gitlab, automation] 91 ``` 92 93 We can now build our site by running 94 ``` 95 hugo 96 ``` 97 98 This won't include our first post because we have left the `draft` variable as `true`. When you are ready to publish change it to `false` and build the site again. You can build with drafts included by running 99 ``` 100 hugo -D 101 ``` 102 103 ### autodevops 104 105 There a many ways you can host a website, and many ways you can use Gitlab's CI/CD pipeline to automate the process. The method I have opted for is to run my Hugo site in a docker container on a DigitalOcean droplet. I have chosen **_not_** to use `docker-compose` to include the Nginx reverse proxy as I host other things behind Nginx and don't want it to be restarted each time I post a log entry. 106 107 On a server with Docker already installed you can set up your Nginx reverse proxy with a Let's Encrypt companion to deal with SSL. 108 109 First, we need to create an new network 110 ``` 111 docker network create nginx-proxy 112 ``` 113 114 Then we can start the Nginx container 115 ``` 116 docker run -d --name nginx-proxy \ 117 -p 80:80 -p 443:443 \ 118 --net nginx-proxy \ 119 -v /etc/nginx/certs \ 120 -v /etc/nginx/vhost.d \ 121 -v /usr/share/nginx/html \ 122 -v /var/run/docker.sock:/tmp/docker.sock:ro \ 123 jwilder/nginx-proxy 124 ``` 125 126 Confirm the container is running by running `docker ps`, the output should look like this but with a different container id 127 ``` 128 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 129 ab7626dd1bec jwilder/nginx-proxy "/app/docker-entrypo…" 2 seconds ago Up 1 second 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp nginx-proxy 130 ``` 131 132 Next we can start the Let's Encrypt companion container (change the email address) 133 ``` 134 docker run -d --name nginx-proxy-letsencrypt \ 135 --volumes-from nginx-proxy \ 136 -v /var/run/docker.sock:/var/run/docker.sock:ro \ 137 -e "DEFAULT_EMAIL=youremail@yourdomain.tld" \ 138 jrcs/letsencrypt-nginx-proxy-companion 139 ``` 140 141 In the Hugo repository we need to create a `.gitlab-ci.yml` file so that we can harness the power of the CI/CD pipeline. 142 143 Enter the following in to the file 144 ``` 145 build: 146 stage: build 147 image: mapitman/docker-hugo:latest 148 services: 149 - docker:dind 150 before_script: 151 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 152 script: 153 - git submodule update --init --recursive 154 - docker build --pull -t $CI_REGISTRY_IMAGE:latest . 155 - docker push $CI_REGISTRY_IMAGE:latest 156 157 deploy: 158 stage: deploy 159 image: docker:latest 160 services: 161 - docker:dind 162 tags: 163 - deploy 164 before_script: 165 - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY 166 script: 167 - docker pull $CI_REGISTRY_IMAGE 168 - docker run -d --name "$CONTAINER_NAME" --expose 1313 --net nginx-proxy -e VIRTUAL_HOST=log.pyratebeard.net -e LETSENCRYPT_HOST=log.pyratebeard.net -v $(pwd):/src $CI_REGISTRY_IMAGE 169 ``` 170 171 You will notice some variables in the file beginning with `$CI_REGISTRY_*` and one called `$CONTAINER_NAME`. These are variables we declare in Gitlab. 172 173 If you use Multi-factor Authentication (MFA) on your Gitlab account you will need to generate a Personal Access Token to use in place of your password. To do this navigate to your account settings and under 'Access Tokens' fill in the Name and tick the 'api' scope. If you don't enter an expiry date the token will not expire. 174 175 ![personal access token](/img/20200302-hugo_blog-03-access_token.png#fitwidth) 176 177 Make sure you copy the access token, we will need it for the next step. 178 179 Navigate to your repository in Gitlab then to 'Settings', 'CI/CD', and expand the 'Variables' section. Enter the following Key/Value pairs 180 181 | Key | Value | 182 | --- | --- | 183 | CI_REGISTRY | registry.gitlab.com | 184 | CI_REGISTRY_IMAGE | registry.gitlab.com/_username_/_project_name_ | 185 | CI_REGISTRY_USER | _username_ | 186 | CI_REGISTRY_PASSWORD | _personal_access_token_ | 187 | CONTAINER_NAME | _anything_ | 188 189 Mark the `CI_REGISTRY_PASSWORD` variable as 'Protected' and make sure you click 'Save variables'. 190 191 Before we push our new Hugo project to Gitlab we need to configure a Runner. Gitlab Runners are used to execute the jobs in our pipeline. 192 193 At first I was trying to use a docker runner to build and deploy my project. Building a new docker image was easy, using kaniko, but I struggled to get the deploy section working. In the end I brought it right back to the Keep It Simple Stupid (KISS) philosophy. 194 195 On the server install a Runner following the instructions [here](https://docs.gitlab.com/runner/install/linux-repository.html). 196 197 ![gitlab runners](/img/20200302-hugo_blog-04-runner.png#fitwidth) 198 199 Use the token that is shown in your repo CI/CD settings under 'Runners', add the tag 'deploy', and select the 'shell' executor. 200 201 Make sure you add the gitlab-runner user to the docker group 202 ``` 203 sudo usermod -aG docker gitlab-runner 204 ``` 205 206 Right, we are almost ready to go! The final file we need is a Dockerfile. This tells docker what we want our image to look like. Enter the following into your Dockerfile, changing the base URL as required 207 ``` 208 FROM jojomi/hugo 209 210 COPY . /src 211 WORKDIR /src 212 213 ENV HUGO_WATCH=true 214 ENV HUGO_THEME=futuremyth 215 ENV HUGO_BASEURL=https://log.pyratebeard.net 216 217 RUN hugo 218 ``` 219 220 Now publish your Hugo site by just running `hugo` again. Make sure all your changes are committed and push 221 ``` 222 git push 223 ``` 224 225 If you navigate to the CI/CD Pipelines page in your Gitlab project your should see the jobs being run. 226 227 ![first pipeline](/img/20200302-hugo_blog-05-pipeline.png#fitwidth) 228 229 Both jobs in the pipeline should complete successfully. Here is a breakdown of what the runner is doing: 230 - build phase 231 - building a new docker image containing our hugo project 232 - pushing the new image to our gitlab project's container registry 233 - deploy phase 234 - pulling our new docker image from the registry onto our server 235 - starting a new container using the image 236 237 There is one final thing we have to add to our `.gitlab-ci.yml` file to ensure the next time you push nothing breaks. In the deploy script, between the `docker pull` and `docker run` commands enter the following 238 ``` 239 docker stop $CONTAINER_NAME 240 docker rm $CONTAINER_NAME 241 docker rmi -f $(docker images --filter "dangling=true" -q) 242 ``` 243 244 These lines make sure to stop and remove the container with the name you have specified, docker doesn't like duplicates. The third line removes and old images to keep things tidy. 245 246 I hope this, fairly long, post helped you in someway. If you want to get any further information you can get in touch on [mastodon](https://mastodon.social/@pyratebeard), or any other way mentioned on my [home page](https://pyratebeard.net). 247