2. 도커 기초
도커의 작동 방식
도커 클라이언트: 도커에 명령을 내릴 수 있는 CLI도구로 도커 클라이언트를 이용해 컨테이너, 이미지, 볼륨 등을 관리할 수 있다.
도커 호스트: 도커를 설치한 서버 혹은 가상 머신으로 물리 서버가 될 수도 있고, 가상 서버가 될 수도 있다.
도커 레지스트리: 도커 이미지를 저장하거나 배포하는 시스템으로 public, private레지스트리로 나눌 수 있다.
도커 허브는 가장 유명한 public 레지스트리로 누구나 도커 허브에서 이미지를 다운로드 하거나 업로드 할 수 있다.
위 이미지는 도커 허브에서 구글의 이미지를 검색한 것으로 이처럼 원하는 이미지를 위와 같이 검색해서 다운할 수 있다.
도커의 동작 방식은 먼저 도커 클라이언트에서 명령어를 입력하면 호커 호스트의 도커 데몬이 이 명령어를 받아서 결과를 주는데 이때 만약 이미지를 출력하는 코드를 클라이언트에 입력을 했는데 호스트에 이미지가 없으면 도커 레지스트리에서 다운을 한다.
※도커 데몬: 도커와 관련된 리소스를 관리하는 백그라운드 프로세스
도커 이미지
컨테이너 형태로 소프트웨어를 배포하기 위해 필요한 코드, 라이브러리, 설정을 실행할 수 있는 포맷이다.
도커 이미지는 독립적이기 때문에 의존성을 고려할 필요가 없고 경량화된 패키지이므로 비교적 작은 용량으로 제 역할을 수행할 수 있다.
이렇게 만들어진 도커 이미지는 여러개의 레이어로 구성되어 있고 도커 허브와 같이 중앙 저장소에 저장되어 관리된다.
도커 컨테이너
도커 이미지를 실행할 수 있는 인스턴스로 실행, 중지, 재실행, 삭제등의 명령을 내릴 수 있다.
컨테이너는 자체적으로 파일 시스탬을 가지고 있고 각 컨테이너는 운영체제를 포함해야 하지만 내부에 운영체제를 전부 포함하지는 않기 때문에 도커 엔진과 운영체제를 공유하여 도커 엔진이 설치되어있는 포스트 운영체제를 이용하여 컨테이너 내부에는 프로그램을 실행시키기 위해 최소한으로 필요한 바이너리, 라이브러리와 같은 구성요소로 이루어져있어 가볍다.
도커 이미지 다운 방법은 다음과 같다.
docker image pull {이미지 이름:태그 이름}
도커 호스트에 해당 이미지가 있는지 확인을 하고 이미지가 없는 경우 레지스트리에서 해당 이미지를 다운한다.
latest의 태그의 우분투 이미지를 다운로드 한다는 뜻이다.
태그란 특정 이미지를 식별하고 관리하기 위해 사용되는 레이블이다.
9c704ecd0c67라고 되어있는 부분은 도커의 이미지가 빌드될 때 생성된ID로 이것은 레이어가 다운 잘 되었다는 뜻으로 레이어의 수만큼 나온다
이제 레이어가 여러개인 것은 다음과 같다.
이미지는 이미지 인덱스, 이미지 매니페스트, 레이어라는 세 가지 구조로 이뤄어져있다.
결과창에 출력되는 digest는 이미지 인덱스에 해당한다.
인덱스는 다수의 매니페스트로 구성되어있는데 각 이미지 매니페스트는 다양한 운영체제 및 아키택처에서 해당 이미지를 활용할 수 있도록 설정값과 다양한 레이어를 제공한다.
도커 이미지 목록은 다음과 같이 확인할 수 있다
REPOSITORY 이미지 이름이고 IMAGE ID는 다운로드 한 이미지의 id를 뜻한다.
여기서 id가 다운로드할 때 digest와 값이 다른데 digest는 도커 레지스트리에 존재하는 이미지의 digest이고 image id는 로컬에서 할당받은 값이기 때문이다.
도커 컨테이너 실행
도커 container run을 하게 되면 도커 호스트의 데몬이 실행 명령을 받아 호스트에 있는 이미지를 컨테이너 형태로 실행한다.
# container로 실행
docker container run ubuntu
# 실행 결과 확인
docker container ls -a
위를 실행하면 실행중인 컨테이너와 정지 상태인 컨테이너 모두를 확인할 수 있다.
각 컨테이너는 container id 라는 것을 갖는데 이는 하나의 이미지로 다수의 컨테이너를 생성할 수있으므로 각 컨테이너 아이디를 따로 갖는 것이다.
컨테이너의 상태 exit(0)이면 컨테이너가 정상적으로 종료되었다는 뜻이다.
이 밖에도 나올 수 있는 컨테이너의 상태는 다음과 같다.
created: 컨테이너가 생성되었지만 아직 시작되지 않은 상태이다.
running: 컨테이너가 현재 실행 중인 상태이다.
paused: 컨테이너의 모든 프로세스가 일시 중지된 상태이다.
restarting: 컨테이너가 비정상적으로 종료되어 자동으로 재시작되고 있는 상태이다.
그리고 뒤에 나오는 숫자는 다음과 같은 것들이 나올 수 있다.
1: 일반적인 에러로 주로 일반적인 명령어 실패나 프로그램 종료 시 발생하는 코드이다.
126: 명령어가 실행 권한이 없어서 실행되지 않았음을 나타낸다.
127: 명령어를 찾을 수 없어서 실행되지 않았음을 나타낸다.
255: 일반적인 종료 상태로 구체적인 에러 코드를 반환하지 않는 경우이다.
위의 코드를 실행을 했는데 결과가 종료인 이유는 컨테이너 내부 프로세스가 모두 종료되면 해당 컨테이너도 종료되기 때문이다.
실행중인 컨테이너 내부 접속
it에서 i는 interactive의 줄인말로 표준 입력을 열어놓는다는 의미이고, t는 tty의 줄임말로 가상 터미널을 의미한다.
즉, 가성 터미널을 통해 키보드 입력을 표준 입력으로 컨테이너에 전달하는 것을 의미한다.
코드를 실행하면 위처럼 결과가 나오는데 여기서 사용자 이름은 root, 컨테이너 id가 호스트 이름으로 바뀐다.
이 상태로 다른 터미널을 열어서 docker container ls 이라는 코드를 실행 하면 status가 up으로 바뀐다.
이는 컨테이너가 실행중이라는 것을 의미한다.
# 내부 접속 컨테이너를 외부에서 종료
docker container stop [컨테이너 id]
# 해당 컨테이너 즉시 종료
docker container kill [컨테이너 id]
# 컨테이너 다시 실행
docker container start [컨테이너 id]
# 내부에 접속
docker container attach [컨테이너 id]
# 내부 접속 컨테이너를 내부에서 종료
exit
# 이미지 삭제
docker image rm [컨테이너 id]
컨테이너 삭제는 docker container rm [컨테이너 id]
만약 여러개 동시에 삭제하고 싶으면 컨테이너 id를 띄어쓰기로 구분해서 여러개 쓰면 된다.
이미지 삭제를 할 때 container가 남아있을 경우 오류가 뜨기 때문에 container를 먼저 제거를 한 다음에 image를 제거하면 된다.
도커 이미지 변경
이제 나만의 도커 이미지를 만들어 보는 과정을 하겠다.
먼저 기존의 ubuntu 이미지에는 net-tools이 설치되어있지 않아 내부ip확인을 할 수 있는 ifconfig가 실행할 수 없다.
# net-tools 설치
apt update && apt install net-tools
이렇게 하면 도커 이미지에 추가적으로 설치를 할 수 있게 된다.
# 나만의 이미지 생성
docker container commit [컨테이너id][새로운 컨테이너 이름]:[태그]
그 후 다른 터미널에서 위와 같이 입력을 하면 나만의 이미지를 저장할 수 있게 된다.
이제 원래 터미널로 돌아가서 기존 컨테이너를 다 종료한 다음 새롭게 만든 이미지를 컨테이너로 실행하면 내가 추가한 net-tools가 이미 깔려있어 바로 ifconfig를 통해ip를 확인할 수 있다.
도커 이미지 명령어
도커 컨테이너 명령어
여기서 create와 start, run은 비슷해보이지만 create는 새로운 컨테이너 생성, start는 정지 상태인 컨테이너 실행, run은 생성후 실행까지 진행한다.
그리고 exec와 attach가 비슷해 보이는데 차이는 exec는 실행중인 컨테이너 내부에서 명령어를 실행하고, attach는 실행중인 컨테이너의 표준 입력, 표준출력, 표준 오류 스트림에 연결할 때 사용한다.
도커 컨테이너 네트워크
위를 보면 컨테이너 내부는 자체적으로 eth0 인터페이스를 가지고 있다. 그리고 도커 호스트는 docker0, enp0s3, vetha6e1141 이라는 인터페이스를 가지고 있다. docker0는 도커 설치시 함께 설치되는 인터페이스로 도커 호스트와 컨테이너를 연결하는 다리 역할을 한다. 그리고 컨테이너를 실행하면 veth라는 가상 인터페이스가 컨테이너 내부의 eth0과 도커 호스트의 docker0를 연결해준다.
그리고 enp0s3은 도커 호스트 자체적으로 보유한 네트워크 인터페이스이다.
이러한 구조를 시각적으로 보면 아래와 같다
도커 네트워크
도커에는 기본적으로 bridge, host, none이라는 세가지 네트워크 드라이버를 제공한다.
# bridge드라이버로 연결(기본값)
docker container run -it --network=bridge my-ubuntu:0.1
# host드라이버로 연결
docker container run -it --network=host my-ubuntu:0.1
# none드라이버로 연결
docker container run -it --network=none my-ubuntu:0.1
bridge드라이버: 컨테이너를 생성할 때 제공하는 기본 드라이버로 컨테이너는 각자의 네트워크 인터페이스를 가진다.
이는 도커 호스트의 docker0와 바인드 된다. bridge드라이버는 컨테이너 생성시 사용되는 기본 네트워크 드라이버이므로 우리가 지금까지 컨테이너를 생성할 때는 모두 bridge드라이버를 사용한 것이다.
host드라이버: 컨테이너를 생성할 때 컨테이너 자체적으로 네트워크 인터페이스를 가지지 않고 호스트 네트워크 인터페이스를공유한다.
none드라이버: 실행한 컨테이너가 네트워크 인터페이스를 가지지않아 컨테이너 외부와의 통신이 불가능하다
파일 전송
# 호스트에서 컨테이너로 파일 전송
# docker container cp [출발 경로][도착 컨테이너 경로]
docker container cp ./test01.txt 63cc62ef0172:/home
# 컨테이너에서 호스트로 파일 전송
# docker cp [출발 경로][도착 경로]
docker cp 63cc62ef0172:/home/test02.txt /home/eevee/work/ch04/ex01
먼저 호스트에서 컨테이너로 파일 전송을 보면 container id는 63cc62ef0172이고 위 코드를 실행하면 test01.txt 파일을 복사해서 지정한 컨테이너의 home위치로 붙여넣기를 한다.
그리고 컨테이너에서 호스트로 파일 전송하는 부분은 컨테이너에 있는 파일을 호스트의 /home/eevee/work/ch04/ex01위치로 복사를 한다.
도커 스토리지
도커 컨테이너가 삭제되면 내부의 파일도 같이 삭제되기 때문에 내부의 사용되거나 생성되는 데이터가 유지하기 위해서는 스토리지가 필요하다.
스토리지는 bind mount, volume, tmpfs와 같이 세 가지 종류가 존재한다.
bind mount: 도커 호스트 디렉터리를 직접 공유하는 방식이다.
volume: 도커를 활용해 볼륨을 생성한 후 컨테이너의 디렉터리와 공유하는 방식이다.
tmpfs: 도커 호스트 메모리에 파일이 저장되는 방식으로 컨테이너를 삭제하면 해당 파일도 함께 삭제된다.
이제 postgreSQL을 사용해 스토리지 필요성을 확인해 볼 것이다.
# postgreSQL 실행
docker container run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres
--name은 컨테이너의 이름, -e는 환경변수로 이번에는 비밀번호 입력 -d는 백그라운드 실행을 의미한다.
이와 같이 psql을 통해 postgreSQL로 접속을 하고 superuser권한을 부여한 user01이라는 사용자를 생성해 소유자로 지정한다. 그리고 간단한 테이블을 만들어 데이터를 삽입하여 DB를 생성한다.
이처럼 완전히 삭제를 할 경우 아래와 같이 다시 컨테이너에 접속을 하더라도 이전에 생성한 데이터가 유지되지 않고 존재하지 않는다고 나오는 것을 볼 수 있다.
volume
volume은 도커 컨테이너에서 생성되는 데이터가 컨테이너를 삭제한 후에도 유지될 수 있도록 도와주는 저장소이다.
# myvolume01이라는 이름의 도커 볼륨 생성
docker volume create myvolume01
# 도커 볼륨으로 컨테이너 실행
docker container run -e POSTGRES_PASSWORD=mysecretpassword --mount type=volume,source=myvolume01,target=/var/lib/postgresql/data -d postgres
--mount를 사용해서 source=[도커 볼륨 이름], target=[컨테이너 내부 경로]와 같은 형태로 사용하여 컨테이너 내부 경로에 존재하는 모든 파일을 도커 볼륨에 저장한다.
volume을 사용하면 이처럼 삭제를 한 뒤에도 해당 경로에 저장이 되어 다시 새로운 컨테이너를 생성했을 때 데이터가 유지되는 것을 볼 수 있다.
Bind mount
도커 호스트 디렉터리와 컨테이너 디럭터리를 연결해서 데이터를 보관하는 방식이다.
위를 보면 bind를 통해 도커 호스트의 경로와 컨테이너 내부의 경로가 연결된 것을 볼 수 있다.
따라서 만약 컨테이너 내부에서 파일을 수정한다면 외부의 도커 호스트의 같은 위치에 있는 파일도 수정이 된다.
tmpfs mount
tmpfs mount는 중요한 데이터를 일시적으로 도커 호스트에 메모리로 저장할 때 사용한다.
따라서 컨테이너 간 데이터 공유를 지원하지 않고 실행중인 컨테이너를 정지시키면 tmpfs mount도 삭제된다.