一行代码把服务干挂了,竟然是Docker误把库删了......

Posted 魏小言

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一行代码把服务干挂了,竟然是Docker误把库删了......相关的知识,希望对你有一定的参考价值。


本文涉及Docker容器化部署相关知识,建议收藏,追更!!!

服务事故

服务挂了

  今个日志实时分析模块 「Analysis,简称A」上线,刚上完;日志收集模块「AnalysisCollect,简称C」挂了……线上数据都没了….完犊子….
  ……
  经过排查,发现是A模块部署的时候把C服务容器Stopped,连镜像都删的一干二净,删库了….

事故排查

  故事的详细过程是这样的:
  A服务上线部署,执行deploy.sh文件,如下:

#!/bin/bash

PRO=$1
LATEST=$2
DOMPOSE_URL=$3

function get_compose_yml(){
    wget $DOMPOSE_URL -O /tmp/docker-compose.yml
    if [[ $? -ne 0 ]];then
        echo 'Download Compose File Failed!!!'
        exit 1
    fi
}

function deploy(){
    get_compose_yml
    current_version=`docker ps -a|awk '{print $NF}'|grep -i $PRO`
    if [[ $current_version == $LATEST ]];then
        # 相同Tag不进行部署,直接跳过部署环节
        echo "$LATEST 容器已部署."
        exit 0
    else
        cd /tmp
        sed -i "s/\\${{VERSION}}/$LATEST/g" docker-compose.yml 
        for((i=1;i<=5;i++));
        do
            if [[ $i == 5 ]];then
                echo "docker pull 已重试5次."
                break
            fi
            docker-compose -f docker-compose.yml  pull
            if [[ $? -eq 0  ]];then
                break
            else 
                continue      
            fi
        done 
    fi

    if [[ $? -eq 0 ]];then
        docker-compose up -d $PRO
        sleep 10s
    fi
    docker ps --filter 'status=running' |grep $LATEST
    if [[ $? -eq 0 ]];then
        echo "$LATEST Container is Running."
    else
        echo "$LATEST Container is Run Failed !"
        exit 1
    fi
}

function remove(){
  deploy
  if [[ $? -eq 0 ]];then
    container_id=`docker ps -a|grep -i $PRO|grep -v $LATEST|grep -v CONTAINER|awk '{print $1}'`
    image=`docker ps -a|grep -i $PRO|grep -v $LATEST|grep -v IMAGE|awk '{print $2}'`
    docker stop $container_id && docker rm -f $container_id && docker rmi -f $image
    echo "Container Rolling Update Success."
  else
    echo "Remove Container is Failed!"
    exit 2
  fi
}

remove

问题定位

  通过观察deploy.sh逻辑会发现,在deploy()之后,会进行remove()「删除非最新版本的镜像,停掉相关的容器」操作。

  remove()其中的部分过滤条件:

docker ps -a|grep -i $PRO|grep -v $LATEST|grep -v CONTAINER|awk {print $1}'

  会把docker的C服务容器匹配到,进行remove逻辑,导致服务挂掉。

问题修正

1、问题定位到了,可为什么会这样呢?

在这里插入图片描述

在整个产品链路中通过Jenkins打不同的服务包给Docker,然后Docker进行部署,提供服务。

  • 生产场景中A服务和C服务在代码层属于同一代码库,即产生同一Tag;
    「例:tag.1.0」
  • 经过Jenkins编译打包处理,出现了镜像有相同后缀;
    「例:XXX.A:tag.1.0|XXX.B:tag.1.0」
  • 在deploy.sh部署文件remove()过滤条件中,grep的几个条件会把C服务的容器拿到,进而进行remove操作,导致C服务挂掉。

2、问题原因明了,怎么进行修正呢?
  既然直接问题是remove条件无法隔离C服务容器,那么就对过滤条件进行调整,新增过滤条件,直至remove到目标镜像/容器。

  调整如下:

container_id=docker ps -a|grep -i $PRO|grep -v $LATEST|grep -v CONTAINER|awk '$NF == "'$PRO ‘ {print $1}'

  也可以是:

docker ps -a|grep -i $PRO|grep -v L A T E S T ∣ g r e p − v C O N T A I N E R ∣ a w k − v n a m e = LATEST|grep -v CONTAINER|awk -v name= LATESTgrepvCONTAINERawkvname=PRO ‘$NF == name {print $1}’

问题思考

1、问题有了修正方法,可细品这样做貌似多余,为何在deploy之后增加remove逻辑呢?
  我们在通过docker-compose up进行部署的时候,对于已经存在的容器,有镜像或者配置文件有调整的时候,Docker会在原镜像层之上进行覆盖调整,containerID\\数据卷都不会变,换句话说,就是不用remover操作,remove是冗余的。

  故deploy可调整为:

#!/bin/bash

