|
发表于 2023-2-15 20:14:59
|
显示全部楼层
现状
现在的开发模式基本是微服务开发模式,一个服务依赖N个其它服务,一个前端依赖一个N个后端,如果本地要debug的话,将会是一件非常头疼的事,常见的debug模式有以下几种:
1. 叫相关开发人员将服务运行起来,然后连接调试
2. 自己将所有代码clone下来,自己在本地运行相关服务好像也没什么问题,也可以调试,但是弊端也很明显
先说方案1的弊端
- 如果开发某天请假了,你没调试环境了,你还能调试吗?
- 如果开发,又加了新功能,因为功能是个半成品,不稳定,导致你依赖的接口访问不了,你还能调试吗?
方案2的弊端
- 这个要求所有开发要熟悉整个技术栈的环境创建、编译构建相关的知识,比如:前端用的是 vue ,后端开发要安装 nodejs ,要懂一些 npm,vue 啥的,后端用的是 go,前端要安装 go ,要懂 go 的依赖安装,go 编译啥的
- 因为有多个服务,服务间如果用了相同端口,就会有端口冲突,改了端口后,相关依赖的服务都要修改,这就要求相关开发要对整个架构、服务依赖非常了解,不然肯定非常折腾
上述方案都只是用一些成本比较大的方式提供了一个不太稳定的开发环境
几个工具
docker
要保证开发环境的稳定,就需要一个不可变的基础设施,说到不可变基础设施,肯定就要说一下docker了,docker用镜像解决了PaaS标准化的问题,将PaaS变成了一个不可变基础设施,这样无论我们在什么操作系统上,创建出来的实例都可以运行起来,配置一样的情况下,提供的服务也是一样的,但docker只保证单实例的一致性,我们的服务有N多个,如何将它们建立关联关系并运行起来呢
docker-compose
docker-compose 可以声明一组服务,指定启动镜像、启动命令、暴露服务端口,可以很好的解决服务编排的问题
我们用以下这种服务架构为例子,为每个应用创建一个开发环境

image.png
用 docker 镜像来解决服务运行环境问题,每个项目有一个Dockerfile、build-dev-image.sh文件,用build-dev-image.sh来构建当前分支下的代码,并push到docker registry,依赖项目只需要引用相关版本的镜像就好了

image.png
用docker-compose来解决服务依赖问题,每个项目都要有一个docker-compose.yml文件,声明依赖服务的镜像版本,服务的端口

image.png
构建开发环境 docker 镜像
<hr/>前端

