簡體   English   中英

如何使用在 docker 鏡像或新容器中不斷變化的 python 庫?

[英]How to use a python library that is constantly changing in a docker image or new container?

我在 python 包中組織我的代碼(通常在virtualenv和/或conda等虛擬環境中),然后通常調用:

python <path_to/my_project/setup.py> develop

或者

pip install -e <path_to/my_project/setup.py>

這樣我就可以使用我的代碼的最新版本。 由於我主要開發統計或機器學習算法,因此我做了很多原型並每天更改我的代碼。 但是,最近在我可以訪問的集群上運行我們的實驗的推薦方法是通過 docker。 我了解了 docker,我想我對如何使它工作有一個粗略的想法,但我不太確定我的解決方案是否很好,或者是否有更好的解決方案。

我認為的第一個解決方案是使用以下解決方案復制我的 docker 映像中的數據:

COPY /path_to/my_project
pip install /path_to/my_project

然后點安裝它。 這個解決方案的問題是我每次都必須實際構建一個新圖像,這看起來很愚蠢,並希望我能有更好的東西。 為此,我正在考慮擁有一個 bash 文件,例如:

#BASH FILE TO BUILD AND REBUILD MY STUFF
# build the image with the newest version of 
# my project code and it pip installs it and its depedencies
docker build -t image_name .
docker run --rm image_name python run_ML_experiment_file.py 
docker kill current_container #not sure how to do get id of container
docker rmi image_name

正如我所說,我的直覺告訴我這很愚蠢,所以我希望有一種單一的命令方式可以使用 Docker 或單個 Dockerfile 來執行此操作。 另外,請注意該命令應該使用-v ~/data/:/data以便在完成訓練時能夠獲取數據和其他一些要寫入(在主機中)的卷/掛載。

我認為的另一個解決方案是將我的庫需要的所有 python 依賴項或其他依賴項放在 Dockerfile 中(因此在圖像中),然后以某種方式在正在運行的容器中執行我的庫的安裝。 也許使用docker exec [OPTIONS] CONTAINER COMMAND作為:

docker exec CONTAINER pip install /path_to/my_project

在正在運行的容器中。 之后,我可以使用相同的 exec 命令運行我想要運行的真實實驗:

docker exec CONTAINER python run_ML_experiment_file.py

不過,我仍然不知道如何系統地獲取容器 id(因為我可能不想每次執行此操作時都查找容器 id)。

理想情況下,在我看來,最好的概念解決方案是簡單地讓 Dockerfile 從一開始就知道它應該掛載到哪個文件(即/path_to/my_project ),然后以某種方式在圖像內部進行python [/path_to/my_project] develop ,以便它總是與可能發生變化的 python 包/項目相關聯。 這樣我就可以使用單個 docker 命令運行我的實驗,如下所示:

docker run --rm -v ~/data/:/data python run_ML_experiment_file.py

並且不必每次都自己顯式更新圖像(包括不必重新安裝應該是靜態的圖像部分),因為它始終與真實庫同步。 此外,讓其他腳本每次都從頭開始構建新圖像並不是我想要的。 此外,如果可能的話,能夠避免編寫任何 bash 也很好。


我認為我非常接近一個好的解決方案。 每次我將簡單地運行CMD命令進行 python 開發時,我將做什么而不是構建一個新圖像,如下所示:

# install my library (only when the a container is spun)
CMD python ~/my_tf_proj/setup.py develop

優點是它只會在我運行新容器時 pip 安裝我的庫。 這解決了開發問題,因為重新創建新圖像需要很長時間。 雖然我剛剛意識到,如果我使用CMD命令,那么我無法運行給我的 docker run 的其他命令,所以我實際上是要運行ENTRYPOINT

現在完成此操作的唯一問題是我在使用卷時遇到問題,因為我無法成功鏈接到Dockerfile 中的主機項目庫(由於某種原因,這似乎需要絕對路徑)。 我目前正在做(這似乎不起作用):

VOLUME /absolute_path_to/my_tf_proj /my_tf_proj

為什么我不能在我的 Dockerfile 中使用 VOLUME 命令進行鏈接? 我使用 VOLUME 的主要目的是在 CMD 命令嘗試安裝我的庫時使我的庫(以及此映像始終需要的其他文件)可訪問。 是否可以在啟動容器時始終讓我的庫可用?

理想情況下,我只想在容器運行時自動安裝庫,如果可能的話,由於總是需要最新版本的庫,所以在初始化容器時安裝它。

