Search
Duplicate
🐳

Docker의 아키텍처

간단소개
도커의 아키텍처에 대해 알아봅니다.
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Docker
태그
Docker
container
Scrap
8 more properties
원본 게시물입니다!!!!!
아래의 명령어를 실행하면 무슨 일이 일어날까?
docker run nginx
Plain Text
이 명령어를 실행하기 전에 간단히 용어를 정의하자
도커는 클라이언트로서의 도커(도커 클라이언트), 서버로서의 도커로 나뉜다.
도커의 핵심인 도커 엔진도커 클라이언트(Cli 명령어 도구)도커 데몬을 포함하고 있다.
컨테이너를 생성하고 실행하며 이미지를 관리하는 주제는 서버도커이다.
도커 데몬이 서버로 떠 있고 도커 클라이언트가 명령어를 보내면 도커 데몬에서 실제 동작이 이루어진다.
docker run 명령어 또한 마찬가지다. "docker run"이라는 명령어를 도커 클라이언트가 도커 데몬으로 전달하면 도커 데몬이 명령어를 해석해 이미지를 다운 받고 컨테이너가 실행된다.

docker run nginx

설치는 이곳을 참고했다. (CentOS 환경)
우선 로컬(기본 저장소인 /var/lib/docker/image)에서 nginx라는 서버 이미지가 있는지 찾는다.
도커 이미지는 layer 아키텍처를 갖고 있기 때문에 위의 저장소에 가도 찾기 어려울 것이다.
현재 nginx 이미지를 지워둔 상태라 캡처이미지 상단에 다음과 같이 뜬다.
Unable to fine image 'nginx:latest' locally latest: Pulling from library/nginx
현재 로컬에 nginx이미지가 존재하지 않기에 도커 데몬은 설정 되어 있는 외부 도커 이미지 저장소(디폴트: DockerHub)에서 찾는다. AWS에서는 ECR
이는 Registry에 적혀있다. https://index.docker.io/v1/ 은 도커 허브의 FQDN이다. 도커 허브에서 nignx이미지를 다운받고 디폴트 경로인 /var/lib/docker/image에 설치하고 이곳에서 설명한 chroot, cgroup, namespace 등의 프로세스 격리 기술을 이용해 컨테이너를 격리하고 기동한다.

스토리지 드라이버

경로 /var/lib/docker/image에 들어가면
overlay2
Plain Text
라는 디렉토리가 존재한다. overlay2는
스토리지 드라이버(Storage driver)
Plain Text
를 말한다.
스토리지 드라이버는 이미지 및 컨테이너가 Docker 호스트에 저장 및 관리되는 방법을 제어한다.
도커가 엔진만 깔려있다면 이곳저곳에서 모두 실행가능 한다고 해도 도커도 결국엔 소프트웨어이니 최적의 성능을 발휘하는 환경이 있지 않을까?
컴퓨터 구조에서 가장 느린 부분은 디스크 입출력이므로 가장 신경 써야 하는 것은 스토리지 드라이버다.
대부분의 OS에서 가장 좋은 성능을 낼 수 있는 스토리지 드라이버가 overlay2다.
도커 공식 문서에서도 모든 Linux배포판에 대해 overlay2를 사용하는 것을 권장하고 있다. 따라서 서버를 구성할 때도 overlay2를 지원하는 스토리지로 서버를 구성하는 것이 옳다.
그 밑에는 Supported backing filesystems이 적혀있다.
백킹 파일시스템은 실제 도커가 파일을 저장할 때 사용되는 파일 시스템이다.
overlay2를 지원하는 xfs를 쓰라고 나와 있는데
디폴트 스토리지 유형과 파일 시스템이 이미 overlay2, xfs로 설정된 것을 확인할 수 있다.
다시 돌아와서 현재까지의 과정을 정리해보자
동일 서버에서 도커 클라이언트로 도커 데몬에 명령어를 보냈고 도커 데몬이 명령어를 해석해서 필요한 이미지를 DockeHub로 부터 pulling해왔다. 가져온 이미지를 사용해 컨테이너를 만들어
docker engine(고래다 이거)
Plain Text
위에서 동작하게 했다.
결과적으로 nignx라는 웹서버를 기동했다.