image.png
Dockerfile 如下:
FROM nginx:1.19.2-alpine
COPY ./dist /usr/share/nginx/htmlbuild-dev-image.sh 如下:
#!/bin/bash
pwdStr=`pwd`
dirName=${pwdStr##*/}
buildTime=`date &#39;+%Y%m%d%H%M&#39;`
harborHost=&#34;docker registry domain&#34;
latestTag=$harborHost/dev/${dirName}:latest
buildTimeTag=$harborHost/dev/${dirName}:${buildTime}
harborUser=&#34;dev&#34;
harborPasswd=&#34;xxxx&#34;
npm run build
docker build -t $latestTag -t $buildTimeTag .
docker login $harborHost -u&#34;$harborUser&#34; -p&#34;$harborPasswd&#34;
docker push $latestTag
docker push $buildTimeTag
echo &#34;docker镜像名称: $latestTag&#34;
echo &#34;docker镜像名称: $buildTimeTag&#34;build-dev-image.sh 主要做了:
- 构建 docker 镜像
- push 到 docker registry
功能开发完了,要联调了,执行一下build-dev-image.sh,镜像就构建好了,并 push 到镜像仓库了,需要的项目按需取
这里输出了两个tag,一个是latest,一个是用年月日的,具体用哪个,看变更频繁度,如果很频繁就用年月日的,否则就用latest的,这样就不用老是修改docker-compose.yml了

image.png
后端

image.png
Dockerfile
FROM alpine:3.11.6
COPY main /app/
EXPOSE 8000
WORKDIR /app
CMD [&#34;/app/main&#34;]build-dev-image.sh
#!/bin/bash
pwdStr=$(pwd)
dirName=${pwdStr##*/}
buildTime=$(date &#39;+%Y%m%d%H%M&#39;)
harborHost=&#34;docker registry domain&#34;
latestTag=$harborHost/dev/${dirName}:latest
buildTimeTag=$harborHost/dev/${dirName}:${buildTime}
harborUser=&#34;dev&#34;
harborPasswd=&#34;xxxx&#34;
GOOS=linux CGO_ENABLED=0 go build -o main main.go
docker build -t $latestTag -t $buildTimeTag .
docker login $harborHost -u&#34;$harborUser&#34; -p&#34;$harborPasswd&#34;
docker push $latestTag
docker push $buildTimeTag
echo &#34;docker镜像名称: $latestTag&#34;
echo &#34;docker镜像名称: $buildTimeTag&#34;
rm -f main

image.png
服务信息
先看下项目信息
graph TD
example-front --> examplea --> exampleb<hr/>exampleb
只是简单的输出一个字符串
package main
import (
&#34;log&#34;
&#34;net/http&#34;
)
func main() {
http.HandleFunc(&#34;/b/api&#34;, func(writer http.ResponseWriter, request *http.Request) {
log.Println(request.URL.Path)
writer.Write([]byte(&#34;project b&#34;))
})
http.ListenAndServe(&#34;:8000&#34;, nil)
}<hr/>examplea
调用examplab,并输出一个字符串
const (
// 从配置服务中取
PROJECT_B_DOMAIN = &#34;http://exampleb:8000&#34;
)
func main() {
http.HandleFunc(&#34;/a/api&#34;, func(writer http.ResponseWriter, request *http.Request) {
log.Println(request.URL.Path)
resp, _ := http.Get(PROJECT_B_DOMAIN + &#34;/b/api&#34;)
defer resp.Body.Close()
result, _ := io.ReadAll(resp.Body)
writer.Header().Set(&#34;Access-Control-Allow-Origin&#34;, &#34;*&#34;)
writer.Write([]byte(&#34;project a<br/>&#34;))
writer.Write(result)
})
http.ListenAndServe(&#34;:8000&#34;, nil)
}example-front
调用examplea
<template>
<div id=&#34;app&#34;>
<h1>
Server Response: <br /><span v-html=&#34;resp&#34; style=&#34;color: red&#34;></span>
</h1>
</div>
</template>
<script>
import axios from &#34;axios&#34;;
export default {
name: &#34;App&#34;,
data() {
return {
resp: &#34;&#34;,
};
},
mounted() {
axios.get(&#34;http://localhost:8000/a/api&#34;).then((resp) => {
this.resp = resp.data;
});
},
};
</script>创建开发环境
example-front
example-front依赖examplea,examplea又依赖exampleb,所以我们需要两个服务,并暴露examplea的端口到宿主机上,供前端调用
docker-compose.yml 如下:
version: &#39;3&#39;
services:
examplea:
image: {{docker registry domain}}/dev/examplea:latest
ports:
- 8000:8000
exampleb:
image: {{docker registry domain}}/dev/exampleb:latest然后运行docker-compose up,再执行npm run serve,然后打开浏览器就可以看到

image.png
为了方便一些,我们可以直接修改 package.json的scripts,将docker-compose up -d加到serve上,这样我们直接npm run serve 也可以有一个开发环境
&#34;scripts&#34;: {
&#34;serve&#34;: &#34;docker-compose up -d && vue-cli-service serve&#34;,
&#34;build&#34;: &#34;vue-cli-service build&#34;,
&#34;lint&#34;: &#34;vue-cli-service lint&#34;
}examplea
docker-compose.yml 如下:
version: &#39;3&#39;
services:
exampleb:
image: {{docker registry domain}}/dev/exampleb:latest
example-front:
image: {{docker registry domain}}/dev/example-front:latest
ports:
- &#34;8080:80&#34;
examplea:
image: golang:1.19.5
working_dir: &#34;/go/src/examplea&#34;
# 调试模式,请制作一个包含go和delve的镜像
# command: [ &#34;dlv&#34;, &#34;debug&#34;, &#34;--headless&#34;, &#34;--listen=:2345&#34;, &#34;--api-version=2&#34;, &#34;--accept-multiclient&#34;, &#34;main.go&#34; ]
command: [ &#34;go&#34;,&#34;run&#34;,&#34;main.go&#34; ]
volumes:
- .:/go/src/examplea
depends_on:
- exampleb
- example-front
ports:
- &#34;8000:8000&#34;
- &#34;2345:2345&#34;这里为了有点区别,输出改成了debug project a<br/>

image.png
直接 docker-compose up,example-front将80映射成8080了,打开浏览器输入http://localhost:8080,

image.png
exampleb
docker-compose.yml如下:
version: &#39;3&#39;
services:
examplea:
image: {{docker registry domain}}/dev/examplea:latest
ports:
- &#34;8000:8000&#34;
example-front:
image: {{docker registry domain}}/dev/example-front:latest
ports:
- &#34;8080:80&#34;
exampleb:
image: golang:1.19.5
working_dir: &#34;/go/src/exampleb&#34;
# 调试模式,请制作一个包含go和delve的镜像
# command: [ &#34;dlv&#34;, &#34;debug&#34;, &#34;--headless&#34;, &#34;--listen=:2345&#34;, &#34;--api-version=2&#34;, &#34;--accept-multiclient&#34;, &#34;main.go&#34; ]
command: [ &#34;go&#34;,&#34;run&#34;,&#34;main.go&#34; ]
volumes:
- .:/go/src/exampleb
depends_on:
- examplea
- example-front
ports:
- &#34;2345:2345&#34;这里为了有点区别,输出改成了debug project b<br/>

image.png

image.png
总结
现在各项目只需要执行一下docker-compose up或者npm run serve就可以有一个稳定的开发环境了,再也不用因为环境的问题,折腾大半天,结果还不一定能折腾出来
<hr/>go 项目也是可以启用 debug 模式,只需要找一个或者自己制作一下包含go和delve的镜像,然后把command改成[ &#34;dlv&#34;, &#34;debug&#34;, &#34;--headless&#34;, &#34;--listen=:2345&#34;, &#34;--api-version=2&#34;, &#34;--accept-multiclient&#34;, &#34;main.go&#34; ],并将端口用ports映射一下就好了
goland用Go Remote

image.png
看到API server listening at: [::]:2345,点击dubug就OK了

image.png

image.png
事例代码
examplea
exampleb
example-front
clone记得替换docker-compose.yml的{{docker registry}},修改build-dev-image.sh中的registry信息 |
|