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. 상황분석
우선 내가 구성한 아키텍처는 다음 다이어그램으로 표현할 수 있겠다.
체크해본 사항들은 다음과 같다.
- 로컬에서 curl https://localhost:8443 으로 요청 보내보기 : 200 OK
- docker network inspect my_network로 컨테이너들이 같은 네트워크상에 있는지 확인: OK
- -> $ docker network inspect my_network
- haproxy의 guide 문서를 보고 haproxy 문법이 맞는지 체크해보기: OK
- https://www.haproxy.com/documentation/haproxy-configuration-tutorials/service-reliability/health-checks/
- keycloak의 guide 문서 및 googling을 통해 잘못된 부분이 없는지 체크해보기: OK
- https://www.keycloak.org/server/health
- -> keycloak은 9000번 management interface용 포트를 통해 health check를 한다고 한다. : OK
- https://keycloak.discourse.group/t/health-check-endpoint-return-404-in-version-25-0-0/26474/4
- haproxy container에서 직접 curl로 쏴보기 : FAIL
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 명령어를 적용하였다.
참고로 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가 매핑돼야한다.
'Programming-[Infra] > Docker' 카테고리의 다른 글
[TIL]Connection error..Max Retries... localhost로 호출 -> docker로 호출시 host (0) | 2023.06.20 |
---|---|
도커 교과서(엘튼 스톤맨, 심효섭) - 20. 비동기 통신, 마무리 (0) | 2023.05.23 |
도커 교과서(엘튼 스톤맨, 심효섭) - 18. 리버스 프록시-1: nginx, 로드밸런싱, 라우팅과 SSL, Traefik (0) | 2023.05.21 |
도커 교과서(엘튼 스톤맨, 심효섭) - 17. 로그 설정: fluentd, elasticsearch, kibana (0) | 2023.05.20 |
도커 교과서(엘튼 스톤맨, 심효섭) - 16. 이미지 최적화, 환경 변수 설정 관리 (0) | 2023.05.14 |