ASP.NET CORE在docker中的健康檢查(healthcheck)

在使用docker-compose的過程中,很多程式都提供了健康檢查(healthcheck)的方法,通過健康檢查,應用程式能夠在確保其依賴的程式都已經啟動的前提下啟動,減少各種錯誤的發生,同時,合理設計的健康檢查也能夠提供給外界關於應用程式狀態的一些資訊。

大多數docker鏡像的詳細說明中,會交代如何進行健康檢查,.NET 6也提供了文檔。也有其他人寫了文章說了更細節的內容。

實踐一下,現在docker-compose裡面是這樣:

version: '3.7'
services:
  postgres:
    container_name: postgres-container
    image: postgres:13
    volumes:
      - ./postgres_data:/var/lib/postgresql/data/pgdata
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: "123456"
      PGDATA: "/var/lib/postgresql/data/pgdata"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5  
    networks: 
      - dotnet-network
  
  dotnet-service:
    image: dotnet-service:latest
    container_name: dotnet-service-container
    ports:
      - 5000:5000
    build:
      context: ./TestWeb/
      dockerfile: ./TestWeb/Dockerfile
    environment:
      # - ASPNETCORE_ENVIRONMENT=Docker
      - ASPNETCORE_URLS=//*:5000
      - App__SelfUrl=//*:5000
    restart: on-failure 
    depends_on:
      postgres:
        condition: service_healthy
    networks: 
      - dotnet-network

networks:
  dotnet-network:
    external: false

首先調整一下程式:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/healthz");

app.Run();

如果直接運行的話,web訪問/healthz節點,那麼可以頁面將直接返回Healthy。看上去沒有問題,那我們按照要求調整一下Dockerfile

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
HEALTHCHECK CMD curl --fail //localhost:5000/healthz || exit
ENTRYPOINT ["dotnet", "TestWeb.dll"]

然後調整docker-compose.yml文件,讓postgres啟動依賴dotnet-service:

version: '3.7'
services:
  postgres:
    container_name: postgres-container
    image: postgres:13
    volumes:
      - ./postgres_data:/var/lib/postgresql/data/pgdata
    ports:
      - "5432:5432"
    environment:
      POSTGRES_PASSWORD: "123456"
      PGDATA: "/var/lib/postgresql/data/pgdata"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5  
    depends_on:
      dotnet-service:
        condition: service_healthy
    networks: 
      - dotnet-network
  
  dotnet-service:
    image: dotnet-service:latest
    container_name: dotnet-service-container
    ports:
      - 5000:5000
    build:
      context: ./TestWeb/
      dockerfile: ./TestWeb/Dockerfile
    environment:
      # - ASPNETCORE_ENVIRONMENT=Docker
      - ASPNETCORE_URLS=//*:5000
      - App__SelfUrl=//*:5000
    restart: on-failure 
    networks: 
      - dotnet-network

networks:
  dotnet-network:
    external: false

然後postgres就啟動不了了,提示容器unhealthy。運行docker ps,發現.net容器的後面有一個unhealthy。但是程式訪問正常,說明只是沒有正確運行這段話而已。

Dockerfile方案

文檔說.NET 6的鏡像裡面不帶curl這個工具了,需要手動裝一下:

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
RUN apt-get update && apt-get install -y curl
HEALTHCHECK CMD curl --fail //localhost:5000/healthz || exit
ENTRYPOINT ["dotnet", "TestWeb.dll"]

這樣再看,就完全沒問題了,如果把aspnet:6.0改成aspnet:6.0-alpine,空間更節省了(默認是239M,alpine版是108M),還自帶了wget,也沒必要這麼麻煩先安裝curl(比較費時間按)。

HEALTHCHECK CMD wget --spider //localhost:5000/healthz || exit

docker-compose方案

我不太喜歡去改Dockerfile,而是更傾向於修改docker-compose.yml文件。當然,沒有curl依然還是得在Dockerfile中添加相應語句安裝,但是可以刪除HEALTHCHECK這一條了,直接在docker-compose.yml中對應服務下面添加:

healthcheck:
    test: ["CMD-SHELL", "wget --spider //localhost:5000/healthz || exit"]
    interval: 10s
    timeout: 5s
    retries: 5  

程式依然可以正常運行。

P.S. 如果程式沒有使用AddHealthChecks,那麼也可以使用暴力一點的,直接用HEALTHCHECK CMD curl --fail //localhost:5000 || exit,只要能夠正常訪問頁面,就認為是Healthy,不過肯定就少了很多高級特性了。