go docker-compose启动前后端分离项目 踩坑之旅

发布于:2025-09-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

文章目录


说明:我用了vpn,所以涉及到添加阿里、淘宝镜像的,我就没搞了

目标

期望在centos7上能够使用docker-compose远程开发,跑起来自己的项目。

包含:react前端项目client、go后端web项目business,redis、mysql依赖。

背景

项目本来在本机windows上跑的,但遇到了如下问题在windows上不可解决,转到了虚拟机centos7上:

问题:windows上go项目无法dlv+air跑起来,除非手动写脚本;手动写的脚本接入到docker-compose中,不如直接centos7远程开发。详见:go 开发环境配置 air + dlv debug 踩坑之旅_go. air-CSDN博客

项目结构

|-client前端项目: package.json、yarn.lock、Dockerfile.dev
|-business后端项目: .air.toml、Dockerfile.dev、main.go、tmp文件夹
|-docker-compose.yml

步骤一:拷贝本地项目到远程

# 本地压缩(排除 node_modules
tar -zcvf project.tar.gz --exclude='project/client/node_modules' project/
# 用 scp 传输压缩包
scp project.tar.gz root@192.168.40.142:~/
# 登录目标主机解压
ssh user@192.168.40.142 "tar -zxvf ~/project.tar.gz -C ~/"
  • node_modules太大,不传过去;传过去会传很久,如果硬盘空间小,还有可能导致硬盘空间用完
  • 压缩传输更快
  • 我将项目文件传送到了~/下

~ 、 / 和 /home 的区别

符号/目录 含义 指向目标 典型场景示例
/ 根目录(Root) 整个文件系统的最顶层目录 访问系统级目录,如/etc/passwd、/usr/bin
/home 普通用户家目录 存储所有普通用户的个人数据 普通用户的文件默认存于/home/username/docments
/root root用户家目录 存储root用户的数据
~ 用户家目录缩写 当前登录用户的个人家目录 cd ~ 快速切换到自己的家目录;ls ~ 查看自己的文件

步骤二:goland连接远程项目,下载goland IDE

注意:goland remote需要至少4G内存,4核CPU

在这里插入图片描述

操作步骤演示

注意:IDE选择和本地windows上的一致最好,我用的goland 2024.1,大小大概是800MB。

goland remote

问题:IDE下载失败

多次retry还是下载失败

在这里插入图片描述

解决方案
cd /root/.cache/JetBrains/RemoteDev/dist/

# 下载 GoLand 2024.1.6 安装包
[root@localhost dist]# curl -fSL --retry 5 --output c07bdfe86a471_goland-2024.1.6.tar.gz \
>   https://download.jetbrains.com/go/goland-2024.1.6.tar.gz

# 解压
tar -zxvf c07bdfe86a471_goland-2024.1.6.tar.gz

# 将解压文件重命名回去(我也不知道为啥要这么做,但我这么做好像就能行了)
mv GoLand-2024.1.6 c07bdfe86a471_goland-2024.1.6

# 确定解压后文件都在,比如build.txt
ls -ln c07bdfe86a471_goland-2024.1.6

# 再次重试打开remote development,期望goland check到已下载好的IDE

步骤三:下载go sdk

go sdk没有安装,goland会提示GOROOT is not defined,go项目依赖也会全部飘红。

注意:

  • 安装完成后,要重启goland才生效
  • goland sdk版本要与go.mod中版本一致 go1.24
cd /tmp

# 下载go sdk
wget https://dl.google.com/go/go1.24.5.linux-amd64.tar.gz

# 解压
tar -C /usr/local -zxvf go1.24.5.linux-amd64.tar.gz 

# 配置path
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc

# 生效
source ~/.bashrc

# 查看go版本:安装完成
go version
输出:go version go1.24.5 linux/amd64

步骤四:下载安装git最新版

虚拟机没有安装git,goland会有提示,跑项目的时候也会有错误提示;

如果git安装的不是2.x的新版本,而是1.x版本,goland也会有错误提示。

跑项目的错误信息:error obtaining VCS status: exit status 128

在这里插入图片描述

Go编译器在构建时会尝试获取版本控制信息,如git提交记录;若项目目录不是Git仓库或者权限不足,会报此警告。

因为我们是scp拷贝过来的项目,所以git还没下载,git user也不对。

