home server 를 1개 더 추가하게 됐는데 추가한 가장 큰 두가지 이유는
1. 가용성을 향상시키기 위함
2. 리소스 자체가 부족함
기존엔 모든 서비스가 단일 장애지점 이었습니다. 사실 개발을 하면서도 문제라는 걸 알고 있었고, 개선을 하고싶지만 지속적인 비용이 투자되어야 하는 문제라 고민이 됐지만, 이번에 서비스 목표를 설정하고 이를 달성 하기 위해 서버 증설을 진행 하기로 마음 먹었습니다.
제가 단일 host 로 구성되어 docker-compose 로 배포하던 방식을 docker swarm 으로 전환 한 경험 입니다.
기존엔 Docker network 의 사용자 정의 bridge 방식으로 통신하고 있었습니다.
그림으로 표현하면 대략 이런그림이고 Traefik 이라는 webserver 를 ingress controller 로 사용하고 Spring Cloud Gateway 로 요청을 전달, Eureka 를 통해 Service Discovery 가 이루어지는 구조 입니다. 따라서 각 service 들은 외부에서 직접적으로 요청을 받을필요가 없어 port 를 노출할 필요가 없습니다.
여러개의 replica 가 생성될 수 있다는 점, 각 application 들의 포트를 고려할 필요가 없다 라고 생각한다면 이부분은 좋았던 점 이라고 생각 해요.
하지만 문제는 network 방식이 서버 증설을 할때 문제였습니다.
요약하자면 A machine 과 B machine 이 있다고 했을때 기존의 방식으론 A와 B의 유연한 통신이 제한된다.
제가 찾아본 솔루션은 다음과 같습니다.
1. Kubernetes 전환
2. Docker swarm 전환 (network overlay 로 수정)
결론적으론 Docker swarm 을 선택했는데요 그 이유는 기존의 docker-compose.yml 파일의 수정을 최소한으로 할 수 있다는점과 러닝커브가 제일 컸습니다.
물론 kubernetes 로 전환이 더욱 좋지만 장점인 auto-scailing 이 불필요하고 여러가지의 add-on에 대한 러닝커브가 프로덕트 관점으로 봤을때 필요한 기능에 비해 개발속도를 지연시킨다고 판단 했습니다.
stack, service, task 와 같은 swarm 아키텍처를 다루진 않습니다
개념적으론 제가 경험해봤던 kubernetes 와 매우 유사하다고 느꼈습니다. 크게 Manager, Worker 노드들이 있고 등록해주는 과정이 필요합니다.
manager node 생성
docker swarm init
worker node
docker swarm join ~
가입 확인
docker node ls
root@pve:~# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
3ag5b7p1m1vqsvs9jhopceffh my Ready Active 27.2.1
yeprklaua13ywnt70migw27ee * pve Ready Active Leader 27.5.1
전환 하며 큰 문제는 없었지만 자잘한 문제들은 있었습니다.
bind source path does not exist 문제
모든 배포과정은 Github action 과 runner 를 사용하고 있었는데, manager node 를 통해서만 배포가 되다 보니 machine 간 config 들을 공유해야 할때 문제가 발생했고 별도의 config 를 생성해서 해결했어요.
제약조건 설정 (Only Manager)
Traefik, Prometheus 와 같은 application은 Manager Node 에서만 실행되어야 해요. 사용하려는 이미지의 공식문서를 참고 하길 바랍니다.
prometheus scrap 방식 docker swarm service discovery
{
"Name": "internal-network",
"Id": "s6hby6rrclqg20j8ks6wxoqtt",
"Created": "2025-01-26T04:03:06.582910074+09:00",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.2.0/24",
"Gateway": "10.0.2.1"
}
]
},
"Internal": false,
"Attachable": true,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"1cdea0f8f29e7951e14d3a56263e6a06747d71394e8fae23e865600a755bd845": {
"Name": "dev_kafka1.1.l5j2n7tqzoozx47l7igo9j428",
"EndpointID": "a17cb29b58c16d832f3165374bb1bfa0e30248271ed709025ddc0a364c7326c8",
"MacAddress": "02:42:0a:00:02:99",
"IPv4Address": "10.0.2.153/24",
"IPv6Address": ""
},
"1faf5c671997b8069f775ee1b89775b8eed929a0d81876a3423af91482699ab6": {
"Name": "dev_prometheus.1.cbl0xp2knd91bq0illyexh6rq",
"EndpointID": "4a4195960aaaa554581a8597cce44b1628f295646e0f250173b1adfb4dea547c",
"MacAddress": "02:42:0a:00:02:8c",
"IPv4Address": "10.0.2.140/24",
"IPv6Address": ""
},
"2c7855cd27640b482e7b195f34dc695374b220a6fa37eaaf3d6fc76e0e0931cc": {
"Name": "dev_find-my-pet-backend.1.5mrzh55qv8h2kmci1zwuje1os",
"EndpointID": "0915825a45b83b8761685346073b5552fa57683a0ce16fb35438d762634cbe06",
"MacAddress": "02:42:0a:00:02:dc",
"IPv4Address": "10.0.2.220/24",
"IPv6Address": ""
},
"371eea414a3e8e08182ad21b9256e9689a77e0b52c81db170494c1fe1c6ffcc7": {
"Name": "dev_orchestrator.1.bjz1mdrr0npluzvjyah1adb6w",
"EndpointID": "feb641a9422d0ea5e4c1dc12df159051c9f29b9ef24d80f7a08ba9559481a64e",
"MacAddress": "02:42:0a:00:02:d8",
"IPv4Address": "10.0.2.216/24",
"IPv6Address": ""
},
"3d320834e1a6f2d9a9d2722875a4c60793aeb5a733016b3577a5524f5d10984b": {
"Name": "dev_reverse-proxy.1.36mdvgqq5x6ms5uzkk7zmala0",
"EndpointID": "11bb79abb6a6220b5ffea217e767b40864b4a1c6df7d063417ee17656ad02fc7",
"MacAddress": "02:42:0a:00:02:51",
"IPv4Address": "10.0.2.81/24",
"IPv6Address": ""
},
"52beedbdbf19c1062a4a93132eecf13223f6d8891058dc5440b0caf5194bee1f": {
"Name": "dev_node_exporter.yeprklaua13ywnt70migw27ee.nysydaoa1l41xsfmncswewrbr",
"EndpointID": "ea79a733a1afa8b8eeed89b0dce4e32678d8ae6aafa77d5958bbd7762415e21d",
"MacAddress": "02:42:0a:00:02:83",
"IPv4Address": "10.0.2.131/24",
"IPv6Address": ""
},
"54ec3ae873043808a1fec082e668de1a9254ba7a3a384a22e5733d3095328804": {
"Name": "dev_redis.1.pr37j2qlpifr8xo9tdal7cfdy",
"EndpointID": "ae1fc99a6ac5c499d5cdc67992625933d9053dd5865ff464d61b2a88c43b4ed6",
"MacAddress": "02:42:0a:00:02:03",
"IPv4Address": "10.0.2.3/24",
"IPv6Address": ""
},
"81a300dbf61ff441c00a9f94e97672edb830c05cc416d4100dc2c66c1c4db075": {
"Name": "dev_auth-server.1.s4f0rur25okr5qco2cf91ypxy",
"EndpointID": "00f36381b013bb3796b3b7310137f2e37723be7e543640a8c4fa6e9606c5b164",
"MacAddress": "02:42:0a:00:02:da",
"IPv4Address": "10.0.2.218/24",
"IPv6Address": ""
},
"a9b4302d7c8e0703fdb650202e09a02090392f141dbe8d643924d17a662a7bf9": {
"Name": "dev_gateway.1.lj0lr2hs1xw8vqc3yyw726gde",
"EndpointID": "262bb8688cf6ecd18594ad05639ee2dfe9ed4c46cdd3ae688a192c0240a0d421",
"MacAddress": "02:42:0a:00:02:d6",
"IPv4Address": "10.0.2.214/24",
"IPv6Address": ""
},
"lb-internal-network": {
"Name": "internal-network-endpoint",
"EndpointID": "9827138b5890ebba2b0ec92975ef5644cb42b76510ea5ad01b7b4a261110be7b",
"MacAddress": "02:42:0a:00:02:04",
"IPv4Address": "10.0.2.4/24",
"IPv6Address": ""
}
},
현재 사용중인 ipv4 의 ip는 다음과 같습니다. 10.0.2.x 대역을 사용하고 있으며, 약 254 개정도 사용 가능 할 것이라 생각되는데 현재 서비스 규모를 생각해보면 매우 여유있는 수치라고 판단했어요. 서비스가 더욱 성장해 IP 문제가 발생하길 바라며 Type A 방식으로 Instance 를 구분 했어요.
version: '3.8'
services:
auth-server:
image: 'wy9295/auth-server:69285ae12c11ad0e7ee4aeb713338b3d072e4c78'
deploy:
replicas: 1
restart_policy:
condition: on-failure
placement:
constraints:
- "node.hostname == pve"
labels:
- "prometheus.enable=true"
- "prometheus.scrape=true"
- "prometheus.path=/actuator/prometheus"
- "prometheus.port=8080"
networks:
- internal-network
- auth-server
- barbellrobot-backend
environment:
- REDIS_PORT=6379
- REDIS_HOST=redis
- REDIS_PASSWORD=${REDIS_PASSWORD}
- RDBMS_URL=barbell-robot-mysql
- RDBMS_USERNAME=${RDBMS_USERNAME}
- RDBMS_PASSWORD=${RDBMS_PASSWORD}
- ACCESS_SECRET_KEY=${ACCESS_SECRET_KEY}
- ACCESS_EXPIRE_MILLIS=${ACCESS_EXPIRE_MILLIS}
- REFRESH_SECRET_KEY=${REFRESH_SECRET_KEY}
- REFRESH_EXPIRE_MILLIS=${REFRESH_EXPIRE_MILLIS}
- EUREKA_URI=http://eureka:8761/eureka
- PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040
... other services
networks:
barbellrobot-backend:
driver: overlay
name: barbellrobot-backend
attachable: true
find-my-pet-backend:
driver: overlay
name: find-my-pet-backend
attachable: true
internal-network:
driver: overlay
name: internal-network
attachable: true
auth-server:
driver: overlay
attachable: true
name: auth-server
gateway:
driver: overlay
name: gateway
attachable: true
volumes:
google-key:
Docker Swarm 후기
완벽하진 않지만 서비스 구성을 마치는데 거의 하루정도 걸린걸 보면 기존의 단일 machine 위에서 docker-compose 로 배포하던것과 큰 차이가 없었고 러닝커브 또한 높지 않다고 생각합니다.
따라서 단일 환경에서 분산환경으로 빠른 전환이 필요하고 Docker 를 사용하고 있다면 좋은 솔루션 이라고 생각합니다.
machine 별로 성능이 다르다보니 사용하는 리소스에 따라 node 구성을 하고자 했었는데 접근을 잘못 했다고 생각이 들었습니다.
다음에 해야할 것은?
서버가 이중화 됐지만 제일 중요한 database 는 개선되지 않았습니다.
다음은 DB 이중화 전략과 이중과 개선작업을 진행 하고자 합니다.