Part 2 : Containers

사전준비

docker run hello-world

소개

Docker 방법으로 어플리케이션을 만들 차례입니다. 이번 장에서는 어플리케이션의 가장 하위 레벨인 Container에 대하여 알아보겠습니다. 그 다음으로 3장에서는 Container가 운영에서 어떻게 되는지 서비스에 대하여 알아볼 것 입니다. 마지막으로 5장에서는 모든 서비스간의 상호작용을 정의한 Stack에 대하여 알아보겠습니다.

  • Stack
  • Services
  • Container (현 위치)

새로운 개발환경

이전에는, 만약 Python 프로그램을 작성한다면 먼제 시스템에 Python을 설치하려고 할 것 입니다. 그렇게 되면 시스템에 Python 프로그램만 돌릴 환경을 만들게 될 것 입니다.

Docker를 사용한다면, Image에 설치가 필요없는 Python 실행환경을 구성하게 될 것 입니다. 그다음 Python Image에 작성한 코드, 필요한 종속성, 실행환경 등을 추가해서 빌드할 수 있습니다.

Dockerfile에 이 모든 것을 정의할 수 있습니다.

Dockerfile로 Container를 정의하기

Dockerfile에는 Container속의 환경정보를 정의한 파일입니다. 네트워크 인터페이스나 디스크 드라이버와 같은 리소스에 접근하는 것이 시스템 독립적으로 가상화 되어있기 때문에 외부 포트에 연결을 하거나, 원하는 파일을 환경 내부로 복사하는 것을 정의해야 합니다. 그러나, 한번 Dockerfile에 정의를 하면 어디서든 동일하게 빌드를 구성할 수 있습니다.

Dockerfile

새로운 디렉터리를 만든 뒤 해당 디렉터리로 이동(cd 명령어)하여 Dockerfile을 생성하겠습니다. 그리고 아래 소스코드를 복사해서 붙여넣기 합니다. 아래 주석이 만들 Dockerfile에 대한 내용 설명입니다.

# Python 공식 이미지를 기반으로 구성하겠습니다.
FROM python:2.7-slim

# 하위로 작업할 경로를 /app으로 지정하겠습니다.
WORKDIR /app

# 현재 디렉터리 내용을 Container의 /app 에 복사합니다.
ADD . /app

# requirements.txt에 정의된 필요한 패키지를 설치합니다.
RUN pip install -r requirements.txt

# Container 외부로 80번 포트를 노출할 수 있도록 표시합니다.
EXPOSE 80

# 환경변수를 정의합니다.
ENV NAME World

# Container가 실행되면 app.py 파일을 실행합니다.
CMD ["python", "app.py"]

Proxy 환경 내에 있은가요?

Proxy 서버가 Container의 어플리케이션에 접근을 차단할 수 있습니다. 만약 Proxy 환경에 있는 경우에는 Dockerfile에 아래의 환경변수를 추가하시면 됩니다.

# Proxy 환경변수 추가. host/port는 사용하는 Proxy 서버의 host와 port를 입력하시면 됩니다.
ENV http_proxy host:/port
ENV https_proxy host:/port

아직은 Dockerfile에 정의한 a pp.pyrequirements.txt 파일을 지금부터 만들어보겠습니다.

어플리케이션

Dockerfile이 있는 같은 위치에 requirements.txt, app.py 2개의 파일을 추가로 만들겠습니다. 아래에 보시겠지만 간단한 어플리케이션입니다. Dockerfile이 이미지로 빌드될 경우 app.pyrequirements.txt 파일이 DockerfileADD 명령으로 추가될 것 입니다. app.py의 결과물은 EXPOSE 명령으로 정의된 포트를 통하여 HTTP 서비스가 될 것 입니다.

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용 FlaskRedis 라이브러리를 설치하고 어플리케이션에서는 socket.gethostname()와 NAME 환경변수를 출력합니다. 최종적으로는 Redis가 동작하고 있지 않으므로(Redis를 설치한 것이 아닌 Redis 라이브러리를 설치한 것 입니다) 지금 실행한다면 오류 메시지를 보시게 될 것 입니다.