临时解决方案:如报错提示,Use -buildvcs=false to disable VCS stamping.

在这里插入图片描述

Go 编译器的buildvcs功能(默认开启)是用于在编译时嵌入版本控制系统(VCS,如git)信息到二进制文件中,主要目的是增强可追溯性。

解决方案:安装配置git
错误示范:会安装1.8版本git,老旧(CentOS 7 默认仓库的版本)
# 更新系统包
yum update -y
# 下载git
yum install -y git

git --version

安装默认版本git,goland会有错误信息提示

安装最新版git
cd /tmp

yum install -y gcc openssl-devel libcurl-devel expat-devel gettext-devel zlib-devel

# 和windows上的git版本保持一致 2.45.2
wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.45.2.tar.gz 

tar -zxvf git-2.45.2.tar.gz

cd git-2.45.2

# 配置编译选项
make prefix=/usr/local/git all

# 安装到指定目录
make prefix=/usr/local/git install

# 编辑环境变量配置文件
sudo vim /etc/profile

# 在文件末尾添加
export PATH=/usr/local/git/bin:$PATH

# 使配置生效
source /etc/profile

go version

注意:我把git安装在/usr/local/git/bin,如果goland提示git找不到,可以configure修改一下文件路径。

在这里插入图片描述

问题:git add . 所有者安全控制

在这里插入图片描述

这个错误是由于 Git 检测到仓库目录的所有权存在问题(通常是因为目录所有者与当前用户不一致)。

解决方案,如错误提示:git config --global --add safe.directory /root/project/business

后续再配置git user、git email等进行提交

过程中可能出现的问题:安装依赖过程中,硬盘满了

比如,air启动失败信息:no space left on device

watching 
go/pkg/mod/google.golang.org/protobuf@v1.36.6/internal/testprotos/editionsfuzztest
failed to watch /root/go/pkg/mod/google.golang.org/protobuf@v1.36.6/internal/testprotos/enums, error: no space left on device
# 检查空间是否真的满了
df -h 

在这里插入图片描述

解决方案:磁盘扩容失败

说明:这部分信息仅作为踩坑记录,这次我失败了。虽然扩容成功,但虚拟机变得不能用了,无法上网和ssh连接,我找不到问题在哪里。

最终,我选择了重新创建一台centos7虚拟机,一开始就给了8G内存,4核,100G的硬盘空间。

步骤一

关闭虚拟机,右键选择设置

在这里插入图片描述

在这里插入图片描述

步骤二:使用fdisk扩容

Linux 根目录 磁盘空间 (/dev/sda3) 扩容

我按照如上教程进行扩容,虽然有些不理解,但扩容成功。但扩容成功后,出现一些问题:

虚拟机可以登录,ssh服务active但ssh无法连接;虚拟机变得很卡,不一会儿就卡死了;网络无法访问,curl https://www.baidu.com失败;

尝试解决网络不通的问题失败
# 查看网卡状态
ip addr

ens33未启动

2: ens33: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:0c:29:6d:33:5d brd ff:ff:ff:ff:ff:ff

我尝试启动网卡,网卡启动成功,但网络还是无法访问,我就放弃这个方案了

# 临时启动 ens33 网卡
ip link set ens33 up

# 确认状态(应显示 state UP)
ip addr show ens33

# 永久配置
vim /etc/sysconfig/network-scripts/ifcfg-ens33

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static  # 静态IP(或dhcp自动获取,若网关支持)
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens33
DEVICE=ens33
ONBOOT=yes  # 关键:开机自动启用网卡
IPADDR=192.168.40.131  # 你的IP(与网关同网段)
NETMASK=255.255.255.0  # 子网掩码
GATEWAY=192.168.40.1   # 网关IP
DNS1=114.114.114.114   # DNS服务器(可选,用于域名解析)
DNS2=8.8.8.8

# 重启网络
systemctl restart network
eth0和ens33的关系和区别
网卡名称 命名规则 稳定性 适用系统版本 核心优势 描述
eth0 按识别顺序编号(eth+数字) 低(易变) <=centos6 简单直观,历史兼容性好 eth是以太网(ethernet)的缩写,0表示被识别的第一个网卡,第二个则是eth1
ens33 按硬件位置命名(en+s+编号) 高(固定) >=centos7 名称固定,不受硬件变动影响 en表示ethernet(以太网网卡),类似的还有wl(无线网卡)、ww(蜂窝网络卡)
s表示网卡连接在"PCIe热插拔插槽"(hotplug slot)
33表示插槽编号(由硬件位置决定,固定不变)