現在作為參考,我的非工作 Dockerfile 如下所示:

# This means you derive your docker image from the tensorflow docker image
# FROM gcr.io/tensorflow/tensorflow:latest-devel-gpu
FROM gcr.io/tensorflow/tensorflow
#FROM python
FROM ubuntu

RUN mkdir ~/my_tf_proj/
# mounts my tensorflow lib/proj from host to the container
VOLUME /absolute_path_to/my_tf_proj

#
RUN apt-get update

#
apt-get install vim

#
RUN apt-get install -qy python3
RUN apt-get install -qy python3-pip
RUN pip3 install --upgrade pip

#RUN apt-get install -y python python-dev python-distribute python-pip

# have the dependecies for my tensorflow library
RUN pip3 install numpy
RUN pip3 install keras
RUN pip3 install namespaces
RUN pip3 install pdb

# install my library (only when the a container is spun)
#CMD python ~/my_tf_proj/setup.py develop
ENTRYPOINT python ~/my_tf_proj/setup.py develop

作為旁注:

此外,由於某種原因,它需要我執行RUN apt-get update才能在我的容器中安裝 pip 或 vim。 人們知道為什么嗎? 我想這樣做是因為以防萬一我想用bash終端附加到容器上,這將非常有幫助。

似乎 Docker 只是迫使您 apt install 始終在容器中擁有最新版本的軟件?


賞金:

COPY有什么解決方案? 也許還有docker build -f path/Docker . . 請參閱: 如何從主用戶目錄構建 docker 映像?

在開發過程中,IMO 非常適合將帶有不斷變化的源的主機目錄映射/掛載到 Docker 容器中。 其余的(python 版本,您所依賴的其他庫都可以以正常方式安裝在 docker 容器中。

穩定后,我刪除 map/mount 並將包添加到要使用pip安裝的項目列表中。 我確實有一個運行devpi的單獨容器,因此無論我將它們一直推送到 PyPI 還是將它們推送到我的本地devpi容器,我都可以pip -install 軟件包。

即使您使用常見的(但更有限的) python [path_to_project/setup.py] develop也可以加快容器創建速度。 在這種情況下,您的Dockerfile應如下所示:

 # the following seldom changes, only when a package is added to setup.py
 COPY /some/older/version/of/project/plus/dependent/packages /older/setup
 RUN pip /older/setup/your_package.tar.gz

 # the following changes all the time, but that is only a small amount of work
 COPY /latest/version/of/project     
 RUN python [path_to_project/setup.py] develop

如果第一個副本會導致/older/setup下的文件發生更改,則容器將從那里重建。

運行python ... develop仍然需要更多時間,您需要重建/重新啟動容器。 由於我的所有軟件包也都可以復制/鏈接到(除了安裝之外),這仍然是一個很大的開銷。 我在容器中運行一個小程序,檢查(安裝/映射的)源是否更改,然后自動重新運行我正在開發/測試的任何內容。 所以我只需要保存一個新版本並觀察容器的輸出。

對於部署/分發,為您的包提供一個 docker 映像將是無縫的。 如果不是作為鏡像,則需要將源代碼傳輸到需要運行的環境,配置卷以將源包含在容器中以便可以構建等,使用鏡像只需拉取並運行容器出它。

但為了方便並擺脫構建映像的手動步驟,請考慮使用 docker-compose。

docker-compose.yml 可能如下所示:

ml_experiment:
  build: <path/to/Dockerfile>
  volumes:
    - ~/data/:/data
  command: ["python", "run_ML_experiment_file.py"] 

現在要構建一個鏡像並啟動一個容器,您只需要這樣做

docker-compose up --build

選項--build是每次都必須重建鏡像,否則 docker-compose 選擇使用已經構建的鏡像

參考https://docs.docker.com/compose/

這可能是在這里重申其他好的答案中的一些內容,但這是我的看法。 為了澄清我認為您的目標是什么,您希望 1) 運行容器而不每次都重新構建它,以及 2) 在啟動容器時使用最新的代碼。

坦率地說,如果不使用綁定掛載( -v host/dir:/docker/dir )、在代碼版本之間切換的ENV變量或構建單獨的 dev 和構建單獨的 dev 和生產圖像。 即,您不能通過使用COPY來實現兩者,這只會得到 (2)。

  • 請注意,容器哲學的這一部分:它們旨在“凍結”您的軟件,使其與構建映像時的軟件完全相同。 圖像本身並不意味着是動態的(這就是為什么容器非常適合跨環境復制結果!); 要成為動態的,您必須使用綁定掛載或其他方法。

