簡體   English   中英

如何使 docker-compose “.env” 文件優先於 shell env vars?

[英]How to make docker-compose ".env" file take precedence over shell env vars?

我希望我的 docker-compose.yml 文件使用與“docker-compose.yml”文件位於同一目錄中的“.env”文件來設置一些環境變量,並讓這些變量優先於設置的任何其他環境變量貝殼。 現在我有

$ echo $DB_USER
tommyboy

在我的 .env 文件中,我有

$ cat .env
DB_NAME=directory_data
DB_USER=myuser
DB_PASS=mypass
DB_SERVICE=postgres
DB_PORT=5432

我的 docker-compose.yml 文件中有這個...

version: '3'

services:

  postgres:
    image: postgres:10.5
    ports:
      - 5105:5432
    environment:
      POSTGRES_DB: directory_data
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: password

  web:
    restart: always
    build: ./web
    ports:           # to access the container from outside
      - "8000:8000"
    environment:
      DEBUG: 'true'
      SERVICE_CREDS_JSON_FILE: '/my-app/credentials.json'
      DB_SERVICE: host.docker.internal
      DB_NAME: directory_data
      DB_USER: ${DB_USER}
      DB_PASS: password
      DB_PORT: 5432
    command: /usr/local/bin/gunicorn directory.wsgi:application --reload -w 2 -b :8000
    volumes:
    - ./web/:/app
    depends_on:
      - postgres 

在我的 Python 3/Django 3 項目中,我的應用程序的 settings.py 文件中有這個

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['DB_NAME'],
        'USER': os.environ['DB_USER'],
        'PASSWORD': os.environ['DB_PASS'],
        'HOST': os.environ['DB_SERVICE'],
        'PORT': os.environ['DB_PORT']
    }
}

但是,當我使用“docker-compose up”運行我的項目時,我看到了

maps-web-1       |   File "/usr/local/lib/python3.9/site-packages/django/db/backends/postgresql/base.py", line 187, in get_new_connection
maps-web-1       |     connection = Database.connect(**conn_params)
maps-web-1       |   File "/usr/local/lib/python3.9/site-packages/psycopg2/__init__.py", line 127, in connect
maps-web-1       |     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
maps-web-1       | psycopg2.OperationalError: FATAL:  role "tommyboy" does not exist

似乎 Django 容器正在使用 shell 的 env var 而不是傳入的內容,我想知道是否有辦法讓 Python/Django 容器在根目錄中使用“.env”文件作為它的 env vars。

起初我以為我誤讀了您的問題,但我認為我最初的評論是正確的。 正如我之前提到的,本地 shell 環境通常會覆蓋.env文件中的內容。 這允許您覆蓋命令行上的設置。 換句話說,如果您的.env文件中有:

DB_USER=tommyboy

並且您想為單個 docker docker-compose up調用覆蓋DB_USER的值,您可以運行:

DB_USER=alice docker-compose up

這就是本地環境中的值優先的原因。


當將docker-compose與存儲持久數據的東西一起使用時——比如 Postgres! -- 在使用用於配置容器的環境變量時,您偶爾會看到一些奇怪的行為。 考慮以下事件序列:

  1. 我們第一次使用.env文件中的值運行docker-compose up

  2. 我們確認我們可以使用myuser用戶連接到數據庫:

     $ docker-compose exec postgres psql -U myuser directory_data psql (10.5 (Debian 10.5-2.pgdg90+1)) Type "help" for help. directory_data=#
  3. 我們通過輸入CTRL-C來停止容器。

  4. 我們在環境變量中使用DB_USER的新值啟動容器:

     DB_USER=tommyboy docker-compose up
  5. 我們嘗試使用tommyboy用戶名進行連接...

     $ docker-compose exec postgres psql -U tommyboy directory_data psql: FATAL: role "tommyboy" does not exist

    ......它失敗了。

這里發生了什么?

用於配置 Postgres 的POSTGRES_*環境變量僅在數據庫尚未初始化時才相關。 當您使用docker-compose停止並重新啟動服務時,它不會創建新容器; 它只是重新啟動現有的。

這意味着在上述事件序列中,數據庫最初是使用myuser用戶名創建的,並且在我們的環境中設置DB_USER時第二次啟動它並沒有改變任何東西。

