본문 바로가기
관리자

Programming-[Infra]/Docker

Docker network: haproxy Layer4 connection problem, info: "Connection refused" Problem

728x90
반응형

 

1. 문제 상황

 

docker container를 통해 keycloak server를 3개 실행하고, HAproxy를 통해 load balancing을 할려고 했다. 설정 파일인 haproxy.cfg는 아래와 같이 설정했다. 설정 내용에 중요한 부분이나 내가 겪었던 문제가 되는 부분들을 주석으로 넣어두었다.

 

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    log         127.0.0.1 local2

	# 처음에는 /var/... 주소로 설정되어있었는데 haproxy container가 뜨질 않아서 그냥 /tmp 쪽에다 
    # .pid 파일을 만들었다. 실제 container에서 파일을 보면 그냥 1만 적혀있다.
    pidfile     /tmp/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats
    
    # 보안 설정하는 부분 같은데, 로컬 테스트상에서는 중요하지 않아서 일단 주석처리했다.
    # utilize system-wide crypto-policies
    # ssl-default-bind-ciphers PROFILE=SYSTEM
    # ssl-default-server-ciphers PROFILE=SYSTEM

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
	
    # mode는 http로 되어있어야 http로 healthcheck을 한다. tcp로 하면 안된다.
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

frontend mykeycloak
    # Copy the haproxy.crt.pem file to /etc/haproxy
    bind *:443 ssl crt /etc/haproxy/haproxy.crt.pem
    default_backend keycloak

backend keycloak
    stats enable
    stats uri /haproxy?status
    option httpchk GET /
    option forwardfor
    http-request add-header X-Forwarded-Proto https
    http-request add-header X-Forwarded-Port 443
    http-request redirect scheme https unless { ssl_fc }
    cookie KC_ROUTE insert indirect nocache
    balance roundrobin
    
    # 서버 목록
    # verify none을 통해 ssl을 위한 certicate이 인증되지 않아도 무시한다.
    server kc1 127.0.0.1:9000 check ssl verify none cookie kc1
    server kc2 127.0.0.1:9000 check ssl verify none cookie kc2
    server kc3 127.0.0.1:9000 check ssl verify none cookie kc3

 

그럼 이렇게 모든 서버가 DOWN 상태라면서 에러가 난다.

 

haproxy status check 페이지에서 봐도 모두 DOWN 상태이다.

 

 

 

2. 상황분석

 

우선 내가 구성한 아키텍처는 다음 다이어그램으로 표현할 수 있겠다.

 

체크해본 사항들은 다음과 같다.

 

haproxy container 내부에서 curl로 요청을 쏘기 위해서, haproxy를 띄워놓고 아래 명령어들을 입력했다.

$ docker exec -u root -it {container ID} /bin/bash
$ apt-get update
$ apt-get install -y curl
$ curl 127.0.0.1:8443    # FAIL

 

haproxy container 내부에서 fail이 발생하니 당연히 health check에 실패한다.

 

 

 

3. 해결방법

 

docker container 간 통신을 위해서는 다음 조건들을 만족하고 있는지 체크해야한다.

  • container들이 같은 네트워크상에 있다.
  • container간 요청을 보낼 때 docker 내부 IP를 통해 통신한다.
  • container간 요청을 보낼 때 docker 내부 port로 요청한다.

만약 nginx container를 8082:80 으로 매핑했다면, docker 내부에서는 {container IP}:80 번으로 요청해야하는 것이다.

 

 

위 사항에 따라 haproxy.cfg 파일을 아래와 같이 변경해주었다. container들의 네트워크 주소는 아래 명령어들을 통해 알 수 있다.

$ docker network inspect {network 이름}
or
$ docker inspect {container 이름}

 

 

backend keycloak
    stats enable
    stats uri /haproxy?status
    option httpchk
    http-check connect ssl alpn h2
    http-check send meth GET uri /health ver HTTP/2
    http-request add-header X-Forwarded-Proto https
    http-request add-header X-Forwarded-Port 443
    http-request redirect scheme https unless { ssl_fc }
    cookie KC_ROUTE insert indirect nocache
    balance roundrobin
    server kc1 {kc1 instance의 docker 내부 ip}:8443 ssl check port 9000 verify none cookie kc1
    server kc2 {kc2 instance의 docker 내부 ip}:8443 ssl check port 9000 verify none cookie kc2
    server kc3 {kc3 instance의 docker 내부 ip}:8443 ssl check port 9000 verify none cookie kc3

 

