docker
도커는 컨테이너 기반 가상화 기술을 활용하여, 호스트 시스템의 커널을 공유하면서 애플리케이션을 실행하는데 필요한 모든 라이브러리 및 종속성을 포함합니다. 이는 가볍고 빠른 실행이 가능하며, 확장성과 유연성이 좋으며, 인프라 구성 관리가 용이하다는 특징을 가지고 있습니다. 최근 웹 프로젝트를 진행하면서, Git Action과 함께 도커를 활용하여 통합 및 배포에 활용하고자 합니다. 이번 포스트에서는 도커에 대해 공부한 내용을 정리하려고 합니다. ‘시작하세요 도커&쿠버네틱스’라는 책을 읽고 학습했습니다.
도커 이미지와 컨테이너
도커 이미지는 여러 계층으로 구성된 바이너리 파일로 존재하며, 컨테이너를 생성하고 실행할 때 읽기 전용으로 사용됩니다. 각 컨테이너에 변경 사항이 발생하면 해당 변경 사항을 별도로 저장하여 각 컨테이너의 정보를 보존합니다. 이미지는 여러 계층으로 구성되며, 새로운 이미지를 생성할 때 변경된 부분만 새로운 계층으로 추가하고, 기존의 계층은 변경되지 않은 채로 유지됩니다. (이는 델타 방식으로 저장 및 관리되는 것으로 생각됩니다.)
도커 이미지의 이름은 기본적으로 [저장소 이름]/[이미지 이름]:[태그]의 형식을 가집니다. 여기서 저장소 이름은 생략할 수 있으며, 생략 시 Docker Hub의 공식 이미지를 가리킵니다. 태그는 이미지의 버전을 나타내며, 생략할 경우 도커 엔진은 latest로 인식합니다. 이미지 이름은 생략할 수 없습니다.
도커 컨테이너를 사용하기 위해서는 4단계가 있습니다.
- 도커 이미지를 내려받습니다. (pull)
- 해당 이미지의 컨테이너를 생성합니다 (create)
- 컨테이너를 실행시킵니다. (start)
- 컨테이너 내부에 접속합니다. (attach)
위의 과정을 ‘run -i -t’을 통해 한 번에 실행할 수 있습니다. 컨테이너를 삭제하기 위해서는 컨테이너를 정지시키고 삭제하거나 rm 명령어의 -f 옵션을 사용합니다. -p 옵션은 호스트의 IP와 포트를 컨테이너 IP와 포트에 연결할 수 있습니다. 호스트 포트를 명시하지 않을 경우, 사용 가능한 호스트 포트 중 하나를 골라서 컨테이너 포트와 바인딩합니다. 이외에도 여러 가지 옵션들이 있습니다.
컨테이너 실행
컨테이너에 하나 이상의 프로그램이 포그라운드로 실행되지 않는다면, 컨테이너는 종료됩니다. run 또는 start 명령어를 통해서 커맨드가 실행되어 기본 프로세스(pid가 1인 primary process)가 끝나면 컨테이너는 중지 상태가 됩니다. (도커는 하나의 컨테이너에 하나의 프로세스만 실행시키는 것을 권장합니다.) 추가적으로, docker exec 명령어는 리눅스 exec 명령어와 조금 다릅니다. 리눅스 exec 명령어는 기존의 프로세스 메모리에 새로운 프로세스를 덮어 쓴다면, docker exec는 컨테이너에 새로운 프로세스를 실행시키는 것처럼 보입니다.
만일, 컨테이너 내부에 프로세스의 에러가 찍히는 것을 보고 싶다면 docker logs 명령어를 쓰면 됩니다.(docker container + 명령어들은 docker + 명령어로 간편하게 씁니다. docker -h, docker container -h 비교해보면 docker 명령어들이 docker container 명령어들을 포함합니다.) log 드라이버로 syslog, awslog 등이 있습니다.
도커 볼륨
볼륨을 활용하는 방법은 세가지가 있습니. 첫 번째 방법은 -v 옵션을 사용하며, 호스트 공유 디렉터리를 컨테이너 공유 디렉터리로 마운트 합니다. 두 번째 방법은volumes-from 옵션으로 볼름을 사용하는 컨테이너를 다른 컨테이너와 공유하는 것입니다. 마지막 방법은 docker volume 명령어를 사용하는 것입니다. dcoker volume create 명령어로 볼륨을 생성한 뒤에 -v 옵션을 사용해서 컨테이너 디렉터리와 바인딩합니다.
도커 네트워크
컨테이너에는 네트워크 인터페이스가 있고 해당 nic에 할당되어 있는 내부 IP가 존재합니다. 이때, 순차적으로 할당되며 재시작 할 때마다 변경됩니다. 내부 IP 는 호스트와 또는 호스트에 존재하는 다른 컨테이너와 통신할 때 사용됩니다. 호스트와 컨테이너의 네트워크 구조는 다음과 같습니다. 컨테이너의 eth0 인터페이스는 호스트의 veth…라는 인터페이스와 연결됐으며 veth 인터페이스는 docker0 브리지와 바인딩돼 외부와 통신할 수 있습니다. 도커에서 기본적으로 사용할 수 있는 드라이버는 브릿지, 논, 호스트가 있습니다. 도커는 기본값으로 브릿지를 사용합니다.(브릿지 하나는 하나의 서브넷을 의미합니다. ) 컨테이너 생성 및 실행 시 —net-alias 옵션을 사용하면 컨테이너 ip주소가 아닌 별칭으로 통신할 수 있습니다. 이때, 별칭과 ip주소는 1:N 관계로 하나의 별칭에 신호를 보내면 해당 별칭의 모든 ip주소에 신호를 보내고, 같은 서브넷 안에서만 작용합니다.(멀티캐스트 기능과 비슷하다고 생각합니다.)
도커 이미지 생성 및 삭제
이미지를 생성하는 방법은 git에서 lcoal db에 버전(스냅샷)을 저장하는 방법과 비슷하다. commit 명령어를 통해 저장하고 싶은 형상을 새로운 이미지 계층으로 저장합니다. 이때, 다른 점은 도커 이미지는 변경 사항만 별도로 저장해서 이미지 계층을 만듭니다. 그래서, 이미지를 삭제할 때, 하위(자식) 계층의 이미지가 존재한다면, 해당 이미지는 untag 상태가 되며, 이름만 삭제됩니다(댕글리 이미지). 하위 계층의 이미지가 삭제되고 나서야 untaged 이미지가 삭제됩니다. 또한, 해당 이미지로 생성된 컨테이너가 있다면, 컨테이너가 해당 이미지를 참조하기 때문에 삭제가 이루어지지 않습니다. force 옵션으로 삭제해도 해당 이미지는 실제로 삭제되지 않습니다.
이미지 배포
save나 export와 길은 방법으로 이미지를 단일 파일로 추출해서 배포할 수도 있지만 이미지 파일의 크기가 너무 크거나 도커 엔진의 수가 많다면 이미지를 파일로 배포하기 어렵습니다. 그래서, 대부분 도커 허브를 이용하거나 도커 사설 레지스트리를 이용합니다. 생성된 도커 이미지를 도커 허브에 push해서 필요할 때마다 pull해서 사용할 수 있습니다.
도커 허브는 중앙 이미지 저장소를 말하며, 도커가 공식적으로 제공하고 있는 이미지 저장소로서,도커 계정을 가지고 있다면 누구든지 이미지를 올리고 내려받을 수 있기 때문에 다른 사람들에게 이미지를 쉽게 공유할 수 있습니다. 도커 허브 저장소를 이용할 때는 주의할 점이 있습니다. 도커 허브에 이미지를 저장할 저장소의 이름이 배포할 이미지의 [저장소 이름]/[이미지 이름]과 같아야 합니다.
Dockerfile
도커 이미지를 생성하는 방법은 기본 컨테이너를 만들어서 패키지를 설치하고 소스를 깃(Git)에서 복제하거나 호스트에서 복사해서 컴파일 및 빌드하는 등의 작업을 가집니다. 이러한 작업은 일일이 수작업으로 해야하고, 만들어진 이미지가 어떤 방법으로 만들어 졌는지 알기 힘듭니다.
그래서, 도커는 위와 같은 일련의 과정을 손쉽게 기록하고 수행할 수 있는 빌드(build) 명령어를 제공합니다. 이미지를 생성하기 위해 컨테이너에 설치해야 하는 패키지, 추가해야 하는 소스코드,실행해야 하는 명령어와 셸 스크립트 등을 하나의 파일(Dockerfile)에 기록해 두면 도커는 이 파일을 읽어 컨테이너에서 작업을 수행한 뒤 이미지로 만들어냅니다.(이 과정에서 도커 엔진은 이미지와 컨테이너가 생성되었다가 삭제되는 과정을 반복합니다.) 도커 파일이 있는 디렉터리가 build 명령어가 실행될 때의 현재 디렉터리(빌드 컨텍스트)가 됩니다. 하지만, 빌드 컨텍스트가 되는 곳을 build 명령 마지막에 따로 지정할 수도 있습니다. 도커 파일에서 사용되는 명령어는 다양하며, 각 명령어마다 고유한 기능과 특징이 있습니다. 이에 따라 필요할 때마다 해당 명령어의 사용법과 역할을 찾아보고 활용하는 것이 효과적이라고 생각합니다.
일반적으로 애플리케이션을 빌드할 때는 많은 의존성 패키지와 라이브러리를 필요로 합니다. 그래서, 간단한 소스 코드를 컴파일하고 실행하는 이미지라도 생성된 이미지의 크기가 큽니다. 이를 해결하기 위해 도커 엔진에서는 이미지의 크기를 줄이기 위해 멀티 스테이지(Multistage) 빌드 방법을 사용할 수 있습니다. 멀티 스테이지 빌드는 하나의 Dockerfile 안에 여러 개의 FROM 이미지를 정의함으로써 빌드 완료 시 최종적으로 생성될 이미지의 크기를 줄이는 역할을 합니다.