Nest.js从0到1搭建博客系统---Nginx、Docker、Docker Compose与Redis缓存最佳实战与理论结合(8)

417 阅读16分钟

Nginx、Docker、Docker Compose与Redis缓存

Nginx

Nginx(“engine x”)是一个高性能的HTTP和反向代理服务器,它具有高并发、高性能、扩展性好、异步非阻塞的事件驱动模型等特点,在互联网行业被广泛应用,使用Nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等

Nginx有三大应用场景

  • 静态资源服务-通过本地文件系统提供服务
  • 反向代理服务-缓存、负载均衡
  • API服务-openresty

nginx.conf配置文件

  • 全局块:配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等

  • events块:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等

  • http块:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等

  • server块:配置虚拟主机的相关参数,一个http中可以有多个server

  • location块:配置请求的路由,以及各种页面的处理情况

user  nginx;                        # 运行用户,默认即是nginx,可以不进行设置
worker_processes  1;                # Nginx 进程数,一般设置为和 CPU 核数一样
error_log  /var/log/nginx/error.log warn;   # Nginx 的错误日志存放目录
pid        /var/run/nginx.pid;      # Nginx 服务启动时的 pid 存放位置

events {
    use epoll;     # 使用epoll的I/O模型(如果你不知道Nginx该使用哪种轮询方法,会自动选择一个最适合你操作系统的)
    worker_connections 1024;   # 每个进程允许最大并发数
}

