如何用Docker Compose创建一个本地开发环境

如何用Docker Compose创建一个本地开发环境

作为一个开发者,当你从事一项服务时,你会面临一个工作环境的问题。当我说到工作环境时,我想到的不是IDE、堆栈、操作系统、库等等。我想的是我们的服务所处的环境。

这些天,我们的服务通常被装在一些容器里,放在某种分布式系统中。大多数容器和其他移动部件都由Kubernetes、Nomad和类似的协调系统控制。

无论它们有多大区别,它们都控制着容器化服务。他们做的不仅仅是控制容器;至少,我们关心的是他们拥有容器。

问题

所以,我正在为一些服务开发一个功能。当我检查代码时,我意识到该服务有外部依赖性:其他服务,一些存储(数据库),消息系统(Kafka)。

在理想的世界里,我不会太在意。我会添加一个API端点和一些DTOs,并对外部服务进行一些调用。为了确保一切正常,我会添加一些单元测试。

后来,当我完成代码时,我会把它推到工作环境中,以测试服务与其他组件的集成。而一切都很顺利。

在现实世界中,并不是这样的。当你使用新做的功能时,会存在一些bug、错误的假设和新发现的约束。

这里的痛苦是等待代码被编译和部署。我做了一系列的小改动,一个接一个。每次修改后,我都会把代码推送到工作环境中。然后你就进入了一个提交、推送、构建和测试的循环,如果通过CI来完成,就会非常慢。

想法: 复制所需的依赖关系

简单。复制所需的依赖关系。

但是,由于你的依赖性有依赖性,你必须复制这些依赖性,然后这些可以有依赖性,等等。这是否意味着复制整个系统?不,那是不聪明的。我们需要弄清楚所需依赖关系的最小集合。我们的想法是只复制一组最小的依赖关系。

好的方面是,复制不需要像在工作环境中那样精确。它可以用更少的内存和CPU工作。例如,假设工作环境中的Postgres数据库版本为14.1.1。在这种情况下,你将使用相同的Postgres版本,但更轻(内存和CPU更少)。存储在副本中的数据将是原始数据的一个片段。

如何做到这一点?

由于有了容器,你可以快速地运行任何程序,而不需要混乱的安装。如果你不相信我,试着在你的本地机器上安装Postgres数据库,然后用Docker做同样的事情(运行容器化的Postgres)。然后比较一下经验,特别是如果你需要运行同一个服务的几个实例,但不同的版本。

所以,容器。我认为,docker compose是这项工作的完美选择。

例子

让我描述一下情况:

  • 我的目标服务,名为app
  • 依赖于名为next的服务
  • app的依赖性为Postgres DB
  • next服务的MariaDB的依赖关系
  • Kafka的依赖关系

让我快速描述一下配置。

服务app和next

这两个服务共享一个代码库。在容器启动之前,必须先构建一个镜像。镜像是通过两步docker构建的:

FROM golang:latest as builder
	WORKDIR /app
	COPY . /app/
	RUN go mod tidy
	RUN go build -o app
	FROM golang:buster
	WORKDIR /app
	COPY --from=builder /app/app /app/
	ENTRYPOINT [ "/app/app" ]

但配置是不同的。服务app使用来自文件的配置,而next服务从环境变量中获得配置。同时,服务app从Kafka写和读,所以它需要等待broker服务。

在另一个方面,next服务只有一个依赖:mariaDB

服务app

app:
	container_name: echo
	build: .
	environment:
	KAFKA_BROKER: "broker:29092"
	ports:
	- 9999:9999
	volumes:
	- ./configs:/app/configs
	depends_on:
	- liquibase_pg
	- broker
	restart: always

指令:

build: .

它告诉Docker,镜像需要先被构建。构建环境是当前目录。

接下来,指示Docker将一个卷从本地FS挂载到容器FS:

 volumes:
	- ./configs:/app/configs

并等待依赖性:

depends_on:
	- liquibase_pg
	- broker

其中liquibase_pg是一个一次性服务,在pg服务启动后启动,以便用Liquibase创建DB模式。只有当模式被创建后,app服务才能启动。

next服务:

next:
	container_name: beta
	build: .
	ports:
	- 8888:8888
	depends_on:
	- liquibase_maria
	environment:
	DB_HOST: maria
	DB_PORT: 3306
	DB_USER: docker
	DB_PASSWORD: password
	DB_NAME: docker
	DB_TYPE: MARIA
	APP_PORT: 8888
	TARGET: "http://echo:9999"
	ERROR_RATE: 10
	DELAY: 3000
	restart: always

next服务是等待Liquibase在MariaDB中创建模式。

其他服务

其他服务代表依赖性: Postgres DB、MariaDB、Kafka broker和Liquibase。有趣的是,设置所有这些依赖关系是非常容易的。如果你在官方文档或DockerHub上搜索它们,你会发现关于设置它们的说明。

最重要的是要设置正确的服务启动顺序。例如,Kafka代理应该在任何使用它的服务之前启动。DB的情况也是如此。

关于名称

有一件事需要注意:服务名称和容器名称。它们可以是不同的。但如果它们是相同的,那就更好了。这将使你的生活更容易。在我的例子中,它们是不同的。原因是要明确这种区别。

在使用docker compose命令时,你会使用服务名称,因为你会与服务进行交互。另一方面,容器只看到容器。这意味着你将使用容器名称来称呼另一个容器。例如,在我的例子中,app服务容器是echo。它周期性地调用另一个容器。为了称呼那个容器,我必须使用容器名:

TARGET="http://beta:8888"

原因是Docker使它的网络,而这些容器是通过名字来解决的。看看服务next环境变量:

  environment:
	DB_HOST: maria
	DB_PORT: 3306
	DB_USER: docker
	DB_PASSWORD: password
	DB_NAME: docker
	DB_TYPE: MARIA
	APP_PORT: 8888
	TARGET: "http://echo:9999"
	ERROR_RATE: 10
	DELAY: 3000

当一切都设置好后,你可以用一个命令启动整个系统:

docker compose up -d

重建应用程序、日志和停止工作

你设置了这一切。开始对你的功能进行工作,在某个时刻,你想看看你做了什么。现在你必须重建你的app:

docker compose -f <compose file> up --detach --build <service name>

使用默认的docker-compose文件名,你可以省略带有文件名的部分:compose.yml

要获得日志:

docker compose logs -f <container name>

要停止整个系统:

docker compose down

要停止并删除所有卷:

docker compose down -v

总结

我希望你喜欢这个想法。它可以做得更好,但这是一个好的开始。我相信你可以改进它。如果你有任何问题,请随时提出。

参考文献

评论留言