What is Fabric ?

What is Fabric ?

Fabric là một thư viện Python mạnh mẽ cho phép bạn thực hiện các công việc deploy cũng như các tác vụ admin môi trường phát triển đến các server production. Nói một cách cụ thể thì Fabric cho phép chúng ta:

  • Chạy một hàm Python bất kỳ từ command line
  • Cho phép ta chạy các lệnh shell thông qua SSH một các dễ dàng và mang hơi hướng của Python (Pythonic) Về cơ bản, mọi người dùng đều sử dụng Fabric như là một công cụ để tự động hoá các công việc hàng ngày.

Cùng "cưỡi ngựa xem hoa" sơ qua một vòng xem Fabric có thể làm được những gì nhé.

Cài đặt

Như thường lệ với bất kì một package nào, ta có thể cài đặt thông qua pip (cài đặt trên system-wide hoặc trong virtual enviroment)

sudo pip install fabric

Hello World bằng Fabric

Tạo một file fabfile.py đơn giản với nội dung như sau:

from fabric.api import *

def hello():
    print("Hello world!")

Và chạy:

(venv)[[email protected] ~/lab/pvp-game (master)]$ fab hello
Hello world!

Done.

Vậy còn chạy lệnh trên shell thì sao ? Ta thêm một function nữa:

from fabric.api import *

env.hosts = ['[email protected]']

def hello():
    print("Hello world!")

def uname():
    run("uname -a")

và thực hiện trên localhost

(venv)[[email protected] ~/lab/pvp-game (master)]$ fab uname
[[email protected]] Executing task 'uname'
[[email protected]] run: uname -a
[[email protected]] Login password for 'vigo':
[[email protected]] out: Linux ubuntu 3.13.0-58-generic #97-Ubuntu SMP Wed Jul 8 02:56:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
[[email protected]] out:

Done.
Disconnecting from localhost... done.

Biến env chứa các thiết lập môi trường cho fabfile của chúng ta. Ở đây ta đã chỉnh định username cũng như host mà ta muốn chạy trên đó. Hãy tham khảo thêm cách thiết lập host tại http://docs.fabfile.org/en/1.10/usage/execution.html#defining-host-lists

Các lệnh có thể thực hiện trong Fabric

Về cơ bản thì có các lệnh sau:

  • run (fabric.operations.run) : chạy một lệnh trên shell của remote host
  • sudo (fabric.operations.sudo) : chạy một lệnh trên shell của remote host dưới sudo
  • local (fabric.operations.local) : chạy một lệnh trên shell của local host
  • get (fabric.operations.get) : dùng để lấy một file từ trên remote host về local host
  • put (fabric.operations.put) : ngược lại với get, dùng để đưa một file từ local host sang remote host
  • prompt (fabric.operations.prompt) : Đưa ra câu hỏi cho người dùng và lấy vào dữ liệu (tương tự raw_input)
  • reboot (fabric.operations.reboot) : khởi động lại server và chờ boot up lại trong khoảng thời gian đã được chỉ định

Deploy một app

Sau đây ta sẽ thử tự động hoá công việc deploy một site được viết bằng Flask lên một server. Để có thể đóng gói một app ta cần có file setup.py. Đây là source của setup.py của app demo: https://github.com/vigov5/pvp-game

Cấu trúc thư mục của project như sau:

├── app
│   ├── forms.py
│   ├── __init__.py
│   ├── models.py
│   ├── static
│   ├── templates
│   ├── views.py
├── build_question.py
├── config.py
├── config.pyc
├── db_create.py
├── db_downgrade.py
├── db_migrate.py
├── db_repository
│   ├── __init__.py
│   ├── manage.py
│   ├── migrate.cfg
│   ├── README
│   └── versions
├── db_upgrade.py
├── fabfile.py
├── LICENSE
├── MANIFEST.in
├── README
├── README.md
├── requirements.txt
├── run.py
├── server.py
├── setup.py

Ta cấu hình file setup.py như sau:

from setuptools import setup, find_packages

