Skip to content

Latest commit

 

History

History
303 lines (249 loc) · 11.2 KB

deploy-01.md

File metadata and controls

303 lines (249 loc) · 11.2 KB

Docker+Docker-compose+Nginx镜像部署前端应用及优化

本文假设读者有一定的docker基础

一、Node作为静态服务器

对于前端而言,可以直接用Node提供静态资源服务,可以通过以下三种方式返回数据

// 1、组装字符串
const html = `<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>部署</title></head>
<body>hello world</body>
</html>`

const server = http.createServer((req, res) => res.end(html))
server.listen(3000, () => { console.log('Listening 3000' )})
// 2、通过读取文件
const html = fs.readFileSync('./index.html')
const server = http.createServer((req, res) => res.end(html))
// 3、将读取文件系统改完 ReadStream 的形式进行响应
// 可以提升静态服务器的性能
const server = http.createServer((req, res) => {
  fs.createReadStream('./index.html').pipe(res)
})

二、为什么用 Nginx + docker

其实以上就是前端开发过程中yarn start开启本地开发服务器的雏形,可以通过localhost:端口号进行访问,那么线上也可以如此部署访问吗?

当然可以,但是有几个问题

  • IP+Port 的访问方式,没有直接访问域名友好
  • yarn start/npm run dev还伴随着监听文件变动、实时编译(.ts、框架代码)以及重启服务,消耗内存以及CPU高负载,导致性能问题

前端生产环境一般采用更高性能的Nginx作为静态服务器,那为什么本文需要docker ?

用来隔离环境,假如公司就一台机器,但同时需要部署三个Node服务,分别需要不同版本的NodeJS才能运行,想想是不是头大。

Docker提供了单独的运行环境,同时与宿主机隔离,我们可以单独维护前端的nginx配置,当然,用来在本地进行学习测试,也是非常的方便

三、从 docker 到 docker-compose 部署简单应用

vite初始化一个vue项目

yarn create vite my-vue-app --template vue-ts

新建一个 Dockerfile文件,以下内容主要分几步

  • 基于node镜像做前端构建工作
  • 将构建产物移到nginx中
  • 暴露端口,启动nginx
# 多阶段构建
FROM node:16-alpine as builder

# 设置为工作目录,以下 RUN/CMD 命令都是在工作目录中进行执行
WORKDIR /code

# 提前将依赖移动至目录,利用docker缓存,只要ADD的内容不变,缓存就不会被破坏
ADD package.json yarn.lock ./
RUN yarn

# 全部代码添加到镜像中
COPY . ./
RUN yarn build

# 利用更小的nginx镜像
FROM nginx:alpine
COPY --from=builder /code/dist/ /usr/share/nginx/html/
# nginx 暴露 80端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]

执行docker build -t [name]:[tag] .命令进行构建镜像

# 构建一个名叫 luogc/cicd-fe 的镜像
# -t 将[name]:[tag]作为镜像名称,不写tag默认是latest
docker build -t luogc/cicd-fe .

# 可通过下面命令查看构建出来的镜像
docker image ls | grep luogc/cicd-fe

构建命令可以重复执行,每次更改镜像名+tag,都会生成不同的镜像,可以通过``查看生成的镜像

然后基于镜像来运行容器 docker run --rm -p [主机端口]:[容器端口] [镜像名]

# -d 在后台运行
# --rm 当容器停止运行时,自动删除容器
# -p 端口映射,将容器的80端口映射到宿主机的8001端口,可在宿主机通过 localhost:8001访问
# 镜像名不写tag默认取 lastest
docker run --rm -d -p 8001:80 luogc/cicd-fe

但是每次都要敲这么多命令,除了冗余繁琐,还容易出错,比如端口、存储、环境变量等,不好维护

docker-compose能高效的解决问题,通过配置文件进行管理,可以理解为webpack ->webpack.config.js

除了配置文件,它更强大在于服务编排

新建一个docker-compose.yaml文件

version: "3"
services:
  app:
    # build 从当前路径构建镜像,即读取当前目录下的 Dockerfile 文件
    build: .
    ports:
      - 8001:80

只需要以下一句命令即可替代以上构建镜像+运行容器的所有命令以及配置

# up 创建并启动容器
# --build 每次启动容器前构建镜像,依据docker-compose.yaml里面的build参数
docker-compose up --build

可以在docker的桌面端看到对应的容器,也可以通过localhost:8001进行访问

四、Nginx镜像

通过上面的配置,我们成功利用Nginx镜像部署了一个简单的前端应用,现在我们来看一下Nginx容器是怎么运行的,同时试试能不能自己改改配置啥的

先忽略上面的应用部署配置,单独部署一下Nginx的默认配置看看

# -it 以交互模式运行容器并为容器重新分配一个伪输入终端
# --rm 和 -p 参数上面介绍过了
docker run -it --rm -p 8002:80 nginx:alpine

直接访问localhost:8002应该能看到Nginx的默认页面

