Docker Compose 사이의 Networking

서론

최근 SSO 서버 개발을 진행하였는데, 테스트를 진행함에 있어 서로 다른 두 도커 컴포즈로 구성된 프로젝트간에 API 요청이 필요한 상황이 발생하였다. 이 과정에서 네트워크 설정에 관해 학습한 내용 및 트러블슈팅을 기록으로 남기고자 한다

 
 

본론

In Docker Compose

도커 컴포즈(Docker Compose)란 다수의 도커 컨테이너(Docker Container)들로 이루어진 애플리케이션을 보다 간편하게 관리하기 위한 도구이다. 이러한 도커 컴포즈로 구성된 프로젝트를 빌드할 경우 기본적으로 내부 컨테이너들간의 통신을 위한 default 네트워크를 생성한다. 일반적으로 프로젝트명_default 형식으로 생성된다.

아래와 같은 도커 컴포즈 파일(docker-compose.yml)이 있을 때 구성된 네트워크를 살펴보면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# docker-compose.yml

version: "3"

services:
django: &django
build:
context: ..
dockerfile: ./bin/compose/production/django/Dockerfile
image: my_project_django
depends_on:
- postgres

postgres:
build:
context: .
dockerfile: ./compose/production/postgres/Dockerfile
image: my_project_postgres

nginx:
build:
context: ..
dockerfile: ./bin/compose/production/nginx/Dockerfile
image: my_project_nginx
ports:
- "8412:80"
depends_on:
- django

1
2
3
4
5
6
7
8
9
# 활성화된 도커 네트워크 확인

> docker network ls

NETWORK ID NAME DRIVER SCOPE
45afd37df4f2 bridge bridge local
024ac5a121bb host host local
c2ad1c8aeadf none null local
fced302c4d64 my_project_default bridge local
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 활성화된 도커 네트워크 상세 정보

> docker network inspect my_project_default

[
{
"Name": "my_project_default",
"Id": "34d046c7981b88c2df9dd4aacf5b130e4a92bf9c99bbc6d11e6c074bf72ab326",
"Created": "2024-02-27T05:04:59.280736237Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "192.168.128.0/20",
"Gateway": "192.168.128.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"01253d81aab5bc696bfff747e562b6efa49caf9ff12a89e012cfaae3f4671861": {
"Name": "my_project-nginx-1",
"EndpointID": "a5a33350d0e2cacfaff9d270ceffd6397dacd2622e78802b5f01b9282a66d181",
"MacAddress": "02:42:c0:a8:80:05",
"IPv4Address": "192.168.128.3/20",
"IPv6Address": ""
},
"3feff36c3541910f679b04adba1076dcd6588ed5d830072b645a4ffff633b86e": {
"Name": "my_project-django-1",
"EndpointID": "39614375de2f40039e890c01ad047b42074d051efb91ee70721b48ca930dc33a",
"MacAddress": "02:42:c0:a8:80:04",
"IPv4Address": "192.168.128.4/20",
"IPv6Address": ""
},
"9499f051f6d855a5b8c21e9c420fa2b284da70f98f3203d3ac6182f2cd952eae": {
"Name": "my_project-postgres-1",
"EndpointID": "f4b38c73cff5c428857cfee1b56469accafa473137714cb8f028e21e6b952959",
"MacAddress": "02:42:c0:a8:80:02",
"IPv4Address": "192.168.128.2/20",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "default",
"com.docker.compose.project": "my_project",
"com.docker.compose.version": "2.23.3"
}
}
]

 

도커 네트워크를 확인해보면 연결된 각 컨테이너별로 임의의 IP가 부여되었음을 확인할 수 있다. 해당 네트워크에 연결된 컨테이너간에는 부여된 IP 혹은 Alias(서비스 이름, 컨테이너 이름 or ID)를 통해서 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# my_project_django docker

> ping 192.168.128.3

PING 192.168.128.3 (192.168.128.3) 56(84) bytes of data.
64 bytes from 192.168.128.3: icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from 192.168.128.3: icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from 192.168.128.3: icmp_seq=3 ttl=64 time=0.064 ms
64 bytes from 192.168.128.3: icmp_seq=4 ttl=64 time=0.043 ms
64 bytes from 192.168.128.3: icmp_seq=5 ttl=64 time=0.061 ms

--- 192.168.128.3 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4096ms
rtt min/avg/max/mdev = 0.042/0.051/0.064/0.009 ms

> ping nginx

PING nginx (192.168.128.3) 56(84) bytes of data.
64 bytes from my_project-nginx-1.my_project_default (192.168.128.3): icmp_seq=1 ttl=64 time=0.080 ms
64 bytes from my_project-nginx-1.my_project_default (192.168.128.3): icmp_seq=2 ttl=64 time=0.071 ms
64 bytes from my_project-nginx-1.my_project_default (192.168.128.3): icmp_seq=3 ttl=64 time=0.060 ms
64 bytes from my_project-nginx-1.my_project_default (192.168.128.3): icmp_seq=4 ttl=64 time=0.044 ms
64 bytes from my_project-nginx-1.my_project_default (192.168.128.3): icmp_seq=5 ttl=64 time=0.085 ms
^C
--- nginx ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4109ms
rtt min/avg/max/mdev = 0.044/0.068/0.085/0.014 ms

