본문 바로가기
IT Security/Docker & Kubernetes

[Docker & Kubernetes] 7. Docker 매우 기본적인 명령어 사용법3

by Rosmary 2022. 5. 25.
728x90
반응형

 

 

 

 

지난 두 개의 포스팅으로, Docker에서 가장 기본적으로 사용하는 명령어에 대해 알아보았다. 오늘은 기본 명령어에 대한 마지막 포스팅으로 docker 이미지 pull + 컨테이너 생성/구동을 한 번에 진행해주는 docker run 명령어와, 이미지를 Registry로 올리는 docker push 명령어에 대해 정리를 해보려 한다. 

 

사실 지금까지 3개의 포스팅에서 작성한 내용은, docker를 사용하면 절대 사용하지 않을 일이 없는 명령어들이다. 지금까지의 명령어 외에도 추가로 알아봐야 할 명령어가 많지만, 그 내용을 모조리 포스팅으로 작성했다가 필자가 돌아버릴 수도 있기 때문에 중간중간 필요할 때마다 언급하면서 넘어가려 한다.

 

뻘소리를 조금 했다. 바로 시작해보자.

 

 

 

1. 이미지 Pull + 컨테이너 생성 및 실행: docker run

 

docker run 명령어는 상당히 특이한 명령어다. 이전의 포스팅에서 보았던 docker pull, docker create + docker start 가 모조리 혼합되어 있는 형태다. 사실, 이 때문에 docker를 사용하다보면 docker create와 docker start는 거의 사용하는 일이 없다. 아래의 그림을 보면 조금 이해가 될 것이다.

 

 

 

지금까지 필자의 포스팅을 그대로 따라오신 분들이라면, 현재 서버 내에 남아있는 이미지는 nginx 최신 태그가 마지막일 것이다. 필자는 docker run 명령어로 ubuntu 최신 태그를 실행할 것인데, 이 명령어의 결과가 어떻게 나타날지 한 번 확인해보자.

 

 

#  docker run -it --name ubuntu_test ubuntu:latest

 

 

docker run으로 현재 서버에 존재하지 않는 이미지를 컨테이너로 구동시키면, 가장 먼저 진행하게 되는 것이 Docker 서버 내에 사용자가 요청한 이미지가 존재하는지에 대한 확인이다. 현재 필자 서버에는 ubuntu 이미지가 없었기 때문에, Unable to find image 라는 문구와 함께 최신 태그의 ubuntu 이미지가 다운로드 된 것이 보인다. 

 

자, 이 이미지가 ubuntu_test라는 컨테이너로 잘 실행이 되었을까? 당연히 실행이 잘 되었다. 자세히 보면 docker run 명령어의 결과가 출력이 완전히 완료되고 난 후의 프롬프트 형태가 달라진 것이 보인다. docker가 ubuntu 컨테이너의 구동을 넘어, 해당 컨테이너에 접속까지 시켜주었다(사실 컨테이너에 접속까지 된 것은 옵션 -it 가 원인이다)

 

이 상태에서 리눅스 ssh 세션을 하나 더 열어 docker ps로 현재 구동중인 컨테이너를 확인해보자. ubuntu_test 컨테이너가 정상적으로 프로세스에 올라온 것이 확인된다.

 

 

이 상태에서, ubuntu_test2를 exit으로 빠져나가보자. 그리고 다시 docker ps를 입력하면, 조금 전과 달리 ubuntu_test 컨테이너가 종료된 것이 확인된다.

 

 

사실, 너무 당연한 것이, ubuntu_test 컨테이너는 bash 쉘 실행을 기반으로 두고 있기 때문에, 사용자가 ubuntu를 빠져나가는 순간 자신이 할 일은 끝이 났다고 생각해서 컨테이너 실행을 멈춘 것이다. 하지만 이전에 -it 옵션없이 ubuntu 컨테이너를 구동했을 때와 달리, 정상 종료가 아니기 때문에 docker start로 컨테이너를 다시 구동하면 정상적으로 구동이 되는 것을 확인할 수 있다. 구동 후, docker exec 명령어로 다시 컨테이너로 접속하는 것도 가능하다.

 

 

그럼, 사용자는 ubuntu_test 컨테이너 서비스를 중단하지 않기 위해 세션을 유지하고 있어야 하는 걸까? 그렇지는 않다. 컨테이너를 구동 상태로 두면서도 빠져나오는 방법이 있다. 현재 ubuntu_test 쉘에서 Ctrl + p + q 를 차례대로 입력해보자. 

 

#  Ctrl + p + q

 

 

