Consul 헬스체크 정의하기

Consul 헬스체크 정의하기

서비스를 카탈로그에 등록했다고 끝이 아니에요. '지금 이 서비스가 정말 정상인가?'를 계속 확인하는 것이 서비스 디스커버리의 품질을 좌우하죠. Consul의 헬스체크는 서비스나 노드의 상태를 주기적으로 검사해서, 트래픽을 보낼 수 있는 정상 인스턴스와 제외해야 할 인스턴스를 가려내는 설정이에요. 이번에는 헬스체크의 종류와 각각의 구성 방법을 하나씩 살펴볼게요.

헬스체크 구성은 service 블록 안에 중첩해서 정의해요. 서비스마다 개별 check 블록으로 하나씩 정의하거나, checks 블록으로 여러 개를 한 번에 정의할 수 있어요.

출처: Define health checks - HashiCorp Developer

본문

Consul에서 만들 수 있는 헬스체크 종류는 여러 가지예요.

  • Script 체크는 외부 애플리케이션을 실행해서 헬스체크를 수행하고, 적절한 종료 코드(exit code)로 종료되며 출력을 만들 수도 있어요. 가장 흔한 체크 타입 중 하나예요.
  • HTTP 체크는 지정한 URL로 HTTP GET 요청을 보내고 지정한 시간만큼 기다려요. 가장 흔한 체크 타입 중 하나예요.
  • TCP 체크는 IP나 호스트명·포트에 TCP로 연결을 시도하고 지정한 시간만큼 기다려요.
  • UDP 체크는 지정한 IP나 호스트명·포트로 UDP 데이터그램을 보내고 지정한 시간만큼 기다려요.
  • TTL(Time-to-live) 체크는 서비스가 상태를 갱신해 주길 기다리는 수동(passive) 체크예요. 지정한 시간 안에 상태 갱신을 받지 못하면 헬스체크가 critical 상태로 들어가요.
  • Docker 체크는 Docker 컨테이너에 패키징된 외부 애플리케이션에 의존하며, Docker exec API 엔드포인트 호출로 트리거돼요.
  • gRPC 체크는 표준 gRPC 헬스 체킹 프로토콜을 지원하는 애플리케이션을 프로브해요.
  • H2ping 체크는 http2를 사용하는 엔드포인트를 테스트해요. 체크는 엔드포인트에 연결하고 ping 프레임을 보내요.
  • Alias 체크는 다른 등록된 노드나 서비스의 헬스 상태를 나타내요.

Kubernetes 환경에서 운영 중이라면, Kubernetes 헬스체크와 서비스 헬스 정보를 동기화할 수도 있어요.

등록

헬스체크를 정의한 뒤에는 체크를 포함한 서비스를 반드시 Consul에 등록해야 해요. 서비스가 이미 등록되어 있다면, 서비스 구성 파일을 리로드(reload)해서 헬스체크를 적용할 수 있어요.

여러 개의 체크 정의하기

checks 블록에는 객체의 배열이 들어가요. 배열의 각 객체가 구현하려는 헬스체크 하나의 구성을 담아요. 다음 예시는 memcpu라는 두 개의 script 체크와 /health API 엔드포인트를 호출하는 HTTP 체크를 포함해요.

checks = [
  {
    id       = "chk1"
    name     = "mem"
    args     = ["/bin/check_mem", "-limit", "256MB"]
    interval = "5s"
  },
  {
    id       = "chk2"
    name     = "/health"
    http     = "http://localhost:5000/health"
    interval = "15s"
  },
  {
    id       = "chk3"
    name     = "cpu"
    args     = ["/bin/check_cpu"]
    interval = "10s"
  }
]
{
  "checks": [
    {
      "id": "chk1",
      "name": "mem",
      "args": ["/bin/check_mem", "-limit", "256MB"],
      "interval": "5s"
    },
    {
      "id": "chk2",
      "name": "/health",
      "http": "http://localhost:5000/health",
      "interval": "15s"
    },
    {
      "id": "chk3",
      "name": "cpu",
      "args": ["/bin/check_cpu"],
      "interval": "10s"
    }
  ]
}