http {   # 配置使用最频繁的部分,代理、缓存、日志定义等绝大多数功能和第三方模块的配置都在这里设置
    # 设置日志模式
    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;   # Nginx访问日志存放位置

    sendfile            on;   # 开启高效传输模式
    tcp_nopush          on;   # 减少网络报文段的数量
    tcp_nodelay         on;
    keepalive_timeout   65;   # 保持连接的时间,也叫超时时间,单位秒
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;      # 文件扩展名与类型映射表
    default_type        application/octet-stream;   # 默认文件类型

    include /etc/nginx/conf.d/*.conf;   # 加载子配置项
    
    server {
    	listen       80;       # 配置监听的端口
    	server_name  localhost;    # 配置的域名或IP
    	
    	location / {
    		root   /usr/share/nginx/html;  # 网站根目录
    		index  index.html index.htm;   # 默认首页文件
    		deny 172.168.22.11;   # 禁止访问的ip地址,可以为all
    		allow 172.168.33.44; # 允许访问的ip地址,可以为all
    	}
    	
    	error_page 500 502 503 504 /50x.html;  # 默认50x对应的访问页面
    	error_page 400 404 error.html;   # 同上
    }
}

Nginx 常用操作命令

  • start nginx:开启服务
  • nginx -s stop | nginx -s quit:停止服务
  • nginx -s reload:重启服务
  • kill -QUIT:从容停止Nginx 主进程号
  • kill -TERM:快速停止Nginx主进程号
  • ./nginx -t:检查配置文件是否有语法操作
  • ./nginx -t -c /usr/local/nginx/conf/nginx.conf:显示指定配置文件
  • nginx -v:查看版本
  • 找对应pid有两种方法
1.使用命令 cat /var/run/nginx.pid
2.直接 ps -ax | grep nginx 找到master process(主线程)的Id
然后执行Linux的杀死线程命令:
sudo kill -s QUIT xxxx

Docker

Docker 是一个开源的应用容器引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者在本地编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台。

简单的理解,Docker类似于集装箱,各式各样的货物,经过集装箱的标准化进行托管,而集装箱和集装箱之间没有影响。也就是说,Docker平台就是一个软件集装箱化平台,这就意味着我们自己可以构建应用程序,将其依赖关系一起打包到一个容器中,然后这容器就很容易运送到其他的机器上进行运行,而且非常易于装载、复制、移除,非常适合软件弹性架构。

因此,就像船只、火车或卡车运输集装箱而不论其内部的货物一样,软件容器充当软件部署的标准单元,其中可以包含不同的代码和依赖项。 按照这种方式容器化软件,开发人员和 IT 专业人员只需进行极少修改或不修改,即可将其部署到不同的环境。

总而言之,Docker 是一个开放平台,使开发人员和管理员可以在称为容器的松散隔离的环境中构建镜像、交付和运行分布式应用程序。以便在开发、QA 和生产环境之间进行高效的应用程序生命周期管理。

Docker 架构

docker-architecture.png

  • Docker 包括三个基本概念:
    • 镜像(Image) :Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。

    • 容器(Container :镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

    • 仓库(Repository) :仓库可看成一个代码控制中心,用来保存镜像。

70544-20181128094117580-274999612.png

Docker的应用场景

  • Web 应用的自动化打包和发布。
  • 自动化测试和持续集成、发布
  • 在服务型环境中部署和调整数据库或其他的后台应用
  • 从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境

70544-20181128094322089-159857776.png

Docker 的安装

Docker阿里镜像加速源

加速器地址:[系统分配前缀].mirror.aliyuncs.com    
  • 登录容器镜像服务控制台,在左侧导航栏选择镜像工具 > 镜像加速器,在镜像加速器页面获取加速器地址

image.png

image.png

  • 尝试运行一个Nginx服务器
docker run -d -p 80:80 --name webserver nginx

image.png

image.png

Docker常用命令

  • docker --help: 看 docker 总体帮助文档

  • docker info:查看 docker 概要信息

  • docker version:查看 Docker 版本信息

  • docker login [OPTIONS] [SERVER]:登录远程仓库

docker login -u myusername -p mypassword mydockerregistry.com
  • docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]:打标签
docker tag myimage:latest mydockerregistry.com/myimage:latest
  • docker push [OPTIONS] NAME[:TAG]:推送镜像
docker push mydockerregistry.com/myimage:latest
  • docker inspect 容器ID或容器名:查看容器详情
  • docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]: 获取镜像
docker pull ubuntu:18.04
  • docker run [options] image [command][arg...]: 启动容器
    • options
      • --name=容器新名字 为容器指定一个名称,用来区分容器
      • -it:交互式(i)终端(t)
      • --rm:容器退出后随之将其删除,可以避免浪费空间
      • -h "mars": 指定容器的hostname;
      • -P: 随机端口映射,大写 P
      • -p: 指定端口映射,小写 p
      • -d: 后台运行容器,并返回容器ID
      • -e username="ritchie": 设置环境变量
      • --env-file=[]: 从指定文件读入环境变量
      • --volume , -v: 绑定一个卷
      • --net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型
      • --network mynetwork 加入网段
docker run -it --rm ubuntu:18.04 bash
#  `ubuntu:18.04`:这是指用 `ubuntu:18.04` 镜像为基础来启动容器
# `bash`:放在镜像名后的是 **命令**,这里我们希望有个交互式 Shell,因此用的是 `bash`

docker run --name webserver -d -p 80:80 nginx

docker run -d --name mysql-container -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 mysql:8.0
  • docker create [OPTIONS] IMAGE [COMMAND] [ARG...]:创建新容器但不启用
docker create --name vhen_Nginx001 nginx:latest # 使用docker镜像nginx:latest创建一个容器,并将容器命名为vhen_Nginx001
  • docker build [OPTIONS] PATH | URL | -:用于使用 Dockerfile 创建镜像
    • –build-arg=[]:  设置镜像创建时的变量
    • –cpu-shares: 设置 cpu 使用权重;
    • –cpu-period: 限制 CPU CFS周期;
    • –cpu-quota: 限制 CPU CFS配额;
    • –cpuset-cpus: 指定使用的CPU id;
    • –cpuset-mems: 指定使用的内存 id;
    • –disable-content-trust: 忽略校验,默认开启;
    • -f: 指定要使用的Dockerfile路径;
    • –force-rm: 设置镜像过程中删除中间容器;
    • –isolation: 使用容器隔离技术;
    • *–label=[]: 设置镜像使用的元数据;
    • *-m: 设置内存最大值;
    • –memory-swap: 设置Swap的最大值为内存+swap,"-1"表示不限swap;
    • –no-cache: 创建镜像的过程不使用缓存;
    • –pull: 尝试去更新镜像的新版本;
    • –quiet, -q: 安静模式,成功后只输出镜像 ID;
    • –rm: 设置镜像成功后删除中间容器;
    • –shm-size: 设置/dev/shm的大小,默认值是64M;
    • –ulimit: Ulimit配置。
    • –squash: 将 Dockerfile 中所有的操作压缩为一层。
    • –tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
    • –network: 默认 default。在构建期间设置RUN指令的网络模式
docker build -t vhen/ubuntu:v1 # 使用当前目录的Dockerfile创建镜像
docker build github.com/creack/docker-firefox # 使用URL 的 Dockerfile 创建镜像
  • docker exec -it nginx bash: 进入容器

    • -it 表示 可交互的终端
    • bash 表示使用命令行进行交互
  • exit 退出容器

  • docker ps [options]: 列出当前所有正在运行的容器

    • -a: 列出当前所有正在运行的容器和历史运行过的
    • -f:根据条件过滤显示的内容
    • -l: 显示最近创建的容器
    • -n: 显示最近n个创建的容器
    • -q: 静默模式,只显示容器编号
    • -s:显示总的文件大小
    • --format:指定返回值的模板文件
    • --no-trunc:不截断输出
  • docker logs 容器ID或容器名:查看容器日志

  • docker top 容器ID或容器名:查看容器内运行的进程,支持 ps 命令参数

  • docker cp 容器ID:容器内路径 目的主机路径: 从容器内拷贝文件到主机上

docker cp nginx:/www /tmp/ #将nginx容器的/www 拷贝到本地/tmp下
  • docker start 容器ID或容器名: 启动已停止运行的容器

  • docker stop 容器ID或容器名:停止容器

  • docker restart 容器ID或者容器名: 重启容器

  • docker pause 容器ID或者容器名:暂停容器中所有的进程

  • docker unpause:恢复容器中所有的进程

  • docker rm [options] 容器名称|容器ID:删除指定容器

    • -f:强制删除一个运行中的容器
    • -l:移除容器间的网络连接,而非容器本身
    • -v: 删除与容器关联的卷
docker rm -f $(docker ps -qa) # 删除所有停止的容器
  • docker search [OPTIONS] 镜像名称:搜索镜像名称
    • --filter , -f:基于给定条件过滤输出。
    • --format:使用模板格式化显示输出。
    • --limit:Max number of search results ,默认值25。
    • --no-trunc:禁止截断输出。
docker search -f stars=10 redis
  • docker images [options]:列出本地主机上的镜像

    • -a:列出本地所有的镜像(含中间映像层,默认情况下,过滤掉中间映像层)
    • --digests:显示镜像的摘要信息
    • -f:显示满足条件的镜像
      • is-official=true|false:搜索官方镜像或非官方镜像
      • is-automated=true|false:搜索自动构建的镜像或非自动构建的镜像
      • stars=xxx:搜索星级大于等于指定值的镜像
      • name=xxx:根据镜像名称进行精确匹配搜索
      • description=xxx:根据镜像描述进行模糊匹配搜索
    • -q: 只显示镜像ID
    • --format:指定返回值的模板文件
    • --no-trunc:显示完整的镜像信息
  • docker image ls列出镜像

    • 列表包含了 仓库名标签镜像 ID创建时间 以及 所占用的空间
docker image ls -f since=ubuntu:18.04 
# 过滤器参数 --filter,或者简写 -f,看到在 ubuntu:18.04 之后建立的镜像
# 想查看某个位置之前的镜像也可以,只需要把 `since` 换成 `before` 即可

docker image ls -q 
# -q 指定范围的 ID 列表
  • docker image rm 删除本地镜像
    • <镜像> 可以是 镜像短 ID镜像长 ID镜像名 或者 镜像摘要
docker image rm [选项] <镜像1> [<镜像2> ...]
  • docker rmi [image]:删除不需要的镜像

    • 删除单个:docker rmi -f 镜像ID
    • 删除多个:docker rmi -f 镜像名1:TAG 镜像名2:TAG
    • 删除全部:docker rmi -f $(docker images -qa)
  • docker system df查看镜像、容器、数据卷所占用的空间

  • 其他命令自行官方查找

Dcokerfile 构建镜像

Dockerfile是一个文本文件,文件中包含了一条条指令(instrucation),用于构建镜像。每一条指定构建一层镜像,因此每一条指令的内容,就是描述该层镜像应当如何构建。

FROM:为构建流程指定基础镜像;必须是第一条指令,一切都是从这里开始的

  • tag 或 digest 是可选项,如果不指定,会使用 latest 版本作为基础镜像
# 语法
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]
# 示例
FROM redis 
FROM redis:7.0.5 
FROM redis@7614ae9453d1

RUN:用于在构建镜像过程中执行命令

# 语法
RUN <command>
RUN ["executable", "param1", "param2"]

# 示例
RUN echo "Hello Dockerfile"
RUN ["echo", "Hello Dockerfile"]

# 多个推荐写法
RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

WORKDIR:指定后续指令的工作目录,类似于 Linux 中的 cd 命令

# 语法
WORKDIR /path/to/workdir
# 示例

# 当前工作目录为 /a/b/c
WORKDIR /a 
WORKDIR b 
WORKDIR c

# 使用 Dockerfile 中设置的环境变量
ENV DIR_PATH=/demo 
WORKDIR $DIR_PATH/$DIR_NAME  # 未显示指定,直接忽略
RUN pwd

ADD:将构建上下文中指定目录下的文件复制到镜像文件系统的指定位置

  • 路径必须位于构建的上下文中; 你不能添加../something / something,因为docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程
  • --chown功能仅用于Dockerfile构建linux容器.在windows容器上无效
  • 目标位置下的某些目录不存在,会自动创建
  • src 支持通配符,主要是 * 和 ?,通过 Go 语言的 filepath.Match 规则进行匹配
  • 如果 src 包含多个文件资源,或者使用了通配符,dest 必须是文件夹,即 dest 以 / 结尾
  • 某一 ADD 指令对应的 src 资源有变更,Dockerfile 中这条指令后的构建缓存都会失效
  • src 支持设置多个文件资源,每个文件资源都会被解析为构建上下文中的相对路径
  • 如果是可识别的压缩格式(identity,gzip,bzip2或xz)的本地tar存档,则将其解压缩为目录。 远程URL中的资源不会被解压缩。 复制或解压缩目录时,它与tar -x具有相同的行为
  • 如果是URL且不以尾部斜杠结尾,则从URL下载文件并将其复制到
  • 如果是URL并且以尾部斜杠结尾,则从URL推断文件名,并将文件下载到<dest> /<filename>。 例如,ADD http://example.com/foobar /将创建文件/foobar。 URL必须具有非常重要的路径,以便在这种情况下可以发现适当的文件名
# 语法
ADD [--chown=<user>:<group>] [--checksum=<checksum>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] # 路径中含有空格的情况,可以使用第二种方式
ADD <git ref> <dir> # 处于实验阶段的特性,直接添加 Git 资源到镜像文件系统中

# 示例
ADD demo.txt /dest_dir/demo.txt    # 将 demo.txt 复制到 /dest_dir 下的 demo.txt
ADD demo* /dest_dir/               # 将以 demo 开头的文件复制到 /dest_dir 下
ADD dem?.txt /dest_dir/            # ? 被一个单字符替换,可以是 demo.txt, 将符合条件的文件复制到 /dest_dir 下
ADD test/ /dest_dir/               # 将 test 文件夹下的所有文件都复制到 /dest_dir/ 下
ADD test/ dest_dir/                # 将 test 文件夹下的所有文件都复制到 <WORKDIR>/dest_dir/ 下
ADD http://example.com/a/url.txt / # 从 URL 下载 url.txt 文件,另存为文件 /a/url.txt,其中文件夹 /a/ 是自动创建的
  
# 假设文件名为 demo[0].txt, 其中含有特殊符号 [ 和 ]
ADD demo[[]0].txt /dest_dir

COPY:功能和语法与 ADD 类似,但是不会自动解压文件,也不能访问网络资源

# 语法
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

# 示例
# 通配符
COPY hom* /mydir/ # adds all files starting with "hom" 
COPY hom?.txt /mydir/ # ? is replaced with any single character, e.g., "home.txt"
# 绝对路径,或相对于WORKDIR的路径
COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # adds "test" to /absoluteDir/

CMD:构建镜像成功后,所创建的容器启动时执行的命令,常与 ENTRYPOINT 结合使用

  • 一个Dockerfile文件中,只能有一个CMD指令,如果出现多个,只有最后一个起作用; 但可以使用 && 连接多个命令
# 语法 使用双引号,而不是单引号
  CMD command param1 param2 # shell 方式
  CMD ["executable","param1","param2"] # exec 方式,这是推荐的方式
  CMD ["param1","param2"]   # 作为 ENTRYPOINT 的默认参数,这种方式是 exec 方式的特殊形式
# 示例
  CMD echo "Hello Dockerfile"
  CMD ["echo", "Hello Dockerfile"]
  
  FROM node
  CMD ["/usr/bin/wc", "--help"]

ENTRYPOINT:用于配置容器以可执行的方式运行,常与 CMD 结合使用

  • Dockerfile 中只有最后一条 ENTRYPOINT 指令生效
  • exec形式的ENTRYPOINT中的命令行参数将会追加到docker run <image>所有元素的后面。并且会覆盖CMD指令指定的参数。这允许将参数传递给入口点。 docker run <image> -d会将-d参数传递给入口点。 您可以使用docker run --entrypoint标志覆盖ENTRYPOINT指令
# 语法 要使用双引号,而不是单引号
  ENTRYPOINT ["executable", "param1", "param2"] # 推荐方式
  ENTRYPOINT command param1 param2
# 示例 
  FROM ubuntu
  ENTRYPOINT ["top", "-b"]
  CMD ["-c"]
#  或
  FROM ubuntu
  ENTRYPOINT exec top -b

ENV:设置环境变量

# 语法
ENV <key>=<value> ... 
ENV <key> <value>
# 示例
ENV MY_NAME="vhen4646" MY_CAT="nginx"  # 设置多个环境变量,支持引号以及反斜杠作为续行符号
ENV MY_CAT nginx # 不推荐这种写法
#  查看最终镜像中的环境变量
docker image inspect -f='{{json.ContainerConfig.Env}}' image_name

EXPOSE:约定容器运行时监听的端口,通常用于容器与外界之间的通信

  • 支持 TCP 或者 UDP 协议,如果不显式指定协议,默认使用 TCP 协议
  • EXPOSE 并不会真正将端口发布到宿主机,而是作为一种约定,让镜像使用者在运行容器时,用 -p 分别发布约定端口,或者 -P 发布所有约定端口
#语法
  EXPOSE <port> [<port>/<protocol>...]
#示例
  EXPOSE 8080
  EXPOSE 80/tcp
  EXPOSE 80/udp
  EXPOSE 9090/tcp 9090/udp

USER:指定当前构建阶段以及容器运行时的默认用户,以及可选的用户组

# 语法
USER <user>[:<group>] 
USER <user>[:<GID>] 
USER <UID>[:<GID>] 
USER <UID>[:<group>]

# 示例 
# 用 USER 指定用户后,Dockerfile 中在此之后的 RUN, CMD, ENTRYPOINT 指令都会使用这个用户
#此外,这个用户也是容器运行时的默认用户
USER vhen864 
USER vhen864 :vhenGroup

# 在 Windows 系统中指定非内置用户时,需要先用 **net user /add** 命令创建用户
FROM microsoft/windowsservercore
RUN net user /add my_user # 在容器中创建 Windows 用户
USER my_user # 设置后续指令的用户

VOLUME:创建具有指定名称的挂载数据卷,用于数据持久化

  • 数据持久化,避免容器重启后丢失重要数据
  • 修改数据卷时不会对容器产生影响,防止容器不断膨胀
  • 有利于多个容器共享数据
# 语法
  VOLUME ["volume1", "volume2", ...] # 必须使用双引号,而不是单引号
  VOLUME volume1 volume2 ...
# 示例
# 定义 VOLUME 后,Dockerfile 的后续指令对该数据卷所做的修改操作会被丢弃
  VOLUME ["/demo/data", "/demo/logs"]
  VOLUME /myvol

LABEL:为镜像添加元数据

# 请确保使用双引号,而不是单引号。特别是当你使用字符串插值时(例如LABEL示例="foo-$ENV_VAR"),
# 单引号将按原样取字符串,而不拆包变量的值
LABEL <key>=<value> <key>=<value> <key>=<value> ...
# 示例
# 同一条 LABEL 指令可以指定多个元数据,用空格分开,可以在同一行,也可以用反斜杠分割,放在多行
LABEL author="Jason 315" version="1.0.0" description="Dockerfile案例" 
LABEL author="Jason 315" \ 
      version="1.0.0" \ 
      description="Dockerfile案例"
# 可以用如下命令查看元数据
 docker image inspect -f='{{json.ContainerConfig.Labels}}' image_name

Vue使用Docker和Nginx部署实战

  • 在根目录下创建nginx.confDockerfile文件

image.png

  • nginx.conf
 # 监听 80 端口
server {
    #监听端口
    listen      80;
    # 域名或ip
    server_name localhost; 
    #编码格式
    charset utf-8;

    # 前端静态文件资源
    location / {
        root  /usr/share/nginx/html;
        index index.html index.htm;
        try_files $uri $uri/ @rewrites;
    }
    # 配置如果匹配不到资源,将url指向 index.html, 在 vue-router 的 history 模式下使用,就不会显示404
    location @rewrites {
        rewrite ^(.*)$ /index.html last;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
  • Dockerfile
# node镜像
FROM node:20-alpine as build-stage
WORKDIR /my-app

ADD package*.json ./
RUN npm install --registry=https://registry.npm.taobao.org \
    && npm ci --quiet
ADD ./ .
RUN npm run build
# nginx镜像
FROM nginx:alpine as prod-stage
RUN mkdir /my-app
COPY --from=build-stage /my-app/dist /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
  • 构建镜像
docker build -t my-app:latest .

image.png

  • 运行镜像
docker run --name my-app-test -d -p 8080:80 my-app:latest

image.png

image.png

image.png

Docker Compose

Docker Compose是一个用于定义和运行多容器 Docker 应用程序的工具。通过Docker Compose ,可以使用 docker-compose.yml文件(YAML 格式)来配置应用程序相关联的容器,包含多个服务services;每个服务中定义了创建容器时所需的镜像、端口映射、参数、依赖、环境变量等。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务,可以让它快速高效地部署多个容器了。这种方法特别适用于开发、测试、部署和扩展复杂应用程序;一旦您有了Compose文件,您就可以用一个命令创建并启动您的应用程序:docker Compose-up

  • docker-compose下载

  • 安装的是图形界面的桌面版本,就不用安装Docker compose,桌面版自带。 命令行输入docker compose version或者docker-compose -v可以查看docker compose的版本信息:

image.png

Docker Compose 用途

  • 简化多容器应用的配置

    • 使用一个 YAML 文件(通常命名为 docker-compose.yml)来定义应用中的所有服务(容器),包括它们的依赖、环境变量、暴露的端口等
    • 适用于需要多个服务协同工作的应用,如前端、后端和数据库服务。
  • 一致的开发、测试和生产环境、部署的便利性

    • 适用于开发者在本地环境进行开发和测试
    • 有助于在本地环境中模拟生产环境,提高开发和测试的效率
    • Docker Compose 确保应用在开发、测试和生产环境中以相同的方式运行,减少环境差异带来的问题
    • 便于运维人员在生产环境中部署和管理复杂的应用堆栈
  • 自动化和持续集成

    • 支持在持续集成管道中运行自动化测试
    • 可以轻松集成到 CI/CD 流程中,自动化构建、测试和部署应用
  • 微服务架构

    • 对于基于微服务架构的应用,Docker Compose 提供了一种管理多个微服务的简单方法,每个服务可以在独立的容器中运行
  • 快速部署和伸缩

    • 支持服务的水平扩展,可以轻松地增加或减少服务的实例数
    • 快速启动和停止整个应用或应用的特定服务
  • 网络和卷的简化管理

    • 管理数据卷,用于在容器之间共享数据或持久化数据
    • 自动配置容器间的网络连接

docker compose常用命令

  • docker-compose up:启动服务。默认情况下,它会运行 docker-compose.yml 文件中定义的所有服务
docker-compose upc # 启动所有服务
docker-compose up -d # 在后台运行服务
docker-compose up --build # 强制重新构建服务
  • docker-compose down:停止并移除由 docker-compose up 创建的容器、网络、卷和默认镜像
docker-compose down # 停止并移除服务
docker-compose down -v #停止服务并移除卷
  • docker-compose logs:查看服务的日志输出
docker-compose logs # 查看所有服务日志
docker-compose logs -f # 跟踪日志输出
  • docker-compose ps:列出项目中当前状态的容器。

  • docker-compose exec:在运行中的容器上执行命令

docker-compose exec service_name /bin/sh # 进入容器的交互式命令行
docker-compose exec service_name ls -l # 执行特定命令
  • docker-compose stop:停止服务,但不移除容器

  • docker-compose start:启动已停止的服务

  • docker-compose restart:重启服务

  • docker-compose pause:暂停服务

  • ocker-compose unpause:恢复web容器

  • docker-compose pull:拉取服务依赖的镜像

  • docker-compose push:推送本地镜像到 Docker 镜像仓库

  • docker-compose build:构建或重新构建服务

  • docker-compose rm:移除已停止的容器

  • docker-compose top:查看各个服务容器内运行的进程

  • docker-compose version:显示 Docker Compose 的版本号

Docker Compose 文件

  • version版本:指定 Compose 文件格式的版本。版本决定了可用的配置选项
  • services 服务:定义了应用中的每个容器(服务)。每个服务可以使用不同的镜像、环境设置和依赖关系
    • image镜像:从指定的镜像中启动容器,可以是存储仓库、标签以及镜像 ID
    • container_name容器名称:自定义容器名字
    • build构建:指定构建镜像的 dockerfile 的上下文路径,或者详细配置对象。,用于构建镜像
    • expose暴露端口:暴露端口给-link或处于同一网络的容器,不暴露给宿主机
    • ports端口:映射容器和宿主机的端口
    • depends_on依赖:依赖配置的选项,意思是如果 服务启动是如果有依赖于其他服务的,先启动被依赖的服务,启动完成后在启动该服务
    • environment环境变量:设置服务运行所需的环境变量
    • env_file环境变量文件: 使用环境变量.env文件
    • restart重启::控制容器的重启策略。在容器退出时,根据指定的策略自动重启容器
      • no:不自动重启
      • always:无论退出状态码如何,总是重启容器
      • on-failure:仅在容器非正常退出时(退出状态码非零)重启
      • unless-stopped:除非手动停止,否则总是重启
  • command命令:覆盖容器启动后默认执行的命令。在启动服务时运行特定的命令或脚本,常用于启动应用程序、执行初始化脚本等
  • networks网络 定义了容器间的网络连接
  • volumes:用于数据持久化和共享的数据卷定义。常用于数据库存储、配置文件、日志等数据的持久化

Docker Engine 与 docker-compose version 之间的有以下关系 前往官网version

Nest项目使用Docker Compose部署实战

  • package.json 配置
"start:prod": "npm run build && cross-env  NODE_ENV=prod node dist/src/main",
  • 在根目录创建Dockerfile文件
# node镜像
FROM node:20.8.1-alpine as my-blog-builder
WORKDIR /blog
ADD package*.json ./
RUN npm install --registry=https://registry.npm.taobao.org \
    && npm install
ADD ./ .
CMD npm run start:prod
EXPOSE 9000
  • 在根目录下创建docker-compose.yml
version: '3.8'
services:
  blog:
    container_name: blog-server
    image: blog:latest
    build: .
    environment:
      TZ: Asia/Shanghai
    ports:
      - 9000:9000
    # restart: always
    depends_on:  # web服务依靠mysql要先等mysql启动
      - db
      # - my-redis
    links:
      - db:mysql
    # 不声明的话,也会在同一个网络中,名称默认是 项目_default, 比如我这个项目叫 nest_vhen_blog, 默认的网络名称就是 nest_vhen_blog_default
    networks: 
      - blog-network
  db:
    container_name: db-server
    image: mysql:8
    restart: on-failure
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: blog
      MYSQL_USER: vhen
      MYSQL_PASSWORD: 123456
      MYSQL_ROOT_HOST: '%'
    ports:
      - 3307:3306
    privileged: true  #设置为true,不然数据卷可能挂载不了,启动不起
    volumes:
      # 数据logs
      # - /usr/local/mysql/log:/var/log/mysql
      - ./db/mysql-files:/var/lib/mysql-files
      - ./db/logs:/var/log/mysql # 映射日志目录,将容器/var/log/mysql目录下的数据,备份到主机的 /db/log目录下
      # 数据数据
      - ./db/data:/var/lib/mysql # 映射数据目录,将容器/var/lib/mysql目录下的数据,备份到主机的 /db/data目录下
      - ./db/conf:/etc/mysql # 映射配置目录,将容器/etc/mysql目录下的数据,备份到主机的 db/conf目录下
      - ./db/init:/docker-entrypoint-initdb.d/ # 存放初始化的脚本
    networks:
      - blog-network
    # 解决外部无法访问
    command: 
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1
networks:
  blog-network:
    driver: bridge
  • my.cnf文件配置,这个也是需要配置的,否则也是会影响数据库连接的
[mysqld]
user=mysql
character-set-server=utf8mb4
default_authentication_plugin=mysql_native_password
secure_file_priv=NULL
expire_logs_days=7
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
max_connections=1000
skip-name-resolve
# skip-grant-tables
log-error=/var/log/mysql/error.log
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
tmpdir=/var/tmp
log-bin=/var/log/mysql/mysql-bin
[client]
default-character-set=utf8mb4
 
[mysql]
default-character-set=utf8mb4
  • 执行docker-compose up -d

image.png

image.png

  • 如遇到上图连接数据库问题,可以能是mysql账号权限问题,配置一下即可
# 进入mysql容器命令
docker exec -it my_sql bin/bash
# 登录mysql
mysql -uroot -p123456
# 查询数据库后进入mysql查询数据表
show databases;
# 使用mysql数据库
use mysql;
# 查看数据库表
show tables;
# 查看user表中的数据 
select User,Host from user;
# 查看创建的用户表没有我们设置连接的用户和host,所以需要创建
CREATE USER 'vhen'@'%' IDENTIFIED BY '123456';
# 给创建的用户赋予权限
GRANT ALL ON *.* TO 'vhen'@'%';
# 刷新权限
flush privileges;
# 退出mysql
exit;
# 如果还报错修改下密码即可
ALTER USER 'vhen'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;
# mysql 报错## You must reset your password using ALTER USER statement before executing this statement.
alter user user() identified by "123456";
flush privileges;

#设置密码 
#PASSWORD EXPIRE NEVER 密码永不过期 
#mysql_native_password 加密插件 
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456' PASSWORD EXPIRE NEVER;

  • 本地用Navicat工具连接3307端口数据库也是成功滴

image.png

  • 测试一下接口

image.png

image.png

Redis

Redis是一款流行的开源(BSD许可)的内存数据存储系统,它支持多种数据类型,包括字符串、哈希表、列表、集合和有序集合。它被广泛应用于缓存、消息传递、实时分析、存储会话状态等领域。

使用场景

  • 计数器

    • 可以对 String 进行自增自减运算,从而实现计数器功能; 比如:计算访问次数、点赞、转发、库存数量等等
    • Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量
  • 缓存

    • 将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率
  • 会话缓存

    • 可以使用 Redis 来统一存储多台应用服务器的会话信息
    • 当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性
  • 全页缓存(FPC

    • 除基本的会话token之外,Redis还提供很简便的FPC平台
    • 以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面
  • 查找表

    • 例如 DNS 记录就很适合使用 Redis 进行存储
    • 查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源
  • 消息队列(发布/订阅功能)

    • List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息
    • 不过最好使用 Kafka、RabbitMQ 等消息中间件
  • 分布式锁实现

    • 在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步
    • 可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现
  • 排名/排行榜

    • 排名/排行榜(Rank/LeaderBoard)是 Redis中一个比较实用的功能,在文章热榜、游戏竞技或社区平台中,排行榜(Leaderboard)和排名(Ranking)系统是常见的功能,用于展示用户在特定活动、比赛或指标上的排名情况,而 Redis的有序集合(Sorted Set)是实现排行榜功能的理想数据结构,因为它可以存储每个成员的分数,并根据分数进行排序
  • 地理位置应用

    • Redis 还具有地理位置服务的特性,可以记录每个用户的位置信息,并将其保存在 Redis 中。然后通过 GeoHash 和 Geospatial 数据类型来实现位置信息的查找和计算,以实现类似于附近的人、打车等服务。

windows配置redis

image.png

image.png

image.png

RESP下载免费的Redis图形化管理软件

RESP官网下载

如果官网打不开可点击下载resp-2022.5.0.0版本 image.png

Nest集成Redis

npm install ioredis @liaoliaots/nestjs-redis

创建redis模块

nest g res redis
  • redis.module.ts
import { Module, Global } from '@nestjs/common'
import { RedisService } from './redis.service'
@Global()
@Module({
    providers: [RedisService],
    exports: [RedisService]
})
export class RedisModule { }
  • redis.service.ts
/*
 * @Author: vhen
 * @Date: 2024-01-02 15:11:35
 * @LastEditTime: 2024-01-02 18:59:59
 * @Description: 现在的努力是为了小时候吹过的牛逼!
 * @FilePath: \nest-vhen-blog\src\common\libs\redis\redis.service.ts
 * 
 */
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis'

@Injectable()
export class RedisService {
    private readonly redis: Redis
    constructor(private readonly configService: ConfigService) {
        this.redis = new Redis({
            host: this.configService.get('redis.host'), // Redis 服务器的主机名
            port: this.configService.get('redis.port'), // Redis 服务器的端口
            password: this.configService.get('redis.password'), // Redis 服务器的密码
            db: this.configService.get('redis.db'), // Redis 服务器的数据库
        });
    }
    getClient(): Redis {
        return this.redis
    }
    /**
     * 设置值
     * @param key 存储的key
     * @param value 对应的值
     * @param time 可选的过期时间,单位秒
     * @returns 
     */
    async set(key: string, value: any, time: number = 0): Promise<any> {
        if (time) {
            return await this.redis.set(key, value, 'EX', time)
        } else {
            return await this.redis.set(key, value)
        }
    }
    /**
     * 获取值
     * @param key 存储的key
     * @returns 
     */
    async get(key: string): Promise<string> {
        return await this.redis.get(key)
    }
    /**
     * 删除值
     * @param key 存储的key
     * @returns 
     */
    async del(key: string): Promise<number | null> {
        return await this.redis.del(key)
    }
}
  • app.module.ts 注册
import { RedisModule } from '@/common/libs/redis/redis.module';
@Module({
    imports: [RedisModule]
})
export class AppModule { }
  • demo.service.ts 测试
/*
 * @Author: vhen
 * @Date: 2023-12-23 19:22:25
 * @LastEditTime: 2024-01-02 18:52:16
 * @Description: 现在的努力是为了小时候吹过的牛逼!
 * @FilePath: \nest-vhen-blog\src\modules\demo\demo.service.ts
 * 
 */
import { Injectable, Inject } from '@nestjs/common';
import { RedisService } from '@/common/libs/redis/redis.service';
@Injectable()
export class DemoService {

  constructor(@Inject(RedisService) private readonly redisService: RedisService) {}
  async findAll() {
    this.redisService.set('nest_redis_test', '测试redis')
    console.log('nest_redis_test', await this.redisService.get('nest_redis_test'));
    return `This action returns all demo`;
  }
}

image.png

image.png

github

项目地址:nest_vhen_blog