Docker前后端项目部署(完整篇)

菠萝追雪 2024-07-13 13:07:02 阅读 64

本文详细讲解了如何使用 Docker 构建镜像并部署前后端项目,包括如何编写 Dockerfile 和启动容器的方式。在实践过程中,作者发现了一些常见问题,并提供了解决方案,例如如何解决无法连接数据库的问题、如何配置 nginx 代理等。本文将对想要深入了解 Docker 镜像部署前后端项目的读者提供帮助。

目录

安装docker

开始前

部署mysql8

生成挂载目录和配置

创建容器

 一些问题

部署后端项目

构建镜像

启动容器

一些问题

Nginx部署前端项目

构建nginx镜像

启动容器

部署多个网站(扩展)

善后工作

Docker-Compose实现


安装docker

此时你有一台服务器,首先安排上docker

<code># 检查是否安装

yum list installed | grep docker

# 在线安装docker必备依赖包

yum install -y yum-utils device-mapper-persistent-data lvm2

# 添加阿里云有关 Docker 的软件源(即Docker的存储库)

yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装docker

yum install -y docker-ce docker-ce-cli containerd.io

# docker默认配置文件中设置阿里云的镜像地址,更多配置参考 https://www.cnblogs.com/yakniu/p/16329611.html

vi /etc/docker/daemon.json

{"registry-mirrors": ["https://gg3gwnry.mirror.aliyuncs.com"]}

开始前

为本项目创建一个虚拟网络network,用于多个容器间通信如连接数据库等

docker network create --driver=bridge --gateway=192.168.0.0 --subnet=192.168.0.0/16  mynet-1

创建工作目录

mkdir /mydata

cd /mydata

部署mysql8

生成挂载目录和配置

mkdir  mysql mysql/conf mysql/data mysql/mysql-files

vi mysql/conf/cus-docker.cnf

[mysqld]

wait_timeout=31536000

interactive_timeout=31536000

# 数据库忽略大小写

lower_case_table_names = 1

#免密登录,如果忘记密码启用这个配置

#skip-grant-tables

# mysql8 换了密码加密方式,要让数据库使用旧的密码加密方式 , 身份插件mysql_native_password 本地密码

authentication_policy=mysql_native_password

#密码失效时间-永不过期

default_password_lifetime=0

# 127.0.0.1只能本地访问,0.0.0.0接收任何来源ip的连接访问

bind-address = 0.0.0.0

# 最大连接数

max_connections = 2000

# 设置数据库时区为正8区

default-time_zone = '+8:00'

启动容器

docker pull mysql:8.0.31

挂载的卷

/etc/mysql/conf.d 

mysql的配置文件my.cnf位于/etc/my.cnf,my.cnf会优先导入/etc/mysql/conf.d目录下的所有扩展名为cnf的配置文件

/var/lib/mysql/ 

mysql的日志、数据存放目录,如导入一个数据库surveyking.sql就会增加一个surveyking的目录

docker run --name cus-mysql --network mynet-1 --ip 192.168.0.2 --restart=always --privileged=true -v /mydata/mysql/conf:/etc/mysql/conf.d -v /mydata/mysql/data:/var/lib/mysql/ -v /mydata/mysql/mysql-files:/var/lib/mysql-files/ -p 3306:3306  -e  MYSQL_ROOT_PASSWORD=123456 -e LANG=zh_CN.utf8 -e LANGUAGE=zh_CN.utf8  -d mysql:8.0.31

mysql容器初始化

宿主机拷贝后端项目数据库到mysql容器中

新增后端项目数据库所需的用户和密码,并赋予该数据库所有的权限

docker cp /mydata/db/surveyking.sql cus-mysql:/tmp;

docker exec -it cus-mysql mysql -u root -p

create database surveyking;

use surveyking;

source /tmp/surveyking.sql;

flush privileges;

alter user 'hao'@'%' identified by 'lihao@123456' password expire never;