如果您不介意每次都(快速)重建圖像,那么您幾乎可以實現這兩個目標; 這就是 Anthon 的解決方案將提供的。 如果您適當地構建代碼更改並確保不修改 Dockerfile 中先前構建的任何內容,這些重建將會很快。 這樣可以確保每次創建新映像時都不會重新運行前面的步驟(因為docker build會忽略未更改的步驟)。

考慮到這一點,這是一種使用COPYdocker build -f ...僅實現 (2) 的方法。

  • 再次注意,這將需要每次重建圖像,因為COPY將復制您指定的任何目錄的靜態快照; 運行docker build ...后對該目錄的更新不會被反映。

假設您將在代碼目錄(而不是主目錄*)中構建映像,您可以在 Dockerfile 的末尾添加如下內容:

COPY . /python_app
ENTRYPOINT python /python_app/setup.py develop

然后通過以下方式構建圖像:

docker build -t your:tag -f path/to/Dockerfile .

請注意,這可能比 Anthon 的方法慢,因為每次重建都會涉及整個代碼目錄,而不僅僅是您最近的更改(前提是您將代碼構造成靜態和開發分區)。


*Nb 通常不建議COPY一個大目錄(例如您的完整主目錄),因為它會使圖像變得非常大(由於帶寬或 I/O 有限,在集群上運行圖像時可能會減慢您的工作流程! )。

關於您帖子中的apt-get update評論:在容器中運行update可確保以后的install不會使用舊的包索引。 因此進行update是一種很好的做法,因為源上游映像通常具有較舊的包索引,這意味着如果沒有事先updateinstall可能會失敗。 另請參閱在 Docker 中,為什么建議在 Dockerfile 中運行 `apt-get` 更新? .

我認為您正在尋找綁定裝載 Docker 功能。 檢查此文檔:使用 Bind Mounts 使用它,您可以使用不斷變化的 python 腳本掛載主機目錄,它將在容器中可用。 如果您只需要使用不斷變化的腳本安裝某些特定目錄,我還會使用 PIP 命令pip install -r requirements.txt並將所有包合並到單個 requirements.txt 文件中(我看到您重復RUN pip3 install ...在您的 Dockerfile 中)。

我通常使用Dockerfile

###########
# BUILDER #
###########

# pull official base image
FROM python:3.8.3-slim as builder

# set work directory
WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install psycopg2 dependencies
RUN apt-get update \
    && apt-get -y install libpq-dev gcc \
    python3-dev musl-dev libffi-dev\
    && pip install psycopg2

# lint
RUN pip install --upgrade pip
COPY . .

# install dependencies
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt

# copy project
COPY . .

#########
# FINAL #
#########

# pull official base image
FROM python:3.8.3-slim

# create directory for the app user
RUN mkdir -p /home/app

# create the app user
RUN addgroup --system app && adduser --system --group app

# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
RUN mkdir $APP_HOME/static
RUN mkdir $APP_HOME/media
RUN mkdir $APP_HOME/currencies
WORKDIR $APP_HOME

# install dependencies
RUN apt-get update && apt-get install -y libpq-dev bash netcat rabbitmq-server
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
COPY wait-for /bin/wait-for
COPY /log /var/log
COPY /run /var/run

