不知道各位有没有配过开发环境和部署生产环境,我觉得但凡是搞过的,都要踩很多坑。
比如,原项目是在linux上开发的,clone下来用windows的npm install或者yarn install后发现不work。
或者在windows上开发好后,在linux上部署的时候一堆坑,比如nginx配置问题了,比如CORS问题了,比如vite打包问题了,node_modules依赖问题,等等。
甚至你跟另一个人在同一个型号同样版本的开发环境下,把你的文件夹给他,他都不一定能直接运行。
但是Docker能解决上面大部分问题。

什么是Dcoker?

Docker是一个开源的容器化应用平台,它允许开发者打包一个应用以及其依赖库到一个可移植的容器中,然后发布到任何运行docker的机器上,而不需要考虑环境配置。Docker的容器化是基于命名空间(namespace)的,它允许容器拥有独立的系统资源视图,包括进程ID、网络接口、挂载点等。这种隔离确保了容器内的变更不会影响到宿主机或其他容器。所以我们完成可以build很多个docker image(可以理解为源文件打包好的程序),通过docker network将不同image run(可以理解为实际运行的程序)的容器连接起来,实现容器化应用。每个image只需要实现一个单一集成功能(数据库,反向代理)即可。

Docker安装

Docker的安装有很多种,但有时候稍不注意会猜到大坑,这里我介绍在linux上安装docker的方法(deb系)

1
2
3
4
5
6
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt update
sudo apt install docker-ce

注意,如果是新手,不要运行apt install dockerapt install docker.io别问我怎么知道的

Docker基本命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 开关服务类
sudo systemctl restart docker
sudo systemctl status docker
sudo systemctl daemon-reload
# 镜像类
docker pull xx
docker push
docker images
docker rmi
docker tag
# 容器类
docker run
docker ps
docker stop
docker rm
docker exec
docker logs

配置Proxy 极为重要

由于一些原因现在docker镜像的下载上传受限制,而且国内的镜像站也基本都不work了,所以配置好自己的proxy很重要,这里以我的配置经历给参考(可能有多余操作)

1
2
3
4
sudo vim /etc/resolv.conf
#add those lines
nameserver 8.8.8.8
nameserver 8.8.4.4
1
2
cd  /etc/systemd/system/docker.service.d/
sudo vim http-proxy.conf

你大概会看到这几行

1
2
3
4
[Service]
Environment="HTTP_PROXY=http://example.com:8080/"
Environment="HTTPS_PROXY=http://example.com:8080/"
Environment="NO_PROXY=http://your_example_proxy.com"

没有也不要紧,加上以下内容:(如果你用Clash)

1
2
3
4
Environment="HTTP_PROXY=http://127.0.0.1:7890/"
Environment="HTTPS_PROXY=http://127.0.0.1:7890/"
#这行是镜像站,随便填一个就好
Environment="NO_PROXY=http://your_example_proxy.com"

或者你用V2ray

1
2
3
Environment="HTTP_PROXY=http://127.0.0.1:10808/"
Environment="HTTPS_PROXY=http://127.0.0.1:10808/"
Environment="NO_PROXY=http://your_example_proxy.com"

Dockerfile

因为对于开发而言,一般都是各种FROM去拉取一些基础镜像,然后安装各种依赖,然后RUN各种命令,最后再COPY各种文件到容器里,最后再CMD运行项目。所以我先讲一个简单的Dockerfile,然后举一个我实际再跑的项目的比较复杂且规范的Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
# 使用官方的 Python 3 基础镜像
FROM python:3
# 将当前docker目录下的文件复制到镜像中的 /app 目录
COPY ./docker /app
# 设置工作目录
WORKDIR /app
# 安装依赖包
RUN pip install -r requirements.txt
# 暴露容器监听的端口
EXPOSE 80
# 定义容器启动时运行的命令
CMD ["python", "app.py"]

Docker会根据Dockerfile的描述来一步一步构建能够最终运行程序的镜像
运行命令为:

1
docker build -t my-app:latest .

再来看一个复杂一点的文件来感受一下
先看具体运行的命令

1
docker compose -f docker-compose.yml up -d

我自己的项目总的来说是一个vite打包好的前端vue运行3000端口(需要预先打包),一个后端Springboot运行8080端口,redis中间件占用6379端口,一个mysql数据库占用3306端口,一个nginx反向代理运行80端口。应用vue可以提前打包所以这里只需要运行4个服务即可。需要FROM引几个基础镜像,然后COPY一些文件到容器里,然后RUN一些命令比如mysql的数据导入,最后再CMD运行项目。
先在项目文件夹执行

