Skip to content

Commit 97334ad

Browse files
authored
Merge pull request #406 from Paraphraser/20210917-mosquitto-master
20210917 Mosquitto - HTTP not HTTPS during build + health check - master branch - PR 1 of 3
2 parents c76ab29 + 3449637 commit 97334ad

File tree

3 files changed

+216
-21
lines changed

3 files changed

+216
-21
lines changed

.templates/mosquitto/Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Download base image
22
FROM eclipse-mosquitto:latest
33

4+
# see https://github.com/alpinelinux/docker-alpine/issues/98
5+
RUN sed -i 's/https/http/' /etc/apk/repositories
6+
47
# Add support tools
58
RUN apk update && apk add --no-cache rsync tzdata
69

@@ -10,6 +13,18 @@ ENV IOTSTACK_DEFAULTS_DIR="iotstack_defaults"
1013
# copy template files to image
1114
COPY --chown=mosquitto:mosquitto ${IOTSTACK_DEFAULTS_DIR} /${IOTSTACK_DEFAULTS_DIR}
1215

16+
# copy the health-check script into place
17+
ENV HEALTHCHECK_SCRIPT "iotstack_healthcheck.sh"
18+
COPY ${HEALTHCHECK_SCRIPT} /usr/local/bin/${HEALTHCHECK_SCRIPT}
19+
20+
# define the health check
21+
HEALTHCHECK \
22+
--start-period=30s \
23+
--interval=30s \
24+
--timeout=10s \
25+
--retries=3 \
26+
CMD ${HEALTHCHECK_SCRIPT} || exit 1
27+
1328
# replace the docker entry-point script
1429
ENV IOTSTACK_ENTRY_POINT="docker-entrypoint.sh"
1530
COPY ${IOTSTACK_ENTRY_POINT} /${IOTSTACK_ENTRY_POINT}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env sh
2+
3+
# assume the following environment variables, all of which may be null
4+
# HEALTHCHECK_PORT
5+
# HEALTHCHECK_USER
6+
# HEALTHCHECK_PASSWORD
7+
# HEALTHCHECK_TOPIC
8+
9+
# set a default for the port
10+
HEALTHCHECK_PORT="${HEALTHCHECK_PORT:-1883}"
11+
12+
# strip any quotes from username and password
13+
HEALTHCHECK_USER="$(eval echo $HEALTHCHECK_USER)"
14+
HEALTHCHECK_PASSWORD="$(eval echo $HEALTHCHECK_PASSWORD)"
15+
16+
# set a default for the topic
17+
HEALTHCHECK_TOPIC="${HEALTHCHECK_TOPIC:-iotstack/mosquitto/healthcheck}"
18+
HEALTHCHECK_TOPIC="$(eval echo $HEALTHCHECK_TOPIC)"
19+
20+
# record the current date and time for the test payload
21+
PUBLISH=$(date)
22+
23+
# publish a retained message containing the timestamp
24+
mosquitto_pub \
25+
-h localhost \
26+
-p "$HEALTHCHECK_PORT" \
27+
-t "$HEALTHCHECK_TOPIC" \
28+
-m "$PUBLISH" \
29+
-u "$HEALTHCHECK_USER" \
30+
-P "$HEALTHCHECK_PASSWORD" \
31+
-r
32+
33+
# did that succeed?
34+
if [ $? -eq 0 ] ; then
35+
36+
# yes! now, subscribe to that same topic with a 2-second timeout
37+
# plus returning on the first message
38+
SUBSCRIBE=$(mosquitto_sub \
39+
-h localhost \
40+
-p "$HEALTHCHECK_PORT" \
41+
-t "$HEALTHCHECK_TOPIC" \
42+
-u "$HEALTHCHECK_USER" \
43+
-P "$HEALTHCHECK_PASSWORD" \
44+
-W 2 \
45+
-C 1 \
46+
)
47+
48+
# did the subscribe succeed?
49+
if [ $? -eq 0 ] ; then
50+
51+
# yes! do the publish and subscribe payloads compare equal?
52+
if [ "$PUBLISH" = "$SUBSCRIBE" ] ; then
53+
54+
# yes! return success
55+
exit 0
56+
57+
fi
58+
59+
fi
60+
61+
fi
62+
63+
# otherwise, return failure
64+
exit 1

