使用 docker compose,如何使用同一地址在内部和外部访问服务?
With docker compose, how do I access a service internally and externally using the same address?
我的问题归结为:我在 docker compose 中有两个服务:app
和 storage
。我正在寻找一种从应用程序内部 和 使用 相同地址 从外部访问 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 上)。这样做的好处是:
- 适用于
localhost
、127.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 时需要选择一个)
由于这些问题,我最终选择了实用的解决方案。
我的问题归结为:我在 docker compose 中有两个服务:app
和 storage
。我正在寻找一种从应用程序内部 和 使用 相同地址 从外部访问 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 上)。这样做的好处是:
- 适用于
localhost
、127.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 时需要选择一个)
由于这些问题,我最终选择了实用的解决方案。