一行代码把服务干挂了,竟然是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= LATEST∣grep−vCONTAINER∣awk−vname=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误把库删了......的主要内容,如果未能解决你的问题,请参考以下文章