초기 헬스체크 상태 정의하기

체크가 Consul 에이전트에 등록되면 기본적으로 critical 상태로 지정돼요. 이는 서비스가 헬스 검증 전에 passing으로 등록되어 서비스 풀에 들어가는 것을 막기 위한 안전장치예요. 체크 정의에 status 파라미터를 추가하면 초기 상태를 직접 지정할 수 있어요. 다음 예시에서는 체크가 passing 상태로 등록돼요.

check = {
  id = "mem"
  args = ["/bin/check_mem", "-limit", "256MB"]
  interval = "10s"
  status = "passing"
}
{
  "check": [
    {
      "args": [
        "/bin/check_mem",
        "-limit",
        "256MB"
      ],
      "id": "mem",
      "interval": "10s",
      "status": "passing"
    }
  ]
}

Script 체크

Script 체크는 외부 애플리케이션을 실행해서 헬스체크를 수행하고, 적절한 종료 코드로 종료되며 출력 데이터를 만들 수도 있어요. script 체크의 출력은 4KB로 제한되며, 이 한도를 넘는 출력은 잘려요.

script 체크는 기본적으로 30초 후 타임아웃되지만, 체크 정의의 timeout 필드로 커스텀 타임아웃을 지정할 수 있어요. Windows에서는 타임아웃에 도달하면 Consul이 스크립트가 생성한 자식 프로세스가 끝나기를 기다려요. 그 외 시스템에서는 타임아웃이 지나면 스크립트와 그 자식 프로세스를 강제 종료하려고 시도해요.

Script 체크 구성

script 체크를 활성화하려면 먼저 에이전트가 외부 요청을 보낼 수 있게 하고, 그다음 서비스 정의에서 헬스체크 설정을 구성해야 해요.

  1. 에이전트 구성 파일에 다음 중 하나를 추가해 script 체크를 활성화해요.

    • enable_local_script_checks: 로컬 구성 파일에 정의된 script 체크만 허용해요. HTTP API로 등록된 script 체크는 허용되지 않아요.
    • enable_script_checks: 등록 방식과 관계없이 script 체크를 허용해요.

    ⚠️ 보안 경고: 일부 구성에서 비로컬(non-local) script 체크를 활성화하면 악성코드가 노리는 알려진 원격 실행 취약점이 생길 수 있어요. enable_local_script_checks를 사용할 것을 강력히 권장해요.

  2. 서비스 구성 파일의 check 블록 args에 실행할 스크립트를 지정해요. 다음 예시에서 Memory utilization이라는 체크는 매 10초마다 check_mem.py 스크립트를 호출하고, 응답이 1초보다 오래 걸리면 타임아웃돼요.

    service {
      ## ...
      check = {
        id = "mem-util"
        name = "Memory utilization"
        args = ["/usr/local/bin/check_mem.py", "-limit", "256MB"]
        interval = "10s"
        timeout = "1s"
      }
    }
    
    {
      "service": [
      {
        "check": {
          "id": "mem-util",
          "name": "Memory utilization",
          "args": ["/usr/local/bin/check_mem.py", "-limit", "256MB"],
          "interval": "10s",
          "timeout": "1s"
          }
      }  ]
    }
    

Script 체크 종료 코드

script 체크가 돌려주는 종료 코드에 따라 헬스 상태가 결정돼요.

  • 종료 코드 0 - 체크가 passing
  • 종료 코드 1 - 체크가 warning
  • 그 외 코드 - 체크가 failing

스크립트의 출력은 캡처되어 HTTP API 응답에 포함된 체크의 Output 필드에서 확인할 수 있어요.

HTTP 체크

HTTP 체크는 지정한 URL에 HTTP 요청을 보내고, HTTP 응답 코드에 따라 서비스 헬스를 보고해요. cURL이나 다른 외부 프로세스로 HTTP 동작을 확인하는 script 체크보다는 HTTP 체크를 권장해요.

HTTP 체크 구성

서비스 정의 파일의 check 블록에 http 필드를 추가하고, 호출할 HTTP 주소와 포트 번호를 지정해요. 그 외 필드는 모두 선택 사항이에요. 다음 예시에서 HTTP API on port 5000이라는 HTTP 체크는 매 10초마다 health 엔드포인트로 POST 요청을 보내요.

