Skip to content

Commit

Permalink
[minor] add Dockerfile healthchecks to some core services (#370)
Browse files Browse the repository at this point in the history
* Create HEALTHCHECK entries for core services

* Ensure secret is set for cantaloupe API access

* Add test to make sure healthcheck works

Allow fpm status from ipv6 localhost

* Add more tests

* update README

* Update README.md

* Healthchecks review (#375)

* Replace legacy cont-init.d for Cantaloupe service

Changes the cantaloupe service init such that it respects service
dependencies.

This insures that the precedence for container environment follows what
is outlined in the container-environment service.

* Test compose projects require names.

* Bump github action upload artifact

* Add names to docker compose tests

---------

Co-authored-by: Nigel Banks <nigelgbanks@users.noreply.github.com>
  • Loading branch information
joecorall and nigelgbanks authored Feb 6, 2025
1 parent c30e521 commit 6818439
Show file tree
Hide file tree
Showing 33 changed files with 482 additions and 7 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/grype.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ jobs:
with:
cache-read-only: false
arguments: ${{ inputs.image }}:grype ${{ inputs.digest }} --info
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: Grype Reports
name: ${{ inputs.image }} Grype Reports
path: build/**/*-grype.*
6 changes: 6 additions & 0 deletions activemq/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ COPY --link rootfs /

RUN create-service-user.sh --name activemq && \
cleanup.sh

HEALTHCHECK CMD curl -s \
-u admin:"$(cat /var/run/s6/container_environment/ACTIVEMQ_WEB_ADMIN_PASSWORD)" \
-H origin:localhost \
"http://localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost,service=Health/CurrentStatus" \
| jq .value | grep -q Good
54 changes: 54 additions & 0 deletions activemq/tests/ServiceHealthcheck/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import plugins.TestsPlugin.DockerComposeUp
import plugins.TestsPlugin.DockerComposeUp.Companion.pool
import java.lang.Thread.sleep
import java.time.Duration.ofSeconds
import java.util.concurrent.CompletableFuture.supplyAsync
import java.io.ByteArrayOutputStream

tasks.named<DockerComposeUp>("test") {
doFirst {
supplyAsync(
{
val maxAttempts = 10
val delayBetweenAttempts = 5000L // 5 seconds in milliseconds
var attempt = 0
var foundHealthyService = false

while (attempt < maxAttempts) {
attempt++
val outputStream = ByteArrayOutputStream()
project.exec {
commandLine = baseArguments + listOf("ps", "--all")
standardOutput = outputStream
workingDir = project.projectDir
}
val output = outputStream.toString()

val healthyServicePattern = """(?m)^.+\s+.+\s+Up \d+ seconds \(healthy\).*$""".toRegex()
foundHealthyService = output.lines().any { line ->
healthyServicePattern.matches(line)
}

if (foundHealthyService) {
project.exec {
commandLine = baseArguments + listOf("stop")
standardOutput = outputStream
workingDir = project.projectDir
}
break
}

if (attempt < maxAttempts) {
println("No healthy service found. Retrying in ${delayBetweenAttempts / 1000} seconds...")
sleep(delayBetweenAttempts)
}
}

// Throw an exception if no healthy service was found after all attempts
if (!foundHealthyService) {
throw GradleException("No service is marked as healthy in docker compose ps output after $maxAttempts attempts.")
}
}, pool
)
}
}
15 changes: 15 additions & 0 deletions activemq/tests/ServiceHealthcheck/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---

# Common to all services
x-common: &common
restart: "no"

name: activemq-servicehealthcheck
services:
activemq:
<<: *common
image: ${ACTIVEMQ:-islandora/activemq:local}
volumes:
- ./test.sh:/test.sh # Test to run.
command:
- /test.sh # Run test and exit.
13 changes: 13 additions & 0 deletions activemq/tests/ServiceHealthcheck/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/command/with-contenv bash
# shellcheck shell=bash

on_terminate() {
echo "Termination signal received. Exiting..."
exit 0
}
trap 'on_terminate' SIGTERM

sleep 60

# The kotlin check should be stopping this container
exit 1
8 changes: 6 additions & 2 deletions cantaloupe/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ ENV \
CANTALOUPE_ENDPOINT_ADMIN_ENABLED="false" \
CANTALOUPE_ENDPOINT_ADMIN_SECRET= \
CANTALOUPE_ENDPOINT_ADMIN_USERNAME="admin" \
CANTALOUPE_ENDPOINT_API_ENABLED="false" \
CANTALOUPE_ENDPOINT_API_ENABLED="true" \
CANTALOUPE_ENDPOINT_API_SECRET= \
CANTALOUPE_ENDPOINT_API_USERNAME= \
CANTALOUPE_ENDPOINT_API_USERNAME=islandora \
CANTALOUPE_ENDPOINT_HEALTH_DEPENDENCY_CHECK="false" \
CANTALOUPE_ENDPOINT_IIIF_1_ENABLED="false" \
CANTALOUPE_ENDPOINT_IIIF_2_ENABLED="true" \
Expand Down Expand Up @@ -271,3 +271,7 @@ ENV \
CANTALOUPE_TEMP_PATHNAME=

COPY --link rootfs /

HEALTHCHECK CMD curl -s \
-u "${CANTALOUPE_ENDPOINT_API_USERNAME}:$(cat /var/run/s6/container_environment/CANTALOUPE_ENDPOINT_API_SECRET)" \
http://localhost:8182/health | jq .color | grep -q GREEN
6 changes: 3 additions & 3 deletions cantaloupe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ additional settings, volumes, ports, etc.
| CANTALOUPE_ENDPOINT_ADMIN_ENABLED | "false" |
| CANTALOUPE_ENDPOINT_ADMIN_SECRET | |
| CANTALOUPE_ENDPOINT_ADMIN_USERNAME | "admin" |
| CANTALOUPE_ENDPOINT_API_ENABLED | "false" |
| CANTALOUPE_ENDPOINT_API_SECRET | |
| CANTALOUPE_ENDPOINT_API_USERNAME | |
| CANTALOUPE_ENDPOINT_API_ENABLED | "true" |
| CANTALOUPE_ENDPOINT_API_SECRET | random 16 char string |
| CANTALOUPE_ENDPOINT_API_USERNAME | "islandora" |
| CANTALOUPE_ENDPOINT_HEALTH_DEPENDENCY_CHECK | "false" |
| CANTALOUPE_ENDPOINT_IIIF_1_ENABLED | "false" |
| CANTALOUPE_ENDPOINT_IIIF_2_ENABLED | "true" |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oneshot
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/etc/s6-overlay/scripts/cantaloupe-defaults.sh
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/command/with-contenv bash
# shellcheck shell=bash
set -ex

# Set the default value for CANTALOUPE_ENDPOINT_API_SECRET if none provided.
DEFAULT_SECRET=$(openssl rand -hex 16)
cat <<EOF | /usr/local/bin/confd-import-environment.sh
CANTALOUPE_ENDPOINT_API_SECRET="{{ getenv "CANTALOUPE_ENDPOINT_API_SECRET" "${DEFAULT_SECRET}" }}"
EOF
54 changes: 54 additions & 0 deletions cantaloupe/tests/ServiceHealthcheck/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import plugins.TestsPlugin.DockerComposeUp
import plugins.TestsPlugin.DockerComposeUp.Companion.pool
import java.lang.Thread.sleep
import java.time.Duration.ofSeconds
import java.util.concurrent.CompletableFuture.supplyAsync
import java.io.ByteArrayOutputStream

tasks.named<DockerComposeUp>("test") {
doFirst {
supplyAsync(
{
val maxAttempts = 10
val delayBetweenAttempts = 5000L // 5 seconds in milliseconds
var attempt = 0
var foundHealthyService = false

while (attempt < maxAttempts) {
attempt++
val outputStream = ByteArrayOutputStream()
project.exec {
commandLine = baseArguments + listOf("ps", "--all")
standardOutput = outputStream
workingDir = project.projectDir
}
val output = outputStream.toString()

val healthyServicePattern = """(?m)^.+\s+.+\s+Up \d+ seconds \(healthy\).*$""".toRegex()
foundHealthyService = output.lines().any { line ->
healthyServicePattern.matches(line)
}

if (foundHealthyService) {
project.exec {
commandLine = baseArguments + listOf("stop")
standardOutput = outputStream
workingDir = project.projectDir
}
break
}

if (attempt < maxAttempts) {
println("No healthy service found. Retrying in ${delayBetweenAttempts / 1000} seconds...")
sleep(delayBetweenAttempts)
}
}

// Throw an exception if no healthy service was found after all attempts
if (!foundHealthyService) {
throw GradleException("No service is marked as healthy in docker compose ps output after $maxAttempts attempts.")
}
}, pool
)
}
}
15 changes: 15 additions & 0 deletions cantaloupe/tests/ServiceHealthcheck/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---

# Common to all services
x-common: &common
restart: "no"

name: cantaloupe-servicehealthcheck
services:
cantaloupe:
<<: *common
image: ${CANTALOUPE:-islandora/cantaloupe:local}
volumes:
- ./test.sh:/test.sh # Test to run.
command:
- /test.sh # Run test and exit.
13 changes: 13 additions & 0 deletions cantaloupe/tests/ServiceHealthcheck/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/command/with-contenv bash
# shellcheck shell=bash

on_terminate() {
echo "Termination signal received. Exiting..."
exit 0
}
trap 'on_terminate' SIGTERM

sleep 60

# The kotlin check should be stopping this container
exit 1
55 changes: 55 additions & 0 deletions ci/healthcheck.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import plugins.TestsPlugin.DockerComposeUp
import plugins.TestsPlugin.DockerComposeUp.Companion.pool
import java.lang.Thread.sleep
import java.time.Duration.ofSeconds
import java.util.concurrent.CompletableFuture.supplyAsync
import java.io.ByteArrayOutputStream

tasks.named<DockerComposeUp>("test") {
doFirst {
supplyAsync(
{
val maxAttempts = 10
val delayBetweenAttempts = 5000L // 5 seconds in milliseconds
var attempt = 0
var foundHealthyService = false

while (attempt < maxAttempts) {
attempt++
val outputStream = ByteArrayOutputStream()
project.exec {
commandLine = baseArguments + listOf("ps", "--all")
standardOutput = outputStream
workingDir = project.projectDir
}
val output = outputStream.toString()

val healthyServicePattern = """(?m)^.+\s+.+\s+Up \d+ seconds \(healthy\).*$""".toRegex()
foundHealthyService = output.lines().any { line ->
healthyServicePattern.matches(line)
}

if (foundHealthyService) {
println("Service is healthy. Exiting test...")
project.exec {
commandLine = baseArguments + listOf("stop")
standardOutput = outputStream
workingDir = project.projectDir
}
break
}

if (attempt < maxAttempts) {
println("No healthy service found. Retrying in ${delayBetweenAttempts / 1000} seconds...")
sleep(delayBetweenAttempts)
}
}

// Throw an exception if no healthy service was found after all attempts
if (!foundHealthyService) {
throw GradleException("No service is marked as healthy in docker compose ps output after $maxAttempts attempts.")
}
}, pool
)
}
}
2 changes: 2 additions & 0 deletions fcrepo6/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@ ENV \
COPY --link rootfs /

RUN chown -R tomcat:tomcat /opt/tomcat

HEALTHCHECK CMD curl -s http://localhost:8080/fcrepo/rest/fcr:systeminfo | jq -e .version
54 changes: 54 additions & 0 deletions fcrepo6/tests/ServiceHealthcheck/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import plugins.TestsPlugin.DockerComposeUp
import plugins.TestsPlugin.DockerComposeUp.Companion.pool
import java.lang.Thread.sleep
import java.time.Duration.ofSeconds
import java.util.concurrent.CompletableFuture.supplyAsync
import java.io.ByteArrayOutputStream

tasks.named<DockerComposeUp>("test") {
doFirst {
supplyAsync(
{
val maxAttempts = 10
val delayBetweenAttempts = 5000L // 5 seconds in milliseconds
var attempt = 0
var foundHealthyService = false

while (attempt < maxAttempts) {
attempt++
val outputStream = ByteArrayOutputStream()
project.exec {
commandLine = baseArguments + listOf("ps", "--all")
standardOutput = outputStream
workingDir = project.projectDir
}
val output = outputStream.toString()

val healthyServicePattern = """(?m)^.+\s+.+\s+Up \d+ seconds \(healthy\).*$""".toRegex()
foundHealthyService = output.lines().any { line ->
healthyServicePattern.matches(line)
}

if (foundHealthyService) {
project.exec {
commandLine = baseArguments + listOf("stop")
standardOutput = outputStream
workingDir = project.projectDir
}
break
}

if (attempt < maxAttempts) {
println("No healthy service found. Retrying in ${delayBetweenAttempts / 1000} seconds...")
sleep(delayBetweenAttempts)
}
}

// Throw an exception if no healthy service was found after all attempts
if (!foundHealthyService) {
throw GradleException("No service is marked as healthy in docker compose ps output after $maxAttempts attempts.")
}
}, pool
)
}
}
19 changes: 19 additions & 0 deletions fcrepo6/tests/ServiceHealthcheck/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---

# Common to all services
x-common: &common
restart: "no"

name: fcrepo6-servicehealthcheck
services:
activemq:
<<: *common
image: ${ACTIVEMQ:-islandora/activemq:local}
fcrepo6:
volumes:
- ./test.sh:/test.sh # Test to run.
command:
- /test.sh # Run test and exit.
image: ${FCREPO6:-islandora/fcrepo6:local}
depends_on:
- activemq
13 changes: 13 additions & 0 deletions fcrepo6/tests/ServiceHealthcheck/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/command/with-contenv bash
# shellcheck shell=bash

on_terminate() {
echo "Termination signal received. Exiting..."
exit 0
}
trap 'on_terminate' SIGTERM

sleep 60

# The kotlin check should be stopping this container
exit 1
2 changes: 2 additions & 0 deletions mariadb/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ ENV \
MYSQL_TRANSACTION_ISOLATION=READ-COMMITTED

COPY --link rootfs /

HEALTHCHECK CMD mysqladmin ping --socket=/var/run/mysqld/mysqld.sock | grep -q alive
1 change: 1 addition & 0 deletions mariadb/tests/ServiceHealthcheck/build.gradle.kts
Loading

0 comments on commit 6818439

Please sign in to comment.