然后在上面的命令基础上加多一个sh参数,进入容器环境中

docker run -it --rm -p 8002:80 nginx:alpine sh
# 打开了一个默认终端,这就是容器的内部环境
# 注意,这种情况,在宿主机访问 localhost:8002 是无法访问的,并不是出错了

先找一下nginx的配置都放在哪里

which nginx # 查看nginx路径
nginx -t # 查看nginx配置文件,一般都是 /etc/nginx/nginx.conf

/ # cat /etc/nginx/nginx.conf
user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    #gzip  on;
    include /etc/nginx/conf.d/*.conf; # 读取配置的地方
}

关键在最后一句include /etc/nginx/conf.d/*.conf;,这个目录下只有default.conf一个配置文件,就是它了,把多余的注释都删掉,得到以下配置

# cat /etc/nginx/conf.d/default.conf
server {
    listen       80;
    server_name  localhost;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

从配置可以得到两个重要信息

  • 默认监听80端口
  • 默认以/usr/share/nginx/html为静态资源目录

所以如果想部署前端应用,只需要把构建出来的index.html+dist/build放到/usr/share/nginx/html目录下就可以了,另外可以直接更改/etc/nginx/conf.d/default.conf配置来生效

再回看一下我们上面那份Dockerfile配置,里面有一句没写注释,现在是不是好理解很多了

FROM nginx:alpine
# 将builder构建出来的前端产物,整个dist目录(里面包括index.html),一股脑扔到nginx的默认静态资源目录里
COPY --from=builder /code/dist/ /usr/share/nginx/html/

再看看响应的server,是nginx

五、通过docker玩转Nginx配置

通过上面我们知道了怎么配置静态资源以及nginx配置,但每次都进入容器去操作,貌似有点麻烦,能否在本地进行配置,然后通过某种方式让docker读取配置生效呢?

有,Volumn(数据卷)可以解决这个问题,不了解的同学可以看看数据卷 - Docker — 从入门到实践

我们来改一下 docker-compser.yaml配置文件,并在当前目录下新增nginx/nginx.conf文件

version: "3"
services:
  app:
    build: .
    ports:
      - 8001:80
    volumes:
		# 分别挂载当前宿主机目录下的nginx/nginx.conf文件、dist目录
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./dist:/usr/share/nginx/html

docker-compose up重新启动容器即可

此时,如果修改了nginx配置,或者项目改动本地yarn build之后,无需重新构建镜像 localhost:8001端口的内容会实时更新

基于此,我们可以快速尝试nginx的一些常用配置,既不需要购买云服务器,也不需要虚拟机,更不需要更改当前主机环境的配置,只管尽情尝试即可

nginx的一些常用配置

  • 配置缓存策略
  • 配置CORS
  • gzip/brotli
  • 路由匹配 Location、rewrite、redirect等待

六、Docker优化:构建缓存、多阶段构建

再看一下最开始上面写的一份构建配置 其实注释里把精髓都写出来了,这里再展开聊一聊

# 多阶段构建
FROM node:16-alpine as builder

# 设置为工作目录,以下 RUN/CMD 命令都是在工作目录中进行执行
WORKDIR /code

# 提前将依赖移动至目录,利用docker缓存,只要ADD的内容不变,缓存就不会被破坏
ADD package.json yarn.lock ./
RUN yarn

# 全部代码添加到镜像中
COPY . ./
RUN yarn build

# 利用更小的nginx镜像
FROM nginx:alpine
COPY --from=builder /code/dist/ /usr/share/nginx/html/
# nginx 暴露 80端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]

初学者很简单就可以写出下面这份简版的配置

FROM node:14-alpine
WORKDIR /code
ADD . /code # 直接全部复制整个项目过去
RUN yarn && npm run build # 一起执行依赖安装和构建

但上面的配置拆了两段,这其实是利用了 ADD指令:如果添加内容的checksum没有发生改版,则可以利用构建缓存Best practices for writing Dockerfiles | Docker Documentation 本质上是镜像分层+镜像cache机制的结果

# 提前将依赖移动至目录,利用docker缓存,只要ADD的内容不变,缓存 node_modules就不会被破坏
ADD package.json yarn.lock ./
# 如果 yarn.lock 内容没有变化,则不会重新依赖安装
RUN yarn

# 全部代码添加到镜像中,业务代码是经常变的
COPY . ./
RUN yarn build

构建过程也可以看到---> Using cache标记

还有一段,这里用到了多阶段构建的优化

FROM nginx:alpine
COPY --from=builder /code/dist/ /usr/share/nginx/html/

首先我们构建的目标是生成dist目录里面的静态资源,但应用最后运行并不需要node环境,而且node的镜像比nginx镜像要大很多,所以利用node完成构建输出产物后,完全可以退出舞台,将产物交给nginx即可

所以整个配置可以分为两段

  • 阶段一:Node镜像构建应用,生产静态资源
  • 阶段二:Nginx镜像对上一阶段的产物进行服务化