PRO=$1
LATEST=$2
DOMPOSE_URL=$3

function get_compose_yml(){
    wget $DOMPOSE_URL -O /tmp/docker-compose.yml
    if [[ $? -ne 0 ]];then
        echo 'Download Compose File Failed!!!'
        exit 1
    fi
}

function deploy(){
    get_compose_yml
    current_version=`docker ps -a|awk '{print $NF}'|grep -i $PRO`
    if [[ $current_version == $LATEST ]];then
        # 相同Tag不进行部署,直接跳过部署环节
        echo "$LATEST 容器已部署."
        exit 0
    else
        cd /tmp
        sed -i "s/\\${{VERSION}}/$LATEST/g" docker-compose.yml 
        for((i=1;i<=5;i++));
        do
            if [[ $i == 5 ]];then
                echo "docker pull 已重试5次."
                break
            fi

            docker-compose -f docker-compose.yml  pull
            if [[ $? -eq 0  ]];then
                break
            else 
                continue      
            fi
        done 
    fi

    if [[ $? -eq 0 ]];then
        docker-compose up -d $PRO
        sleep 10s
    fi
    docker ps --filter 'status=running' |grep $LATEST
    if [[ $? -eq 0 ]];then
        echo "$PRO Container is Running."
    else
        echo "$PRO Container is Run Failed !"
        exit 1
    fi
}

deploy

2、看到这里,未涉及过docker的同学就会有疑问了,docker是什么/?jenkins不是通吃吗/?那么docker是个什么东西呢/?

下面继续告诉你…


Docker部署详解

  Docker是一种使资源容器化的一个产品,在云原生架构、微服务、k8s…现主流组件及架构中发挥着重要作用。
  在一些大厂牌,各种服务几乎都是安排在容器中,实体机比例已经非常小了,容器化也是未来服务部署的一种主趋势。

为什么容器化将会成为顶流?

容器的主要差异化优势在于:

1、能够包装,便于移植
  容器把服务包装成以镜像为基础单位的容器提供服务,基本解除了对服务器的依赖,且具备可移植性。

2、减少资源占用空间,提高了服务器的资源利用率
容器是作为服务器的一进程运行,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。

3、启动迅速
Docker 相当于启动宿主操作系统上的一个进程;
而启动虚拟机需要先启动虚拟机的操作系统,再启动应用,这个过程非常慢;

4、…

Docker作为一种容器化的产物,具备容器化许多优势,当然容器化也有些缺点,比如数据持久化,等等。我们暂不对这部分展开,下面聊下关于Docker的部署。

Docker部署三件套

Dockerfile

  Dockerfile是Docker构建镜像的配方,docker build的就是dockerfile文件,之后可docker run/start启动容器。

  Dockerfile如下:

//FROM 指定基础镜像
FROM XXX/centos7.4:base_v2
//RUN执行命令。每一条 RUN 都会生成一层,一个需求尽量使用&&,这样就减少了 RUN ,即减少了分层
RUN mkdir -p /XXX/Analysis \\
    && mkdir -p /XXX/Analysis \\
    && mkdir -p /XXX/AnalysisCollect
//ENV设置环境变量 定义 TE,那么之后就可以使用 $TE来执行了
ENV TZ Asia/Shanghai
//ADD 添加,ADD能自动解压文件
ADD analysis.tgz /XXX/Analysis
ADD analysis.tgz /XXX/AnalysisCollect

  Dockerfile从代码上看,是一个指令集合:

第一列是指令,后面列是指令参数;
每一行就是一新镜像。

Dockerfile执行过程

  Dockerfile可以看做是一个洋葱。

1、洋葱芯是From,这个指令表示了镜像根基,如上文即centos7.4:base_v2,是基础镜像;
2、每执行一行指令,会在基础镜像上执行docker commit,形成新的镜像层;
3、再次执行下一行则基于刚提交的镜像,一次次的执行,最终一个洋葱就是docker build的最终镜像产物。

Dockerfile指令详解

  • FROM:基础镜像
  • MAINTAINER:镜像维护者
  • RUN:容器构建时需要运行的命令
  • EXPOSE:当前容器对外暴露出的端口
  • WORKDIR:指定在创建容器后,终端默认登陆的进来工作目录
  • ENV:用来构建镜像过程中设置环境变量
  • ADD:将宿主机目录下的文件拷贝进镜像,且会自动处理
  • URL和解压tar压缩包
  • COPY:拷贝文件和目录到镜像中
  • VOLUME:容器数据卷,用于数据保存和持久化工作
  • CMD:指定一个容器启动要运行的命令,Dockerfile中可以有多个CMD指令,但是 只有最后一个生效,CMD会被docker run之后的参数替换。
  • ENTRYPOINT:指定一个容器启动要运行的命令,docker
    run之后的参数会被当做参数传递给ENTRYPOINT,之后形成新的命令组合
  • ONBUILD:当构建一个被继承的Dockerfile时运行命令,父镜像在被子继承后父镜像的onbuild被触发
  • LABEL:语法:LABEL = = = … LABEL 指令将元数据添加到镜像。LABEL 是键值对。要在 LABEL
    值中包含空格,请使用引号和反斜杠,就像在命令行解析中一样。