docs/Containers/Mosquitto.md

Lines changed: 137 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ Using `mosquitto.conf` as the example, assume you wish to use your existing file
179179
$ cd ~/IOTstack
180180
$ sudo mv ./services/mosquitto/mosquitto.conf ./volumes/mosquitto/config/mosquitto.conf
181181
```
182-
182+
183183
> The move overwrites the default. At this point, the moved file will probably be owned by user "pi" but that does not matter.
184-
184+
185185
2. Mosquitto will always enforce correct ownership (1883:1883) on any restart but it will not overwrite permissions. If in doubt, use mode 644 as your default for permissions:
186186

187187
```bash
@@ -193,7 +193,7 @@ Using `mosquitto.conf` as the example, assume you wish to use your existing file
193193
```bash
194194
$ docker-compose restart mosquitto
195195
```
196-
196+
197197
4. Check your work:
198198

199199
```bash
@@ -279,7 +279,7 @@ A common problem with the previous version of Mosquitto for IOTstack occurred wh
279279
The Mosquitto container performs self-repair each time the container is brought up or restarts. If `pwfile` is missing, an empty file is created as a placeholder. This prevents the restart loop. What happens next depends on `allow_anonymous`:
280280

281281
* If `true` then:
282-
282+
283283
- Any MQTT request *without* credentials will be permitted;
284284
- Any MQTT request *with* credentials will be rejected (because `pwfile` is empty so there is nothing to match on).
285285

@@ -292,13 +292,17 @@ To create a username and password, use the following as a template.
292292
```
293293
$ docker exec mosquitto mosquitto_passwd -b /mosquitto/pwfile/pwfile «username» «password»
294294
```
295-
295+
296296
Replace «username» and «password» with appropriate values, then execute the command. For example, to create the username "hello" with password "world":
297-
297+
298298
```
299299
$ docker exec mosquitto mosquitto_passwd -b /mosquitto/pwfile/pwfile hello world
300300
```
301-
301+
302+
Note:
303+
304+
* See also [customising health-check](#healthCheckCustom). If you are creating usernames and passwords, you may also want to create credentials for the health-check agent.
305+
302306
#### <a name="checkPasswordFile"> check password file </a>
303307

304308
There are two ways to verify that the password file exists and has the expected content:
@@ -308,7 +312,7 @@ There are two ways to verify that the password file exists and has the expected
308312
```bash
309313
$ sudo cat ~/IOTstack/volumes/mosquitto/pwfile/pwfile
310314
```
311-
315+
312316
> `sudo` is needed because the file is neither owned nor readable by `pi`.
313317

314318
2. View the file using its **internal** path:
@@ -342,24 +346,24 @@ There are several ways to reset the password file. Your options are:
342346
$ sudo rm ./volumes/mosquitto/pwfile/pwfile
343347
$ docker-compose restart mosquitto
344348
```
345-
349+
346350
The result is an empty password file.
347-
351+
348352
2. Clear all existing passwords while adding a new password:
349353

350354
```bash
351355
$ docker exec mosquitto mosquitto_passwd -c -b /mosquitto/pwfile/pwfile «username» «password»
352356
```
353-
357+
354358
The result is a password file with a single entry.
355-
359+
356360
3. Clear all existing passwords in favour of a single dummy password which is then removed:
357361

358362
```bash
359363
$ docker exec mosquitto mosquitto_passwd -c -b /mosquitto/pwfile/pwfile dummy dummy
360364
$ docker exec mosquitto mosquitto_passwd -D /mosquitto/pwfile/pwfile dummy
361365
```
362-
366+
363367
The result is an empty password file.
364368

