본문 바로가기
DevOps/Docker

Docker 이미지 & 컨테이너

by 민우's 코딩 2024. 2. 6.

Dockerfile 작성 요령


FROM      이미지 구축에 필요한 이미지 이름을 넣는다. ex) FROM node -> 도커한테 노드 이미지를 가지고 오는걸 시작우선하고 다음 실행을하자.

WORKDIR  도커 컨테이너의 작업 디렉토리를 설정하는 명령 ex) WORKDIR /app 설정하면 이후 모든 후속 명령이 /app 폴더 내부에서 실행 될 거라고 알려줌


COPY . .       도커한테 로컬머신에 있는 파일이 이미지에 들어가야하는지 알려야한다.
. . 은 두 개의 경로를 지정하는데 
첫 번째 자리는 컨테이너 외부, 이미지의 외부 경로이며 이미지로 복사되어야 하는 파일들이 있는 경로이다. -> . 을 넣었으니 이 프로젝트의 모든 폴더, 하위 폴더 및 파일을 복사하라고 도커한테 알려준 것
두 번째 자리는 그 파일을 저장해야 하는 이미지 내부의 경로이다. -> 만약 두 번째 .대신 /app을 쓴다면? Dockerfile과 동일한 폴더에 있는 파일과 하위 폴더들이 컨테이너 내부의 app 폴더에 복사되는 것이다. 

RUN  위에 까지 해서 모든 로컬 파일을 이미지에 복사 후 이미지에서 명령을 RUN하기 위해 사용, node를 쓴다면
RUN npm install  하여 node의 종속성 설치를 실행 할 수 있다.

이 모든 설정은 이미지 설정을 위한 도커 한테 내린 명령들임
이미지를 실행 시키는 것이 아니라 이미지를 기반으로 컨테이너를 실행하는 것이다.


EXPOSE 포트번호
Dokerfile 마지막 명령 전에 로컬 시스템에 특정 포트를 노출하고 싶다는 것을 도커한테 알려줌 
설정을 하고 나서야 그 포트번호를 수신하고 있는 컨테이너를 실행할 수 있게 된다.
그냥 이 포트를 노출한다는 것을 문서화한것이다. 실제론 docker run을 실행할때 -p(publish)를 사용하여 실제로 포트를 노출 시켜야한다.

우리는 컨테이너를 시작하는 경우에만 서버를 시작하고 싶기에
RUN node server.js 같이 실행하는 것이 아닌 CMD 를 이용한다.

CMD ["" , ""]   node, server.js 를 각각 넣는다고 하여 설명하면...
도커에게 이미지를 기반으로 컨테이너 생성 시 그 내부에 있는 node 명령을 사용하여 server.js 파일을 실행하게 지시함

dockerfile 작성 후 이미지 만들기 
터미널로 들어가서
docker build  이미지 실행이 아닌 이미지를 만드는 명령어 . 을 붙혀서 도커에게 이 명령을 실행하는 곳에 Dockerfile이 있다는 것을 알림

이후 나온 ID를 docker run ID  를 실행 시켰지만 localhost:80 에 접속해도 웹 사이트가 표시되지 않음
왜? -> EXPOSE 80 을 썼지만 실제론 추가만 되었을뿐 아무 것도 하지 않음!
해결 방법 -> 컨테이너 실행 시 -p 플래그 추가  -p : publish 도커에게 어떤 로컬 port가 있는지 알려줌

docker run -p 3000:80 ID   3000 포트에 내부 도커 컨테이너 노출 포트 80을 넣어서 localhost:3000에 표시 되게한다.  3000에 publish되어서 작동이 되는 것.

별첨. docker run 을 실행시 ID를 전체 다 복사하여 넣을 필요가 없다. 첫 번째 몇개의 문자만 사용하여 실행 가능함.



이미지는 읽기 전용이다 !

소스 코드를 이미지에 복사하고 이후 소스 코드를 편집하면 이 변경 사항은 이미지 소스 코드에 포함되지 않는다.
업데이트된 소스 코드를 새 이미지로 복사하려면 이미지를 다시 빌드해야한다.
이미지는 닫힌 템플릿이다...

