作为一个开发者,当你从事一项服务时,你会面临一个工作环境的问题。当我说到工作环境时,我想到的不是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文件名,你可以省略带有文件名的部分:compose.yml。
要获得日志:
要停止整个系统:
docker compose down
要停止并删除所有卷:
docker compose down -v
总结
我希望你喜欢这个想法。它可以做得更好,但这是一个好的开始。我相信你可以改进它。如果你有任何问题,请随时提出。
参考文献
- Docker Compose
- Kafka
- Postgres
- MariaDB
原文地址:https://www.wbolt.com/local-development-environment.html