proofreading

This commit is contained in:
2022-02-28 22:25:02 +01:00
parent e84aea7ad3
commit 7702d0da9c
6 changed files with 57 additions and 63 deletions

View File

@ -66,8 +66,10 @@ Before continue, let's add some labels on nodes in order to differentiate proper
```sh
# worker-01 is intended for running production app container
docker node update --label-add environment=production worker-01
# runner-01 is intended to build docker image through CI/CD pipeline
docker node update --label-add environment=build runner-01
```
{{< /highlight >}}
@ -91,7 +93,7 @@ Thankfully, Traefik can be configured to take cares of all SSL certificates gene
I should say that Traefik is not really easy to setup for new comers. The essential part to keep in mind is that this reverse proxy has 2 types of configuration, *static* and *dynamic*. [Go here](https://doc.traefik.io/traefik/getting-started/configuration-overview/) for detail explication of difference between these types of configuration.
Here we'll talk about static configuration. Create a YAML file under `/etc/traefik/traefik.yml` of `manager-01` server with following content (TOML is also supported) :
Here we'll talk about static configuration. Create next YAML file (TOML is also supported) :
{{< highlight host="manager-01" file="/etc/traefik/traefik.yml" >}}
@ -184,7 +186,7 @@ All I have to do is to add a specific label `traefik.enable=true` inside the Doc
#### Traefik deployment
In order to deploy Traefik on our shiny new Docker Swarm, we must write a Docker Swarm deployment file that looks like to a classic Docker compose file. Create a `traefik-stack.yml` file somewhere in your manager server with following content :
In order to deploy Traefik on our shiny new Docker Swarm, we must write a Docker Swarm deployment file that looks like to a classic Docker compose file. Create next file :
{{< highlight host="manager-01" file="~/traefik-stack.yml" >}}
@ -410,7 +412,7 @@ If you go to the stacks menu, you will note that both `traefik` and `portainer`
It's finally time to test our new cluster environment by testing some stacks through the Portainer GUI. We'll start by installing [`Diun`](https://crazymax.dev/diun/), a very useful tool which notify us when used docker images has available update in its Docker registry.
Create a new `diun` stack through Portainer and set following content :
Create the next stack through Portainer :
{{< highlight host="stack" file="diun" >}}

View File

@ -72,7 +72,7 @@ With last command, you now access the db directly from the manager by
We are now ready to go for installing phpMyAdmin as GUI DB manager. Thanks to our Docker Swarm cluster, it's super simple !
Create a new `phpmyadmin` stack with following :
Create next stack :
{{< highlight host="stack" file="phpmyadmin" >}}
@ -200,7 +200,7 @@ sudo chown -R 5050:5050 /mnt/storage-pool/pgadmin/
{{< /highlight >}}
Finally, create a new `pgadmin` stack with following :
Finally, create next stack :
{{< highlight host="stack" file="pgadmin" >}}
@ -251,7 +251,7 @@ Let's now test our cluster with 3 app samples. We'll deploy them to the worker n
### Matomo over MySQL
Be free from Google Analytics with Matomo. It's incredibly simple to install with our cluster. Note as Matomo only supports MySQL or MariaDB database. Let's create dedicated storage folder with `sudo mkdir /mnt/storage-pool/matomo` and create following `matomo` stack :
Be free from Google Analytics with Matomo. It's incredibly simple to install with our cluster. Note as Matomo only supports MySQL or MariaDB database. Let's create dedicated storage folder with `sudo mkdir /mnt/storage-pool/matomo` and create following stack :
{{< highlight host="stack" file="matomo" >}}
@ -327,7 +327,7 @@ cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 40 | head -n 1
{{< /highlight >}}
Next create new following `redmine` stack :
Next create new following stack :
{{< highlight host="stack" file="redmine" >}}

View File

@ -18,15 +18,15 @@ This part is totally optional, as it's mainly focused on monitoring. Feel free t
## Metrics with Prometheus 🔦
Prometheus is become the standard de facto for self-hosted monitoring in part thanks to its architecture. It's a TSDB (Time Series Database) that will poll (aka scrape) standard metrics REST endpoints, provided by the tools to monitor. It's the case of Traefik, as we have seen in [part III]({{< ref "04-build-your-own-docker-swarm-cluster-part-3#traefik-" >}}). For tools that don't support it natively, like databases, you'll find many exporters that will do the job for you.
Prometheus became the standard de facto for self-hosted monitoring in part thanks to its architecture. It's a TSDB (Time Series Database) that will poll (aka scrape) standard metrics REST endpoints, provided by the tools to monitor. It's the case of Traefik, as we have seen in [part III]({{< ref "04-build-your-own-docker-swarm-cluster-part-3#traefik-" >}}). For tools that don't support it natively, like databases, you'll find many exporters that will do the job for you.
### Prometheus install 💽
I'll not use GlusterFS volume for storing Prometheus data, because :
* 1 instance needed on the master
* 1 prometheus instance needed on the master
* No critical data, it's just metrics
* No need of backup, and it can be pretty huge
* No need of backup, as it can be pretty huge
First go to the `manager-01` node settings in Portainer inside *Swarm Cluster overview*, and apply a new label that indicates that this node is the host of Prometheus data.
@ -61,7 +61,7 @@ scrape_configs:
It consists on 2 scrapes job, use `targets` in order to indicate to Prometheus the `/metrics` endpoint locations. I configure `5s` as interval, that means Prometheus will scrape `/metrics` endpoints every 5 seconds.
Finally create a `prometheus` stack in Portainer :
Finally create next stack in Portainer :
{{< highlight host="stack" file="prometheus" >}}
@ -123,12 +123,12 @@ In *Status > Targets*, you should have 2 endpoints enabled, which correspond to
### Get cluster metrics
We have the monitor brain, new it's time to have some more relevant metrics data from all containers as well as docker nodes. It's doable thanks to exporters :
We have the monitor brain, now it's time to have some more relevant metrics data from all containers as well as docker nodes. Its doable thanks to exporters.
* **cAdvisor** from Google which scrape metrics of all running containers
* **Node exporter** for more global cluster evaluation
* **Node exporter** for more global node (aka host) level metrics
Before edit above stack, we need to make a specific docker entrypoint for node exporter that will help us to fetch the original hostname of the docker host machine name. This is because we run node exporter as docker container, which have no clue of docker hostname.
Before edit above stack, we need to make a specific docker entry point for node exporter that will help us to fetch the original hostname of the docker host machine name. This is because we run node exporter as docker container, which have no clue of original node hostname.
Besides this node exporter (like cAdvisor) work as an agent which must be deployed in *global* mode. In order to avoid have a file to put on each host, we'll use the *config* docker feature availabe in swarm mode.
@ -153,7 +153,7 @@ exec "$@"
It will take the node hostname and create an exploitable data metric for prometheus.
Next we'll edit our `prometheus` stack by expanding YML config with next 2 additional services :
Next we'll edit our `prometheus` stack by expanding YML config with 2 additional services :
{{< highlight host="stack" file="prometheus" >}}
@ -262,7 +262,7 @@ sudo systemctl enable redis-server.service
As always, it's just a Swarm stack to deploy ! Like [N8N]({{< ref "/posts/05-build-your-own-docker-swarm-cluster-part-4#n8n-over-postgresql" >}}), we'll use a proper real production database and production cache.
First connect to pgAdmin and create new grafana user and database. Don't forget *Can login?* in *Privileges* tab, and set grafana as owner on database creation.
First connect to pgAdmin and create new grafana user and database. Don't forget to tick *Can login?* in *Privileges* tab, and set grafana as owner on database creation.
Create storage folder with :
@ -275,7 +275,7 @@ sudo chown -R 472:472 /mnt/storage-pool/grafana
{{< /highlight >}}
Next create new following `grafana` stack :
Next create new following stack :
{{< highlight host="stack" file="grafana" >}}
@ -325,7 +325,7 @@ Set proper `GF_DATABASE_PASSWORD` and deploy. Database migration should be autom
For best show-case scenario of Grafana, let's import an [existing dashboard](https://grafana.com/grafana/dashboards/11939) suited for complete Swarm monitor overview.
First we need to add Prometheus as main metrics data source. Go to *Configuration > Data source* menu and click on *Add data source*. Select Prometheus and set the internal docker prometheus URL, which should be `http://prometheus:9090`.
First we need to add Prometheus as main metrics data source. Go to *Configuration > Data source* menu and click on *Add data source*. Select Prometheus and set the internal docker prometheus URL, which should be `http://prometheus:9090`. A successful message should appear when saving.
[![Grafana prometheus datasource](grafana-prometheus-datasource.png)](grafana-prometheus-datasource.png)

View File

@ -17,7 +17,6 @@ This is the **Part VI** of more global topic tutorial. [Back to first part]({{<
A real production cluster should have centralized logs. Of course, we have some basic service logs viewer on Portainer, which shows the containers STDOUT, but :
* With more and more containers, it can be unmanageable
* Logs of containers will not persist after each container restart
* Not very powerful to navigate, can be tedious with huge logs
Moreover, it'll be nice if logs of `data-01` services (MySQL, PostgreSQL, etc.) can be centralized too.
@ -32,8 +31,8 @@ The common way to deal with this is to use *ELK*, but I'll show you a better opt
The mains exporters are :
* Promtail which fetch logs local file based on some patterns perfect for our `data-01` managed server
* Docker driver plugin which redirect all STDOUT to Loki.
* Promtail which fetch local logs file based on some patterns, which is perfect for our `data-01` managed server
* Docker driver plugin which redirect all containers STDOUT to Loki.
## Logs with Loki 📄
@ -46,15 +45,7 @@ curl -O -L "https://github.com/grafana/loki/releases/download/v2.4.2/loki-linux-
unzip "loki-linux-amd64.zip"
chmod a+x "loki-linux-amd64"
sudo mv loki-linux-amd64 /usr/local/bin/loki
```
{{< /highlight >}}
Prepare the config file :
{{< highlight host="data-01" >}}
```sh
wget https://raw.githubusercontent.com/grafana/loki/master/cmd/loki/loki-local-config.yaml
sudo mkdir /etc/loki
sudo mv loki-local-config.yaml /etc/loki/
@ -70,7 +61,7 @@ Then prepare the service :
{{< highlight host="data-01" file="/etc/systemd/system/loki.service" >}}
```conf
```txt
[Unit]
Description=Loki
After=network.target
@ -156,7 +147,7 @@ Then prepare the service :
{{< highlight host="data-01" file="/etc/systemd/system/promtail.service" >}}
```conf
```txt
[Unit]
Description=Promtail
After=network.target
@ -200,9 +191,9 @@ docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all
docker plugin ls
```
Now we have 2 options, reedit all active docker stack YAML description to use the Loki driver (boring), or downright consider it as default driver for all containers, which is relevant in our case, I think.
Now we have 2 options, reedit all active docker stack YAML description for using the Loki driver instead of default docker *json-file* (boring), or downright consider it as default driver for all containers, which is relevant in our case, I think.
Create following file on each docker host with following content :
Create following file on each docker host :
{{< highlight file="/etc/docker/daemon.json" >}}
@ -246,7 +237,7 @@ After this primary testing, let's use the power of Grafana with variables :
[![Grafana loki datasource](grafana-variables.png)](grafana-variables.png)
1. Return to your panel editor. A new *stack* selector will appear in the top will all you to select the stack logs to show !
1. Return to your panel editor. A new *stack* selector will appear in the top, allowing selection of the stack logs to show !
2. Let's apply for saving the panel and test the selector. The Panel should reactive with the *stack* selector.
3. Save the dashboard.
@ -256,7 +247,7 @@ After this primary testing, let's use the power of Grafana with variables :
For further advanced development or any complex troubleshoot analysis, notably in a performance point of view, a tracing tool can be a real capital gain. It really helps for getting a high detail level of all code execution stacks, with granular time execution for each function call, like an SQL query executed from a backend stack, etc.
We'll not discuss development side here, as it's a subject that will be treated in [next part]({{< ref "08-build-your-own-docker-swarm-cluster-part-7#tracing-with-opentelemetry-" >}}). But we'll use Traefik as a perfect integration example.
We'll not discuss development side here, as it's a subject that will be treated in next parts. But we'll use Traefik as a perfect integration example.
It's important to use a really efficient asynchronous tool for this task as it will receive potentially a huge amount of calls. A popular tracing tool nowadays is Jaeger, which is CNCF compliant. Jaeger is a flexible tool made of multiple little services that serves specific task.
@ -299,7 +290,7 @@ Before starting, let's calm down Java legendary memory consumption by creating f
{{< highlight host="data-01" file="/etc/elasticsearch/jvm.options.d/hs.options" >}}
```conf
```txt
-Xms512m
-Xmx512m
```
@ -320,7 +311,7 @@ sudo systemctl start elasticsearch.service
Be sure that Elasticsearch is correctly responding from docker nodes by doing `curl http://data-01:9200`.
As a bonus, expand above promtail config file by adding a new job :
As a bonus, expand above promtail config file for visualizing Elasticsearch logs in Grafana by adding a new job :
{{< highlight host="data-01" file="/etc/loki/promtail-local-config.yaml" >}}
@ -398,9 +389,9 @@ networks:
| name | description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `collector` | acts as a simple GRPC endpoint for saving all traces in particular span storage, as Elasticsearch. |
| `agent` | a simple REST endpoint for receiving traces, the latter being forwarded to the collector. An agent should be proper to a machine host, similarly as the portainer agent. |
| `query` | a simple UI that connects to the span storage and allows simple visualization. |
| `collector` | Acts as a simple GRPC endpoint for saving all traces in particular span storage, as Elasticsearch. |
| `agent` | A simple REST endpoint for receiving traces, the latter being forwarded to the collector. An agent should be proper to a machine host, similarly as the portainer agent. |
| `query` | A simple UI that connects to the span storage and allows simple visualization. |
After few seconds, go to <https://jaeger.sw.dockerswarm.rocks> and enter Traefik credentials. You will land to Jaeger Query UI with empty data.
@ -446,7 +437,7 @@ networks:
{{< /highlight >}}
Then redeploy the stack by `docker stack deploy -c traefik-stack.yml traefik`. You'll probably need to reexport the `HASHED_PASSWORD` variable environment.
Then redeploy the stack by `docker stack deploy -c traefik-stack.yml traefik`. You'll probably need to reexport the `HASHED_PASSWORD` variable environment. Note as you can eventually add the `jaeger_private` network directly from Portainer in `traefik` service instead of doing this in CLI.
Go back to Traefik dashboard and ensure Jaeger is enabled in *Features* section. Traefik should now correctly send traces to Jaeger agent.

View File

@ -20,13 +20,13 @@ This specific VCS part is optional and is only for developers that would be comp
A backup is highly critical ! Don't underestimate that part and be sure to have a proper solution. **Restic** described in [this previous section]({{< ref "05-build-your-own-docker-swarm-cluster-part-4#data-backup-" >}}) is a perfect choice.
{{< /alert >}}
Of course, in a ~$30 cluster, forget about running a self-hosted GitLab, you will be forced to have an additionnal worker node with at least 4Gb fully dedicated just for running it. I will privilege here a super lightweight solution, Gitea. Besides, the last version 1.16 finally support dark mode !
Of course, in this low-budget cluster, forget about running properly a self-hosted GitLab, you will be forced to have an additionnal worker node with at least 4Gb fully dedicated just for running it. I will privilege here a super lightweight solution, Gitea. Besides, the last version 1.16 finally support dark mode !
### Install Gitea 💽
You guess it, it's just an additional stack to run !
Do `sudo mkdir /mnt/storage-pool/gitea` and create a new `gitea` stack :
Do `sudo mkdir /mnt/storage-pool/gitea` and create next stack :
{{< highlight host="stack" file="gitea" >}}
@ -48,7 +48,7 @@ services:
- traefik.http.services.gitea.loadbalancer.server.port=3000
- traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)
- traefik.tcp.routers.gitea-ssh.entrypoints=ssh
- traefik.tcp.services.gitlab-ssh.loadbalancer.server.port=22
- traefik.tcp.services.gitea-ssh.loadbalancer.server.port=22
placement:
constraints:
- node.role == manager
@ -61,19 +61,18 @@ networks:
{{< /highlight >}}
{{< alert >}}
We added a specific TCP router in order to allow SSH cloning. The SSH Traefik entry point will redirect to the first available service with TCP router.
Note as we need to indicate entry points in order to avoid bad redirection from other HTTPS based service.
We added a specific TCP router in order to allow SSH cloning. The SSH Traefik entry point will redirect to the first available service with TCP router.
{{< /alert >}}
Now go to <https://gitea.sw.dockerswarm.rocks> and go through the installation procedure. Change default SQLite provider by a more production purpose database.
Create a new `gitea` PostgreSQL database as usual from pgAdmin or `psql` for pro-CLI user, and set the according DB info access to Gitea installer. Host should be `data-01`.
Don't forgive to change all domain related field by the proper current domain URL, which is `gitea.sw.dockerswarm.rocks` in my case. You should set proper SMTP settings for notifications.
Don't forgive to change all domain related field by the proper current domain URL. You should set proper SMTP settings for notifications.
[![Gitea admin dashboard](gitea-install.png)](gitea-install.png)
For information all these settings are saved in `/mnt/storage-pool/gitea/gitea/conf/app.ini` file. You can change them at any time. You may want to disable registration by changing `DISABLE_REGISTRATION`.
For information all these settings will be saved in `/mnt/storage-pool/gitea/gitea/conf/app.ini` file. You can change them at any time. You may want to disable registration by changing `DISABLE_REGISTRATION`.
Next just create your first account. The 1st account will be automatically granted to administrator.
@ -87,7 +86,7 @@ Before attack the CI/CD part, we should take care of where we put our main docke
### Install official docker registry 💽
We'll use the official docker registry with addition of nice simple UI for images navigation. It's always the same, do `sudo mkdir /mnt/storage-pool/registry` and create `registry` stack :
We'll use the official docker registry with addition of nice simple UI for images navigation. It's always the same, do `sudo mkdir /mnt/storage-pool/registry` and create next stack :
{{< highlight host="stack" file="registry" >}}
@ -182,7 +181,7 @@ Delete the image test through UI and from local docker with `docker image rm reg
{{< alert >}}
Note as the blobs of image is always physically in the disk, even when "deleted". You must launch manually the docker GC in order to cleanup unused images.
For that execute `registry garbage-collect /etc/docker/registry/config.yml` inside docker registry.
For that execute `registry garbage-collect /etc/docker/registry/config.yml` inside docker registry container console. You can ssh into it directly through Portainer.
{{< /alert >}}
### Register registry in Portainer
@ -191,7 +190,7 @@ For our future app deployments from our built docker image, we need to register
[![Portainer registries](portainer-registries.png)](portainer-registries.png)
Save it, and you have registered repository, which will allow proper pulling from it when next stack developments.
Save it, and you have registered repository, which will allow proper pulling from it for next custom app stack deployments.
## CI/CD with Drone 🪁
@ -226,7 +225,7 @@ Let's follow [the official docs](https://docs.drone.io/server/provider/gitea/) f
[![Gitea drone application](gitea-drone-application.png)](gitea-drone-application.png)
Save and keep the client and secret tokens. Then create a new `drone` PostgreSQL database and create a new `drone` stack :
Save and keep the client and secret tokens. Then create a new `drone` PostgreSQL database and create next stack :
{{< highlight host="stack" file="drone" >}}
@ -280,7 +279,7 @@ networks:
{{< /highlight >}}
{{< alert >}}
Don't forget to have proper docker labels on nodes, as explain [here]({{< ref "04-build-your-own-docker-swarm-cluster-part-3#add-environment-labels" >}}), otherwise docker runner will not run because of `node.labels.environment == build`.
Don't forget to have proper docker labels on nodes, as explain [here]({{< ref "04-build-your-own-docker-swarm-cluster-part-3#cli-tools--environment-labels" >}}), otherwise docker runner will not run because of `node.labels.environment == build`.
{{< /alert >}}
| variable | description |
@ -437,7 +436,7 @@ If all's going well, the final image should be pushed in our docker registry. Yo
### Deployment (the CD part) 🚀
Our application is now ready for production deployment ! Let's create our new shiny `weather` stack :
Our application is now ready for production deployment ! Let's create our new shiny stack :
{{< highlight host="stack" file="weather" >}}
@ -503,7 +502,7 @@ Now let's add a new `deploy` step inside `.drone.yml` into our pipeline for auto
{{< /highlight >}}
The as example edit `Program.cs` file and change next line :
Let's test by editing `Program.cs` file :
{{< highlight file="Program.cs" >}}

View File

@ -109,7 +109,7 @@ sudo service procps restart
{{< /highlight >}}
Create a `sonar` PostgresSQL database, and create a `sonar` stack :
Create a `sonar` PostgresSQL database, and create next stack :
{{< highlight host="stack" file="sonar" >}}
@ -216,8 +216,8 @@ This above 2 methods will speed up CI drastically.
We have now a perfect environment for a well-balanced load testing ! There are 2 popular options :
* **Locust** which offers a nice integrated chart web UI and master-workers architecture which allows distributed loading. Load scripts are written on Python.
* **k6** which is more efficient and less resource demanding for the same load. Load scripts are written on Javascript.
* **Locust**, from Python world, which offers a nice integrated chart web UI and master-workers architecture which allows distributed loading. Load scripts are written on Python.
* **k6**, written in Go, which is more efficient and less resource demanding for the same load. Load scripts are written on JavaScript.
Here I'll cover usage of k6. Note that it can be integrated to a time series database for a nice graphical extraction. And guess what, Grafana is the perfect tool for that ! Let's profit of our powerful tools we have !
@ -252,7 +252,7 @@ volumes:
{{< /highlight >}}
{{< alert >}}
Add proper `influxdb.data=true` docker label in the node you want to store the influx data. Here I chose to put in the `runner-01` node by taping this command : `docker node update --label-add influxdb.data=true runner-01`.
Add proper `influxdb.data=true` docker label in the node you want to store the influx data. Here I chose to put in the `runner-01` node by taping this command in `manager-01` : `docker node update --label-add influxdb.data=true runner-01`.
{{< /alert >}}
Add InfluxDB private network to Grafana stack :
@ -298,6 +298,8 @@ export default function () {
[![Portainer config k6](portainer-configs-k6.png)](portainer-configs-k6.png)
Then create the following stack :
{{< highlight host="stack" file="k6" >}}
```yml
@ -341,7 +343,7 @@ configs:
| `K6_OUT` | The data source where to store current results. |
{{< alert >}}
The `restart_policy` in above deploy section is important here, as we don't want the service restarting every time. This is a specific stack intended to be launch once.
The `restart_policy` in above deploy section is important here, as we don't want the service restarting every time. This is a specific stack intended to be launch once manually.
{{< /alert >}}
Deploy the stack, and it should launch a load testing for 1 minute. In the end, the task status of docker service should indicate `complete` status.
@ -352,7 +354,7 @@ With Loki as the default log driver, we get only current logs of running tasks i
### Visualization through Grafana
It's now time to go back to Grafana and try to get some charts. First Add a new InfluxDB *Data source*. Set `http://influxdb_db:8086` inside *URL* field and `k6weather` in *Database* field, then *Save & test*.
It's now time to go back to Grafana and try to get some charts from influxdb ! First Add a new InfluxDB *Data source*. Set `http://influxdb_db:8086` inside *URL* field and `k6weather` in *Database* field, then *Save & test*.
Now create a new dashboard, a new panel, and keep *Time series* as main graph system. Select above InfluxDB data source and switch to raw query expression. Finally, put following query `SELECT sum("value") FROM "http_reqs" WHERE $timeFilter GROUP BY time(1s)` in fields. Some graph should appear, select the right time interval where you have done previous load testing and voilà !
@ -409,11 +411,11 @@ export default function () {
This is a progressive 5 minutes load testing scenario from 1 user to 200 concurrent users.
Then use this script on above `k6` stack and be sure to comment `K6_VUS` and `K6_DURATION` environment variables. Check the logs to ensure that you have correct scenario :
Use this script on above `k6` stack and be sure to comment `K6_VUS` and `K6_DURATION` environment variables. Check the logs to ensure that you have correct scenario :
[![Portainer k6 logs](portainer-k6-logs.png)](portainer-k6-logs.png)
Check the raw and graph result in Grafana as previous scenario. Here the corresponding *Chart.js* result in my case for the 1st minute :
Then check the raw and graph result in Grafana. Here the corresponding *Chart.js* result in my case for the 1st minute :
{{< chart >}}
type: 'line',