Note 331 Docker
도커(Docker, 개발환경 중 하나)는 애플리케이션 실행 환경을 코드로 작성할 수 있고, OS를 격리화하여 관리하는 기술이다. 리눅스에서 돌아가는 프로그램을 사용자의 PC에서도 쉽고(docker file을 통해) 빠르게(Container를 통해) 동작할 수 있도록 툴을 제공한다.
이번 포스팅에서는 도커를 이해하기 위한 자세한 개념들과 실행방법에 대해 다뤄보자.
도커를 사용해야 하는 이유
1) 환경이 일정하지 않아서 생기는 문제
현재 사용되는 운영체제는 Linux, Windows, Mac OS 등이 있는데 운영체제마다 환경과 명령어들이 조금씩 다르다. 이렇게 되면 여러 사람들이 협업 할 때 각각 다른 명령어로 작업을 수행하기때문에 공유와 개발 후 배포가 힘들다.
2) 수작업으로 일치시키는 환경 구성
위의 문제를 해결하기 위해 OS환경을 일치시킬 수 있다. 하지만, OS 환경을 수작업으로 일치시키기에는 방화벽, 권한, Port 설정 등 필요한 설정이 개인마다 다르기 때문에 이 역시 실수가 발생하거나 통일이 되지 않을 수 있다. 시간도 오래 걸리게 될 것이다.
3) 리소스 격리성
IP는 다양한 컴퓨터들이 서로 통신할 때 쓰는 약속된 데이터 이동간의 규약이고, IP 주소는 이 통신에 필요한 컴퓨터의 고유주소이다. 이 IP주소는 숫자로 되어있어서 사용하기가 어렵기 때문에 DNS(Domain Name Service)를 사용해서 주소를 우리가 흔히 알고 있는 www.gmail.com 과 같은 이름으로 바꿔준다. 아무튼, 이 IP주소는 컴퓨터의 고유주소라고 했는데 이 IP 주소가 Port Number라는 것과 함께 사용되어 정확히 어디에 어떤 정보를 주고 받을 것인지 알 수 있게 한다. 쉽게 말하면 IP주소는 우리집의 주소이고, Port Number는 나의 방 주소라고 생각하면 된다.
위의 개념을 활용하여 만약 서버가 하나 밖에 없는데 클라이언트가 각각 다른 설정을 원한다면(방화벽 규칙, 포트 번호 등) 이 하나의 서버 내에서 설정의 충돌이 일어난다. 방화벽 A 규칙을 쓰라는건지, 방화벽 B 규칙을 쓰라는 건지?
이러한 문제를 해결하기 위해서는 하나의 컴퓨터를 여러 컴퓨터를 이용하는 것처럼 하면 될 것이다. 그렇다면 각각 다른 컴퓨터로 취급해서 여러 설정들을 독립적으로 적용할 수 있기 때문에 충돌을 방지 할 수 있다.
위 3가지의 문제를 해결하기 위해 가상머신, 도커를 사용할 수 있는데 도커는 가상머신처럼 OS 위에 다른 OS를 설치하는 것이 아니기 때문에 완전한 격리성을 제공하진 않지만, 속도, 메모리 효율성 등의 면에서 더 좋은 성능을 낼 수 있다.
4) 클라우드 플랫폼과의 연동
Aws와 같은 클라우드 플랫폼에서 특정 프로젝트를 배포할 때 Docker를 사용하면 사용자들의 OS 환경에 개의치 않고 배포할 수 있기 때문에 좋다. Docker file만 배포하면, 이 파일을 통해 Docker에서 OS에 상관없이 구현된 프로젝트를 작동시킬 수 있다.
이러한 이유들로 도커는 운영체제부터, 파일, 프로그램과 같은 환경을 통일시키기 위해 사용한다. OS마다 명령어가 다르기 때문에 도커를 사용하면서 명령어도 통일시켜 줄 수 있다.
Linux Container
Linux Container는 리눅스 기반 기술 중에 하나로 필요한 라이브러리와 어플리케이션을 모아서 마치 별도의 서버처럼 구성한 것을 말한다. 컨테이너를 이루는 네트워크 설정, 환경 변수 등의 리소스는 각 컨테이너가 독립적을 소유하고 있다.
Linux Container의 특징은
1) 프로세스의 구획화
- 특정 컨테이너에서 작동하는 프로세스는 기본적으로 그 컨테이너 안에서만 액세스 할 수 있다.
- 컨테이너 안에서 실행되는 프로세스는 다른 컨테이너의 프로세스에게 영향을 줄 수 없다.
2) 네트워크의 구획화
- 기본으로 컨테이너 하나에 IP주소가 할당되어 있다.
3) 파일 시스템의 구획화
- 해당 컨테이너에서의 명령이나 파일 등의 액세스를 제한 할 수 있다. 컨테이너 안에 메모리가 따로 생겨서 이 메모리의 액세스를 제한하면 다른 컨테이너에서는 현재 컨테이너의 파일을 사용할 수 없게 한다. 파일들을 카피해서 사용할 수도 있지만 디폴트는 제한되어 있다.
도커는 초기에 이 Linux Conainer를 사용해서 사용자들에게 통일된 환경에서 작업할 수 있도록 툴을 제공했지만, 현재는 독자적인 Container를 만들어서 프로그램을 배포중이다. 도커의 가장 큰 특징은 어플리케이션에 대한 환경을 격리성을 중심으로 하는 VM(가상머신)의 관점보다는, Container의 관점에서 빠르고, 개발자와 사용자 커뮤니티를 중심으로 혜택을 제공한다는 것이다.
Docker 사용방법
Docker는 크게 Image, Registry, Repository로 구성되어 있다. Docker image는 레지스트리에 저장되어 있는 파일이나 프로그램이다.
Repository는 Registry내의 도커 이미지가 저장되는 공간이다. 이미지 이름이 사용되기도 하고, Github 레포지토리와 유사한 개념으로 생각하면 쉽다.
Registry는 도커 이미지가 관리되는 공간으로 특별히 다른 것을 지정하지 않으면 도커 허브라는 원격 저장소를 기본 레지스트리로 설정한다. 도커 허브 외에도 회사 내부용 레지스트리나 Private Docker Hub등을 사용할 수 있다.
태그는 이미지를 설명하는 버전 정보를 주로 입력한다. 특별히 다른 것을 지정하지 않으면 latest 태그를 자동으로 붙여서 이미지를 가져온다.
쉽게 말하면 Registry는 Github, Repository는 Github Repository, Image는 Registry 내부에 있는 파일이나 프로그램이다.
Docker image의 이름은 레지스트리 계정, 레포지토리 이름, 태그 이 세 가지 정보로 구성되어 있다.
Registry_Account/Repository_name:Tag
Docker 기본명령어
1) image 가져오기
docker image pull docker/whalesay:latest
2) image 리스트 출력
docker image ls
3) 받아온 이미지 실행
container run 명령어가 컨테이너를 실행하는 것이고 –name은 이름을 할당한다. 이름 뒤에 이미지 이름이 들어가고 cowsay라는 명령어를 사용해서 boo라는 글자를 인수로 넘겨 boo가 출력된다.
docker container run --name 컨테이너_이름 docker/whalesay:latest cowsay boo
4) 컨테이너 리스트 출력
-a를 붙이지 않으면 기본적으로 실행되는 컨테이너만 보여주고, -a를 붙임으로써 실행이 종료된 컨테이너를 포함한 모든 컨테이너를 출력한다.
docker container ps -a
5) 컨테이너 삭제
docker container rm 컨테이너_이름
6) 이미지 삭제
docker image rm docker/whalesay
컨테이너가 저장되면 메모리를 잡아먹기 때문에 실행이 종료되면 자동으로 컨테이너가 삭제 되도록 위의 명령어들을 조합해서 사용할 수도 있다.
-it는 -i와 -t를 동시에 사용한 것으로 사용자와 컨테이너 간에 인터렉션이 필요할 때 사용된다. 예를 들어, 파이썬이나 다른 추가적은 명령이 필요할 때 -it를 사용하면 이 명령어들을 컨테이너와 연결시켜줄 수 있다.
docker container run -it --rm danielkraic/asciiquarium:latest
7) 이미지 파일의 환경변수 출력(끝에 env 커맨드)
docker run danielkraic/asciiquarium:latest env
Docker 컨테이너에 파일 복사(CP, Mount)
내가 원하는 이미지가 로컬, 웹 서버와 같은 레포지토리에 구성되어 있지 않다면, 로컬에 있는 이미지를 활용해야 할 것이다. 이렇게 서버는 도커 컨테이너를 사용하지만, 파일을 사용자가 만든 파일이나 다른 곳에서 가져온 파일을 쓴다면 서버에 문제가 생기는 것을 호스트와 별개로 파악할 수 있고, 문제가 생긴 서버를 끄고 도커이미지로 서버를 초기화 하는 것처럼 서버를 재구동 할 수 있다.
로컬과 컨테이너를 연결하기 위해서는 Copy,와 Mount라는 개념을 알아야 하는데 먼저 Copy(CP)는 호스트와 컨테이너 사이에 파일을 복사하는 것을 의미하고, Mount는 저장 공간을 다른 장치에서 접근할 수 있도록 경로를 허용해서 마치 하나의 저장 공간을 이용하는 것처럼 보이게 하는 작업을 말한다.
- httpd 웹 서버 이미지를 활용하여 컨테이너 혹은 로컬에 이미지 파일 복사하기
pacman-canvas 파일을 깃허브나 다른 사이트에서 클론했다고 가정하고 진행한다.
1) docker 컨테이너 실행 및 포트 연결
-p는 로컬 포트와 컨테이너 포트를 연결시킨다는 뜻이다. 여기서는 818이라는 로컬 포트를 80이라는 컨테이너 포트와 연결시키는 것. 127.0.0.1:818을 주소창에 치면 현재 컨테이너가 정상적으로 돌아가고, 이미지 파일이 실행되고 있는지 확인 가능하다.
docker container run --name 컨테이너_이름 --rm -p 818:80 httpd
2) 로컬 호스트에 있는 파일을 컨테이너에 전달(경로를 무조건 해당 이미지 파일이 있는 경로로 들어가서 작업해야함)
cp명령어의 앞에 있는 주소가 복사할 경로, 뒤에 있는 주소가 붙여넣기 할 경로
- 현재 로컬 경로에 있는 이미지 파일 전부를 내 컨테이너의 htdocs 경로로 복사.
docker container cp ./ 컨테이너_이름:/usr/local/apache2/htdocs/
- 반대로 내 컨테이너의 파일을 로컬에 복사
docker cp 컨테이너_이름:usr/local/apache2/htdocs/ ./
Docker 이미지 만들기
Docker에서 필요한 이미지들을 미리 만들어 놓으면 이전에 작업했던 내용을 다시 한 번 수행하지 않아도 되고, 배포 및 관리가 유용하다. 즉, 이미지 파일을 다시 첨부터 불러오고 저장하고 하는 작업들이 필요없어지게 된다. 이미지는 기본적으로 두 가지 방법으로 만들 수 있다.
1) 구동한 Container를 이미지로 만드는 방법(docker container commit 명령어 이용)
docker container commit 컨테이너_이름 이미지_이름:버전
위에서 저장해놓은 이미지를 다른 컨테이너에서 사용할 수 있게 된다.
docker run --name 또다른_컨테이너_이름 -p 저장해놓은_이미지이름:버전
2) Image 빌드를 위한 파일인 Dockerfile로 만드는 방법
Dockerfile은 이미지 파일의 설명서다. 이 설명서를 통해 이미지가 어떻게 동작하게 되는지 알 수 있다.
예시)
FROM httpd:2.4 = 베이스 이미지를 httpd:2.4 로 사용한다.
COPY ./ /usr/local/apache2/htdocs/ = 호스트의 현재 경로에 있는 파일을 생성할 이미지 /usr/local/apache2/htdocs/ 에 복사한다.
docker build 명령어를 사용하여 도커 이미지 파일을 생성하고 이 생성된 이미지 파일을 실행할 수 있게 된다.
# --tag는 name:tag 형식으로 이미지를 생성할 수 있다.
# 지정한 경로에 있는 Dockerfile을 찾아서 빌드한다.
docekr build --tag my_pacman:2.0
다른 포트번호를 사용하여 이미지가 정상적으로 build 되고 현재 잘 구동되고 있는지 확인 가능.
docker run --name 또다른_컨테이너 -p 900:80 my_pacman:2.0
Docker 실습
1) 이미지 내의 특정 파일 실행시키기
aicontents/n312_part1:1.0의 이미지 내에서 part1.py의 결과를 보려고 한다. container run함수를 실행해서 컨테이너를 만들고 실행시키고 -e command를 사용해서 환경변수를 설정하는데 github_username이라는 변수에 내 github 닉네임을 저장한다. part1.py 에서는 이미지의 환경변수가 내 github 닉네임과 일치하는지 확인한다. part1.py 파일을 실행시키기 위해 python command를 사용했다.
이미지 내의 파일을 직접 지정함으로써 해당 파일만 실행시켜서 결과를 확인할 수 있다.
docker container run --name part1 -e github_username=Hyeonseung0103 --rm aibcontents/n312_part1:1.0 python part1.py
2) httpd 이미지를 활용하여 서버에 게임을 출력(포트번호는 로컬은 818, 컨테이너는 80)
먼저 httpd 이미지를 활용하여 서버를 연결 시킨다.
docker container run --name part1_2 --rm -p 818:80 httpd
그 다음 usr/local/apache2/htdocs/에 Github에서 클론한 게임에 대한 소스코드를 복사한다. (복사 하기전 클론한 branch로 이동해야 한다.)
docker container cp ./ part1_2:usr/local/apache2/htdocs
마지막으로 내 로컬 서버에서(127.0.0.1:818) 에서 결과를 확인한다.
3) 웹 서버에 있는 파일 가져오기(컨테이너에서 호스트로 파일 가져오기)
먼저 docker로 container를 실행한다.
docker container run --name part1_3 aibcontents/n312_part2:1.0
그 다음 cp 함수를 이용해서 container의 원하는 파일을 호스트로 가져온다.
docker cp part1_3:/usr/local/apache2/queen_track ./
Docker compose
우리가 사용하는 시스템은 단일 어플리케이션으로 구동이 되지않고, 여러 개의 어플리케이션이 서로 의존성 있게 구성되어 작동한다. 그렇다면 여러 개의 컨테이너가 서로 연결되어 사용되어야 하는데 이때 필요한 기술이 Docker Compose이다.
Docker Compose를 사용하지않으면 하나의 시스템을 사용하기 위해 여러 컨테이너를 컨테이너 갯수만큼 따로 실행시켜 연동해야 한다. 사용되는 컨테이너가 많을수록 복잡하고, 연동시키기가 번거로워질텐데 docker compose는 yml 파일 하나로 여러 컨테이너를 실행 및 연동시킬 수 있게 한다.
만약 특정서버에 DB에 있는 정보를 가져와서 띄우려고 한다면, 두 서버를 띄어서 따로 작업을 시키고 연동시켜주어야한다. 이를 yml파일에 하나로 작성함으로써 한번에 연동되도록 해보자.
# docker-compose 버전
version: '3.7'
# 사용할 container
services: # services 문법이기 때문에 변경하면 안 됨.
express: # 변경 가능합니다.
# 웹 서버
container_name: part2 #서버의 컨테이너 이름 지정.
image: jmuppala/node-server #서버의 이미지
ports:
- 3000:3000 #포트번호. 로컬과 컨테이너의 포트 번호를 3000으로 맞춰줌.
command : ["./wait-for-it.sh", "mongodb:27017","--","npm","start"] # 사용할 command
mongodb:
container_name: mongo # DB 컨테이너 이름
image: jmuppala/mongo-server # DB 이미지
위의 yml 파일로 인해 node-server와 mongoDB의 서버가 한 파일로 연결되어 원하는 정보를 서버에서 확인할 수 있다.(127.0.0.1:3000)
docker-compose.yaml 파일에 정의된 서비스를 컨테이너로 실행할 때
docker-compose up
yaml 파일에 정의된 서비스의 컨테이너를 종료할 때
docker-compose down
yaml 파일에 정의된 특정 서비스만 컨테이너로 실행할 때
docker-compose up {특정 서비스}