[英]How do I prevent my script from being run every time a Docker container is brought up?
只有在構建我的 docker 容器時,我才會運行腳本(填充我的 MySql Docker 容器)。 我正在運行以下 docker-compose.yml 文件,其中包含一個 Django 容器。
version: '3'
services:
mysql:
restart: always
image: mysql:5.7
environment:
MYSQL_DATABASE: 'maps_data'
# So you don't have to use root, but you can if you like
MYSQL_USER: 'chicommons'
# You can use whatever password you like
MYSQL_PASSWORD: 'password'
# Password for root access
MYSQL_ROOT_PASSWORD: 'password'
ports:
- "3406:3406"
volumes:
- my-db:/var/lib/mysql
web:
restart: always
build: ./web
ports: # to access the container from outside
- "8000:8000"
env_file: .env
environment:
DEBUG: 'true'
command: /usr/local/bin/gunicorn maps.wsgi:application -w 2 -b :8000
depends_on:
- mysql
apache:
restart: always
build: ./apache/
ports:
- "80:80"
#volumes:
# - web-static:/www/static
links:
- web:web
volumes:
my-db:
我有這個網絡/Dockerfile
FROM python:3.7-slim
RUN apt-get update && apt-get install
RUN apt-get install -y libmariadb-dev-compat libmariadb-dev
RUN apt-get update \
&& apt-get install -y --no-install-recommends gcc \
&& rm -rf /var/lib/apt/lists/*
RUN python -m pip install --upgrade pip
RUN mkdir -p /app/
WORKDIR /app/
COPY requirements.txt requirements.txt
RUN python -m pip install -r requirements.txt
COPY entrypoint.sh /app/
COPY . /app/
RUN ["chmod", "+x", "/app/entrypoint.sh"]
ENTRYPOINT ["/app/entrypoint.sh"]
這些是我的 entrypoint.sh 文件的內容
#!/bin/bash
set -e
python manage.py migrate maps
python manage.py loaddata maps/fixtures/country_data.yaml
python manage.py loaddata maps/fixtures/seed_data.yaml
exec "$@"
問題是,當我反復運行“docker-compose up”時,entrypoint.sh 腳本正在運行其命令。 我希望命令僅在首次構建 docker 容器時運行,但它們似乎總是在容器恢復時運行。 有什么方法可以調整我必須實現的目標嗎?
我以前使用過的一種方法是將您的loaddata
調用包裝在您自己的管理命令中,該命令首先檢查數據庫中是否有任何數據,如果有,則不執行任何操作。 像這樣的東西:
# your_app/management/commands/maybe_init_data.py
from django.core.management import call_command
from django.core.management.base import BaseCommand
from address.models import Country
class Command(BaseCommand):
def handle(self, *args, **options):
if not Country.objects.exists():
self.stdout.write('Seeding initial data')
call_command('loaddata', 'maps/fixtures/country_data.yaml')
call_command('loaddata', 'maps/fixtures/seed_data.yaml')
然后將您的入口點腳本更改為:
python manage.py migrate
python manage.py maybe_init_data
(這里假設您有一個Country
模型 - 替換為您的裝置中實際擁有的模型。)
在第一次運行時為數據庫做種的想法是一種非常常見的情況。 正如其他人所建議的那樣,您可以更改您的entrypoint.sh
腳本並對其應用一些條件邏輯,並使其按照您希望的方式工作。
但我認為,如果您將用於seeding the database
和running services
的邏輯分開,並且不要讓它們彼此糾纏在一起,那將是一個更好的做法。 這可能會導致將來出現一些不需要的行為。
我打算建議使用docker-compose
的解決方法,並開始搜索一些語法以在執行docker-compose up
時排除某些服務,但發現這仍然是一個懸而未決的問題。 但是我發現這個堆棧溢出答案女巫提出了一個非常好的方法。
version: '3'
services:
all-services:
image: docker4w/nsenter-dockerd # you want to put there some small image
command: sh -c "echo start"
depends_on:
- mysql
- web
- apache
mysql:
restart: always
image: mysql:5.7
environment:
MYSQL_DATABASE: 'maps_data'
# So you don't have to use root, but you can if you like
MYSQL_USER: 'chicommons'
# You can use whatever password you like
MYSQL_PASSWORD: 'password'
# Password for root access
MYSQL_ROOT_PASSWORD: 'password'
ports:
- "3406:3406"
volumes:
- my-db:/var/lib/mysql
web:
restart: always
build: ./web
ports: # to access the container from outside
- "8000:8000"
env_file: .env
environment:
DEBUG: 'true'
command: /usr/local/bin/gunicorn maps.wsgi:application -w 2 -b :8000
depends_on:
- mysql
apache:
restart: always
build: ./apache/
ports:
- "80:80"
#volumes:
# - web-static:/www/static
links:
- web:web
seed:
build: ./web
env_file: .env
environment:
DEBUG: 'true'
entrypoint: /bin/bash -c "/bin/bash -c \"$${@}\""
command: |
/bin/bash -c "
set -e
python manage.py loaddata maps/fixtures/country_data.yaml
python manage.py loaddata maps/fixtures/seed_data.yaml
/bin/bash || exit 0
"
depends_on:
- mysql
volumes:
my-db:
如果你使用類似上面的東西,你將能夠在運行docker-compose up
之前運行seeding
階段。
要播種您的數據庫,請運行:
docker-compose up seed
要運行所有堆棧,請使用:
docker-compose up -d all-services
我認為這是一種干凈的方法,可以擴展到許多不同的場景和用例。
更新
如果您真的希望能夠完全運行整個堆棧並防止多次運行loaddata
命令導致的意外行為,我建議您定義一個新的 django 管理命令來檢查現有數據。 看這個:
檢查種子.py
from django.core.management.base import BaseCommand, CommandError
from project.models import Country # or whatever model you have seeded
class Command(BaseCommand):
help = 'Check if seed data already exists'
def handle(self, *args, **options):
if Country.objects.all().count() > 0:
self.stdout.write(self.style.WARNING('Data already exists .. skipping'))
return False
# do all the checks for your data integrity
self.stdout.write(self.style.SUCCESS('Nothing exists'))
return True
在此之后,您可以更改docker-compose
seed
部分,如下所示:
seed:
build: ./web
env_file: .env
environment:
DEBUG: 'true'
entrypoint: /bin/bash -c "/bin/bash -c \"$${@}\""
command: |
/bin/bash -c "
set -e
python manage.py checkseed &&
python manage.py loaddata maps/fixtures/country_data.yaml
python manage.py loaddata maps/fixtures/seed_data.yaml
/bin/bash || exit 0
"
depends_on:
- mysql
這樣,您可以確保如果有人錯誤地運行docker-compose up -d
,不會導致完整性錯誤和類似問題。
不使用entrypoint.sh
文件,為什么不直接運行 web/Dockerfile 中的命令?
RUN python manage.py migrate maps
RUN python manage.py loaddata maps/fixtures/country_data.yaml
RUN python manage.py loaddata maps/fixtures/seed_data.yaml
這樣,這些更改將被烘焙到圖像中,並且當您啟動圖像時,這些更改將已經被執行。
我最近有一個類似的案例。 由於“ENTRYPOINT”包含每次容器啟動時將執行的命令,因此解決方案是在entrypoint.sh腳本中包含一些邏輯以避免應用更新(在您的情況下是遷移和data ) 如果這些操作的影響已經存在於數據庫中。
例如:
#!/bin/bash
set -e
#Function that goes to verify if effects of migration and load data are present on database
function checkEffects() {
IS_UPDATED=0
#Check effects and set to 1 IS_UPDATED if effects are not present
}
checkEffects
if [[ $IS_UPDATED == 0 ]]
then
echo "Database already initialized. Nothing to do"
else
echo "Database is clean. Initializing it"
python manage.py migrate maps
python manage.py loaddata maps/fixtures/country_data.yaml
python manage.py loaddata maps/fixtures/seed_data.yaml
fi
exec "$@"
然而,該場景更加復雜,因為如果這些涉及多個數據和數據,則驗證允許決定是否繼續進行更新的影響可能是相當困難的。 此外,如果您考慮隨着時間的推移容器升級,它會變得非常復雜。
示例:今天,您正在為您的Web服務使用本地 Dockerfile,但我認為在生產中,您將開始對該服務進行版本控制,並將其上傳到 Docker 注冊表。 因此,當您上傳第一個版本(例如1.0.0 版本)時,您將在 docker-compose.yml 中指定以下內容:
web: restart: always image: <DOCKER_REGISTRY_HOST>:<DOCKER_REGISTRY_PORT>/web:1.0.0 ports: # to access the container from outside - "8000:8000"
然后,當您在架構上包含其他更改(例如在entrypoint.sh上加載其他數據)時,您將發布Web服務容器的“1.2.0”版本:
#1.0.0 updates python manage.py migrate maps python manage.py loaddata maps/fixtures/country_data.yaml python manage.py loaddata maps/fixtures/seed_data.yaml #1.2.0 updates python manage.py loaddata maps/fixtures/other_seed_data.yaml
在這里,您將有 2 個場景(讓我們暫時忽略檢查對腳本的影響的需要):
1- 您第一次使用web:1.2.0部署您的服務:當您從一個干凈的數據庫開始時,您應該確保所有更新都被執行( 1.1.0和1.2.0 )。
這種情況的解決方案很簡單,因為您只需執行所有更新即可。
2- 您在運行1.0.0的現有環境中將Web 容器升級到1.2.0 :由於您的數據庫已使用 1.0.0 的更新進行初始化,因此您應該確保僅執行1.2.0更新這很困難,因為您應該能夠檢查應用的數據庫版本是什么,以便跳過 1.0.0 更新。 這意味着您應該將網絡版本存儲在數據庫中的某處,例如
根據所有這些討論,我認為最好的解決方案是直接處理創建模式和填充數據的腳本,以便使這些指令冪等地關注升級指令。
一些例子:
1- 創建一個表
而是按如下方式創建表:
CREATE TABLE country
使用if not exists以避免表已存在錯誤:
CREATE TABLE IF NOT EXISTS country
2- 插入默認數據
而不是在沒有指定主鍵的情況下插入數據:
INSERT INTO maps.country (name) VALUES ("USA");
包括主鍵以避免重復:
INSERT INTO maps.country (id,name) VALUES (1,"USA");
通常構建和部署步驟是分開的。
您的ENTRYPOINT
是deploy 的一部分。 如果你想手動配置 witch deploy run 應該運行 migrate 命令並且只用一個新的容器(可能來自新的鏡像)替換容器,那么你可以把它分成一個單獨的命令
啟動數據庫(如果沒有運行)
docker-compose -p production -f docker-compose.yml up mysql -d
遷移
docker run \
--rm \
--network production_default \
--env-file docker.env \
--entrypoint python \
my-backend-image-name:prod python manage.py migrate maps
然后部署新鏡像
docker-compose -p production -f docker-compose.yml up -d
每次手動決定是否運行遷移步驟
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.