Таймаут загрузки s3 на докеризованной установке Digital Ocean
У меня есть хранилище, совместимое с S3, и сервер Droplet на Digital Ocean. Докеризованное приложение Django, которое я запускаю, пытается синхронизировать статические активы с хранилищем. Это не удается с сервера Droplet / контейнера Docker, но не при доступе к тому же хранилищу S3 из моей локальной установки. Я также могу протестировать загрузку прямо с сервера (вне докеризованного приложения), и это тоже работает. Значит, что-то в настройках Docker приводит к тому, что запросы к S3 не работают.
Я сделал простой тестовый пример, в s3upload.py
с foobar.txt
, присутствующим в той же директории:
from boto3.s3.transfer import S3Transfer
import boto3
import logging
logging.getLogger().setLevel(logging.DEBUG)
client = boto3.client('s3',
aws_access_key_id="…",
aws_secret_access_key="…",
region_name="ams3",
endpoint_url="https://ams3.digitaloceanspaces.com")
transfer = S3Transfer(client)
bucket_name = "…"
transfer.upload_file("foobar.txt", bucket_name, "foobar.txt")
При вызове этой функции из докер-контейнера я вижу следующую ошибку:
Traceback (most recent call last):
File "/usr/local/lib/python3.13/site-packages/boto3/s3/transfer.py", line 372, in upload_file
future.result()
~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.13/site-packages/s3transfer/futures.py", line 103, in result
return self._coordinator.result()
~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.13/site-packages/s3transfer/futures.py", line 264, in result
raise self._exception
File "/usr/local/lib/python3.13/site-packages/s3transfer/tasks.py", line 135, in __call__
return self._execute_main(kwargs)
~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/s3transfer/tasks.py", line 158, in _execute_main
return_value = self._main(**kwargs)
File "/usr/local/lib/python3.13/site-packages/s3transfer/upload.py", line 796, in _main
client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args)
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/botocore/client.py", line 569, in _api_call
return self._make_api_call(operation_name, kwargs)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/botocore/client.py", line 1023, in _make_api_call
raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (RequestTimeout) when calling the PutObject operation (reached max retries: 4): None
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/app/s3upload.py", line 14, in <module>
transfer.upload_file("foobar.txt", bucket_name, "foobar.txt")
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/site-packages/boto3/s3/transfer.py", line 378, in upload_file
raise S3UploadFailedError(
...<3 lines>...
)
boto3.exceptions.S3UploadFailedError: Failed to upload foobar.txt to [bucketname]/foobar.txt: An error occurred (RequestTimeout) when calling the PutObject operation (reached max retries: 4): None
Сначала я подумал о возможных настройках брандмауэра службы, но вызов извне контейнера docker (на сервере дроплета) работает. Это также подтверждает, что учетные данные и код загрузки как таковые работают.
Поскольку это кажется связанным с Docker, а не с Digital Ocean Droplet или S3, вот моя настройка docker:
Простой докер compose.yml
для приложения выглядит примерно так:
services:
db:
image: postgres:17
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
env_file:
- .env
django-web:
build: .
container_name: django-docker
ports:
- "80:80"
expose:
- "80"
depends_on:
- db
environment:
SECRET_KEY: ${SECRET_KEY}
DEBUG: ${DEBUG}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DATABASE_HOST: ${DATABASE_HOST}
DATABASE_PORT: ${DATABASE_PORT}
env_file:
- .env
volumes:
postgres_data:
И Dockerfile
:
# Stage 1: Build
# Use the official Python runtime image
FROM python:3.13-slim AS builder
# Create the app directory
RUN mkdir /app
# Set the working directory inside the container
WORKDIR /app
# Set environment variables
# Prevents Python from writing pyc files to disk
ENV PYTHONDONTWRITEBYTECODE=1
#Prevents Python from buffering stdout and stderr
ENV PYTHONUNBUFFERED=1
# Upgrade pip
RUN pip install --upgrade pip
# Copy the Django project and install dependencies
COPY requirements.txt /app/
# run this command to install all dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Stage 2: Run production code
FROM python:3.13-slim
RUN useradd -m -r appuser && mkdir /app && chown -R appuser /app
# Copy the Python dependencies from the builder stage
COPY --from=builder /usr/local/lib/python3.13/site-packages/ /usr/local/lib/python3.13/site-packages/
COPY --from=builder /usr/local/bin/ /usr/local/bin/
# Set the working directory
WORKDIR /app
# Copy application code
COPY --chown=appuser:appuser . .
# Set environment variables to optimize Python
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Switch to non-root user
USER appuser
# Expose the Django port
EXPOSE 80
# # Migrate the DB
# RUN ["python", "manage.py", "migrate"]
# Gather static assets
# ---> This is the s3 command not working
# RUN ["python", "manage.py", "collectstatic", "--no-input"]
# Run the app via the gunicorn server
CMD ["gunicorn", "--bind", "0.0.0.0:80", "--workers", "3", "fonts.wsgi"]
# python manage.py migrate && python manage.py collectstatic --no-input && gunicorn myapp.wsgi
Итак, подведем итоги:
В моей локальной тестовой среде:
python s3upload.py
работаетdocker compose exec -T django-web python s3upload.py
работает
На моем дроплет сервере Digital Ocean:
python s3upload.py
работаетdocker compose exec -T django-web python s3upload.py
завершается с вышеуказанной ошибкой
Сообщение об ошибке также очень бесполезно. Я видел и другие проблемы с ошибкой boto3.exceptions.S3UploadFailedError: Failed to upload [filename] to [bucketname]/[filename]: An error occurred (RequestTimeout) when calling the PutObject operation (reached max retries: 4):
, но последняя None
деталь ошибки немного раздражает.
Есть ли какие-либо подсказки, как отладить это дальше или что может быть причиной этого?
Столкнулись с той же проблемой.
Попробуйте понизить зависимость boto3 до версии 1.35.99.