3

只有在构建我的 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:

我有这个 web/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 容器时运行,但它们似乎总是在容器恢复时运行。有什么方法可以调整我必须实现的目标吗?

4

5 回答 5

2

我之前使用的一种方法是将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模型 - 替换为您在固定装置中实际拥有的模型。)

于 2020-02-23T03:20:54.817 回答
1

在第一次运行时播种数据库的想法是一种非常常见的情况。正如其他人所建议的那样,您可以更改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:

如果你使用类似上面的东西,你将能够在运行seeding之前运行阶段docker-compose up

要播种数据库,请运行:

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

在此之后,您可以更改您的seed部分,docker-compose如下所示:

  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错误运行,不会导致完整性错误和类似的问题。

于 2020-02-26T21:35:47.183 回答
0

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

这样,这些更改将被烘焙到图像中,并且当您启动图像时,这些更改将已经执行。

于 2020-02-20T07:09:26.173 回答
0

我最近有一个类似的案例。由于“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");
于 2020-02-26T11:16:33.940 回答
0

通常构建部署步骤是分开的。

ENTRYPOINTdeploy的一部分。如果您想手动配置,则部署运行应该运行迁移命令,并且只需用新容器替换容器(可能来自新图像),那么您可以将其拆分为单独的命令

启动数据库(如果未运行)

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

并且每次手动决定是否运行迁移步骤

于 2020-02-27T17:35:57.077 回答