Build an image and run it as one container
· Version : Docker
이 문서는 Docker 공식 문서를 번역한 내용이며 필자의 생각과 의견이 반영되어 있습니다. 자세한 내용은 원문을 참고 바랍니다.
Introduction
Docker 방식으로 앱을 제작할 차례이다. 이 응용 프로그램은 계층 구조 맨 아래부터 시작한다. 이번 포스트에서 다루는 응용 프로그램 내용은 컨테이너이다. 이 레벨 위에는 컨테이너가 프로덕션 환경에서 작동하는 방식을 정의하는 서비스가 있다. 마지막 최상위 단계는 스택이며 다루는 모든 서비스의 상호 작용을 정의한다.
Stack |
Service |
Container (이번 포스트는 여기) |
Your new development environment
과거에는 Python 응용프로그램을 작성하기 위해 가장 먼저 하는 첫 번째 순서는 Python 런타임을 시스템에 설치하는 것이었다. 그리고 이러한 프로그램이 정상적으로 작동하려면 개발 컴퓨터 환경과 프로덕션 환경이 일치해야 하는 상황이 필요하다.
Docker를 사용하면 이식 가능한 Python 런타임을 이미지로 가져올 수 있으며 설치가 필요 없다. 그런 다음 빌드는 앱 코드와 함께 기본 Python 이미지를 포함 할 수 있으므로 앱 종속성 및 런타임이 함께 이동한다. 이 휴대용 이미지는 Dockerfile이라고 하는 무언가에 의해 정의된다.
Define a container with Dockerfile
Dockerfile은 컨테이너 내부의 환경을 정의한다. 네트워킹 인터페이스 및 디스크 드라이브와 같은 리소스에 대한 액세스는 시스템의 나머지 부분과 격리 되어있는 환경에서 가상화 되므로 포트를 외부에 매핑하고 사용자가 설정한 파일을 “복사”하여 환경을 구성한다. 이 작업을 수행한 후에는 이 Dockerfile 정의된 앱의 빌드가 어디서나 똑같이 작동한다고 기대할 수 있다.
Dockerfile
빈 디렉터리를 만든다. 디렉터리 경로를 새로운 디렉터리로 변경( cd) 하고 Dockerfile이라는 파일을 작성한 후 다음 내용을 해당 파일에 복사하여 붙여 넣고 저장한다. 아래 코드는 Dockerfile의 각 문장을 설명하는 주석이 함께 기록되어 있다.
# Use an official Python runtime as a parent image FROM python:2.7-slim
# Set the working directory to /app WORKDIR /app
# Copy the current directory contents into the container at /app COPY . /app
# Install any needed packages specified in requirements.txt RUN pip install --trusted-host pypi.python.org -r requirements.txt
# Make port 80 available to the world outside this container EXPOSE 80
# Define environment variable ENV NAME World
# Run app.py when the container launches CMD ["python", "app.py"] |
이 Dockerfile은 아직 작성하지 않은 몇 가지 파일, 즉 app.py및 requirements.txt를 포함한다. 다음 단계에서 해당 파일을 만들어 본다.
The app itself
Dockerfile과 같은 폴더에 requirements.txt와 app.py 파일을 두개 더 만든다. 위의 Dockerfile 이미지에 내장되면 app.py의 copy 명령으로 인해 app.py 및 requirements.txt가 존재하고 EXPOSE명령으로 HTTP를 통해 app.py의 출력에 액세스 할 수 있다.
[requirements.txt]
Flask Redis |
[app.py]
from flask import Flask from redis import Redis, RedisError import os import socket
# Connect to Redis redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/") def hello(): try: visits = redis.incr("counter") except RedisError: visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \ "<b>Hostname:</b> {hostname}<br/>" \ "<b>Visits:</b> {visits}" return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__": app.run(host='0.0.0.0', port=80) |
이제 pip install -r requirements.txt가 Python용 Flask및 Redis 라이브러리를 설치하고 app이 socket.gethostname()에 대한 호출 결과와 함께 환경변수 NAME 인쇄하는지 확인한다. 마지막으로 Redis가 실행되지 않기 때문에 (Redis 자체가 아닌 Python 라이브러리만 설치했기 때문에) 여기에서 사용하려는 시도가 실패하고 오류 메시지가 나타난다.
참고 : 컨테이너 내부에서 호스트 이름에 액세스하면 실행중인 실행 파일의 프로세스ID와 같은 컨테이너ID가 검색된다. |
Build the app
우리는 앱을 만들 준비가 되었다. 새 디렉터리의 최상위 레벨에 있는지 확인한다. ls명령어를 실행하였을 때 여기에 아래 그림과 같이 표시되어야 한다.
ls |
이제 빌드 명령을 실행한다. 이렇게 하면 Docker 이미지가 생성되며 이 이미지는 -t 태그를 사용하여 사용자에게 친숙한 이름을 부여할 수 있다.
docker build . -t friendlyhello |
내장된 이미지는 어디에 있을까? 컴퓨터의 로컬Docker이미지 레지스트리에 있다.
Docker images |
Run the app
응용프로그램을 실행하여 -p 사용하여 컴퓨터 포트 4000을 컨테이너의 게시 된 포트 80에 매핑한다.
docker run -p 4000:80 friendlyhello |
Python이 http://0.0.0.0:80에서 앱을 제공한다는 메시지가 표시되어야 한다. 하지만 그 메시지는 컨테이너 내부에서 들어 오는데 컨테이너의 포트 80을 4000으로 매핑했는지 모르므로 실제 테스트에서는 올바른 URL http://localhost:4000 을 사용 한다. 웹 브라우저에서 해당URL로 이동하여 웹 페이지에 표시된 디스플레이 내용을 확인한다.
참고 : Windows7에서 Docker Toolbox를 사용하는 경우 localhost 대신 Docker Machine IP를 사용한다. Ex ) http://192.168.99.100:4000/ IP 주소를 찾으려면 docker-mchine ip 명령을 사용한다. |
쉘에서도 curl 명령을 사용하여 동일한 내용을 볼 수 있다.
$ curl http://localhost:4000
<h3>Hello World!</h3><b>Hostname:</b> 8fc990912a14<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i> |
4000:80의 포트 재매핑은 Dockerfile내의 EXPOSE와 docker run -p 때 publish 값이 설정된 값의 차이를 보여준다. 나중 단계에서 호스트의 포트 4000을 컨테이너 포트 80에 매핑하고 http://localhost 사용. CTRL+C를 눌러 종료한다.
Windows에서는 명시적으로 컨테이너를 중지한다. Windows 시스템에서 CTRL+C는 컨테이너를 중지하지 않는다. 따라서 CTRL+C를 입력하여 프롬프트를 다시 얻거나 다른 쉘을 연 다음 실행중인 컨테이너 목록을 표시하려면 docker container ls를 입력하고 컨테이너를 중지하려면 docker container stop <Container NAME or ID>를 입력한다. 그렇지 않으면 다음 단계에서 컨테이너를 다시 실행하려고 시도할 때 데몬에서 오류 응답을 받는다. |
이제 백그라운드에서 분리 모드로 앱을 실행해 본다,
docker run -d -p 4000:80 friendlyhello |
터미널에서는 앱에 대한 긴 컨테이너 ID를 나타낸다. 컨테이너가 백그라운드에서 실행 중이다. docker container ls 명령을 사용하여 축약된 컨테이너 ID를 볼 수도 있다. (둘 다 명령을 실행 할 때 상호 교환 적으로 작동한다.)
docker container ls |
Container ID는 http://localhost:4000에 있는것과 일치한다. 다음과 같이 Container ID 사용하여 프로세스를 종료하려면 docker container stop을 사용한다.
docker container stop 58427d7fd3dc |
Share your image
방금 만든 이미지의 이식성을 보여주기 위해 빌드 된 이미지를 업로드하고 다른 곳으로 옴긴다. 결국 프로덕션에 컨테이너를 배포하려는 경우 레지스트리로 푸시하는 방법을 알아야 한다. 레지스트리는 저장소 모음이며 저장소는 이미 만들어진 코드를 제외하고는 Github 저장소와 같은 종류의 이미지 모음이다. 레지스트리의 계정은 많은 리포지토리를 생성할 수 있다. docker CLI는 기본적으로 Docker 공개 레지스트리를 사용한다.
참고 : Docker의 공용 레지스트리는 무료이며 미리 구성되어 있기 때문에 여기에서 사용한다. 그러나 선택할 수 있는 공용 레지스트리가 많으며 Docker Trusted Registry를 사용하여 개인 레지스트리를 설정할 수도 있다. |
Log in with your Docker ID
Docker 계정이 없다면 hub.docker.com에 가입한다. 로컬 시스템의 Docker 공용 레지스트리에 로그인 한다.
docker login |
Tag the image
로컬 이미지를 레지스트리의 저장소와 연관시키는 표기법은 username/repository:tag이다. 이 태그는 선택사항이지만 Docker 이미지에 버전을 제공하는데 사용하는 메커니즘이기 때문에 권장된다. 저장소에 제공하고 get-started:part2와 같이 컨텍스트에 의미있는 이름을 태그한다. 그러면 이미지가 get-started에 저장되고 part2로 태그가 지정된다. 이제 이미지를 모두 태그를 추가한다. Docker tag image가 원하는 대상에 업로드되도록 사용자 이름, 저장소 및 태그 이름으로 docker tag image를 실행한다. 명령 구문은 다음과 같다.
Syntax : docker tag image username/repository:tag Ex : docker tag friendlyhello gordon/get-started:part2 |
docker images를 실행하면 새로 태그가 추가된 이미지를 볼 수 있다.
docker images |
Publish the image
태그가 지정된 이미지를 저장소에 업로드 한다. 완료되면 이 업로드 결과가 공개된다. Docker Hub에 로그인하면 끌어오기 명령과 함께 새 이미지가 표시된다.
docker push username/repository:tag |
Pull and run the image from the remote repository
이제부터는 다음 명령어로 모든 컴퓨터에서 docker run하고 앱을 실행할 수 있다.
docker run -p 4000:80 username/repository:tag |
이미지가 로컬 컴퓨터에서 사용할 수 없는 경우 Docker는 저장소에서 이미지를 가져온다.
Docker run 되는 위치에 관계 없이 Python 및 requirements.txt 모든 종속성과 함께 이미지를 가져와서 코드를 실행한다. 이 모든 것이 깔끔한 작은 패키지로 함께 진행되므로 Docker가 호스트 시스템을 실행하기 위해 호스트 시스템에 아무것도 설치할 필요가 없다.
Recap and cheat sheet [optional]
이번 포스트에서 다룬 내용은 아래와 같다.
동영상 : https://asciinema.org/a/blkah0l4ds33tbe06y4vkme6g
아래는 이번 포스트에 사용된 Docker의 명령어 목록이다.
docker build -t friendlyhello . # Create image using this directory's Dockerfile docker run -p 4000:80 friendlyhello # Run "friendlyname" mapping port 4000 to 80 docker run -d -p 4000:80 friendlyhello # Same thing, but in detached mode docker container ls # List all running containers docker container ls -a # List all containers, even those not running docker container stop <hash> # Gracefully stop the specified container docker container kill <hash> # Force shutdown of the specified container docker container rm <hash> # Remove specified container from this machine docker container rm $(docker container ls -a -q) # Remove all containers docker image ls -a # List all images on this machine docker image rm <image id> # Remove specified image from this machine docker image rm $(docker image ls -a -q) # Remove all images from this machine docker login # Log in this CLI session using your Docker credentials docker tag <image> username/repository:tag # Tag <image> for upload to registry docker push username/repository:tag # Upload tagged image to registry docker run username/repository:tag # Run image from a registry |
[참고자료]
https://docs.docker.com/get-started/part2
2018-12-20 / Sungwook Kang / http://sqlmvp.kr
'SW Engineering > DevOps, SRE' 카테고리의 다른 글
Docker Stack (0) | 2019.03.26 |
---|---|
Docker Swarms (0) | 2019.03.26 |
Docker Services (0) | 2019.03.26 |
Docker Orientation and Setup (0) | 2019.03.26 |
Docker란 무엇인가? (0) | 2019.03.26 |