云计算、AI、云原生、大数据等一站式技术学习平台

网站首页 > 教程文章 正文

如何快速搭建一个稳定的开发环境

jxf315 2025-01-05 18:15:05 教程文章 30 ℃

现状

现在的开发模式基本是微服务开发模式,一个服务依赖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 可以声明一组服务,指定启动镜像、启动命令、暴露服务端口,可以很好的解决服务编排的问题

我们用以下这种服务架构为例子,为每个应用创建一个开发环境

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

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

构建开发环境 docker 镜像


前端

Dockerfile 如下:

FROM nginx:1.19.2-alpine
COPY ./dist /usr/share/nginx/html
复制代码

build-dev-image.sh 如下:

#!/bin/bash
pwdStr=`pwd`
dirName=${pwdStr##*/}
buildTime=`date '+%Y%m%d%H%M'`
harborHost="docker registry domain"
latestTag=$harborHost/dev/${dirName}:latest
buildTimeTag=$harborHost/dev/${dirName}:${buildTime}
harborUser="dev"
harborPasswd="xxxx"

npm run build
docker build -t $latestTag -t $buildTimeTag .
docker login $harborHost -u"$harborUser" -p"$harborPasswd"
docker push $latestTag
docker push $buildTimeTag

echo "docker镜像名称: $latestTag"
echo "docker镜像名称: $buildTimeTag"

复制代码

build-dev-image.sh 主要做了:

构建 docker 镜像 push 到 docker registry

功能开发完了,要联调了,执行一下build-dev-image.sh,镜像就构建好了,并 push 到镜像仓库了,需要的项目按需取

这里输出了两个tag,一个是latest,一个是用年月日的,具体用哪个,看变更频繁度,如果很频繁就用年月日的,否则就用latest的,这样就不用老是修改docker-compose.yml了

后端

Dockerfile

FROM alpine:3.11.6
COPY main /app/
EXPOSE 8000
WORKDIR /app
CMD ["/app/main"]
复制代码

build-dev-image.sh

#!/bin/bash
pwdStr=$(pwd)
dirName=${pwdStr##*/}
buildTime=$(date '+%Y%m%d%H%M')
harborHost="docker registry domain"
latestTag=$harborHost/dev/${dirName}:latest
buildTimeTag=$harborHost/dev/${dirName}:${buildTime}
harborUser="dev"
harborPasswd="xxxx"

GOOS=linux CGO_ENABLED=0 go build -o main main.go
docker build -t $latestTag -t $buildTimeTag .
docker login $harborHost -u"$harborUser" -p"$harborPasswd"
docker push $latestTag
docker push $buildTimeTag

echo "docker镜像名称: $latestTag"
echo "docker镜像名称: $buildTimeTag"

rm -f main
复制代码

服务信息

先看下项目信息

example-front

examplea

exampleb


exampleb

只是简单的输出一个字符串

package main

import (
 "log"
 "net/http"
)

func main() {
 http.HandleFunc("/b/api", func(writer http.ResponseWriter, request *http.Request) {
  log.Println(request.URL.Path)
  writer.Write([]byte("project b"))
 })
 http.ListenAndServe(":8000", nil)
}
复制代码

examplea

调用examplab,并输出一个字符串

const (
 // 从配置服务中取
 PROJECT_B_DOMAIN = "http://exampleb:8000"
)

func main() {
 http.HandleFunc("/a/api", func(writer http.ResponseWriter, request *http.Request) {
  log.Println(request.URL.Path)
  resp, _ := http.Get(PROJECT_B_DOMAIN + "/b/api")
  defer resp.Body.Close()
  result, _ := io.ReadAll(resp.Body)
  writer.Header().Set("Access-Control-Allow-Origin", "*")
  
  writer.Write([]byte("project a<br/>"))
  writer.Write(result)
 })
 http.ListenAndServe(":8000", nil)
}
复制代码

example-front

调用examplea

<template>
  <div id="app">
    <h1>
      Server Response: <br /><span v-html="resp" style="color: red"></span>
    </h1>
  </div>
</template>
<script>
import axios from "axios";
export default {
  name: "App",
  data() {
    return {
      resp: "",
    };
  },
  mounted() {
    axios.get("http://localhost:8000/a/api").then((resp) => {
      this.resp = resp.data;
    });
  },
};
</script>

复制代码

创建开发环境

example-front

example-front依赖examplea,examplea又依赖exampleb,所以我们需要两个服务,并暴露examplea的端口到宿主机上,供前端调用

docker-compose.yml 如下:

version: '3'
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,然后打开浏览器就可以看到

为了方便一些,我们可以直接修改 package.json的scripts,将docker-compose up -d加到serve上,这样我们直接npm run serve 也可以有一个开发环境

"scripts": {
    "serve": "docker-compose up -d && vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  }
复制代码

examplea

docker-compose.yml 如下:

version: '3'
services:
  exampleb:
    image: {{docker registry domain}}/dev/exampleb:latest
  example-front:
    image: {{docker registry domain}}/dev/example-front:latest
    ports:
      - "8080:80"
  examplea:
    image: golang:1.19.5
    working_dir: "/go/src/examplea"
    # 调试模式,请制作一个包含go和delve的镜像
#    command: [ "dlv", "debug", "--headless", "--listen=:2345", "--api-version=2", "--accept-multiclient", "main.go" ]
    command: [ "go","run","main.go" ]
    volumes:
      - .:/go/src/examplea
    depends_on:
      - exampleb
      - example-front
    ports:
      - "8000:8000"
      - "2345:2345"
复制代码

这里为了有点区别,输出改成了debug project a<br/>

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

exampleb

docker-compose.yml如下:

version: '3'
services:
  examplea:
    image: {{docker registry domain}}/dev/examplea:latest
    ports:
      - "8000:8000"
  example-front:
    image: {{docker registry domain}}/dev/example-front:latest
    ports:
      - "8080:80"
  exampleb:
    image: golang:1.19.5
    working_dir: "/go/src/exampleb"
    # 调试模式,请制作一个包含go和delve的镜像
#    command: [ "dlv", "debug", "--headless", "--listen=:2345", "--api-version=2", "--accept-multiclient", "main.go" ]
    command: [ "go","run","main.go" ]
    volumes:
      - .:/go/src/exampleb
    depends_on:
      - examplea
      - example-front
    ports:
      - "2345:2345"
复制代码

这里为了有点区别,输出改成了debug project b<br/>

总结

现在各项目只需要执行一下docker-compose up或者npm run serve就可以有一个稳定的开发环境了,再也不用因为环境的问题,折腾大半天,结果还不一定能折腾出来


go 项目也是可以启用 debug 模式,只需要找一个或者自己制作一下包含go和delve的镜像,然后把command改成[ "dlv", "debug", "--headless", "--listen=:2345", "--api-version=2", "--accept-multiclient", "main.go" ],并将端口用ports映射一下就好了

goland用Go Remote

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

docker-compose.yml配置详解


每个docker-compose.yml必须定义image或者build中的一个,其它的是可选的。

image

指定镜像tag或者ID。示例:

image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd
复制代码

ps:在version 1里同时使用image和build是不允许的,version 2则可以,如果同时指定了两者,会将build出来的镜像打上名为image标签

build

用来指定一个包含Dockerfile文件的路径。一般是当前目录.。Fig将build并生成一个随机命名的镜像。

ps:在version 1里bulid仅支持值为字符串。version 2里支持对象格式。

build: ./dir

build:
  context: ./dir
  dockerfile: Dockerfile-alternate
  args:
    buildno: 1
复制代码
  • context:路径
  • dockerfile:需要替换默认docker-compose的文件名
  • args:为构建(build)过程中的环境变量,用于替换Dockerfile里定义的ARG参数,容器中不可用。

command

用来覆盖缺省命令。示例:

command: bundle exec thin -p 3000

也支持数组形式:

command: [bundle, exec, thin, -p, 3000]

env_file

从文件中获取环境变量,可以为单独的文件路径或列表。 如果通过 docker-compose -f FILE 指定了模板文件,则 env_file 中路径会基于模板文件路径。 如果有变量名称与 environment 指令冲突,则以后者为准。

env_file: .env
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
复制代码

环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

# common.env: Set Rails/Rack environment
RACK_ENV=development
复制代码

links

用于链接另一容器服务,如需要使用到另一容器的mysql服务。可以给出服务名和别名;也可以仅给出服务名,这样别名将和服务名相同。同docker run --link。示例:

links:
 - db
 - db:mysql
 - redis
复制代码

ports

用于暴露端口。同docker run -p。示例:

ports:
 - "3000"
 - "8000:8000"
 - "49100:22"
 - "127.0.0.1:8001:8001"
复制代码

ps:冒号前面是主机上的端口,冒号后面是容器内部的端口。

expose

expose提供container之间的端口访问,不会暴露给主机使用。同docker run --expose。

expose:
 - "3000"
 - "8000"
复制代码

volumes

挂载数据卷。同docker run -v。示例:

volumes:
 - /var/lib/mysql
 - cache/:/tmp/cache
 - ~/configs:/etc/configs/:ro
复制代码

volumes_from

挂载数据卷容器,挂载是容器。同docker run --volumes-from。示例:

volumes_from:
 - service_name
 - service_name:ro
 - container:container_name
 - container:container_name:rw
复制代码

ps:container:container_name格式仅支持version 2。

environment

添加环境变量。同docker run -e。可以是数组或者字典格式:

environment:
  RACK_ENV: development
  SESSION_SECRET:

environment:
  - RACK_ENV=development
  - SESSION_SECRET
复制代码

depends_on

用于指定服务依赖,一般是mysql、redis等。 指定了依赖,将会优先于服务创建并启动依赖。

links也可以指定依赖。

external_links

链接搭配docker-compose.yml文件或者Compose之外定义的服务,通常是提供共享或公共服务。格式与links相似:

external_links:
 - redis_1
 - project_db_1:mysql
 - project_db_1:postgresql
复制代码

ps:external_links链接的服务与当前服务必须是同一个网络环境。

extra_hosts

添加主机名映射。

extra_hosts:
 - "somehost:162.242.195.82"
 - "otherhost:50.31.209.229"
复制代码

将会在/etc/hosts创建记录:

162.242.195.82  somehost
50.31.209.229   otherhost
复制代码

extends

继承自当前yml文件或者其它文件中定义的服务,可以选择性的覆盖原有配置。

extends:
  file: common.yml
  service: webapp
复制代码

service必须有,file可选。service是需要继承的服务,例如web、database。

net

设置网络模式。同docker的--net参数。

net: "bridge"
net: "none"
net: "container:[name or id]"
net: "host"
复制代码

dns

自定义dns服务器。

dns: 8.8.8.8
dns:
  - 8.8.8.8
  - 9.9.9.9
复制代码

Tags:

最近发表
标签列表