check = {
  id = "api"
  name = "HTTP API on port 5000"
  http = "https://localhost:5000/health"
  tls_server_name =  ""
  tls_skip_verify = false
  method = "POST"
  header = {
     Content-Type = ["application/json"]
  }
  body = "{\"method\":\"health\"}"
  disable_redirects = true
  interval = "10s"
  timeout = "1s"
}
{
  "check": {
    "id": "api",
    "name": "HTTP API on port 5000",
    "http": "https://localhost:5000/health",
    "tls_server_name": "",
    "tls_skip_verify": false,
    "method": "POST",
    "header": { "Content-Type": ["application/json"] },
    "body": "{\"method\":\"health\"}",
    "interval": "10s",
    "timeout": "1s"
  }
}

HTTP 체크는 기본적으로 GET 요청을 보내지만, method 필드에서 다른 요청 메서드를 지정할 수 있어요. header 블록으로 추가 헤더를 보낼 수도 있는데, header 블록은 키와 문자열 배열을 담아요(예: {"x-foo": ["bar", "baz"]}). HTTP 체크는 기본적으로 10초 후 타임아웃되며, timeout 필드에서 커스텀 값을 지정할 수 있어요.

HTTP 체크는 기본적으로 유효한 TLS 인증서를 기대해요. tls_skip_verify 필드를 true로 설정하면 인증서 검증을 끌 수 있어요. TLS를 사용할 때 http 필드에 호스트 이름이 지정되면 체크가 URL에서 SNI를 자동으로 결정해요. http 필드가 IP 주소로 구성되었거나 SNI를 명시적으로 설정하고 싶다면 tls_server_name 필드에 이름을 지정해요.

체크는 기본적으로 네트워크에 구성된 HTTP 리다이렉트를 따라가요. disable_redirects 필드를 true로 설정하면 리다이렉트를 비활성화할 수 있어요.

HTTP 체크 응답 코드

4KB보다 큰 응답은 잘려요. HTTP 응답에 따라 서비스 상태가 결정돼요.

  • 200299 응답 코드는 정상(healthy)
  • 429(요청이 너무 많음) 응답 코드는 경고(warning)
  • 그 외 모든 응답 코드는 실패(failure)

TCP 체크

TCP 체크는 지정한 IP나 호스트에 연결을 설정해요. 연결 설정에 성공하면 서비스 상태를 success로 보고하고, IP나 호스트가 연결을 수락하지 않으면 critical로 보고해요. netcat이나 다른 외부 프로세스로 소켓 동작을 확인하는 script 체크보다 TCP 체크를 권장해요.

TCP 체크 구성

서비스 정의 파일의 check 블록에 tcp 필드를 추가하고, 호출할 주소와 포트 번호를 지정해요. 다음 예시에서 SSH TCP on port 22라는 TCP 체크는 매 10초마다 localhost:22에 연결을 시도해요.

check = {
  id = "ssh"
  name = "SSH TCP on port 22"
  tcp = "localhost:22"
  interval = "10s"
  timeout = "1s"
}
{
  "check": {
    "id": "ssh",
    "name": "SSH TCP on port 22",
    "tcp": "localhost:22",
    "interval": "10s",
    "timeout": "1s"
  }
}

호스트 이름이 IPv4와 IPv6 주소로 모두 해석되면 Consul은 두 주소에 모두 연결을 시도하고, 첫 번째 성공한 연결 시도가 체크 성공으로 이어져요. TCP 체크 요청은 기본적으로 10초 후 타임아웃되며, timeout 필드에서 커스텀 값을 지정할 수 있어요.

UDP 체크

UDP 체크는 Consul 에이전트가 지정한 IP나 호스트명·포트로 UDP 데이터그램을 보내도록 해요. 대상 UDP 서버로부터 어떤 응답이라도 받으면 체크 상태가 success가 되고, 그 외 결과는 critical이 돼요.

UDP 체크 구성