1
yarn build

在IDEA中,找到target文件夹里的xxx.-SNAPSHOT.jar文件,将这个文件复制到backend文件夹里面(不要按照网上说的用Artifacts导出)
Nginx 配置文件 nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;

location / {
index index.html;
try_files $uri $uri/ /index.html;
}

location /api {
proxy_pass http://backend:8080/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
}

Redis 配置文件 redis.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Redis 服务器的端口号(默认:6379)
port 6379

# 绑定的 IP 地址,如果设置为 127.0.0.1,则只能本地访问;若设置为 0.0.0.0,则监听所有接口(默认:127.0.0.1)
bind 0.0.0.0

# 设置密码,客户端连接时需要提供密码才能进行操作,如果不设置密码,可以注释掉此行(默认:无)
# requirepass foobared
requirepass 123456

# 设置在客户端闲置一段时间后关闭连接,单位为秒(默认:0,表示禁用)
# timeout 0

# 是否以守护进程(daemon)模式运行,默认为 "no",设置为 "yes" 后 Redis 会在后台运行
daemonize no

# 设置日志级别(默认:notice)。可以是 debug、verbose、notice、warning
loglevel notice

# 是否启用 AOF 持久化,默认为 "no"。如果设置为 "yes",将在每个写操作执行时将其追加到文件中
appendonly no

同时需要在Navicat或者其他软件把你的项目数据库全部导出。
springboot的配置文件application.properties也可以跟上述配置文件放在一起,配置好后在docker network中做更改更方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Spring Configuration
spring.application.name=backend
server.port = 8080
# Aop Configuration
spring.aop.auto=true
# Postgres Configuration
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
# QQ mail Configuration
spring.mail.host=smtp.qq.com
spring.mail.port=587
spring.mail.username= xx@qq.com #需要更换为自己的邮箱服务
spring.mail.password= xxxx #需要更换为自己的邮箱服务密钥
spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
# Redis Configuration
spring.data.redis.host=${SPRING_REDIS_HOST}
spring.data.redis.port=${SPRING_REDIS_PORT}
spring.data.redis.password=${SPRING_REDIS_PASSWORD}
spring.data.redis.database=0
# MyBatis-Plus Configuration
mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.type-aliases-package=com.mybatis-plus.model
spring.servlet.multipart.max-file-size= 10MB
spring.servlet.multipart.max-request-size= 100MB
spring.web.resources.static-locations=file:./uploads/

最后的docker文件
docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
version: '3.8'

services:

nginx:
image: nginx:latest
container_name: scu_oasystem_nginx
ports:
- "80:80"
volumes:
- ../frontend/dist:/usr/share/nginx/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- backend #等backend启动好了再启动nginx
networks:
- oasystem_network

backend:
image: openjdk:21
container_name: scu_oasystem_backend
working_dir: /app
volumes:
- ../backend/backend.jar:/app/backend.jar
- ./application.properties:/app/application.properties #移动配置文件到容器中
ports:
- "8080:8080"
command: java -jar backend.jar --spring.config.location=/app/application.properties
environment: #各项环境变量设置,application.properties可以通过${}直接读取环境变量
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/oa_database?useSSL=false&allowPublicKeyRetrieval=true #直接通过docker network连接db
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=123456
- SPRING_REDIS_HOST=redis #直接通过docker network连接redis
- SPRING_REDIS_PORT=6379
- SPRING_REDIS_PASSWORD=123456
depends_on:
db:
condition: service_started #等待db启动,避免初始化错误
redis:
condition: service_started #等待redis启动,避免初始化错误
networks:
- oasystem_network

redis:
image: redis:6.0
container_name: scu_oasystem_redis
ports:
- "6379:6379"
volumes:
- ./redis.conf:/etc/redis/redis.conf #移动配置文件到容器中
- ./redis/data:/data #持久化保存数据
- ./redis/logs:/logs #持久化保存数据
command: redis-server /etc/redis/redis.conf
depends_on:
db:
condition: service_started
networks:
- oasystem_network

db:
image: mysql:5.7
container_name: scu_oasystem_db
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=123456 #配置root密码(在高版本mysql中可能无效)
volumes:
- /usr/local/mysql/conf:/etc/mysql/conf.d #持久化保存数据
- ./mysql/data:/var/lib/mysql #持久化保存数据
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # 添加此行以挂载 init.sql 文件,初始化数据库
networks:
- oasystem_network

networks:
oasystem_network:
driver: bridge #最常见的网络模式