Docker 案例:为多个服务组成应用构建本地开发环境
在当今非常多的复杂 web 应用运行在容器化产品的时代,我们仍旧保持使用古董开发方法,在本地开发机器上安装 Postgresql , Redis , Ruby 及其它依赖控件。
将使维护开发进程变成非常困难的任务,尤其当基础系统变得异构化、扩展出大量的后台服务且依赖多种多样版本的控件时,特别是依赖的各控件有多个不同版本的变化时,将变得异常困难。
本文,将以我的开发项目 Amplifr 为例,回顾本地开发环境容器化布署方法。主要通过 docker-compose 及其内部的虚拟网络技术的帮助下完成。这种技术简单而高效。
应用开发项目的所有基础服务都进行容器化,在 Kubenetes 产品上布署管理。我们将只学习在单机本地化布署的方法,主要依据配置开发进程便利性的原则。
本地开发环境容器化的益处#
- 无需在本地主机安装所有开发依赖控件,诸如,数据库、语言解释器等,保持本地主机的相对 纯净。
- 自然支持多个开发环境,诸如,在主机上运行不同版本的 Ruby、Postgresql 数据库服务等。
项目概览#
虽然 Amplifr 项目后台运行在 Rails 基础上,项目也具备复杂的前端,由独立的 Node.js 服务、Logux web-socket 服务和其它协助者服务提供,分别由 Node.js、Ruby 和 Golang 开发语言编写。
下图展示了项目基本架构逻辑组成:T
下面就整个系统架构及依赖进行简单回顾。
后端服务#
后端服务是典型的 Rails 应用,完成项目的所有商业逻辑及利用 Sidekiq 完成许多后台优化性能的任务。
前端服务#
前端仅仅是整个项目公共 HTTP 请求的入口。它服务于前端资源分配且代理其它请求并传输到 Rails 后端。
后端服务也与前端整合共享一些数据,诸如, 'browsers.json' 格式的文件,用于正确渲染 HTML 网页。
Logux 服务#
Logux 是对外暴露 web-socket 端口、控制与客户端浏览器双向连接的一项服务。执行项目商业逻辑中,它在后端整合 2 类 HTTP 通讯。它可保证所有商业逻辑运行在 Ralis 后端服务的同时,通过 HTTP 发送来自后端服务的通知到原始的客户。
链接地址缩短服务#
链接地址缩短(link shortener) 是特有的一项 web 服务,由 Golang 开发语言编写。 它的目标是缩短一个链接地址或展开,且管理对缩短链接地址展开的统计。
「预览」服务#
预览服务是公共服务,用于来自客户端浏览器对 OpenGraph 展示的渲染请求。它仅有公共 http 请求端入口。
其它控件#
Shortener (链接缩短)- 是独立的服务,用于缩短 url 和保持对展开的链接地址的分析。它使用 Golang 开发语言编写。它具有公共用于展开缩短的链接地址的外部入口,及与后端运行的控制发布社交内容缩短的链接地址服务的内部入口。
一些其它的内部服务,诸如 telegram 和 facebook 自动机器人程序等,只有与后端整合连接的入口。
服务控件间的依赖关系#
许多服务控件其本身就是一种复合的 web 服务,它可能依赖更底层的系统服务控件。例如,依赖 Postgres 、Redis 等服务。
容器化#
💡我们将利用 Docker Compose 工具整合每一个独立容器化的服务控件。Docker Compose 就是一个配置且同时运行多个 docker 容器,并将多容器整合在一起的易用工具,它只需一个启动(up)命令就可将配置好的相互依赖的多容器全部运行起来,完成整体服务:
docker-compose up
💡使所有的服务容器整合在一起利用了 docker networks(docker 虚拟网络)技术,它使得其配置的所有容器彼此间可以通讯交互。本例子项目中,简单化地将所有容器通过名为 'internal' 的 docker 虚拟网络进行联通。做为睿智的读者您,应可以为每个服务容器或互相联通的服务容器组配置不同的独立 docker 虚拟网络。
Docke 化 Ruby 后端(Backend)#
现在我们有了一个标准的开发栈计划:Postgres,Redis, Rails web 服务和 Sidekiq 后台。所有这些我们在 'docker-compose.yaml' 配置文件中配置。
下面是关键要点:
- 对于 Postgres 和 Redis ,我们将配置 永久卷 存储应用运行中的数据。
- 我们不把 Ruby 源代码拷入容器中,替代方法是 - 挂载保存 Rails 应用源代码目录到
/app
文件夹下。 - 我们也为 bundle 和其它服务配置永久存储,加快之后的启动速度。
- 我们将配置 amplifr_internal 网络,使所有容器可通过这个网络交互。
- 应用项目中会需要配置一些应用使用的环境变量,我们也将在 docker-compose 文件中配置。
- 在 docker-compose.yaml 配置文件中,我们配置了一个基础服务(app),之后,使用 YAML 文件的 锚(Anchors ) 和 别名(aliases) 语法去引用它,而不必要在后续的基于基础服务(app)的服务配置中重复书写其基类配置。
❗请记住本例项目中的配置,不同于为项目创建 docker image(映像)的方法。创建 docker 映像的方法是将所有的源代码和依赖的服务都拷贝或安装在映像里,使构建的 docker 映像(image)是一个自恰系统,而无外部依赖。
'gist with all the config ',这是在 github 上项目的全部配置内容文件,但现在让我们关注一些配置重点:
用于其它服务继承的基类服务(base-service)配置描述#
services:
app: &app
build:
context: .
dockerfile: Dockerfile.dev
args:
PG_VERSION: '9.6'
image: amplifr-dev:0.1.0
volumes:
- .:/app:cached
- bundle:/bundle
environment:
# 环境变量设置(environment settings)
- BUNDLE_PATH=/bundle
- BUNDLE_CONFIG=/app/.bundle/config
- RAILS_ENV=${RAILS_ENV:-development}
- DATABASE_URL=postgresql://postgres@postgres/amplifr_${RAILS_ENV}
- REDIS_URL=redis://redis:6379/
# 前后端服务整合环境变量设置(service integrations)
- FRONTEND_URL=https://frontend-server:3001/
- LOGUX_URL=http://logux-server:31338
depends_on:
- postgres
- redis
tmpfs:
- /tmp
基类服务(base service)容器将按 Dockerfile.dev
配置文件配置创建,其中指定了创建容器对 Postgres 数据库版本的依赖。所有其它 Ruby 类的服务映像都将继承这个基类服务的配置,下面是其继承关系图表:
配置文件中也定义了挂载 /app
目录为这几个相关服务容器的 docker 卷。这防止每次启动时依赖包的重新安装。
配置中还定义了两组项目环境变量:
1) 系统(system) 级别的环境变量。如 BUNDLE_PATH
, REDIS_URL
和 DATABASE_URL
。
2) 整合各依赖服务的访问地址环境变量:FRONTEND_URL
- 后端(backend)对前端(frontend)的内部入口访问地址。LOGUX_URL
- 内部访问 Logux HTTP 服务入口地址,用于从 Rails-app 到 Logux 发送操作任务。
运维(runner)服务配置说明#
runner 服务容器用于执行运维指令任务,诸如,rake 任务,或做为 Rails 环境生成器。它是面向控制台,使用命令行控制的服务。因此,必须建立 stdin_open
和 tty
配置选项,以满足当服务容器启动时通过 -i
和 --t
命令选项保持服务容器可通过 Bash shell 进行交互的需求:
services:
runner:
<<: *app #原文这里为 *backend, 译者认为应是 *app
stdin_open: true
tty: true
command: /bin/bash
可使用如下命令启动它并下达指令
$ docker-compose run runner bundle exec rake db:create
# 或直接运行 (run)容器并在容器中运行任何命令
$ docker-compose run runner
名为 server 的服务容器配置#
定义 web 服务容器。这里的关键点是它定义了 docker 虚拟网络 internal
且加入在这个虚拟网络下的其服务容器别名 backend-server
,这样它在这个网络中将被通过 backend-server
这个网络别名访问到。
services:
server:
<<: *app
command: bundle exec thin start
networks:
default:
internal:
aliases:
- backend-server
ports:
- '3000:3000'
networks:
internal:
Sidekiq 服务容器配置#
简单,它只是运行 sidekiq 且继承自基类服务(base service)容器配置:
services:
sidekiq:
<<: *app
command: sidekiq
Redis 和 Postgres 服务容器配置#
postgres:
image: postgres:9.6
volumes:
- postgres:/var/lib/postgresql/data
ports:
- 5432
redis:
image: redis:3.2-alpine
volumes:
- redis:/data
ports:
- 6379
volumes:
postgres:
redis:
这里配置的重点是挂载容器永久卷的目录路径,保证运行中数据的永久存储。
Dockerfile#
我不想深入讨论 Dockefile
的编写。你可在 这里 找到完全的文件内容。只是注意它来自标准的 ruby 映像(image),并安装上依赖的软件控件,诸如, Postgressql 连接客户端软件及其它捆绑的执行程序等。
使用方法#
使用方法较简单:
$ docker-compose run runner ./bin/setup # 在 docker 中执行 ./bin/setup 命令
$ docker-compose run runner bundle exec rake db:drop # 执行 rake 任务
$ docker-compose up server # 启动 web 服务g
$ docker-compose up -d # 启动所有的服务容器(web,sidekiq)
$ docker-compose up rails db # 启动 postgres 客户端
Docker Compose 的配置执行机制可以指定启动服务的依赖,即当一个服务容器启动时,先要保证其依赖的服务容器已启动。例如, Sidekiq 服务容器的运行需要 Redis 和 Postgres 服务提供服务,这就是我们在服务容器配置段中配置 depends_on
节的缘故。
下面是服务依赖关系图示,显示出这些服务容器运行次序:
小结#
我们已建立起开发基于 Rails 技术的 docker 类型的本地开发环境。它与在本机上布署的开发环境功能相同: 有持久的数据库数据存储,可运行 rake 任务等等。运行在容器上的 rails db
,rails c
等命令也可以很好地工作。
主要的优点是,我可以很容易地使用一行配置的改变,切换 Postgres 或 Ruby 的运行版本。重建 docker image 后将使开发运行在新的环境下。
容器化 Node.js (前端服务 -- frontend server)#
主要关键点有这些:
- 使用官方基础的
node
docker 映像(image),不需任何调整 - 添加名为
server
服务容器到名为amplifr_internal
的 docker 虚拟网络下 - 定义
BACKEND_URL
环境变量,表示接入后端服务的途径 - 挂载
mode_modules
卷,其是 Node.js 模块的安装目录
version: '3.4'
services:
app: &app
image: node:11
working_dir: /app
environment:
- NODE_ENV=development
#译注:下面引用的是后端服务簇中 server 服务的别名和映射端口
- BACKEND_URL=http://backend-server:3000
volumes:
- .:/app:cached
- node_modules:/app/node_modules
runner:
<<: *app
command: /bin/bash
stdin_open: true
tty: true
server:
<<: *app
command: bash -c "yarn cache clean && yarn install && yarn start"
networks:
default:
amplifr_internal:
aliases:
- frontend-server
ports:
- "3001:3001"
networks:
amplifr_internal:
external: true
volumes:
node_modules:
使用方法#
现在,前端(frontend)服务容器可十分简单地启动,执行如下命令:
$ docker-compose up server
但是,要启动前端(frontend)服务必须先启动后端(backend)服务,因为前端服务引用到后端的 internal
网络,它只有将后端服务启动后才有效。(译注:这里的意思是要启动前端,必须先按后端的 docker compose 配置文件启动后端,然后再启动前端的 docker compose)。
容器化 Logux 服务#
这总体上较简单,Logux 服务可以参考前端(frontend)服务的配置。不同点是 Logux 服务有自己的环境变量设置,以建立与整体各服务的交互。
docker-compose up server # 启动名为 server 的 docker compose 服务容器
容器化 Golang (链接地址缩短 web 服务)#
主旨相同:
- 使用建好的
Golang
映像,并挂载程序源代码目录,运行go run
做为 Golang 语言解释器。 - 通过 docker 虚拟网络与 Ruby 后端服务整合。
我们项目中的这个 web 服务依赖 Postgres 和 Redis 服务。下面是对其 Dockerfile
文件的配置一些说明,完整的配置例子,参见 这里:
FROM golang:1.11
ARG MIGRATE_VERSION=4.0.2
# 为开发环境安装 postgres 客户端连接软件
RUN apt-get update && apt-get install -y postgresql-client
# 安装 dep 工具保证 Golang 依赖关系
RUN go get -u github.com/golang/dep/cmd/dep
# 安装迁移命令行环境,保证可进行数据库迁移任务
ADD https://github.com/golang-migrate/migrate/releases/download/v${MIGRATE_VERSION}/migrate.linux-amd64.tar.gz /tmp
RUN tar -xzf /tmp/migrate.linux-amd64.tar.gz -C /usr/local/bin && mv /usr/local/bin/migrate.linux-amd64 /usr/local/bin/migrate
ENV APP ${GOPATH}/src/github.com/evilmartians/ampgs
WORKDIR ${APP}
下面是一些可能感兴趣的配置说明:
- 在开发环境 docker image (映像) 中,安装了 postgres 客户端(postgres-client )。当使用类似
docker-compose run runner "psql $DATABASE_URL"
的命令连接 postgres 数据库执行 psql 命令时,其提供支持。相同方法可用于 Ruby 服务容器。 - 我们安装了
dep
工具检查并安装所有的依赖包,如使用命令:docker-compose run runner dep ensure
- 我们在 docker 映像中安装了数据库迁移工具,可将服务容器中的数据库进行迁移,如使用命令:
docker-compose run runner "migrate -source file://migrations/ -database ${DATABASE_URL} up"
‼️ 我们不需要组成 docker 开发环境映像中的大多数工具,因为它仅包含编译后的二进制文件。
我们将使用与 Ruby 服务相同的方式将 Golang 服务容器化:
- 提取所有服务容器共性构建名为
app
的基类容器,在此基础上构建名为runner
的服务容器执行运维任务 - 加入对具有永久数据存储能力的 Postgres 和 Redis 服务的依赖
下面是 docker-compose.yml
配置文件中的重要部分:
services:
# 基类服务(base service)配置
app: &app
image: ampgs:0.3.1-development
build:
context: .
dockerfile: docker/development/Dockerfile
environment:
REDIS_URL: redis://redis:6379/6
DATABASE_URL: postgres://postgres:postgres@postgres:5432/ampgs
volumes:
- .:/go/src/github.com/evilmartians/ampgs
depends_on:
- redis
- postgres
runner:
<<: *app
web:
<<: *app
command: "go run ampgs.go"
ports:
- '8000:8000'
networks:
default:
amplifr_internal:
aliases:
- ampgs-server
总结#
Docker-compose 是简化多服务管理复杂性的强大工具。
让我们回顾一下,使用 docker compose 布署构建本地的 docker 化的开发环境的一些主要原则:
- 挂载程序或工具源代码目录到容器中,消除 docker 启动时对这些的重建工作,这使 docker 的重启节约大量的时间。
- 使用 docker 虚拟网络来保证服务之间的通信。它有助于整体测试所有服务,但各服务保持其独立性。
- 通过在
docker-compose
中配置环境变量 (environments variables)使 各服务可彼此感知
就这些了。感谢您的参阅!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: