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

With docker compose, how do I access a service internally and externally using the same address?

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

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

app,我可以使用 http://storage:9000. From outside docker, I can access storage at http://localhost:9000, or http://0.0.0.0:9000, or even at http://192.168.xxx.yyy(使用网络上的不同设备)访问 storage。没有惊喜。

但是,当生成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:

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

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

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

附加信息:

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

但似乎使用我的计算机外部 ip 是可行的。如果我使用 192.168.3.177:9000,我可以从 app、浏览器甚至外部设备访问 storage(完美!)。但是,这个 ip 不是固定的,对于我的同事来说显然不一样,所以我似乎只需要一种在执行 docker-compose up

时动态分配它的方法

已经有一段时间了,但我想我会分享我是如何针对我的情况最终解决这个问题的,如果有人遇到过类似的问题。 Relevant XKCD

实用解法

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

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

我必须打补丁的三种情况:

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

我想使用当前请求的主机作为对象链接的主机(但在 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

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

备选方案

我很快发现要在 docker 端修复它,我真正需要的是获取主机的 IP 地址( 而不是 docker 主机)并使用它来创建指向我的 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

settings.py

    AWS_S3_ENDPOINT_URL = f'http://{os.environ['HOST_IP']}:9000'

如果在调用 docker-compose up 时设置了环境变量 DOCKER_HOST_IP,这将创建使用该 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)

对于 PyCharm(对调试非常有用)设置有点棘手,因为默认环境变量不能是动态的。但是,您可以定义一个为 'run configuration' 运行 'Before launch' 的脚本。我创建了一个以与 .bash_profile 相同的方式设置环境变量的命令,并且奇迹般地似乎 PyCharm 在 运行 docker-compose 命令时保持该环境,使它以我想要的方式工作。

问题:

  • 当 ip 更改时需要重新启动容器(wifi off/on,sleep/wake 在不同的位置,以太网拔掉)
  • 需要环境中的动态值,正确设置很挑剔
  • 未连接到网络(没有以太网和 wifi)时不起作用
  • 不能使用localhost,需要使用当前ip地址
  • 仅适用于一个 IP 地址(因此使用以太网和 wifi 时需要选择一个)

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