docker前端镜像部署

1,977 阅读11分钟

docker

前言

最近在研究云开发,接触到了docker,就简单了解了一下,记录一下这几天实操的笔记。

docker是什么

网上有很多相关的资料,按照我的理解,docker最核心的就是镜像和容器,镜像实现了封装,容器实现了环境隔离。

部署项目

  • vue
  • nodejs
  • mysql

准备工作

  • 注册镜像仓库,我用的是https://hub.docker.com/,也可以用阿里云的镜像仓库,用于保存我们的镜像
  • 安装docker,网上有很多教程,window安装比较麻烦(文末会提到一些注意事项),mac安装docker desktop即可,linux安装很简单。

镜像

我们从最基本的镜像打包开始,现在我有一个vue项目,准备部署到服务器上

  • 在项目根目录创建Dockerfiledefault.conf文件,内容如下
# Dockerfile
# 必须,基于官方nginx镜像,为了部署服务
FROM nginx
# 非必须,说明
MAINTAINER website-library
# 移除默认nginx配置
RUN rm /etc/nginx/conf.d/default.conf
# 使用我们定义的nginx配置
ADD default.conf /etc/nginx/conf.d/
# 复制dist目录下的文件
COPY dist/ /usr/share/nginx/html/
# default.conf
server {
    listen       80;
    server_name  localhost; # 修改为docker服务宿主机的ip
    
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html =404;
    }
    
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}
  • 打包项目,执行命令docker build -t 镜像名 .,不要忘记后面的.,镜像名改为你想要的名字
  • 一个完整的镜像名应该是[镜像仓库名]/[镜像名]:[tag],当然可以简写,当你需要push到镜像仓库时,镜像名是必须的;当你需要标示当前镜像的版本或其他信息,tag是必须的,tag如果省略,默认是latest
  • 例如我的镜像仓库名是chakcheung,这个镜像我希望可以看出来是版本1的,那么我在打包镜像的时候会一步到位,docker build -t chakcheung/vuedemo:v1 .,当然后面是可以改的。
  • 这时候执行命令docker imagesdocker image ls,可以看到镜像列表
  • 接下来我想测试一下镜像,执行命令docker run -p 8080:80 -d --restart=always chakcheung/vuedemo:v1,如果运行成功,终端会反馈容器id
  • 执行命令docker ps,可以看到容器id,如果没有,可能是发错错误,执行命令docker ps -a可以看到所有容器,包括未运行的,执行命令docker logs 容器id,可以打印容器执行的日志,方便定位错误。
  • 如果执行成功,那可以打开浏览器,访问localhost:8080,这是应该可以看到项目正常运行。
  • 我们拆解一下运行镜像的命令
    • -p 该参数表示将对服务器8080端口的访问,映射到容器的80端口,我们已经在容器中将项目部署在80端口
    • -d 表示后台运行
    • --restart=always 表示自动重启

推送镜像

  • 完成了镜像的打包,接下来就可以push到我们的远程库
  • 执行命令docker login --username=仓库名,输入密码,登录成功会返回Login Succeeded
  • 执行命令docker push chakcheung/vuedemo:v1,将镜像推送到仓库
  • 如果在打包镜像的时候你没有把仓库名附上,可以执行命令docker tag 原镜像 [仓库名]/镜像名:[tag],标记镜像,将其归入仓库
  • 这时执行命令docker image ls可以看到多了一个镜像,执行push操作将其推送到远程库即可
  • 打开远程库的地址,或者打开docker desktop,可以在镜像列表中看到我们刚推送的镜像

拉取镜像并部署服务

  • 到服务器中,执行命令docker pull chakcheung/vuedemo:v1,前提是你已经登录了
  • 可以看到镜像列表多了一条记录,接下来我们要把这个镜像部署到服务器的9500端口
  • 执行docker run -p 9500:80 -d --restart=always chakcheung/vuedemo:v1
  • 执行docker ps看是否运行成功
  • 这时候就已经部署成功了,这时就可以访问服务器的9500端口,看到部署后的项目了

