flasky

deploy 과정

태스크를 애플리케이션이 설치될 때나 업그레이드될 때 수동으로 실행하면 에러에 취약하고 시간 소모적인 작업이 된다. 따라서 필요한 모든 태스크를 실행하는 커맨드를 manage.py에 추가한다.

ex) 17-1 manage.py

def deploy():
	"""Run deployment tasks."""
	from flask.ext.migrate import upgrade
	from app.models import Role, User
	
	# migrate database to latest revision
	upgrade()
	
	# create user roles
	Role.insert_roles()
	
	# create self-follows for all users
	User.add_self_follows()

이 커맨드에 의해 실행될 함수는 이전에 모두 생성되었으며 일괄적으로 함께 호출된다.

  • 업데이트 함수를 설계함으로써 설치나 업그레이드가 실행될 때마다 deploy 커맨드를 실행하는 것이 가능하다.

제품화 과정 중의 에러에 대한 로그

디버거는 개발 중에 애플리케이션 문제점들을 디버그하기 위한 훌륭한 툴이지만 제품화 배포에서는 사용할 수 없다. 제품화에서 발생한 에러는 숨겨져 있으며 사용자들은 단지 코드 500 에러 페이지만을 받게 된다.

  • 이러한 에러의 스택 트레이스를 완전히 잃어 버리지는 않는다. 플라스크가 그 에러를 로그 파일(log file)에 작성하기 때문이다.

시작하는 동안에 플라스크는 Python의 logging.Logger 클래스의 인스턴스를 생성하고 그것을 애플리케이션 인스턴스의 app.logger로 붙인다.

  • 이 로거는 디버그 모드에서는 콘솔에 작성하지만 제품화 모드에서는 기본적으로 이것을 설정하는 핸들러가 없다. 따라서 핸들러에 로그가 추가되어야 저장이 된다.

ex) 17-2 config.py : 애플리케이션 에러에 대해 이메일 전송

class ProductionConfig(Config):
	# ...
	@classmethod
	def init_app(cls, app):
		Config.init_app(app)
		
		# email errors to the administrators
		import logging
		from logging.handlers import SMTPHandler
		credentials = None
		secure = None
		if getattr(cls, 'MAIL_USERNAME', None) is not None:
			credentials = (cls.MAIL_USERNAME, cls.MAIL_PASSWORD)
			if getattr(cls, 'MAIL_USE_TLS', None):
				secure = ()
	mail_handler = SMTPHandler(
		mailhost=(cls.MAIL_SERVER, cls.MAIL_PORT),
		fromaddr=cls.FLASKY_MAIL_SENDER,
		toaddrs=[cls.FLASKY_ADMIN],
		subject=cls.FLASKY_MAIL_SUBJECT_PREFIX + ' Application Error',
		credentials=credentials,
		secure=secure)
	mail_handler.setLevel(logging.ERROR)
	app.logger.addHandler(mail_handler)
		

이러한 변경 사항은 제품화 모드에서 실행하는 동안 발생하는 에러를 FLASKY_ADMIN에 세팅에 설정되어 있는 관리자 리스트의 이메일로 전송하도록 로깅 핸들러를 설정한다.

모든 설정 인스턴스가 create_app()에 의해 호출되는 init_app() 정적 메소드를 갖고 있다는 것을 기억하자!!!

ProductionConfig 클래스를 위한 이 메소드의 구현에서 애플리케이션 로거는 이메일 로거로 로그 에러를 설정하도록 되어 있다.

  • 이메일 로거의 로그 레벨은 logging.ERROR로 설정된다. 따라서 단지 심각한 문제만 이메일로 전송된다. 덜 심각한 레벨로 로깅된 메세지는 파일, syslog 혹은 적절한 로깅 핸들러를 추가하여 지원하는 다른 메소드를 사용하여 로그된다. 이러한 메시지를 위해 사용되는 로깅 메소드는 호스팅 플랫폼에 상당히 의존적이다.

클라우드 배포

가장 최근의 트렌드는 “클라우드”로 호스팅하는 것이다. 이 기술은 애플리케이션 개발자들을 애플리케이션이 실행하게 될 하드웨어와 소프트웨어 플랫폼에 따라 설치와 유지 보수를 해야 하는 기존의 태스크로부터 해방시켜 준다.

  • 헤로쿠(Heroku)는 가장 유명한 PaaS 프로바이더이며 파이썬을 지원한다.

헤로쿠 플랫폼

헤로쿠는 2007년부터 비즈니스를 시작했다. 헤로쿠 플랫폼은 유연성이 뛰어나며 다양한 프로그래밍 언어를 지원한다.

  • 애플리케이션을 헤로쿠에 배포하기 위해서는 개발자가 Git을 사용하여 애플리케이션을 헤로쿠의 Git 서버에 푸시한다. 서버에서는 git push 커맨드가 자동으로 설치, 설정, 배포를 트리거한다.