這里的解決方案是使用docker-compose down命令,它會刪除容器...

docker-compose down

然后使用更新的環境變量創建一個新的:

DB_USER=tommyboy docker-compose up

現在我們可以按預期訪問數據庫:

$ docker-compose exec postgres psql -U tommyboy directory_data
psql (10.5 (Debian 10.5-2.pgdg90+1))
Type "help" for help.

directory_data=#

我無法提供比@larsks 提供的優秀答案更好的答案,但請讓我嘗試給你一些想法。

正如@larsks 還指出的那樣,任何 shell 環境變量都將優先於 docker-compose .env文件中定義的那些環境變量。

在處理環境變量時,docker-compose 文檔中也說明了這一事實,強調我的:

您可以使用.env文件設置環境變量的默認值,Compose 會自動在項目目錄(Compose 文件的父文件夾)中查找該文件。 在 shell 環境中設置的值會覆蓋在.env文件中設置的值

這意味着,例如,提供這樣的 shell 變量:

DB_USER= tommyboy docker-compose up

將最終覆蓋您可以在.env文件中定義的任何變量。

該問題的一種可能解決方案是嘗試直接使用.env文件,而不是環境變量。

搜索有關您的問題的信息時,我遇到了這篇很棒的文章

除其他外,除了解釋您的問題外,它還在帖子末尾的注釋中提到了一種基於使用django-environ的替代方法。

我不知道該庫,但它似乎提供了一種配置應用程序的替代方法,直接從配置文件中讀取配置:

import environ
import os

env = environ.Env(
    # set casting, default value
    DEBUG=(bool, False)
)

# Set the project base directory
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

# False if not in os.environ because of casting above
DEBUG = env('DEBUG')

# Raises Django's ImproperlyConfigured
# exception if SECRET_KEY not in os.environ
SECRET_KEY = env('SECRET_KEY')

# Parse database connection url strings
# like psql://user:pass@127.0.0.1:8458/db
DATABASES = {
    # read os.environ['DATABASE_URL'] and raises
    # ImproperlyConfigured exception if not found
    #
    # The db() method is an alias for db_url().
    'default': env.db(),

    # read os.environ['SQLITE_URL']
    'extra': env.db_url(
        'SQLITE_URL',
        default='sqlite:////tmp/my-tmp-sqlite.db'
    )
}

#...

如果需要,您似乎也可以混合環境中定義的變量

可能python-dotenv將允許您遵循類似的方法。

當然,值得一提的是,如果您決定使用這種方法,您需要使.env文件可訪問到您的 docker-compose Web 服務和相關容器,可能是掛載和附加卷或將.env文件復制到web目錄您已經安裝為卷。

您仍然需要處理 PostgreSQL 容器配置,但以某種方式它可以幫助您實現您在評論中指出的目標,因為您可以使用相同的.env文件(當然,一個重復的文件)。

根據您的評論,另一種可能的解決方案可能是使用 Docker 機密。

例如,在 Kubernetes 中,secrets 的工作方式與官方文檔中的解釋類似:

就 Docker Swarm 服務而言,秘密是一團數據,例如密碼、SSH 私鑰、SSL 證書或其他不應通過網絡傳輸或未加密存儲在 Dockerfile 或應用程序中的數據源代碼。 您可以使用 Docker 機密集中管理這些數據,並將其安全地傳輸到需要訪問它的容器。 秘密在傳輸過程中被加密,在 Docker 群中處於靜止狀態。 一個給定的秘密只能被那些被授予顯式訪問權限的服務訪問,並且只有在這些服務任務正在運行時才可以訪問。

簡而言之,它提供了一種跨 Docker Swarm 服務存儲敏感數據的便捷方式。

重要的是要了解 Docker 機密僅在使用Docker Swarm 模式時可用。

Docker Swarm 是 Docker 提供的一種編排服務,與 Kubernetes 類似,當然也有區別。

假設您在 Swarm 模式下運行 Docker,您可以根據官方 docker-compose docker secrets 示例以類似於以下方式部署您的 compose 服務:

version: '3'

