dev post
This commit is contained in:
@ -6,9 +6,10 @@ tags: ["hugo", "docker", "drone"]
|
||||
---
|
||||
|
||||
{{< lead >}}
|
||||
Here we are, never too late too init my first blog 🙊.
|
||||
I have a dream that I'll have a blog...
|
||||
{{< /lead >}}
|
||||
|
||||
Here we are, never too late too init my first blog 🙊.
|
||||
So as a [cliché](https://www.hsablonniere.com/once-upon-a-blog--9849zg/), this first post must have to talk about his blog...
|
||||
|
||||
## Why Hugo ?
|
||||
@ -33,9 +34,14 @@ You obviously must have a Github account, if you prefer self-hosted solution and
|
||||
|
||||
As you can see the [repo of this blog](https://github.com/adr1enbe4udou1n/blog), thanks to Hugo modules we easily successfully manage to have *only what maters* versioned, i.e. only hugo and theme related config files and obviously the contents and layouts overload for customization.
|
||||
|
||||
### Deployment
|
||||
### Automatic build
|
||||
|
||||
Hugo supports many deployments methods, with Github Actions as the simplest. In my case I prefer more self-hosted approach with my favorite CI/CD tool aka [Drone CI](https://www.drone.io/). Here is the simplest way to build an image and pushing into a custom private docker image registry.
|
||||
As any static site generators, the process steps are :
|
||||
|
||||
1. Generate all static assets files from the content.
|
||||
2. Serving these outputted assets through simple web server as Nginx.
|
||||
|
||||
Hugo supports many deployments methods, with Github Actions as one of the simplest. In my case I prefer more self-hosted approach with my favorite CI/CD tool aka [Drone CI](https://www.drone.io/). Here is the simplest way to build an image and pushing into a custom private docker image registry.
|
||||
|
||||
```yaml
|
||||
kind: pipeline
|
||||
@ -51,6 +57,7 @@ steps:
|
||||
- name: image
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: registry.okami101.io
|
||||
repo: registry.okami101.io/adr1enbe4udou1n/blog
|
||||
tags: latest
|
||||
username:
|
||||
@ -58,3 +65,118 @@ steps:
|
||||
password:
|
||||
from_secret: registry_password
|
||||
```
|
||||
|
||||
{{< alert >}}
|
||||
Note as I use `latest-mod` image tag in my case because I use Congo theme as Go modules dependency, which is the cleanest way to do it as I do not need to include it on my repo. The `latest` image tag has only Hugo binary without Go dependency.
|
||||
{{< /alert >}}
|
||||
|
||||
This Drone pipeline consists of simple 2 steps as we're talking above :
|
||||
|
||||
1. The first **build step** use minimal Go based image container which includes Hugo binary. All we have to do is launching `hugo --minify` command which will firstly download Congo theme dependency and then generate all assets into `public` subfolder. Note as Drone will automatically clone the repo with `depth=1` and mount it into Hugo container.
|
||||
2. Then we use official [Docker plugin](http://plugins.drone.io/drone-plugins/drone-docker/) in order to build our final docker image and **push into custom private registry**.
|
||||
|
||||
{{< alert >}}
|
||||
In order to use public docker registry simply change image step as following :
|
||||
|
||||
```yaml
|
||||
...
|
||||
- name: image
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: foo/bar
|
||||
tags: latest
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
...
|
||||
```
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
The Drone Docker plugin needs a local **Dockerfile** which will describe how to build the image. In our case, all we need is to choose a light web server (`Nginx` will be perfect) and copy previously built `public` subfolder from the 1st step into our docker image. It's only 2 lines !
|
||||
|
||||
```Dockerfile
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY public /usr/share/nginx/html
|
||||
```
|
||||
|
||||
{{< alert >}}
|
||||
By default Nginx use `/usr/share/nginx/html` as default public directory.
|
||||
{{< /alert >}}
|
||||
|
||||
Note as Drone will automatically mount the current volume state on each step so the `public` folder will be directly available on all subsequent step.
|
||||
|
||||
It's that it. We now have ready-to-deploy production image that will be auto updated on each push.
|
||||
|
||||
### Hosting & Deployment
|
||||
|
||||
In my case, I use custom self-hosted `Docker Swarm` for all my projects, and [`Traefik`](https://doc.traefik.io/traefik/) as reverse proxy. This proxy allows automatic service discovery and SSL management. All I have to do is define a new blog `stack` into my swarm cluster. A stack is just the same as docker-compose file in standalone Docker host, but with additional `deploy` statement that allows resource management as scaling strategy, etc.
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: registry.okami101.io/adr1enbe4udou1n/blog
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.blog.entrypoints=https
|
||||
- traefik.http.routers.blog.rule=Host(`blog.okami101.io`)
|
||||
- traefik.http.services.blog.loadbalancer.server.port=80
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
```
|
||||
|
||||
If we forget all necessary `traefik` related config, the stack can't be more basic than that. It's just a matter of pulling our image from our private registry. Don't forget to save private registry credentials by `drone login hub.myregistry.com` command before.
|
||||
|
||||
All we have to do is to redirect web traffic from custom domain to our new Docker container instantiated from our final image. It's really easy with a proper configured Traefik reverse proxy :
|
||||
|
||||
1. Firstly the service must be connected to the public dedicated Traefik internal private network.
|
||||
2. The `traefik.enable=true` deploy labels allows automatic discovery by Traefik. This is required when [`exposedByDefault`](https://doc.traefik.io/traefik/providers/docker/#exposedbydefault) is set to `false`.
|
||||
3. The `traefik.http.routers.blog.entrypoints` is used for selecting the proper configured [entrypoint](https://doc.traefik.io/traefik/routing/entrypoints/). See it as the external port access between end users and your host.
|
||||
4. The `traefik.http.routers.blog.rule` allows proper routing from a specific URL request pattern (most of the time the DNS host) to this service.
|
||||
5. Finally the `traefik.http.services.blog.loadbalancer.server.port` is mandatory for Docker Swarm in order to proper routing to the internal port of our Docker image. For Nginx based images, it's `80` by default.
|
||||
|
||||
Then use the `docker stack deploy -c blog.yml blog` command for launching the stack. If successfully started, Traefik will automatically discover the new service and route all traffics from `blog.okami101.io` URLs to our custom Nginx container.
|
||||
|
||||
### Continuous Deployment
|
||||
|
||||
The final task is to configure automatic deploy to production on each push. For that we have to restart the above service on every push. This can be achieved via `docker service update blog_app`, note as `blog_app` is the default service name that follow `<stack_name>_<service_name>` naming convention.
|
||||
|
||||
**BUT** by default it will not use the latest updated image by default. So we need to add additional `--image` argument as following : `docker service update --image registry.okami101.io/adr1enbe4udou1n/blog:latest blog_app --with-registry-auth`. The `--with-registry-auth` argument is mandatory for private registries. This command **must be launch on a manager** Swarm cluster.
|
||||
|
||||
All we have to do now is to use our Drone pipeline with a new final *deploy* step that will consist on simple SSH command 🙌 with this above one-line script. This can be achieved easily thanks to [Drone SSH plugin](http://plugins.drone.io/appleboy/drone-ssh/).
|
||||
|
||||
```yaml
|
||||
...
|
||||
- name: deploy
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host: front.okami101.io
|
||||
port: 2222
|
||||
username: okami
|
||||
key:
|
||||
from_secret: swarm_ssh_key
|
||||
script:
|
||||
- docker service update --image registry.okami101.io/adr1enbe4udou1n/blog:latest blog_app --with-registry-auth
|
||||
...
|
||||
```
|
||||
|
||||
{{< alert >}}
|
||||
`swarm_ssh_key` is the secret private SSH key that will allow proper SSH connection to the swarm manager cluster.
|
||||
{{< /alert >}}
|
||||
|
||||
We have now full CI/CD 🎉. Pretty heavy for just a fancy blog, but it would be not cool without a little overkill complexity. Moreover, it will be impossible to write my first post ! A blog without a single post will be so sad 🙈.
|
||||
|
||||
I just regret to not have tried with Kubernetes or either tried to develop my [own fancy HTTP framework](https://www.hsablonniere.com/once-upon-a-blog--9849zg/#served-with-my-own-http-framework) as Hubert do for more 📎🔥...
|
||||
|
||||
{{< lead >}}
|
||||
Now, I have a blog...
|
||||
{{< /lead >}}
|
||||
|
Reference in New Issue
Block a user