Docker image & Container 빌드, 관리

1. 이미지 & 컨테이너
1.1. 이미지 vs 컨테이너
1.1.1. 이미지
도커에서 서비스 운영에 필요한 서버, 소스코드, 라이브러리 등 필요한 것들을 묶어서 가지고 있는 상태를 Docker image라고 한다. 그런데 이 이미지는 설정값과 파일들을 가지고 있지만 실행중이라는 것을 의미하지는 않는다.
1.1.2. 컨테이너
그 이미지를 실제로 실행하고 있는 것은 컨테이너라고 한다. 단순히 실행 안 하면 이미지, 실행하면 컨테이너라기보단 이미지가 어떠한 틀이라면 그걸로 컨테이너를 찍어낼(실행) 할 수 있는 것이다. 그래서 이미지 하나로 여러 컨테이너를 생성하는 것도 가능하고, 컨테이너를 삭제해도 이미지는 변하지 않는다.
1.2. Dockerfile
아까 설명한 이미지를 만들기 위해 작성하는 파일이다. 예시로
FROM node
WORKDIR /app
COPY . /app
RUN npm install
EXPOSE 80
CMD ["node", "server.js"]
이렇게 생긴 도커파일이 있을 수 있다. 위 도커파일을 살펴보면 처음에 FROM이 있는데,
1.2.1. FROM
FROM은 도커파일에서 베이스 이미지를 지정하는 지시이다. 도커 이미지를 만들 때 무의 상태에서 만들어가는 게 아니라 어떤 베이스를 가지고 그 위에 쌓는 게 일반적인데, 이런 베이스를 지정하는 것이다. 위의 코드에서는 node를 베이스로 하는 이미지를 만들 것이라는 뜻이다.
1.2.2. WORKDIR
컨테이너 내부의 작업 디렉토리를 /app 으로 설정한다는 의미이다. 이후의 모든 명령은 /app에서 실행되게 된다.
1.2.3. COPY
호스트 머신의 현재 디렉토리를 컨테이너의 /app디렉토리로 복사하는 것이다. 로컬에서 작업한 코드들이 이 과정을 통해 컨테이너로 들어가게 된다.
1.2.4. RUN
컨테이너 내부에서 실행할 명령이다. 위 도커파일에서는 npm install 명령을 실행해서 컨테이너 내부에 의존성들을 설치하게 된다.
1.2.5. EXPOSE 80
컨테이너가 80번 포트를 사용함을 명시한다. 그런데 이것은 실제로 기능하는 것이 아니라, 문서화 목적으로 쓰이는 것이다. 그래서 이 명령어를 쓴다고 실제 포트 매핑이 되는 것은 아니다. 실제 포트 매핑은 도커 실행 시 -p 옵션을 써서 진행해야한다.
1.2.6. CMD
컨테이너가 시작될 때 실행될 기본 명령이다. RUN은 빌드 시점에 실행되지만, CMD는 컨테이너 실행 시 작동한다. 그래서 RUN의 결과는 이미지에 포함되게 되고, 설치와 설정 작업에 이용된다. CMD는 컨테이너가 실행될 때 어떤 프로세스를 시작할지 지정하는 명령어로, CMD 여러 개를 써도 마지막에 지정한 프로세스가 시작된다.
1.3. Dockerfile로 빌드하기
도커파일이 준비되면 docker build 명령을 사용하여 이미지를 빌드할 수 있다.
docker build .
도커파일이 있는 위치에서 위 커맨드를 실행하면 이미지가 빌드된다.
빌드된 이미지를 바탕으로 컨테이너 실행은 docker run으로 진행할 수 있다. 빌드된 이미지는 읽기 전용이기 때문에 변경사항이 생기면 이미지를 다시 빌드하고 돌려야한다.
아까 말했듯 EXPOSE80 자체는 문서화 목적이라 실제 포트 매핑이 되지 않는다. 그래서 옵션 없이 실행하면 localhost로 빌드한 웹 앱에 접속할 수 없다.
docker run -p 3000:80
1.4. 이미지 레이어
도커 이미지는 레이어들로 구성되는데, 이 구조가 있어서 도커가 매번 명령을 처음부터 실행할 필요가 없다. 도커는 매번 명령을 캐싱해두었다가 이미지를 다시 빌드할 때 명령을 다시 실행할 필요가 없으면(변경사항이 없으면) 캐싱된 결과를 다시 가져와서 사용한다.
따라서 그 원리를 이용하면
FROM node
WORKDIR /app
COPY . /app
RUN npm install
EXPOSE 80
CMD ["node", "server.js"]
도커파일에서
FROM node
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
EXPOSE 80
CMD ["node", "server.js"]
으로 바꾸게 되면 npm install이 재실행되는 것을 막을 수 있다는 것이다.
2. 이미지 & 컨테이너 관리
2.1. 컨테이너 생성, 실행, 중지
docker [command] --help
도커 명령어들에 --help을 달게 되면 사용법이 출력된다. run명령어와 start명령어는 다르다. run은 컨테이너를 생성하고 실행하지만, start는 기존에 존재하는 컨테이너를 시작하는 것이다. 따라서 run에는 이미지명이 들어가야하고 start는 컨테이너명이 들어간다. 컨테이너를 중지하려면 stop명령어를 사용하면 된다.
docker run [옵션] [이미지명]
docker start [컨테이너명]
docker stop [컨테이너명]
2.2. Attached & Detached
한국어로는 포그라운드 & 백그라운드로 번역하는 것 같다. attach detach 개념은 실행 여부와는 별개의 것인데, attached일 때 현재 실행하는 터미널에 컨테이너의 동작 상태를 출력하게 된다. detached라면 실행은 하고 있지만, 현재 터미널에 출력되고 있지는 않다.
docker run명령어 실행 시 디폴트로 attached, docker start 시 디폴트로 detached가 된다. 디폴트를 따라야 하는 것은 아니고
docker run -d [이름]
docker start -a [이름]
와 같이 옵션을 붙여주면 디폴트로 attached가 되어야하지만 detached로 실행된다.
실제 실행해보면
~/devops/study_docker/nodeapp ───────────────────────────────────────────────────────────────────────────────────────────────── max-env at 21:22:49 ─╮
❯ docker run -p 3000:80 -d a1bab505873c ─╯
6be1ec3c5c838336604d90598df4c2798ae4a6e65e5232812a59e30d7b5a33e1
이런 식으로 나온다.
만약 다시 터미널에서 동작 상태를 보고 싶다면, 두 가지 방법이 있는데
2.2.1. 다시 Attach
docker attach [이름]
으로 다시 포그라운드에서 돌게 할 수 있다.
2.2.2. Log 가져오기
또 다른 방법으로는 컨테이너에 출력되는 로그를 log 명령어로 가지고 올 수 있다.
docker logs [이름]
으로 attached상태에서 출력되는 것들이 나오게 된다.
2.3. 인터렉티브 모드로 들어가기
localhost로 접속할 수 있는 형태가 아니라 터미널에서 상호작용하는 프로그램을 처리하려면 어떻게 해야하는가?
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)
만약에 이런 코드를
FROM python
WORKDIR /app
COPY . /app
CMD ["python", "rng.py"]
로 돜파일을 작성해 이미지를 빌드했을 떄, docker run을 그냥 실행해버리면 에러만 출력된다. 그 이유는 현재 입력을 터미널 외부에서 줄 수가 없는 상황이기 때문이다. detached로 바꿔도 입력을 줄 수 없는 것은 마찬가지라 터미널에서 상호작용이 가능하도록 하는 다른 옵션이 필요하다.
도움이 될 수 있는 옵션은 -i와 -t인데 각각 인터렉티브 모드와 터미널 생성을 의미한다. 이 옵션은 같이 쓰는 것도 가능하다.
docker run -it 023043bd53c8 ─╯
Please enter the min number: 1
Please enter the max number: 1
1
docker start -a -i trusting_easley
로도 인터랙티브 모드를 쓸 수 있다.
2.4. 이미지 & 컨테이너 삭제하기
docker rm [컨테이너명]
으로 컨테이너를 삭제할 수 있지만 실행중인 컨테이너는 삭제할 수 없기 때문에 중지해야 한다.
이미지 삭제는
docker rmi [이미지]
를 입력하면 레이어를 비롯해서 이미지가 삭제된다. 그런데 이미지를 쓰고있는 컨테이너가 존재하는 경우 이미지를 삭제할 수 없기 때문에 컨테이너를 먼저 제거해야한다.
컨테이너가 중지되면 자동으로 삭제되게 할 수 있는데
docker run --rm [컨테이너명]
으로 중지될 때마다 자동 삭제되게 할 수도 있다. 노드 서버와 같은 것들은 코드 변경 같은 상황이 아니면 중지하지 않는 경우가 많은데, 이런 것들은 어차피 다시 빌드해야하기 때문에 자동삭제 옵션은 꽤 유용한 편이다.
2.5. 이미지 검사
docker image inspect [이미지]
로 이미지에 대한 정보를 확인할 수 있다. 엔트리 포인트, 도커 버전, 사용중인 운영체제에 대한 정보들도 포함한다. 레이어에 대한 정보들도 여기서 확인할 수 있는데, 도커 파일의 명령어 개수보다 많을 수 있다. 이것들은 베이스에 있는 것들이 포함된 것으로 볼 수 있다.
2.6. 컨테이너 파일 복사하기
호스트에 있는 파일을 컨테이너로 복사하려면
docker cp [복사할 파일 경로] [컨테이너명]:[컨테이너 내부 파일 경로]
로 복사할 수 있다.
반대로 컨테이너에서 호스트로 복사하려면
docker cp [컨테이너명]:[컨테이너 내부 파일 경로] [복사할 파일 경로]
로 쓰면 된다.
2.7. 컨테이너와 이미지 이름&태그
컨테이너를 만들면 아무 의미 없는 이름이 붙는다. 원하는 이름을 붙여주기 위해서는
docker run --name [원하는 이름] [이미지]
로 --name옵션을 이용하여 이름을 붙여줄 수 있다.
이름 변경 시에는
docker rename [과거 이름] [변경할 이름]
으로 바꿀 수 있다.
이미지도 마찬가지로 그냥 생성하면 숫자로 구성된 ID만 나오게 된다. 이미지는 이름:태그 로 구성할 수 있다.
docker build -t imagename:ver1 .
와 같이 -t옵션을 이용하면 된다.
이미지 이름, 태그 변경은
docker image tag [과거 이름:태그] [새로운이름:태그]
로 변경할 수 있다.
3. 이미지 공유하기
3.1. Dockerhub에 이미지 push하기
도커 이미지는 개인 레포에도 올릴 수 있고, 도커허브에도 push 가능하다. 도커에는 이미지 공유를 위해 내장된 명령어가 있다.
Dockerhub에 가입하고 웹에서 리포지토리를 생성하고 여기에 이미지를 푸시하려면 docker push명령어를 사용하면 되는데, 이때 Dockerhub에서 생성한 리포지토리 이름과 로컬 이미지 이름이 같아야한다. 유저네임까지 포함해서 username/dockerhubrepo의 형식이어야 한다.
해당 이름의 이미지를 다시 빌드해도 되지만 이미지 이름을 변경해서 푸시해도 된다.
로그인을 안 하고 push하려고 하면 permission denied가 뜨는데, docker login으로 로그인해주면 된다.
3.2. 공유 이미지 가져오기
반대로 남이 Dockerhub에 올린 이미지도 가져올 수 있다. 명령어는
docker pull username/repo
로 로컬에 이미지를 가져올 수 있다.