read escape sequence 라는 문구 출력과 함께 다시 CentOS 쉘로 복귀한 것이 보인다. 하지만 exit를 입력했을 때와 달리, 컨테이너는 중단되지 않고 계속 구동 중임을 확인할 수 있다.

 

이제 조금 다른 상황을 생각해보자. 방금 전에는 Docker 로컬 서버에 존재하지 않은 이미지를 docker run으로 구동시킨 사례였다. 이제, ubuntu 이미지가 서버 내에 존재하고 있으니, 동일한 docker run 명령어를 사용하되, 컨테이너 이름만 ubuntu_test2로 지정해보자.

 

#  docker run -it --name ubuntu_test2 ubuntu

 

 

조금 전과는 달리, docker pull의 결과나 컨테이너 ID 값이 출력되지 않고 바로 컨테이너로 접속된 것이 보인다. 

이를 통해, docker run 명령어는 다음과 같이 동작함을 확인할 수 있다.

 

-  로컬 Docker 서버에 지정한 이미지가 있는지 확인한다.

-  없으면 이미지를 Pulling하고, 있으면 해당 이미지로 컨테이너를 만든다.

-  만든 컨테이너를 구동한다.

-  docker run 옵션에 따라 컨테이너 실행을 포어그라운드나 백그라운드로 진행한다.

 

docker run에 익숙해지면, docker create과 docker start의 사용 빈도는 확실히 줄어든다. 그럼에도 불구하고 필자가 docker run에 대해 늦게 포스팅한 것은, docker create와 docker start가 어떻게 동작하는지 알아야 docker run 명령어가 실행되는 방식을 이해할 수 있기 때문이다. 

 

 

 

2. docker run(create) 옵션

바로 직전의 포스팅에서, 필자가 docker create와 docker run의 옵션은 동일하다고 언급했다. 

 

글자가 작아서 잘 안보일 것인데, 대략적인 모양을 보면 두 출력 결과에 차이점이 보이지 않는다.

 

즉, docker run의 옵션은 전적으로 컨테이너 생성과 관련이 있다는 이야기다. 그 옵션이 너무 많은데다, 어떤 옵션들은 Docker에 대한 심층적인 이해가 필요한 내용들도 있으니, 이 포스팅에서는 "아~ 이런 옵션들도 있구나" 하고 간략히 짚고 넘어가면 된다.

 

(1) --name(-n) {컨테이너이름} :  컨테이너 생성 이름 지정

포스팅에서 필자가 거의 빠짐없이 입력한 옵션이다. 이미지로부터 컨테이너 생성 시, 특정 이름을 컨테이너에 부여하는 옵션이다. 이 옵션을 제외하고 컨테이너를 생성하면 Docker에 의해 임의의 이름이 컨테이너에 부여된다. 사람이 아무리 천재적이라도 임의의 이름, 그리고 12자리의 컨테이너 ID를 외워가면서 관리하기는 여간 쉽지 않을 것이다. 따라서 이 옵션은 생략해도 무방하지만 99.9% 빠짐없이 사용되는 옵션이라 생각하면 된다.

 

(2) --hostname {컨테이너 OS 호스트이름}: 컨테이너 OS hostname 지정

 

필자가 이전의 포스팅에서 잠깐 언급했는데, 컨테이너는 생성되면 OS의 hostname으로 컨테이너 ID가 부여된다. 

 

ubuntu_test2의 컨테이너 ID와 hostname 값이 동일함을 알 수 있다.

마찬가지로, hostname 역시 컨테이너 ID가 부여되면 여간 관리가 어려운 것이 아니다. 왜냐하면 하나의 Docker 서버에서 기본 네트워크 설정으로 생성한 컨테이너는 모두 같은 네트워크 대역대에 위치하고 있는데, 컨테이너 수가 많아지면 컨테이너마다 부여된 IP를 일일이 관리하기 어렵기 때문이다. 지금 필자는 ubuntu 컨테이너 2개가 있는데, 만약 이 두 컨테이너 정보를 /etc/hosts 같은 곳에 작성하려 한다면...

 

서버 대수가 100대 가까이 된다고 가정해보자... 어딘가에는 잘못 입력하게 되지 않을까?

 

따라서 container 생성 시, hostname도 별도로 지정할 수 있는 옵션이 존재한다. --name과 비슷한 모양을 가지는 --hostname 옵션이다. ubuntu_test3으로 컨테이너 생성 및 구동을 통해 hostname을 알아보자.

 

 

(3) --rm:  컨테이너 구동 종료 시 컨테이너 자동 삭제 옵션

 