애플리케이션의 준비

헤로쿠에서 작업하기 위해서는 애플리케이션이 Git 저장소에 호스트되어야 한다.

헤로쿠 계정의 생성

헤로쿠 툴벨트의 설치

헤로쿠 애플리케이션을 관리하는 가장 간편한 방법은 헤로쿠 툴벨트(Toolbelt) 커맨드 라인 유틸리티를 이용하는 것이다.

  • 툴벨트는 두 개의 헤로쿠 애플리케이션으로 구성되어 있다.

    • heroku : 헤로쿠 클라이언트이며, 애플리케이션을 생성하고 관리하는 데 사용된다.

    • foreman : 컴퓨터에 테스트할 목적으로 헤로쿠 환경을 시뮬레이션하는 툴이다.

헤로쿠 클라이언트 유틸리티는 서비스에 연결하기 전에 헤로쿠 계정 자격증을 갖고 있어야 한다

ex) heroku login command

$ heroku login 
Enter your Heroku credentials.
Email: <your-email-address>
Password (typing will be hidden): <your-password>
Uploading ssh public key .../id_rsa.pub

다음 단계는 헤로쿠 클라이언트를 사용하여 애플리케이션을 생성하는 것이다.

이 작업을 하기 위해, 먼저 애플리케이션이 Git 소스 컨트롤에 있는지 확인한 후에 최상위 디렉토리에서 다음의 커맨드를 실행한다.

$ heroku create <appname>
Creating <appname>...done, stack is cedar
http://<appname>.herokuapp.com/ | git@heroku.com:<appname>.git
Git remote heroku addred

헤로쿠 애플리케이션 이름은 반드시 고유의 이름이어야 하므로 다른 애플리케이션에서 사용하지 않은 이름을 찾아야 한다. create 커맨트의 결과에서 볼 수 있는 것처럼 한 번 배포된 애플리케이션은 http://<appname>.herokuapp.com에서 사용 가능하다.

  • 커스텀 도메인 이름 역시 애플리케이션에 붙일 수 있다.

애플리케이션 생성의 부분으로 헤로쿠는 Git 서버를 할당한다

git@heroku.com:<appname>.git

create 커맨드는 이 서버를 로컬 Git 저장소에 heroku라는 이름을 가진 git remote인 것처럼 추가한다.

데이터베이스의 프로비저닝(provisioning)

헤로쿠는 Postgre 데이터베이스를 애드온으로 지원한다. 약 10,000개 정도의 행을 갖는 작은 데이터베이스를 무료로 애플리케이션에 추가할 수 있다.

$ heroku addons:add heroku-postgresql:dev
Adding heroku-postgresql:dev on <appname>... done, v3 (free)
Attached as HEROKU_POSTGRESQL_BROWN_URL
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pgbackups:restore.
Use 'heroku addons:docs heroku-postgresql:dev' to view documentation.

HEROKU_POSTGRESQL_BROWN_URL 레퍼런스는 데이터베이스 URL을 갖고 있는 환경 변수의 이름이다. 이 작업을 하려고 할 때 브라운(brown)과는 다른 컬러를 얻을 수 있다. 헤로쿠는 애플리케이션당 다중 데이터베이스를 지원하며 각각은 URL에서 다른 컬러로 보인다. 데이터베이스는 DATABASE_URL 환경 변수에 자신의 URL을 포함시킨다.

  • 다음 커맨드는 브라운 컬러의 데이터베이스에 대한 처리를 보여준다.
$ heroku pg:promote HEROKU_POSTGRESQL_BROWN_URL
Promoting HEROKU_POSTGRESQL_BROWN_URL to DATABASE_URL... done

DATABASE_URL 환경 변수의 포맷은 정확하게 SQLAlchemy에서 필요한 것과 동일하다.

  • 정의해 둔 것이 있다면 DATABASE_URL의 값을 사용하는 config.py 스크립트를 기억하자. Postgre 데이터베이스에 대한 연결은 이제 자동으로 진행된다.

로깅 설정

로깅 설정은 init_app() 정적 메소드에 있는 ProductionConfig 클래스에 추가되지만 이러한 타입의 로깅은 헤로쿠에만 적용되기 때문에 새로운 설정이 플랫폼에 따라 생성될 수 있다. 다른 타입의 제품 플랫폼을 위한 기본 설정으로 ProductionConfig는 남겨둔다. HerokuConfig 클래스는 다음 예와 같다

ex) 17-3. config.py : 헤로쿠 설정

