将多服务应用程序 Docker 化以进行本地开发
包起来
由于现在许多复杂的 Web 应用程序都在生产容器上运行,我们继续以“老式”的方式开发它们,在本地开发机器上安装Postgresql、Redis、Ruby和其他组件。
维护开发过程变得越来越困难,特别是当系统变得异构并扩展到大量服务,并且依赖各种版本的组件运行时。当依赖组件的版本多种多样时,这一点就变得尤为现实。
在本文中,我将以我参与的项目Amplifr为例,回顾一下本地开发容器化。借助 docker-compose 和 docker 网络,这个过程简单高效。
由于所有基础设施都是容器化的,并且在生产中使用 Kubernetes 进行管理,我们将仅参与本地开发的设置,遵循一个原则——开发过程的便利性。
本地容器化的好处
- 无需在本地机器上安装所有组件,例如数据库引擎、语言解释器等,保持本地机器清洁。
- 自然支持不同的环境,在本地机器上运行不同版本的 Ruby、Postgresql 服务
项目概述
尽管 Amplifr 的后端在 Rails 上运行,但该项目还具有复杂的前端,由独立的 Node.js 服务器和 Logux web-socket 服务器以及其他辅助服务提供服务,这些服务是用 Node.js、Ruby 和 Golang 编写的。
下图是该项目的简化架构:
我将快速回顾一下整个系统的一些组件。
后端服务器
后端是经典的 Rails 应用程序,执行所有业务逻辑并使用 Sidekiq 执行许多后台作业。
前端服务器
前端是整个应用程序唯一的公共 HTTP 入口点。它提供前端资源,并将其他请求代理到 Rails 后端。
后端也与前端服务器集成,用于共享一些数据,例如browsers.json
用于正确渲染 HTML 的文件。
Logux 服务器
Logux 是一个暴露 web-socket 端口的服务器,与客户端浏览器保持双向连接。为了执行业务逻辑,它提供了两种与后端进行 HTTP 集成的方式。这使得我们可以将所有业务逻辑保留在 Rails 后端,并通过 HTTP 访问 Logux 来从后端返回通知。
“链接缩短器”
链接缩短器是一个用 Golang 编写的 Web 服务。它用于缩短和扩展链接,并管理链接扩展的整体统计信息。
“预览”服务
预览是公开服务,用于在客户端浏览器中渲染任何链接的 OpenGraph 表示。它仅具有公开的 http 端点。
其他组件
Shortener - 是一款独立的服务,用于缩短 URL 并保存有关链接扩展的分析数据。它使用 Golang 编写。它拥有一个外部公共端点用于扩展已缩短的链接,以及一个内部端点用于在后端的后台作业中发布社交内容的同时缩短链接。
还有一些其他内部服务,例如电报和 Facebook 机器人,它们仅具有后端集成。
组件依赖项
大多数组件本身都是复杂的Web服务,依赖于底层组件,例如Postgres,Redis和其他服务低级系统服务。
集装箱化
💡我们将使用Docker Compose分别对每个服务进行容器化。它是一个用于定义和运行多容器 Docker 应用程序的工具,只需使用一个命令即可运行所有服务,轻松启动:
docker-compose up
💡为了使服务集成,我们将使用Docker 网络,它允许任何 Docker 容器相互通信。internal
为了简单起见,我们将为所有组件仅使用一个 Docker 网络。更准确地说,读者将能够为每个服务依赖项和每组连接设置单独的网络。
Docker化Ruby后端
这里我们有标准技术栈:Postgres、Redis、Rails Web 服务器和 Sidekiq 后台。对于所有这些,我们将在 中定义服务docker-compose.yaml
。
以下是关键点:
- 对于 Postgres 和 Redis,我们将定义持久卷来保存运行之间的数据
- 我们不会将 Ruby 源代码复制到容器中,而是将 Rails 应用程序源代码挂载到
/app
文件夹 - 我们还将定义捆绑包和其他内容的持久存储,以便在下次启动时增加
- 我们将定义
amplifr_internal
网络并将交互容器添加到该网络 - 应用程序应该准备好配置环境变量,我们将在 docker-compose 文件中设置这些变量
- 我们将在 YAML 文件中定义基本应用服务,然后使用 YAML 语法的 Anchors 和别名以免重复。
❗请记住,此配置与为生产构建 docker 镜像的方式不同,其中所有源代码和所有依赖包都被复制到 docker 镜像中,以使其足够并且没有外部依赖!
以下是所有配置的完整要点,但请允许我注意以下要点:
描述从其继承的基础服务
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
基础服务的容器将使用 Postgres 版本作为参数构建Dockerfile.dev
。所有其他基于 Ruby 的镜像都将继承基础版本。服务继承关系图如下:
我们还定义了当前文件夹到容器/app
目录的映射,并为 bundles 挂载了 docker 卷。这样可以避免每次都安装依赖项。
我们还定义了两组环境变量:
1)system
变量,例如BUNDLE_PATH
,REDIS_URL
和DATABASE_URL
URL。2
)用于集成的依赖服务内部URL:FRONTEND_URL
- 是前端服务器获取支持的浏览器列表的内部端点。-LOGUX_URL
是用于从Rails应用程序向Logux发送操作的内部Logux HTTP端点。
描述“跑步者”
运行器服务用于在 Rails 环境中运行维护命令,例如 Rake 任务或生成器。它是面向控制台的服务,因此我们需要设置stdin_open
和tty
配置选项,这对应于docker 的-i
和--t
选项,并为容器启动启用 bash shell:
services:
runner:
<<: *backend
stdin_open: true
tty: true
command: /bin/bash
我们可以这样使用它:
docker-compose run runner bundle exec rake db:create
# or run container and any command within the container
docker-compose run runner
组成服务器
定义 Web 服务器。这里的关键点在于,我们需要定义一个额外的 Docker 网络internal
,并将 Web 服务器添加到其中,并backend-server
为该网络中的容器主机指定一个别名。这样,Web 容器就可以通过该backend-server
网络名称访问了。
services:
server:
<<: *app
command: bundle exec thin start
networks:
default:
internal:
aliases:
- backend-server
ports:
- '3000:3000'
networks:
internal:
编写 Sidekiq
很简单,它只需运行 sidekiq 并继承基本服务:
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 镜像,包含一些构建包所需的组件,例如 Postgresql 客户端和其他一些二进制文件。
用法
使用方法非常简单:
docker-compose run runner ./bin/setup # runs the bin/setup in docker
docker-compose run runner bundle exec rake db:drop # runs rake task
docker-compose up server # get the web-server running
docker-compose up -d # runs all the services (web, sidekiq)
docker-compose up rails db # runs the postgres client
Docker Compose 还允许指定服务依赖关系,并在运行服务需要时启动依赖服务,ge Sidekiq 需要 Redis 和 Postgres 服务才能正常工作,这就是我们在depends_on
服务部分定义它们的原因。
概括
rails db
我们在本地运行了 Rails 应用程序进行开发。它的工作方式与本地相同:持久化数据库,运行 rake 任务。此外,像 这样的命令rails c
在容器中也能正常工作。
主要优点是我们可以通过更改一行轻松更改 Postgres 版本或 Ruby 版本,然后重建图像并尝试在新环境中运行。
Docker 化 Node.js(前端服务器)
这里的主要关键点是:
- 使用基础官方
node
docker 镜像,无需任何调整 - 将服务添加
server
到amplifr_internal
网络 - 定义
BACKEND_URL
环境变量映射到后端服务内部的docker路径。 - 挂载
mode_modules
Node.js 模块安装路径的卷
version: '3.4'
services:
app: &app
image: node:11
working_dir: /app
environment:
- NODE_ENV=development
- 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:
用法
现在可以轻松启动前端服务器,只需运行以下命令:
docker-compose up server
但是需要后端先启动,因为前端服务指的是internal
网络,后端启动后网络就会启动。
将 Logux 服务器 Docker 化
在任何简单情况下,Logux 服务器都具有任何数据库依赖项,并且可以像前端一样进行配置。唯一的区别是,Logux 服务有自己的环境变量,用于设置与集成服务的交互。
docker-compose up server # runs the server
Dockerizing Golang(链接缩短器 Web 服务)
主要思想也是一样的:
- 使用设置的docker镜像
Golang
,将应用程序源代码挂载到那里并使用go run
解释器运行它。 - 与 docker 网络共享服务以便与 Ruby 后端集成
我们的 Web 服务依赖 Postgres 和 Redis。让我们从这里开始描述Dockerfile
,整体配置示例如下:
FROM golang:1.11
ARG MIGRATE_VERSION=4.0.2
# install postgres client for local development
RUN apt-get update && apt-get install -y postgresql-client
# install dep tool to ensuring dependencies
RUN go get -u github.com/golang/dep/cmd/dep
# install migrate cli for running database migrations
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}
以下是一些有趣的细节:
- 我们为本地开发镜像安装了 postgres-client。它简化了数据库的访问,方便您随时访问:
docker-compose run runner "psql $DATABASE_URL"
。Ruby 后端 dockerization 也提供了类似的功能。 - 我们安装该
dep
工具来安装并确保所有依赖项:docker-compose run runner dep ensure
- 我们将迁移工具安装到镜像中,以允许直接从 docker 容器进行数据库迁移:
docker-compose run runner "migrate -source file://migrations/ -database ${DATABASE_URL} up"
‼️ 我们不需要生产环境 docker 镜像中的大多数工具,因为它只包含已编译的二进制文件。
我们将使用与 Ruby 服务相同的方式将 Golang 服务 docker 化:
- 提取基础
app
服务和特殊runner
服务以运行维护任务 - 添加具有持久数据卷的 Postgres 和 Redis 依赖项
以下是该文件的重要部分docker-compose.yml
:
services:
# base service definition
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
就这样。感谢阅读!
鏂囩珷鏉ユ簮锛�https://dev.to/amplifr/dockerize-the-multi-services-application-for-local-development-2oig