簡體   English   中英

如何防止每次啟動 Docker 容器時運行我的腳本?

[英]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 databaserunning 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.01.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");

通常構建部署步驟是分開的。

您的ENTRYPOINTdeploy 的一部分。 如果你想手動配置 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.

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