365369
### <a name="activateSecurity"> Activate Mosquitto security </a>
@@ -375,7 +379,7 @@ There are several ways to reset the password file. Your options are:
375379
```
376380
#password_file /mosquitto/pwfile/pwfile
377381
```
378-
382+
379383
so that it becomes:
380384

381385
```
@@ -387,14 +391,14 @@ There are several ways to reset the password file. Your options are:
387391
```
388392
allow_anonymous true
389393
```
390-
394+
391395
If `true` then:
392-
396+
393397
* Any MQTT request without credentials will be permitted;
394398
* The validity of credentials supplied with any MQTT request will be enforced.
395399

396400
If `false` then:
397-
401+
398402
* Any MQTT request without credentials will be rejected;
399403
* The validity of credentials supplied with any MQTT request will be enforced.
400404

@@ -458,7 +462,7 @@ $ mosquitto_sub -v -h 127.0.0.1 -p 1883 -t "/password/test" -F "%I %t %p" -u hel
458462
```
459463

460464
Repeat the earlier test:
461-
465+
462466
```
463467
$ mosquitto_pub -h 127.0.0.1 -p 1883 -t "/password/test" -m "up up and away" -u hello -P world
464468
2021-02-16T14:40:51+1100 /password/test up up and away
@@ -467,7 +471,7 @@ $ mosquitto_pub -h 127.0.0.1 -p 1883 -t "/password/test" -m "up up and away" -u
467471
Note:
468472

469473
* the second line above is coming from the `mosquitto_sub` running in the background.
470-
474+
471475
When you have finished testing you can kill the background process (press return twice after you enter the `kill` command):
472476

473477
```
@@ -476,6 +480,118 @@ $
476480
[1]+ Terminated mosquitto_sub -v -h 127.0.0.1 -p 1883 -t "/password/test" -F "%I %t %p" -u hello -P world
477481
```
478482

483+
## <a name="healthCheck"> Container health check </a>
484+
485+
### <a name="healthCheckTheory"> theory of operation </a>
486+
487+
A script , or "agent", to assess the health of the Mosquitto container has been added to the *local image* via the *Dockerfile*. In other words, the script is specific to IOTstack.
488+
489+
The agent is invoked 30 seconds after the container starts, and every 30 seconds thereafter. The agent:
490+
491+
* Publishes a retained MQTT message to the broker running in the same container. The message payload is the current date and time, and the default topic string is:
492+
493+
```
494+
iotstack/mosquitto/healthcheck
495+
```
496+
497+
* Subscribes to the same broker for the same topic for a single message event.
498+
* Compares the payload sent with the payload received. If the payloads (ie time-stamps) match, the agent concludes that the Mosquitto broker (the process running inside the same container) is functioning properly for round-trip messaging.
499+
500+
### <a name="healthCheckMonitor"> monitoring health-check </a>
501+
502+
Portainer's *Containers* display contains a *Status* column which shows health-check results for all containers that support the feature.
503+
504+
You can also use the `docker ps` command to monitor health-check results. The following command narrows the focus to mosquitto:
505+
506+
```bash
507+
$ docker ps --format "table {{.Names}}\t{{.Status}}" --filter name=mosquitto
508+
```
509+
510+
Possible reply patterns are:
511+
512+
1. The container is starting and has not yet run the health-check agent:
513+
514+
```
515+
NAMES STATUS
516+
mosquitto Up 3 seconds (health: starting)
517+
```
518+
519+
2. The container has been running for at least 30 seconds and the health-check agent has returned a positive result within the last 30 seconds:
520+
521+
```
522+
NAMES STATUS
523+
mosquitto Up 34 seconds (healthy)
524+
```
525+
526+
3. The container has been running for more than 90 seconds but has failed the last three successive health-check tests:
527+
528+
```
529+
NAMES STATUS
530+
mosquitto Up About a minute (unhealthy)
531+
```
532+
533+
You can also subscribe to the same topic that the health-check agent is using to view the retained messages as they are published:
534+
535+
```bash
536+
$ mosquitto_sub -v -h localhost -p 1883 -t "iotstack/mosquitto/healthcheck" -F "%I %t %p"
537+
```
538+
539+
Notes:
540+
541+
* This assumes you are running the command *outside* container-space on the *same* host as your Mosquitto container. If you run this command from *another* host, replace `localhost` with the IP address or domain name of the host where your Mosquitto container is running.
542+
* The `-p 1883` is the *external* port. You will need to adjust this if you are using a different *external* port for your MQTT service.
543+
* If you enable authentication for your Mosquitto broker, you will need to add `-u «user»` and `-P «password»` parameters to this command.
544+
* You should expect to see a new message appear approximately every 30 seconds. That indicates the health-check agent is functioning normally. Use <kbd>control</kbd>+<kbd>c</kbd> to terminate the command.
545+
546+
### <a name="healthCheckCustom"> customising health-check </a>
547+
548+
You can customise the operation of the health-check agent by editing the `mosquitto` service definition in your *Compose* file:
549+
550+
1. By default, the mosquitto broker listens to **internal** port 1883. If you need change that port, you also need to inform the health-check agent via an environment variable. For example, suppose you changed the **internal** port to 12345:
551+
552+
```yaml
553+
environment:
554+
- HEALTHCHECK_PORT=12345
555+
```
556+
557+
2. If the default topic string used by the health-check agent causes a name-space collision, you can override it. For example, you could use a Universally-Unique Identifier (UUID):
558+
559+
```yaml
560+
environment:
561+
- HEALTHCHECK_TOPIC=4DAA361F-288C-45D5-9540-F1275BDCAF02
562+
```
563+
564+
Note:
565+
566+
* You will also need to use the same topic string in the `mosquitto_sub` command shown at [monitoring health-check](#healthCheckMonitor).
567+
568+
3. If you have enabled authentication for your Mosquitto broker service, you will need to provide appropriate credentials for your health-check agent:
569+
570+
```yaml
571+
environment:
572+
- HEALTHCHECK_USER=healthyUser
573+
- HEALTHCHECK_PASSWORD=healthyUserPassword
574+
```
575+
576+
4. If the health-check agent misbehaves in your environment, or if you simply don't want it to be active, you can disable all health-checking for the container by adding the following lines to its service definition:
577+
578+
```yaml
579+
healthcheck:
580+
disable: true
581+
```
582+
583+
Notes:
584+
585+
* The directives to disable health-checking are independent of the environment variables. If you want to disable health-checking temporarily, there is no need to remove any `HEALTHCHECK_` environment variables that may already be in place.
586+
* Conversely, the mere presence of a `healthcheck:` clause in the `mosquitto` service definition overrides the supplied agent. In other words, the following can't be used to re-enable the supplied agent:
587+
588+
```yaml
589+
healthcheck:
590+
disable: false
591+
```
592+
593+
You must remove the entire `healthcheck:` clause.
594+
479595
## <a name="upgradingMosquitto"> Upgrading Mosquitto </a>
480596

481597
You can update most containers like this:
@@ -549,7 +665,7 @@ If you need to pin Mosquitto to a particular version:
549665
```
550666

551667
The new *local image* is built, then the new container is instantiated based on that image. The `prune` deletes the old *local image*.
552-
668+
553669
Note:
554670

555671
* As well as preventing Docker from updating the *base image*, pinning will also block incoming updates to the *Dockerfile* from a `git pull`. Nothing will change until you decide to remove the pin.
@@ -585,7 +701,7 @@ If you have a use-case that needs port 9001, you can re-enable support by:
585701
listener 1883
586702
listener 9001
587703
```
588-
704+
589705
You need **both** lines. If you omit 1883 then Mosquitto will stop listening to port 1883 and will only listen to port 9001.
590706

591707
3. Restarting the container:

0 commit comments

Comments
 (0)