docker-compose

  • 按照上面的操作,我们可以把nodejs项目和mysql都部署到服务器上
  • nodejsDockerfile也是大同小异,如下
      # 使用官方 Node.js 12 轻量级镜像.
      FROM node:12-slim
      # 定义工作目录
      WORKDIR /usr/src/app
      # 将依赖定义文件拷贝到工作目录下
      COPY package*.json ./
      # 以 production 形式安装依赖
      RUN npm install --only=production
      # 将本地代码复制到工作目录内
      COPY . ./
      # 启动服务
      CMD [ "npm", "run", "start" ]
    
  • mysql可以在服务器拉取官方镜像,执行命令docker run --name mysql-demo -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d --restart=always mysql启动即可
  • 你当然可以这样,一个镜像一个镜像地打包、推送、拉取、启动,然后在每次更新代码的时候,也是重复地打包、推送、拉取、删除容器、启动。但你有没有想过有什么工具可以帮你做这些事情,那就是docker-compose
  • docker-compose基于yml脚本,可以很好地管理我们的镜像和容器,文档可以自己去搜
  • 安装docker-composewindowsmac安装docker时会同时安装,linux需要自己安装
  • 找一个目录,存储yml文件,执行命令vim docker-compose.yml创建yml文件,文件名随意,内容如下
# 使用版本
 version: "3"
    # 服务列表
    services:
      vuedemo:
        # 使用的镜像 也可以通过dockerfile指定打包脚本
        image: chakcheung/vuedemo:v1
        # 端口映射
        ports:
          - "9500:80"
      mysql:
        image: mysql
        ports:
          - "3306:3306"
        # 文件夹映射
        volumes:
          - /data/mysql/data/:/var/lib/mysql/
          - /data/mysql/conf/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf
        # 环境变量
        environment:
          - MYSQL_ROOT_PASSWORD=123456
          - LANG=C.UTF-8
      backend:
        image: chakcheung/backend
        ports:
          - "1333:1333"
  • 然后,在yml所在目录下执行命令docker-compose pull,这个命令会打包或拉取所有服务所需要的镜像,紧接着执行命令docker-compose up -d,该命令会根据yml启动服务
    • 当然,这两个命令都可以通过-f指定yml文件
    • docker-compose up可以声明--force-recreate强制重建服务,否则如果镜像没有更新,服务不会重建
  • 这时候,可以执行命令docker ps -a看看服务的执行情况,应该三个服务都执行成功
  • 以后我们代码更新了,只需要更新镜像,然后到服务器上,执行命令docker-compose pulldocker-compose up -d就可以了,丝滑!

dockerize

上一步,你也可能滑不下去,出现部分服务启动失败到情况。有一种情况,假如backend项目启动的时候,尝试连接数据库,那很可能会连接失败并报错,也就是项目依赖的问题,网上有不同的解决办法,这里我们不妨试试dockerize这个工具集

  • dockerize提供一些参数,让我们可以等待某个服务启动完成后,执行操作
  • 首先整理一下项目依赖关系,vuedemo基本上是不依赖其他两个的,backend因为启动的时候就初始化了mysql的连接,所以我们要等到mysql启动完毕之后,再启动backend,也就是我们要在backend这个容器的启动命令中,执行dockerize,我们修改一下yml文件,在backend中增加执行命令
  backend:
    image: chakcheung/backend
    ports:
      - "1333:1333"
    command: ["dockerize", "-wait", "tcp://mysql:3306", "-timeout", "1800s", "npm", "run", "start:prod"]
  • -wait 表示等待某个服务;-timeout 表示多少秒后超时;后面则是成功后执行的命令
  • 但这时候直接执行,会发现dockerize找不到,因为容器中不存在这个命令,我们要在容器中安装dockerize,这时候你可能会想到进到容器中安装,docker也提供了execattach方法让我们进到容器中,但这样做,以后每次更新我们都要重复去安装,那可不行,既然这个镜像没有,我们自己做个镜像!

制作镜像

这些是我学习过程中遇到的问题,也是自己想的解决办法,如果有更好的办法,请无情地嘲笑,然后教下我。

  • 原来backend是基于官方的node:12-slim,那我们可以基于这个镜像,封装一个带dockerize的镜像
  • 执行命令docker pull node:12-slim
  • 执行命令dcoker run -ti node:12-slim /bin/bash,运行并进入容器
  • 进入容器后,因为是mini版,要安装一些包,执行命令
    • apt-get update
    • apt-get upgrade
    • apt-get wget
  • 安装dockerize
wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-alpine-linux-amd64-v0.6.1.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-v0.6.1.tar.gz \
    && rm dockerize-alpine-linux-amd64-v0.6.1.tar.gz
  • exit退出容器
  • 执行docker commit -m "init commit" -a "chak" 容器id chakcheung/node:dockerize
  • 这时镜像列表会有一条新的记录,push到仓库就可以了
  • 修改backendDockerfile