alter user'hao'@'%' identified with mysql_native_password by 'lihao@123456';

grant all privileges on surveyking.* to 'hao'@'%';

flush privileges;

 一些问题

    mysql8容器启动后如果一直登录不上(密码错误),Access denied for user 'root'@'localhost' (use Password Yes) 可以开启免密登录,初始化完成再关闭免密登录

因为mysql8更改了登录方式使用密码加密(明文和密文打八辈子也不等好吧)

    如果容器内可以正常进入mysql,其它容器或者我们的navicat远程工具无法连上mysql,需要检查登录的用户是否是允许的主机地址,select user,host from mysql.user;

如果host是%即允许任何主机地址,那么不可远程连接很有可能是配置文件bind-address设置了localhost或者127.0.0.1(我遇到的问题),改为0.0.0.0即可

    将主机目录下的文件夹挂载到容器的文件夹后,进入到docker容器内对应的挂载目录中,运行命令ls后提示

ls: cannot open directory .: Permission denied

原因是CentOS7中的安全模块selinux把权限禁掉了,解决方法容器启动时--privileged=true

部署后端项目

拷贝后端jar包到工作目录 /mydata

构建镜像

/mydata/surveyking-dockerfile文件如下

FROM openjdk:8

RUN mkdir -p /home/app

# 设置工作目录

WORKDIR /mydata

# apt-get update && apt-get install -y iputils-ping;

COPY surveyking-v1.5.1.jar /home/app/surveyking-v1.5.1.jar

EXPOSE 8080

# 设置容器启动时运行的命令

CMD ["java", "-jar","/home/app/surveyking-v1.5.1.jar"]

docker build -f surveyking-dockerfile -t surveyking-image:1.5.1 .

启动容器

docker run -p 8080:8080 --name surveyking-server --network mynet-1 --ip 192.168.0.3 -d surveyking-image:1.5.1

一些问题

docker ps -a 发现如果启动容器后自动退出,即项目运行出现异常

docker logs surveyking-server 检查启动日志

按网上的资料说同一个自定义network下的多个容器间可以 ping 容器名,但是一直测试报错ping的容器名是未知的地址,原因未遂,最后还是决定使用固定ip即--ip的参数在容器run的时候指定

后端项目容器连接数据库的容器的url:

jdbc:mysql://192.168.0.2:3306/surveyking?characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&useUnicode=true

项目启动没有问题,但是一旦访问到数据库响应异常docker logs surveyking-server查看后端项目日志,访问数据库报错如下

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.

网上各种答案都说是url没有useSSL=false,各种答案都试过都没得,困扰我灰常的久。尝试将这个jar包运行在主机电脑连接远程服务器容器的数据库,居然数据库访问就正常了。

解决尝试:navicat试连,用户要有权限,最重要url上的mysql地址在jar包运行的容器中能ping通 (使用自定义的network,能ping通后访问就解决了)

⭐⭐注意,mysql在初始化完后一定要关闭免密登录,否则也会导致上面的问题

Nginx部署前端项目

创建挂载卷目录(/mydata工作目录)

1. mkdir nginx nginx/conf /nginx/html /nginx/logs

2. 拷贝前端打包项目到/nginx/html下

3. vi nginx/conf/nginx.conf

#定义Nginx运行的用户和用户组

#user nobody;

#开启的线程数(默认为1),一般跟逻辑CPU核数一致

worker_processes 1;

#制定日志路径,级别。这个设置可以放入全局块,http块,server块,级别以此为:debug | info | notice | warn | error | crit | alert | emerg

#error_log logs/error.log;

#error_log logs/error.log notice;

#error_log logs/error.log info;

#指定nginx进程运行文件存放地址

#pid logs/nginx.pid;

events {

accept_mutex on; #设置网路连接序列化,防止惊群现象发生,默认为on

multi_accept on; #设置一个进程是否同时接受多个网络连接,默认为off

use epoll; #事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport

#单个进程最大连接数(最大连接数=连接数*进程数)

#根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行。每个进程允许的最多连接数,理论上每台nginx服务器的最大连接数为。

worker_connections 1024;

}