테스트를 위해 잠깐 컨테이너를 구동해야 하는 경우가 있는데, 컨테이너를 종료한 뒤 손 아프게 일일이 명령어를 치기 귀찮을 때 사용하기 좋은 옵션이 있다. 바로 --rm 이다. 이 옵션은 컨테이너가 종료되는 순간 알아서 해당 컨테이너를 삭제까지 진행해준다. 

 

필자는 ubuntu_test4를 docker run으로 컨테이너 실행하되, 옵션 --rm을 넣으려 한다.

 

 

필자가 docker run 명령어 입력 시, 옵션으로 -it를 명시하지 않았기 때문에 해당 컨테이너는 Exit 코드 0번으로 종료되었을 것이다. 보통같으면 docker ps -a에 종료된 컨테이너로 등장을 해야하는데, --rm 옵션으로 인해 컨테이너가 종료되면서 자동 삭제되어 docker ps -a 결과에 출력되지 않음을 확인할 수 있다.

 

(4) --publish {HostOS 포트: 컨테이너 포트}: 포트 접속이 필요한 컨테이너의 포트 맵핑 옵션

 

지금까지 사용한 ubuntu 컨테이너 말고, 실제 서비스를 제공하는 컨테이너로 넘어와보자. 인터넷 사용자에게 홈페이지를 제공하는 웹 서비스는 tcp/443 포트(https)로 통신을 진행한다. 그리고 이러한 웹 페이지를 제공하는 서비스들 - apache(리눅스에서 웹 서비스를 구동하는 가장 기본적인 방법은 이 포스팅을 참고하자), django, nginx 등 - 은 별도의 설정이 되어 있지 않다면 기본 tcp/80, http로 통신을 진행한다. 

 

자, nginx와 같은 웹 서비스가 80번 포트로만 통신을 하는데, 필자의 HostOS인 CentOS 리눅스는 방화벽에 80번 포트가 개방되지 않았다. 그리고, 설령 개방한다 하더라도 HostOS에서 제공하는 웹 서비스가 없기 때문에 컨테이너 IP의 80번 포트로 접속할 수 있도록 맵핑 설정이 진행되어야 한다. 그림으로 나타내면 이렇다.

 

 

 

이 때문에, 포트로 통신을 진행해야하는 컨테이너들은, HostOS의 포트와 컨테이너OS 포트를 맵핑 시켜야만 외부에서 container의 서비스를 이용하는 것이 가능해진다. 이렇게 Host와 컨테이너의 포트를 맵핑하는 옵션은 --publish 라는 이름으로 사용되며, 사용 방법은 아래와 같다.

 

#  --publish=HostPort:ContainerPort

 

보통 컨테이너 서비스의 Port는 고정값으로 부여된다(이는 나중에 알아보자). 우선 지금은 nginx 컨테이너가 80번 포트로 서비스를 제공한다는 것만 인지하고 다음과 같이 컨테이너를 구동해보려 한다.

 

#  docker run --name=web_test2 --publish=10001:80 nginx

 

프롬프트가 떨어지지 않을텐데, 괜찮다.

 

이 상태에서 웹 브라우저를 켜고, 리눅스 IP:10001을 입력해보자. 필자와 같이 nginx 서비스가 나타나면 된다.

 

 

분명 HostOS는 10001번 포트를 별도로 개방하지 않았음에도 컨테이너 웹 접속이 이루어짐을 확인할 수 있다. 이는 ps -ef | grep 80으로 결과를 확인하면 등장하는 docker-proxy라는 녀석 때문이다. 이 녀석에 대해 지금 이야기를 꺼내면 필자가 밤을 새워야 할 수 있으니, 이 이야기는 추후 진행하는 것으로 하고...(Docker 네트워크에 대한 이야기를 꺼내야 하는데 그림 그릴게 많다)

 

 

"--publish 옵션을 통해 컨테이너의 서비스 포트를 외부로 노출 시키는 방법이 존재한다" 정도로만 우선 알고 있으면 될 듯 하다.

 

(5) 컨테이너의 백그라운드 구동

 

리눅스에는 프로세스를 백그라운드로 구동할 수 있는 명령어가 존재한다. 바로 & 기호다. 필자가 echo "HelloWorld"를 입력하면 화면에 HelloWorld가 출력되지만, 이를 백그라운드로 실행하면 내용 출력없이 해당 작업(프로세스)이 마무리된다.

 

해당 명령어가 프로세스 3355번으로 동작되었던 것을 확인할 수 있다.

 