# 使用封装的镜像
FROM chakcheung/node:dockerize
# 定义工作目录
WORKDIR /usr/src/app
# 将依赖定义文件拷贝到工作目录下
COPY package*.json ./
# 以 production 形式安装依赖
RUN npm install --only=production
# 将本地代码复制到工作目录内
COPY . ./
# 启动服务
CMD [ "npm", "run", "start:prod" ]
  • 重新打包镜像并push,到服务器中,执行pull和up
  • 完成后,执行命令docker logs 容器id查看backend的运行日志,可以看到
2021/07/28 07:51:45 Waiting for: tcp://mysql:3306
2021/07/28 07:51:46 Problem with dial: dial tcp 172.19.0.3:3306: connect: connection refused. Sleeping 1s
2021/07/28 07:51:47 Problem with dial: dial tcp 172.19.0.3:3306: connect: connection refused. Sleeping 1s
2021/07/28 07:51:48 Connected to tcp://mysql:3306
  • mysql服务部署完成后,才会开始部署backend,这样就解决了服务依赖的问题

一些问题

windows安装docker

windows上,docker安装完成后一般会出现这个错误

error during connect: In the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect.: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/containers/json: open //./pipe/docker_engine: The system cannot find the file specified.

好像是因为docker仅支持linux的内核,而网上的解决方法并不适用我,虽然很多人都说解决了,但一直都还是这个错误

# 网上的解决办法,切换容器
cd "C:\Program Files\Docker\Docker"
./DockerCli.exe -SwitchDaemon

因此我只能另外想办法,cmdpowershell都不能正常启动,那就试试linux的内核,刚好最近升级了系统,支持WSL2,于是安装了WSL2,并且安装了Ubuntu 20.04分发版,在Ubuntu终端下,成功执行docker命令

# 适用于 Linux 的 Windows 子系统安装指南 (Windows 10)
https://docs.microsoft.com/zh-cn/windows/wsl/install-win10#manual-installation-steps
# 适用于 Linux 的 Windows 子系统发行版包(Microsoft Store不可用时,可以在这下载)
https://docs.microsoft.com/zh-cn/windows/wsl/install-manual

mysql8 连接报错

mysql8以上的版本,连接时可能会出现这个错误

Client does not support authentication protocol requested by server; consider upgrading MySQL client

原因是MySQL8 之前的版本中加密规则是mysql_native_password,而在MySQL8之后,加密规则是caching_sha2_password,一般的解决方法是把mysql用户登录密码加密规则还原成mysql_native_password,因此你要么降低版本,要么修改加密规则,我选择保留8的版本

  • 执行docker exec -it 容器id /bin/bash进入容器,执行命令mysql -u root -p登录mysql
# 依次执行
ALTER USER 'root'@'%' IDENTIFIED BY '新密码' PASSWORD EXPIRE NEVER;
ALTER USER 'root'@'%' IDENTIFIED WITH MYSQL_NATIVE_PASSWORD BY '新密码';
# 重启
FLUSH PRIVILEGES;
  • 退出,重新连接即可
  • 但这么做,意味着如果mysql的容器销毁了,重建的时候我们还得做一遍,这很麻烦,虽然mysql的镜像不会轻易重建
  • 但我毕竟忍不了,所以还是按照上面解决dockerize的方法一样,基于官方mysql做了一个自己的镜像

nginx的部署问题

部署过程中遇到一个问题,我希望在访问9502端口的web目录时,代理到我的vue项目,也就是

访问localhost:9502 或 localhost:9502/web 或 localhost:9502/web/时
代理到部署在9500端口的服务
# 这是我的配置
 server {
    listen      9502;
    server_name localhost;
    location /    {
      rewrite     ^/$ http://$host:9502/web/;
    }
    location /web {
      rewrite     ^/web$ http://$host:9502/web/;
      proxy_pass  http://127.0.0.1:9500/;
    }
}
  • 一开始没整明白,以为9502/web/代理到9500时,会去读取当前目录的index.html以及其他资源,后来试了几回才发现,当index.html读取其他资源的时候,实际上是向localhost:9502/xxx发起请求,所以一直读取不到
  • 想明白了这一点,我把vue.config.jspublicPath值改为/web,这样index.html读取其他资源的时候,是向localhost:9502/web/xxx发起请求,那自然就被代理到9500下了
  • 没接触过就会比较陌生,还是要多看多学