해당 도커의 Alias를 비롯한 상세 정보는 아래 명령어를 통해 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
> docker inspect my_project-django-1                                                           

[
{
"Id": "788be7c1addd717d0f24153d26b999b78d8fc72f9f233e8095556d06f313f270",

< 중략 >

"NetworkSettings": {
"Bridge": "",
"SandboxID": "bc079bbbccf793165a1e4f1e13c67edf850a70d43c2d953149cf92fb7f504474",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"13200/tcp": null
},
"SandboxKey": "/var/run/docker/netns/bc079bbbccf7",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {
"my_project": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"my_project-django-1",
"django",
"788be7c1addd"
],
"MacAddress": "02:42:c0:a8:50:07",
"NetworkID": "59eee52a5c0d0828b000f4f4ee59ec73bf0abb8e3f0da7a99c7708c277c7481b",
"EndpointID": "8f27d94c137cf7a8f2b25d0837c94d39e379d889817df8cd6c430b84921a4530",
"Gateway": "192.168.80.1",
"IPAddress": "192.168.80.7",
"IPPrefixLen": 20,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DriverOpts": null
}
}
}
}
]

 

단 이 과정에서 주의해야할 점은 컴포즈 내 도커 컨테이너가 특정 포트를 외부에 개방하고 있을 경우, 해당 컨테이너로 진입하기 위해선 외부로 노출된 포트가 아닌 내부 포트를 지정해야 한다. 일례로 위 컴포즈 내 Nginx 컨테이너의 경우 8412:80 을 통해 알 수 있듯이 8412 포트를 개방하여 80 번 포트로 포워딩을 진행하는데, 도커 컴포즈 내부 다른 컨테이너에서 해당 Nginx 컨테이너로 진입하고자 할 경우 80 번 포트를 사용해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> tcpping 192.168.128.3 8412

seq 0: tcp response from 192.168.128.3 [closed] 0.069 ms
seq 1: tcp response from 192.168.128.3 [closed] 0.066 ms
seq 2: tcp response from 192.168.128.3 [closed] 0.062 ms
seq 3: tcp response from 192.168.128.3 [closed] 0.109 ms
seq 4: tcp response from 192.168.128.3 [closed] 0.203 ms

> tcpping 192.168.128.3 80

seq 0: tcp response from 192.168.128.3 [open] 0.069 ms
seq 1: tcp response from 192.168.128.3 [open] 0.066 ms
seq 2: tcp response from 192.168.128.3 [open] 0.062 ms
seq 3: tcp response from 192.168.128.3 [open] 0.109 ms
seq 4: tcp response from 192.168.128.3 [open] 0.203 ms

 
 

Between Docker Compose

본 프로젝트를 개발하는 도중에 발생한 문제는 서로 다른 도커 컴포즈로 구성된 프로젝트를 로컬에서 실행하였는데, 기본적으로 default 네트워크는 외부에 공개가 되어있지 않아 컴포즈간의 통신이 불가능하였기 때문이다. 이를 해결할 수 있는 다양한 방법들이 존재하겠지만 테스트 단계이니만큼 간단한 방법으로 수정하였다.

우선 두 컴포즈가 공통으로 사용할 네트워크를 생성해준다

1
$ docker network create my_project

 

이후 각 도커 컴포즈 파일에서 해당 네트워크를 참조하도록 명시해준다. 연결할 다른 프로젝트의 도커 컴포즈 파일에도 아래와 동일한 방식으로 네트워크를 명시해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# docker-compose.yml

version: "3"

services:
django: &django
build:
context: ..
dockerfile: ./bin/compose/production/django/Dockerfile
image: my_project_django
depends_on:
- postgres
networks:
- my_project

postgres:
build:
context: .
dockerfile: ./compose/production/postgres/Dockerfile
image: my_project_postgres
networks:
- my_project

nginx:
build:
context: ..
dockerfile: ./bin/compose/production/nginx/Dockerfile
image: my_project_nginx
ports:
- "8412:80"
depends_on:
- django
networks:
- my_project

networks:
my_project:
driver: bridge
external: true
internal: true

 

이후 프로젝트를 빌드하면 다음과 같은 네트워크가 생성 되었음을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
> docker network ls

NETWORK ID NAME DRIVER SCOPE
45afd37df4f2 bridge bridge local
024ac5a121bb host host local
59eee52a5c0d my_project bridge local
c2ad1c8aeadf none null local
41fdadbf9aff my_project_default bridge local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
> docker network inspcet my_project