http {

#文件扩展名与文件类型映射表

include mime.types;

#默认文件类型

default_type application/octet-stream;

#access_log off; #取消服务日志

#引用日志main

#access_log logs/access.log main;

#下面代码为日志格式的设定,main为日志格式的名称,可自行设置,后面引用

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '

# '$status $body_bytes_sent "$http_referer" '

# '"$http_user_agent" "$http_x_forwarded_for"';

#开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。

#sendfile指令指定 nginx 是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度,降低系统uptime。

sendfile on;

# 当使用sendfile 函数,tcp_nopush 才起作用,是tcp协议栈中的知识点

# 当tcp_nopush = on 时,会调用tcp_cork 方法,是默认的,就是收到的数据报不会立即发送出去,而是等到数据报最大时,一次性传输出去,有利于解决网络堵塞。

#tcp_nopush on;

# 客户端连接超时时间

# = 0 : 表示禁用长连接。

# = x :表示长连接timeout

#keepalive_timeout 0;

keepalive_timeout 65;

#HttpGZip模块配置

#开启gzip压缩

#gzip on;

#设置允许压缩的页面最小字节数

#gzip_min_length 1k;

#申请4个单位为16K的内存作为压缩结果流缓存

#gzip_buffers 4 16k;

#设置识别http协议的版本,默认为1.1

#gzip_http_version 1.1;

#指定gzip压缩比,1-9数字越小,压缩比越小,速度越快

#gzip_comp_level 2;

#指定压缩的类型

#gzip_types text/plain application/x-javascript text/css application/xml;

#让前端的缓存服务器进过gzip压缩的页面

#gzip_vary on;

server {

listen 80;

server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {

# 设置虚拟主机的网站根目录

root html/website-surveyking;

# history 模式下需要加上这一行,防止刷新页面时404

try_files $uri $uri/ /index.html;

#设置虚拟主机默认访问的网页

index index.html index.htm;

}

#对 / 启用反向代理

#location /sk-api {

#proxy_pass http://127.0.0.1:8080;

#以下是一些反向代理的配置可删除

#proxy_redirect off;

#rewrite "^/sk-api/(.*)$" /$1 break ; # 最终url中去掉/api前缀

#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP

#proxy_set_header Host $host;

#proxy_set_header X-Real-IP $remote_addr;

#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

#client_max_body_size 10m; #允许客户端请求的最大单文件字节数

#client_body_buffer_size 128k; #缓冲区代理缓冲用户端请求的最大字节数

#proxy_connect_timeout 300; #nginx跟后端服务器连接超时时间(代理连接超时)

#proxy_send_timeout 300; #后端服务器数据回传时间(代理发送超时)

#proxy_read_timeout 300; #连接成功后,后端服务器响应时间(代理接收超时)

#proxy_buffer_size 4k; #设置代理服务器(nginx)保存用户头信息的缓冲区大小

#proxy_buffers 4 32k; #proxy_buffers缓冲区,网页平均在32k以下的话,这样设置

#proxy_busy_buffers_size 64k; #高负荷下缓冲大小(proxy_buffers*2)

#proxy_temp_file_write_size 64k; #设定缓存文件夹大小,大于这个值,将从upstream服务器传

#}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html

#

error_page 500 502 503 504 /50x.html;

location = /50x.html {

root html;

}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80

#

#location ~ \.php$ {

# proxy_pass http://127.0.0.1;

#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000

#

#location ~ \.php$ {

# root html;

# fastcgi_pass 127.0.0.1:9000;

# fastcgi_index index.php;

# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;

# include fastcgi_params;

#}

# deny access to .htaccess files, if Apache's document root

# concurs with nginx's one

#

#location ~ /\.ht {

# deny all;

#}

}

# another virtual host using mix of IP-, name-, and port-based configuration

#

#server {

# listen 8000;

# listen somename:8080;

# server_name somename alias another.alias;

# location / {

# root html;

# index index.html index.htm;

# }

#}

# HTTPS server

#

#server {

# listen 443 ssl;

# server_name localhost;

# ssl_certificate cert.pem;

# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;

# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;

# ssl_prefer_server_ciphers on;

# location / {

# root html;

# index index.html index.htm;

# }

#}

}