서버 접근 방법

위에서 chroot, cgroup, namespace 등의 프로세스 격리 기술을 이용해 컨테이너를 격리하고 기동한다.라고 말했는데 namespace는 프로세스 간에 네트워크 충돌을 방지하기 위해 네트워크 디바이스, IP주소, 포트 번호, 라우팅 테이블, 필터링 테이블 등과 같은 네트워크 리소스를 독립적으로 가질 수 있게 해준다.
따라서 서버를 만들 때 서버에 접근할 수 있는 내부 IP 하나가 바인딩 된다.
host network 설정 중 밑에서 4번째 줄을 보면
docker0
Plain Text
라 쓰여 있는 것을 볼 수 있다. 도커는
브릿지 유형(docker0)
Plain Text
의 네트워크를 사용한다.
도커를 설치하면 docker0라는 내부 ip가 만들어지는데, 이 ip가 게이트웨이로서 HOST OS와 컨테이너 사이의 커뮤니케이션을 가능하게 한다.
그림을 보면 고래에 배꼽이 생긴 것을 볼 수 있는데 저 배꼽이
docker0
Plain Text
다.
노란색 선을 보면 알 수 있듯이 HOST OS와 컨테이너를 연결해준다. 또한 컨테이너의 ip는 docker0의 ip 끝자리가 1씩 증가되어 할당된다.
하지만 할당된 컨테이너의 IP는 Private IP이므로 동일 서버에서만 유효하다. 그뿐만 아니라 위의 사진처럼 namespace에 의해 여러 개의 컨테이너가 기동 되고 있다면 public ip로 접속했을 때 어떤 컨테이너로 가야 하는지 알 수 없다.
이 문제는 포트 포워딩으로 해결할 수 있다.
아래의 명령어를 사용해 컨테이너를 실행할 때 포트 번호를 지정해 주었다.
docker run -p 8080:81 nginx
Plain Text
결과적으로 아래와 같이 연결될 것이다.
1.
HOST IP의 8080 포트로 접근
2.
HOST 8080 포트를 Container(nginx)의 81포트로 포워딩
3.
nignx 웹서버로 접근
이렇게 웹서버에 접근할 수 있다.

서버 접근 속도

도커가 애플리케이션의 속도를 높이기 위해 중요한 것들 중 하나는 네트워크 속도이다.
도커는 커널 기반의 네트워크 기능인 Netfilter를 사용한다. Netfilter는 여러 기능을 수행한다.
1.
패킷 선택 및 필터링(iptables)
2.
네트워크 주소 변환(NAT)
3.
라우팅 전후로 패킷 헤더의 내용을 수정(Mangling)
4.
연결 추적
5.
네트워크 통계 수집
위에서 특정 컨테이너에 접근하기 위해 포트 포워딩을 진행했다.
docker run -p 8081:80 nginx
Plain Text
포트 포워딩 규칙이 어딘가에 셋팅이 되어야 하는데 이 규칙은 커널 기반의 Netfilter를 설정해주는 iptables에 도커 데몬(dockerd)이 자동으로 설정해 준다.
이처럼 도커가 사용하는 네트워크 기술이 원래 있던 Linux 커널 기반의 기술을 가져다 쓰기 때문에 빠른 속도를 갖는다.

서버 접근기록 저장

서비스 운영에서 로그는 매우매우 중요하다. 컨테이너는 프로세스이기 때문에 실행 도중에 문제가 생겨 죽어버리면 지금까지 쌓인 로그 데이터가 다 날아간다.
따라서 컨테이너를 설계할 때 이런 부분을 생각하고 설계해야 한다. 조금 다른 이야기지만 넷플릭스는 컨테이너를 24시간 이내에 삭제하고 다시 만들어서 해커들의 백도어를 무용지물로 만드는 등 신기한 방식을 사용하고 있다.
그렇다면 언제든 사라질 수 있는 로그 캐시 데이터들은 어디에 저장할까? 별도의 스토리지에 저장해야 한다.
기본적으로 컨테이너는 만들어지는 데이터를 /var/lib/docker/containers//-json.log에 저장한다.
다음의 명령어로 확인할 수 있다.
docker inspect --format "{{.LogPath}}"
Plain Text
json형식인 이유는 도커가 디폴트 Logging Driver로 json-file을 사용하기 때문이다.
다시 돌아와서 언제든 사라질 수 있는 컨테이너 DATA를 어떻게 유지할까? 컨테이너 격리 때 사용된 namespace가 제공하는 기술 중 하나인 mount를 사용하면 가능하다. 컨테이너 외부 공간 HOST의 특정 경로에 mount를 사용할 것이다.
임의의 HOST 데이터 저장경로를 컨테이너의 데이터 저장경로에 mount시킨다. 이것을 도커 명령어로 실행하면 다음과 같다.
$ docker run -v /data/mysql:/opt/db/
Plain Text
이 명령어는 컨테이너를 다시 생성할 때 볼륨이라는 기능으로 mount하는 것인데 다음 포스트에 더 자세히 설명하겠다.
만약 컨테이너가 죽었다면, 새로운 컨테이너를 다시 만들 것이다. 이때 새로운 컨테이너는 동일한 데이터 경로를 갖을 것이므로 mount된 경로에 있는 데이터를 다시 새로운 컨테이너로 옮겨주면 된다.
결과적으로 HOST의 데이터 저장경로인 /data/mysql에 쌓이는 데이터를 nginx서버의 /opt/db에 쌓이는 데이터와 공유한다.
이런 모양이 될 것이다.하지만 HOST의 데이터 저장 공간은 무한하지 않다. 마운트된 파일 시스템의 크기와 용량을 df -Th명령어로 확인하면 루트에 하나의 파일 스토리지만 존재하는 것을 알 수 있다.
이 파일 스토리지는 HOST OS와 같이 있는데 윈도우로 비유하면 컴퓨터에 C드라이브 하나만 존재하는 것이다. 서비스를 운영하면서 사용자의 데이터와 로그 데이터가 쌓여가는데 저장 공간은 한계가 있으니 언젠가는 사용률이 100%가 될 것이다. 심지어 부팅을 할 때도 로그가 쌓이는데 사용률이 100%면 부팅조차 되지 않아 서비스를 운영할 수 없을 것이다.
그래서 설계 단계에서 스토리지에 대한 용량 설계를 해야 한다. 즉, 데이터 및 로그용 영역을 따로 만들어야 한다. 이렇게 하면 언젠가 데이터 및 로그용 영역의 사용률이 100%가 되어도 서비스는 계속 운영될 것이다.
보통의 기업들은 데이터, 로그를 백업, 통합 관리하기 위한 표준 경로가 있다. 이 경로에 맞게 컨테이너의 데이터 저장 경로를 mount해야한다. 지금부터 그 방법을 알아보자

Container 데이터 저장 경로 변경

Container 데이터 저장 경로를 바꾼다고 함은 도커 루트 경로를 바꾼다는 것이다. 이 루트 경로를 바꾸기 위해서는 daemon.json 파일을 수정해야 한다.
cd /etc/docker
Plain Text
명령어를 실행해서 디렉토리에 들어가면 daemon.json 파일이 없는 경우가 있는데 하나 새로 만들면 도커 데몬이 기동할 때 자동으로 daemon.json파일을 확인하고 적용한다. 여기서 logging driver같은 여러 설정을 바꿀 수 있는데 허용된 모든 configuration 옵션은 이곳에 가면 확인할 수 있다.
이제 루트 경로를 바꿔보자 위와 같이 설정하고 도커를 재부팅 한 뒤에 결과를 확인하자
systemctl stop docker systemctl start docker systemctl status docker // 여기서 Active 설정이 running이어야 한다. docker info | grep Root
Plain Text
끝!