[英]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 選擇使用已經構建的鏡像
這可能是在這里重申其他好的答案中的一些內容,但這是我的看法。 為了澄清我認為您的目標是什么,您希望 1) 運行容器而不每次都重新構建它,以及 2) 在啟動容器時使用最新的代碼。
坦率地說,如果不使用綁定掛載( -v host/dir:/docker/dir
)、在代碼版本之間切換的ENV
變量或構建單獨的 dev 和構建單獨的 dev 和生產圖像。 即,您不能通過使用COPY
來實現兩者,這只會得到 (2)。
如果您不介意每次都(快速)重建圖像,那么您幾乎可以實現這兩個目標; 這就是 Anthon 的解決方案將提供的。 如果您適當地構建代碼更改並確保不修改 Dockerfile 中先前構建的任何內容,這些重建將會很快。 這樣可以確保每次創建新映像時都不會重新運行前面的步驟(因為docker build
會忽略未更改的步驟)。
考慮到這一點,這是一種使用COPY
和docker 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
是一種很好的做法,因為源上游映像通常具有較舊的包索引,這意味着如果沒有事先update
, install
可能會失敗。 另請參閱在 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.