Docker-compose.yml

  Docker-compose是Docker编排容器的工厂,负责对容器的配置/启动/终止/替换镜像….编排。

  上面说dockerfile是Docker的配方,可用于启动容器;
  在实际生产中,每个项目需要多个镜像,多个容器实例「如mysql、kafka、nginx…等基础服务」,如果每个依赖docker run启动管理,在基于云的服务中,这样几乎不可能完成服务的管理,相当繁琐;
  这样就需要一个组件进行编排,统筹管理一/多个项目,那么docker-compose营运而生。如果用docker-composer,就可以把这些命令一次性写在docker-composer.yml文件中,以后每次启动这一整个环境(含多个个容器)的时候,只要一个docker-composer up命令就可以了!

  Docker-compose如下:

version: ‘3.5'
services:
  analysis:
    container_name: flowanalysis
    # 指定服务的镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。
    image: XXX/analysis:${{VERSION}}
        # 网络模式,与Docker client的--net参数类似
    network_mode: "host"
    restart: always
    ulimits:
      nofile: 150000
    working_dir: /XXX/Analysis
    # 挂载一个目录或者一个已存在的数据卷容器,可以直接使用 [HOST:CONTAINER] 这样的格式,或者使用 [HOST:CONTAINER:ro] 这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统。
    volumes:  
      - /XXX/Analysis:/XXXAnalysis
      - /XXX
    # CMD 执行命令
    command:
      - /bin/bash
      - -c
      - |XXX
  
  analysiscollect:
    container_name: analysiscollect
    image: XXX/analysiscollect:${{VERSION}}
    network_mode: "host"
    restart: always
    ulimits:
      nofile: 150000
    working_dir: /XXX/AnalysisCollect
    volumes:  
      - /XXX/Analysis:/XXX/Analysis
      - /XXX

    command:
      - /bin/bash
      - -c
      - |XXX
        

例子中,Docker-compose通过配置文件的方式进行容器参数的注入。

Docker-compose配置解析

  • version:指定 docker-compose.yml 文件的写法格式
  • services:多个容器集合
  • build:配置构建时,Compose 会利用它自动构建镜像,该值可以是一个路径,也可以是一个对象,用于指定 Dockerfile 参数
  • dns:配置 dns 服务器,可以是一个值或列表
  • dns_search:配置 DNS 搜索域,可以是一个值或列表
  • expose:暴露端口,只将端口暴露给连接的服务,而不暴露给主机
  • network_mode:设置网络模式
  • ports:对外暴露的端口定义,和 expose 对应
  • links:将指定容器连接到当前连接,可以设置别名,避免ip方式导致的容器重启动态改变的无法连接情况
  • logs:日志输出信息
  • command:覆盖容器启动后默认执行的命令

Docker-compose指令详解

格式:docker-compose [–OPTION]

OPTION LIST

  • ps:列出所有运行容器
  • logs:查看服务日志输出
  • up:构建、启动容器
  • kill:通过发送 SIGKILL 信号来停止指定服务的容器
  • pull:下载服务镜像
  • rm:删除指定服务的容器
  • start:启动指定服务已存在的容器
  • stop:停止已运行的服务的容器
  • build:构建或者重新构建服务

Deploy.sh

  Deploy.sh是项目的部署脚本,根据实际的生产环境,选择适当的策略进行Docker的部署。

Dockerfile & Docker-compose区别

简单总结下

dockerfile是从无到有的构建镜像。它包含安装运行所需的环境、程序代码等。
docker-compose是编排容器的。在启动服务「含多个容器」的场景使用。

Q&A

1、Docker的架构是怎样的呢/?

请关注后续更文

2、数据卷是什么/?存储了哪些东西呢/?

请关注后续更文

3、为什么docker-compose up 数据卷不覆盖呢/?

请关注后续更文

附录

人人都听过Docker、K8s…可你真得了解它多少!!!

一天一个小技巧,偷偷超越隔壁小朋友!!

以上是关于一行代码把服务干挂了,竟然是Docker误把库删了......的主要内容,如果未能解决你的问题,请参考以下文章

卧槽,SQL注入竟然把我们的系统搞挂了!

dockergitlab服务器挂了

SQL 注入竟然把我们的系统搞挂了

卧槽,sql注入竟然把我们的系统搞挂了

我就想加个索引,怎么就这么难?

我就想加个索引,怎么就这么难?