class HerokuConfig(ProductionConfig):
	@classmethod
	def init_app(cls, app):
		ProductionConfig.init_app(app)
		
		# log to stderr
		import logging
		from logging import StreamHandler
		file_handler = StreamHandler()
		file_handler.setLevel(logging.WARNING)
		app.logger.addHandler(file_handler)

애플리케이션을 헤로쿠에 의해 실행하면, 사용해야 할 설정을 알아야 한다. manage.py에서 생성된 애플리케이션 인스턴스는 FLASK_CONFIG 환경 변수로 어떤 설정을 사용해야 할 지 알게되며, 따라서 이 변수는 헤로쿠 환경에 설정되어야 한다. 환경 변수는 헤로쿠 클라이언트의 config:set 커맨드를 사용하여 설정한다.

$ heroku config:set FLASK_CONFIG=heroku
Setting config vars and restarting <appname>... done, v4
FLASK_CONFIG: heroku

이메일 설정

헤로쿠는 SMTP 서버를 제공하지 않으므로 외부 서버를 설정해 줘야 한다.

  • 스크립트에 로그인 자격 내용을 직접 포함시키게 되면 보안 위험이 있을 수 있기 때문에 지메일 SMTP에 액세스하기 위한 사용자 이름과 패스워드는 다음과 같이 환경변수를 통해 제공한다.
$ heroku config:set MAIL_USERNAME=<your-gmail-username>
$ heroku config:set MAIL_PASSWORD=<your-gmail-password>

제품 웹 서버의 실행

헤로쿠는 애플리케이션을 위한 호스트 목적의 웹 서버를 제공하지 않는다. 대신에 애플리케이션이 자신의 서버를 시작하고 환경 변수 PORT에 설정된 포트 번호에서 리스닝할 수 있도록 한다.

  • 플라스크를 사용한 개발 웹 서버는 성능이 매우 좋지 않다. 제품 환경에서 실행되도록 설계된 것이 아니기 때문이다.

  • 플라스크 애플리케이션을 잘 동작시키는 제품화 준비를 마친 두 개의 웹 서버는 구니콘(Gunicorn)과 uWSGI다.

구니콘은 다음과 같이 설치한다.

(venv) $ pip install gunicorn

구니콘에서 애플리케이션을 실행하려면 다음 커맨드를 사용한다.

(venv) $ gunicorn manage:app
  • 구니콘은 포트 8000을 사용한다.

필요 파일의 추가

헤로쿠는 최상위 폴더에 저장된 requirements.txt 파일에서 패키지 의존성을 로드한다.

psycopg2 패키지는 Postgre 데이터베이스와 구니콘 웹 서버 지원을 할 수 있게 한다.

ex) 17-4. requirements.txt : 헤로쿠 필요 파일

-r requirements/prod.txt
gunicorn==18.0
psycopg2==2.5.1

Procfile의 추가

헤로쿠는 애플리케이션을 시작하기 위해 사용하는 커맨드가 무엇인지 알고 있어야 한다. 이 커맨드는 Procfile이라고 하는 특정 파일에 작성되어 있다. 이 파일은 애플리케이션 최상위 폴더에 추가되어야 한다.

  • 다음은 이파일의 콘텐츠를 보여준다.

ex) 17-5. Procfile : 헤로쿠 Procfile

web: gunicorn manage:app

Procfile의 포맷은 매우 간단하다. 각 라인에는 태스크 이름이 있고, 그 다음에는 콜론, 그 다음에는 태스크를 실행하는 명령어가 온다.

포맨을 이용한 테스트

헤로쿠 툴벨트는 포맨(Foreman)이라는 두 번째 유틸리티를 포함하고 있다. 이 유틸리티는 테스트 목적으로 Procfile을 통해 애플리케이션을 로컬로 실행하는 데 사용된다.

  • 포맨은 애플리케이션의 최상위 디렉토리에 있는 .env라는 이름의 파일에서 환경변수들을 찾는다. 예를들어, .env 파일은 다음과 같은 변수들을 포함하고 있다.
FLASK_CONFIG=heroku
MAIL_USERNAME=<your-username>
MAIL_PASSWORD=<your-password>

.env 파일은 패스워드와 다른 민감한 계정 정보를 포함하고 있기 때문에 Git 저장소에는 절대 추가해서는 안된다

포맨의 주요한 두 개의 옵션은 foreman run 과 foreman start다.

  • run 커맨드는 애플리케이션 환경에서 커맨드를 실행할 때 사용되고, deploy 커맨드는 애플리케이션에서 데이터베이스를 생성할 때 사용된다.

start 커맨드는 Procfile이 필요하며 그 안에 있는 모든 태스크를 실행한다.

(venv) $ foreman start

Flask-SSLify를 이용한 보안 HTTP의 활성화

