만약 프로젝트 내의 html 에서 작은 변화가 일어 났다고 가정
수정 이후 다시 이 이미지를 기반으로 컨테이너를 통하여 실행하게 되어도 이 변화가 감지 되어 변경되지 않는다
Html 코드는 소스코드인 애플리케이션코드의 일부이다
COPY . .
을 진행 할 때에 기본적으로 소스 코드를 이미지에 복사하고 복사한 시점에서 소스코드의 스냅샷을 만듭니다.
Html 의 작은 수정이 일어난 이후의 스냅샷은 현재 수정된 이후의 이미지와 달라야 합니다
즉 어떠한 작은 변화가 일어났더라도 새롭게 이미지를 만들어 주어야 한다는 뜻이죠
이미지는 일단 빌드되면 끝! 이라고 이해 하면 편합니다. (Closed Image)
이미지의 모든 것이 ReadOnly
이며 과거에 해당 코드를 복사했기 때문에 단순히 코드를 업데이트 하여 외부에서 편집할 수 없다
그렇기에 변경이 일어난 이후의 앱을 도커에서 실행 시키고자 한다면 새롭게 build
를 통하여 이미지를 재 생성 한다
가령 이미지를 생성하기 위한 dockerfile 이 다음과 같을 때
FROM node
WORKDIR /app
COPY . /app
RUN npm install
EXPOSE 80
CMD ["node", "server.js"]
다음 코드를 통하여 이미지가 빌드된 이후 이미지 안은 잠기게 되고 수정을 할 수 없게 된다
👍 만약 수정이 일어나 적용해줘야한다면?
새롭게 이미지를 빌드하고 컨테이너를 실행시켜 변경사항이 적용되도록 한다
🤢 ?? 이미지를 재 빌드 할 때 변경된 부분의 명령과 그 이후의 모든 명령이 재평가된다는 의미
만약 이미 빌드된 이미지를 수정 사항없이 재빌드 한다 했을 때 시간이 엄청 단축되는 것을 볼수 있다. 이는 캐시 때문이다.
✔ 도커는 기본적으로 모든 명령어에 대해 명령어를 다시 실행했을 때의 결과가 이전과 동일하다는 것을 인식했기 때문
이미지를 빌드할때마다 도커는 모든 명령 결과를 캐시하고 이미지를 다시 빌드할 때 캐시를 사용하여 명령을 다시 실행할 필요가 없다면 캐시된 결과를 사용한다
이것을 “레이어 기반 아키텍처”
라고 한다
모든 명령은 Dockerfile 의 레이어를 나타냄
이미지를 기반으로 컨테이너를 실행하게 되면 컨테이너는 기본적으로 Dockerfile 에 지정한 명령을 실행한 결과로 코드를 실행 중인 앱인 이미지 위에 새로운 추가 레이어를 추가하게 된다
이렇게 하면 이미지를 레이어로 실행할 때만 활성화되는 최종 레이어가 추가된다
EX)
FROM node WORKDIR /app COPY . /app RUN npm install EXPOSE 80 CMD ["node", "server.js"]
코드에서 COPY 부분에서 캐시된 결과값과 다르기 때문에 변화가 일어났다고 하면 아래 커맨드들도 캐시된 결과값과 일치하는지 여부를 알 수 없기 때문에 그 아래는 모두 실행된다
즉, 도커는 다시 실행해야 하는 항목만 다시 빌드하여 캐시를 활용하여 생성 속도를 높인다
캐시를 활용하여 위 코드를 최적화 한다면 npm install 하기 전에 package 파일 만 따로 복사한 후 npm install 이 일어나도록 하여 그외 파일의 변경이 있을 때 캐시된 결과를 그대로 쓸 수 있도록 해준다
FROM node
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
EXPOSE 80
CMD ["node", "server.js"]
도커는 코드에 관한 모든 것임 ( 구축하고 있는 애플리케이션 )
애플리케이션 구성 코드는 이미지를 통하여 관리하게 된다.
이미지에는 코드만 넣는 것이 아닌 코드를 실행하는데 필요한 도구인 실행 환경도 넣는다. 예를 들면 nodeJs 일 경우 node 환경을 FROM
으로 import
해오는 것과 같이
이러한 이미지는 Dockerfile
을 통하여 생성되는데 세부적인 명령을 이곳에서 제공하고 설정단계, 포트 문서화, 문서 카피 등등을 제공한다
이미지를 기반으로 여러 개의 컨테이너를 관리하게 되는데 컨테이너는 이미지위에 추가된 얇은 레이어일 뿐이다. 컨테이너 → 이미지를 기반으로 하는 실행 애플리케이션
오해하기 쉬운곳은 컨테이너는 이미지로부터 환경 설정등과 같은 커맨드를 복사해오는 것이 아니라 이미 존재하는 READONLY
인 이미지에 추가로 layer 로서 추가되는 것 뿐이다
결론적으로 도커는 앱을 포함하는 격리된 환경과 그 앱을 실행하는데 필요한 모든 것, 모든 환경들을 격리된 컨테이너 내부에 모두 포함하는 것이라 할 수 있다
컨테이너
: 이미지를 기반으로 하는 격리된 소프트웨어 유닛, 이미지의 실행중인 인스턴스
이미지의 레이어
: 이미지의 모든 명령은 캐시 가능한 레이어를 생성. 레이어는 이미지 재구축 및 공유를 돕게됨
만약 기존의 이미지를 통하여 실행했던 적이 있던 컨테이너를 삭제하지 않고 갖고 있다고 하였을 때 이미지를 다시 빌드하여 컨테이너를 생성하는 것은 매우 비효율적이다
그렇기에 기존의 docker 컨테이너를 실행하는 docker run {컨테이너_이름}
을 사용 해주어야 한다
만약 위의 커맨드를 입력하였다면 컨테이너가 백업되며 다른 모드에서 컨테이너를 시작하게 된다
명령 프롬프트로도 가능하지만 docker 데스크톱을 사용한다면 그저 다시 실행하고자 하는 컨테이너에 플레이 버튼을 누르므로서 똑 같은 효과를 볼 수 있다
하지만 run 을 통하여 실행하게 되면 실제로 상호 작용하거나 로그를 볼수가 없다
Run 을 하게 되었을 때 앱이 실행되며 디버그모드가 종료 되지 않는 것과 차이가 있는데
만약 컨테이너를 재실행 하고자 할 때 사용되는 start
명령어는 터미널의 프로세스가 즉시 완료된다
터미널의 실행 중인 도커 컨테이너에 더 이상 연결해주진 않는다 (컨테이너는 백그라운드에서 실행되지만 말이다)
그렇다면 run 을 통한 컨테이너의 실행은 어떠할까? Run
을 통한 컨테이너의 실행은 컨테이너를 생성과 동시에 프로세스가 터미널을 차단하게 된다 (컨테이너는 포어그라운드에서 실행중이기 때문)
이러한 결과는 설정에 의해서 나타나게 된다. 물론 사용자 설정을 통하여 이를 변경할 수 있겠지만 디폴트로 적용된다 하였을 때에 docker start 로 시작하는 경우 detached 모드가 적용되며 앞단에서 실행되며 docker run 의 경우에는 attached 모드가 적용되여 뒷단에서 실행된다
🤔 detached 와 attached 는 언제 설정하는 것이 좋을까?
먼저 attached 모드의 경우 실시간으로 앱을 모니터링 할 경우 입출력에 대한 분석이 가능하다. 터미널에 표시되는 로그를 통해서 로그 분석에 용이할 것 같다.
즉, attached 모드는 컨테이너의 출력 결과를 수신한다는 것을 의미
만약 컨테이너를 새로 만들고 detached 모드로 하고 싶다면?
docker run –p {로컬포트}:{컨테이너포트} -d {이미지ID}
를 통하여 모드를 쉽게 변경하여 프로세스를 즉시 완료할 수 있다
만약 필요에 의해 현재 백그라운드에 실행중인 detached 컨테이너를 attached 로 갖고 오고자 한다면?
docker attach {컨테이너이름}
을 통하여 바꿀 수 있다
만약 실시간으로 모니터링 하지 않고 백그라운드에서 실행중인 컨테이너의 로그에 접근하고자 한다면 위의 방법보다는 logs
를 활용하는 것이 좋다
docker logs {컨테이너이름}
: 해당 컨테이너가 실행된 이후 적립된 로그를 볼 수 있다
또한 이전 로그들을 보면서 실시간으로 로그를 모니터링 하고자 한다면 logs
의 키워드인 -f 로 계속 트래킹 할 수 있다 ( 로그를 보여주면서 attached 모드로 바꿀 수 있음 ). 이렇게 향후 로그들을 볼 수 있게 된다
마지막으로 중지된 컨테이너를 시작하고자 할 때, 그리고 attached 모드로 적용하고자 할 때
docker start -a {컨테이너이름}
을 통하여 attached 모드로 바로 시작 할 수 있다
도커의 주요한 셀링 포인트는 웹서버를 독립적으로 가지게 하는 컨테이너를 통하여 애플리케이션 관리의 용이함이라고 할 수 있는데, 이에 국한되지 않고 서버를 전혀 사용하지 않는 애플리케이션 또한 도커를 활용해 볼 수 있다
가령 다음과 같은 파이썬 프로젝트가 있다고 해보자
간단하게 최소값과 최대값을 통해 boundary 를 설정하여 해당 범위 내의 랜덤 값을 보내주는 프로그램을 예시로 시작한다
from random import randint
min_number = int(input('Please enter the min number: '))
max_number = int(input('Please enter the max number: '))
if (max_number < min_number):
print('Invalid input - shutting down...')
else:
rnd_number = randint(min_number, max_number)
print(rnd_number)
이미 이해하겠지만 해당 프로그램의 경우 사용자의 입력을 받지 않으면 interact 할 수 없는 프로그램이다. 이러한 프로그램을 도커화 하여 사용하게 될때에 detached 로 실행 하였다 한들 전혀 쓸곳이 없을 것이다. 그렇기에 앞서 진행한 detached 와 attached 모드에 대한 공부를 하게 되었던 것
먼저 docker hub 로부터 python 베이스 이미지를 import 하고
Workdir
를 지정하여 python-app 폴더로 명시한다
이후 현재 프로젝트 디렉토리의 모든 파일을 해당 workdir 로 복사하고
메인 프로그램인 rng.py 를 실행하게 된다
이렇게 하여 이미지를 빌드, docker build .
이후 docker run {이미지ID}
하게 되었을 때 다음과 같은 에러가 발생한다
✔ : 네트워크 환경에서 이루어지는 앱이 아니기 때문에 -p 를 통한 포트 퍼블리싱 작업을 생략
Run 명령어를 통하여 컨테이너에 연결할 수는 있으며 run 의 default 모드는 attached 이기 때문에 interact 할 수 있을 것이라 생각하였지만 컨테이너에는 그 어떤것도 입력할 수 없기 때문에 콘솔처럼 작동 시킬수 없다. 그렇기에 input 이 포함되는 라인에서 에러가 발생됨
이를 정상적으로 작동되게 하려면 몇가지 옵션이 존재하는데
첫번째, -i
: interactive, keep stdin open even if not attached ( attached 모드가 아니더라도 표준 입력을 open 한다 )
두번째, -t
: --tty, Allocate a pseudo-TTY (의사 TTY 의 할당) → 터미널 생성
즉, i 를 통하여 표준 입력을 받을 수 있게 하고, t 를 통하여 터미널을 생성하여 터미널안에서 상호작용 할 수 있게 한다 (👀 -i -t 따로 써도 되지만 -it 로 커맨드 입력하여도 똑같이 동작함)
입출력 이후 rng.py 가 종료되면 컨테이너에서도 더 이상 실행되지 않음을 확인할 수 있음
만약 종료되고 난 이후에 다시 실행하고자 한다면?
Start 에서-a
를 통하여 attached 모드로 그리고-i
를 통하여 상호작용할 수 있도록 하여 시작해주면 똑같이 작동한다
도커는 웹서버나 웹 애플리케이션 같은 장기적인 실행 프로세스에만 국한되지 않고 간단한 유틸리티 애플리케이션을 도커화하는데에도 사용할 수 있다
더 이상 사용하지 않는 이미지나 컨테이너의 관리
docker rm {컨테이너 이름}
을 통하여 더 이상 사용되지 않는 컨테이너들을 삭제해볼 수 있음docker images
를 통하여 현재 생성된 이미지를 확인할 수 있음
실습 위주로 사용하였던 node 같은 경우 크기는 노드와 노드 실행 툴만 할당된 것이 아니라 노드 이미지 빌드에 이들 툴과 운영체제 이미지가 추가된 크기이다
1. 수동으로 삭제 : docker rmi {이미지ID}
를 통하여 이미지를 삭제할 수 있다. 이미지를 삭제하게 되면 기본적으로 내부의 모든 레이어를 삭제하게 된다
이미지의 경우에는 컨테이너가 run/stop 이건 상관없이 컨테이너를 삭제하여야만 삭제할 수 있다. 이는 컨테이너가 이미지에 실행 레이어를 띈 형태를 취하고 있기 때문에 참조되는 이미지가 존재한다면 이미지를 삭제 할 수 없도록 되어 있기 때문
2. 도커 데스크톱으로 삭제
3. docker image prune
: 사용되지 않는 모든 이미지를 제거
docker run --rm
: 컨테이너가 종료될 때 자동으로 제거
다음과 같이 3000 포트에 앱을 도커 컨테이너를 생성하여 실행 시켰다 하였을 때
docker stop
을 통하여 해당 컨테이너를 종료 시키고 난뒤 docker ps
를 통하여 확인해보면 컨테이너가 자동으로 제거되었음을 알 수 있다
만약 지속적인 변화가 이루어지는 웹앱 같은 경우에 어차피 수정이 일어날때마다 이미지를 재빌드하여야 하기 때문에 기존 컨테이너를 제거해야 하는 것이 올바른 시나리오이기에 이런 방식을 많이 사용하게 된다
docker image inspect {이미지ID}
: 이미지의 세부 정보 확인
docker cp
: 실행중인 컨테이너로 또는 실행중인 컨테이너 밖으로 파일 또는 폴더 복사
소스 지정하기
다른 위치에 복사하려는 폴더나 파일을 지정
가령 dummy 폴더를 현재 실행중인 컨테이너에 복사하고 싶다면
docker cp dummy
또는 해당 폴더내의 특정 파일을 복사한다면 dummy/test.txt 로 접근하면 된다
아니라면 dummy/. 으로 모든 파일 복사
복사 목적지 지정하기
데이터는 컨테이너 내부에 있게된다. 여기서 컨테이너 이름이 필요하게 되며 (복사할 컨테이너를 알아야 하기 때문)
→ {컨테이너이름}:/{작업디렉토리}
: 만약 작업 디렉토리가 존재하지 않다면 생성해준다
복사된 파일 확인해보기
더미 폴더에 더미 파일로 사용한 text 파일을 지우고 다음 명령을 통해서 컨테이너로부터 로컬 폴더로 복사한다
→ docker cp {컨테이너이름}:/{디렉토리} {로컬폴더}
위 명령어들을 통해서 실행중인 컨테이너에 이미지를 다시 작성하지 않고도 컨테이너에 무언가를 추가해줄 수 있다
🤔 잘 사용되지 않는 이유?
일반적으로는 오류가 발생하기 쉽기 때문에 수정이후에 파일을 컨테이너에 복사하진 않는다 또한 현재 실행중인 파일을 교체하는 것도 불가능하다
주로 웹 서버의 구성 파일을 변경하던가 할때에 쓰이게 됨. 또는 컨테이너 자체에서 갖게되는 로그 파일이 있다하였을 때 로컬로 복사하여 직접 접근하는 방법도 있다
일반적으로 이름을 지정하지 않고 빌드나 run 하게 될때에 도커가 임의로 이름을 해당 이미지/컨테이너에 지어주게 된다. 다만 무작위로 만들어진 이름은 관리하기도 쉽지 않으며 가독성면에서 어떤 이미지/ 컨테이너인지 유추할 수 없는 문제가 존재한다
1. 컨테이너에 이름 지정하기
--name
을 이용하여 컨테이너에 이름을 지정
2. 이미지에 태그 지정하기
이미지 태그는 두가지로 구성되어 있다. 레파지토리(이름):태그
로 나뉘어져 있다
네임으로는 이미지의 이름을 지정할 수 있으며 그룹이나 특정 이미지를 지정할 수 있다. 그리고 태그는 그룹이나 특정 이미지의 버전이나 특징을 지정할 수 있다.
Docker hub 에서 제공되는 공식 이미지의 경우 다양한 태그들을 지원하는데 커스텀으로 제작한 애플리케이션의 태그 또한 임의로 지정할 수 있다 (버전관리, 백업등을 위해)
물론 이미지에 태그가 없더라도 이름만으로도 고유 식별자가 된다
docker build -t {이름:태그} .
: 태그의 경우 optional 이지만 -t 를 통하여 이름과 태그를 지정해 줄 수 있다
이미지 태그를 통하여 실행시키기