【问题标题】:With docker compose, how do I access a service internally and externally using the same address?使用 docker compose,如何使用相同的地址在内部和外部访问服务?
【发布时间】:2019-07-10 12:37:06
【问题描述】:

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

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

app,我可以使用http://storage:9000 访问storage。从 docker 外部,我可以通过http://localhost:9000http://0.0.0.0:9000,甚至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时动态分配它的方法@

【问题讨论】:

    标签: django docker docker-compose django-storage


    【解决方案1】:

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

    实用解决方案

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

    由于我使用 Django Rest Framework 来公开存储中对象的 url,因此我必须修补由 Django Storage 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
    
    

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

    替代解决方案

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

    问题:

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

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

    【讨论】:

      猜你喜欢
      • 2015-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多