이미지는 레이어 기반이다 !
이미지를 빌드하거나 다시 빌드할 때 변경된 부분의 명령과 그 후 모든 명령이 재평가함
이미지를 빌드 할 때마다 도커는 모든 명령 결과를 캐시하고 이미지를 다시 빌드할때 명령을 다시 안써도 된다면 캐시된 결과를 사용한다. -> 레이어 기반 아키텍쳐이다.

하나의 레이어가 변경될 때마다 다른 모든 레이어가 다시 빌드 된다. 소스코드에 한 글자만 변경되어도 다시 빌드하면 시간이 오래 걸린다.

그래서 package.json 파일을 COPY 한다는 코드를 Dockerfile에 적어준다면 최적화 되어 다시 빌드하면 속도가 전보다 빠르다.. 최적화 한 것이다. -> 도커가 package.json 파일이 변경되지 않았음을 확인했기 때문에!

여기서 가장 중요한 것은 레이어 기반 접근 방식, 레이어 기반 아키텍처를 이해하는 것이다.

도커 명령어 정리

docker ps -a : 더 이상 실행 되지 않는 중지된 컨테이너 포함하여 과거 모든 컨테이너 표시
docker start ID,name(둘중하나) : 컨테이너를 백업함, 터미널이 중지 되진않는데 컨테이너가 실행됨

docker run VS docker start 차이?
각 명령어 default 값
docker start : detached(분리) 모드 , 터미널에 명령어 입력 가능

docker run : attached(연결) 모드 , 터미널이 중지됨

둘의 차이를 간단하게 예시로 설명하자면...
server.js 코드에 console을 추가하여 터미널에 표시되는 걸 살펴보면
docker start 로 실행한 컨테이너는 입력시 터미널창에 아무런 표시가 되지 않는 반면에
docker run 으로 실행한 컨테이너는 터미널 창에 내가 입력한테 표시 된다.
즉 attached 모드는 우리가 컨테이너의 출력 결과를 수신함

docker start를 하고도 log를 보는 법
docker logs ID -> 과거 logs들을 보여줌
docker logs -f ID -> follow 모드로 변경되어 attached 모드 처럼 터미널에 logs가 보여짐
docker attach ID : 컨테이너를 attached 모드로 변경

docker start -a ID : 컨테이너 백업시 detached 모드가 아닌 attached 모드로 실행


python 파일을 docker 이용해보자
FROM python
WORKDIR /app
COPY . /app
CMD ["python", "python.py"]

docker build .
docker run ID -> 오류나는 이유? -> 컨테이너로 실행되는 app에는 어떤 것도 입력할 수 없다.
실행중인 컨테이너와 컨테이너로 실행 중인 app이 상호 작용할 수 없는데 docker run을 사용하면 디폴트로 컨테이너에 연결된다. 

해결 방법 : 인터렉티브 모드로 시작하자. -i  + -t (터미널 생성한다는 의미)

docker run -i -t ID     or  docker run -it ID

근데 docker start 로 재실행하면 detached 모드여서 컨테이너와 통신이 불가함
attached 모드로 재실행하던지 or docker start -a -i ID

이런식으로 웹 서버나 노드만 사용하는게 아닌 방법도 있다
필요에 따라 컨테이너와 상호작용 할 수 있고, 필요하지 않은 경우 하지 않을 수 있음




이미지 컨테이너 관리 (삭제)
docker ps -> 컨테이너 목록 불러옴   docker rm 컨테이너이름 -> 컨테이너 삭제
docker image -> 이미지 목록 불러옴 docker rmi 이미지ID -> 이미지 삭제
이미지 삭제시 더 이상 컨테이너에서 사용되지 않고 중지된 컨테이너에 포함된 경우에만 삭제 가능
현재 사용되지 않는 모든 이미지 제거를 위해
docker image prune 

docker rm 컨테이너이름 

컨테이너 자동 제거 방법
--rm (컨테이너가 종료될 때 자동 제거되는 플래그) run 명령에 간단히 추가
docker run -p 3000:80 -d --rm ID

이 컨테이너를 중지 시키면 컨테이너가 제거된다.
docker ps -a를 실행하여 목록을 보면 없다는 걸 알 수 있다.
노드 서버 컨테이너는 다시 시작하지 않는 경우가 많다.




