2024. 3. 29. 14:04ㆍDocker
이 장은 다음을 다룹니다:
- Networking background
- Creationg Docker container networks
- Network-less and host-mode containers
- Publishing services on the ingress network
- Container network의 주의사항
네트워킹은 컴퓨팅의 전체 분야이므로 이 장에서는 컨테이너 네트워크에 필요한 특정 문제, 구조 및 도구를 다루면서 표면적인 부분만 다룰 것입니다. 웹 사이트, 데이터베이스, 이메일 서버 또는 Docker 컨테이너 내부의 웹 브라우저와 같이 네트워킹에 의존하는 소프트웨어를 실행하려면 해당 컨테이너를 네트워크에 연결하는 방법을 이해해야 합니다.
이 장을 읽고 나면 실행 중인 애플리케이션에 적합한 네트워크 노출이 포함된 컨테이너를 생성하고, 한 컨테이너에서 다른 컨테이너의 네트워크 소프트웨어를 사용하고, 컨테이너가 호스트 및 호스트의 네트워크와 상호 작용하는 방식을 이해할 수 있습니다.
5.1 네트워킹 배경(초보자를 위해)
이 장에서 다루는 주제를 이해하는 데 도움이 될 관련 네트워킹 개념의 간략한 개요가 유용할 것입니다. 이 섹션은 고급 세부 정보만 포함하므로, 전문가라면 앞부분을 건너뛰어도 됩니다.
네트워킹은 같은 로컬 리소스를 공유하거나 공유하지 않을 수 있는 프로세스 간의 통신에 관한 것입니다. 이 장의 내용을 이해하려면, 프로세스가 일반적으로 사용하는 몇 가지 기본 네트워크 추상화만 고려하면 됩니다. 네트워킹에 대한 이해가 더 깊어질수록 작동하는 메커니즘에 대해 더 많이 배우게 됩니다. 하지만 도커가 제공하는 도구를 사용하기 위해 깊은 이해가 필요한 것은 아닙니다. 이곳에 포함된 내용은 여러분이 독립적으로 선택한 주제에 대해 연구하도록 유도해야 합니다. 프로세스가 사용하는 기본 추상화에는 프로토콜, 네트워크 인터페이스, 포트 등이 포함됩니다.
5.1.1 기초: 프로토콜, 인터페이스, 포트
통신 및 네트워킹에 대한 프로토콜은 일종의 언어입니다. 프로토콜에 동의하는 두 당사자는 서로가 무엇을 통신하고 있는지 이해할 수 있습니다. 이것은 효과적인 통신의 핵심입니다. 하이퍼텍스트 전송 프로토콜(HTTP)은 많은 사람들이 들어본 인기 있는 네트워크 프로토콜 중 하나입니다. 이는 월드 와이드 웹을 제공하는 프로토콜입니다. 수많은 네트워크 프로토콜과 여러 계층의 통신이 이러한 프로토콜에 의해 생성됩니다. 지금으로서는 프로토콜이 무엇인지 알기만 해도 네트워크 인터페이스와 포트를 이해하는 데 충분합니다.
네트워크 인터페이스는 주소를 가지고 위치를 나타냅니다. 인터페이스를 주소가 있는 실제 세계 위치와 유사하게 생각할 수 있습니다. 네트워크 인터페이스는 우편함과 같습니다. 메시지는 그 주소의 수신자를 위해 우편함에 배달되고, 메시지는 다른 곳으로 배달되기 위해 우편함에서 취해집니다.
우편함이 우편 주소를 가지고 있는 것처럼, 네트워크 인터페이스는 인터넷 프로토콜에 의해 정의된 IP 주소를 가집니다. IP의 세부 사항은 흥미롭지만 이 책의 범위를 벗어납니다. IP 주소에 대해 알아야 할 중요한 것은 그것들이 네트워크에서 고유하며 네트워크 내 위치에 대한 정보를 포함한다는 것입니다.
컴퓨터에는 이더넷 인터페이스와 루프백 인터페이스라는 두 종류의 인터페이스가 있을 수 있습니다. 이더넷 인터페이스는 여러분이 가장 익숙할 것입니다. 다른 인터페이스와 프로세스에 연결하는 데 사용됩니다. 루프백 인터페이스는 다른 인터페이스에 연결되어 있지 않습니다. 처음에는 이것이 무용지물처럼 보일 수 있지만, 동일한 컴퓨터의 다른 프로그램과 네트워크 프로토콜을 사용하여 통신할 수 있는 기능이 종종 유용합니다. 이 경우에는 루프백이 훌륭한 해결책입니다.
우편함 비유를 계속해서, 포트는 수신자나 발신자와 같습니다. 하나의 주소에서 여러 사람이 메시지를 받을 수 있습니다. 예를 들어, 하나의 주소가 웬디 웹서버, 데보라 데이터베이스, 케이시 캐시에게 메시지를 받을 수 있습니다. 각 수신자는 자신의 메시지만 열어야 합니다.
그림 5.1 여러 사람이 같은 우편함을 사용할 수 있는 것과 같은 방식으로, 프로세스는 같은 인터페이스를 사용하며 고유하게 식별됩니다.
실제로, 포트는 단지 숫자이며 전송 제어 프로토콜(TCP) 또는 사용자 데이터그램 프로토콜(UDP)의 일부로 정의됩니다. 다시 말하지만, 프로토콜의 세부 사항은 이 책의 범위를 벗어납니다만, 언젠가 이에 대해 읽어보는 것이 좋습니다. 프로토콜에 대한 표준을 만든 사람들 또는 특정 제품을 소유한 회사들은 특정 목적을 위해 어떤 포트 번호를 사용해야 하는지 결정합니다. 예를 들어, 웹 서버는 기본적으로 80번 포트에서 HTTP를 제공합니다. 데이터베이스 제품인 MySQL은 기본적으로 3306번 포트에서 그 프로토콜을 제공합니다. 빠른 캐시 기술인 Memcached는 11211번 포트에서 그 프로토콜을 제공합니다. 포트는 봉투에 이름이 쓰여 있는 것처럼 TCP 메시지에 적힙니다.
인터페이스, 프로토콜, 포트는 모두 소프트웨어와 사용자에게 직접적인 관심사입니다. 이러한 것들에 대해 배움으로써, 프로그램이 어떻게 통신하는지뿐만 아니라 컴퓨터가 더 큰 그림에 어떻게 맞는지에 대한 더 나은 이해를 개발할 수 있습니다.
5.1.2 더 큰 그림: 네트워크, NAT, 포트 포워딩
인터페이스는 더 큰 네트워크에서 단일 지점입니다. 네트워크는 인터페이스가 서로 연결되는 방식으로 정의되며, 그 연결이 인터페이스의 IP 주소를 결정합니다.
때때로 메시지의 수신자가 인터페이스와 직접 연결되어 있지 않을 수 있으므로, 대신 메시지를 배달하기 위한 경로를 아는 중개자에게 전달됩니다. 우편 비유로 돌아가면, 이는 실제 세계의 우편 배달원이 작동하는 방식과 유사합니다.
당신이 발신함에 메시지를 넣으면, 우편 배달원이 그것을 가져가서 현지 라우팅 시설에 배달합니다. 그 시설 자체가 하나의 인터페이스입니다. 그것은 메시지를 가져와 목적지로 가는 경로의 다음 정거장으로 보낼 것입니다. 우편 배달원을 위한 현지 라우팅 시설은 메시지를 지역 시설로 전달하고, 그 다음에는 목적지의 현지 시설로, 그리고 마지막으로 수신자에게 전달할 수 있습니다. 네트워크 경로가 비슷한 패턴을 따르는 것이 일반적입니다. 그림 5.2는 설명된 경로를 보여주고 물리적 메시지 라우팅과 네트워크 라우팅 사이의 관계를 그립니다.
이 장은 단일 컴퓨터에 존재하는 인터페이스에 관한 것이므로, 우리가 고려하는 네트워크와 경로는 그렇게 복잡하지 않을 것입니다. 실제로, 이 장은 두 가지 특정 네트워크와 컨테이너가 그것들에 어떻게 연결되는지에 관한 것입니다. 첫 번째 네트워크는 컴퓨터가 연결된 네트워크입니다. 두 번째는 도커가 실행 중인 모든 컨테이너를 로컬 호스트 컴퓨터가 연결된 네트워크에 연결하기 위해 생성하는 가상 네트워크입니다. 그 두 번째 네트워크는 브리지라고 불립니다.
그 5.2 우편 시스템과 컴퓨터 네트워크에서 메시지 경로
브리지는 여러 네트워크를 연결하여 단일 네트워크로 기능하도록 하는 인터페이스입니다. 그림 5.3에서 보여주듯, 브리지는 연결된 네트워크 간의 트래픽을 다른 유형의 네트워크 주소를 기반으로 선택적으로 전달함으로써 작동합니다. 이 장의 내용을 이해하려면, 이 추상적인 아이디어에만 익숙해질 필요가 있습니다.
그림 5.3 두 개의 서로 다른 네트워크를 연결하는 브리지 인터페이스
이것은 몇 가지 미묘한 주제에 대한 매우 대략적인 소개였습니다. 설명은 Docker 사용 방법과 Docker가 단순화하는 네트워킹 기능을 이해하는 데 도움을 주기 위해 표면적으로만 설명되었습니다.
5.2 도커 컨테이너 네트워킹
Docker는 컨테이너에서 기본 호스트 연결 네트워크를 추상화합니다. 이렇게 하면 애플리케이션에 어느 정도 런타임 환경 불가지론(environment agnosticism)이 제공되고 인프라 관리자가 운영 환경에 맞게 구현을 조정할 수 있습니다.
※ 여기서 일컫는 불가지론(agnosticism)이란, 특정 플랫폼, 기술, 운영체제 등에 의존하지 않는 소프트웨어나 시스템을 의미.
도커 네트워크에 연결된 컨테이너는 동일한 도커 네트워크에 연결된 다른 컨테이너에서 라우팅할 수 있는 고유한 IP 주소를 받게 됩니다.
도커 네트워크에서는 컨테이너 간의 통신을 용이하게 하기 위해 각 컨테이너에 고유한 IP 주소를 할당합니다. 이는 도커가 관리하는 가상 네트워크 내에서 컨테이너가 서로를 찾고 통신할 수 있도록 해줍니다. 다음은 이 과정을 좀 더 자세히 설명한 것입니다:
1. 네트워크 생성: 도커를 사용할 때, 여러분은 하나 이상의 도커 네트워크를 생성할 수 있습니다. 각 네트워크는 도커가 관리하는 가상의 네트워크 공간을 제공하며, 컨테이너들은 이 네트워크에 연결될 수 있습니다.
2. IP 주소 할당: 도커 네트워크에 컨테이너를 연결하면, 도커는 해당 네트워크의 주소 범위에서 컨테이너에 고유한 IP 주소를 할당합니다. 이 IP 주소는 네트워크 내의 다른 컨테이너들이 해당 컨테이너와 통신할 때 사용됩니다.
3. 컨테이너 간 통신: 한 컨테이너가 다른 컨테이너와 통신하려면, 송신 컨테이너는 수신 컨테이너의 IP 주소를 사용하여 데이터를 보냅니다. 도커 네트워크는 이러한 통신을 내부적으로 라우팅하여 올바른 컨테이너로 메시지가 전달되도록 합니다.
4. 네트워크 격리: 각 도커 네트워크는 독립적인 통신 공간을 제공하기 때문에, 하나의 네트워크에 연결된 컨테이너는 다른 네트워크에 연결된 컨테이너와 직접 통신할 수 없습니다(특별한 네트워크 구성이나 라우팅 규칙을 설정하지 않는 한). 이는 컨테이너 간의 통신을 보다 안전하게 관리할 수 있게 해줍니다.
이런 방식으로 도커 네트워크는 컨테이너화된 애플리케이션의 네트워킹을 간소화하고, 서로 다른 컨테이너 간에 효율적이고 안전한 통신 방법을 제공합니다.
이 접근 방식의 주요 문제점은 컨테이너 내부에서 실행되는 소프트웨어가 컨테이너가 실행 중인 호스트의 IP 주소를 확인할 수 있는 쉬운 방법이 없다는 것입니다. 이는 컨테이너가 컨테이너내의 서비스 엔드포인트를 컨테이너 네트워크 외부의 다른 서비스에 알리는 것을 막습니다. 섹션 5.5에서는 이러한 극단적인 경우를 처리하는 몇 가지 방법을 다룹니다.
도커는 또한 네트워크를 일급 객체로 취급합니다. 이는 네트워크가 자체적인 생명 주기를 가지고 있으며 다른 객체에 구속되지 않는다는 것을 의미합니다. docker network 하위 커맨드를 사용하여 직접 정의하고 관리할 수 있습니다.
도커에서 네트워크를 시작하기 위해, 모든 도커 설치와 함께 제공되는 디폴트 네트워크를 살펴보세요. _docker network ls_를 실행하면 터미널에 모든 네트워크의 표가 출력됩니다. 결과는 다음과 같아야 합니다:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
63d93214524b bridge bridge local
6eeb489baff0 host host local
3254d02034ed none null local
디폴트로 도커는 세 개의 네트워크를 포함하며, 각각은 다른 드라이버에 의해 제공됩니다. bridge라는 네트워크는 디폴트 네트워크이며 브리지 드라이버에 의해 제공됩니다. bridge 드라이버는 동일한 머신에서 실행되는 모든 컨테이너의 컨테이너 간 연결성을 제공합니다. host 네트워크는 host 드라이버에 의해 제공되며, 이는 도커에게 연결된 컨테이너를 위해 어떤 특별한 네트워킹 네임스페이스나 자원도 생성하지 않도록 지시합니다. host 네트워크의 컨테이너는 제한되지 않은 프로세스처럼 호스트의 네트워크 스택과 상호 작용합니다. 마지막으로, none 네트워크는 null 드라이버를 사용합니다. none 네트워크에 연결된 컨테이너는 외부와의 네트워크 연결성이 없을 것입니다.
도커의 host
네트워크 모드에서 "제한되지 않은 프로세스처럼 호스트의 네트워크 스택과 상호작용한다"는 설명에서 "제한되지 않은 프로세스"라는 의미는, 해당 컨테이너의 네트워크 동작이 호스트 머신에서 실행되는 일반 프로세스와 동일하게 취급된다는 것을 뜻합니다. 즉, 컨테이너와 호스트의 네트워크 스택이 공유되어 네트워크 측면에서 격리가 없는 상태를 의미합니다.
'제한되지 않은 프로세스'의 의미
- 호스트 네트워크 스택 공유
- 일반적으로 도커 컨테이너는 격리된 네트워크 네임스페이스를 사용하여 자체 네트워크 인터페이스와 설정(IP, 포트 등)을 가집니다.
- 하지만
host
네트워크 모드에서는 컨테이너가 별도의 네트워크 네임스페이스를 생성하지 않고, 호스트 네트워크 스택을 그대로 사용합니다.
- 격리 제거
- 컨테이너가 호스트와 동일한 네트워크 환경에서 동작하므로, 컨테이너 내부에서 네트워크 설정을 변경하거나 포트를 열면 호스트 네트워크에 직접 영향을 미칩니다.
- 이로 인해 네트워크 격리 수준이 완전히 제거됩니다.
- 네트워크 인터페이스 접근
- 컨테이너는 호스트의 네트워크 인터페이스를 직접 사용합니다.
- 예를 들어, 컨테이너에서
eth0
인터페이스를 설정하거나, 포트를 열면 이것이 곧 호스트 네트워크의 설정을 변경하는 것과 같습니다.
- 제한 없음
- 일반적인 컨테이너는 네트워크 트래픽이 도커 네트워크를 통해 관리되고 필터링될 수 있지만,
host
네트워크 모드에서는 도커의 네트워크 제어 계층을 우회하기 때문에 이런 제한이 없습니다. - 즉, 네트워크 동작이 호스트에서 실행 중인 일반 프로세스와 동일하게 동작합니다.
- 일반적인 컨테이너는 네트워크 트래픽이 도커 네트워크를 통해 관리되고 필터링될 수 있지만,
예시: '제한되지 않은 프로세스'로 동작하는 컨테이너
일반적인 도커 네트워크 모드(예: bridge
):
- 컨테이너는 독립적인 네트워크 네임스페이스를 가지며, 호스트와 격리된 가상 네트워크에서 동작합니다.
- 포트를 열거나 네트워크를 설정하려면, 컨테이너와 호스트 간에 명시적으로 포트 매핑이 필요합니다.
docker run -d -p 8080:80 nginx
- 여기서
8080
포트가 호스트와 연결되지만, 기본적으로 컨테이너 내부와 호스트 네트워크는 분리됩니다.
- 여기서
host
네트워크 모드:
- 컨테이너가 호스트 네트워크를 그대로 사용하므로 포트 매핑 없이도 동일한 네트워크 자원을 공유합니다.
docker run --network host -d nginx
- Nginx가 컨테이너 내부에서 호스트의 네트워크 인터페이스와 동일한 IP 및 포트로 실행됩니다.
- 컨테이너가 80번 포트를 사용하면 호스트의 80번 포트를 직접 차지합니다.
장점과 단점
장점:
- 성능 최적화:
- 호스트 네트워크를 직접 사용하므로 네트워크 계층에서의 오버헤드(가상 네트워크 처리)가 줄어듭니다.
- 낮은 지연 시간과 높은 네트워크 성능이 필요한 애플리케이션에 적합.
- 간단한 설정:
- 포트 매핑 없이 컨테이너가 호스트 네트워크를 그대로 사용하므로, 네트워크 설정이 간단합니다.
단점:
- 보안 격리 부족:
- 컨테이너와 호스트가 동일한 네트워크 공간을 공유하기 때문에, 컨테이너 내부의 네트워크 설정이 호스트에 영향을 미칩니다.
- 악의적인 컨테이너가 호스트 네트워크를 손상시킬 수 있는 위험이 있음.
- 포트 충돌:
- 컨테이너가 호스트의 네트워크 포트를 직접 사용하므로, 동일한 포트를 사용하는 다른 애플리케이션이나 컨테이너와 충돌할 수 있습니다.
- 네트워크 관리 제한:
- 컨테이너가 네트워크 네임스페이스를 격리하지 않으므로, 도커의 네트워크 계층에서 제공하는 관리 및 모니터링 기능을 사용할 수 없습니다.
결론: '제한되지 않은 프로세스'의 의미
- 제한되지 않은 프로세스는 네트워크 측면에서 격리가 없는 상태를 의미하며, 컨테이너가 마치 호스트에서 직접 실행되는 애플리케이션처럼 호스트의 네트워크 리소스를 자유롭게 사용하는 것을 말합니다.
- 이는 성능과 간단한 네트워크 설정이 필요한 경우 유용하지만, 보안 및 리소스 관리 측면에서는 주의가 필요합니다.
네트워크의 범위는 local, global, swarm의 세 가지 값을 가질 수 있습니다. 이는 네트워크가 해당 네트워크가 존재하는 머신에 제한되어 있는지(local), 클러스터의 모든 노드에 생성되어야 하지만 그 사이를 라우팅하지 않아야 하는지(global), 또는 Docker swarm에 참여하는 모든 호스트에 걸쳐 원활하게 확장되어야 하는지(swarm; 다중 호스트 또는 클러스터 전체)를 나타냅니다. 보시다시피, 모든 디폴트 네트워크는 local 범위를 가지며, 다른 머신에서 실행되는 컨테이너 간에 직접적으로 트래픽을 라우팅할 수 없습니다.
디폴트 브리지 네트워크는 레거시 Docker와의 호환성을 유지하며, 서비스 디스커버리나 로드 밸런싱을 포함한 현대적인 Docker 기능을 활용할 수 없습니다. 그 사용은 권장되지 않습니다. 그러므로 해야 할 첫 번째 일은 자신만의 브리지 네트워크를 생성하는 것입니다.
5.2.1 사용자 정의 브리지 네트워크 생성하기
도커 브리지 네트워크 드라이버는 리눅스 네임스페이스, 가상 이더넷 장치, 그리고 리눅스 방화벽을 사용하여 브리지라고 불리는 한정적이고 사용자 지정 가능한 가상 네트워크 토폴로지를 구축합니다. 결과적으로 생성된 가상 네트워크는 도커가 설치된 머신에 로컬이며, 참여하는 컨테이너와 호스트가 연결된 더 넓은 네트워크 간의 경로를 만듭니다. 그림 5.4는 브리지 네트워크와 그 구성 요소에 연결된 두 개의 컨테이너를 보여줍니다.
그림 5.4 The default local Docker network topology and two attached containers
컨테이너는 자체적인 Private 루프백 인터페이스와 호스트의 네임스페이스 안에 있는 다른 가상 인터페이스에 연결된 별도의 가상 이더넷 인터페이스를 가집니다. 이 두 연결된 인터페이스는 호스트의 네트워크와 컨테이너 사이의 링크를 형성합니다. 일반적인 가정용 네트워크처럼, 각 컨테이너에는 외부 네트워크에서 직접 도달할 수 없는 고유한 Private IP 주소가 할당됩니다. 커넥션 컨테이너 간의 트래픽을 라우팅하고 호스트의 네트워크에 연결하여 브리지를 형성할 수 있는 다른 도커 네트워크를 통해 라우팅됩니다.
가상 네트워크는 물리적 네트워크 하드웨어를 사용하지 않고 소프트웨어를 통해 생성된 네트워크입니다. 이는 데이터 센터나 클라우드 환경에서 널리 사용되며, 컴퓨터, 서버, 기타 네트워킹 장비 간의 통신을 가능하게 하는 가상의 연결을 제공합니다. 가상 네트워크는 물리적 네트워크 위에 구축되어 실제 하드웨어의 한계를 초월하여 더 유연하고, 확장 가능하며, 보안이 강화된 네트워킹 솔루션을 제공합니다.가상 네트워크의 주요 특징은 다음과 같습니다:
1. 유연성: 가상 네트워크는 물리적 위치에 구애받지 않고 네트워크 리소스와 서비스를 구성하고 관리할 수 있는 높은 유연성을 제공합니다.
2. 분리와 격리: 가상 네트워킹을 통해 여러 가상 네트워크를 동일한 물리적 네트워크 인프라 위에 생성하고, 각 네트워크를 완전히 분리하여 운영할 수 있습니다. 이는 다중 테넌트 환경에서 각 클라이언트의 네트워크를 분리하는 데 유용합니다.
3. 보안: 가상 네트워크는 트래픽의 격리, 방화벽, 가상 사설 네트워크(VPN) 등을 통해 보안을 강화할 수 있습니다. 이는 네트워크 내 데이터의 안전성을 높이고 외부 위협으로부터 보호하는 데 도움이 됩니다.
4. 비용 효율성: 가상 네트워크는 추가적인 물리적 하드웨어 구매 없이 네트워크를 확장하거나 수정할 수 있으므로 비용 효율적입니다. 이는 특히 네트워크를 자주 변경하거나 실험적인 환경을 구축해야 하는 경우에 유리합니다.
가상 네트워크는 클라우드 컴퓨팅, 데이터 센터 관리, 그리고 소프트웨어 정의 네트워킹(SDN)과 같은 기술의 발전과 함께 더욱 중요해지고 있습니다. 가상화 기술은 기업과 개인이 더 빠르고 효율적으로 네트워크 리소스를 관리하고 배포할 수 있게 해 줍니다.
가상 인터페이스는 물리적 하드웨어에 의존하지 않고 소프트웨어를 통해 생성된 네트워크 인터페이스입니다. 이는 컴퓨터 또는 서버 내에서 가상화된 네트워크 연결을 제공하며, 가상 머신, 컨테이너, 또는 다른 가상 네트워크 구성요소와의 통신을 가능하게 합니다. 가상 인터페이스를 사용하면 여러 네트워크를 동시에 운영하거나, 네트워크 트래픽을 격리하고, 다양한 네트워킹 실험을 손쉽게 수행할 수 있습니다.가상 인터페이스의 특징은 다음과 같습니다:
1. 가상화 지원: 가상 인터페이스는 가상 머신이나 컨테이너와 같은 가상 환경에서 네트워크 연결을 제공하는 데 필수적입니다. 이는 가상 환경이 물리적 네트워크 인프라와 독립적으로 작동할 수 있게 해줍니다.
2. 유연성과 확장성: 소프트웨어를 통해 구성되기 때문에, 가상 인터페이스는 필요에 따라 쉽게 추가, 수정, 제거할 수 있습니다. 이를 통해 네트워크 구성의 유연성이 크게 향상됩니다.
3. 네트워크 격리와 보안: 가상 인터페이스는 특정 가상 머신이나 애플리케이션 간의 네트워크 트래픽을 격리하는 데 사용될 수 있습니다. 이는 네트워크 보안을 강화하고, 서로 다른 네트워크 세그먼트 간의 데이터 유출을 방지합니다.
4. 테스트 및 개발 용이성: 개발자와 네트워크 관리자는 가상 인터페이스를 사용하여 네트워킹 시나리오를 쉽게 테스트하고 개발할 수 있습니다. 이는 실제 물리적 변경 없이도 네트워크 구성을 실험할 수 있게 해줍니다.
가상 인터페이스는 클라우드 컴퓨팅, 네트워크 가상화, 그리고 컨테이너 기술의 발전에 따라 그 중요성이 증가하고 있습니다. 이러한 기술은 네트워크 설계와 관리에 있어 새로운 가능성을 열어주고 있으며, 복잡한 네트워크 요구 사항을 보다 효율적으로 충족시킬 수 있게 해줍니다.
다음과 같이 싱글 커맨드로 새 네트워크를 빌드할 수 있습니다:
docker network create \
--driver bridge \
--label project=dockerinaction \
--label chapter=5 \
--attachable \
--scope local \
--subnet 10.0.42.0/24 \
--ip-range 10.0.42.128/25 \
user-network
이 명령은 user-network라는 이름의 새로운 로컬 브리지 네트워크를 생성합니다. 네트워크에 라벨 메타데이터를 추가하는 것은 나중에 리소스를 식별하는 데 도움이 됩니다. 새 네트워크를 attachable로 표시함으로써 언제든지 컨테이너를 네트워크에 연결하거나 분리할 수 있습니다. 여기서는 네트워크 범위 속성을 수동으로 지정하고 이 드라이버의 기본값으로 설정했습니다. 마지막으로, 이 네트워크에 대해 사용자 정의 서브넷과 할당 가능한 주소 범위가 정의되었습니다, 10.0.42.0/24, 마지막 옥텟의 상위 절반(10.0.42.128/25)에서 할당합니다. 이는 이 네트워크에 컨테이너를 추가할 때, 그들이 10.0.42.128부터 10.0.42.255 범위의 IP 주소를 받게 됨을 의미합니다.
서브넷 정의 10.0.42.0/24에서 "/24" 표기는 서브넷 마스크의 표현 방법 중 하나로, CIDR (Classless Inter-Domain Routing) 표기법에 따른 것입니다. "/24"는 서브넷 마스크에서 1로 설정된 비트가 24개라는 의미입니다. IP 주소는 32비트로 구성되어 있으며, 이를 8비트씩 4개의 옥텟으로 나누어 표현합니다. 각 옥텟은 8비트이므로 8비트 * 3옥텟 = 24비트가 되며, 이는 첫 세 옥텟이 네트워크 부분을 나타내고 마지막 옥텟이 호스트 부분을 나타냅니다.
"/24" 서브넷 마스크는 실제로 255.255.255.0과 동일하며, 이는 첫 번째, 두 번째, 세 번째 옥텟(각각 8비트)이 네트워크를 식별하는 데 사용되고, 마지막 옥텟(8비트)이 네트워크 내의 호스트(또는 장치)를 식별하는 데 사용됨을 의미합니다. 따라서 /24 네트워크는 최대 256개의 IP 주소를 가질 수 있으며(0에서 255까지), 이 중 첫 번째 주소(10.0.42.0)는 네트워크 주소로, 마지막 주소(10.0.42.255)는 브로드캐스트 주소로 사용되므로 실제로 사용할 수 있는 호스트 주소는 254개입니다.
10.0.42.128/25에 대한 설명은 CIDR 표기법을 사용한 IP 주소 범위 지정에 기반합니다. 여기서 /25는 서브넷 마스크에서 1로 설정된 비트가 총 25개라는 의미입니다. 이는 IP 주소의 첫 25비트가 네트워크 주소를 정의하는 데 사용되고, 남은 7비트가 호스트 주소를 정의하는 데 사용됨을 의미합니다.
⦁ IP 주소: 10.0.42.128
⦁ CIDR 표기: /25
CIDR /25 서브넷 마스크는 255.255.255.128을 의미하며, 이는 32비트 IP 주소에서 첫 25비트가 네트워크 주소 부분을, 마지막 7비트가 호스트 주소 부분을 나타냅니다. 이렇게 설정하면, 해당 서브넷 내에서 사용할 수 있는 호스트 주소의 개수는 \(2^{7} = 128\)개입니다. 이는 주어진 /25 서브넷 내에서 할당 가능한 IP 주소가 128개라는 것을 의미하며, 이 주소들은 10.0.42.128에서 시작하여 10.0.42.255로 끝납니다.
요약하면, 10.0.42.128/25는 10.0.42.128부터 시작하여 10.0.42.255까지의 IP 주소 범위를 정의하며, 이 범위 내에서 128개의 IP 주소를 컨테이너 또는 다른 네트워크 장치에 할당할 수 있습니다. 이러한 설정은 특정한 네트워크 세그먼트 내에서 IP 주소를 효율적으로 관리하고 할당하는 데 유용하게 사용됩니다.
다른 일급 도커 엔터티처럼 네트워크를 검사할 수 있습니다. 다음 섹션에서는 사용자 네트워크를 사용하는 컨테이너와 결과적인 네트워크 구성을 검사하는 방법을 보여줍니다.
※ 결과적인 네트워크(Resulting Network)란, 도커에서 컨테이너가 특정 네트워크에 연결된 후 생성되는 최종 네트워크 상태를 의미합니다. 이는 컨테이너가 속한 네트워크와 해당 네트워크에서 제공하는 설정(IP 주소, 서브넷, 게이트웨이 등)을 포함합니다.
결과적인 네트워크는 컨테이너가 실제로 네트워크에 어떻게 연결되고 구성되었는지를 보여줍니다. 도커는 컨테이너가 사용자 정의 네트워크나 디폴트 네트워크(bridge, host, none)에 연결될 때, 컨테이너의 네트워크 인터페이스와 네트워크 설정을 동적으로 생성합니다. 이로 인해 컨테이너가 실행되면서 결과적으로 형성된 네트워크 구성을 확인할 수 있습니다.
First-class Docker entity란 도커에서 중요하고 기본적인 요소나 구성요소를 의미합니다. 이 용어는 도커의 핵심 개념이나 객체를 지칭할 때 사용되며, 이러한 객체는 도커 엔진에 의해 직접 관리되고 사용자가 직접적으로 조작할 수 있는 엔터티를 말합니다. 일반적으로 도커의 일급 엔터티에는 컨테이너, 이미지, 볼륨, 네트워크 등이 포함됩니다. 이러한 엔터티들은 도커 명령어를 통해 생성, 관리, 삭제 등의 조작이 가능하며, 도커의 기능과 운영에 필수적인 요소들입니다.
⦁ 컨테이너(Container): 도커에서 실행되는 애플리케이션의 인스턴스입니다. 격리된 환경에서 프로세스를 실행하여 애플리케이션이 호스트 시스템과 분리되도록 합니다.
⦁ 이미지(Image): 컨테이너를 생성하는 데 사용되는 템플릿입니다. 응용 프로그램과 그 의존성을 포함하고 있으며, 읽기 전용으로 사용됩니다.
⦁ 볼륨(Volume): 데이터를 저장하고 컨테이너 사이, 혹은 컨테이너와 호스트 시스템 사이에서 데이터를 공유하는 데 사용되는 영속적인 데이터 영역입니다.
⦁ 네트워크(Network): 컨테이너 간의 통신이나 컨테이너와 외부 세계 사이의 네트워킹을 관리하기 위한 도구입니다. 가상 네트워크를 통해 컨테이너가 서로 통신할 수 있게 합니다.이러한 일급 엔터티들은 도커의 기능성과 유연성을 크게 확장해 주며, 사용자가 도커를 통해 애플리케이션을 더 쉽게 배포하고 관리할 수 있게 해 줍니다.
5.2.2 브리지 네트워크 탐색하기
컨테이너 네트워크 내부에서 네트워크 소프트웨어를 실행할 계획이라면, 그 컨테이너에서 네트워크가 어떻게 보이는지 잘 이해해야 합니다. 새로운 브리지 네트워크를 탐색하는 것을 시작하려면, 해당 네트워크에 연결된 새로운 컨테이너를 생성하세요:
docker run -it \
--network user-network \
--name network-explorer \
alpine:3.8 \
sh
터미널에서 (이제 실행 중인 컨테이너에 연결되어 있음) 다음을 실행하여 컨테이너에서 사용 가능한 IPv4 주소 목록을 가져오세요:
ip -f inet -4 -o addr
결과는 다음과 같아야 합니다:
1: lo inet 127.0.0.1/8 scope host lo\ ...
18: eth0 inet 10.0.42.129/24 brd 10.0.42.255 scope global eth0\ ...
이 목록에서 볼 수 있듯이, 컨테이너에는 IPv4 주소를 가진 두 개의 네트워크 장치가 있습니다. 그것들은 루프백 인터페이스(또는 로컬호스트)와 eth0(가상 이더넷 장치)이며, 브리지 네트워크에 연결되어 있습니다. 더욱이, eth0이 user-network 구성에 의해 지정된 범위와 서브넷 내의 IP 주소를 가지고 있음을 볼 수 있습니다(범위는 10.0.42.128에서 10.0.42.255까지). 이 IP 주소는 이 브리지 네트워크에 있는 다른 컨테이너가 이 컨테이너에서 실행하는 서비스와 통신하기 위해 사용할 주소입니다. 루프백 인터페이스는 같은 컨테이너 내부의 통신에만 사용될 수 있습니다.
다음으로, 또 다른 브리지 네트워크를 생성하고 실행 중인 network-explorer 컨테이너를 두 네트워크에 모두 연결하세요. 먼저 실행 중인 컨테이너에서 터미널을 분리하고(CTRL-P를 누른 다음 CTRL-Q) 그다음 두 번째 브리지 네트워크를 생성하세요:
docker network create \
--driver bridge \
--label project=dockerinaction \
--label chapter=5 \
--attachable \
--scope local \
--subnet 10.0.43.0/24 \
--ip-range 10.0.43.128/25 \
user-network2
두 번째 네트워크가 생성되면, 여전히 실행 중인 network-explorer 컨테이너를 연결할 수 있습니다:
docker network connect \
user-network2 \
network-explorer
컨테이너가 두 번째 네트워크에 연결된 후에는, 터미널을 다시 연결하여 탐색을 계속하세요:
docker attach network-explorer
이제, 컨테이너 안에서 다시 네트워크 인터페이스 구성을 검사하면 다음과 같은 것을 볼 수 있습니다:
1: lo inet 127.0.0.1/8 scope host lo\ ...
18: eth0 inet 10.0.42.129/24 brd 10.0.42.255 scope global eth0\ ...
20: eth1 inet 10.0.43.129/24 brd 10.0.43.255 scope global
eth1\ ...
예상했듯이, 이 출력은 network-explorer 컨테이너가 두 개의 사용자 정의 브리지 네트워크에 연결되어 있음을 보여줍니다.
5.2.3 Beyond bridge networks
사용 사례에 따라, 브리지 네트워크만으로 충분할 수 있습니다. 예를 들어, 브리지 네트워크는 일반적으로 LAMP 스택이 실행되는 콘텐츠 관리 시스템 또는 대부분의 로컬 개발 작업과 같은 단일 서버 배포에 적합합니다. 하지만 머신 실패를 견딜 수 있도록 설계된 멀티서버 환경에서 운영되고 있다면, 다른 머신에 있는 컨테이너 간에 트래픽을 원활하게 라우팅할 수 있어야 합니다. 브리지 네트워크로는 이것이 불가능합니다.
Docker는 이러한 사용 사례를 위해 몇 가지 옵션을 기본적으로 제공합니다. 최선의 옵션은 네트워크를 구축하는 환경에 따라 달라집니다. Linux 호스트에서 Docker를 사용하고 호스트 네트워크를 제어할 수 있는 경우, macvlan 또는 ipvlan 네트워크 드라이버가 제공하는 언더레이 네트워크를 사용할 수 있습니다. 언더레이 네트워크는 각 컨테이너에 대해 first-class 네트워크 주소를 생성합니다. 이러한 id은 호스트가 연결된 같은 네트워크에서 디스커버리되고 라우팅될 수 있습니다. 머신에 실행되는 각 컨테이너는 네트워크상의 독립된 노드처럼 보입니다.
Mac이나 Windows용 Docker를 실행하거나 관리되는 클라우드 환경에서 실행하는 경우, 이러한 옵션은 작동하지 않습니다. 더욱이, 언더레이 네트워크 구성은 호스트 네트워크에 의존적이므로 정의가 거의 이동 가능하지 않습니다. 더 인기 있는 멀티호스트 컨테이너 네트워크 옵션은 오버레이 네트워크입니다.
스웜 모드가 활성화된 Docker 엔진에서 사용할 수 있는 오버레이 네트워크 드라이버는 브리지 네트워크의 구성과 유사하지만, 논리적 브리지 구성 요소는 멀티호스트를 인식하고 스웜의 모든 노드 간의 컨테이너 간 연결을 라우팅할 수 있습니다.
브리지 네트워크와 마찬가지로, 오버레이 네트워크의 컨테이너는 클러스터 외부에서 직접 라우팅될 수 없습니다. 하지만 컨테이너 간 통신은 간단하며, 네트워크 정의는 대부분 호스트 네트워크 환경과 독립적입니다.
일부 경우에는 언더레이 또는 오버레이 네트워크로 해결되지 않는 특별한 네트워크 요구 사항이 있을 수 있습니다. 호스트 네트워크 구성을 조정하거나 컨테이너가 완전한 네트워크 격리 상태에서 운영되도록 해야 할 수도 있습니다. 이러한 경우에는 특별한 컨테이너 네트워크 중 하나를 사용해야 합니다.
5.3 특별한 컨테이너 네트워크: host와 none
docker network list로 사용 가능한 네트워크를 나열할 때, 결과에는 host와 none이라는 두 가지 특별한 항목이 포함됩니다. 이들은 실제로 네트워크가 아니라, 특별한 의미를 가진 네트워크 연결 유형입니다.
docker run 명령에 --network host 옵션을 지정하면, Docker에게 특별한 네트워크 어댑터나 네트워크 네임스페이스 없이 새로운 컨테이너를 생성하도록 지시하는 것입니다. 결과적으로 생성된 컨테이너 내부에서 실행되는 소프트웨어는 컨테이너 외부에서 실행될 때와 동일한 정도의 호스트 네트워크 접근 권한을 가집니다. 네트워크 네임스페이스가 없기 때문에, 네트워크 스택을 조정하기 위한 모든 커널 도구들이 수정을 위해 사용할 수 있습니다(수정하는 프로세스가 그렇게 할 접근 권한을 가진 경우).
host 네트워크에서 실행되는 컨테이너는 localhost에서 실행 중인 host 서비스에 접근할 수 있으며, host 네트워크 인터페이스 중 어느 것이든 볼 수 있고 바인딩할 수 있습니다. 다음 커맨드[ip -o addr]는 호스트 네트워크의 컨테이너 내부에서 사용 가능한 모든 네트워크 인터페이스를 나열함으로써 이를 보여줍니다:
docker run --rm \
--network host \
alpine:3.8 ip -o addr
호스트 네트워크에서 실행되는 것은 시스템 서비스나 다른 인프라 구성 요소에 유용하지만, 멀티테넌트 환경에서는 적절하지 않으며 제3의 컨테이너에 대해서는 허용되어서는 안 됩니다. 이러한 맥락에서, 컨테이너를 네트워크에 연결하지 않으려는 경우가 종종 있습니다. 최소 권한의 시스템을 구축하는 취지에 따라, 가능한 한 none 네트워크를 사용해야 합니다.
none 네트워크에서 컨테이너를 생성하는 것은 Docker에게 새 컨테이너에 대해 연결된 가상 이더넷 어댑터를 제공하지 않도록 지시합니다. 이것은 자체 네트워크 네임스페이스를 가질 것이므로 격리될 것이지만, 네임스페이스 경계를 넘어 연결된 어댑터가 없기 때문에 네트워크를 사용하여 컨테이너 외부와 통신할 수 없습니다. 이렇게 구성된 컨테이너는 여전히 자신의 루프백 인터페이스를 가지므로, 멀티프로세스 컨테이너는 프로세스 간 통신을 위해 localhost로의 연결을 여전히 사용할 수 있습니다.
이를 직접 네트워크 구성을 검사함으로써 확인할 수 있습니다. 다음 명령을 실행하여 none 네트워크의 컨테이너 내부에서 사용 가능한 인터페이스를 나열하세요:
docker run --rm \
--network none \
alpine:3.8 ip -o addr
이 예제를 실행하면, 사용 가능한 유일한 네트워크 인터페이스는 루프백 인터페이스로, 주소 127.0.0.1에 바인딩되어 있음을 볼 수 있습니다. 이 구성은 세 가지를 의미합니다:
- 컨테이너 내에서 실행되는 어떤 프로그램도 루프백 인터페이스에 연결하거나 연결을 기다릴 수 있습니다.
- 컨테이너 외부의 어떤 것도 루프백 인터페이스에 연결할 수 없습니다.
- 해당 컨테이너 내에서 실행되는 어떤 프로그램도 컨테이너 외부의 어떤 것에도 도달할 수 없습니다.
마지막 포인트는 중요하며 쉽게 입증됩니다. 인터넷에 연결되어 있다면, 항상 사용 가능해야 하는 인기 있는 서비스에 접근해 보세요. 이 경우, Cloudflare의 공개 DNS 서비스에 접근해 보세요:
docker run --rm \
--network none \ <-- Creates a closed container
alpine:3.8 \
ping -w 2 1.1.1.1 <-- Pings Cloudflare
이 예에서는 네트워크 격리 컨테이너를 생성하고 컨테이너와 Cloudflare에서 제공하는 공용 DNS 서버 간의 속도를 테스트해 봅니다. 이 시도는 ping: send-to: Network is unreachable과 같은 메시지와 함께 실패합니다.
이는 컨테이너에 더 큰 네트워크로의 경로가 없다는 것을 알고 있기 때문에 의미가 있습니다.
closed container를 사용해야 하는 경우
none 네트워크는 네트워크 격리의 필요성이 가장 높거나 프로그램이 네트워크 접근을 요구하지 않을 때 사용해야 합니다. 예를 들어, 터미널 텍스트 편집기를 실행하는 것은 네트워크 접근을 필요로 하지 않아야 합니다. 랜덤 비밀번호를 생성하기 위해 실행되는 프로그램은 그 비밀이 도난당하는 것을 방지하기 위해 네트워크 접근 없이 컨테이너 내부에서 실행되어야 합니다.
none 네트워크의 컨테이너는 서로 격리되어 있으며 나머지 세계와도 격리되어 있습니다. 하지만 브리지 네트워크의 컨테이너라도 Docker 엔진을 실행하는 호스트 외부에서 none 네트워크로 직접 라우팅할 수는 없다는 점을 기억하세요.
브리지 네트워크는 NAT(네트워크 주소 변환)를 사용하여 대상이 브리지 네트워크 외부에 있는 모든 아웃바운드 컨테이너 트래픽이 호스트 자체에서 오는 것처럼 보이게 합니다. 이는 컨테이너에서 실행 중인 서비스 소프트웨어가 나머지 세계 및 대부분의 클라이언트와 고객이 위치한 네트워크 부분과 격리되어 있음을 의미합니다. 다음 섹션에서는 이러한 격차를 해소하는 방법을 설명합니다.
5.4 NodePort Publising를 사용한 들어오는 트래픽 처리
Docker 컨테이너 네트워크는 컨테이너 간의 심플한 커넥션과 라우팅에 관한 것입니다. 그 컨테이너에서 실행되는 서비스를 외부 네트워크 클라이언트와 연결하는 것은 추가 단계를 요구합니다. 컨테이너 네트워크가 네트워크 주소 변환을 통해 더 넓은 네트워크에 연결되어 있기 때문에, 외부 네트워크 인터페이스에서 트래픽을 어떻게 전달할지 Docker에게 구체적으로 알려주어야 합니다. 호스트 인터페이스의 TCP 또는 UDP 포트와 타겟 컨테이너 및 컨테이너 포트를 지정해야 하며, 이는 홈 네트워크의 NAT 장벽을 통해 트래픽을 전달하는 것과 유사합니다.
노드포트 발행 (NodePort Publishing) 이해하기
간단히 말하자면 노드포트 발행은 컨테이너화된 응용 프로그램을 외부 세계에 노출하는 방법 중 하나입니다. Kubernetes 클러스터 외부의 사용자가 응용 프로그램에 액세스할 수 있도록 해줍니다.
이하 내용은 제공된 설명을 더욱 자세히 분석합니다.
⦁ 노드 (Node): 클러스터의 노드는 응용 프로그램을 실행하는 실제 머신입니다. 하나의 클러스터는 여러 노드로 구성될 수 있으며, 각 노드는 고유한 IP 주소를 가지고 있습니다.
⦁ 포트 (Port): 네트워크 트래픽은 특정 포트를 통해 라우팅됩니다. 각 서비스는 특정 포트에서 수신 대기합니다.
⦁ 클러스터 외부 (External World): 클러스터 외부는 Kubernetes 클러스터 자체 외부를 의미합니다. 인터넷이나 로컬 네트워크의 다른 시스템이 여기에 포함됩니다.노드포트 발행 방식
1. 서비스 정의 (Service Definition): Kubernetes 서비스는 컨테이너 집합을 나타내는 추상체입니다. 서비스를 만들 때 노드포트 발행을 사용하도록 구성할 수 있습니다.
2. 노드포트 할당 (NodePort Allocation): Kubernetes는 클러스터의 각 노드에서 사용하지 않는 고유 포트를 할당합니다. 이 포트 번호는 보통 30000 이상의 상위 포트 범위에서 선택됩니다.
3. NAT 규칙 설정 (NAT Rule Configuration): 각 노드의 운영 체제는 네트워크 주소 변환 (NAT) 규칙을 설정합니다. 외부에서 노드포트로 들어오는 트래픽은 서비스의 실제 포트 (보통은 컨테이너 포트)로 라우팅됩니다.
외부 사용자 접속 (External User Access): 외부 사용자는 클러스터 외부에서 노드의 IP 주소와 할당된 노드포트 번호를 사용하여 서비스에 액세스할 수 있습니다.노드포트 발행의 장단점
장점:
⦁ 간편한 설정: 외부 로드 밸런서를 설정하지 않고도 외부에서 서비스에 액세스할 수 있습니다.
⦁ 유연성: 사용자 지정 노드포트를 사용할 수 있습니다.
단점:
⦁ 단일 포인트 장애 (Single Point of Failure): 특정 노드가 다운되면 해당 노드를 통해 서비스에 액세스할 수 없습니다.
⦁ 보안 우려: 노드포트는 일반적으로 상위 포트 범위에 있지만 여전히 보안을 강화해야 할 수 있습니다.
스케일링 문제: 클러스터를 확장할 때 노드가 추가되면 외부 사용자는 모든 노드의 IP 주소와 노드포트를 알아야 합니다.요약하자면 노드포트 발행은 Kubernetes 외부에서 컨테이너화된 응용 프로그램에 액세스하는 간편한 방법이지만, 단일 포인트 장애, 보안 및 스케일링 문제를 고려해야 합니다.
Port publication 구성은 컨테이너 생성 시 제공되며 나중에 변경할 수 없습니다. docker run 및 docker create 명령은 -p 또는 --publish 목록 옵션을 제공합니다. 다른 옵션들처럼 -p 옵션은 콜론으로 구분된 문자열 아규먼트를 받습니다. 그 아규먼트는 호스트 인터페이스, 호스트에서 전달할 포트, 대상 포트, 및 포트 프로토콜을 지정합니다. 다음 아규먼트들은 모두 동일합니다:
- 0.0.0.0:8080:8080/tcp
- 8080:8080/tcp
- 8080:8080
이러한 옵션들은 모두 호스트 인터페이스의 TCP 포트 8080에서 새 컨테이너의 TCP 포트 8080으로 트래픽을 전달합니다. 첫 번째 아규먼트는 전체 형식입니다. 문법을 더 완전한 맥락에서 고려해보려면, 다음 예제 명령을 고려하세요:
docker run --rm \
-p 8080 \
alpine:3.8 echo "forward ephemeral TCP -> container TCP 8080"
docker run --rm \
-p 8088:8080/udp \
alpine:3.8 echo "host UDP 8088 -> container UDP 8080"
docker run --rm \
-p 127.0.0.1:8080:8080/tcp \
-p 127.0.0.1:3000:3000/tcp \
alpine:3.8 echo "forward multiple TCP ports from localhost"
이 커맨드들은 모두 다른 작업을 수행하며, 문법의 유연성을 보여줍니다. 새로운 사용자가 처음에 마주치는 문제는 첫 번째 예시가 호스트의 8080 포트를 컨테이너 내의 8080 포트에 매핑할 것이라고 가정하는 것입니다. 실제로는 호스트 운영 시스템이 임의의 호스트 포트를 선택하고, 트래픽은 컨테이너 내의 8080 포트로 라우팅됩니다. 이 설계와 기본 동작의 이점은 포트가 희소한 자원이며, 임의의 포트를 선택함으로써 소프트웨어와 도구가 잠재적인 충돌을 피할 수 있다는 것입니다. 그러나 컨테이너 내부에서 실행되는 프로그램은 자신이 컨테이너 내부에서 실행되고 있으며, 컨테이너 네트워크에 바인딩되어 있고, 호스트에서 어떤 포트가 포워딩되고 있는지 알 방법이 없습니다.
Docker는 포트 매핑을 조회하는 메커니즘을 제공합니다. 이 기능은 운영 시스템이 포트를 선택하게 할 때 중요합니다. 주어진 컨테이너로 포워딩된 포트를 보기 위해 docker port 하위 명령어를 실행하세요:
docker run -d -p 8080 --name listener alpine:3.8 sleep 300
docker port listener
이 정보는 docker ps 하위 명령어를 통해 요약 형태로도 사용할 수 있지만, 테이블에서 특정 매핑을 선택하는 것은 번거로울 수 있으며 다른 명령어와 잘 조합되지 않습니다. docker port 하위 명령어는 컨테이너 포트와 프로토콜을 지정하여 조회 쿼리를 좁힐 수도 있습니다. 이는 여러 포트가 게시될 때 특히 유용합니다:
docker run -d \
-p 8080 \
-p 3000 \
-p 7500 \
--name multi-listener \
alpine:3.8 sleep 300
docker port multi-listener 3000
이 섹션에서 다룬 도구들을 사용하면, 호스트에서 실행되는 올바른 컨테이너로 들어오는 트래픽을 관리할 수 있어야 합니다. 그러나 Docker 네트워크를 사용할 때 컨테이너 네트워크 구성을 사용자 정의하는 다른 방법들과 주의 사항이 여러 가지 있습니다. 그 내용은 다음 섹션에서 다룹니다.
5.5 Container networking caveats and customizations
네트워킹은 모든 종류의 애플리케이션과 다양한 맥락에서 사용됩니다. 일부는 오늘날 충족될 수 없거나, 추가적인 네트워크 사용자 정의를 요구할 수 있는 요구 사항을 제시합니다. 이 섹션은 네트워크 애플리케이션을 위한 컨테이너를 채택하는 모든 사용자가 익숙해야 할 몇 가지 주제를 짧게 다룹니다.
5.5.1 No firewalls or network policies
오늘날 Docker 컨테이너 네트워크는 컨테이너 간의 어떠한 접근 제어나 방화벽 메커니즘도 제공하지 않습니다. Docker 네트워킹은 Docker의 많은 다른 곳에서 사용되는 네임스페이스 모델을 따르도록 설계되었습니다. 네임스페이스 모델은 자원 접근 제어 문제를 주소 지정성 문제로 변환함으로써 해결합니다. 생각하는 바는 같은 컨테이너 네트워크 안의 두 컨테이너에 있는 소프트웨어는 서로 통신할 수 있어야 한다는 것입니다. 실제로, 이것은 사실과 거리가 멀고, 애플리케이션 레벨의 인증 및 권한 부여 없이는 같은 네트워크에 있는 컨테이너들이 서로를 보호할 수 있는 것은 없습니다. 다른 애플리케이션은 다른 취약점을 가지고 있으며, 다른 호스트에서 다른 보안 자세를 가진 컨테이너에서 실행될 수 있습니다. 타협된 애플리케이션은 네트워크 연결을 열기 전에 권한을 상승시킬 필요가 없습니다. 방화벽은 당신을 보호해주지 않을 것입니다.
이 설계 결정은 우리가 인터네트워크 서비스 의존성을 구축하고 일반적인 서비스 배포를 모델링하는 방식에 영향을 미칩니다. 간단히 말해서, 항상 적절한 애플리케이션 수준의 접근 제어 메커니즘을 가진 컨테이너를 배포하세요. 왜냐하면 같은 컨테이너 네트워크에 있는 컨테이너들은 상호간 (양방향) 제한 없는 네트워크 접근을 가질 것이기 때문입니다.
Docker에서 컨테이너들은 각자의 작은 가상 환경에서 실행되지만, 이들은 '네트워크'를 통해 서로 통신할 수 있어요. Docker는 컨테이너들이 서로 소통할 수 있는 방법을 제공하는데, 여기에는 '브리지 네트워크', '호스트 네트워크', '오버레이 네트워크' 등 다양한 종류가 있습니다. 그런데 Docker의 기본적인 네트워킹 접근 방식에는 중요한 제한이 하나 있어요. 그것은 바로 컨테이너들 간의 네트워크 통신에 대한 세부적인 접근 제어나 방화벽과 같은 보안 메커니즘이 기본적으로 포함되어 있지 않다는 거예요. 이는 같은 네트워크에 있는 컨테이너들이 기본적으로 서로 자유롭게 통신할 수 있다는 의미입니다.
이런 상황에서 중요한 포인트는, 만약 서로 다른 용도의 컨테이너들이 같은 네트워크에 있다면, 그중 하나가 취약점을 가지고 있어 공격자에 의해 타협될 경우, 같은 네트워크에 있는 다른 컨테이너들에게도 위험이 될 수 있다는 거예요. 예를 들어, 하나의 컨테이너가 해커에 의해 제어된다면, 그 컨테이너는 네트워크를 통해 다른 컨테이너에 접근할 수 있게 되고, 그 결과 다른 컨테이너들도 위험에 노출될 수 있습니다.
이러한 문제를 방지하기 위해서, Docker 컨테이너를 배포할 때는 항상 애플리케이션 수준에서의 접근 제어와 인증/인가 메커니즘을 적절히 구현해야 합니다. 즉, 네트워크 상에서 서로 통신할 수 있는 컨테이너들 사이에도, 서로가 서로에게 안전한지를 확인할 수 있는 절차를 마련해야 한다는 의미죠. 이렇게 함으로써, 심지어 같은 네트워크 안에서도, 믿을 수 있는 컨테이너만이 서로 통신할 수 있도록 제한할 수 있어요.
5.5.2 Custom DNS configuration
도메인 이름 시스템(DNS)은 호스트 이름을 IP 주소로 매핑하는 프로토콜입니다. 이 매핑은 클라이언트가 특정 호스트 IP에 대한 의존성에서 벗어나 대신 알려진 이름으로 참조되는 어떤 호스트에도 의존할 수 있게 합니다. IP 주소에 대한 이름을 생성하여 외부 통신을 변경하는 가장 기본적인 방법 중 하나입니다.
일반적으로 브리지 네트워크에 있는 컨테이너와 네트워크상의 다른 컴퓨터는 공개적으로 라우팅할 수 없는 Private IP 주소를 가지고 있습니다. 이는 자신만의 DNS 서버를 운영하지 않는 한, 이름으로 그들을 참조할 수 없다는 것을 의미합니다. Docker는 새 컨테이너에 대한 DNS 구성을 사용자 정의하기 위한 다양한 옵션을 제공합니다.
첫째, docker run 명령어는 새 컨테이너의 호스트 이름을 설정할 수 있는 --hostname 플래그를 가지고 있습니다. 이 플래그는 컨테이너 내부의 DNS 재정의 시스템에 항목을 추가합니다. 이 항목은 제공된 호스트 이름을 컨테이너의 브리지 IP 주소로 매핑합니다.
docker run --rm \
--hostname barker \
alpine:3.8 \
nslookup barker
이 예시는 barker라는 호스트 이름을 가진 새 컨테이너를 생성하고, 동일한 이름에 대한 IP 주소를 조회하는 프로그램[nslookup barker]을 실행합니다. 이 예시를 실행하면 다음과 같은 출력이 생성됩니다:
Server: 10.0.2.3
Address 1: 10.0.2.3
Name: barker
Address 1: 172.17.0.22 barker
마지막 라인의 IP 주소는 새 컨테이너의 브리지 IP 주소입니다. Server라고 표시된 라인에 제공된 IP 주소는 매핑을 제공한[provided the mapping] 서버의 주소입니다.
"매핑을 제공한 서버의 주소"라는 표현은 DNS 조회 과정에서 사용되는 용어로, DNS 서버의 IP 주소를 의미합니다. 즉, 이는 호스트 이름을 IP 주소로 변환하는 작업을 수행한 DNS 서버의 주소를 가리킵니다.
DNS(Domain Name System)는 인터넷에서 호스트 이름(예: www.example.com)을 해당하는 IP 주소(예: 192.0.2.1)로 변환하는 시스템입니다. 이 과정을 통해, 우리는 복잡한 IP 주소를 기억하지 않고도 웹사이트에 쉽게 접근할 수 있습니다.
예를 들어, 컨테이너 내부에서 특정 도메인(예: docker.com)에 대한 IP 주소를 조회할 때 사용하는 `nslookup` 명령어를 실행하면, 이 명령어는 설정된 DNS 서버에 요청을 보냅니다. 이 때 "Server"라고 표시된 라인에 나타난 IP 주소는 바로 이 DNS 조회 요청을 처리한 DNS 서버의 주소입니다.
따라서, 위에서 언급된 예시에서 "Server: 10.0.2.3"이라고 나타난 것은, 호스트 이름 'barker'를 IP 주소 '172.17.0.22'로 매핑하는 조회 요청을 처리한 DNS 서버의 IP 주소가 '10.0.2.3'임을 의미합니다. 이 정보를 통해, 어떤 DNS 서버가 호스트 이름과 IP 주소 간의 매핑 정보를 제공했는지 알 수 있습니다.
컨테이너의 호스트 이름을 설정하는 것은 컨테이너 내부에서 실행되는 프로그램이 자신의 IP 주소를 조회하거나 자기 자신을 식별해야 할 때 유용합니다. 다른 컨테이너들은 이 호스트 이름을 모르기 때문에, 그 사용법은 제한적입니다. 하지만 외부 DNS 서버를 사용한다면, 이러한 호스트 이름을 공유할 수 있습니다.
컨테이너의 DNS 구성을 사용자 정의하는 두 번째 옵션은 하나 이상의 DNS 서버를 지정할 수 있는 기능입니다. 예시를 들어, 다음 예시는 새 컨테이너를 생성하고 해당 컨테이너의 DNS 서버를 Google의 공개 DNS 서비스로 설정합니다:
docker run --rm \
--dns 8.8.8.8 \
alpine:3.8 \
nslookup docker.com
특정 DNS 서버를 사용하면, 노트북에서 Docker를 실행하고 자주 인터넷 서비스 제공자를 변경할 경우 일관성을 제공할 수 있습니다. 이는 서비스와 네트워크를 구축하는 사람들에게 중요한 도구입니다. 자신의 DNS 서버를 설정할 때 몇 가지 중요한 사항은 다음과 같습니다:
- 값은 IP 주소여야 합니다. 생각해보면 이유는 명백합니다: 컨테이너는 이름에 대한 조회를 수행하기 위해 DNS 서버가 필요합니다.
- --dns=[] 플래그는 여러 번 설정할 수 있어 여러 DNS 서버를 설정할 수 있습니다(하나 이상이 접근 불가능한 경우를 대비하여).
- --dns=[] 플래그는 백그라운드에서 실행되는 Docker 엔진을 시작할 때 설정할 수 있습니다. 이렇게 하면, 이러한 DNS 서버가 기본적으로 모든 컨테이너에 설정됩니다. 하지만 엔진을 실행 중인 컨테이너와 함께 중지하고 엔진을 다시 시작할 때 기본값을 변경하는 경우, 실행 중인 컨테이너는 여전히 이전 DNS 설정을 가지고 있을 것입니다. 변경사항이 적용되려면 이러한 컨테이너를 다시 시작해야 합니다.
세 번째 DNS 관련 옵션인 `--dns-search=[]`를 통해 DNS 검색 도메인을 지정할 수 있습니다. 이는 기본 호스트 이름 접미사와 같습니다. 하나를 설정하면, 알려진 최상위 도메인(예: .com 또는 .net)이 없는 모든 호스트 이름이 지정된 접미사가 추가된 상태로 검색됩니다.
docker run --rm \
--dns-search docker.com \
alpine:3.8 \
nslookup hub
이 명령은 DNS 검색 도메인이 제공되어 호스트 이름을 완성하기 때문에 hub.docker.com의 IP 주소를 해석하게 됩니다. 이는 /etc/resolv.conf, 즉 일반 이름 해석 라이브러리[common name-resolution libraries]를 구성하는 데 사용되는 파일을 조작함으로써 작동합니다. 다음 명령은 이러한 DNS 조작 옵션이 파일에 어떤 영향을 미치는지 보여줍니다:
docker run --rm \
--dns-search docker.com \
--dns 1.1.1.1 \
alpine:3.8 cat /etc/resolv.conf
# Will display contents that look like:
# search docker.com
# nameserver 1.1.1.1
이 기능은 주로 내부 기업 네트워크에 대한 단축 이름과 같은 사소한 것들에 사용됩니다. 예를 들어, 귀하의 회사가 내부 문서 위키를 유지 관리할 수 있으며, 단순히 http://wiki/에서 참조할 수 있습니다. 하지만 이 기능은 훨씬 더 강력할 수 있습니다.
개발 및 테스트 환경에 대한 단일 DNS 서버를 유지 관리한다고 가정해 보겠습니다. 환경에 특정된 이름(예: myservice.dev.mycompany.com)을 하드코딩한 환경 인식 소프트웨어를 구축하는 대신, DNS 검색 도메인을 사용하고 환경을 인식하지 못하는 이름(예: myservice)을 사용하는 것을 고려할 수 있습니다.
docker run --rm \
--dns-search dev.mycompany \
alpine:3.8 \
nslookup myservice
docker run --rm \
--dns-search test.mycompany \
alpine:3.8 \
nslookup myservice
이 패턴을 사용할 때, 변경되는 것은 프로그램이 실행되는 컨텍스트뿐입니다. 사용자 지정 DNS 서버를 제공하는 것처럼, 동일한 컨테이너에 여러 사용자 지정 검색 도메인을 제공할 수 있습니다. 단순히 검색 도메인이 있는 만큼 플래그를 설정하면 됩니다. 예를 들어:
docker run --rm \
--dns-search mycompany \
--dns-search myothercompany ...
이 플래그는 Docker 엔진을 시작할 때 설정하여 생성되는 모든 컨테이너에 대한 기본값을 제공할 수도 있습니다. 다시 한번, 이 옵션들은 컨테이너가 생성될 때만 설정된다는 것을 기억하세요. 컨테이너가 실행 중일 때 기본값을 변경하면, 그 컨테이너는 이전 값들을 유지할 것입니다.
고려해야 할 마지막 DNS 기능은 DNS 시스템을 오버라이드할 수 있는 기능을 제공합니다. 이는 `--hostname` 플래그가 사용하는 같은 시스템을 사용합니다. `docker run` 명령의 `--add-host=[]` 플래그를 사용하면 IP 주소와 호스트 이름 쌍에 대한 사용자 지정 매핑을 제공할 수 있습니다:
docker run --rm \
--add-host test:10.10.10.255 \
alpine:3.8 \
nslookup test
--dns 및 --dns-search와 마찬가지로, 이 옵션은 여러 번 지정될 수 있습니다. 하지만 다른 옵션들과 달리, 이 플래그는 엔진 시작 시 디폴트 값으로 설정될 수 없습니다.
이 기능은 일종의 이름 해석용 스캘펠입니다. 개별 컨테이너에 대해 구체적인 이름 매핑을 제공하는 것은 가능한 가장 세밀한 사용자 정의입니다. 이를 사용하여 127.0.0.1과 같은 알려진 IP 주소로 특정 호스트 이름을 매핑함으로써 효과적으로 타겟된 호스트 이름을 차단할 수 있습니다. 또한 특정 목적지로의 트래픽을 프록시를 통해 라우팅하는 데 사용할 수 있습니다. 이 방법은 종종 비안전한 트래픽을 SSH 터널과 같은 안전한 채널을 통해 라우팅하기 위해 사용됩니다. 이러한 오버라이드를 추가하는 것은 웹 애플리케이션의 자체 로컬 복사본을 실행하는 웹 개발자들이 수년간 사용해 온 트릭입니다. 이름-IP 주소 매핑이 제공하는 인터페이스에 대해 조금만 생각해 보면, 여러분은 다양한 용도로 이를 활용할 수 있을 것입니다.
모든 사용자 지정 매핑은 컨테이너 내부의 `/etc/hosts` 파일에 존재합니다. 어떤 오버라이드가 적용되어 있는지 보고 싶다면, 그 파일을 검사하기만 하면 됩니다. 이 파일을 편집하고 파싱하는 규칙은 온라인에서 찾을 수 있으며, 이 책의 범위를 조금 벗어납니다:
docker run --rm \
--hostname mycontainer \
--add-host docker.com:127.0.0.1 \
--add-host test:10.10.10.2 \
alpine:3.8 \
cat /etc/hosts
이 명령은 다음과 같은 출력을 생성해야 합니다:
172.17.0.45 mycontainer
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.10.10.2 test
127.0.0.1 docker.com
DNS는 동작을 변경하는 강력한 시스템입니다. name-IP 주소 매핑은 사람들과 프로그램이 특정 네트워크 주소로부터 자신을 분리할 수 있게 하는 간단한 인터페이스를 제공합니다. 만약 DNS가 아웃바운드 트래픽 동작을 변경하는 데 최고의 도구라면, 방화벽과 네트워크 토폴로지는 인바운드 트래픽을 제어하는 데 있어 최고의 도구입니다.
5.5.3 Externalizing network management
일부 조직, 인프라, 또는 제품은 컨테이너 네트워크 구성, 서비스 발견 및 기타 네트워크 관련 자원의 직접적인 관리를 요구합니다. 이런 경우에, 당신 또는 사용 중인 컨테이너 오케스트레이터는 Docker의 none 네트워크를 사용하여 컨테이너를 생성한 다음, 다른 컨테이너 인식 도구를 사용하여 컨테이너 네트워크 인터페이스를 생성 및 관리하고, NodePort Publishing를 관리하며, 컨테이너를 서비스 발견 시스템에 등록하고, 상위 로드 밸런싱 시스템과 통합합니다.
쿠버네티스는 네트워킹 제공업체의 전체 생태계를 가지고 있으며, 쿠버네티스를 사용하는 방식(프로젝트로서, 상품화된 배포 또는 관리되는 서비스로서)에 따라 어떤 제공업체를 사용할지에 대한 선택권이 있을 수도 있고 없을 수도 있습니다. 쿠버네티스의 네트워킹 옵션에 관한 책을 통째로 쓸 수 있을 것입니다. 여기서 그들을 요약하려고 시도하는 것은 부적절할 것입니다.
네트워크 제공업체 계층 위에서, 서비스 발견 도구의 전체 연속체가 리눅스와 컨테이너 기술의 다양한 기능을 사용합니다. 서비스 발견은 해결된 문제가 아니므로, 해결책의 풍경은 빠르게 변화합니다. Docker 네트워킹 구성 요소가 통합 및 관리 문제를 해결하기에 충분하지 않다고 판단되면, 현장을 조사하세요. 각 도구는 자체 문서와 구현 패턴을 가지고 있으며, Docker와 효과적으로 통합하기 위해 그 가이드를 참조해야 할 것입니다.
네트워크 관리를 외부화할 때, Docker는 여전히 컨테이너의 네트워크 네임스페이스를 생성하는 책임이 있지만, 네트워크 인터페이스를 생성하거나 관리하지 않습니다. Docker 도구를 사용하여 네트워크 구성이나 포트 매핑을 검사할 수 없게 됩니다. 일부 컨테이너 네트워킹이 외부화된 혼합 환경에서 실행하는 경우, 내장된 서비스 발견 메커니즘을 사용하여 Docker 관리 컨테이너로부터 외부화된 컨테이너로 트래픽을 라우팅할 수 없습니다. 혼합 환경은 드물고 피해야 합니다.
요약
네트워킹은 제대로 다루기 위해 여러 권의 책이 필요한 광범위한 주제입니다. 이 장은 네트워크 기초에 대한 기본적인 이해를 가진 독자들이 Docker가 제공하는 단일 호스트 네트워킹 시설을 채택하는 데 도움이 되어야 합니다. 이 자료를 읽음으로써, 여러분은 다음을 배웠습니다:
- Docker 네트워크는 컨테이너, 볼륨, 이미지처럼 생성, 나열, 제거될 수 있는 일급 객체입니다.
- 브리지 네트워크는 내장된 컨테이너 이름 해석[resolution] 기능을 가진 직접적인 컨테이너 간 네트워크 통신을 허용하는 특별한 종류의 네트워크입니다.
- Docker는 기본적으로 두 가지 특별한 네트워크를 제공합니다: host와 none.
- none 드라이버로 생성된 네트워크는 연결된 컨테이너를 네트워크로부터 격리시킬 것입니다.
- 호스트 네트워크에 있는 컨테이너는 호스트의 네트워크 시설과 인터페이스에 전체 접근 권한을 가집니다.
- NodePort Publishing를 통해 호스트 포트로의 네트워크 트래픽을 대상 컨테이너와 포트로 전달합니다.
- Docker 브리지 네트워크는 네트워크 방화벽이나 접근 제어 기능을 제공하지 않습니다.
- 각 컨테이너의 네트워크 이름 해석[resolution] 스택은 사용자 정의될 수 있습니다. 사용자 정의 DNS 서버, 검색 도메인, 그리고 정적 호스트를 정의할 수 있습니다.
- 네트워크 관리는 Docker의 none 네트워크를 사용하고 타사 도구를 통해 외부화될 수 있습니다.
'Docker' 카테고리의 다른 글
7 Packaging software in images (0) | 2024.04.03 |
---|---|
6 Limiting risk with resource controls (0) | 2024.04.03 |
4. Working with storage and volumes (0) | 2024.03.26 |
3. Software installation simplified (0) | 2024.03.25 |
2. Running software in containers (0) | 2024.03.20 |