9000번 포트인 이유는 상기 언급한대로 keycloak의 healthcheck 포트가 9000번이기 때문이다. container별로 health check 내부 포트로 잘 매핑해야한다. 또한 connect ssl 구문을 통해 https 요청을 보내도록 한 점을 참고한다(내부망이라면 굳이 https로 보낼 필요는 없을 것 같긴하다. 서버 설정상 https로 하다보니 적용한 것이다). 그리고 haproxy의 문서를 참고하여 http-check 명령어를 적용하였다.

https://www.haproxy.com/documentation/haproxy-configuration-tutorials/service-reliability/health-checks/

 

참고로 haproxy container를 실행한 명령어는 다음과 같다. {my_network} 부분은 keycloak 인스턴스들을 띄울 때의 네트워크 이름으로 수정해야한다.

$ docker run -d \
  -v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro \
  -v $(pwd)/haproxy.crt.pem:/etc/haproxy/haproxy.crt.pem:ro \
  -p 443:443 \
  --network {my_network} \
  haproxy

 

 

참고로 haproxy.crt.pem 파일은 다음과 같다.

 

KeyCIoak - 모던 애플리케이션을 위한 ID 및 접근관리 | 에이콘출판(주) | 스티안 토르거센, 페드로 이고르 실바 지음, 최만균 옮김

 

에서 제공한 파일이고 자체서명 파일일 뿐이다.

-----BEGIN CERTIFICATE-----
MIID0zCCArugAwIBAgIUEhaz7pf/ievSFyNu2CCKPUEPUj8wDQYJKoZIhvcNAQEL
BQAweTELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG
A1UECgwKbXlrZXljbG9hazETMBEGA1UECwwKbXlrZXljbG9hazETMBEGA1UEAwwK
bXlrZXljbG9hazERMA8GCSqGSIb3DQEJARYCWFgwHhcNMjEwNDA5MTIwNDA4WhcN
MzEwNDA3MTIwNDA4WjB5MQswCQYDVQQGEwJYWDELMAkGA1UECAwCWFgxCzAJBgNV
BAcMAlhYMRMwEQYDVQQKDApteWtleWNsb2FrMRMwEQYDVQQLDApteWtleWNsb2Fr
MRMwEQYDVQQDDApteWtleWNsb2FrMREwDwYJKoZIhvcNAQkBFgJYWDCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBANYtVSmtEqyfpuvswOwvn912gTXCmPvX
SH8YT86nc07t4sg10qG2dijYlmocQdbD6IM9IFrWTs+WCwxSYh+ScvxKe2GMDJGh
j90CEZiAaCS0M0bJM2kEZCR1JOyrPktsNpxuHZgrd4JWIag/LmAQy0WHjgdwMJEx
2RTa0rjOlYk+bQhGJsc5ZQY+dSmCln2XdjHFK5QIvPvVwPum0J0H4Fme4BWntXTx
cUZ4ix1zLBmOic8DUxd3oVZZfNUuR48SBKATXRE/y1oZpLeFRMl7UeGhqTb/Ayhd
dkZb+TWx/+5HsHipKAGk7mbzqLyeBtpOgMf/cWbwn3G3sOf4W6u0p70CAwEAAaNT
MFEwHQYDVR0OBBYEFD3712jGcnZ6dRvWA+04yknH2za8MB8GA1UdIwQYMBaAFD37
12jGcnZ6dRvWA+04yknH2za8MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAD46NzLzxe9DXNz0Y0jP8aYcb48CLBuZDwZuXEQZGMlDRvJv4SrZ1E7K
kPcR3TkSyncePU14x0KPQQY8n/mp/UitMDM68iy6teXkN77hbe3OT3fCeCHj84hR
ukIvGpH9gJvvNaiRUvS2at6xXT/zry2O+VN2atviO8i2MB7lvReFvPdoDwBs3uaF
mB6ytdfLqSEeUrV/JCGkAfu0I00AwAWX/qcJ+LnSglBY4kPxslsEP1Gk8yVlDZwm
1xlnnB88Yux6Ge8oXbCNOfO/9IOYcksEXGsOdMG5ACBTl17ygEyL6IgeORX7OJET
RnHZNU3X7KgXY5f7ZR8RjYnry1ITtMw=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDWLVUprRKsn6br
7MDsL5/ddoE1wpj710h/GE/Op3NO7eLINdKhtnYo2JZqHEHWw+iDPSBa1k7PlgsM
UmIfknL8SnthjAyRoY/dAhGYgGgktDNGyTNpBGQkdSTsqz5LbDacbh2YK3eCViGo
Py5gEMtFh44HcDCRMdkU2tK4zpWJPm0IRibHOWUGPnUpgpZ9l3YxxSuUCLz71cD7
ptCdB+BZnuAVp7V08XFGeIsdcywZjonPA1MXd6FWWXzVLkePEgSgE10RP8taGaS3
hUTJe1Hhoak2/wMoXXZGW/k1sf/uR7B4qSgBpO5m86i8ngbaToDH/3Fm8J9xt7Dn
+FurtKe9AgMBAAECggEAb1QtGna+aECssaHlPmAbBzEcROecQfxL0NTAVzkvdO2u
nkdr72c60EyVEx8REiPPbriNYupXGQxzPbptCuBDKOVGcRQtTF3gvA7hOpY0jC+n
H7piIMqJi3Hg+ayhuu7LDFEozPp7KqK+6Ae/gWv5XXDy4ObuN+rjXjXIpwurKyT1
7NDKnxpzrCk1zXfvV2F48tOEfqaSxUUU7OjuEIxPYH3lDa8v1AO1DLxHwhhv9auD
wFzOkEeS7w/QITgYbul1m2OHZXI4vTaPgocAlHnKbUYCGeKJpkwvSpJN86TiSgFV
TLBlIUWfaqv8BWkV0Y+5dIM5oWAcy39COoEVEnte+QKBgQD6Louzd8h63Ei9UxuU
dn1T2afAyNjSvTwkee8HIuU8rH94SxTCdz5zy0O0e+vlLX06izPJbLDPYYpGNmeJ
M/HZXpc3nporKY1gJfvhi0Gyvjamr8oMyEXnvA50SKvdMSTwJL1ROXKN3JXp52+7
QN2v3NucHNcbcvXMtssGciaShwKBgQDbKG7mjvac1O40HNF4zBMVMOYogv3iuxtU
rhn/mk+bz0pg7QkCv4KOf8AZczDH75LPNpSVozc3v1A0MVQo/6+jWfHWaiSf9+vl
ZIEF2k4hXLzhjczTUefAJb4ShUSdTIV8e9t5Gs8hlD7DneDiJYFi2shYu/daGG14
zppQ6iKQmwKBgBZ+AMlNx5RkIZYD1sLuNC5Jry9B31xy7ulInRjDJmDiEUO7XE5v
cgnvCFM9oOOlx1BwG2PMhcjfOBM/6OcI6IFmY6n4dFvVDITMZWzNnEZ5m2g4/a1Q
hBhla9dAgVMNjAibBPo8c/QVFNVGnWD0X/njnUrXvO8W0spo1K/rq1QZAoGAez9p
3s9XcStuKnBqfPyHXst5JB9GmFORMzYV+ODXFFCnC4tCHgGFco31glp9fHMGpPGU
7fI0A23btP5ozgW8yKi0kFhw8GWEjCTRSnFSrwBwWIheQBk3s5+GHPRFehCmoTMm
Yhzpj4DHK0uGRKfC880GDqDmogxKxD2sGwURGzECgYAiAP6/ROjqAmL04Fy6Xa1u
3VF/kvLVbo/4W8Y6t3tLhpBh76YtPezVhqbIElmgrJK1i57d9bBoO8q/tszNVrbf
K+/FEA0uhtWoeDI4sktX683Sz1AGpr7ieBqjuHLJElQsKTCCce+1vRZDz1Bb/fL9
D+fAJjBaZzb8QtiFCwyK1A==
-----END PRIVATE KEY-----

 

 

 

이후 정상적으로 health check가 잘되는 것을 확인할 수 있었다. 그리고 haproxy의 443 포트, 즉 https://localhost로 여러 번 요청을 보내면 kc1, kc2로 load distribution이 되는 것도 볼 수 있다.

 

 

다시 정리하자면, 아래와 같은 그림으로 network, ip, port가 매핑돼야한다.

728x90
반응형