Note : 실행중인 프로세스 ID와 같이 Container ID를 Host 이름으로 활용하여 Container에 접근할 수 있습니다.

모두 완성되었습니다. 시스템에 따로 Python을 설치하거나 requirements.txt에 정의된 라이브러리가 필요하지 않습니다. 겉으로는 Python과 Flask 등의 환경을 구성한 것 처럼 보이지는 않지만 Image를 빌드하고 실행하시기만 하면 됩니다.

어플리케이션 빌드

이제 어플리케이션을 빌드할 차례입니다. 지금까지 작업한 디렉터리에서 명령을 수행하도록 하겠습니다. 먼저 ls 명령을 통해 무슨 파일이 있는지 확인하겠습니다.

$ ls
Dockerfile        app.py            requirements.txt

빌드명령을 수행해보겠습니다. -t 옵션으로 지정한 이름으로 Docker Image를 생성하게 됩니다.

docker build -t friendlyhello .

이미지가 어디에 빌드될까요? 빌드를 수행한 시스템의 로컬 Docker Image Registry에 빌드되게 됩니다. 다음 명령으로 확인해보겠습니다.

$ docker images

REPOSITORY            TAG                 IMAGE ID
friendlyhello         latest              326387cea398

TIP : docker images 또는 docker image ls 명령을 통해서 동일한 결과의 이미지목록을 확인할 수 있습니다.

어플리케이션 실행

-p 옵션을 사용해서 컨테이너의 80 포트를 시스템의 4000 포트로 매핑하여 실행하도록 하겠습니다.

docker run -p 4000:80 friendlyhello

우리가 작성한 Python 어플리케이션은 http://0.0.0.0:80으로 구성되어 있습니다. 그렇지만 이것은 Container 내부에 구성된 것 이며 우리가 Container의 80 포트를 시스템의 4000포트로 매핑하였기 때문에 http://localhost:4000으로 접속하여야 합니다.

브라우저를 열어서 해당 주소에 접속을 하면 다음과 같이 "Hello World!"라는 텍스트와 Container ID, Redis 오류 메시지를 확인할 수 있습니다.

또는 curl 명령을 통해서 Shell 환경에서도 확인할 수 있습니다.

$ curl http://localhost:4000

<h3>Hello World!</h3><b>Hostname:</b> 8fc990912a14<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>

NOTE : 4000:80 으로 포트를 매핑하는 것은 Dockerfile내에서 EXPOSE로 정의한 포트와 docker run -p 옵션으로 publish한 포트가 다름을 보여줍니다. 나중 단계에서는 Container의 80포트와 시스템 80포트를 연결하여 http://localhost로 접속하게할 것 입니다.

CTRL + C 를 입력하여 터미널을 종료하겠습니다.

Detached Mode를 활용하여 백그라운드로 어플리케이션을 실행해보겠습니다.

docker run -d -p 4000:80 friendlyhello

그럼 실행한 어플리케이션에 대한 Container ID가 출력되며 다시 터미널로 돌아올 것 입니다. Container가 백그라운드에서 실행중이라는 것 입니다. docker container ls 명령을 통하여 축약된 Container ID와 현재 수행중인 명령어를 볼 수 있습니다.

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED
1fa4ab2cf395        friendlyhello       "python app.py"     28 seconds ago

http://localhost:4000에 표시된 CONTAINER ID를 볼 수 있습니다.

이제, docker container stop 명령에서 CONTAINER ID를 이용하여 프로세스를 종료할 수 있습니다.

docker container stop 1fa4ab2cf395

Image 공유

방금 만든것에 대한 이식성을 확인하기 위해서, 우리가 만든 Image를 업로드한 뒤 다른 곳에서 실행해보겠습니다. 먼저, 운영환경으로 Container를 배포하길 원할 때 Registry에 어떻게 푸쉬하는지 알아야합니다.

