Commit bc98a082 authored by JooHan Hong's avatar JooHan Hong

docker network init

parent e08037bd
Pipeline #5267 passed with stages
in 44 seconds
[![logo](https://www.hongsnet.net/images/logo.gif)](https://www.hongsnet.net)
# Docker Network 검토
> Docker의 네트워크 개념을 검토하고, 서비스에 적용한다.
# Docker의 Network 방식
Docker는 **크게** **Overlay**, **Bridge**, **host**, **none**`네 가지 Network 방식이 존재`하며, 다음과 같이 개념과 역할에 대해 알아보자.
# Overlay Network [ `Pick` ]
* **Overlay Network****Docker Swarm에 참여**하는 Docker Daemon 간의 통신을 관리한다.
* 독립 실행형 컨테이너의 Network를 생성하는 방법과 동일한 방식으로 Overlay Network를 생성할 수 있다.
* 기존에 생성된 Overlay Network에 Service를 연결시켜, `Service 간 통신을 활성화`할 수 있다.
* Overlay Network는 Overlay Network Driver를 사용한다.
> www.hongsnet.net 서비스는 다음과 같이,
> Front-end(HA-Proxy) -> Internal Network -> Backend(Docker) -> Internal Network -> DBMS 의 구성이므로, `Overlay Network`를 이용하여 구성한다.
## Ingress Network ( Overlay Network **Sub mode** )
* **Ingress Network**는 Service의 Node 들간에 **Load Balancing**을 하는 Overlay Network 이다.
* **Docker Swarm**의 모든 Node가 노출된 Port로 요청을 받게되면, 해당 요청을 IPVS라는 모듈로 전달한다.
* IPVS는 해당 Service에 참여하는 모든 IP 주소를 추적하고, 그 중 하나를 선택한 뒤 요청을 해당 경로로 Routing 한다.
* Igreess Network는 Docker Swarm을 Init하거나 Join할 때 **자동으로 생성**된다.
# host
**Host 모드**는 docker0 interface를 사용하지 않고 **Host OS의 네트워크 자원을 사용**한다. bridge 형식으로 사용하지 않기 때문에 **brctl show docker0** 명령으로 검색해도 interfaces에 나타나지 않으며, Container에서 Port를 노출하지 않는 경우 사용할 수가 없으며, `보안상 권장하지 않는 방법`이다.
# None
컨테이너가 LoopBack(127.0.0.1)을 제외한 어떠한 네트워크도 사용하지 않는다. 이는 격리가 필요할 경우 사용된다.
# Bridge
Docker에서 `기본적으로 설정되는 Network 방식`이다. 호스트에서 다음과 같이 **docker network inspect bridge** 명령을 통해 상세정보를 확인해본다.
```bash
# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "43e5b193049b1f29fa48819a0cf137f433cb3e4f512efccaedfa6f034e69e62d",
"Created": "2020-01-21T05:35:46.711742895+09:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
```
Bridge라는 특성에 맞게, **같은 네트워크(Layer 2)에서 통신이 가능**하다. 즉, Ingress와 같이 외부로 노출하지 않을 경우에는 `기본적으로 외부와의 통신이 불가능`하다.
## docker0 Network ( Bridge Network **Sub mode** )
Docker Host를 설치한 후 host의 Network Interface를 보면, **docker0**라는 Virtual Interface가 존재하는 것을 볼 수 있다. 아래는 Docker를 설친한 host의 Interface를 확인한 정보이다.
```bash
# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
inet6 fe80::42:7aff:fe09:f1d8 prefixlen 64 scopeid 0x20<link>
ether 02:42:7a:09:f1:d8 txqueuelen 0 (Ethernet)
RX packets 5489687 bytes 20588189759 (19.1 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 9576525 bytes 26208253051 (24.4 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
```
**docker0** Interface의 특징은 다음과 같다.
* IP는 자동으로 172.17.0.0/16 네트워크로 설정된다.
* 이 IP는 DHCP를 통해 할당받는 것은 아니며, docker 내부 로직에 의해서 자동으로 할당받는 것이다.
* docker0는 일반적인 Interface가 아니며, Virtual Etherent Bridge이다.
**docker0**는 Container가 통신하기 위한 `가상 Linux Bridge`이다. Bridge는 기본적으로 Layer2 통신 기반이며, 만약 Container가 생성되면, 이 Bridge에 Container의 Interface가 하나씩 Binding되는 형태이다. 따라서 **Container가 외부로 통신할 때는 무조건 docker0 Interface를 거쳐야 한다.**
## Container Network ( Bridge Network **Sub mode** )
Container Network 방식은 `같은 호스트에 있는 컨테이너들 끼리의 통신`을 제공하는 방법이다. 컨테이너 간의 통신은 내부 도커 네트워크를 통해서 이루어지며, 통신은 호스트의 네트워크로 노출되지 않는다는것을 의미한다.
**--link *CONTAINER*:*ALIAS*** 로 설정하며, *CONTAINER* 는 링크 컨테이너(연결되는 컨테이너), *ALIAS*는 마스터 컨테이너(시작되는 컨테이너)의 내부에서 링크 컨테이너를 참조할 때 사용하는 로컬 이름을 설정 한다.
# Docker Container의 외부통신 구조
Docker host에 Container가 배포되면, **각 Container**에는 격리된 네트워크 환경(namespace)이 제공된다. **이 네트워크 환경은 오로지 각각의 Container 만을 위한 네트워크 환경**이다. 각 Container에는 통신을 위한 인터페이스도 새롭게 할당되며, mac 주소와 private IP도 부여받게 된다.
각 Container 들의 인터페이스는 자신들이 상주하고 있는 Docker host와 통신을 위해 Linux Bridge 방식으로 Binding 되어 있다. 따라서 같은 Docker host 내에 배포된 Container 들 사이에는 각자 할당받은 Private IP를 이용해서 자유롭게 통신이 가능하다.
## Container Port를 외부로 노출(expose)
Container를 생성하면 **기본적으로 외부와 통신이 불가능한 상태**이다. 따라서 외부와 통신을 위해서는 Container를 외부로 노출할 Port를 지정해야 한다. 노출할 Port를 지정하는 방법은 **Container를 생성할 때 -p 옵션을 사용**하면 된다.
```bash
# docker run -d -p 8000:80 --name hongsnet-nginx-1.16
```
만약 docker-compose를 사용할 경우에는 다음과 같이 기술한다.
```bash
version: '3'
services:
hongsnet-nginx-1.16:
image: hongsnet-nginx-1.16:latest
#restart: always
hostname: 'nginx116.hongsnet.net'
container_name: hongsnet-nginx-1.16
ports:
- "8000:80"
- "8443:443"
```
위 명령대로 실행하면, **외부에서 Docker 호스트의 8000 포트로 요청되면 컨테이너의 8000번 포트로 해당 요청을 forwarding 하겠다는 의미**이다.
아래와 같이 컨테이너 상태를 살펴보면, 8000 -> 80 포트로 forwarding 되어 있는 것을 볼 수 있다.
```bash
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
78f248e1817f hongsnet-nginx-1.16:latest "nginx -g 'daemon ..." 27 minutes ago Up 27 minutes 0.0.0.0:8000->80/tcp, 0.0.0.0:8443->443/tcp hongsnet-nginx-1.16
# netstat -lnp |grep 8000
tcp6 0 0 :::8000 :::* LISTEN 8182/docker-proxy-c
```
## docker-proxy
그런데 위 8000번 포트를 Listen 하는 프로세스는 **docker-proxy** 이다. 이 프로세스의 **목적**은 Docker **호스트로 들어온 요청을 컨테이너로 넘기는 것 뿐**이다. docker-proxy는 Kernel이 아닌, userland에서 수행되기 때문에 Kernel과 상관없이 host가 받은 패킷을 그대로 컨테이너의 port로 넘긴다.
컨테이너를 시작할 때 port를 외부로 노출하도록 설정하게 되면, Docker 호스트에는 **docker-proxy**라는 프로세스가 생성된다.
```bash
# tail -f /var/log/messages
...중략
00:00:00 /usr/libexec/docker/docker-proxy-current -proto tcp -host-ip 0.0.0.0 -host-port 8000 -container-ip 172.19.0.2 -container-port 80
```
**docker-proxy 프로세스**는 컨테이너의 port를 노출하도록 설정한 수 만큼, 추가로 프로세스가 생성된다(run process per port). 만약 하나의 포트를 오픈하는 두 개의 컨테이너를 생성한다면, docker-proxy는 두 개가 생성된다. 또한 한 개의 컨테이너에 두 개의 포트에 대해서 외부로 노출하도록 설정한다면, 마찬가지로 docker-proxy 프로세스는 두 개가 생성된다.
하지만 실제로는 Docker host로 요청이 들어온 패킷이 컨테이너로 전달되는 것은 docker-proxy와는 무관하게 Docker **호스트****iptables에 의해서 동작**된다. 즉, docker-proxy 프로세스를 kill 하더라도, 외부에서 들어오는 요청이 컨테이너로 전달되는데는 문제가 없다는 의미이다.
## iptables를 이용한 DNAT
먼저 Docker 호스트의 iptables를 확인해 보자.
```bash
# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.19.0.2 172.19.0.2 tcp dpt:22
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:2222 to:172.19.0.2:22
```
위의 iptables 설정내역을 보면, 먼저 **Docker 호스트에 들어온 패킷이 PREROUTING chain을 통해 DOCKER Chain으로 전달**되고, **Docker chain에서는 DNAT로 2222번 포트로 들어온 요청을 172.19.0.2 IP를 가진, 컨테이너의 22번 포트로 포워딩**되는 것을 알 수 있다.
반대로 **컨테이너의 외부로 나갈때**는 POSTROUTING chain을 거쳐, **MASQUERADE 되어 외부로 나간다.**
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment