ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 5. Docker Engine, Docker 스토리지 그리고 Docker 네트워킹
    DevOps/Docker 2023. 5. 14. 17:29

    Docker Engine

    - Docker가 설치된 호스트

    - Linux 호스트에 Docker를 설치하면 사실 컴포넌트가 세 개 설치된다.

        ㆍDocker CLI : 명령줄 인터페이스, 컨테이너 실행, 중지 및 이미지 제거 등에 사용된다.

                                  REST API를 사용하여 Docker Daemon과 상호 작용하는데

                                  이때는 Docker CLI 가 반드시 같은 호스트에 없어도 된다.

        ㆍREST API : 프로그램이 쓰는 API 인터페이스로 Daemon과 통신하고 명령어를 제공할 때 사용

        ㆍDocker Daemon : Docker 객체인 이미지와 컨테이너, 볼륨 및 네트워크를 관리하는 백그라운드 프로세스

     

     

    -  Docker는 Namespace로 공간을 구분하며, 프로세스 ID와 네트워크, 프로세스 간 통신, 마운트 및 Unix 시분할(Timesharing) 시스템이 독립된 Namespace에 생성되고 따라서 컨테이너가 각각 분리된다.

     

    Namespace 분리 기술 중 프로세스 ID(PID)

    - Linux 시스템이 부팅될 때마다 PID 1인 프로세스 하나로 시작한다.

        ㆍ즉, 시스템의 다른 모든 프로세스를 시작하는 루트 프로세스이다.

    - 시스템이 완전히 부팅될 때까지는 소수의 프로세스만 실행된다.

    - ps 명령어를 사용하여 실행 중인 모든 프로세스를 확인할 수 있다.

    - PID는 고유하며 프로세스 두 개가 같은 PID를 가질 수는 없다.

    - 만약 현재 시스템 내부에 자식 시스템 컨테이너를 생성하면 자식 시스템은 자체적으로 독립된 시스템으로 취급해야한다.

    - 하지만 컨테이너와 기본 호스트가 물리적으로 분리되지는 않는다.

    - 컨테이너 안에 실행되는 프로세스는 사실 기본 호스트에서 실행된다.

    - 그래서 두 프로세스가 하나의 프로세스 ID를 가질 수 없다.

    - 이때 Namespace 가 필요하다

    - 프로세스 ID Namespace 로 각 프로세스는 여러 PID를 연결하여 가질 수 있게 된다.

     

    - Namespace 에서는 같은 프로세스에 다수의 프로세스 ID를 부여해 컨테이너가 이를 그 컨테이너의 루트 프로세스로 인식하게 한다.

        ㆍ그러나 사실은 근간의 Docker 호스트에서 실행되는 다른 프로세스다.

    - 컨테이너화하면 이 프로세스를 격리시켜 Namespacce를 사용한 컨테이너 안에서 실행시킬 수 있다.

    - 컨테이너 내부에서는 PID 1로 실행되고 Docker 호스트 외부에서는 PID가 다른 별개 프로세스로 실행된다.

     

    Docker 스토리지

    - Docker를 시스템에 설치하면 /var/lib/docker 와 같은 폴더 구조가 생기고 그 안에 aufs, containers, image, volumes 등 여러 폴더가 생성되고 Docker는 기본적으로 이 경로에 모든 데이터를 저장한다.

    - 여기서 데이터란 Docker 호스트에서 실행되는 이미지와 컨테이너 관련 파일이다.

    - 예를 들어, 컨테이너와 관련되 파일은 모두 containers 폴더에, 이미지와 관련된 파일은 image 폴더에, 그리고 Docker 컨테이너에서 생성한 볼륨은 volumes 폴더에 저장된다.

     

    * Docker는 캐시에서 이전 레이어를 그대로 가져와 최신 소스 코드만 업데이트해서 앱 이미지를 바로 생성해 주므로 재구축과 업데이트 시 시간을 많이 절약할 수 있다.

     

     

    - 모든 레이어는 docker build 명령어를 실행하여 최종 Docker 이미지 생성시 생성되는 Docker 이미지 레이어들이다.

    - 그리고 한 번 구축이 완료되면 레이어 내용을 수정할 수 없고 읽기 전용 상태이기 때문에 새로운 빌드를 만들어 수정해야 한다.

    - docker run  명령어로 이 이미지에 기반해 컨테이너를 실행하면 Docker 에서는 이 레이어들에 기반한 컨테이너를 생서앟고 이미지 레이어 위에 쓰기 가능한 새로운 레이어를 생성한다.

    - 이 쓰기 레이어에는 컨테이너에서 생성한 데이터가 저장된다.

        ㆍ애플리케이션에서 작성한 로그파일, 컨테이너에서 생성된 임시 파일,

            또는 컨테이너에서 사용자가 수정한 파일들이 해당된다.

    - 이 레이어는 컨테이너가 활성 상태일 때만 사용할 수 있으며, 컨테이너가 없어지면 레이어와 거기에 저장된 모든 변경 사항도 함께 없어진다.

    - 해당 이미지로 생성한 컨테이너들은 모두 같은 레이어를 공유한다.

     

    copy-on-write(COW)

    - 컨테이너를 실행하고 변경 사항 테스트를 위해 코드를 수정하고 싶다면?

    - 수정 사항을 저장하기 전에 Docker에서는 읽기/쓰기 레이어에 사본을 생성하기 때문에 사실상 읽기/쓰기 레이어에 다른 파일 버전을 수정하게 되고 이후 변경 사항은 모두 읽기/쓰기 레이어에 저장된다.

     

    - 이미지 레이어는 읽기 전용이므로 레어이 내 파일은 이미지 자체에서는 수정할 수 없기 때문에 이미지는 계속 같은 상태가 되고 docker build 명령어로 이미지를 재구축해야만 파일을 변경할 수 있다.

    - 여기서 컨테이너를 없애면 컨테이너에 저장된 모든 데이터도 삭제된다.

     

    그러면 데이터를 보존하려면 어떻게 해야할까?

    - 컨테이너에서 생성된 데이터를 보존하고 싶다면 컨테이너에 영구 볼륨을 추가하면 된다.

    - 이를 위해서는 docker volume create 명령어로 볼륨을 먼저 생성해야 한다.

    - 데이터 베이스에 쓰인 모든 데이터는 Docker 호스트에서 생성한 볼륨에 저장된다.

    - 따라서 컨테이너가 없어져도 데이터는 그대로 활성 상태이다.

    - docker run -v file_name:file_path 명령어시 file이 없다면 file_name대로 볼륨을 생성해 컨테이너에 마운트해준다.

    - 모든 볼륨을 확인해볼려면 /var/lib/docker/volumes 폴더에 있다.

    - 이것을 볼륨 마운팅(volume mounting) 이라고 하며 Docker에서 생성한 볼륨을 /var/lib/docker/volumes 폴더에 마운트하는 방식이다.

     

    만약 데이터가 다른 위치에 있는 경우에는 어떻게 해야 할까?

    - 가령 /data 디렉터리에 외장 스토리지가 있는데 그 볼륨에 데이터 베이스 데이터를 저장하고 싶다.

    - 기본값인 /var/lib/docker/volumes 폴더가 아니라.

    - docker run -v 명령어에서 마운트하고자 하는 전체 경로를 입력해줘야한다.

    - 그러면 컨테이너가 생성되고 폴더를 컨테이너에 마운트할 수 있다. - 이걸 바운드 마운팅(bind mounting)이라고 한다.

     

     

    - 볼륨 마운팅 : volumes 디렉터리에 볼륨을 마운트하는 것

    - 바인드 마운팅 : Docker 호스트에 원하는 디렉터리를 마운트하는 것

     

     

    - -v 옵션은 구식 방법이고 새로운 방식으로는 -mount 옵션이 있는데 하이픈(-)을 두 번 쓴 --mount가 보다 길어서 더 널리 쓰인다.

    - --mount 옵션에서는 매개변수를 값 형식에 맞추어 키에 적어야 한다.
    - 예를 들어 이전에 실행한 명령어는 -mount 옵션에서는 이렇게 type, source, target 옵션을 적어 주어야 한다.

        ㆍtype 은 bind

        ㆍsource는 호스트의 위치

        ㆍtarget은 컨테이너의 위치

     

     

    이 모든 것을 수행하는 주체는 무엇일까?

    - 계층형 아키텍처 관리, 쓰기, 레이어 생성, 레이어 간 파일 이동과 복사, 쓰기는 스토리지 드라이버 덕분에 가능하다.

    - Docker는 스토리지 드라이버를 통해 계층형 아키텍처를 구현한다.

    - 주로 사용되는 스토리지 드라이버로는 AUFS, ZFS,BTRFS, Device Mapper, Overlay, Overlay2 가 있다.

    - 기반 운영체제에 따라 다르게 사용한다. Ubuntu의 경우에는 기본 스토리지 드라이버는 AUFS이고 이 드라이버는 Fedora나 CentOS와 같은 다른 운영 체제에서는 사용할 수 없으므로 이 경우는 Device Mapper를 사용하는 편이 좋다.

    - Docker는 운영 체제에 가장 적합한 스토리지 드라이버를 선택해 준다.

    - 그리고 드라이버마다 성능과 안정성이 다르기 때문에 애플리케이션과 조직에 적합한 드라이버를 선택해야 한다.

     

     

    - 스토리지 드라이버마다 데이터를 다르게 저장하는데 AUFS 스토리지 드라이버는 diff, layers, mnt 라는 세 개의 폴더에 저장한다.

        ㆍdiff 폴더 : 각 계층의 콘텐츠가 저장, Docker 파일의 명령어마다 새 계층이 생성,

                            Dokcer 파일 안에서 Dokcer 이미지에 소스 코드를 추가하면 새로운 계층이 생성되고

                            애플리케이션의 모든 소스 코드가 해당 계층에 저장된다.

                            즉, 계층은 스토리지 드라이버가 매핑한 또 다른 폴더일 뿐이며

                            애플리케이션의 소스 코드를 이미지에 추가하면 모든 소스 코드가 해당 계층에 복사된다

        ㆍlayers 폴더 : 이미지 계층 스택의 메타데이터를 저장

        ㆍmnt 폴더 : 마운트 포인트 정보를 저장

     

    * docker system df

    - Docker의 디스크 사용 용량을 알려준다.

    - 이미지, 컨테이너, 로컬 볼륨 별로 디스크 사용량을 알려준다.

    - -v 옵션 추가시 이미지별로 디스크 사용 용량 확인

     

    * du -sh *

    - 각 폴더의 크기를 알 수 있는 명령어

     

    Docker 네트워킹

    - Docker를 설치하면 자동으로 브리지, none(null), 호스트 세 네트워크를 생성한다.

     

    - 컨테이너를 다른 네트워크와 연결하려면 network 명령어 매개변수로 네트워크 정보를 지정한다.

     

    브리지

    - 컨테이너에서 사용하는 기본 네트워크이다.

    - 브리지 네트워크는 Docker 가 호스트에 생성한 프라이빗 내부 네트워크이다.

    - 모든 컨테이너는 기본적으로 이 네트워크에 연결되며 보통 172.17로 시작하는 내부 IP가 할당된다.

    - 이 내부 IP를 사용해 컨테이너끼리 액세스할 수 있다.

    - 외부에서 이 컨테이너에 액세스하려면 컨테이너의 포트를 Docker 호스트의 포트에 매핑한다.

     

    호스트

     - 외부에서 액세스하는 방법 2번째는 컨테이너를 호스트 네트워크에 연결하는 방법이 있다.

    - 이 방법은 Docker 호스트와 Docker 컨테이너에 네트워크 격리를 일으킨다.

    - 가령 웹 앱 컨테이너에서 포트 5000에 웹 서버를 작동시키려면 웹 컨테이너에서 호스트 네트워크를 사용할 경우 포트 매핑 없이 자동으로 같은 포트에 외부 접속이 가능하다.

    - 같은 포트의 같은 호스트에서 다수의 웹 컨테이너를 실행할 수 없다.

    - 호스트 네트워크에서는 포트가 모든 컨테이너에 공유되기 때문이다.

     

    none

    - 컨테이너는 네트워크의 테스트가 아니다.

    - 또 외부 네트워크나 다른 컨테이너에 엑세스할 수없다.

    - 격리된 네트워크에서 실행된다.

     

    - 기본적으로 Docker는 하나의 내부 브리지 네트워크만 생성한다.

    - 명령어를 사용해 내부 네트워크를 생성하려면 docker network create를 입력한 후 드라이버를 bridge로 지정하고 해당 네트워크 서브넷을 입력한 다음 네트워크명 custom-isolated-network를 입력

    - docker network ls 명령어로 모든 네트워크 목록을 표시한다.

     

    기존의 컨테이너에 배정된 네트워크 설정과 IP 주소를 보려면 어떻게 할까요?

    - docker inspect 명령어 다음에 컨테이너의 ID나 이름을 입력, 컨테이너가 배정된 네트워크 유형 확인

        ㆍ내부 IP 주소와 MAC 주소 기타 설정도 볼 수 있다.

     

    - 컨테이너끼리는 이름을 사용해 서로 액세스할 수 있다.

    - 내부 IP를 사용해 액세스하는 것은 시스템이 재부팅되었을 때 같은 IP가 배정된다는 보장이 없어 그다지 좋은 방법이 아니다.

    - 정답은 컨테이너 이름을 사용하는 것이다.

    - Docker에는 컨테이너들이 이름으로 서로 액세스할 수 있게 도와주는 DNS 서버가 내장되어 있다.

    - 내장 DNS 서버는 항상 127.0.0.11 주소로 작동한다.

     

    그렇다면 Docker는 어떻게 네트워킹을 실행할까? 어떤 기술을 사용할까?

    어떻게 호스트안에서 컨테이너를 격리시킬까?

    - Docker는 네트워크 Namespace를 사용해 각 컨테이너에 별개의 Namespace를 생성한다.

    - 그리고 가상 이더넷 쌍을 사용해 컨테이너를 함께 연결한다.

     

     

     

    댓글

Designed by Tistory.