步骤四:防火墙开放端口8000 8080

说明:8080是api的端口,8000是前端服务的接口

# 永久开放 TCP 协议的 8080 端口
firewall-cmd --add-port=8080/tcp --permanent 
firewall-cmd --add-port=8000/tcp --permanent

firewall-cmd --reload

# 检查是否开放
firewall-cmd --list-ports --permanent

在虚拟机上启动web服务127.0.0.1:8080, 直接curl 127.0.0.1:8080/hello能访问;但在主机上访问192.168.40.131:8080不能访问,可能就是防火墙没开,也可能是启动的服务是127.0.0.1而不是0.0.0.0

步骤五:business(后端)项目的dockerfile文件编写

FROM golang:1.24-alpine

# 安装 C 语言编译工具链和标准库(解决 stdlib.h 缺失)
# alpine 镜像需要安装 musl-dev(提供 C 标准库头文件)和 gcc(编译器)
RUN apk add --no-cache git gcc musl-dev
RUN go install github.com/go-delve/delve/cmd/dlv@latest
# 安装 air 热重载工具(全局安装,确保在 PATH 中)
RUN go install github.com/air-verse/air@latest

WORKDIR /app/business

# 复制依赖文件并下载
COPY go.mod go.sum ./
RUN go mod download

COPY . .
CMD [""]

air未安装报错

Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "air": executable file not found in $PATH: unknown

air依赖gcc musl-dev,未安装gcc musl-dev时报错

在这里插入图片描述

WORKDIR注意要和client(前端项目)区分开

WORKDIR注意要和client(前端项目)区分开,后端WORKDIR /app/business,前端WORKDIR /app/client;如果前后端workdir都写/app会出现覆盖,有问题。

数据卷注意同步修改

volumes:
  # 挂载本地代码到容器,实时生效
  - ./business:/app/business

COPY go.mod go.sum ./优化

COPY go.mod go.sum ./
# 表示仅复制go.mod(依赖声明)和go.sum(存储依赖校验值);目的:利用docker缓存:当项目代码变化时,只要go.mod和go.sum没变,run go mod download就会复用之前的缓存,无需重新下载依赖。
# 如果没有这一步,代码的任何修改都会重新执行go mod download(没有利用上docker的缓存机制,docker缓存机制按层失效)

COPY . .
# 表示复制当前目录下的所有其他文件,这一步会在依赖安装后执行,确保代码的最新版本被复制到镜像中
docker缓存机制按层失效

镜像并非单一的文件,而是由一系列只读的层(layer)叠加而成,每一层对应dockerfile中的一条指令。

反面例子
FROM golang:1.24  # 层1:基础镜像层
WORKDIR /app      # 层2:工作目录层
# 错误:直接复制所有文件(代码+依赖文件)
COPY . .          # 层3:复制所有文件(源是宿主机整个目录)
RUN go mod download  # 层4:下载依赖
RUN go build -o main  # 层5:编译代码

当修改项目,层3及其后面所有层都失效。

正面例子
FROM golang:1.24  # 层1:基础镜像层
WORKDIR /app      # 层2:工作目录层
# 第一步:只复制依赖描述文件(go.mod/go.sum)
COPY go.mod go.sum ./  # 层3:复制依赖文件
RUN go mod download     # 层4:下载依赖(关键:此层缓存只与go.mod/go.sum绑定)
# 第二步:复制所有代码(包括.go文件、配置等)
COPY . .               # 层5:复制代码(此层缓存与代码文件绑定)
RUN go build -o main    # 层6:编译代码

当修改项目时,层5才开始失效,层3、层4未变

步骤六:client(前端)项目的dockerfile文件编写

FROM node:20
WORKDIR /app/client
# 暂时不锁死yarn.lock文件,因为当前锁死时yarn不下来,还不确定原因
# 错误提示:There appears to be trouble with your network
COPY package.json ./
RUN yarn install --production=false  # 安装依赖(仅首次构建执行,后续用缓存)
COPY . .

