使用 docker compose,如何使用相同的地址在内部和外部访问服务?

rob*_*bin 4 django django-storage docker docker-compose

我的问题归结为:我在 docker compose:app和中有两个服务storage我正在寻找一种使用相同地址从应用程序内部外部访问storage服务(端口 9000)的方法。

app是一个使用 django-storages 和 S3 后端的 Django 应用程序。 storage是一个minio服务器(S3兼容,仅用于开发)。

app,我可以storage使用http://storage:9000进行访问。从外部 docker,我可以访问storagehttp ://localhost:9000http://0.0.0.0:9000,甚至可以访问http://192.168.xxx.yyy(使用网络上的不同设备)。那里没有什么惊喜。

但是,当生成 URL 时,我不知道它是要在内部使用还是在外部使用(或两者兼而有之)。

docker-compose.yml

services:

  app:
    build: backend/
    ports:
      - "8000:8000"
    volumes:
      - ./backend/src:/app/src
    command: /usr/local/bin/python src/manage.py runserver 0.0.0.0:8000

  storage:
    image: minio/minio:RELEASE.2019-06-19T18-24-42Z
    volumes:
      - storage:/data
    environment:
      MINIO_ACCESS_KEY: "DevelopmentAccessKey"
      MINIO_SECRET_KEY: "DevelopmentSecretKey"
    ports:
      - "9000:9000"
    command: minio server /data

volumes:
  storage:

Run Code Online (Sandbox Code Playgroud)

我已经研究过更改后端以根据上下文生成端点 url,但这远非微不足道(并且仅用于开发,生产使用外部 S3 存储,我希望使它们尽可能相似)。

我已经尝试过 docker-compose 网络配置,但我似乎无法完成这项工作。

关于如何在 docker-compose 中解决这个问题有什么想法吗?

附加信息:

我已经尝试过host.docker.internal(和gateway.docker.internal)但无济于事。host.docker.internal解析为192.168.65.2,我可以使用该 IP 进行访问storageapp但从浏览器192.168.65.2:9000会超时。

但看来使用我的电脑的外部IP是可行的。如果我使用,192.168.3.177:9000我可以storageapp浏览器甚至外部设备进行访问(完美!)。然而,这个ip不是固定的,而且对于我的同事来说显然不一样,所以看来我需要的是一种在执行时动态分配它的方法docker-compose up

rob*_*bin 7

已经有一段时间了,但我想如果有人遇到类似的问题,我想分享一下我最终如何根据我的情况解决这个问题。相关XKCD

实用的解决方案

在花了相当长的时间让它只与 docker 一起工作(见下文)之后,我最终走上了实用的道路,并在 Django 方面修复了它。

由于我使用 Django Rest Framework 公开存储中对象的 url,因此我必须修补 Django Storages S3 后端创建的对象 url 的默认输出,以便在本地开发时交换主机。在内部,Django 使用 API 密钥直接连接到对象存储,但在外部,文件只能通过签名的 URL(私有存储桶)访问。并且由于主机名可以是签名内容的一部分,因此需要在生成签名之前正确设置它(否则主机名的脏查找和替换就足够了。)

我必须修补的三种情况:

  • 签名网址(用于在浏览器中查看)
  • 签名的下载网址(提供下载按钮)
  • 预先签名的帖子网址(用于上传)

我想使用当前请求的主机作为对象链接的主机(但在 Minio 的端口 9000 上)。这样做的优点是:

  • 适用于localhost127.0.0.1、 以及分配给我的机器的任何 IP 地址。因此,我可以在我的机器上使用localhost并使用192.168.x.x手机上的地址进行测试,而无需更改代码。
  • 无需针对不同开发者进行设置
  • ip更改时不需要重新启动容器

上述情况实施如下:

# dev settings, should be read from env for production etc.

AWS_S3_ENDPOINT_URL = 'http://storage:9000'
AWS_S3_DEV_ENDPOINT_URL = 'http://{}:9000'

def get_client_for_presigned_url(request=None):
    # specific client for presigned urls
    endpoint_url = settings.AWS_S3_ENDPOINT_URL

    if request and settings.DEBUG and settings.AWS_S3_DEV_ENDPOINT_URL:
        endpoint_url = settings.AWS_S3_DEV_ENDPOINT_URL.format(request.META.get('SERVER_NAME', 'localhost'))

    storage = S3Boto3Storage(
        endpoint_url=endpoint_url,
        access_key=settings.AWS_ACCESS_KEY_ID,
        secret_key=settings.AWS_SECRET_ACCESS_KEY,
    )
    return storage.connection.meta.client

class DownloadUrlField(serializers.ReadOnlyField):
    # example usage as pre-signed download url
    def to_representation(self, obj):
        url = get_client_for_presigned_url(self.context.get('request')).generate_presigned_url(
            "get_object",
            Params={
                "Bucket": settings.AWS_STORAGE_BUCKET_NAME,
                "Key": str(obj.file_object),  # file_object is key for object store
                "ResponseContentDisposition": f'filename="{obj.name}"', # name is user readable filename
            },
            ExpiresIn=3600,
        )
        return url

# similar for normal url and pre-signed post

Run Code Online (Sandbox Code Playgroud)

这为我和其他开发人员提供了一个易于使用、本地、离线可用的开发对象存储,而只需少量签入代码。

替代解决方案

我很快发现,要在 docker 端修复它,我真正需要的是获取主机(而不是docker 主机)的 IP 地址,并使用它来创建到我的 Minio 存储的链接。正如我在问题中提到的,这与地址不同docker.host.internal

解决方案:使用env变量传入主机ip。

docker-compose.yml

services:
 
  app:
    build: backend/
    ports:
      - "8000:8000"
    environment:
      HOST_IP: $DOCKER_HOST_IP
    volumes:
      - ./backend/src:/app/src
    command: /usr/local/bin/python src/manage.py runserver 0.0.0.0:8000

    # ... same as in question
Run Code Online (Sandbox Code Playgroud)

设置.py

    AWS_S3_ENDPOINT_URL = f'http://{os.environ['HOST_IP']}:9000'
Run Code Online (Sandbox Code Playgroud)

DOCKER_HOST_IP当调用此方法时设置环境变量时docker-compose up,将创建使用该 IP 并正确签名的 url。获取环境变量到docker-compose的几种方法:

  • 将其设置在.bash_profile
  • -e将其传递给带有标志的命令
  • 在 PyCharm 中设置

因为.bash_profile我使用了以下快捷方式:

alias myip='ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\  -f2'
export DOCKER_HOST_IP=$(myip)
Run Code Online (Sandbox Code Playgroud)

对于 PyCharm(对于调试非常有用)来说,设置有点棘手,因为默认环境变量不能是动态的。但是,您可以为“运行配置”定义一个在“启动前”运行的脚本。我创建了一个命令,以与中相同的方式设置环境变量,.bash_profile并且奇迹般地,PyCharm 在运行 docker-compose 命令时保留了该环境,使其按照我想要的方式工作。

问题:

  • ip 更改时需要重新启动容器(wifi 关闭/打开、在不同位置睡眠/唤醒、以太网拔出)
  • 需要环境中的动态值,需要正确设置
  • 未连接到网络时不起作用(没有以太网和无线网络)
  • 无法使用localhost,需要使用当前IP地址
  • 仅适用于一个 IP 地址(因此使用以太网和 WiFi 时需要选择一个)

由于这些问题,我最终采用了实际的解决方案。