我试图从部署用户桌面应用程序的角度了解Docker。我的应用程序只是一个Flask Web应用程序和Mongo数据库。通常,我会在虚拟机中安装它们,并将主机端口转发到客户机Web应用程序上。 我想尝试使用Docker,但我不确定如何使用多个程序。文档说只能有一个ENTRYPOINT,那么我该如何同时运行Mongo和我的Flask应用程序呢?还是它们需要在单独的容器中,那么它们如何相互通信,这样做如何轻松分发应用程序?
我试图从部署用户桌面应用程序的角度了解Docker。我的应用程序只是一个Flask Web应用程序和Mongo数据库。通常,我会在虚拟机中安装它们,并将主机端口转发到客户机Web应用程序上。 我想尝试使用Docker,但我不确定如何使用多个程序。文档说只能有一个ENTRYPOINT,那么我该如何同时运行Mongo和我的Flask应用程序呢?还是它们需要在单独的容器中,那么它们如何相互通信,这样做如何轻松分发应用程序?
只能有一个ENTRYPOINT,但通常该目标是启动所需程序的脚本。您还可以使用例如Supervisord或类似工具来负责在单个容器内启动多个服务。 这是一个运行mysql、apache和wordpress的Docker容器的示例。
假设您有一个数据库仅由一个Web应用程序使用,则将它们都放在单个容器中可能更容易。
如果您有一个被多个应用程序共享的数据库,则最好将数据库放在自己的容器中,每个应用程序都在自己的容器中。
当应用程序在不同的容器中运行时,至少有两种可能性可以使它们彼此通信:
我强烈反对之前一些解决方案的建议,即在同一个容器中运行两个服务。文档中明确指出这不是推荐做法:
通常建议使用每个容器一个服务来分离关注点。该服务可以分叉成多个进程(例如,Apache Web服务器启动多个工作进程)。拥有多个进程是可以的,但为了从Docker中获得最大的好处,避免一个容器负责您整个应用程序的多个方面。您可以使用用户定义的网络和共享卷连接多个容器。
supervisord或类似程序有很好的用例,但运行Web应用程序+数据库不属于其中之一。
您应该绝对使用docker-compose来完成这项任务,并编排具有不同职责的多个容器。
由于在这一点上存在明显的误解,我将进行更全面的解释。
为了达到这个目的,我将使用一个更复杂的部署示例,因为 Flask 应用程序 + MongoDB 数据库有点太简单了,无法展示现代 Web 应用程序开发中的真正复杂性。让我们想象一下我们有:所以我们有 6 种类型的服务。这种部署方式在现代 Web 开发中并不新鲜或特别,因为大多数真实世界的专业项目往往至少具有这种复杂性(通常更多)。
Docker 允许我们在这里做什么,以及文档中明确说明的推荐使用方式,是将这 6 个服务中的每一个分别放入独立的容器中。
以下是我们更喜欢这样做的原因:
每个服务都有自己的依赖关系。至少,它们都直接或间接地依赖于系统的libc,即使这种依赖有时也会出现问题(某些程序被打包使用较新版本的C库)。它们还肯定会有其他与系统相关的依赖项。这可以是低级别的C库,如openssl、libxml、libevent等,也���以是更高级别的工具,如Python、nodejs、ruby等。而且,每个服务只支持每个依赖项的一定范围版本。
虽然您“可以”尝试在单个容器中安装每个服务,但这将比您想象的更麻烦。您可能必须依赖于镜像的基本发行版软件包管理器,这意味着对每个服务强制执行特定版本。例如,您无法选择在给定Ubuntu版本上默认安装的postgresql或redis版本。您还可以通过使用第三方存储库安装所需的特定版本,但在大多数Linux / Unix系统上,这通常会遇到依赖项不兼容性问题。
很不幸,现在被固定到特定版本的这些服务并不是一个令人满意的解决方案。也许您想使用更新版本的Postgresql版本,因为该版本中有新功能,您想在Python程序中使用它们。或者由于openssl实现中存在安全问题需要快速升级,您可能需要升级Caddy实例。
试图将您使用的每个服务的特定版本放入单个容器中,迟早会成为与在任何Linux机器上尝试一起安装这些服务相同的地狱。
Docker允许通过允许每个服务都拥有其自己的依赖关系,彼此完全独立,来解决这个问题。更改任何这些服务之一的版本通常只需要更改docker-compose.yml
文件中的一行配置。这是巨大的优势和使用Docker的许多原因之一。
假设你已经成功部署了这6个服务,现在你的Web项目正在运行。现在你有一个新问题:真实用户正在使用你的应用程序。这些真实用户使用资源,包括CPU、RAM、磁盘IO、网络IO、存储空间等,所有这些都受到部署机器的限制。你现在需要能够扩展你的应用程序。
要想扩展应用程序,你不能只是复制整个堆栈。对于Python flask应用程序,你可能可以横向扩展(如果它是无状态的,就像应该的那样),但MongoDB实例的扩展完全不同,并需要特定的配置。如果你扩展了Python应用程序,你可能需要适应你的caddy实例来实现负载均衡。你可能不会扩展你的redis实例,因为它是一个内存缓存服务器。也许你只需要扩展后台Python应用程序处理消息队列中的消息,因为你发现这是应用程序中最消耗CPU的部分。等等...
Docker通过将每个服务定义为单独的容器,使得这种扩展和配置适应更加容易。在最好的情况下(不是全部情况但很多情况下),您需要做的唯一适应是调整Docker配置,而不会对镜像产生任何影响。在多台计算机上扩展部署也将大大简化,可能需要使用更复杂的编排工具(如Kubernetes),因为使用docker-compose存在一定的限制,但至少您将有解决方案。
我们这里有一个数据库,我们的MongoDB实例。像所有数据库一样,它需要特殊的考虑因素,这些考虑因素在无状态服务的背景下不存在。我们说的是数据持久性、备份、档案、数据迁移...这些小事情并不那么容易处理,但即使是最简单的实际用例也是100%必要的。
为了处理这些考虑因素,在Docker出现之前的旧日常规是强制规定数据库位于运行应用程序代码的不同计算机上。这种做法即使在Docker可用的情况下仍然存在,因为仍然有充分的理由完全独立地存储数据。
但是对于那些想在数据库中使用Docker的人来说,即使只是在开发阶段,将它们系统地放在独立的容器中仍然是最好的选择。这将允许以明显独立的方式管理数据库的生命周期和存储,避免与其他任何东西产生任何不必要且可能不需要的交互。
您还可以选择在开发中使用Docker部署数据库,以便于使用,但在生产环境中使用像AWS RDS这样的东西(因此不使用Docker)。这也可以轻松完成,只需使用不同的配置,而不会对其他服务产生任何副作用。
在学习如何使用Docker时,有两个不同的方面必须掌握:
Dockerfile
文件,以及关于sh/bash脚本的许多知识。docker-compose
,因为它可能是最简单的工具,但Kubernetes似乎越来越多地用于此(它也更复杂)。这些技能有点分离,但也紧密交织在一起。你需要知道Docker容器的工作原理才能编排它们,而为此你需要能够创建Docker镜像。但是要创建良好的Docker镜像,你还需要了解它们的最终目标,即能够轻松地将它们编排到多个容器中。
因此,鉴于最初的问题,给出的最佳建议是不要尝试为Python flask应用程序+MongoDB数据库创建单个镜像。最好的建议是创建一个仅包含Python flask应用程序的Docker镜像,并创建一个docker-compose.yml
文件,其中包含Python flask应用程序和MongoDB数据库(重用官方MongoDB镜像之一)。
当然,这将需要学习使用docker-compose,学习配置官方MongoDB镜像,并可能对Python flask应用程序进行一些适应,以通过环境变量注入一些配置参数。但是这些必要性远非浪费时间。这些是使用Docker的“正确”方式的完整部分,以实现其真正的有用性。
我有一个类似的需求,需要运行 LAMP 堆栈、Mongo DB 和我的自定义服务。
Docker 是基于操作系统的虚拟化技术,因此它会将容器与正在运行的进程隔离开来,因此至少需要一个前台进程在运行中。
因此,您需要提供自己的启动脚本作为入口点,这样您的启动脚本就成为了扩展的 Docker 镜像脚本,您可以在其中堆叠任意数量的服务,只要至少有一个前台服务已启动,而且放在最后。
因此,我的 Docker 镜像文件在最后面有以下两行:
COPY myStartupScript.sh /usr/local/myscripts/myStartupScript.sh
CMD ["/bin/bash", "/usr/local/myscripts/myStartupScript.sh"]
我的脚本中运行了MySQL、MongoDB、Tomcat等等。最后我将Apache作为前台线程运行。
source /etc/apache2/envvars
/usr/sbin/apache2 -DFOREGROUND
这使我能够启动所有的服务并保持容器处于活动状态,最后启动的服务将在前台运行。
希望对您有所帮助。
更新:自我上次回答这个问题以来,出现了新的东西,比如Docker Compose,它可以帮助您将每个服务都运行在自己的容器中,并将它们绑定在一起作为这些服务之间的依赖关系,请尝试了解更多关于docker-compose的内容并使用它,除非您的需求与之不符。
虽然不建议这样做,但是可以使用wait
来在前台运行两个进程。只需创建一个包含以下内容的bash脚本,例如start.sh
:
# runs 2 commands simultaneously:
mongod & # your first application
P1=$!
python script.py & # your second application
P2=$!
wait $P1 $P2
在你的Dockerfile中,使用以下内容开始
CMD bash start.sh
如果您想同时运行多个进程,我建议设置本地Kubernetes集群。您可以通过提供一个简单的Kubernetes清单文件来"分发"应用程序。
对于一个更简单、小型的安装,所有依赖项都在同一个容器中,由最初被调用为ENTRYPOINT的程序启动数据库和Python运行时也是可能的。这可以是一个简单的shell脚本,或者其他进程控制器 - Supervisord非常流行,在公共Dockerfiles中存在许多示例。
CMD
运行。这是一个非常朴素的例子。首先,包装脚本:#!/bin/bash
# Start the first process
./my_first_process -D
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start my_first_process: $status"
exit $status
fi
# Start the second process
./my_second_process -D
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start my_second_process: $status"
exit $status
fi
# Naive check runs checks once a minute to see if either of the processes exited.
# This illustrates part of the heavy lifting you need to do if you want to run
# more than one service in a container. The container will exit with an error
# if it detects that either of the processes has exited.
# Otherwise it will loop forever, waking up every 60 seconds
while /bin/true; do
ps aux |grep my_first_process |grep -q -v grep
PROCESS_1_STATUS=$?
ps aux |grep my_second_process |grep -q -v grep
PROCESS_2_STATUS=$?
# If the greps above find anything, they will exit with 0 status
# If they are not both 0, then something is wrong
if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 ]; then
echo "One of the processes has already exited."
exit -1
fi
sleep 60
done
FROM ubuntu:latest
COPY my_first_process my_first_process
COPY my_second_process my_second_process
COPY my_wrapper_script.sh my_wrapper_script.sh
CMD ./my_wrapper_script.sh
例如,在Hipache中,包含的Dockerfile运行supervisord,文件supervisord.conf指定hipache和redis-server都要运行。
sh -c
显式地生成单独的进程。例如:CMD sh -c 'mini_httpd -C /my/config -D &' \
&& ./content_computing_loop
CMD executable_first arg1; executable_second arg1 arg2
ENTRYPOINT executable_first arg1; executable_second arg1 arg2
该版本将创建一个shell并执行上述命令。您可以使用任何shell语法,例如“;”、“&”、“|”等。因此,您可以在此运行任意数量的命令。如果您有一组复杂的命令需要运行,则可以创建单独的shell脚本并使用它。
CMD my_script.sh arg1
ENTRYPOINT my_script.sh arg1
CMD ["executable", "parameter 1", "parameter 2", …]
ENTRYPOINT ["executable", "parameter 1", "parameter 2", …]
在这里,您可以注意到只有第一个参数是可执行文件。从第二个参数开始,一切都成为该可执行文件的参数。
要以EXEC格式运行多个命令
CMD ["/bin/sh", "-c", "executable_first arg1; executable_second"]
CMD ["/bin/sh", "-c", "executable_first arg1; executable_second"]
CMD ["executable_first parameter", "executable_second parameter"]
ENTRYPOINT ["executable_first parameter", "executable_second parameter"]
CMD ["executable_first", "parameter", ";", "executable_second", "parameter"]
ENTRYPOINT ["executable_first", "parameter", ";", "executable_second", "parameter"]
以下是与上面相同的答案。但是带有详细信息和推荐的解决方案。如果您对此感兴趣。
警告。尽管如此,使用相同的容器来运行多个服务并不受 Docker 社区的推荐。Docker 文档中写道:“通常建议您通过每个容器使用一项服务来分隔关注点。”源网址为:
• https://archive.ph/3Roa6#selection-307.2-307.100
• https://docs.docker.com/config/containers/multi-service_container/
如果您选择忽略上述建议,则您的容器可能会面临更弱的安全性、更不稳定以及未来的痛苦增长。
如果您能够接受以上风险,则使用一个容器来运行多个服务的文档位于:
• https://archive.ph/3Roa6#selection-335.0-691.1
• https://docs.docker.com/config/containers/multi-service_container/
如果您需要更强的安全性、更稳定的容器以及未来更大规模和更好的性能,则Docker社区建议采取以下两个步骤:
每个Docker容器只使用一个服务。最终结果是您将拥有多个容器。
使用Docker "Networking"功能,按照您的喜好连接任何这些容器。