헤로쿠는 헤로쿠 내의 SSL 인증을 사용하는 설정 없이도 모든 애플리케이션이 http://https:// 모두에서 가능한 herokuapp.com 도메인을 액세스하도록 해 준다. 이때 필요한 유일한 작업은 애플리케이션이 http:// 인터페이스로 전송하는 리퀘스트를 가로채는 것과 그것을 https://로 리다이렉트하는 것이다. 그리고 이것은 Flask-SSLify 확장이 하는 작업이다.

  • 이러한 확장은 requirements.txt 파일에 추가되어야 한다.

ex) 17-6. app/__init__.py : 모든 리퀘스트를 보안 HTTP로 리다이렉트

def create_app(config_name):
	# ...
	if not app.debug and not app.testing and not app.config['SSL_DISABLE']:
		from flask.ext.sslify import SSLify
		sslify = SSLify(app)
	# ...

ex) 17-7. config.py : SSL의 사용 설정

class Config:
	# ...
	SSL_DISABLE = True
	
class HerokuConfig(ProductionConfig):
	# ...
	SSL_DISABLE = bool(os.environ.get('SSL_DISABLE'))
  • 환경변수가 비어 있는 문자열 대신에 다른 것으로 설정되면, 불린으로의 변경은 True를 리턴하며 SSL은 비활성화 된다. 환경변수가 존재하지 않거나 비어 있는 문자열로 설정되면, 불린으로의 변경은 False가 된다. 포맨을 사용할 때 SSL이 활성화되는 것을 막기 위해서는 .env 파일에 SSL_DISABLE=1을 추가해야 한다.

헤로쿠를 사용할 때 클라이언트는 호스트한 애플리케이션을 직접 연결하지 않으며 리버스 프록시 서버(reverse proxy server)에 연결한다.

  • 리버스 프록시 서버는 애플리케이션에 리퀘스트를 리다이렉트한다.

프록시 서버는 클라이언트로부터 받은 원래의 리퀘스트를 커스텀 HTTP 헤더를 통해 리다이렉트된 웹 서버에 전송하도록 작성된 정보를 넘긴다. 따라서 사용자가 이러한 정보를 바탕으로 SSL을 통해 애플리케이션과 통신할지를 결정할 수 있다.

Werkzeug는 WSGI 미들웨어를 제공하는데, 이 미들웨어는 프록시 서버로부터의 커스텀 헤더를 체크하고 그에 맞는 리퀘스트 오브젝트를 업데이트한다. 예를 들어, request.is_secure는 클라이언트가 리버스 프록시 서버에게 전송하는 리퀘스트의 보안성을 반영하며 프록시 서버가 애플리케이션에 전송하는 리퀘스트는 반영하지 않는다.

  • 다음은 애플리케이션에 ProxyFix 미들웨어를 추가하는 방법을 보여준다

ex) 17-8. config.py : 프록시 서버의 지원

class HerokuConfig(ProductionConfig):
	# ...
	@classmethod
	def init_app(cls, app):
		# ...
		
		# handler proxy server headers
		from werkzeug.contrib.fixers import ProxyFix
		app.wsgi_app = ProxyFix(app.wsgi_app)

Git push를 이용한 배포

모든 변경 사항이 로컬 Git 저장소에 커밋된 것을 확인한 후에 git push master를 사용하여 애플리케이션을 heroku 리모트에 업로드한다.

$ git push heroku master

이제 애플리케이션이 배포되고 실행된다. 그러나 이 과정이 정상적으로 진행된것 같지는 않다. 그 이유는 deploy 커맨드를 아직 실행하지 않았기 때문이다.

$ heroku run python manage.py deploy
  • 데이터 베이스 테이블이 생성되고 설정된 후에, 애플리케이션은 깔끔하게 시작하기 위해 재시작한다.
$ heroku restart

애플리케이션은 이제 배포되었고 https://<appname>.herokuapp.com에서 액세스가 가능하다.

로그 검토

애플리케이션에서 생성한 로그 출력은 헤로쿠에서 캡쳐 할 수 있다. 로그 내용을 보기 위해서는 logs 커맨드를 사용한다

$ heroku logs
  • 테스트를 하는 동안 로그 파일의 가장 최신 내용을 보는 것이 편리하며 다음과 같이 작업한다
$ heroku logs -t

배포와 업그레이드

헤로쿠 애플리케이션은 반복되어야 하는 동일한 프로세스를 업그레이드해야 한다.

$ heroku maintenance:on
$ git push heroku master
$ heroku run python manage.py deploy
$ heroku restart
$ heroku maintenance:off
  • maintanence 옵션은 업그레이드가 진행되는 동안 애플리케이션을 오프라인에서 사용하게 하며 사이트가 재개될 것이라는 정보를 사용자에게 알려주는 정적 페이지를 보여주게 된다.