서비스의 경우도 백그라운드 구동을 많이 사용한다. 리눅스에서 흔히 사용하는 systemctl start 명령어의 경우도, 각 서비스들이 백그라운드로 구동되는 것이다. 만약 포어그라운드, 일반적인 실행 상황이라면 서비스가 진행되는 동안 해당 세션에서는 어떠한 작업도 불가능할 것이다. 

 

멀리가지 않고, 당장 위에서 진행한 nginx만 봐도 그렇다. 컨테이너를 실행했는데, 백그라운드로 실행하지 않았기 때문에 서비스가 종료되지 않아 프롬프트가 떨어지지 않고 계속 로그를 찍는 것이다. 만약 이 putty 창이 닫힌다면, 현재 서비스 중인 컨테이너 웹도 접속이 불가능해지게 된다.

 

 

그렇기 때문에 지속적으로 서비스를 제공하는 컨테이너들은, 구동 시 백그라운드 구동이 요구된다. 이를 위해 docker create(run) 명령어는 옵션 -d를 제공한다. 지금 실행중인 nginx를 Ctrl + C로 중지하고 웹 접속을 시도한 뒤, 접속이 안되는 것을 확인했다면 아래의 명령어로 새 컨테이너를 만들어 웹 서비스를 다시 구동해보자.

 

#  docker run -d --name=web_test3 --publish=10002:80 nginx

 

 

 

이 외에도 컨테이너에서 생성한 데이터를 HostOS의 폴더와 공유하는 --volume 옵션, 컨테이너의 메모리, CPU 등 자원 사용량을 제한하는 옵션 등등등, 재미있는 이야기가 많지만, 이 내용들은 추후 작성하는 포스팅에서 하나씩 소개하려 한다. 

 

 

 

3. docker tag {기존이미지이름} {변경이미지이름}

만약 필자가, 필자가 만든 이미지를 공식 docker registry에 올린다고 가정해보자. Docker Registry는 개인 계정 생성 시, 개인이 만든 이미지를 저장할 수 있는 별도의 공간을 마련해준다. 문제는 필자가 ubuntu:latest 이미지에 작업을 마치고 이미지 이름 변경없이 docker pull을 진행하면 이미지가 올라가지 않는다. 왜냐하면 필자가 작업한 이미지의 이름이 ubuntu:latest인데, 이는 Docker Hub의 Ubuntu 공식 이미지와 동일한 이름이기 때문이다. 필자에게 이 이미지에 대한 수정 권한이 있다면 올리는데 문제가 없겠지만, 그렇지 않기 때문에 필자만의 이미지 이름으로 지정해야 한다.

 

이미지 이름을 변경해서 Docker Hub에 올리는 경우, 변경하는 이미지의 이름은 아래와 같이 작성한다.

 

#  {Docker Hub 계정명}/{기존(변경)이미지명}:{태그}

 

당연히 필자 계정의 저장소에 등록하기 위해서는 반드시 {Docker Hub 계정명}에 필자의 계정 정보가 입력이 되어야 한다. 필자는 우선 확인을 위해 공식 ubuntu 이미지의 이름을 아래와 같이 변경하려한다.

 

#  docker tag ubuntu:latest {docker계정명}/ubuntu:1.1

 

 

 

 

4. docker login, docker push {이미지}: 제작한 docker 이미지를 개인 저장소에 업로드

이제 이름을 변경한 이미지를 필자의 Docker 계정 저장소에 올려보자. 이를 위해 먼저 필자의 Docker Hub 계정에 로그인이 선행되어야 한다. 로그인 명령어는 매우 간단하다. 단순히 docker login 이다.

#  docker login

 

명령어 입력 후, Docker Hub 계정 ID와 비밀번호를 알맞게 입력했다면 마지막에 Login Succeeded 문구가 출력된다. 이 상태에서 docker info | grep Username 명령어를 입력하면, 현재 Docker Hub에 접속한 계정 ID가 출력되는 것을 확인할 수 있다.

 

 

이제, 필자가 만든 이름을 변경한 이미지를 업로드해보자. 업로드 명령어는 docker push다. 인자로는 필자가 이름을 변경한 이미지를 입력해주면 된다.

 

# docker push {Docker Hub 계정명}/{변경이미지이름}

 

 

필자의 Docker Hub 계정으로 접속해서 Repository 부분을 확인하면 Ubuntu:1.1이 저장된 것을 확인할 수 있다.

 

 


 

지금까지 3개의 포스팅을 통해, Docker 시스템의 가장 기본적인 명령어에 대해 알아보았다. 다음 포스팅은... Docker의 네트워크와 통신에 대해 하나씩 포스팅해보려 한다.

 

 

 

 

Fin.

반응형

댓글