我的yarn.lock文件是windows上生成的,直接scp到centos7上使用应该会有问题;所以最好在centos7上重新生成一份再配置到dockerfile里。

问题:yarn.lock中依赖无法下载

我直接使用scp过来的yarn.lock文件遇到如下问题:

在这里插入图片描述

=> ERROR [frontend 4/4] RUN yarn  # 安装依赖(仅首次构建执行,后续用缓存)                                                                                                                                                                     152.6s
------
 > [frontend 4/4] RUN yarn  # 安装依赖(仅首次构建执行,后续用缓存):
0.645 yarn install v1.22.22
1.108 [1/5] Validating package.json...
1.113 [2/5] Resolving packages...
3.649 [3/5] Fetching packages...
55.01 info There appears to be trouble with your network connection. Retrying...
108.3 info There appears to be trouble with your network connection. Retrying...
114.4 info There appears to be trouble with your network connection. Retrying...
119.4 info There appears to be trouble with your network connection. Retrying...
临时解决方案:不在dockerfile中锁死yarn.lock文件

在dockerfile中只COPY package.json ./,不copy yarn.lock。然后再跑项目就能跑了,虽然还会出现info There appears to be trouble with your network connection. Retrying…但多等会儿就能行了。

解决方案:下载nvm,更新yarn.lock
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash

# 使nvm生效
source ~/.bashrc

nvm --version

nvm install 18

npm install -g yarn
问题:centos CLIBC版本太低,不支持node18+的安装

报错:因为我安装了node 20+

npm install -g yarn
node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by node)
node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by node)
node: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by node)
node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by node)

推荐解决方案:卸载高版本,安装低版本

步骤七:docker-compose up -d --build 跑项目

docker-compose.yml文件:

# docker-compose up -d
# 查看前端日志(实时输出)
# docker-compose logs -f frontend
# 查看后端日志
# docker-compose logs -f backend
# 重新构建全部镜像
# docker-compose up -d --build
# 重新构建后端镜像
# docker-compose up -d --build backend
# 单独启动一个服务
# docker-compose restart backend
services:
  frontend:
    build:
      context: ./client
      dockerfile: Dockerfile.dev
    ports:
      - "8000:8000"
    volumes:
      # 将本地前端代码挂载到容器,实现实时更新
      - ./client:/app/client
      # 排除 node_modules(用容器内安装的依赖,避免系统差异)
      - /app/client/node_modules # 匿名卷
    environment:
      - NODE_ENV=development  # 告诉 React 处于开发模式
      - REACT_APP_API_URL=http://backend:8080/  # 后端 API 地址,注意用服务名称
    depends_on:
      - backend
    command: yarn dev
  backend:
    build:
      context: ./business
      dockerfile: Dockerfile.dev
    ports:
      - "8080:8080"
      - "2345:2345"
    # 添加以下安全配置
    security_opt:
      - seccomp:unconfined  # 禁用安全计算模式过滤:cite[4]:cite[6]
    cap_add:
      - SYS_PTRACE         # 允许进程跟踪(Delve 必需):cite[4]:cite[6]
    volumes:
      # 挂载本地代码到容器,实时生效
      - ./business:/app/business
      - ./testdata/tmp_file:/app/testdata/tmp_file
      - ./testdata/file:/app/testdata/file
    environment:
      # 数据库和Redis配置
      - MYSQL_USER=root
      - MYSQL_PASSWORD=123456
      - MYSQL_HOST=mysql
      - MYSQL_PORT=3306
      - MYSQL_DBNAME=business
      - REDIS_ADDR=redis:6379
      # 文件存储路径(容器内路径)
      - FILE_TMP_PATH=/app/testdata/tmp_file
      - FILE_TARGET_PATH=/app/testdata/file
      - GO_ENV=development
      - TZ=Asia/Shanghai
    depends_on:
      - mysql
      - redis
    command: air -c .air.toml

  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=123456
      - MYSQL_DATABASE=business
    volumes:
      - /root/project/testdata/mysql_data:/var/lib/mysql
      # 挂载本地 SQL 脚本,启动时自动执行(初始化表结构)
      - ./mysql/init:/docker-entrypoint-initdb.d
    # command: --default-authentication-plugin=mysql_native_password  # 兼容旧客户端

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    volumes:
      - /root/project/testdata/redis_data:/data
    command: redis-server --appendonly yes  # 开启数据持久化