Registry는 Repository의 집합이며, Repository는 Image의 집합입니다. Github 저장소와 비슷하지만 Code가 아닌 이미 빌드된 것이 차이점이라고 볼 수 있습니다. Registry 계정은 여러개의 Repository를 만들 수 있습니다. 기본적으로 docker 명령은 공개용 Registry를 사용합니다.

NOTE : 지금부터 우리는 무료이기도 하고 이미 준비가 되어있기 때문에 Docker의 공용 Registry를 사용할 것 입니다. 그 외의 다른 공개된 Registry를 사용해도 되고, 아니면 개인용 사설 Registry를 Docker Trusted Registry 부분을 참고해서 구성할 수 도 있습니다.

Docker 계정으로 로그인

Docker 계정이 없다면 cloud.docker.com에 가셔서 가입을 하시면 됩니다.

그 후 다음 명령으로 Docker 공용 Registry에 로그인할 수 있습니다.

docker login

Image에 Tag추가

Registry에 적용할 Image의 명명규칙은 username/repository:tag와 같습니다. Tag 부분은 선택사항이기는 하나, Registry가 Docker Image에 버전을 부여할 때 사용되기 때문에 권장되고 있습니다. Repository와 Tag에 내용에 맞는 의미있는 명칭(예 : get-started:part2)을 부여하시면 좋습니다. 예시를 기준으로 Image는 get-started라는 Repository에 part2 태그로 구성됩니다.

이제 생성한 Image에 대하여 Tag를 추가해보겠습니다. Image를 업로드할 곳의 username, repository, tag를 이용해서 docker tag image 명령을 실행하겠습니다. 이 명령의 문법은 다음과 같습니다.

docker tag image username/repository:tag

예는 다음과 같습니다.

docker tag friendlyhello john/get-started:part2

docker images 명령(또는 docker image ls)을 실행하면 새로운 태그가 부여된 Image를 볼 수 있습니다.

$ docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
friendlyhello            latest              d9e555c53008        3 minutes ago       195MB
john/get-started         part2               d9e555c53008        3 minutes ago       195MB
python                   2.7-slim            1c7128a655f6        5 days ago          183MB
...

Image 배포하기

Tag를 추가한 이미지를 업로드하겠습니다.

docker push username/repository:tag

업로드가 완료되면, Image가 공개가 된 것 입니다. Docker Hub에 로그인하시면 업로드한 이미지를 보실 수 있으며 Pull로 받으실 수 있습니다.

원격 Repository에서 Image를 Pull하고 실행하기

방금 만든 어플리케이션을 docker run 명령을 통해서 실행해보겠습니다.

docker run -p 4000:80 username/repository:tag

로컬 시스템에 해당 이미지가 없는 경우에는 Docker는 자동으로 Repository에서 Image를 Pull받게 됩니다.

$ docker run -p 4000:80 john/get-started:part2
Unable to find image 'john/get-started:part2' locally
part2: Pulling from john/get-started
10a267c67f42: Already exists
f68a39a6a5e4: Already exists
9beaffc0cf19: Already exists
3c1fe835fb6b: Already exists
4c9f1fa8fcb8: Already exists
ee7d8f576a14: Already exists
fbccdcced46e: Already exists
Digest: sha256:0601c866aab2adcc6498200efd0f754037e909e5fd42069adeff72d1e2439068
Status: Downloaded newer image for john/get-started:part2
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

NOTE : 만약에 Image를 실행하거나 빌드할 때 :tag 부분을 별도로 기입하지 않으면 자동으로 :latest를 사용합니다. 실행할 때도 동일합니다. Docker는 Tag를 지정하지 않을 경우에는 가장 최신 버전의 Image를 사용하게 됩니다.

결론

이번 장이 끝났습니다. 다음 장에서는 Service 환경에서 Container로 실행되는 어플리케이션을 어떻게 확장하는지 알아보겠습니다.

results matching ""

    No results matching ""