本文是对自己博客项目虚拟化部署思路的设计与实践记录。希望用容器编排部署给大家带来灵感的多服务应用。项目地址为:项目地址:python/fastapi+golang/gin+Vue+docker基于异步技术栈的个人博客系统同时,博客项目上有几篇介绍文章供大家参考:Python-FastAPI使用asyncio生态系统开发异步博客(一)数据Python-FastAPI异步生态开发基于asyncio异步博客(二)通信逻辑Python-FastAPI异步框架开发博客系统(三)——异步特性虚拟化思路容器虚拟化编排需求有很多要考虑的问题。大家可以参考《Kubernetes in Action》这本书,看看最主流的kubernetes是如何精简地讨论容器编排问题的。需要考虑的基本配置,网络(通信),存储是主要的方面。这次使用docker-compose进行编排也主要解决了这三部分的问题。容器:是指通过linux-namespace、cgroups、AUFS、虚拟网络等技术实现的一个独立隔离的运行环境。它具有与虚拟机相同的效果,但体积更轻,更易于部署。Docker是目前主流的容器工具之一。容器编排:是指在集群上调度容器生命周期的工具。负责统筹所有容器的网络、存储、配置、通信、资源分配、节点分配、安全机制等。kubernetes(k8s)是最流行的容器编排工具。docker-compose是docker本身的一个简单的编排工具,适用于小型项目和测试环境。服务编排设计的第一步是考虑服务的拆分。在云服务时代,我们提倡我们的服务不要太耦合,尽量做到轻量级,一个服务专一。Frodo的服务大致分为以下五个部分:nginx:通用反向代理,负责api转发和静态文件转发。mysql:持久化数据redis:缓存和部分持久化python_web:使用fastapi实现的前端API,主要返回html(模板)golang_web:使用Gin实现的后台API,主要负责内容管理划分服务后,必须考虑关系它们之间可以从配置、网络(通信)和存储进行扩展。想清楚它们的依赖关系对于布局文件的正确性非常重要。但是在编写编排文件之前,我们需要获取各个服务模块的镜像(images),这是服务(容器)启动的基础。前三个工具的镜像可以从各个hub获取,后两个镜像的制作细节将在下一节介绍。用户服务DockerfileDockerfile的语法可以在docker官网找到详细的指导。一次写对往往是不可能的。大致流程是:写入dockerfile->测试构建->构建成功->尝试启动容器->启动失败->返回修改Dockerfile。Dockerfile的写法也直接影响到镜像的质量(稳定性和体积等)。先看python服务的镜像:##使用fastapi团队提供的python镜像环境,有利于直接解决底层依赖FROMtiangolo/uvicorn-gunicorn-fastapi:python3.7ASbuilds##pip安装依赖库,可以使用加速源WORKDIR/installCOPYrequirements.txt/requirements.txtRUNpipinstall-r/requirements.txt-ihttps://pypi.tuna.tsinghua.edu.cn/simple\&&mkdir-p/install/lib/python3.7/site-packages\&&cp-rp/usr/local/lib/python3.7/site-packages/install/lib/python3.7##多阶段构建,即有利于减小镜像大小FROMtiangolo/uvicorn-gunicorn-fastapi:python3.7COPY--from=builds/install/lib/usr/local/libWORKDIR/appCOPY./app需要说明的地方,tiangolo/uvicorn-的镜像gunicorn-fastapi:python3.7指的是fastapi团队提供的全栈web模板镜像。项目地址在tiangolo/full-stack-fastapi-postgresql。拉取他们的镜像的好处是这个镜像本身解决了很多依赖问题。比如一些pip库可能需要安装gcc等依赖。直接使用python裸机镜像debug需要很多时间。其次,多阶段构建请参考附件文章。它的主要作用是减少构建时间,减小图像尺寸,解决异构构建,非常有用。我们看golang的镜像FROMgolang:alpineasbuilder##设置环境变量使用proxy加速下载,MODULE模式ENVGOPROXY="https://goproxy.io"ENVGO111MODULE="on"WORKDIR/appCOPY。/appRUNgobuild-oadmin./admin.go##还是用multi-stagebuildfromalpine:latestWORKDIR/root/COPY--from=builder/app.ENTRYPOINT["./admin"]Golang的镜像是我用的调整时间最长。让我得到一个奇怪的知识,都是编译型语言,golang和c++的编译和连接差别太大,所以我们不能直接运行倒数第二行的编译文件,需要复制它依赖文档的配置。(PS:这一点在golang的编译机制中会进一步研究)配置、网络、存储关系镜像测试无误后,就可以编写编排文件docker-compose.yml了。官网的文档很详细,具体到Frodo来说一般来说,搞清楚这五个部分的依赖关系很重要。从配置、网络和存储的角度来看:网络通信结构如上图所示。需要说明的是:容器桥接网络是指容器之间使用内部端口和主机进行通信,并且相互之间是可见的。与外界的通信依赖于端口节点最终归纳到主机的eth0网络设备的转发。在这里,Nginx作为与外界通信的唯一入口。桥接网络只是一种选择,其他形式的网络拓扑也是可能的。Volumemount是容器经常使用的特性,可以将容器数据映射到宿主机。这里把mysql和redis的数据映射到宿主机上的一个数据卷上,这样即使容器消失了,数据依然存在。static静态文件由nginx单独提取代理,需要修改python_web和golang_web的配置文件。那么上述关系是如何实现的呢?主要看各个服务的配置(python_web、goadmin的config.model.ini和nginx.conf)和docker-compose.yml的配置。先看docker-compose.ymlversion:'3'services:db:image:mysqlrestart:alwaysenvironment:MYSQL_DATABASE:'fast_blog'MYSQL_USER:'root'MYSQL_PASSWORD:''MYSQL_ROOT_PASSWORD:''MYSQL_ALLOW_EMPTY_PASSWORD:'true'ports:-'3308:3306'volumes:-my-datavolume:/var/lib/mysqlnetworks:-app-networkredis:image:redis:alpinenetworks:-app-networkports:-'6378:6379'frodo_python:image:frodo/pyweb:latestnetworks:-app-networkports:-'9004:9004'expose:-'9004'volumes:##为了方便调试,生产环境可删除-./python_web:/appdepends_on:-db-redis环境:PYTHONPATH:$PYTHONPATH:/usr/local/src命令:'uvicornmain:app--host0.0.0.0--port9004'frodo_golang:image:frodo/gowebports:-'9003:9003'expose:-'9003'depends_on:-db-redisworking_dir:/root命令:sh-c'./admin'networks:-app-networknginx:image:nginxworking_dir:/data/staticvolumes:##映射配置文件和静态文件-./nginx.conf:/etc/nginx/nginx.conf:ro-./static:/data/staticports:-"9080:9080"networks:-app-networkdepends_on:-frodo_python-frodo_golangvolumes:my-datavolume:networks:app-network:driver:bridge有很多东西要解释,except需要注意的是最重要的是[depends]的配置,它指定了依赖关系,体现在各个服务的启动顺序上。在nginx的配置文件中,需要将服务分发给python和golang,它们的初始化依赖于mysql和redis的服务地址,所以启动顺序很重要。让我们看一下服务应用程序的配置。python和golang区别不大:[global]host_path=localhost[database]host=dbusername=rootpassword=port=3306db=fast_blogcharset=utf8[redis]host=redisredis_url=redis:6379port=6379[port]golang=9003fastapi=9004[server]python=frodo_pythongolang=frodo_golang需要说明如下:redis和mysql主机使用docker-compose.yml中指定的主机服务名。这个可以使用docker-composeps--service查看,这个host一定是用来发现对方的。端口号均使用容器桥接网络的内部端口号。pythongolang服务地址也随之变化。最后由nginx配置文件确定转发服务的地址:server{listen9080;位置/{proxy_passhttp://frodo_python:9004;}位置/静态{根/数据;}location/api{proxy_passhttp://frodo_golang:9003;}location/auth{proxy_passhttp://frodo_golang:9003;}location/api/status{proxy_passhttp://frodo_python:9004;}}我们的应用程序使用9080作为访问的唯一入口点。我们注意到nginx分配的地址都是容器桥接网络中的服务名,端口也是容器端口,所以nginx必须在五个服务的最后,启动后才能找到所有服务,否则报错启动时会报告。当你看到上图时,证明服务已经启动,但不代表通信、存储、配置完全正确。调试的路还很长,有时甚至需要在个别容器内部排查原因。或者修改源码获取更多日志。最后需要说明的是,docker-compose最好只用于测试,而kubernetes是更全面和标准化的工具,也是大型系统最流行的选择。希望本文能给大家带来实践部署多服务容器编排应用的启发~