RUN pip install --no-cache /wheels/*

# copy project
COPY . $APP_HOME

# chown all the files to the app user
RUN chown -R app:app $APP_HOME
RUN chown -R app:app /var/log/
RUN chown -R app:app /var/run/

EXPOSE 3000

# change to the app user
USER app

# only for dgango
CMD ["gunicorn", "Config.asgi:application", "--bind", "0.0.0.0:8000", "--workers", "3", "-k","uvicorn.workers.UvicornWorker","--log-file","-"]

碼頭工人-compose.yml

# docker-compose.yml

version: "3.7"

services:
  db:
    container_name: postgres
    hostname: postgres
    image: postgres:12
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    env_file:
      - .env.prod.db
    networks:
      - main
    restart: always

  pgbackups:
    container_name: pgbackups
    hostname: pgbackups
    image: prodrigestivill/postgres-backup-local
    restart: always
    user: postgres:postgres # Optional: see below
    volumes:
      - ./backups:/backups
    links:
      - db
    depends_on:
      - db
    env_file:
      .env.prod.db
    networks:
      - main

  web:
    build: .
    container_name: web
    expose:
      - 8000
    command: sh -c "wait-for db:5432\
      && python manage.py makemigrations&&python manage.py migrate&&gunicorn Config.asgi:application --bind 0.0.0.0:8000 -w 3 -k uvicorn.workers.UvicornWorker --log-file -"
    volumes:
      - static_volume:/home/app/web/static
      - media_volume:/home/app/web/media
    env_file:
      - .env.prod
    hostname: web
    image: web-image
    networks:
      - main
    depends_on:
      - db
    restart: always

  prometheus:
    container_name: prometheus
    image: prom/prometheus
    hostname: prometheus
    volumes:
      - ./prometheus/:/etc/prometheus/
    ports:
      - 9090:9090
    networks:
      - main
    depends_on:
      - web
    restart: always

  grafana:
    container_name: grafana
    image: grafana/grafana:6.5.2
    hostname: grafana
    ports:
      - 3060:3000
    networks:
      - main
    depends_on:
      - prometheus
    restart: always

  nginx:
    container_name: nginx
    image: nginx:alpine
    hostname: nginx
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./wait-for:/bin/wait-for
      - static_volume:/home/app/web/static
      - media_volume:/home/app/web/media
    ports:
      - 80:80
    depends_on:
      - web
    networks:
      - main
    restart: always

networks:
  main:
    driver: bridge

volumes:
  static_volume:
  media_volume:
  postgres_data:

等待

#!/bin/sh

TIMEOUT=120
QUIET=0

echoerr() {
  if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}

usage() {
  exitcode="$1"
  cat << USAGE >&2
Usage:
  $cmdname host:port [-t timeout] [-- command args]
  -q | --quiet                        Do not output any status messages
  -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
  -- COMMAND ARGS                     Execute command with args after the test finishes
USAGE
  exit "$exitcode"
}

wait_for() {
  for i in `seq $TIMEOUT` ; do
    nc -z "$HOST" "$PORT" > /dev/null 2>&1

    result=$?
    if [ $result -eq 0 ] ; then
      if [ $# -gt 0 ] ; then
        exec "$@"
      fi
      exit 0
    fi
    sleep 1
  done
  echo "Operation timed out" >&2
  exit 1
}

while [ $# -gt 0 ]
do
  case "$1" in
    *:* )
    HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
    PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
    shift 1
    ;;
    -q | --quiet)
    QUIET=1
    shift 1
    ;;
    -t)
    TIMEOUT="$2"
    if [ "$TIMEOUT" = "" ]; then break; fi
    shift 2
    ;;
    --timeout=*)
    TIMEOUT="${1#*=}"
    shift 1
    ;;
    --)
    shift
    break
    ;;
    --help)
    usage 0
    ;;
    *)
    echoerr "Unknown argument: $1"
    usage 1
    ;;
  esac
done

if [ "$HOST" = "" -o "$PORT" = "" ]; then
  echoerr "Error: you need to provide a host and port to test."
  usage 2
fi

wait_for "$@"

nginx/nginx.conf

# nginx.conf

upstream back {
    server web:8000;
}
server {

    listen 80;

    location / {
        proxy_pass http://back;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /static/ {
     root /home/app/web/;
    }

    location /media/ {
     root /home/app/web/;
    }

}

普羅米修斯/prometheus.yml

global:
  scrape_interval: 10s
  evaluation_interval: 10s

  external_labels:
    monitor: django-monitor

scrape_configs:
  - job_name: "main-django"
    metrics_path: /metrics
    tls_config:
      insecure_skip_verify: true
    static_configs:
      - targets:
        - host.docker.internal

  - job_name: 'prometheus'
    scrape_interval: 10s
    static_configs:
      - targets: [ 'host.docker.internal:9090' ]

.env.prod 對您的項目來說是獨一無二的

.env.prod.db

POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_HOST=
POSTGRES_EXTRA_OPTS="-Z6 --schema=public --blobs"
SCHEDULE="@every 0h30m00s"
BACKUP_KEEP_DAYS=7
BACKUP_KEEP_WEEKS=4
BACKUP_KEEP_MONTHS=6
HEALTHCHECK_PORT=8080

項目運行

docker build -t web-image .
docker-compose up

項目更新

docker-compose up -d --build
docker-compose up

運行腳本

docker-compose exec web {script} 

一群

docker swarm init --advertise-addr 127.0.0.1:2377
docker stack deploy -c docker-compose.yml  proj

移除群

docker stack rm proj
docker swarm leave --force

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM