Tim Jones 3 дней назад
Родитель
Сommit
1d0cbd5c0e

+ 3 - 2
README.md

@@ -53,9 +53,10 @@ services:
       - rackpeek-config:/app/config
     restart: unless-stopped
     healthcheck:
-      test: ["CMD-SHELL", "curl -s http://localhost:8080 | grep -q 'rackpeek' || exit 1"]
+      test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
       interval: 30s
-      timeout: 10s
+      timeout: 5s
+      start_period: 15s
       retries: 3
 
 volumes:

+ 9 - 0
RackPeek.Web/Dockerfile

@@ -42,6 +42,12 @@ WORKDIR /app
 
 USER root
 
+# Install curl for the container HEALTHCHECK probe (the aspnet runtime image
+# ships without curl/wget, so without this the HEALTHCHECK below cannot run).
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends curl \
+    && rm -rf /var/lib/apt/lists/*
+
 # Create shared config directory safely
 RUN mkdir -p /app/config \
     && chown -R ${APP_UID}:0 /app/config \
@@ -68,4 +74,7 @@ ENV RPK_YAML_DIR=/app/config
 # Drop privileges
 USER ${APP_UID}
 
+HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
+    CMD curl -fsS http://localhost:8080/health || exit 1
+
 ENTRYPOINT ["dotnet", "RackPeek.Web.dll"]

+ 3 - 2
Shared.Rcl/wwwroot/raw_docs/install-guide.md

@@ -34,9 +34,10 @@ services:
       - rackpeek-config:/app/config
     restart: unless-stopped
     healthcheck:
-      test: ["CMD-SHELL", "curl -s http://localhost:8080 | grep -q 'rackpeek' || exit 1"]
+      test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
       interval: 30s
-      timeout: 10s
+      timeout: 5s
+      start_period: 15s
       retries: 3
 
 volumes:

+ 49 - 0
Tests.E2e/DockerHealthcheckTests.cs

@@ -0,0 +1,49 @@
+using DotNet.Testcontainers.Builders;
+using DotNet.Testcontainers.Containers;
+using Xunit.Abstractions;
+
+namespace Tests.E2e;
+
+public class DockerHealthcheckTests(ITestOutputHelper output) {
+    private const string _dockerImage = "rackpeek:ci";
+    private static readonly TimeSpan _healthDeadline = TimeSpan.FromSeconds(60);
+
+    [Fact]
+    public async Task Container_Reports_Healthy_Status_Via_Docker_HEALTHCHECK() {
+        await using IContainer container = new ContainerBuilder(_dockerImage)
+            .WithPortBinding(8080, true)
+            .WithWaitStrategy(
+                Wait.ForUnixContainer()
+                    .UntilContainerIsHealthy())
+            .Build();
+
+        using var cts = new CancellationTokenSource(_healthDeadline);
+
+        try {
+            // StartAsync blocks until the wait strategy succeeds. If the image
+            // has no HEALTHCHECK instruction Docker reports `health: none` forever,
+            // so we bound the wait — the cancellation token forces a fast failure
+            // rather than letting the test hang.
+            await container.StartAsync(cts.Token);
+
+            Assert.Equal(TestcontainersStates.Running, container.State);
+            Assert.Equal(TestcontainersHealthStatus.Healthy, container.Health);
+        }
+        catch (Exception) {
+            output.WriteLine($"Container did not reach 'Healthy' within {_healthDeadline.TotalSeconds:F0}s.");
+            output.WriteLine($"Container state:  {container.State}");
+            output.WriteLine($"Container health: {container.Health}");
+            try {
+                (string Stdout, string Stderr) logs = await container.GetLogsAsync();
+                output.WriteLine("==== CONTAINER STDOUT ====");
+                output.WriteLine(logs.Stdout);
+                output.WriteLine("==== CONTAINER STDERR ====");
+                output.WriteLine(logs.Stderr);
+            }
+            catch (Exception logEx) {
+                output.WriteLine($"Failed to capture container logs: {logEx.Message}");
+            }
+            throw;
+        }
+    }
+}