이미지와 컨테이너
- 컨테이너
- 애플리케이션, 웹사이트, 서버, 애플리케이션을 실행하는 전체 환경 등 무엇이든 포함하는 작은 패키지
- 컨테이너에 소프트웨어 실행 유닛이 존재하고, 우리는 이를 실행하는 것
- 이미지
- 컨테이너의 블루프린트로 실제 코드와 코드를 실행하는데 필요한 도구를 포함한다
- 이후 이미지를 통해 여러 컨테이너를 만들 수 있다. 자바로 비교하면 이미지를 클래스, 컨테이너를 객체라고 생각해도 좋다
- 이미지는 모든 설정 명령과 모든 코드가 포함된 공유 가능한 패키지로, 컨테이너는 이미지의 구체적인 실행 인스턴스
이미지를 사용하는 방법
- 이미 존재하는 이미지 사용
- 도커 허브에서 관리되는 이미지를 받아오는 방법
docker run [이미지]
또는docker pull [이미지]
를 통해 이미지를 받아올 수 있다
- 자신의 고유한 이미지 생성 by Dockerfile
- 공식 베이스 이미지를 받아온 후, 그 위에 코드를 추가하여 그 이미지로 코드를 실행
- 이는 우리의 코드를 지닌 애플리케이션을 자신의 이미지로 구축하는 방법임
- 즉, 고유한
Dockerfile
을 작성하는 것
Dockerfile
// 이를 통해 다른 베이스 이미지에 우리 고유의 이미지를 구축할 수 있음
FROM node
// 도커에게 모든 명령이 해당 폴더에서 실행되어야 한다고 알림
// 모든 후속 명령어가 /app 폴더에서 실행될 것
WORKDIR /app
// 로컬 머신에 있는 어떤 파일이 이미지에 포함되어야 하는지를 알려줌
// 첫 번째 경로는 컨테이너의 외부, 이미지의 외부 경로 -> 이미지로 복사되어야 할 파일들이 있는 곳 = 호스트 파일 시스템('.'은 현재 디렉토리 하위 모든 폴더 및 파일)
// 두 번째 경로는 그 파일을 저쟁햐아 하는 이미지 내부의 경로 = 이미지/컨테이너 파일 시스템 (여기서는 WORKDIR가 /app이므로 './'도 컨테이너 내부의 /app 폴더가 됨 -> /app 처럼 절대경로를 명시하는 대신, './'로 상대경로를 입력해도 됨)
COPY . /app
// 파일 복사 후, 이미지에서 실행할 커맨드
RUN npm install
// 우리의 로컬 시스템에 특정 포트를 노출시키는 것을 "문서화함"
// 이는 선택사항으로 실제로 이 포트가 노출되는 것은 아니며, docker run 시 -p 플래그를 통해서만 노출가능
// 문서화의 기능일뿐이지만, 이를 추가해주는 것이 모범적인 사용법이니까 추가하자
EXPOSE 80
// RUN은 이미지가 생성될 때마다 실행되는 커맨드이고, CMD는 이미지를 기반으로 컨테이너가 시작될 때 실행되는 커맨드
CMD ["node", "server.js"]
이렇게 Dockerfile을 통해 생성된 이미지는 '읽기 전용'으로 이후 코드의 수정이 있더라도 이미지를 새롭게 빌드하지 않으면 수정사항이 반영되지 않는다.
그러니 매번 수정이 있을 때마다 새롭게 이미지를 빌드하고, 이를 다시 컨테이너로 실행시켜주어야 한다.
이는 매우 번거로운 과정으로 추후에 더 우아하고 빠른 방법을 배워보자..! 지금은 일단 이렇게..
명령어
docker run [이미지ID](:버전)
- 도커 허브에서 해당 이미지를 받아온 후 컨테이너로 실행한다
- 플래그
-it
: 컨테이너 내부에서 호스팅 머신으로 대화형 세션을 노출 시킴 -> 터미널 접속-p
: publish의 약자로 도커에게 어떤 로컬 포트가 있는지 알려줌docker run -p [액세스 하려는 로컬 포트]:[컨테이너 노출 포트] [이미지ID]
docker ps -a
- 현재 실행 중인 도커 컨테이너, 프로세스가 표시된다
docker build .
- Dockerfile을 기반으로 새 커스텀 이미지를 빌드한다
- 인자로 Dockerfile의 위치를 넘겨주어야 하는데, '.'을 넘겨주면, 현재 명령을 실행하는 곳과 동일한 폴더임을 의미한다
- 이후 발급받은 이미지 ID를 통해 docker run을 시켜주면 컨테이너가 실행된다
- Dockerfile을 기반으로 새 커스텀 이미지를 빌드한다
docker stop [컨테이너 이름]
- 해당 컨테이너와 컨테이너 내부에 실행 중인 애플리케이션이 종료됨
이미지 레이어 이해하기
이미지를 빌드하면 Dockerfile에 명시되어 있는 명령이 실행되고 이미지가 닫힌다. 때문에, 코드가 변경되어도 수정사항이 바로 반영이 안되며, 이를 반영하기 위해서는 새롭게 이미지를 다시 빌드해야 한다. 이는 중요한 특징이고 위에서 한번 설명했다. 이외에도 중요한 개념이 있는데, 그것은 이미지가 레이어 기반이라는 것이다.
이미지를 빌드할 때, 변경된 부분의 명령과 그 이후의 모든 명령이 revalidation 된다. 우리는 코드는 변경했지만 Dockerfile을 변경하지 않을 수 있으며, 대개 그럴 것이다.
코드의 수정사항을 반영하기 위해 docker build .
을 통해 재빌드를 수행하면 처음 빌드할 때와 다르게 매우 빠르게 빌드가 완료되는 것을 확인할 수 있다. 그리고 메시지를 확인하면 Using cache
라는 부분이 보일 것이다.
이는 도커가 Dockerfile을 분석했을 때, 명령어를 재실행했을 때의 결과가 이전과 동일하다는 것을 인식했기 때문에 캐시를 사용한 것이다.
때문에 도커는 이미지를 빌드할 때마다 모든 명령 결과를 캐시하고, 이미지를 다시 빌드할 때 명령을 다시 실행할 필요가 없다고 판단되면(추론) 캐시된 결과를 재사용한다
=> 이것을 레이어 기반 아키텍처라고 한다
Dockerfile에 명시된 모든 명령은 Dockerfile의 레이어를 나타낸다. 그리고 이미지는 이러한 다양한 명령을 기반으로 여러 레이어에서 간단하게 구성된다. 모든 명령어를 기반으로 하는 이미지 레이어는 레이어를 생성하고, 이러한 레이어는 캐시된다. 그런 다음 이미지를 기반으로 컨테이너를 실행하면 그 컨테이너는 기본적으로 Dockerfile에 지정한 명령을 실행한 결과로 코드를 실행 중인 애플리케이션인 이미지 위에 새로운 추가 레이어를 추가한다.
이렇게 하면 이미지를 레이어로 실행할 때만 활성화되는 최종 레이어가 추가되는 것이다. 최종 명령 이전의 모든 명령은 이미 이미지의 일부이지만, 동시에 별도의 레이어이다. 그리고 아무것도 변경되지 않으면 이러한 모든 레이어를 캐시에서 사용할 수 있다.
만약 코드에서 무언가를 변경한 후 재빌드를 수행한다면, 캐시의 일부 결과만을 사용할 것이다.
- 아마 FROM 명령어와 WORKDIR 명령어의 결과는 동일할 것이니 캐시를 사용한다.
- 하지만, COPY 부터는 결과가 다를 것이라고 추론한 후 캐시를 사용하지 않고 다시 실행한다.
- COPY 레이어가 변경되었으니, 이후 모든 레이어도 캐시없이 다시 빌드되어야 한다. 결과가 동일할지 여부를 알 수 없기 때문이다.
=> 도커는 재빌드 시, 기본적으로 각 명령어(레이어)마다 캐시를 사용하려고 할 것이며, 다시 실행해야 하는 항목만 다시 실행하여 이미지 생성 속도를 높인다
그럼 여기서 최적화 가능한 부분은 다음과 같다. 코드부터 보자면,
FROM node
WORKDIR /app
COPY package.json // 추가
RUN npm install // 순서 변경
COPY . /app // 순서 변경
EXPOSE 80
CMD ["node", "server.js"]
npm install
명령어는 코드의 변화와 상관없이package.json
이 변경되지 않는 한 항상 동일한 결과를 산출할 것이다.- 하지만, 이전 코드의 경우 코드의 변화가 생기면
COPY . /app
부분부터 캐시를 사용할 수 없게 되기에 npm install도 불필요하게 항상 재실행 했어야 했다. - 이를 방지하기 위해
package.json
을 먼저 카피한 후,npm install
을 먼저 수행하며, 그 이후에 코드를 카피하도록 최적화해두었다. - 이러면
package.json
의 변경이 없다면RUN npm install
레이어까지는 캐시를 사용하여 이미지 생성 속도를 더 향상 시킬 수 있다.
이미지 & 컨테이너 관리
컨테이너 중지 & 재시작
docker ps
: 실행 중인 모든 컨테이너를 디폴트로 보여준다-a
플래그를 추가하면 더 이상 실행되지 않는 중지된 컨테이너를 포함하여 모든 컨테이너가 보여진다.
docker stop [컨테이너명 or 컨테이너ID]
: 해당 컨테이너를 중지한다docker start [컨테이너명 or 컨테이너ID]
- 해당 컨테이너를 재시작한다.
docker run
을 통해서도 컨테이너를 시작할 수 있지만, docker start는 변경사항이 없다면 굳이 새로운 컨테이너를 만들지 않고 기존의 중지된 컨테이너를 재시작할 수 있다는 점이 특징이다.- 이때, 디폴트로
Detached
모드로 실행되는데, 터미널을 차단하지 않고 백그라운드에서 실행한다.docker run
의 경우 디폴트로Attached
모드로 실행되어 터미널을 차단한다. 이는 실행 중인 컨테이너와 연결되어서 해당 컨테이너의 출력 결과를 수신할 수 있게 된다.(콘솔, 로그 확인 가능)docker run
시-d
플래그를 추가하여 Detached 모드로 실행되도록 할 수 있다.docker start -a
플래그를 통해 재시작 시에도 Attached 모드로 실행되도록 할 수 있다.
docker container attach [컨테이너명]
: 분리된 컨테이너를 다시 Attach 모드로 연결한다.docker logs [컨테이너명]
: 해당 컨테이너의 출력된 과거의 로그를 확인한다-f
태그를 추가하면 follow 모드로 진입하여 계속 수신 대기할 수 있다
인터렉티브 모드로 돌아가기
지금까지는 웹 서버를 컨테이너로 띄우는 작업을 했지만, 도커는 웹 서버에 관한 것만이 아니다. 로컬에 파이썬이 설치되어 있지 않지만, 도커 컨테이너를 통해 파이썬을 실행시켜 상호작용하는 것과 같은 작업도 가능하다.
이는 위에서 배운 연결(Attached) 및 분리(Detached)와 관련이 있다.
FROM python
WORKDIR /app
COPY . /app
CMD ["python", "hello.py"]
위처럼 Dockerfile을 작성한 후, 이미지로 빌드하고, 컨테이너를 실행시키자.(이때 hello.py는 사용자로부터 입력을 받는 코드가 포함되어 있어야 한다.)
그러면 EOFError가 발생하고 외에는 아무것도 표시되지 않는다. 컨테이너를 연결 모드로 실행헀지만, 이는 출력 결과만 받을 수 있는 것이지 입력이 가능하다는 것은 아니기에 사용자로부터 입력을 받을 수 없어 발생한 에러이다.
컨테이너와 상호작용을 하려면 인터렉티브 옵션을 주어야 하며 이를 가능하게 하는 플래그가 -i
플래그이다. 이를 통해 컨테이너에 무언가 입력할 수 있게 된다. 하지만 입력할 수 있는 터미널이 존재하지 않는데 -t
태그를 통해 터미널을 노출시킬 수 있다. 즉, 이 둘을 조합한 -it
태그를 통해 입력을 받을 수 있게 된다.
docker start
를 통해 컨테이너를 재시작한 경우, 기본적으로 분리(Detached) 모드이기 때문에, 컨테이너와 통신할 수 없다. 이 경우 -a
태그를 통해 연결 모드로 전환해준 후, -i
태그를 통해 인터렉티브하게 변경해주어야 한다.
ex) docker start -a -i CONTAINER
도커는 웹 서버와같은 장기적인 실행 프로세스에만 적용되지 않는다. 도커는 간단히 유틸리티 애플리케이션을 도커화하는데에도 사용될 수 있으며, 이럴 때 필요한 것이 인터렉티브 모드와 연결 모드이다.
이미지 & 컨테이너 삭제하기 + 이미지 검사(inspect)
docker rm [컨테이너명 or 컨테이너ID]
- 실행 중이지 않은 컨테이너를 제거한다. 이때, 실행 중인 컨테이너 제거 시도 시 에러가 발생한다.
- 하지만 하나하나 컨테이너를 제거하는 것은 너무 수고로운 일이다.. 컨테이너가 중지되면 자동으로 제거하도록 하고 싶다..
docker run
시--rm
태그를 사용하면 된다.- ex)
docker run -p 3000:80 -d --rm [컨테이너ID]
-> 이 컨테이너가 중지될 때마다 항상 제거된다.
docker images
- 우리가 가진 모든 이미지가 표시된다
docker rmi [이미지ID]
- 이미지와 이미지 내부의 모든 레이어를 삭제한다
- 단, 더 이상 컨테이너에서 사용되지 않고, 중지된 컨테이너에 포함된 경우에만 이미지를 제거할 수 있다. 해당 이미지에 대한 컨테이너가 실행 중인 경우 이미지도 삭제할 수 없다.
docker image prune
- 사용되지 않는 모든 이미지를 제거한다
docker image inspect [이미지ID]
- 이미지에 대한 정보가 포함된 긴 출력결과가 표시된다
- 해당 이미지의 레이어도 확인할 수 있다.
- 사용되지 않는 모든 이미지를 제거한다
컨테이너/컨테이너로부터 파일 복사하기
docker cp [복사할 파일 또는 폴더 경로] [컨테이너명:컨테이너 내부경로]
- 실행 중인 컨테이너로 또는 실행 중인 컨테이너 밖으로 파일 또는 폴더를 복사할 수 있다
- ex) docker cp dummy/. conatiner_name:/test
- 명령을 실행하고 있는 현재 디렉토리에서 dummy 폴더 내의 모든 파일 및 폴더를
- container_name이라는 이름을 가진 컨테이너의 '/test' 폴더 내부에 복사한다.
- 이를 통해 이미지를 재빌드하지 않고도 컨테이너 내부에 무언가를 추가할 수 있다.
- 하지만 로컬 -> 컨테이너 복사는 추천되지는 않는 작업이다. 반면, 컨테이너 -> 로컬 작업은 유용할 수도 있는데, 보통 컨테이너 내부에 쌓인 로그를 로컬로 복사해올 때 유용하다.
컨테이너와 이미지에 이름 지정 & 태그 지정하기
docker run --name testapp [이미지ID](:버전)
--name
태그를 통해 컨테이너 실행 시 이름을 지정할 수 있다.- 이 바로 아래에서 배우지만 이제 이미지ID는 이미지 name과 대응되고, 버전은 tag와 대응된다는 것을 알 수 있다.
이미지에도 비슷한 개념이 있는데, 이를 '태그(Tag)'라고 한다.
실제로 이미지 태그는 다음과 같은 형태로 두 부분으로 구성된다.name:tag
- name
- 이미지의 REPOSITORY라고도 한다.
- tag
- 이는 선택사항이다.
- 해당 이미지의 보다 특정화된 버전을 정의할 수 있다.
이렇게 두 부분으로 나눈 이유는 다음과 같다. 이름을 사용하여 이미지의 일반적인 이름을 설정할 수 있다. 정확히 말하면 여러 개의 특정화된 이미지 그룹을 만들 수 있다. 그리고 태그를 통해 해당 이름의 이미지 그룹 중 특정한 버전을 명시할 수 있다.
이미지 빌드 시 태그 설정 방법
docker build -t gaols:latest .
-t
태그를 통해 해당 이미지에name:tag
조합을 지정할 수 있다.
이미지 공유하기
이미지를 공유하는 방법은 2가지가 있는데
- Dockerfile을 공유하는 방법
- 구성 파일을 공유함으로써 다른 사용자가 이를 통해 직접 이미지를 빌드하도록 함
- 해당 이미지에 필요한 모든 주변 코드와 폴더 구조가 필요함
- 빌드된 전체 이미지를 공유하는 방법
- 이미지를 다운로드하기만 하면, 사용자는 이미지를 빌드할 필요 없이 사용하면 됨
- 모든 것이 이미지에 포함되어 있음
DockerHub에 이미지 push 하기
이미지를 푸시(push)할 수 있는 두 가지 주요 위치(Registry)가 있다.
- DockerHub
- 공식 도커 이미지 레지스트리
- 들어오는 이미질르 처리하는 방법과 이미지를 저장하고 배포하는데 사용할 수 있는 수천 개의 다른 서비스가 있음
- 배포 시에는 잘 사용 안함
- 무료
- public & private 이미지 여부 설정 가능
- Private Registry
- 실제 배포 시 더 많이 활용됨
- 도커 이미지를 지원하는 많은 공급자가 있으며, 제공업체에 따라 이미지 노출 정도를 제한할 수 있음. 공급자에 달려있음
어떤 레지스트리를 사용하든 간에 이미지를 push하여 도커 허브 또는 다른 공급자에 이미지를 공유할 수 있다. 그리고 이 이미지를 받아와서 사용할 수 있다.
도커 허브에 이미지를 푸시하는 방법을 알아보자!
- 도커 허브에 가입한 후 repository로 이동 > Create Repository 클릭
- 레포지토리 이름을 지정하고, visibility를 설정한 후, Create
- 도커 허브 Free plan의 경우 private repository는 계정당 1개만 생성 가능하다
- 레포지토리 우측 상단에
Docker commands
부분에 있는 명령어(docker push [사용자ID]/[이미지Name]:[이미지TAG]
) 복사- '/'를 포함하는 이미지명을 사용함으로써 더 이상 로컬에 존재하지 않고 Docker Hub에 있음을 도커에게 알려줄 수 있음.
- 이때, 해당 이미지명과 동일한 이미지가 로컬에 빌드되어 있어야 하는데, 없다면 다음 2가지 방법을 사용할 수 있다.
- 해당 이름으로 새롭게 이미지 빌드
- 이미지가 이미 있고 이를 푸시하고 싶은 경우 ->
docker tag [이전이름:태그] [새로운이름:태그]
- ex)
docker tag node-demo:latest maruhxn/node-hello-world
- 태그는 생략할 수 있다
- 이를 통해 기존 이미지를 새로운 이름으로 '복사'할 수 있다. 기존 이미지가 삭제되는 것이 아니다.
- ex)
이렇게 공유된 이미지는 public으로 설정되어 있을 경우, 모든 사용자가 다운로드 가능한 이미지가 된다. 때문에, 보안 상 문제가 될 수 있는 부분은 이미지에서 제외해야 한다.
만약 액세스 거부 에러가 발생한다면 docker login을 통해 도커에 로그인을 한 후, 다시 시도해보자!
docker pull & docker run 관련 주의 사항
docker pull은 항상 레지스트리에서 최신 버전을 받아오는 반면, docker run은 로컬 레지스트리에서 해당 이미지가 있는지 확인한 후, 이미지가 있다면 해당 이미지를 사용하고 없다면 원격 레지스트리에서 다운받는다. 때문에 로컬에 있는 이미지가 최신이 아니라면 docker run을 통해 실행한 컨테이너도 최신 버전이 아니게 된다. 항상 최신 버전을 사용하고 싶다면 docker run을 사용해서 이미지를 받은 후에 docker run을 실행하자!
'DevOps' 카테고리의 다른 글
[Kubernetes] 쿠버네티스 기본 개념 (0) | 2024.10.20 |
---|---|
[Docker] Docker Compose (1) | 2024.10.16 |
[Docker] 네트워킹: (교차) 컨테이너 통신 (1) | 2024.10.15 |
[Docker] 볼륨과 인수 및 환경 변수 (1) | 2024.10.15 |
[Docker] Docker란? (1) | 2024.10.14 |