问题一:数据卷写E:/goland/testdata/redis_data报错

报错信息:

[+] Running 0/0
 ⠋ Container project-redis-1  Creating                                                                                                                                                                                         0.0s 
 ⠋ Container project-mysql-1  Creating                                                                                                                                                                                         0.0s 
Error response from daemon: invalid volume specification: 'E:/goland/testdata/redis_data:/data:rw'


  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    volumes:
      - E:/goland/testdata/redis_data:/data
    command: redis-server --appendonly yes  # 开启数据持久化

解决方案:数据卷不能写E:/goland/testdata/redis_data,要写/E:/goland/testdata/redis_data

问题二:air main文件不可执行

报错:

在这里插入图片描述

问题排查&解决方案:

# 进入容器
docker-compose exec backend sh

# 检查文件权限
ls -l /app/business/tmp/main  # 应显示 -rwxr-xr-x 或类似权限(含 x)

# 尝试手动执行
/app/business/tmp/main

有权限能手动执行,那应该是docker安全限制:dlv需要sys_ptrace能力调试进程,而默认docker容器没有权限。所以,尝试放宽权限:

docker-compose.yml backend部分添加安全配置,重跑docker-compose up -d–force-recreate

# 添加以下安全配置
security_opt:
  - seccomp:unconfined  # 禁用安全计算模式过滤:cite[4]:cite[6]
cap_add:
  - SYS_PTRACE         # 允许进程跟踪(Delve 必需):cite[4]:cite[6]

在这里插入图片描述

思考:什么场景需要重新构建镜像,修改哪部分文件 restart容器就行
  • 通过volumns数据卷映射传到容器中的,修改是可以实时生效的,比如源码。

  • 但像是.air.toml文件,虽然也映射到容器中,但是在容器启动是执行的,所以修改了需要restart 容器再次执行。

  • 对于影响镜像构建的部分,比如dockerfile.dev文件的修改,可能需要重新构建镜像。

  • 镜像构建时会使用缓存,如果需要弃用缓存,docker-compose build --no-cache service_name 需要添加–no-cache参数。

  • docker-compose up会使用现有容器重启,如果修改了docker-compose.yml中的运行时配置,,需要添加–force-recreate重新创建容器。

    运行时配置修改,比如:

    • 上面的添加security_opt、cap_add配置

    • 修改ports端口

    • volumns修改

    • 环境变量修改

    问题三:数据库初始化文件中文乱码

问题:

在这里插入图片描述

解决方案:

init.sql头部添加utf-8编码

SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;

在这里插入图片描述

演示:

在这里插入图片描述

问题四:mysql存在残存文件未清理,导致启动失败

报错:

project-mysql-1  | 2025-09-08 05:05:07+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.43-1.el9 started.
project-mysql-1  | 2025-09-08 05:05:07+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
project-mysql-1  | 2025-09-08 05:05:07+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.43-1.el9 started.
project-mysql-1  | 2025-09-08 05:05:07+00:00 [Note] [Entrypoint]: Initializing database files
project-mysql-1  | 2025-09-08T05:05:07.860388Z 0 [Warning] [MY-011068] [Server] The syntax '--skip-host-cache' is deprecated and will be removed in a future release. Please use SET GLOBAL host_cache_size=0 instead.
project-mysql-1  | 2025-09-08T05:05:07.862556Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.43) initializing of server in progress as process 80
project-mysql-1  | 2025-09-08T05:05:07.864096Z 0 [ERROR] [MY-010457] [Server] --initialize specified but the data directory has files in it. Aborting.
project-mysql-1  | 2025-09-08T05:05:07.864104Z 0 [ERROR] [MY-013236] [Server] The designated data directory /var/lib/mysql/ is unusable. You can remove all files that the server added to it.
project-mysql-1  | 2025-09-08T05:05:07.864143Z 0 [ERROR] [MY-010119] [Server] Aborting
project-mysql-1  | 2025-09-08T05:05:07.864238Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.43)  MySQL Community Server - GPL.

[Server] --initialize specified but the data directory has files in it. Aborting. 存在残存文件未清理。

首次启动mysql会执行这些脚本,若后续修改脚本需要删除mysql_data文件夹下的数据再重启。


网站公告

今日签到

点亮在社区的每一天
去签到