setup(
    # Application name:
    name="pvp_game",

    # Version number (initial):
    version="0.1.2",

    # Application author details:
    author="Tien Nguyen Anh",
    author_email="[email protected]",

    # Packages
    packages=find_packages(),

    # Include additional files into the package
    include_package_data=True,

    # Details
    url="http://pypi.python.org/pypi/PvPGame_v010/",

    #
    license="LICENSE",
    description="web socket quiz game",

    long_description=open("README").read(),

    zip_safe=False,

    # Dependent packages (distributions)
    install_requires=[
        'Babel==1.3',
        'Fabric==1.10.2',
        'Flask==0.10.1',
        'Flask-Admin==1.0.8',
        'Flask-Babel==0.9',
        'Flask-Login==0.2.11',
        'Flask-SQLAlchemy==1.0',
        'Flask-WTF==0.9.5',
        'Flask-WhooshAlchemy==0.55',
        'Jinja2==2.8',
        'MarkupSafe==0.23',
        'MySQL-python==1.2.5',
        'SQLAlchemy==0.9.7',
        'Tempita==0.5.2',
        'WTForms==1.0.5',
        'Werkzeug==0.10.4',
        'Whoosh==2.7.0',
        'argparse==1.2.1',
        'backports.ssl-match-hostname==3.4.0.2',
        'blinker==1.4',
        'certifi==2015.4.28',
        'decorator==4.0.0',
        'ecdsa==0.13',
        'flup==1.0.2',
        'itsdangerous==0.24',
        'paramiko==1.15.2',
        'pbr==0.11.0',
        'pycrypto==2.6.1',
        'pytz==2014.4',
        'six==1.9.0',
        'speaklater==1.3',
        'sqlalchemy-migrate==0.9.1',
        'tornado==4.0',
        'wsgiref==0.1.2'
    ],
)

Ta chỉ định các package cần cài đặt tại install_requires đi kèm với version tương ứng, tương tự như ở trong file requirements.txt. Ngoài package chính ta còn sử dụng các srip init nên cần có thêm file MANIFEST.in (đi kèm với thiết lập include_package_data=True) để kèm thêm các script này vào trong package. Bạn có thể tham khảo cách tạo package tại https://www.digitalocean.com/community/tutorials/how-to-package-and-distribute-python-applications hoặc https://docs.python.org/2/distutils/index.html

include db_create.py db_downgrade.py db_upgrade.py config.py build_question.py requirements.txt run.py
recursive-include app/templates *
recursive-include app/static *
recursive-include db_repository *

Chúng ta sẽ định nghĩa một func pack dùng để tạo một zip file dùng cho việc deploy:

def pack():
    local('python setup.py sdist --formats=gztar', capture=False)

Chạy func này sẽ sinh ra một file tar.gz ở trong thư mục dist:

(venv)[[email protected] ~/lab/pvp-game (master)]$ fab pack
[[email protected]] Executing task 'pack'
[localhost] local: python setup.py sdist --formats=gztar
running sdist
running egg_info
writing requirements to pvp_game.egg-info/requires.txt
writing pvp_game.egg-info/PKG-INFO
writing top-level names to pvp_game.egg-info/top_level.txt
writing dependency_links to pvp_game.egg-info/dependency_links.txt
writing pbr to pvp_game.egg-info/pbr.json
reading manifest file 'pvp_game.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'pvp_game.egg-info/SOURCES.txt'
running check
creating pvp_game-0.1.2
creating pvp_game-0.1.2/app
creating pvp_game-0.1.2/app/static
creating pvp_game-0.1.2/app/static/css
... truncated ...
hard linking db_repository/__init__.py -> pvp_game-0.1.2/db_repository
hard linking db_repository/manage.py -> pvp_game-0.1.2/db_repository
hard linking db_repository/migrate.cfg -> pvp_game-0.1.2/db_repository
hard linking db_repository/versions/__init__.py -> pvp_game-0.1.2/db_repository/versions
hard linking pvp_game.egg-info/PKG-INFO -> pvp_game-0.1.2/pvp_game.egg-info
hard linking pvp_game.egg-info/SOURCES.txt -> pvp_game-0.1.2/pvp_game.egg-info
hard linking pvp_game.egg-info/dependency_links.txt -> pvp_game-0.1.2/pvp_game.egg-info
hard linking pvp_game.egg-info/not-zip-safe -> pvp_game-0.1.2/pvp_game.egg-info
hard linking pvp_game.egg-info/pbr.json -> pvp_game-0.1.2/pvp_game.egg-info
hard linking pvp_game.egg-info/requires.txt -> pvp_game-0.1.2/pvp_game.egg-info
hard linking pvp_game.egg-info/top_level.txt -> pvp_game-0.1.2/pvp_game.egg-info
Writing pvp_game-0.1.2/setup.cfg
Creating tar archive
removing 'pvp_game-0.1.2' (and everything under it)