서비스 정의 파일의 check 블록에 udp 필드를 추가하고, 데이터그램을 보낼 주소와 포트 번호를 지정해요. 다음 예시에서 DNS UDP on port 53이라는 UDP 체크는 매 10초마다 localhost:53으로 데이터그램을 보내요.

check = {
  id = "dns"
  name = "DNS UDP on port 53"
  udp = "localhost:53"
  interval = "10s"
  timeout = "1s"
}
{
  "check": {
    "id": "dns",
    "name": "DNS UDP on port 53",
    "udp": "localhost:53",
    "interval": "10s",
    "timeout": "1s"
  }
}

UDP 체크는 기본적으로 10초 후 타임아웃되며, timeout 필드에서 커스텀 값을 지정할 수 있어요. 읽기(read) 타임아웃이 있더라도 체크는 여전히 정상으로 간주돼요.

OSService 체크

OSService 체크는 호스트에서 OS 서비스가 실행 중인지 확인해요. Windows 호스트에서는 Windows 서비스, Unix 호스트에서는 SystemD 서비스를 지원해요. 서비스가 실행 중이면 체크는 healthy로 기록하고, 실행 중이 아니면 critical로 기록해요. 그 외 결과는 warning으로 기록되는데, warning 상태는 서비스 헬스를 판단하지 못하게 하는 문제가 있어 체크가 신뢰할 수 없다는 뜻이에요.

OSService 체크 구성

서비스 정의 파일의 check 블록에 os_service 필드를 추가하고, 확인할 서비스 이름을 지정해요. 다음 예시에서 svcname-001 Windows Service Health라는 OSService 체크는 매 10초마다 myco-svctype-svcname-001 서비스가 실행 중인지 확인해요.

check = {
  id = "myco-svctype-svcname-001"
  name = "svcname-001 Windows Service Health"
  service_id = "flash_pnl_1"
  os_service = "myco-svctype-svcname-001"
  interval = "10s"
}
{
  "check": {
    "id": "myco-svctype-svcname-001",
    "name": "svcname-001 Windows Service Health",
    "service_id": "flash_pnl_1",
    "os_service": "myco-svctype-svcname-001",
    "interval": "10s"
  }
}

TTL 체크

TTL(Time-to-live) 체크는 외부 프로세스가 서비스 상태를 Consul의 /agent/check HTTP 엔드포인트에 보고해 주기를 기다려요. 지정한 ttl 시간 안에 갱신을 받지 못하면 체크는 서비스를 critical로 기록해요. 예를 들어 정상 애플리케이션이 주기적으로 HTTP 엔드포인트에 상태 갱신 PUT 요청을 보내도록 구성되어 있다면, TTL이 만료되기 전에 애플리케이션이 갱신을 보내지 못했을 때 헬스체크가 critical 상태를 기록해요.

TTL 체크는 마지막으로 알려진 상태를 디스크에 영속해서, Consul 에이전트가 재시작 이후에도 체크의 마지막 상태를 복원할 수 있어요. 영속된 체크 상태는 마지막 체크 시점부터 TTL이 끝날 때까지 유효해요.

ttl 시간이 길다면, TTL 헬스체크를 기다리지 않고 consul maint CLI 명령이나 agent/maintenance HTTP API 엔드포인트로 서비스를 수동으로 비정상(unhealthy) 상태로 표시할 수도 있어요.

TTL 체크 구성

서비스 정의 파일의 check 블록에 ttl 필드를 추가하고, 외부 프로세스의 갱신을 얼마나 기다릴지 지정해요. 다음 예시에서 Web App Status라는 TTL 체크는 30초마다 상태 갱신을 받지 못하면 애플리케이션을 critical로 기록해요.

check = {
  id = "web-app"
  name = "Web App Status"
  notes = "Web app does a curl internally every 10 seconds"
  ttl = "30s"
}
{
  "check": {
    "id": "web-app",
    "name": "Web App Status",
    "notes": "Web app does a curl internally every 10 seconds",
    "ttl": "30s"
  }
}

Docker 체크

Docker 체크는 Docker 컨테이너에 패키징된 애플리케이션을 호출해요. 애플리케이션은 헬스체크를 수행하고 적절한 종료 코드로 종료되어야 해요.

