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

时间:2019-07-10 12:37:06

标签: django docker docker-compose django-storage

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

app是一个使用django-storages和S3后端的Django应用。 storage是微型服务器(与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从storage访问app,但是从浏览器192.168.65.2:9000给出超时。

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

时动态分配它的方法。

1 个答案:

答案 0 :(得分:1)

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

实际解决方案

花了很多时间使其只能与docker一起使用(见下文)后,我最终走上了实际道路,并将其修复在Django方面。

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

我必须修补的三种情况:

  • 签名的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主机的 ),并使用该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

settings.py

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

在调用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)

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

问题:

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

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