개요
도커의 이미지와 컨테이너에 대해 알아보자.
컨테이너
컨테이너는 애플리케이션, 웹사이트, 노드서버, 애플리케이션을 실행하는 전체 환경을 포함하는 패키지이다. 컨테이너에 는 소프트웨어 실행 유닛이 존재한다. 이 유닛을 우리가 실행시키는 것이다.
도커로 작업할 때 이미지라는 Dissolver 개념도 필요하다. 이미지는 템플릿, 컨테이너의 블루프린트가 될 것이기 때문이다.
이미지
이미지는 코드와 코드를 실행하는데 필요한 도구를 포함한다. 그 다음 컨테이너가 실행되어 코드를 실행한다. 이 이미지를 기반으로 여러 컨테이너를 만들 수 있다. 즉, 이미지는 모든 설정 명령과 모든 코드 모든 환경이 포함된 공유 가능한 패키지이다. 컨테이너는 이미지의 구체적인 실행 인스턴스이다. 우리는 이미지를 기반으로 하는 컨테이너를 실행하는 것이다. 위에 서술한 내용이 기본 핵심 개념이며, 도커의 모든 것이다.
이미지는 하나의 클래스이고 컨테이너는 클래스의 인스턴스라고 생각하면 될 것 같다.
외부 이미지의 사용 및 실행
외부 이미지의 사용과 실행 방법에 대해 알아보자.
먼저 실제로 컨테이너를 실행할 수 있도록 이미지를 생성하고, 가져오는 방법은 두 가지가 있다.
첫 번째로, 이미 존재하는 이미지를 사용하는 것이다. 이를 위한 소스는 도커 허브에 있다.
터미널에서 docker run node를 입력해보자.
입력을 하면 로컬에 이미지가 없다고 오류가 뜰 것이다 -->unable to find image ‘node:latest’ locally
왜냐하면 이 이미지는 Docker Hub에 있기 때문이다. 그러면 자동으로 Docker Hub에서 이미지를 가져온다. 이후 다운로드가 끝나면 이 이미지를 컨테이너로 실행한다.
실제로 작동하는지 확인해보자. 명령어는 docker ps - a 이다.
상태(STATUS)를 보면 무언가가 발생했지만, 더 이상 실행되지 않는 것을 알 수 있다. 왜냐하면 컨테이너가 격리되어 실행되기 때문이다. 노드를 이미지로 실행하거나 노드 이미지를 기반으로 하는 컨테이너로 실행했지만 이것만으로는 별 의미가 없다. 노드에 의해 노출된 인터랙티브 쉘은 컨테이너에 의해 자동으로 우리에게 노출되지 않기 때문이다.
이를 해결하는 법은 간단하다 바로 -it를 넣어주는 것이다. docker run -it -node 로 명령어를 입력해보자. 이 명령어는 도커에게 컨테이너 내부에서 호스팅 머신으로 대화형 세션을 노출하고 싶다고 알리는 것이다. 이제 enter를 눌러보면 실제로 기본 노드 명령을 실행할 수 있는 인터랙티브 노드 터미널로 들어가진다. 이후 터미널에서 1 + 1을 입력해보면 노드 API를 사용한 결과를 얻을 수 있다. 여기서 중요한 점은 노드가 생성된 컨테이너 내부에서 실행 중이며 -it 플래그를 추가함으로써 터미널로 들어가 컨테이너 및 컨테이너에서 실행 중인 노드와 상호 작용할 수 있다는 점이다.
여기서 노드는 우리의 컴퓨터에서 실행되고 있지 않은데, 이를 알아보자.
도커로 실행했을 때 노드 버젼이 v22.7.0이고 오른쪽은 로컬에서 노드 버전을 확인 했을 때 v20.12.2로 나온다. 이는 컨테이너 내부의 버전임을 증명하는 것이다. 이 노드 컨테이너를 실행하고, 상호작용하기 위해 시스템에 노드를 설치할 필요가 전혀 없다. 이것이 바로 컨테이너로 작업하는 방법이다.
이미지는 컨테이너에 필요한 모든 논리와 모든 코드를 보관하는데 사용한다. run 명령어를 사용하여 이미지의 인스턴스를 만든다. 이 말은 즉, 이미지를 기반으로 하는 구체적인 컨테이너를 생성한다는 뜻이다. 이제 docker ps -a 을 입력해보자.
2개의 컨테이너가 생긴 것을 볼 수 있고, 둘 다 종료되었다. 두 컨테이너는 동일한 이미지를 기반으로 한다. docker run 으로 동일한 이미지를 기반으로 하는 2개의 컨테이너가 동시에 실행될 수 있음을 알 수 있다. 이것이 이미지와 컨테이너의 이면에 있는 아이디어이다.
자체 이미지 빌드
자체 이미지를 빌드해보자.
FROM node
WORKDIR /app
COPY . /app
RUN npm install
위 명령어들을 해석해보면
1. node를 도커 허브에서 가져와서 베이스 이미지로 사용한다.
2. 하위에서 진행하는 작업 경로는 도커 이미지 내부의 /app으로 설정
3. 도커 파일이 있는 루트폴더에서 전부 복사해서 이미지 내부의 /app폴더에 복사
4. 모든 로컬 파일을 이미지에 복사 후 이미지에서 명령을 RUN하고 싶다고 알린다.(npm install)
실행을 하려면 RUN node server.js 를 적어야 한다고 생각할 수 있지만. RUN명령어는 이미지가 빌드 될 때마다 실행되기 때문에 CMD 명령어를 사용해야한다. CMD 명령어는 컨테이너가 시작될 때 실행된다.
FROM node
WORKDIR /app
COPY . /app
RUN npm install
CMD ["node", "server.js"]
한가지 더 추가해야할 것이 있다. 아래 사진을 보자.
listen(80) 즉, 포트 80을 수신대기하고 있다는 의미이다. 여기서 생각해야할 점은 도커컨테이너는 격리되어있다는 점이다. 이 말은 우리의 로컬과 격리되어있다는 말이다. 도커에는 자체 내부 네트워크도 존재한다. 컨테이너 내부의 노드 애플리케이션에서 포트80을 수신할 때 컨테이너는 그 포트를 우리의 로컬 머신에 노출하지 않는다. 따라서 컨테이너 내부에서만 수신대기중이기 때문에 그 포트에 수신할 수 없다.
이를 해결하기 위해 Dockerfile의 마지막 명령 전에 EXPOSE <PORT>명령어를 추가해준다. 이 명령어는 컨테이너가 시작될 때 우리의 로컬 시스템에 특정 포트를 노출하고 싶다는 것을 알려주는 명령어이다.
FROM node
WORKDIR /app
COPY . /app
RUN npm install
EXPOSE 80
CMD ["node", "server.js"]
자체 이미지를 기반으로 컨테이너 실행
우리가 만든 이미지를 기반으로 컨테이너를 실행해보자.
두 가지 방법이 있는데. 첫번째는 docker run명령어를 사용하는 방법과 docker start 를 사용하는 방법이 있다. docker run으로 진행을 해보자. docker run <id or 이름> 으로 입력을 해주면 된다.
docker run -p 3000:80 test_name 로 실행을 해보면 잘 된다.
여기서 -p로 포트를 지정해주는 이유는 EXPOSE 80을 넣어주었지만 이 명령은 문서 목적으로만 추가 됐을 뿐 아무것도 하지 않기 때문에 우리의 로컬 머신의 어떤 포트가 내부의 도커 특정 포트에 액세스할 수 있는지 지정해줘야 한다.
이미지는 읽기 전용이다!!
만약 우리가 이미지를 만든 후 코드 변경이 일어났다면 다시 빌드를 해야한다. 왜냐하면 이미지는 읽기 전용이기 때문이다.
이미지 레이어 이해하기
이미지는 레이어 기반이다. 즉, 이미지를 빌드하거나 이미지를 다시 빌드할 때 변경된 부분의 명령(레이어)과 그 이후의 모든 명령(레이어)이 재평가된다는 의미이다. 도커는 이미지를 빌드할 때 모든 명령 결과를 캐싱하고 이미지를 다시 빌드할 때 명령을 다시 실행할 필요가 없으면 캐싱된 결과를 사용한다.
레이어
레이어는 Dockerfile에 작성된 명령어들 하나하나가 레이어라고 생각하면 된다.
이미지는 여러 레이어로 구성된다. 즉, 명령이 실행되고 이미지가 빌드되면 이미지가 잠기고, 이미지를 다시 빌드하지 않는 한 그 코드를 변경할 수 없다.
그 다음 이미지를 기반으로 컨테이너를 실행하면 아래와 같이 된다.
컨테이너는 기본적으로 Dockerfile에 지정한 명령을 실행한 결과로 코드를 실행 중인 애플리케이션인 이미지 위에 새로운 레이어를 추가한다. 이렇게 하면 이미지를 레이어로 실행할 때만 활성화되는 최종 레이어가 추가된다.
중요한 점은 하나의 레이어가 변경되면 변경된 레이어 이후의 모든 레이어가 재빌드 된다는 점이다. 이 부분은 최적화와 관련된 부분이므로 알아두는 것이 좋다.
위 그림과 같은 방식은 매우 비효율적이다. 왜냐하면 코드에 변경이 일어나면 Layer3 ~ Layer6까지 다시 빌드 되는데 이때 npm install을 계속 호출해줘야 한다.
이렇게 바꿔보자 이렇게 하면 package.json이 바뀌지 않는 이상 코드가 변경되어도 npm install이 계속 호출되는 일은 없을 것이다.
레이어 기반 아키텍쳐는 도커와 도커 이미지의 핵심 개념이다.
이미지와 컨테이너 관리
컨테이너 중지 & 재시작
컨테이너 중지는 docker stop <컨테이너 ID 혹은 이름> 으로 실행중인 컨테이너를 중지시킬 수 있다.
재시작의 경우 docker start <컨테이너 ID 혹은 이름> 이 명령어로 재실행 시킬 수 있다.
Attached & Detached 컨테이너
Attached와 Detached는 컨테이너를 Foreground로 실행시킬건지 Background로 실행시킨건지를 의미한다.
docker run의 경우 Attached가 기본모드이고, docker start의 경우 Detached가 기본모드이다. 따라서 docker run을 Detached로 사용하려면 docker run -d 이런식으로 플래그를 넣어주고, docker start를 Attached로 사용하려면 docker start -a 로 사용하면된다.
로그보기
컨테이너에 출력되는 로그를 가져와보자. docker logs <컨테이너 이름> 명령어를 사용하자. 과거의 컨테이너에 출력된 로그들도 볼 수 있다.
인터랙티브 모드 들어가기
도커 컨테이너에서 실행중인 컨테이너와 입력 출력을 상호작용할 수 있게 하는 명령어도 있다.
docker run -i -t <이미지 ID>, docker start -a -i <컨테이너 이름> 를 입력해보면 상호작용을 할 수 있다.
이미지 & 컨테이너 삭제
컨테이너 삭제는 rm, 이미지는 rmi 명령어로 삭제할 수 있고, 한번에 여러개를 지울 수 있다. docker rm <name1> <name2> 각 이름들은 공백으로 구분해주어야 한다.
이미지를 삭제할 때 주의할 점이 있는데, 이지미를 삭제하려면 해당 이미지가 더 이상 컨테이너에서 사용되자 않고 중지된 컨테이너에 포함된 경우여야만 한다.
매번 컨테이너를 삭제하는 것은 귀찮은 일이다. 다행이 컨테이너가 중지되면 자동삭제해주는 명령어도 존재한다. run 명령에 --rm 플래그를 넣어주면 된다!
이미지 파일 세부 조회
이미지 파일 세부사항을 조회할 수 있다. docker image inspect <이미지ID>를 사용해보자. 이미지 전체아이디, 생성 날짜, 생성된 시간, 컨테이너에 구성 등등 여러 정보들을 알 수 있다.
컨테이너와 이미지에 이름&태그 지정
--name 옵션으로 컨테이너 이름을 지정해줄 수 있다. docker run --name <name>
이미지는 -t 옵션으로 레포지토리와 태그를 지정해줄 수 있다. -> docker build -t name:tag
컨테이너에 / 컨테이너로부터 파일 복사하기
로컬에있는 파일을 컨테이너로 복사할 수 있고, 컨테이너에 있는 파일을 로컬로 복사할 수 있다.
docker cp <source> <dest> 를 사용하면 된다.