Done.

Các bước cần làm trước khi deploy lần đầu

  • Khi deploy lên server lần đầu tiên, có thể bạn sẽ cần cài đặt thêm một số denpency cho các package cần cài đặt, ví dụ như MySQL-python. Ta có thể cài đặt các package đó thông qua lệnh:
def pre_install():
  sudo('apt-get install python-dev libmysqlclient-dev')

Deploy code lên một server đang chạy

Ta thiết lập hàm deploy trong fabfile.py như sau:

def deploy():
    dist = local('python setup.py --fullname', capture=True).strip()
    put('dist/%s.tar.gz' % dist, '/tmp/pvp_game.tar.gz')

    # now we're on the remote host from here on out!
    sudo('rm -rf /tmp/pvp_game')
    run('mkdir /tmp/pvp_game')
    with cd('/tmp/pvp_game'):
        run('tar zxf /tmp/pvp_game.tar.gz')
        run('mv ' + dist + '/* .')
        sudo('/var/www/pvp_game/venv/bin/python setup.py install')

    sudo('rm -rf /tmp/pvp_game /tmp/pvp_game.tar.gz')

    # alert Apache to reload the project
    sudo('touch /var/www/pvp_game/run.py')

Các lệnh trong hàm khá là dễ hiểu. Ta sẽ copy file tar.gz của package vào thư mục tạm, sau dó cài đặt package và cập nhật script run.py

Còn đây là cấu hình Apache của server:

<VirtualHost *:80>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  <Directory /var/www/pvp_game>
    Require all granted
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>

  WSGIDaemonProcess pvp_game python-path=/var/www/pvp_game:/var/www/pvp_game/venv/lib/python2.7/site-packages
  WSGIProcessGroup pvp_game
  WSGIScriptAlias / /var/www/pvp_game/run.py

</VirtualHost>

Việc còn lại là deploy thôi 😄

(venv)[[email protected] ~/lab/pvp-game (master)]$ fab deploy
[[email protected]] Executing task 'deploy'
[localhost] local: python setup.py --fullname
[[email protected]] Login password for 'vigo':
[[email protected]] put: dist/pvp_game-0.1.2.tar.gz -> /tmp/pvp_game.tar.gz
[[email protected]] sudo: rm -rf /tmp/pvp_game
[[email protected]] out: sudo password:
[[email protected]] out:
[[email protected]] run: mkdir /tmp/pvp_game
[[email protected]] run: tar zxf /tmp/pvp_game.tar.gz
[[email protected]] run: mv pvp_game-0.1.2/* .
[[email protected]] sudo: /var/www/pvp_game/venv/bin/python setup.py install
[[email protected]] out: sudo password:
[[email protected]] out: running install
[[email protected]] out: running bdist_egg
[[email protected]] out: running egg_info
.... truncated ....
[[email protected]] out: Best match: setuptools 18.0.1
[[email protected]] out: Processing setuptools-18.0.1-py2.7.egg
[[email protected]] out: setuptools 18.0.1 is already the active version in easy-install.pth
[[email protected]] out: Installing easy_install script to /var/www/pvp_game/venv/bin
[[email protected]] out: Installing easy_install-2.7 script to /var/www/pvp_game/venv/bin
[[email protected]] out:
[[email protected]] out: Using /var/www/pvp_game/venv/lib/python2.7/site-packages/setuptools-18.0.1-py2.7.egg
[[email protected]] out: Finished processing dependencies for pvp-game==0.1.2
[[email protected]] out:

[[email protected]] sudo: rm -rf /tmp/pvp_game /tmp/pvp_game.tar.gz
[[email protected]] out: sudo password:
[[email protected]] out:
[[email protected]] sudo: touch /var/www/pvp_game/run.py
[[email protected]] out: sudo password:
[[email protected]] out:

Done.
Disconnecting from localhost... done.

Kiểm tra lại code mới:

pvp_game.png

Kết luận

Các tính năng của Fabric hết sức cơ bản nhưng dựa trên các công cụ này, hoàn toàn có thể phát triển mở rộng để có thể tối ưu hoá các công việc thông thường của người quản trị hệ thống.

Tham khảo