文章目录
- 前言
- 前后端不完全独立
- docker 部署
- mysql
- redis
- rbac
- docker compose 部署
- 部署 nginx
- 前后端独立部署
前言
项目地址:https://gitee.com/Cauchy_AQ/rbac
项目前端使用 vue3 并且由 vite 构建,后端采用 gin 框架,搭建了一个简易的权限管理系统(rbac)。本节对该项目进行容器化部署。
前后端不完全独立
代码地址:https://gitee.com/Cauchy_AQ/rbac/tree/master/
前后端部署不完全独立,指通过 vue 的 build 构建后,返回一个静态页面 index.html,然后将构建后的文件放置后端项目中,并由后端返回该页面。
docker 部署
由于项目需要使用 mysql 和 redis 两个数据库,所以除了本体项目会使用一个 docker 镜像构建,数据库也会分别独立使用 docker 容器部署。
mysql
在后端项目中,mysql 的配置如下:
mysql:
host: mysql57
port: 3306
user: root
password: root
db: wms
max_open_conns: 100
max_idle_conns: 20
指定 mysql 的主机名是 mysql57
,端口 3306,用户 root,密码 root,数据库 wms。
除了上述配置外,项目还需要初始化构建数据,sql 文件在后端项目中有,需要在构建 mysql 镜像时一并导入执行数据库的初始化工作。
如上图,在本地 /home/cauchy/docker/mysql
文件夹下,将后端项目中的 sql 文件拷贝过来 rbac.sql
,并且编写 Dockerfiler
文件:
FROM mysql:5.7
COPY rbac.sql /docker-entrypoint-initdb.d/
容器内目录 /docker-entrypoint-initdb.d/
下的 .sql
文件会被检测到,并自动执行。所以在构建镜像时,将 sql 文件拷贝至该目录下。
docker build -t mysql57:1.0 .
docker run -itd -v /home/cauchy/docker/mysql/data:/var/lib/mysql --name mysql57 -e MYSQL_ROOT_PASSWORD=root --network rbac mysql57:1.0
执行上述语句构建 mysql57:1.0
镜像,并且 -v
将容器的 /var/lib/mysql
目录挂载到本地 data 目录,--name
指定运行容器名称 mysql57,-e MYSQL_ROOT_PASSWORD=root
指定 root 用户的密码,--network
指定容器关联的网络 rbac。
三个容器会绑定在同一个网络 rbac,用于网络通信,这样在项目容器中的配置文件指定 mysql 的 host
时,使用 mysql57
就能成功发现容器并正常通信了。
如果上述执行失败,可先创建网络:docker network create rbac
,再去执行上述 shell 指令。
redis
项目中使用的 redis 配置如下:
redis:
host: redis7
port: 6379
password: redis
db: 0
指定 redis 主机名是 redis7
,端口 6379,密码 redis,数据库 0。
同理构建 redis:
docker run -itd -v /home/cauchy/docker/redis/data:/data --network rbac --name redis7 redis:7.0 --requirepass redis
直接采用拉取的 redis:7.0
镜像构建 redis7
容器,--requirepass
指定需要认证的密码,容器的 /data
目录挂载到本地 data 目录。
如果没有镜像,可先拉取镜像:docker pull redis:7.0
,再去执行上述 shell 指令。
rbac
该项目在部署前还需调整一下相关配置:
- 前端构建
vite.config.ts
文件调整
server: {
// 监听地址
host: "127.0.0.1",
// 端口号
port: 80,
// 服务启动时是否自动打开浏览器
open: true,
// 允许跨域
cors: true,
// 自定义代理规则
proxy: {},
}
修改 host 为服务器的 ip 地址
.env.development
文件调整
ENV = 'development'
VITE_APP_BASE_URL = 'http://127.0.0.1:8080'
修改 VITE_APP_BASE_URL
为后续实际访问服务的 url,即 http://服务器ip:服务监听端口
,本项目配置端口在 conf/conf.yaml
中是 8080
。
npm run build
构建前端项目
构建成功后会生成一个 dist
目录,直接拷贝到后端的 static
目录下。
- 后端返回
在后端中,routers/routers.go
添加如下代码:
r.LoadHTMLGlob("static/dist/*.html")
r.Static("/assets", "./static/dist/assets")
r.GET("/", func(c *gin.Context) {
if strings.HasSuffix(c.Request.RequestURI,".js") {
c.Header("Content-Type", "application/javascript; charset=utf-8")
} else {
c.Header("Content-Type", "text/html; charset=utf-8")
}
c.HTML(http.StatusOK, "index.html", nil)
})
r.NoRoute(func(c *gin.Context) {
if strings.HasSuffix(c.Request.RequestURI, ".js") {
c.Header("Content-Type", "application/javascript; charset=utf-8")
} else {
c.Header("Content-Type", "text/html; charset=utf-8")
}
c.HTML(http.StatusOK, "index.html", nil)
})
LoadHTMLGlob
加载静态页面,Static
指定访问静态文件 js、css
时去哪个实际目录访问,GET("/", ...)
所有访问都会返回 index.html
页面,NoRoute
路由不存在时也返回 index.html
页面。由于获取前端界面需要 http 请求发给后端,后端会返回对应的资源文件,即上述的 js、css 文件,那么这些上行的路由在实际的路由中是不存在的,所以在此需要指定不存在路由是也同样返回界面,而不是报错。
对源码做上述修改后,就可以来构建基础镜像了。
后端的源码上传到服务器 /home/cauchy/docker/rbac
上:
编写 Dockerfile
文件:
FROM golang:1.22-alpine AS builder
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
WORKDIR /build
COPY . .
RUN go mod tidy
RUN go build -o rbac .
FROM scratch
WORKDIR /app
COPY ./conf /app/conf
COPY ./static /app/static
COPY --from=builder /build/rbac .
ENTRYPOINT ["/app/rbac"]
这里采用分阶段构建,实际运行仅需要一个二进制文件和相关配置文件。
先拉取 golang:1.22-alpine
镜像提供 go 的基础环境,ENV
中指定了程序需要的的环境变量,WORKDIR
指定工作目录,COPY
拷贝当前宿主机 Dockerfile
文件所在的同级所有文件到容器中工作目录下 /build
,RUN
执行构建 gin 项目的指令,最终生成一个二进制文件 rbac
。接下来拉取 scratch
镜像作为最终运行程序的环境,拷贝上一个容器中的二进制文件 rbac
,源码下的配置文件夹 conf
,前端构建后的文件夹 static
,到这个小镜像的工作目录下,然后 ENTRYPOINT
指定执行这个二进制文件即可。
执行构建指令:
docker build -t rbac:1.0 .
docker run -itd -p 8080:8080 --network rbac --name rbac rbac:1.0
创建 rbac:1.0
镜像,构建容器 rbac
,开放宿端口 8080
映射为容器内的 8080
端口。(服务器需要放行这个端口)
至此,三个 docker 容器,依此部署,成功部署该项目。
docker compose 部署
使用 docker compose 管理编排多个容器,按照依赖顺序构建容器服务。
首先需要在项目源码(后端)中添加新文件:docker-compose.yml
,并编写如下内容:
version: "3.8"
services:
mysql57:
#build:
# context: ../mysql/
# dockerfile: Dockerfile
image: mysql:5.7
environment:
- "MYSQL_ROOT_PASSWORD=root"
volumes:
- mysqlData:/var/lib/mysql
- ./rbac.sql:/docker-entrypoint-initdb.d/rbac.sql
networks:
- rbac
restart: always
redis7:
image: redis:7.0
command: redis-server --requirepass redis
restart: always
volumes:
- redisData:/data
networks:
- rbac
rbac:
build: ./
command: sh -c "sleep 10; ./wait-for.sh mysql57:3306 redis7:6379 -- ./rbac"
restart: on-failure
ports:
- "8080:8080"
depends_on:
- mysql57
- redis7
networks:
- rbac
networks:
rbac:
volumes:
mysqlData:
redisData:
详细的 docker compose
模板文件可参考:https://vuepress.mirror.docker-practice.com/compose/
上述文件详细说明:
version
:当前 docker compose file 的版本,可以查看 docker 引擎的版本(docker compose version
),然后对照该表 这里,一般默认3.8
即可services
:描述一组服务,必须写build
:在启动容器之前,根据Dockerfile
构建镜像, 然后根据构建的镜像启动容器。该命令下的context
指定Dockerfile
所在的上下文目录位置mysql57
、redis7
、rbac
:都代表服务的名称,docker-compose
文件中服务名称都是唯一的。要指定容器的名称,可以使用container_name
模板命令,指定特定名称,每个服务器容器名称唯一image
:指定启动容器使用的镜像environment
:给容器启动指定环境变量,env_file
模板命令也可以指定某个文件作为环境变量载入volumes
:指定宿主机与容器目录或文件的映射,但是在docker compose
中不会主动创建未存在的目录,可以通过上述代码中最后的声明命令声明要挂载的目录networks
:指定启动容器使用网桥,同样未存在的network
也需要声明restart
:指定当前容器的重启策略,always
表示总是运行,on-failure
表示失败后重启,详细可参考 这里command
:覆盖容器启动后默认执行的命令depends_on
:服务启动依赖的其他服务,但是当前服务不会等待依赖的服务完全启动之后才启动ports
:宿主机和容器的端口映射
可以看到,rbac
服务 command
命令中使用了 sleep 10;
还用了 ./wait-for.sh
。按理执行这个脚本文件,会在启动 rbac
容器前先启动好 mysql、redis,但是我不知道为什么行不通,所以索性强制休眠 10 秒,等待另两个服务完成构建。
这个脚本文件参考:https://github.com/Eficode/wait-for,用于解决上述 depends_on
遗留的问题,帮助服务更好的完成按依赖顺序构建。
当然执行完这个 docker-compose.yml
文件,如配置中所示会构建一个新镜像,rbac
服务中 build: ./
,即会在当前同级目录下找到 Dockerfile
文件构建,我们重新来看一下这个文件。当前目录文件如下所示:
Dockerfile
:与之前分步构建时大致相同,不过换了一个镜像(不是 scratch 这个小镜像了),因为 wait-for.sh
脚本文件需要依赖 netcat
,需要借助 apt-get
环境下载。(前面说了,我没使用成功,不能正常的等待依赖服务执行,所以如果不想使用的也可以换个能正常执行 sleep 的小镜像就行,不必用 ubuntu:latest 这个偏大的镜像)
FROM golang:1.22-alpine AS builder
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
WORKDIR /build
COPY . .
RUN go mod tidy
RUN go build -o rbac .
FROM ubuntu:latest
WORKDIR /app
COPY ./wait-for.sh .
COPY ./conf /app/conf
COPY ./static /app/static
COPY --from=builder /build/rbac .
RUN set -eux; \
apt-get update; \
apt-get install -y \
--no-install-recommends \
netcat; \
chmod 755 wait-for.sh;
# ENTRYPOINT ["/app/rbac"]
注释 # ENTRYPOINT ["/app/rbac"]
,因为在 docker-compose.yml
中执行完 sleep 命令后,会去执行这个二进制文件,注意到文件中的 -- ./rbac
。
至此,通过 docker compose 部署本项目也成功了。
部署 nginx
由于要通过域名访问后端项目地址,那么可以通过加一个 nginx 容器,做一层正向代理。
第一步首先要进行域名解析:
- 服务器的管理后台添加域名解析
- 域名管理解析网站主机 IP
- 防火墙开放 80 端口:
sudo apt install firewalld
sudo firewall-cmd --zone=public --add-port=80/tcp --permanent
sudo firewall-cmd --reload
在 docker-compose.yml
中添加一个 nginx
镜像:
nginx125:
image: nginx:1.25-alpine
ports:
- "80:80"
networks:
- rbac
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
nginx.conf
配置如下:
user root;
worker_processes 2;
error_log /error.log;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://172.18.0.5:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
}
}
}
这里域名 server_name
自行更换,proxy_pass
代理地址这里使用 docker inspect xxx
查看 rbac
这个 docker 的 ip,若换成自己的服务器 ip 或 localhost 也许也行,自行实验。
重新一键部署 docker compose up
即可通过域名访问,或是直接使用服务器 ip 访问。
前后端独立部署
代码地址:https://gitee.com/Cauchy_AQ/rbac/tree/rbac/
前面部署是通过后端程序返回前端静态页面完成的,也可在不修改源代码的情况下(之前部署需要在后端添加返回前端界面的代码),独立部署前后端,并且也通过 docker-compose.yml
一键部署。
在前端 frontend
文件夹下,需要自行修改 .env.development
,vite.config.ts
,并且添加 Dockerfile
和一个 nginx.conf
文件。
.env.development
:配置后端的地址VITE_APP_BASE_URL = http://121.xxx.xxx.7:8080
,后端 ip + 监听的端口vite.config.ts
:server
模块配置host
为后端主机地址
nginx.conf
:
user root;
worker_processes 4;
error_log /error.log;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
index index.html index.htm;
include mime.types;
default_type application/octet-stream;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
alias /usr/share/nginx/html/assets/;
expires 1h;
}
location ^~ /api/ {
proxy_pass http://121.xxx.xxx.7:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
}
}
}
Dockerfile
:
FROM node:latest AS builder
WORKDIR /app
COPY package*.json ./
RUN npm i
COPY . .
RUN npm run build
FROM nginx:1.25-alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
即在前端中,需要单独配置一个 docker,并使用 nginx 部署,上述配置不详细解释了。
在后端 backend
文件夹下,也需要单独配置一个 docker:
FROM golang:1.22-alpine AS builder
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
WORKDIR /build
COPY . .
RUN go mod tidy
RUN go build -o rbac .
FROM alpine:latest
WORKDIR /app
COPY ./conf /app/conf
COPY --from=builder /build/rbac .
RUN echo "https://mirrors.tuna.tsinghua.edu.cn/alpine/latest-stable/main" > /etc/apk/repositories && \
echo "https://mirrors.tuna.tsinghua.edu.cn/alpine/latest-stable/community" >> /etc/apk/repositories
RUN apk add --no-cache busybox
目录结构如下:
|- backend
|- Dockerfile
|- ***
|- frontend
|- Dockerfile
|- nginx.conf
|- ***
|- docker-compose.yml
这里仍然直接在一台服务器上部署,通过 docker compose
一键部署,配置如下:
version: "3.8"
services:
mysql57:
image: mysql:5.7
environment:
- "MYSQL_ROOT_PASSWORD=root"
volumes:
- mysqlData:/var/lib/mysql
- ./backend/rbac.sql:/docker-entrypoint-initdb.d/rbac.sql
networks:
- rbac
restart: always
redis7:
image: redis:7.0
command: redis-server --requirepass redis
restart: always
volumes:
- redisData:/data
networks:
- rbac
rbac:
build: ./backend
command: sh -c "sleep 5; ./rbac"
restart: on-failure
ports:
- "8080:8080"
depends_on:
- mysql57
- redis7
networks:
- rbac
vue:
build: ./frontend
ports:
- "80:80"
restart: always
networks:
rbac:
volumes:
mysqlData:
redisData:
可以看到,vue 这个 docker 容器(实际容器名称 rbac-vue-1)不在 network
为 rbac
的网络中。前端界面通过 nginx 代理缓存静态资源返回,所有 rpc 通信会统一前缀 /api
并转到后端对应接口返回响应数据。这里图个方便直接全部丢在一个 docker-compose.yml
一键生成所需容器,并自动化部署。前端这个 nginx 容器独立出去也是可以的。
到此,第一个前后端项目部署就完成了,这次记录并未涉及很复杂的操作。我也使用 SSL 证书,只是自己简单购买了一个域名,写了一个前后端小项目,快速的部署了一下。文章如有错误,烦请指正。