스웜 서비스 네트워크 관리하기
스웜 서비스 네트워크 관리하기
이 페이지에서는 Docker 스웜 서비스에서 사용하는 네트워킹에 대해 설명드릴게요. 스웜 모드에서 네트워크가 어떻게 동작하는지, 어떤 종류의 트래픽이 존재하는지, 그리고 오버레이 네트워크를 만들고 커스터마이즈하는 방법까지 차근차근 알아보겠습니다. 실제 운영 환경에서 네트워크를 구성할 때 꼭 필요한 내용이니 잘 따라와 주세요!
출처: 공식문서
본문
스웜과 트래픽의 종류
Docker 스웜에서는 두 가지 서로 다른 종류의 트래픽이 발생합니다:
- 컨트롤 및 관리 플레인 트래픽(Control and management plane traffic): 스웜 관리 메시지, 예를 들어 스웜에 참여하거나 떠나는 요청 같은 것들이 여기에 해당해요. 이 트래픽은 항상 암호화됩니다.
- 애플리케이션 데이터 플레인 트래픽(Application data plane traffic): 컨테이너 간 트래픽과 외부 클라이언트와 주고받는 트래픽을 포함합니다.
핵심 네트워크 개념
스웜 서비스를 이해하려면 다음 세 가지 네트워크 개념이 아주 중요해요.
-
오버레이 네트워크(Overlay networks): 스웜에 참여하는 Docker 데몬들 간의 통신을 관리합니다. standalone 컨테이너를 위한 사용자 정의 네트워크를 만드는 것과 같은 방식으로 오버레이 네트워크를 만들 수 있어요. 서비스를 기존 오버레이 네트워크 하나 이상에 연결해서 서비스 간 통신을 가능하게 할 수도 있습니다. 오버레이 네트워크는
overlay네트워크 드라이버를 사용하는 Docker 네트워크입니다. -
인그레스 네트워크(Ingress network): 서비스의 노드들 간에 로드 밸런싱을 수행하는 특별한 오버레이 네트워크예요. 스웜 노드가 published port로 요청을 받으면, 그 요청을
IPVS라는 모듈에 넘깁니다.IPVS는 해당 서비스에 참여하는 모든 IP 주소를 추적하고 있다가 그중 하나를 선택해서ingress네트워크를 통해 요청을 라우팅해요.ingress네트워크는 스웜을 초기화하거나 참여할 때 자동으로 생성됩니다. 대부분의 사용자는 그 구성을 커스터마이즈할 필요가 없지만, Docker는 필요하다면 커스터마이즈할 수 있게 해줍니다. -
docker_gwbridge: 오버레이 네트워크(
ingress네트워크 포함)를 개별 Docker 데몬의 물리적 네트워크에 연결해 주는 브리지 네트워크입니다. 기본적으로 서비스가 실행되는 각 컨테이너는 로컬 Docker 데몬 호스트의docker_gwbridge네트워크에 연결돼요.docker_gwbridge네트워크도 스웜을 초기화하거나 참여할 때 자동으로 생성됩니다. 역시 대부분의 사용자는 커스터마이즈할 필요가 없지만, Docker는 커스터마이즈를 허용합니다.
팁
스웜 네트워킹 전반에 대한 더 자세한 내용은 Networking overview 문서도 함께 참고해 주세요.
방화벽 고려사항
스웜에 참여하는 Docker 데몬들은 다음 포트를 통해 서로 통신할 수 있어야 합니다:
- 포트
7946TCP/UDP — 컨테이너 네트워크 발견(discovery)용 - 포트
4789UDP(설정 가능) — 오버레이 네트워크(ingress 포함) 데이터 경로용
스웜에서 네트워킹을 설정할 때는 각별히 주의해야 해요. 개요는 튜토리얼을 참고하세요.
오버레이 네트워킹
스웜을 초기화하거나 기존 스웜에 Docker 호스트를 참여시키면, 해당 Docker 호스트에 두 개의 새 네트워크가 생성됩니다:
ingress라는 오버레이 네트워크 — 스웜 서비스와 관련된 컨트롤 및 데이터 트래픽을 처리해요. 스웜 서비스를 만들 때 사용자 정의 오버레이 네트워크에 연결하지 않으면, 기본적으로ingress네트워크에 연결됩니다.docker_gwbridge라는 브리지 네트워크 — 개별 Docker 데몬을 스웜에 참여한 다른 데몬들과 연결해 줍니다.
오버레이 네트워크 만들기
오버레이 네트워크를 만들려면 docker network create 명령에서 overlay 드라이버를 지정하면 됩니다:
$ docker network create \
--driver overlay \
my-network
위 명령은 특별한 커스텀 옵션을 지정하지 않았으므로, Docker가 서브넷을 할당하고 기본 옵션을 사용해요. 네트워크 정보는 docker network inspect로 확인할 수 있습니다.
아직 아무 컨테이너도 오버레이 네트워크에 연결되지 않았다면, 네트워크 구성이 그렇게 화려하지 않아요:
$ docker network inspect my-network
[
{
"Name": "my-network",
"Id": "fsf1dmx3i9q75an49z36jycxd",
"Created": "0001-01-01T00:00:00Z",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": []
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"Containers": null,
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4097"
},
"Labels": null
}
]
위 출력에서 드라이버가 overlay이고 scope가 swarm이라는 점을 눈여겨보세요. 다른 Docker 네트워크에서 볼 수 있는 local, host, global 같은 scope와는 달라요. 이 scope는 오직 스웜에 참여한 호스트만 이 네트워크에 접근할 수 있다는 뜻입니다.
네트워크의 서브넷과 게이트웨이는 서비스가 처음으로 네트워크에 연결될 때 동적으로 구성됩니다. 아래 예시는 위와 동일한 네트워크에 redis 서비스의 컨테이너 3개가 연결된 모습을 보여줘요.
$ docker network inspect my-network
[
{
"Name": "my-network",
"Id": "fsf1dmx3i9q75an49z36jycxd",
"Created": "2017-05-31T18:35:58.877628262Z",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"Containers": {
"0e08442918814c2275c31321f877a47569ba3447498db10e25d234e47773756d": {
"Name": "my-redis.1.ka6oo5cfmxbe6mq8qat2djgyj",
"EndpointID": "950ce63a3ace13fe7ef40724afbdb297a50642b6d47f83a5ca8636d44039e1dd",
"MacAddress": "02:42:0a:00:00:03",
"IPv4Address": "10.0.0.3/24",
"IPv6Address": ""
},
"88d55505c2a02632c1e0e42930bcde7e2fa6e3cce074507908dc4b827016b833": {
"Name": "my-redis.2.s7vlybipal9xlmjfqnt6qwz5e",
"EndpointID": "dd822cb68bcd4ae172e29c321ced70b731b9994eee5a4ad1d807d9ae80ecc365",
"MacAddress": "02:42:0a:00:00:05",
"IPv4Address": "10.0.0.5/24",
"IPv6Address": ""
},
"9ed165407384f1276e5cfb0e065e7914adbf2658794fd861cfb9b991eddca754": {
"Name": "my-redis.3.hbz3uk3hi5gb61xhxol27hl7d",
"EndpointID": "f62c686a34c9f4d70a47b869576c37dffe5200732e1dd6609b488581634cf5d2",
"MacAddress": "02:42:0a:00:00:04",
"IPv4Address": "10.0.0.4/24",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4097"
},
"Labels": {},
"Peers": [
{
"Name": "moby-e57c567e25e2",
"IP": "192.168.65.2"
}
]
}
]
오버레이 네트워크 커스터마이즈하기
오버레이 네트워크의 기본 구성을 그대로 쓰기 싫은 상황도 있을 거예요. 설정 가능한 전체 옵션 목록은 docker network create --help 명령으로 확인할 수 있고, 가장 자주 바꾸는 옵션 몇 가지를 소개해 드릴게요.
서브넷과 게이트웨이 구성하기
기본적으로 네트워크의 서브넷과 게이트웨이는 첫 번째 서비스가 네트워크에 연결될 때 자동으로 구성됩니다. 네트워크를 만들 때 --subnet과 --gateway 플래그를 사용해서 직접 구성할 수도 있어요. 아래 예시는 앞선 예시를 확장해서 서브넷과 게이트웨이를 직접 지정한 것입니다.
$ docker network create \
--driver overlay \
--subnet 10.0.9.0/24 \
--gateway 10.0.9.99 \
my-network
커스텀 기본 주소 풀 사용하기
스웜 네트워크의 서브넷 할당을 커스터마이즈하려면 swarm init 시점에 선택적으로 구성할 수 있어요.
예를 들어, 스웜을 초기화할 때 다음과 같은 명령을 사용합니다:
$ docker swarm init --default-addr-pool 10.20.0.0/16 --default-addr-pool-mask-length 26
사용자가 네트워크를 만들 때 --subnet 명령줄 옵션을 사용하지 않으면, 해당 네트워크의 서브넷은 풀에서 다음으로 사용 가능한 서브넷부터 순차적으로 할당됩니다. 만약 지정된 네트워크가 이미 할당된 상태라면, 그 네트워크는 스웜에 사용되지 않아요.
연속되지 않은 주소 공간이 필요하다면 여러 개의 풀을 구성할 수도 있습니다. 다만 특정 풀에서만 할당하는 방식은 지원되지 않아요. 네트워크 서브넷은 IP 풀 공간에서 순차적으로 할당되며, 삭제된 네트워크에서 할당이 해제되면 그 서브넷은 재사용됩니다.
기본 마스크 길이도 구성할 수 있는데, 모든 네트워크에 동일하게 적용됩니다. 기본값은 /24예요. 기본 서브넷 마스크 길이를 바꾸려면 --default-addr-pool-mask-length 명령줄 옵션을 사용하세요.
참고
기본 주소 풀은
swarm init시에만 구성할 수 있고, 클러스터 생성 후에는 변경할 수 없어요.
오버레이 네트워크 크기 제한
Docker는 /24 블록으로 오버레이 네트워크를 만들 것을 권장합니다. /24 오버레이 네트워크 블록은 네트워크를 256개의 IP 주소로 제한해요.
이 권장사항은 스웜 모드의 제한사항과 관련이 있습니다. 256개 이상의 IP 주소가 필요하다면 IP 블록 크기를 늘리지 마세요. 대신 dnsrr 엔드포인트 모드를 외부 로드 밸런서와 함께 사용하거나, 여러 개의 더 작은 오버레이 네트워크를 사용하면 됩니다. 다양한 엔드포인트 모드에 대한 자세한 내용은 서비스 디스커버리 구성을 참고하세요.
직접 서버 반환(DSR) 사용하기
직접 서버 반환(Direct server return, DSR)은 동서(east-west) 오버레이 로드 밸런싱 방식을 변경해서, IPVS가 목적지 MAC 주소를 변경하는 방식으로 패킷을 라우팅하게 해요. 소스 IP와 목적지 가상 IP(VIP)는 변경되지 않습니다.
DSR을 사용하는 오버레이 네트워크를 만들려면:
$ docker network create \
--driver overlay \
--opt dsr \
dsr-net
참고
DSR은 Linux 노드에서 실행되는 서비스 간 트래픽에만 지원됩니다. ingress 라우팅 메시는 DSR을 사용하지 않아요.
애플리케이션 데이터 암호화 구성하기
스웜과 관련된 관리 및 컨트롤 플레인 데이터는 항상 암호화됩니다. 암호화 메커니즘에 대한 자세한 내용은 Docker swarm mode overlay network security model 문서를 참고하세요.
스웜 노드 간의 애플리케이션 데이터는 기본적으로 암호화되지 않아요. 특정 오버레이 네트워크에서 이 트래픽을 암호화하려면 docker network create에 --opt encrypted 플래그를 사용하세요. 이 옵션은 vxlan 레벨에서 IPSEC 암호화를 활성화합니다. 다만 이 암호화는 무시할 수 없는 성능 저하를 일으키므로, 프로덕션에서 사용하기 전에 반드시 테스트해 보시기 바랍니다.
참고
암호화를 활성화하려면 자동으로 생성된 ingress를 커스터마이즈해야 해요. 기본적으로 모든 ingress 트래픽은 암호화되지 않습니다. 암호화는 네트워크 레벨 옵션이기 때문이에요.
서비스를 오버레이 네트워크에 연결하기
서비스를 기존 오버레이 네트워크에 연결하려면 docker service create에 --network 플래그를, docker service update에는 --network-add 플래그를 사용하면 됩니다.
$ docker service create \
--replicas 3 \
--name my-web \
--network my-network \
nginx
오버레이 네트워크에 연결된 서비스 컨테이너들은 그 네트워크를 통해 서로 통신할 수 있어요.
서비스가 어떤 네트워크에 연결되어 있는지 확인하려면 docker service ls로 서비스 이름을 찾은 다음, docker service ps <service-name>으로 네트워크 목록을 확인하면 됩니다. 반대로 특정 네트워크에 어떤 서비스의 컨테이너들이 연결되어 있는지 보려면 docker network inspect <network-name>을 사용하세요. 이 명령들은 스웜에 참여하고 running 상태인 어떤 스웜 노드에서든 실행할 수 있어요.
서비스 디스커버리 구성하기
서비스 디스커버리는 Docker가 서비스의 외부 클라이언트 요청을 개별 스웜 노드로 라우팅하는 메커니즘이에요. 클라이언트가 서비스에 몇 개의 노드가 참여하는지, 그 IP 주소나 포트가 무엇인지 알 필요가 없게 해 주죠. 같은 네트워크에 있는 서비스 간에 사용되는 포트는 게시(publish)할 필요가 없어요. 예를 들어 WordPress 서비스가 MySQL 서비스에 데이터를 저장하고 둘이 같은 오버레이 네트워크에 연결되어 있다면, MySQL 포트를 클라이언트에 게시할 필요 없이 WordPress HTTP 포트만 게시하면 됩니다.
서비스 디스커버리는 두 가지 방식으로 동작할 수 있어요:
- 내부 연결 기반 로드 밸런싱(Layers 3 및 4): 내장 DNS와 가상 IP(VIP)를 사용하는 방식이에요. 기본적으로 서비스를 네트워크에 연결하고 하나 이상의 포트를 게시하면, Docker는 서비스에 VIP를 할당합니다. 이 VIP가 클라이언트가 서비스에 도달하기 위한 "앞문(front end)" 역할을 해요. Docker는 서비스의 모든 워커 노드 목록을 유지하면서 클라이언트와 노드 중 하나 사이의 요청을 라우팅합니다. 클라이언트의 각 요청은 매번 다른 노드로 라우팅될 수 있어요.
- 외부 및 커스터마이즈된 요청 기반 로드 밸런싱(Layer 7): DNS 라운드 로빈(DNSRR)을 사용하는 방식이에요. 서비스를 DNS 라운드 로빈(DNSRR) 서비스 디스커버리를 사용하도록 구성하면 단일 가상 IP가 없어요. 대신 Docker가 서비스에 대한 DNS 항목을 설정해서, 서비스 이름에 대한 DNS 쿼리가 IP 주소 목록을 반환하고 클라이언트가 그중 하나에 직접 연결하게 됩니다.
DNS 라운드 로빈은 HAProxy 같은 자체 로드 밸런서를 사용하고 싶을 때 유용해요. DNSRR을 사용하도록 서비스를 구성하려면 새 서비스를 만들거나 기존 서비스를 업데이트할 때 --endpoint-mode dnsrr 플래그를 사용하세요.
컨테이너 디스커버리
대부분의 상황에서는 서비스 이름으로 연결하면 됩니다. Docker가 서비스를 뒷받침하는 모든 실행 중인 태스크("컨테이너")에 걸쳐 요청을 로드 밸런싱해 주거든요. 서비스를 뒷받침하는 개별 태스크의 IP 주소를 직접 확인하려면 tasks.<service-name>에 대해 DNS 조회를 수행하세요. Docker는 해당 서비스의 모든 태스크 IP 주소 목록을 실행 중인 레플리카당 하나씩 반환합니다.
ingress 네트워크 커스터마이즈하기
대부분의 사용자는 ingress 네트워크를 구성할 필요가 없지만, Docker는 커스터마이즈를 허용해요. 자동으로 선택된 서브넷이 네트워크에 이미 존재하는 서브넷과 충돌하거나, MTU 같은 저수준 네트워크 설정을 변경해야 하거나, 암호화를 활성화하려는 경우에 유용합니다.
ingress 네트워크를 커스터마이즈하려면 기존 네트워크를 제거하고 다시 만들어야 해요. 보통 스웜에서 서비스를 만들기 전에 이 작업을 수행합니다. 포트를 게시하는 기존 서비스가 있다면, ingress 네트워크를 제거하기 전에 그 서비스들을 먼저 제거해야 합니다.
ingress 네트워크가 없는 동안에는 포트를 게시하지 않는 기존 서비스는 계속 동작하지만 로드 밸런싱되지 않아요. 포트를 게시하는 서비스(예: 80번 포트를 게시하는 WordPress 서비스)는 영향을 받습니다.
-
docker network inspect ingress명령으로ingress네트워크를 확인하고, 해당 네트워크에 컨테이너가 연결된 서비스(포트를 게시하는 서비스, 예: 80번 포트를 게시하는 WordPress 서비스)를 모두 제거하세요. 이런 서비스가 모두 중지되지 않으면 다음 단계가 실패합니다. -
기존
ingress네트워크를 제거합니다:
$ docker network rm ingress
WARNING! Before removing the routing-mesh network, make sure all the nodes
in your swarm run the same docker engine version. Otherwise, removal may not
be effective and functionality of newly created ingress networks will be
impaired.
Are you sure you want to continue? [y/N]
--ingress플래그와 함께 원하는 커스텀 옵션을 지정해서 새 오버레이 네트워크를 만듭니다. 아래 예시는 MTU를 1200으로, 서브넷을10.11.0.0/16으로, 게이트웨이를10.11.0.2로 설정합니다.
$ docker network create \
--driver overlay \
--ingress \
--subnet=10.11.0.0/16 \
--gateway=10.11.0.2 \
--opt com.docker.network.driver.mtu=1200 \
my-ingress
참고
ingress네트워크의 이름을ingress가 아닌 다른 이름으로 지을 수는 있지만, 오직 하나만 존재할 수 있어요. 두 번째로 만들려고 하면 실패합니다.
- 첫 번째 단계에서 중지했던 서비스들을 다시 시작합니다.
docker_gwbridge 커스터마이즈하기
docker_gwbridge는 오버레이 네트워크( ingress 네트워크 포함)를 개별 Docker 데몬의 물리적 네트워크에 연결하는 가상 브리지예요. 스웜을 초기화하거나 Docker 호스트를 스웜에 참여시킬 때 Docker가 자동으로 생성하지만, 이는 Docker 장치가 아니라 Docker 호스트의 커널에 존재하는 장치입니다. 설정을 커스터마이즈해야 한다면, Docker 호스트를 스웜에 참여시키기 전에 하거나, 호스트를 스웜에서 임시로 제거한 후에 해야 해요.
기존 브리지를 삭제하려면 운영 체제에 brctl 애플리케이션이 설치되어 있어야 합니다. 패키지 이름은 bridge-utils입니다.
-
Docker를 중지합니다.
-
brctl show docker_gwbridge명령으로docker_gwbridge라는 브리지 장치가 존재하는지 확인합니다. 존재한다면brctl delbr docker_gwbridge로 제거합니다. -
Docker를 시작합니다. 아직 스웜에 참여하거나 초기화하지 마세요.
-
커스텀 설정으로
docker_gwbridge브리지를 생성하거나 다시 생성합니다. 아래 예시는 서브넷10.11.0.0/16을 사용합니다. 커스터마이즈 가능한 전체 옵션 목록은 Bridge driver options을 참고하세요.
$ docker network create \
--subnet 10.11.0.0/16 \
--opt com.docker.network.bridge.name=docker_gwbridge \
--opt com.docker.network.bridge.enable_icc=false \
--opt com.docker.network.bridge.enable_ip_masquerade=true \
docker_gwbridge
- 스웜을 초기화하거나 참여합니다.
컨트롤 및 데이터 트래픽에 별도 인터페이스 사용하기
기본적으로 모든 스웜 트래픽은 동일한 인터페이스를 통해 전송됩니다. 여기에는 스웜 자체를 유지하기 위한 컨트롤 및 관리 트래픽과 서비스 컨테이너 간의 데이터 트래픽이 모두 포함돼요.
스웜을 초기화하거나 참여할 때 --data-path-addr 플래그를 전달하면 이 트래픽을 분리할 수 있습니다. 인터페이스가 여러 개인 경우 --advertise-addr을 명시적으로 지정해야 하며, --data-path-addr을 지정하지 않으면 기본적으로 --advertise-addr 값이 사용됩니다. 스웜 참여, 탈퇴, 관리에 관한 트래픽은 --advertise-addr 인터페이스를 통해 전송되고, 서비스 컨테이너 간 트래픽은 --data-path-addr 인터페이스를 통해 전송돼요. 이 플래그들은 IP 주소나 eth0 같은 네트워크 장치 이름을 받을 수 있습니다.
다음 예시는 별도의 --data-path-addr로 스웜을 초기화합니다. Docker 호스트에 두 개의 서로 다른 네트워크 인터페이스가 있다고 가정해요. 10.0.0.1은 컨트롤 및 관리 트래픽용, 192.168.0.1은 서비스 관련 트래픽용입니다.
$ docker swarm init --advertise-addr 10.0.0.1 --data-path-addr 192.168.0.1
다음 예시는 호스트 192.168.99.100:2377이 관리하는 스웜에 참여하면서 --advertise-addr을 eth0으로, --data-path-addr을 eth1로 설정합니다.
$ docker swarm join \
--token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2d7c \
--advertise-addr eth0 \
--data-path-addr eth1 \
192.168.99.100:2377
오버레이 네트워크에서 포트 게시하기
같은 오버레이 네트워크에 연결된 스웜 서비스들은 사실상 모든 포트를 서로에게 노출합니다. 포트가 서비스 외부에서 접근 가능하려면 docker service create 또는 docker service update에서 -p 또는 --publish 플래그로 포트를 게시(publish) 해야 해요. 레거시 콜론 구분 문법과 최신 쉼표 구분 값 문법이 모두 지원됩니다. 더 긴 문법이 어느 정도 자기 문서화(self-documenting)되므로 선호됩니다.
| 플래그 값 | 설명 |
|---|---|
-p 8080:80 또는 -p published=8080,target=80 |
서비스의 TCP 80번 포트를 라우팅 메시의 8080번 포트로 매핑합니다. |
-p 8080:80/udp 또는 -p published=8080,target=80,protocol=udp |
서비스의 UDP 80번 포트를 라우팅 메시의 8080번 포트로 매핑합니다. |
-p 8080:80/tcp -p 8080:80/udp 또는 -p published=8080,target=80,protocol=tcp -p published=8080,target=80,protocol=udp |
서비스의 TCP 80번 포트를 라우팅 메시의 TCP 8080번 포트로, 서비스의 UDP 80번 포트를 라우팅 메시의 UDP 8080번 포트로 매핑합니다. |