services:

  postgres:
    image: postgres:10.5
    ports:
      - 5105:5432
    environment:
      POSTGRES_DB: directory_data
      POSTGRES_USER: /run/secrets/db_user
      POSTGRES_PASSWORD: password
    secrets:
       - db_user
  web:
    restart: always
    build: ./web
    ports:           # to access the container from outside
      - "8000:8000"
    environment:
      DEBUG: 'true'
      SERVICE_CREDS_JSON_FILE: '/my-app/credentials.json'
      DB_SERVICE: host.docker.internal
      DB_NAME: directory_data
      DB_USER_FILE: /run/secrets/db_user
      DB_PASS: password
      DB_PORT: 5432
    command: /usr/local/bin/gunicorn directory.wsgi:application --reload -w 2 -b :8000
    volumes:
    - ./web/:/app
    depends_on:
      - postgres
    secrets:
       - db_user

secrets:
   db_user:
     external: true

請注意以下事項。

我們在秘密部分定義了一個名為db_usersecrets

這個秘密可以基於一個文件或從標准 in 計算,例如:

echo "tommyboy" | docker secret create db_user -

秘密應該暴露給需要它的每個容器。

在 Postgres 的情況下,如官方 Postgres docker image description中的Docker secrets部分所述,您可以使用 Docker secrets 定義POSTGRES_INITDB_ARGSPOSTGRES_PASSWORDPOSTGRES_USERPOSTGRES_DB的值:秘密變量的名稱是與帶有后綴_FILE的普通文件相同。

在我們的用例中,我們定義了:

POSTGRES_USER_FILE: /run/secrets/db_user

在 Django 容器的情況下,此功能不支持開箱即用,但由於您可以根據需要編輯settings.py ,例如在這篇簡單但很棒的文章中建議您可以使用幫助程序在settings.py文件中讀取所需值的函數,例如:

import os

def get_secret(key, default):
    value = os.getenv(key, default)
    if os.path.isfile(value):
        with open(value) as f:
            return f.read()
    return value

DB_USER = get_secret("DB_USER_FILE", "")

# Use the value to configure your database connection parameters

存儲數據庫密碼可能更有意義,但它也可能是數據庫用戶的有效解決方案。

請考慮查看這篇出色的文章

基於問題似乎是由 Django 容器中環境變量的更改引起的,您可以嘗試的最后一件事如下。

您的settings.py文件的唯一要求是使用您的配置聲明不同的全局變量。 但它並沒有說明如何閱讀它們:實際上,我在答案中暴露了不同的方法,而且畢竟是 Python,您可以使用該語言來滿足您的需求。

此外,重要的是要了解,除非在 Dockerfile 中更改任何變量,否則在創建Postgres 和 Django 容器時,將收到完全相同的.env文件和完全相同的配置。

考慮到這兩件事,您可以嘗試在settings-py文件中創建所提供環境的 Django 容器本地副本,並在重新啟動之間或在導致變量更改的任何原因之間使用它。

在你的settings.py (請原諒我代碼的簡單性,我希望你明白):

import os
import ast

env_vars = ['DB_NAME', 'DB_USER', 'DB_PASS', 'DB_SERVICE', 'DB_PORT']

if not os.path.exists('/tmp/.env'):
    with open('/tmp/.env', 'w') as f:
        for env_var in env_vars:
            f.write(env_var)
            f.write('=')
            f.write(os.environ[env_var])
            f.write('\n')



with open('/tmp/.env') as f:
    cached_env_vars = f.read()
      
cached_env_vars_dict = ast.literal_eval(cached_env_vars)

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': cached_env_vars_dict['DB_NAME'],
        'USER': cached_env_vars_dict['DB_USER'],
        'PASSWORD': cached_env_vars_dict['DB_PASS'],
        'HOST': cached_env_vars_dict['DB_SERVICE'],
        'PORT': cached_env_vars_dict['DB_PORT']
    }

    #...
}

我認為上述任何一種方法都更好,但肯定會確保環境變量在環境變化和容器重啟時的一致性。

shell 中的值優先於 .env 文件中指定的值。

如果您在 shell 中將 TAG 設置為不同的值,則圖像中的替換將使用該值:

export TAG=v2.0

docker compose convert

 
version: '3'
services:
  web:
    image: 'webapp:v1.5'

更多詳情請參考鏈接: https ://docs.docker.com/compose/environment-variables/

暫無
暫無

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

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