[
{
"Name": "my_project",
"Id": "59eee52a5c0d0828b000f4f4ee59ec73bf0abb8e3f0da7a99c7708c277c7481b",
"Created": "2024-02-26T23:13:36.51300227Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.80.0/20",
"Gateway": "192.168.80.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"15484f36924b0f15a7ce29090563cf455cbff9035966badd2826bc13999f45a9": {
"Name": "my_project-postgres-1",
"EndpointID": "407da93da18a0acb89fcfd6963a5d1ceccc6ff89a505aad4d56500ff019aa98c",
"MacAddress": "02:42:c0:a8:50:05",
"IPv4Address": "192.168.80.5/20",
"IPv6Address": ""
},
"1904a079a0304805112084c0816d4b22c89addd454da4e3406453d4dd198b3bb": {
"Name": "my_project-mssql-1",
"EndpointID": "b84e1e7c816d33e8b3e842e39ca210357066a625366be7942f810fa13b5154bf",
"MacAddress": "02:42:c0:a8:50:06",
"IPv4Address": "192.168.80.6/20",
"IPv6Address": ""
},
"4a4aa5e022835443ed7dd2e5c36052cb43711fd2da9d8d8d6bd788bce18a3411": {
"Name": "my_project_2-django-1",
"EndpointID": "df5e099740e05637e14261f92d64517592197370fe4e4f6e64722c22dd411734",
"MacAddress": "02:42:c0:a8:50:03",
"IPv4Address": "192.168.80.3/20",
"IPv6Address": ""
},
"52de2bd8ed747dde0e7741a9dd2d7dfaadb5ca101177cbe99f75f4d31fbc07ec": {
"Name": "my_project_2-postgres-1",
"EndpointID": "7f266846bac3d81a4fd1292d564edf863f5fb2348cacdeb2ca8efaae4b6e3043",
"MacAddress": "02:42:c0:a8:50:02",
"IPv4Address": "192.168.80.2/20",
"IPv6Address": ""
},
"788be7c1addd717d0f24153d26b999b78d8fc72f9f233e8095556d06f313f270": {
"Name": "my_project-django-1",
"EndpointID": "8f27d94c137cf7a8f2b25d0837c94d39e379d889817df8cd6c430b84921a4530",
"MacAddress": "02:42:c0:a8:50:07",
"IPv4Address": "192.168.80.7/20",
"IPv6Address": ""
},
"7c14bbd8e71181a55ad5fa510b8fc8ff3e2b87a761c952255f442583d7ea210a": {
"Name": "my_project-nginx-1",
"EndpointID": "6e7cbbba63677f0f7592985802ee5896b1d429be3bf544dcfd0e59202f8b9ba5",
"MacAddress": "02:42:c0:a8:50:08",
"IPv4Address": "192.168.80.8/20",
"IPv6Address": ""
},
"915927a07f2645c214dac26ab3c870b32b9fa13cde13c1453984002bd205a206": {
"Name": "my_project_2-nginx-1",
"EndpointID": "673d094a037d12266250c50ad05b691031ad6630e10fde4d1b815c9346b65c43",
"MacAddress": "02:42:c0:a8:50:04",
"IPv4Address": "192.168.80.4/20",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

 

상기 네트워크 정보와 같이 두 컴포즈의 각 컨테이너들이 한 네트워크를 공유하고 있음을 확인할 수 있다. 이전과 동일하게 IP 주소나 Alias를 통해서 각 컨테이너에 접근할 수 있다.

다만 이 과정에서 또다른 문제가 발생하였는데, 같은 템플릿에서 파생된 두 프로젝트이다보니 동일한 서비스 명을 공유하고 있었다. 이러할 경우 컨테이너 이름을 통해 다른 컨테이너에 연결을 시도할 경우 중복된 두 컨테이너 사이에서 갈팡질팡하는 상황이 발생하였다.

1
2
3
4
5
6
7
8
9
10
> tcpping nginx 80

seq 0: tcp response from 192.168.80.8 [open] 0.240 ms
seq 1: tcp response from 192.168.80.8 [open] 0.094 ms
seq 2: tcp response from 192.168.80.4 [open] 0.064 ms
seq 3: tcp response from 192.168.80.8 [open] 0.071 ms
seq 4: tcp response from 192.168.80.4 [open] 0.066 ms
seq 5: tcp response from 192.168.80.8 [open] 0.082 ms
seq 6: tcp response from 192.168.80.4 [open] 0.093 ms
seq 7: tcp response from 192.168.80.4 [open] 0.072 ms

 

이를 방지하기 위해 서비스명을 중복되지 않도록 바꿔주거나 컨테이너 이름 혹은 ID를 통해 접근함으로써 해결할 수 있다.

1
2
3
4
5
6
7
8
> tcpping my_project-nginx-1

seq 0: tcp response from 192.168.80.8 [open] 0.094 ms
seq 1: tcp response from 192.168.80.8 [open] 0.180 ms
seq 2: tcp response from 192.168.80.8 [open] 0.103 ms
seq 3: tcp response from 192.168.80.8 [open] 0.081 ms
seq 4: tcp response from 192.168.80.8 [open] 0.078 ms
seq 5: tcp response from 192.168.80.8 [open] 0.078 ms

 

참고

Docker 네트워크 사용법

Docker Compose 네트워크

도커 네트워크 (Docker Network)

docker compose 간 network 공유

Docker Network, 제대로 이해하기 (1)

Docker Compose 네트워킹: 컨테이너와 네트워크 연결