애플리케이션은 실행 중인 컨테이너 안에서 Docker exec API를 통해 트리거돼요. Docker HTTP API 또는 Unix 소켓에 접근할 수 있어야 하며, Consul은 $DOCKER_HOST 환경 변수로 Docker API 엔드포인트를 결정해요. Docker 체크의 출력은 4KB로 제한되며, 더 큰 출력은 잘려요.

Docker 체크 구성

Docker 체크를 활성화하려면 먼저 에이전트가 외부 요청을 보낼 수 있게 하고, 그다음 서비스 정의에서 헬스체크 설정을 구성해야 해요.

  1. 에이전트 구성 파일에 다음 중 하나를 추가해 Docker 체크를 활성화해요.

    • enable_local_script_checks: 로컬 구성 파일에 정의된 script 체크만 허용해요. HTTP API로 등록된 script 체크는 허용되지 않아요.
    • enable_script_checks: 등록 방식과 관계없이 script 체크를 허용해요.

    ⚠️ 보안 경고: 일부 구성에서 비로컬 script 체크를 활성화하면 악성코드가 노리는 알려진 원격 실행 취약점이 생길 수 있어요. enable_local_script_checks를 사용할 것을 강력히 권장해요.

  2. 서비스 정의 파일의 check 블록에서 다음 필드를 구성해요.

    • docker_container_id: docker ps 명령이 ID를 얻는 흔한 방법이에요.
    • shell: 체크를 수행하는 데 사용할 셸을 지정해요. 같은 호스트에서 컨테이너마다 다른 셸을 실행할 수 있어요.
    • args: 호출할 외부 애플리케이션을 지정해요.
    • interval: 체크를 실행할 간격을 지정해요.

다음 예시에서 Memory utilization이라는 Docker 체크는 매 10초마다 컨테이너 f972c95ebf0e 안의 check_mem.py 애플리케이션을 호출해요.

check = {
  id = "mem-util"
  name = "Memory utilization"
  docker_container_id = "f972c95ebf0e"
  shell = "/bin/bash"
  args = ["/usr/local/bin/check_mem.py"]
  interval = "10s"
}
{
  "check": {
    "id": "mem-util",
    "name": "Memory utilization",
    "docker_container_id": "f972c95ebf0e",
    "shell": "/bin/bash",
    "args": ["/usr/local/bin/check_mem.py"],
    "interval": "10s"
  }
}

gRPC 체크

gRPC 체크는 지정한 엔드포인트에 요청을 보내요. 이 체크는 표준 gRPC 헬스 체킹 프로토콜을 지원하는 애플리케이션을 대상으로 해요.

gRPC 체크 구성

서비스 정의 파일의 check 블록에 grpc 필드를 추가하고, 요청을 보낼 엔드포인트와 포트 번호를 지정해요. 다음 예시에서 Service health status라는 gRPC 체크는 매 10초마다 127.0.0.1:12345로 요청을 보내 애플리케이션 전체를 프로브해요.

check = {
  id = "mem-util"
  name = "Service health status"
  grpc = "127.0.0.1:12345"
  grpc_use_tls = true
  interval = "10s"
}
{
  "check": {
    "id": "mem-util",
    "name": "Service health status",
    "grpc": "127.0.0.1:12345",
    "grpc_use_tls": true,
    "interval": "10s"
  }
}

gRPC 체크는 gRPC 서버 전체를 프로브하지만, gRPC 체크의 엔드포인트 뒤에 서비스 식별자를 /:service_identifier 형식으로 추가하면 특정 서비스만 확인할 수 있어요. 다음 예시의 gRPC 체크는 매 10초마다 127.0.0.1:12345의 애플리케이션에서 my_service를 프로브해요.

check = {
  id = "mem-util"
  name = "Service health status"
  grpc = "127.0.0.1:12345/my_service"
  grpc_use_tls = true
  interval = "10s"
}
{
  "check": {
    "id": "mem-util",
    "name": "Service health status",
    "grpc": "127.0.0.1:12345/my_service",
    "grpc_use_tls": true,
    "interval": "10s"
  }
}