构建nginx镜像

/mydata/nginx-dockerfile文件如下

FROM centos:7

#给基础镜像centos安装必备的环境

# 加上“rpm --rebuilddb &&”是为了防止数据库损坏而影响yum安装。

# 注意,比较保险的做法是有多少个yum命令,就加多少个“rpm --rebuilddb &&”在yum前面。

# 另外yum install一定要安装的是pcre,zlib,net-tools,make,gcc,wget,tar及其相关内容

RUN rpm --rebuilddb && yum install -y autoconf automake make wget proc-devel net-tools zlib zlib-devel make gcc g++ openssl-devel pcre pcre-devel tar

# 在线获取nginx压缩包(也可以在本地系统(centos)里先下载压缩包,再ADD命令将包加入到基础镜像/usr/src目录中)

RUN wget http://nginx.org/download/nginx-1.17.1.tar.gz

# 解压到当前目录

RUN tar -zxvf nginx-1.17.1.tar.gz

# 设置环境

WORKDIR nginx-1.17.1

# 配置nginx

RUN ./configure --prefix=/usr/local/nginx && make && make install

# RUN rm -rf /usr/src/nginx

ENV TimeZone=Asia/Shanghai

# 拷贝前端打包资源到nginx容器中

# COPY /mydata/website-surveyking /usr/local/nginx/html

#开启 80 和 443 端口

EXPOSE 80

#https

EXPOSE 443

#不能直接nginx,可能环境变量没有配置

CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]

docker build -f nginx-dockerfile -t cus-nginx-image:1.7 .

启动容器

因为上面镜像的dockerfile文件中指定了编译安装nginx的路径为/usr/local/nginx

挂载的卷

/usr/local/nginx/conf/nginx.conf:挂载nginx配置文件到宿主机中/usr/local/nginx/html:nginx的网站根目录/usr/local/nginx/logs:nginx的运行、错误等日志

docker run -p 81:80 --name cus-nginx --network mynet-1 --ip 192.168.0.4 --restart=always --privileged=true -v /mydata/nginx/conf/nginx.conf:/usr/local/nginx/conf/nginx.conf -v /mydata/nginx/logs:/usr/local/nginx/logs -v /mydata/nginx/html:/usr/local/nginx/html -d cus-nginx-image:1.7

到此项目部署完毕

部署多个网站(扩展)

nginx的配置基础

root和alias都是用于指定请求的静态文件的根目录

root:当Nginx接收到请求时,会将location后面的路径与root拼接起来作为文件的实际路径。这意味着,实际文件路径为root路径/location后面的路径。

例如 location /surveyking { root: html/p1; }

请求: /surveyking/index.html    实际:/surveyking/p1/surveyking/index.html 

alias:alias在构建文件路径时会直接使用location后面的路径匹配请求之后请求的剩余路径。这意味着,实际文件路径为alias路径/请求路径 - location后面的路径。

例如 location /surveyking { alias: html/p1; }

请求: /surveyking/project/index.html    实际:/surveyking/p1/project/index.html

反向代理

nginx反向代理可以实现每个api前缀的代理映射到不同的服务器端口进行处理

反向代理主要是解决后端服务器负载均衡和保证安全性,而请求转发则是实现服务器请求处理的调度和功能灵活性

