美文网首页
docker单宿主机多容器实现自动化编译部署

docker单宿主机多容器实现自动化编译部署

作者: 4164fccdcf1c | 来源:发表于2019-11-12 14:57 被阅读0次

    一、应用环境

    操作系统:
    CentOS 7.4
    应用软件:
    Docker 19.03.4、 Certbot 0.40.1、nginx 1.17.5、Jenkins 2.202、Mysql Server 8.0.18、Redis server 5.0.6
    api服务器:
    ThinkJS 3.0 (基于 Koa 2.x)
    前端应用:
    Ant Design Pro V1
    域名解析:(指向当前主机)
    jenkins.***.domain、antd.***.domain、api.***.domain

    二、自动化编译部署的需求分析:

    前后端开发上传代码至git服务器,通过设置的webhooks调起jenkins中配置的编译部署流程完成自动化编译部署

    三、容器部署的整体思路与架构

    各容器组织架构.png
    1. 访问jenkins.***.domain域名,通过nginx容器代理,指向jenkins容器
    2. 通过jenkins构建“服务端镜像”,实现“服务端容器”的自动化编译部署
    3. 通过jenkins构建“前端镜像”,实现“前端容器”的自动化编译打包并共享数据卷给nginx
    4. 访问antd.***.domain域名,通过nginx容器代理,指向antd容器共享的前端静态文件
    5. 访问antd.***.domain/api路由,通过nginx容器代理,指向api服务器容器
    6. 实现api服务器容器与Mysql、Redis容器间的数据访问(处于安全考虑,数据容器不对外开放端口,无法通过域名直接访问)
    7. Certbot生成泛域名证书支持https访问

    四、容器部署需解决的问题

    1、容器间通信:

    1)容器每次重启分配给容器的内部ip都会改变,所以无法通过访问容器ip的形式进行容器间通信;
    2)一个容器如何与不同网络间的容器通信

    解决方案:
    4.1.1 创建两个桥接网络net0 - 网络名称natnet、net1 - 网络名称intranet
    4.1.2 natnet网络用于nginx容器与jenkins、api服务器容器通信(jenkins、api服务器容器需设置该网络下的网络别名)
    4.1.3 intranet网络用于api服务器容器与Mysql、Redis容器通信(Mysql、Redis容器需设置该网络下的网络别名)

    2、数据(文件)共享:

    容器间是相互独立的,前端容器打包生成的文件如何共享给nginx容器使用

    解决方案:
    4.2.1 通过挂载数据卷的形式,将宿主机下的数据共享目录分别挂载到多个容器下用于共享数据

    3、自定义镜像中依赖库的重复安装:

    镜像的创建基于一个已有的基础镜像,每次重新构建镜像时都必须重新下载依赖,如何减少依赖的重复下载

    解决方案:
    4.3.1 判断目标镜像是否构建,未构建则基于基础镜像构建新的镜像,已构建则基于已构建的镜像更新镜像

    五、具体实现步骤

    ps:docker安装,镜像获取及使用参考底部链接此处不再赘述

    1、网络设置
    #1、创建转发网络,供nginx代理转发
    docker network create natnet
    #2、创建内部网络,供服务访问数据库
    docker network create intranet
    
    2、容器设置
    #创建nginx容器,加入natnet网络,映射主机80、433端口,
    #挂载nginx配置文件路径,日志路径,网站路径、证书路径,并在后台运行
    docker run --name nginx \
      --network natnet \
      -p 80:80 -p 443:443 \
      -v /var/nginx/conf.d:/etc/nginx/conf.d \
      -v /var/nginx/logs:/var/log/nginx \
      -v /var/website:/var/website \
      -v /etc/letsencrypt:/etc/letsencrypt \
      -d nginx
    
    #创建mysql容器,加入intranet网络并设置别名,挂载文件路径,并在后台运行
    docker run --name mysql \
      --network intranet --network-alias mysql \
      -v mysql-data:/var/lib/mysql \
      -e MYSQL_ROOT_PASSWORD=MyPassW0rd.. \
      -d mysql
    
    #创建redis容器,加入intranet网络并设置别名,挂载文件路径,并在后台运行
    docker run --name redis \
      --network intranet --network-alias redis \
      -v redis-data:/data \
      -d redis
    
    #创建jenkins容器,加入natnet网络并设置别名,挂载文件路径,并在后台运行
    docker run --name jenkins \
      -u root \
      --network natnet --network-alias jenkins \
      -v jenkins-data:/var/jenkins_home \
      -v /var/run/docker.sock:/var/run/docker.sock \
      -v $(which docker):/usr/bin/docker \
      -v "$HOME":/home \
      -d jenkins/jenkins
    
    3、Nginx解析

    创建nginx容器时已将配置目录挂载至宿主机/var/nginx/conf.d目录下(在该目录下添加如下配置文件)

    forbidden.conf(显示的定义一个 default server 禁止ip以及未绑定域名的访问)

    # 显示的定义一个 default server 禁止ip以及未绑定域名的访问
    server {
      listen 80 default_server;
      server_name _;
      return 403; # 403 forbidden
    }
    server {
      listen 443 default_server;
      server_name _;
      return 403; # 403 forbidden
    }
    

    ssl_certificate.conf(泛域名证书路径)证书的申请请自行百度

    # 证书路径
    ssl_certificate /etc/letsencrypt/live/***.domain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/***.domain/privkey.pem;
    

    jenkins.conf(jenkins服务配置)

    server {
      listen 80;
      listen [::]:80;
      server_name jenkins.***.domain;
    
      location / {
        # 重定向到https
        rewrite ^/(.*)$ https://${server_name}$1 permanent;
      }
    }
    
    server {
      listen 443 ssl http2;
      server_name jenkins.***.domain;
    
      # 证书的公私钥
      include conf.d/ssl_certificate.conf;
    
      location / {
        proxy_pass http://jenkins:8080; #此处的jenkins为运行jenkins容器时配置的网络别名
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
      }
    }
    

    ps:至此,重载nginx配置即可访问jenkins服务

    proj_name.conf(项目服务配置)

    server {
      listen 80;
      listen [::]:80;
      server_name ***.domain www.***.domain proj_name.***.domain;
    
      location / {
        # 重定向到https
        rewrite ^/(.*)$ https://${server_name}$1 permanent;
      }
    }
    
    server {
      listen 443 ssl http2;
      server_name ***.domain www.***.domain proj_name.***.domain;
      # gzip config
      gzip on;
      gzip_min_length 1k;
      gzip_comp_level 9;
      gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
      gzip_vary on;
      gzip_disable "MSIE [1-6]\.";
    
      root /var/website/proj_name; #此路径为前端容器共享数据卷目录
    
      # 证书的公私钥
      include conf.d/ssl_certificate.conf;
    
      location / {
        # 用于配合 browserHistory使用
        try_files $uri $uri/ /index.html;
      }
      location /api {
        proxy_pass http://proj_name.api:8360/api; #此处为api容器网络别名,端口及模块路由
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Host              $http_host;
        proxy_set_header   X-Real-IP         $remote_addr;
      }
    }
    
    4、自动化编译部署

    4.1 构建proj_name/api镜像并运行

    在api工程根目录创建Dockerfile并根据自身项目修改具体配置
    示例内容如下(工程基于ThinkJS 3.0)

    ARG  BASE_IMAGE=node
    FROM ${BASE_IMAGE}
    
    WORKDIR /proj/server
    COPY package.json ./package.json
    RUN npm i --production --registry=https://registry.npm.taobao.org
    
    COPY src ./src
    COPY view ./view
    #COPY www ./www
    COPY production.js ./production.js
    
    ENV DOCKER=true
    EXPOSE 8360
    CMD [ "node", "./production.js" ]
    

    将如下内容复制到jenkins相应项目配置 -> 构建中,(或在api工程根目录创建build.sh并复制如下内容,而后在jenkins相应项目配置 -> 构建中运行该脚本)
    示例内容如下(工程基于ThinkJS 3.0)
    ps:注意修改镜像名称、容器名称,及运行容器时的配置

    #!/bin/bash
    
    #构建的镜像名称
    IMAGE='proj_name/api'
    #运行的容器名称
    CONTAINER='proj_name.api'
    
    #构建镜像并启动容器
    function build_run {
      #使用根目录下的Dockerfile构建镜像,默认使用node作为基镜像
      docker build -t $IMAGE \
        --build-arg BASE_IMAGE=${1:-"node"} .
    
      #停止并移除旧容器
      remove_container
    
      #创建容器,加入指定网络,并在后台运行
      docker run --name $CONTAINER \
        --network intranet \
        -d $IMAGE
      #连接其他网络并设置别名
      docker network connect --alias $CONTAINER natnet $CONTAINER
    }
    
    #移除旧容器
    function remove_container {
      #判断容器是否已存在
      cID=`docker ps -aqf 'name='$CONTAINER`
      if [ -z "$cID" ]; then
        #容器不存在
        echo '未找到该容器,将创建新的容器并启动'
        return 1
      fi
    
      #判断容器是否运行
      cID=`docker ps -qf 'name='$CONTAINER`
      if [ -n "$cID" ]; then
        #停止容器
        echo '该容器已运行,将关闭该容器'
        docker stop $CONTAINER
      fi
      #移除容器
      echo '该容器已停止运行,将移除该容器'
      docker rm $CONTAINER
    }
    
    #判断镜像是否已存在
    imgID=`docker images -q $IMAGE`
    if [ -z "$imgID" ]; then
      #镜像不存在,构建镜像并运行容器
      echo '未找到该镜像,开始构建新的镜像。。。。'
      build_run
    else
      #镜像已存在,更新镜像并运行容器
      echo '该镜像已存在,开始更新镜像。。。。'
      build_run $IMAGE
    fi
    

    4.2 构建proj_name.web镜像并运行

    在web工程根目录创建Dockerfile并根据自身项目修改具体配置
    示例内容如下(工程基于Ant Design Pro)

    ARG  BASE_IMAGE=node
    FROM ${BASE_IMAGE}
    
    WORKDIR /usr/src/app/
    COPY package.json ./
    RUN npm install --registry=https://registry.npm.taobao.org
    
    COPY ./ ./
    
    CMD ["npm", "run", "build"]
    

    将如下内容复制到jenkins相应项目配置 -> 构建中,(或在api工程根目录创建build.sh并复制如下内容,而后在jenkins相应项目配置 -> 构建中运行该脚本)
    示例内容如下(工程基于Ant Design Pro)
    ps:注意修改镜像名称、容器名称,及容器共享数据卷的挂载目录(供nginx容器读取)

    #!/bin/bash
    
    #构建的镜像名称
    IMAGE='proj_name/web'
    #运行的容器名称
    CONTAINER='proj_name.web'
    
    #构建镜像并启动容器
    function build_run {
      #使用根目录下的Dockerfile构建镜像,默认使用node作为基镜像
      docker build -t $IMAGE \
        --build-arg BASE_IMAGE=${1:-"node"} .
    
      #停止并移除旧容器
      remove_container
    
      #创建容器,挂载编译后的文件路径,并在后台运行
      docker run --name $CONTAINER \
        -v /var/website/proj_name:/usr/src/app/dist \
        -d $IMAGE
    }
    
    #移除旧容器
    function remove_container {
      #判断容器是否已存在
      cID=`docker ps -aqf 'name='$CONTAINER`
      if [ -z "$cID" ]; then
        #容器不存在
        echo '未找到该容器,将创建新的容器并启动'
        return 1
      fi
    
      #判断容器是否运行
      cID=`docker ps -qf 'name='$CONTAINER`
      if [ -n "$cID" ]; then
        #停止容器
        echo '该容器已运行,将关闭该容器'
        docker stop $CONTAINER
      fi
      #移除容器
      echo '该容器已停止运行,将移除该容器'
      docker rm $CONTAINER
    }
    
    #判断镜像是否已存在
    imgID=`docker images -q $IMAGE`
    if [ -z "$imgID" ]; then
      #镜像不存在,构建镜像并运行容器
      echo '未找到该镜像,开始构建新的镜像。。。。'
      build_run
    else
      #镜像已存在,更新镜像并运行容器
      echo '该镜像已存在,开始更新镜像。。。。'
      build_run $IMAGE
    fi
    
    5、配置jenkins与git服务端的Webhooks

    请自行百度,不再赘述!

    六、结束:

    ps:最后可将上诉步骤自行整合成 docker-compose.yml

    参考:
    Docker 软件安装

    相关文章

      网友评论

          本文标题:docker单宿主机多容器实现自动化编译部署

          本文链接:https://www.haomeiwen.com/subject/bswvbctx.html