gRPC 체크는 기본적으로 TLS가 비활성화돼 있어요. grpc_use_tlstrue로 설정하면 TLS를 활성화할 수 있는데, 이때 유효한 TLS 인증서를 제공하거나 tls_skip_verify 필드를 true로 설정해 인증서 검증을 꺼야 해요. gRPC 체크는 기본적으로 10초 후 타임아웃되며, timeout 필드에서 커스텀 값을 지정할 수 있어요.

H2ping 체크

H2ping 체크는 HTTP2를 사용하는 엔드포인트를 테스트해요. 엔드포인트에 연결하고 ping 프레임을 보내서, 지정한 간격 안에 응답이 오면 체크 상태가 success로 설정돼요.

H2ping 체크 구성

서비스 정의 파일의 check 블록에 h2ping 필드를 추가하고, ping을 보낼 HTTP2 엔드포인트와 포트 번호를 지정해요. 다음 예시에서 h2ping이라는 H2ping 체크는 매 10초마다 localhost:22222 엔드포인트에 ping을 보내요.

check = {
  id = "h2ping-check"
  name = "h2ping"
  h2ping = "localhost:22222"
  interval = "10s"
  h2ping_use_tls = false
}
{
  "check": {
    "id": "h2ping-check",
    "name": "h2ping",
    "h2ping": "localhost:22222",
    "interval": "10s",
    "h2ping_use_tls": false
  }
}

TLS는 기본적으로 활성화되어 있어요. h2ping_use_tlsfalse로 설정하면 TLS를 끌 수 있고, TLS가 꺼지면 Consul은 h2c로 ping을 보내요. TLS가 활성화된 경우 tls_skip_verifytrue로 설정되지 않았다면 유효한 인증서가 필요해요. H2ping 체크는 기본적으로 10초 후 타임아웃되며, timeout 필드에서 커스텀 값을 지정할 수 있어요.

Alias 체크

Alias 체크는 다른 등록된 노드나 서비스의 헬스 상태를 지속해서 보고해요. alias가 실제 노드나 서비스를 감시하는 동안 오류가 발생하면 체크는 critical 상태를 보고해요. Consul은 alias와 실제 노드·서비스의 상태를 비동기적으로, 거의 즉시 갱신해요.

같은 에이전트에 있는 별칭 서비스의 경우 체크는 추가 네트워크 자원을 소모하지 않고 로컬 상태를 감시해요. 다른 에이전트에 있는 서비스·노드의 경우 체크는 현재 서버와의 에이전트 연결을 유지한 채 blocking query를 사용하고 stale 요청을 허용해요.

ACL

blocking query를 위해 alias 체크는 실제 서비스에 설정된 ACL 토큰 또는 체크 정의에 구성된 토큰을 제시해요. 둘 다 없으면 alias 체크는 에이전트에 설정된 기본 ACL 토큰으로 폴백해요.

Alias 체크 구성

서비스 정의 파일의 check 블록에 alias_service 필드를 추가하고, alias할 서비스나 노드의 이름을 지정해요. 다음 예시에서 ID가 web-alias인 alias 체크는 web 서비스의 헬스 상태를 보고해요.

check = {
  id = "web-alias"
  alias_service = "web"
}
{
  "check": {
    "id": "web-alias",
    "alias_service": "web"
  }
}

기본적으로 alias는 alias 체크와 같은 Consul 에이전트에 등록되어야 해요. 서비스가 같은 에이전트에 등록되어 있지 않다면 check 구성에 "alias_node": "<node_id>"를 지정해야 해요. 서비스를 지정하지 않고 alias_node 필드를 활성화하면 체크는 그 노드의 헬스를 alias해요. 서비스를 지정하면 체크는 해당 노드의 지정된 서비스를 alias해요.

헬스체크 등록하기

로컬 Consul 에이전트에 헬스체크를 동적으로 등록하려면 /agent/check/register API 엔드포인트로 PUT 요청을 보내요. 다음 예시 요청은 payload.json 파일에 정의된 헬스체크를 등록해요.

$ curl --request PUT --data @payload.json http://localhost:8500/v1/agent/check/register

더 알아보기