이미지 자세히 살펴보기

컨테이너는 이미지를 기반으로 빌드되고 동일한 이미지를 기반으로 실행하는 여러 컨테이너는 이미지 내부의 코드를 공유한다. 
이미지를 자세히 보는 명령어
docker image inspect ID
이미지 전체 ID, 생성된 날짜 및 시간, 이미지 기반으로 실행될 컨테이너에 대한 구성(포트, 변수,ENTRYPOINT),도커 버전, 사용중인 운영 체제, 이미지의 레이어들 
우리는 CMD 명령 앞에 6가지 (FROM,WORKDIR,COPY,RUN,COPY,EXPOSE)명령을 적었는데 이미지를 자세히 보았더니 6개 이상의 레이어가 있다.-> Dockerfile에 정의된 레이어에만 국한되지 않기 때문!





컨테이너로 부터 파일 복사
cp 명령 : 컨테이너에 무언가를 추가하거나 무언가를 추출하고 싶으면 쓰는 복사를 의미하는 명령
실행중인 컨테이너로 또는 실행 중인 컨테이너 밖으로 파일 또는 폴더를 복사할 수 있다.
예를 들어 새로운 폴더와 파일을 만들어(dummy/test.txt) 확인해볼 수 있다.

docker cp dummy/.  컨테이너이름:/test
우리는 dummy 하위 내용들이 컨테이너 내부 test 폴더에 복사했다.
그런다음 원래 dummy 폴더에 있던 test.txt 파일을 지우고

docker cp 컨테이너이름:/test/test.txt  dummy
를 실행하면 dummy 밑에 없던 test.txt 파일이 가져와지는걸 알 수 있었다.

이를 첫번째 인수와 두 번째 인수의 차이를 둘을 비교하면 알 수 있는데

docker cp dummy/.  컨테이너이름:/test  첫 번째 인수:로컬 폴더 두 번째 인수: 컨테이너와 경로
docker cp 컨테이너이름:/test/test.txt  dummy  첫 번째 인수: 컨테이너와 경로  두 번째 인수: 로컬 폴더

이런식으로 컨테이너에 무언가를 추가하거나 무언가를 추출 할 수 있다.

우리는 소스 코드가 변경된다면 일반적으론 이미지를 다시 빌드하고 컨테이너를 재시작해야한다.
하지만 이런식으로 파일을 교체하는 것은 좋은 해결책이 아니다.
현재 실행중인 파일을 교체하는 것도 실제로 불가능하고 app이 손상할 수 있다.





컨테이너와 이미지에 이름 지정 & 태그 지정하기

우리는 설정하지 않았기에 지금껏 자동으로 설정된 컨테이너 이름 및 이미지ID를 사용했다.
잊어먹기 쉽기 때문에 이름 및 태그를 지정해둔다면 내가 어떤 이름으로 어떤 컨테이너 및 이미지를 사용하는지 확인하기 쉽기 때문에 지정하는 법을 아는게 좋을 것 이다.

--name (컨테이너에 이름 추가하는 옵션)
docker run -p 3000:80 --name myname 이미지ID
이 컨테이너를 실행한 후 docker ps로 확인하면 name이 myname으로 되어 있는걸 알 수 있다.

이미지 태그
이미지에 이름을 지정 할 수 있는데 태그 설정을 하면 된다.
이미지 태그는 두 가지로 나뉘는데. 
name(REPOSITORY) : tag(TAG)

예를 들면 node는 이미 name(REPOSITORY)가 있는 걸 알 수있는데
node에 다양한 태그가 존재한다. node: 14,12 등 버전을 나타내는데, 특정 버전 및 구성을 사용 할 수 있고 구성이 가벼워진다.

이미지 태그 지정 법
-t 옵션을 사용한다
docker build -t 이름:태그  조합으로 지정 할 수 있다.
이미지의 이름:태그 사용하여 컨테이너 실행하기
docker run -p 3000:80 --name myname 이름:태그
우리는 myname이라는 컨테이너를 생성할때 이미지ID 대신 내가 이름:태그를 정한 이미지로 만들었다.

