フラスコ、gunicornをパッケージ化する方法


フラスコ、gunicorn、nginx、スタックはpowerfullツールチェーンです.それをdockerizingする方法について多くのチュートリアルがありますが、それは異なります.我々は、セキュリティ懸念の観点で、不朽の方法からそれを行います.

目次
  • 1-Distroless Concept
  • 2-How to work with theys
  • 3-Adding Packages
  • 4-Security Scanning

  • 1 )無重力概念

    What are distroless containers ?

    Think in slim down linux distribution, where are removed all unused things.
    Don't have shell or package manager, come without: apt, apk, pip, bash, etc..
    When remove all unnecessary things get a bonus of size saving, halving or even more the final size.So is more faster to upload it. From a security concern we obtain a small surface attack area.

    To get more info about it: Google Container Tools
    コンセプトは明らかです:ちょうどアプリケーションランタイムとアクセシビリティライブラリを追加します.

    2 )どうやって動くか

    The gcr.io/distroless/python3 is a batteries not included toy.

    The classic recipe resolve all in one saucepan:

    FROM python:3.6
    ADD . /app
    WORKDIR /app
    RUN pip install flask gunicorn
    EXPOSE 8000
    CMD ["gunicorn", "-b", "0.0.0.0:8000", "app"]
    

    Now the build process change, is more like a crispy home fries recipe, first boil the potatoes in water, then drain and cool, add spices and finally send to oven.

    First we will take a base system python:3.6-slim and call build-env , over this it will be added the needed packages. Second move the packages and our application to the distroless image gcr.io/distroless/python3 .

    To do it, here is where multistage build come to help us: Docker: Use multi-stage builds

    2.2 )パッケージインストール
    インストールするパッケージ
    #file:requirements.txt
    flask==1.1.4
    gunicorn>19
    
    イメージを構築するには、次の2つの段階が必要です.
    # file: flask_gunicorn_distroless.dockerfile
    
    FROM python:3.6-slim  AS build-env
    WORKDIR /app
    
    # First install packages to avoid re-run pip install 
    COPY requirements.txt ./
    RUN pip install --disable-pip-version-check -r requirements.txt --target /packages
    
    COPY ./versions.py /app
    COPY ./app.py /app
    COPY ./standalone.py /app
    
    FROM gcr.io/distroless/python3
    WORKDIR /app
    COPY --from=build-env /app /app
    COPY --from=build-env /packages /packages
    #instead of COPY --from=build-env /packages /usr/local/lib/python/site-packages
    
    ENV PYTHONPATH=/packages
    #instead of ENV PYTHONPATH=/usr/local/lib/python/site-packages
    
    CMD ["standalone.py"]
    

    いくつかのヒント
    distexlessでは、nextsコマンドは動作しません.RUN mkdir /app RUN cp ./source /app代わりにWORKDIR /app mkdirとcdを暗黙的に実行します.COPY ./app /app ターゲットディレクトリを作成する

    2.2 )内部は何か
    イメージgcr.io/distroless/python3 いくつかのプリインストールされたパッケージが付属しているだけで、その中に小さなPythonファイルが書かれています.
    # file version.py
    import sys
    import os
    import argparse
    from pathlib import Path
    
    print("Versions:------------------")
    parser = argparse.ArgumentParser()
    parser.add_argument('folder', type=str, help='The folder to list.', nargs='?', default=os.getcwd())
    
    #--------------------------
    import flask
    print("Flask Version:",flask.__version__)
    
    import gunicorn 
    print("Gunicorn Version:",gunicorn.__version__)
    
    #--------------------------
    print("Getcwd: ",os.getcwd()) 
    print("Sys.path count:",str(len(sys.path)))
    print('\n'.join(sys.path))
    
    
    def listFolders(str_path: str):
        dir_path=Path(str_path).expanduser()
        print(" Packages:------------------")
        try:
           for path in dir_path.iterdir():            
             if path.is_dir(): 
                print(path)
        except PermissionError:
           pass
    
    def main(args):
        print('Folder:'+args.folder)
        listFolders(args.folder)
    
    
    if __name__ == "__main__":
        main(parser.parse_args())
        #listFolders('/packages')
    
    dockfileで最後の行を変更します.CMD ["versions.py", "/usr/lib/python3.9"]画像を再構築しますdocker build --tag flask_gunicorn_distroless --file flask_gunicorn_distroless.dockerfile .実行しますdocker run flask_gunicorn_distrolessコンソールでは、パスとあらかじめインストールされたパッケージが表示されます.
    :docker run flask_gunicorn_distroless
    Versions:------------------
    Flask Version: 1.1.4
    Gunicorn Version: 20.1.0
    Getcwd:  /app
    Sys.path count: 5
    /app
    /packages
    /usr/lib/python39.zip
    /usr/lib/python3.9
    /usr/lib/python3.9/lib-dynload
    Folder:/usr/lib/python3.9
    Folders:------------------
    /usr/lib/python3.9/multiprocessing
    /usr/lib/python3.9/zoneinfo
    /usr/lib/python3.9/sqlite3
    /usr/lib/python3.9/distutils
    /usr/lib/python3.9/venv
    /usr/lib/python3.9/http
    /usr/lib/python3.9/xml
    /usr/lib/python3.9/asyncio
    /usr/lib/python3.9/collections
    /usr/lib/python3.9/wsgiref
    /usr/lib/python3.9/encodings
    /usr/lib/python3.9/pydoc_data
    /usr/lib/python3.9/ctypes
    /usr/lib/python3.9/xmlrpc
    /usr/lib/python3.9/test
    /usr/lib/python3.9/email
    /usr/lib/python3.9/importlib
    /usr/lib/python3.9/curses
    /usr/lib/python3.9/html
    /usr/lib/python3.9/json
    /usr/lib/python3.9/logging
    /usr/lib/python3.9/dbm
    /usr/lib/python3.9/lib-dynload
    /usr/lib/python3.9/urllib
    /usr/lib/python3.9/concurrent
    /usr/lib/python3.9/unittest
    /usr/lib/python3.9/__pycache__
    

    2.3 )実行フラスコ
    ちょうどSharityチェックのために、我々のフラスコアプリケーションが動くことができるかどうか見てください:
    #file:app.py
    from time import gmtime, strftime   
    from flask import Flask
    
    application = Flask(__name__)
    
    @application.route("/")
    def index():    
        return {'status':'Available','time':strftime("%H:%M:%S", gmtime())  }    
    
    if __name__ == "__main__":    
        application.run(host="0.0.0.0", port=5000,debug=True)
    
    
    dockfileで最後の行を変更します.CMD ["app.py"]画像を再構築しますdocker build --tag flask_gunicorn_distroless --file flask_gunicorn_distroless.dockerfile .実行しますdocker run -p 80:5000 flask_gunicorn_distroless好みのwebブラウザーでアドレーションを開きます.http://localhost , すべてがOKならば、JSONは示されます.


    2.4 . gunicornの実行
    古典的なレシピでは、gunicornを実行する通常の方法は以下の通りです.
    CMD ["gunicorn", "-b", "0.0.0.0:8000", "app"]
    
    でも今はENTRYPOINT is /usr/bin/python3.9ですから、Pythonの内部からgunicornを実行する必要があります.
    Gunicorn Custom Application
    アイデアはサブクラスgunicorn.app.base.BaseApplication と過負荷load_config and load .
    # file:standalone.py
    from app import application
    import multiprocessing
    import gunicorn.app.base
    
    def number_of_workers():
        return  (multiprocessing.cpu_count() * 2) + 1
    
    class StandaloneApplication(gunicorn.app.base.BaseApplication):
    
        def __init__(self, app, options=None):
            self.options = options or {}
            self.application = app
            super().__init__()
    
        def load_config(self):
            config = {key: value for key, value in self.options.items()
                      if key in self.cfg.settings and value is not None}
            for key, value in config.items():
                self.cfg.set(key.lower(), value)
    
        def load(self):
            return self.application
    
    if __name__ == '__main__':
        options = {
            'bind': '%s:%s' % ('0.0.0.0', '8000'),
            'workers': number_of_workers(),
        }
        StandaloneApplication(application, options).run()
    
    
    dockfileで最後の行を変更します.CMD ["standalone.py"]画像を再構築しますdocker build --tag flask_gunicorn_distroless --file flask_gunicorn_distroless.dockerfile .実行しますdocker run -p 80:8000 flask_gunicorn_distroless好みのwebブラウザーでアドレーションを開きます.http://localhost , すべてがOKならば、JSONは示されます.


    脆弱性走査

    We will use Clair to perform a static analysis of vulnerabilities.

    1) Run the vulnerabilities database:

    docker run -d --name db arminc/clair-db:latest

    2) Run the service:

    docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan

    3) Run the client:

    Download from: Clair Scanner
    画像IDを取得します.
    docker image ls flask_gunicorn_distroless
    REPOSITORY                  TAG       IMAGE ID       CREATED         SIZE
    flask_gunicorn_distroless   latest    fa824d3ab0a6   3 minutes ago   70.8MB
    
    
    スキャンバック、私たち自身のIPとイメージIDを通過します../clair-scanner_linux_386 --ip=127.0.0.1 fa824d3ab0a6 | grep Unapproved | wc -l脆弱性を得る

    と比較するpython:3.9-slim画像IDを取得します.
    docker image ls python:3.9-slim
    REPOSITORY   TAG        IMAGE ID       CREATED       SIZE
    python       3.9-slim   5e714d33137a   5 days ago    122MB
    
    
    スキャンバック、私たち自身のIPとイメージIDを通過します../clair-scanner_linux_386 --ip=127.0.0.1 5e714d33137a | grep Unapproved | wc -l脆弱性を得る