location /sk-api {  proxy_pass  http://192.168.0.3:8080; }

需求:比如我想一个nginx部署多个网站项目,而不是根路径/直接映射到root指向的项目路径,而导致其它网站的location地址没有意义了

首先nginx.conf需要修改

location /surveyking {

     root   html/website-surveyking;

     try_files $uri $uri/ /surveyking/index.html;             

     index  index.html index.htm;

}

修改后就可以了吗,会报错

已拦截加载自“http://xx.xx.xx.x:81/assets/index.6e0363b3.js”的模块,它使用了不允许的 MIME 类型(“text/html”)。

来源为“http://xx.xx.xx.x:81/assets/index.6e0363b3.js”的模块加载失败。

因为vue工程打包后目录结构为

- assets  

    -xxx.js

- index.html

index.html对所有资源assets内的访问路径为http://xxx.xxx.xx.x/assets/xxx.js

需要给所有资源的访问路径拼接surveyking前缀,让nginx能够匹配location

vue项目中使用vite打包的话,增加配置即可

vite.conf.js

export default defineConfig(({ command, mode }) => {

const env = loadEnv(mode, process.cwd(), '')

let base;

if (command.indexOf('build')!==1) {

base = '/surveyking'

} else {

base = '/'

}

return{

base,

{option..}

}

}

到这问题还没结束,index.html页面可以正常加载css、js等静态资源渲染显示了

但是如果vue中点击就了路由跳转,使用了history的地址栏是不是surveyking前缀又没了,如果此时刷新,nginx将该刷新请求解析为/路由地址而不是使try_files $uri $uri/ /surveyking/index.html生效的/surveyking/路由地址

最后,将路由前缀也添加上即可解决,vue3 + vue-router解决方案

const router = createRouter({

    history: createWebHistory("/surveyking"),

    routes: constantRoutes

});

善后工作

如果是1核1G三个容器跑起来,慢慢地越来越卡,一直到程序崩溃,查看服务器的仪表盘,发现磁盘读流量异常的高,总是突然飙升,而cpu使用率则并没有太大的动静。

这里应该考虑服务器为容器调整合适的配置参数,如mysql中的InnoDB缓冲池大小、InnoDB日志文件大小、查询缓存大小等参,后端项目运行日志存储策略等

通过不断的摸索改造,可算得出以下的较为合适的配置🆒

mysql自定义配置中

vi /mydata/mysql/conf/cus-docker.cnf,新增

# mysql8 换了密码加密方式,要让数据库使用旧的密码加密方式

authentication_policy = mysql_native_password

#密码失效时间-永不过期

default_password_lifetime=0

# 设置数据库时区为正8区

default-time_zone = '+8:00'

# 设置MySQL使用的字符集

character-set-server = utf8mb4

# 设置MySQL使用的存储引擎

default-storage-engine = InnoDB

# 设置InnoDB的缓冲池大小,总内存的一半或更小的值

innodb_buffer_pool_size = 256M

# 设置InnoDB的日志文件大小(根据服务器性能和数据量进行调整),控制InnoDB事务日志的容量。将其设置为128M可能过高,尤其是在1核1G的服务器上。可以考虑将其降低一些,例如设置为64M或者更低,以减少磁盘I/O负载

innodb_redo_log_capacity = 32M

# 性能要求不高的环境,可以将其设置为0或2,以减少写入日志的频率,会增加数据丢失风险

innodb_flush_log_at_trx_commit = 0

# 限制磁盘I/O的资源使用

innodb_io_capacity = 150

innodb_io_capacity_max = 300

# 写入和读取I/O线程的数量,降低能减少I/O线程对服务器资源的竞争

innodb_write_io_threads = 8

innodb_read_io_threads = 4

# 让InnoDB自动调整并发线程的数量。这样可以充分利用服务器的CPU和I/O资源

innodb_thread_concurrency = 0

# 由于资源受限(1G),限制连接数

max_connections = 50

# 设置查询缓存大小(如果MySQL版本>=8.0,请注释或删除此行)

# query_cache_size = 0

# 设置临时表的最大数量(根据服务器性能和应用程序需求进行调整)

tmp_table_size = 64M

# 设置排序缓存大小(根据服务器性能和应用程序需求进行调整)

sort_buffer_size = 2M

# 设置线程栈大小(根据应用程序需求进行调整)

thread_stack = 256K

# 禁用DNS反向解析(根据服务器网络环境进行调整)

skip-name-resolve

innodb_log_buffer_size = 8M

# 此参数确定些日志文件所用的内存大小,以M为单位。缓冲区更大能提高性能,但意外的故障将会丢失数据。MySQL开发人员建议设置为1-8M之间

# 以下两个参数控制InnoDB事务日志文件的大小和缓冲区大小。在1核1G的服务器上,可以尝试将它们降低一些,

# 例如分别设置为256M和8M,以减少日志文件对磁盘空间的使用。(mysql>8,删除或注释,使用innodb_redo_log_capacity替换)

#innodb_log_file_size = 128M

# 此参数确定数据日志文件的大小,更大的设置可以提高性能,但也会增加恢复故障数据库所需的时间,

#innodb_log_files_in_group = 3

# 为提高性能,MySQL可以以循环方式将日志文件写到多个文件。推荐设置为3

jar容器启动时 ,指定日志配置

  --log-driver k8s-file --log-opt max-size=10m --log-opt max-file=3

这里列举一些分析和清理的一些命令

#运行容器CPU、内存、网络等系统资源指标

docker stats

#运行的容器的总大小

docker ps -s

#分析磁盘空间占用

docker system df

## 以下注意备份

#清理无用的容器、镜像和资源

docker system prune

#清理所有未被使用的镜像

docker image prune -a

#清理所有停止的容器

docker container prune

#清理悬挂的匿名卷

docker volume prune

#清理未使用的网络

docker network prune

Docker-Compose实现

上面的多个步骤,使用单体服务容器编排Docker-Compose技术通过一个docker-compose.yaml脚本实现,大致如下

工作目录 /mydata (在前面的基础上)

mkdir /mysql/initdb将数据库文件移动到initdb中vi mysql/initdb/surveyking-extend.sql

flush privileges;

alter user 'hao'@'%' identified by 'lihao@123456' password expire never;

alter user'hao'@'%' identified with mysql_native_password by 'lihao@123456';

grant all privileges on surveyking.* to 'hao'@'%';

flush privileges;

mysql容器运行后,默认执行docker.entrypoint.sh,该脚本会去/docker-entrypoint-initdb.d下寻找所有sh、sql等脚本执行

docker-compose.yaml

version: '3'

services:

nginx:

container_name: cus-nginx

image: surveyking

build:

context: /mydata

dockerfile: nginx-dockerfile

environment:

- TZ=Asia/Shanghai

ports:

- "80:80"

volumes:

- /mydata/nginx/html:/usr/local/nginx/html

- /mydata/nginx/conf/usr/lcoal/nginx/conf/nginx.conf

- /mydata/nginx/logs:/usr/local/nginx/logs

networks:

- mynet-1

mysql:

image: mysql:8,0.31

container_name: cus-mysql

environment:

- TZ=Asia/Shanghai

- MYSQL_ROOT_PASSWORD=123456

- MYSQL_DATABASE=surveyking

- MYSQL_USER=hao

- MYSQL_PASSWORD=lihao@123456

ports:

- "3306:3306"

volumes:

- /mydata/mysql/data:/var/lib/mysql

- /mydata/mysql/conf:/etc/mysql/conf.d/

- /mydata/mysql/initdb:/docker-entrypoint-initdb.d

networks:

- mynet-1

restart: always

surveyking:

container_name: surveyking

image: surveyking

build:

context: /mydata

dockerfile: surveyking-dockerfile

ports:

- "8080:8080"

depends_on:

- cus-mysql

networks:

- mynet-1

restart: always

networks:

my-network:

driver: bridge

ipam:

config:

- subnet: "192.168.0.101/16"

gateway: 192.168.0.100

容器编排启动

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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。