우리는 이런 태그들을 사용하여 다양한 버전을 사용 할 수 있고 식별하기 쉬워질 것이다.



이미지 공유하기

우리는 우리의 이미지와 컨테이너를 다른 사람과 공유하기 위해 도커를 사용한다고 볼 수 있다.
이미지가 있으면 그 이미지를 기반으로 컨테이너를 실행할 수 있기 때문에 이미지를 공유한다.

이미지를 공유할 때 두 가지 방식
1.Dockerfile을 공유하는 방법
2. 완성된 이미지를 공유하는 방법

완성된 이미지 공유하는 방법
도커의 이미지 공유를 위해 내장된 명령 이용하기.
이미지를 푸시할 수 있는 두 가지 주요 위치
1. docker hub 2.Private Registry
(무료 or 유료) (유료)
어느 쪽이든 상관없이 그 레지스트리에 이미지를 push 하여 이미지를 공유할 수 있다. docker push image_name
docker pull 명령을 실행하면 푸시된 이미지를 사용할 수 있다. docker pull image_name

goals latest

도커 허브에 푸시하는 이미지로 변경
1. 해당하는 폴더에서 도커 고유태그로 이 이미지를 다시 빌드하기.
(우리가 원하는 이미지의 복사본을 만들어서 푸시함.
 1. docker tag golas:latest 도커허브ID/설정한레포지토리이름
      우리는 golas 이름과 latest태그를 가진 이미지의 복사본을 만들었다.
우리의 도커허브ID/설정한레포지토리이름으로 설정한 복사본 이미지가 있는걸 docker images로 확인가능하다.
2. docker push 도커허브ID/설정한레포지토리이름 으로 푸시하면 dockerhub 레포지토리에 올라간걸 확인 할 수 있다.
)

우리는 이 설정을 하고나면 그 노드 이미지에 대한 연결을 설정하여 필요한 추가 코드만 푸시한다.


도커  pull 방법
docker pull 도커허브ID/설정한레포지토리이름
docker images 를 통해 가져온걸 확인 할 수 있다. 
도커 허브에서 공개로 설정했기에 로그아웃을 했어도 모든 사람이 가져 올 수 있다.

내가 보는 강의영상 제작자의 이미지를 가져와서 직접 run 시켜보았더니
실제로 그 파일이 존재하지 않아도 loaclhost:8000에 들어가니 실행이 되었다.

원격 이미지 pulling 및 실행에 대한 참고사항
docker pull 도커허브ID/설정한레포지토리이름 -> 항상 컨테이너 레지스트리에서 최신 이미지 가져옴
docker pull 없이 docker run 도커허브ID/설정한레포지토리이름을 실행해도
이미지 이름이 사용한 컨테이너 히스토리에 자동으로 접근하기에 Dockerhub에서 이미지를 자동으로 풀링함

하지만 docker run은 그 이미지의 최신 버전이 내 로컬 시스템이 있는지 체크하지 않기에 
그동안 이미지를 업데이트하여 다시 push 했다면 docker pull로 최신 이미지 버전을 받아와야한다.



정리

이미지는 템플릿이자 컨테이너의 블루프린트이며 하나의 동일한 이미지를 기반으로 하는 여러 컨테이너를 실행 할 수 있으며, 다양한 컨테이너를 위해 여러 이미지를 가질 수 있다.
또한 이러한 컨테이너가 이미지 위에 작은 레이어로 효율적인 방식으로 실행된다는 것을 배웠다.

컨테이너가 아닌 이미지에 저장된 코드와 환경을 사용하여 이미지의 도움으로 구성된 app을 실행한다.
이미지는 docker pull로 다운로드 되거나 자체 빌드의 경우에는 Dockerfile의 도움으로 생성된다.

Dockerfile과 docker build 는 새 이미지를 빌드한다.

이미지를 기반으로 컨테이너를 실행할 때마다 실행해야하는 명령이 있다!
Dockerfile에 넣은 모든 명령은 레이어를 만들고 이미지는 여러 레이어로 구성된다.
레이어 -> 빌드 속도를 최적화 하기 위해 존재함

 

출처 : https://www.udemy.com/course/docker-kubernetes-2022/