我们的应用发布都是以镜像为基础,git打tag触发cicd,发布对应version的镜像。现在有个应用需要部署到客户内网中,如果以导出镜像的形式,再给客户部署,是比较繁琐的。并且无法做一些额外的工作,例如安装前的环境检测,卸载时删除镜像,以及更新等。当然这些都可以通过脚本实现,但不够优雅。而同事之前做过以linux安装包的形式发布容器应用,这种方式更加正式且方便,遂复用之。
deb包是 Debian 系列的应用安装包格式。
由于脚本涉及到一些敏感信息,所以进行了脱敏工作。
在chrome项目里增加一个build文件夹存放打包相关的文件,下面是build目录的结构。
.
├── README.md
├── deb
│ ├── DEBIAN
│ │ ├── control
│ │ ├── postinst
│ │ ├── preinst
│ │ └── prerm
│ ├── opt
│ │ └── google
│ │ └── chrome
│ │ ├── docker-compose.yml
│ │ └── chrome-interface
│ │ ├── configs
│ │ └── data
│ └── usr
│ ├── lib
│ │ └── systemd
│ │ └── system
│ │ └── chrome.service
│ └── local
│ └── bin
│ └── chrome-start.sh
└── package-deb.sh
deb包一般分成两部分:
build/package-deb.sh
#!/bin/bash
###############################################################################
###################### 打Deb包需要修改的变量,主要是依赖组件的版本 ################
###############################################################################
# 设置chrome当前需要发布的版本
APP_VERSION="1.0.0"
declare -A versions
# 设置chrome-interface当前组件依赖的版本
versions["chrome-interface"]="latest"
###############################################################################
########################## 后续脚本不需要修改 ####################################
###############################################################################
# 环境检测
if ! dpkg --version >/dev/null 2>&1; then
echo "请在当前机器安装dpkg工具"
exit 1
fi
if ! docker version >/dev/null 2>&1; then
echo "请在当前机器安装docker"
exit 1
fi
if ! sed --version >/dev/null 2>&1; then
echo "请在当前机器安装sed工具"
exit 1
fi
# 是否需要下载tag为latest的镜像,默认是需要下载的,因为latest的镜像以为者随时可能会更新。
# 如果不想下载tag为latest的镜像,可以通过执行: ./package-deb.sh --allwaysDownloadLatestImage=false来取消镜像下载
allwaysDownloadLatestImage="true"
ARGS=$(getopt -o a:: --long allwaysDownloadLatestImage:: -n "$0" -- "$@")
eval set -- "$ARGS"
while true ; do
case "$1" in
-a|--allwaysDownloadLatestImage) allwaysDownloadLatestImage="false" ; shift 2 ;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
if [ "${allwaysDownloadLatestImage}" == "true" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 更新tag为latest的镜像,并重新制作其镜像压缩包\n"
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 忽略更新tag为latest的镜像,继续使用之前制作的镜像压缩包\n"
fi
SHELL_PATH=$(cd "$(dirname "$0")"||exit;pwd)
SCRIPT_START_TIME=$(date +%s)
# 镜像仓库地址
HARBOR_ADDR="1.1.1.1"
# 镜像
declare -A images
images["chrome-interface"]="chrome-group/chrome-interface:${versions["chrome-interface"]}"
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 开始下载镜像"
START_IMAGE_DOWNLOAD_TIME=$(date +%s)
for name in ${!images[*]}; do
{
image="${HARBOR_ADDR}/${images[$name]}"
version=${versions[$name]}
# 如果镜像不存在才下载镜像
if ! docker image inspect "${image}" >/dev/null 2>&1 || ([ "${allwaysDownloadLatestImage}" == "true" ] && [ "$version" == "latest" ]); then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 下载镜像:${image}"
if ! docker pull "${image}"; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 下载镜像:${image} 失败"
fi
fi
if ! docker image inspect "${images[$name]}" >/dev/null 2>&1 || [ "$version" == "latest" ]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 执行: docker tag ${image} ${images[$name]}命令,重新打Tag,隐藏镜像仓库地址"
docker tag "${image}" "${images[$name]}"
fi
}&
done
wait
END_IMAGE_DOWNLOAD_TIME=$(date +%s)
EXECUTING_TIME=$((END_IMAGE_DOWNLOAD_TIME - START_IMAGE_DOWNLOAD_TIME))
allImageDownloadSuccessful="true"
for image in ${images[@]}; do
if ! docker image inspect "$image" >/dev/null 2>&1; then
allImageDownloadSuccessful="false"
fi
done
if [ "$allImageDownloadSuccessful" == "false" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 部分镜像下载失败,请重新此脚本下载镜像,或者手动下载镜像\n"
exit 1
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 所有镜像下载完成,共消耗:$EXECUTING_TIME 秒\n"
fi
# 创建保存镜像压缩包目录
imageDir="${SHELL_PATH}/deb/opt/google/docker"
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 镜像打包目录为:${imageDir}\n"
if [ ! -d "${imageDir}" ]; then
mkdir -p "${imageDir}"
fi
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 开始打包镜像为tar压缩包"
START_IMAGE_TAR_TIME=$(date +%s)
for name in ${!images[*]}; do
{
image=${images[$name]}
version=${versions[$name]}
path="${imageDir}/${name}_${version}.tar"
deleteFiles=$(ls "${imageDir}" | grep "${name}" |grep -v "${name}_${version}.tar")
for element in ${deleteFiles[@]}; do
file="${imageDir}/${element}"
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 删除无用的镜像压缩包:${file}"
rm -f "${file}"
done
# 如果镜像压缩包不存在或者tag为latest,都需要打包
if ([ "${allwaysDownloadLatestImage}" == "true" ] && [ "$version" == "latest" ]) || [ ! -f "$path" ]; then
rm -f "$path"
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 打包镜像为tar压缩包:$image, 压缩包路径为:$path"
if ! docker save -o "${path}" "${image}"; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 打包镜像${image}为${path}压缩包失败!!!"
fi
fi
}&
done
wait
END_IMAGE_TAR_TIME=$(date +%s)
EXECUTING_TIME=$((END_IMAGE_TAR_TIME - START_IMAGE_TAR_TIME))
allImageTarSuccessful="true"
for name in ${!images[*]}; do
version=${versions[$name]}
path="$imageDir/${name}_${version}.tar"
# 如果镜像压缩包不存在或者tag为latest,都需要打包
if [ ! -f "$path" ]; then
allImageTarSuccessful="false"
fi
done
if [ "$allImageTarSuccessful" == "false" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 部分镜像打包失败,请重新此脚本打包镜像,或者手动打包镜像\n"
exit 1
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 所有镜像打包完成, 打包镜像共消耗:$EXECUTING_TIME 秒\n"
fi
# 替换变量
controlPath="$SHELL_PATH/deb/DEBIAN/control"
sed -i "s/^Version.*/Version: ${APP_VERSION}/" "$controlPath"
preinstPath="$SHELL_PATH/deb/DEBIAN/preinst"
sed -i "s/APPVERSION/${APP_VERSION}/g" "$preinstPath"
prermPath="$SHELL_PATH/deb/DEBIAN/prerm"
sed -i "s/APPVERSION/${APP_VERSION}/g" "$prermPath"
postinstPath="$SHELL_PATH/deb/DEBIAN/postinst"
for name in ${!images[*]}; do
image="${images[$name]}\""
version=${versions[$name]}
cmd="docker/${name}_${version}.tar"
sed -i "s@docker/${name}.*tar@${cmd}@" "$postinstPath"
sed -i "s@image/${name}.*@${image}@" "$postinstPath"
sed -i "s@image/${name}.*@${image}@" "$prermPath"
done
sed -i "s/APPVERSION/${APP_VERSION}/g" "$postinstPath"
composePath="${SHELL_PATH}/deb/opt/google/chrome/docker-compose.yml"
sed -i "s@x-appVersion:.*@x-appVersion: \"${APP_VERSION}\"@" "$composePath"
for name in ${!images[*]}; do
image=${images[$name]}
array=(${image//:/ })
prefix=${array[0]}
sed -i "s@image:.*${prefix}.*@image: ${image}@" "$composePath"
done
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 删除遗留 deb 包"
rm -f *.deb
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 开始制作deb包,由于镜像较大,dpkg打包时间比较长,在4核16G的机器上打包大概需要10分钟,请根据实际的打包机器资源耐心等待!!!"
BUILD_DEB_START_TIME=$(date +%s)
dpkg -b "${SHELL_PATH}/deb"
dpkg-name "deb.deb"
BUILD_DEB_END_TIME=$(date +%s)
EXECUTING_TIME=$((BUILD_DEB_END_TIME - BUILD_DEB_START_TIME))
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 制作deb包共消耗:$EXECUTING_TIME 秒\n"
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 删除导出的镜像"
rm -rf deb/opt/google/docker
mv chrome_${APP_VERSION}_amd64.deb Chrome_On-Premise_V${APP_VERSION}.deb
SCRIPT_END_TIME=$(date +%s)
EXECUTING_TIME=$((SCRIPT_END_TIME - SCRIPT_START_TIME))
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 执行此脚本共消耗:$EXECUTING_TIME 秒\n"
build/deb/DEBIAN/control
Package: chrome
Version: 1.0.0
Section: utils
Prioritt: standard
Architecture: amd64
Maintainer: xxx@gmail.com
Description: Chrome On-Premise
build/deb/DEBIAN/preinst
#!/bin/bash
set -e
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 开始执行Chrome_On-Premise_VAPPVERSION.deb的preinst脚本,dpkg传入参数为:[$*]\n"
if ! df --version >/dev/null 2>&1; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器未安装df工具,无法检测磁盘容量,不能保证应用正常安装,请联系管理员安装df工具\n"
exit 1
fi
if ! awk --version >/dev/null 2>&1; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器未安装awk工具,无法检测磁盘容量,不能保证应用正常安装,请联系管理员安装awk工具\n"
exit 1
fi
if ! grep --version >/dev/null 2>&1; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器未安装grep工具,无法检测磁盘容量,不能保证应用正常安装,请联系管理员安装grep工具\n"
exit 1
fi
if ! free --version >/dev/null 2>&1; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器未安装free工具,无法检内容容量,不能保证应用正常运行,请联系管理员安装free工具\n"
exit 1
fi
if ! docker compose version >/dev/null 2>&1; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器安装的docker过低,请安装23.0.0以上的docker版本"
exit 1
fi
if ! dpkg-name --help >/dev/null 2>&1; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器未安装dpkg-dev工具,无法构建deb包,不能保证应用正常运行,请联系管理员安装dpkg-dev工具\n"
exit 1
fi
# 磁盘空间检测
total=$(df / | grep "/$" | awk '{print $2}')
totalG=$((${total}/1024/1024))
used=$(df / | grep "/$" | awk '{print $3}')
usedG=$((${used}/1024/1024))
available=$(df / | grep "/$" | awk '{print $4}')
availableG=$((${available}/1024/1024))
if [ `echo ${availableG} | awk -v tem=50 '{print($1<tem)? "1":"0"}'` -eq "1" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前磁盘空间根目录总共分配${totalG}G,已使用${usedG}G,剩余${availableG}G; 当前机器根目录可用磁盘空间不足50G,无法安装应用,请保证根目录可用磁盘空间大于50G!!!\n"
exit 1
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前磁盘空间根目录总共分配${totalG}G,已使用${usedG}G,剩余${availableG}G; 磁盘可用空间充足,可以正常安装!!!\n"
fi
# 内存检测
total=$(free | sed '1d;3d' | awk '{print $2}')
totalG=$(echo ${total} 1024 1024 | awk '{print("%.2f",$1/$2/$3)}' | awk '{print $2}')
used=$(free | sed '1d;3d' | awk '{print $3}')
usedG=$(echo ${used} 1024 1024 | awk '{print("%.2f",$1/$2/$3)}' | awk '{print $2}')
available=$(free | sed '1d;3d' | awk '{print $7}')
availableG=$(echo ${available} 1024 1024 | awk '{print("%.2f",$1/$2/$3)}' | awk '{print $2}')
if [ `echo ${totalG} | awk -v tem=15 '{print($1<tem)? "1":"0"}'` -eq "1" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器内存总共分配${totalG}G,已使用${usedG}G,剩余${availableG}G; 当前机器分配内存小于16G,无法保证应用正常运行,请增加内存!!!\n"
exit 1
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器内存总共分配${totalG}G,已使用${usedG}G,剩余${availableG}G; 可以正常安装!!!\n"
fi
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] dpkg开始解压Deb包,请耐心等待!!!\n"
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 执行Chrome_On-Premise_VAPPVERSION.deb的preinst脚本结束\n"
build/deb/DEBIAN/postinst
#!/bin/bash
action=$(echo $* | awk '{print $1}')
if [ "${action}" == "abort-upgrade" ] || [ "${action}" == "abort-install" ]; then
exit 0
fi
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 开始执行执行Chrome_On-Premise_VAPPVERSION.deb的postinst脚本,dpkg传入参数为:[$*]\n"
if ! docker version >/dev/null 2>&1; then
echo "请在当前机器安装docker"
exit 1
fi
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 解压deb完成,开始安装应用!!!\n"
mkdir -p /var/google/chrome/chrome-interface/logs
if [ ! -d "/var/google/chrome/chrome-interface/data" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 本地库不存在,拷贝安装包里的数据库\n"
mv /opt/google/chrome/chrome-interface/data /var/google/chrome/chrome-interface
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 本地库已存在,删除安装包里的数据库\n"
rm -rf /opt/google/chrome/chrome-interface/data
fi
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 检查internal网络"
if ! docker network ls | grep internal >/dev/null 2>&1; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器没有创建internal网络, 创建internal网络"
if ! docker network create internal --driver=bridge --subnet=169.254.253.0/24; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器创建internal网络出错"
exit 1
fi
fi
declare -A archives
archives["chrome-interface"]="/opt/google/docker/chrome-interface.tar"
declare -A images
images["chrome-interface"]="image/chrome-interface:latest"
IMPORT_IMAGE_START_TIME=$(date +%s)
for name in ${!archives[*]}; do
loadImage=${archives[$name]}
image=${images[$name]}
array=(${image//:/ })
subfix=${array[1]}
if [ "${subfix}" == "latest" ] || ! docker image inspect "${image}" >/dev/null 2>&1; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器导入${image}镜像..."
if ! docker load -i "${loadImage}"; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 执行: docker load -i ${loadImage}导入到当前机器镜像失败,请联系管理员排查具体失败原因\n"
else
rm -f "${loadImage}"
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器导入${image}镜像成功,将移除${loadImage}镜像压缩包!!!\n"
fi
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器已经导入${image}镜像,跳过导入\n"
fi
done
IMPORT_IMAGE_END_TIME=$(date +%s)
EXECUTING_TIME=$((IMPORT_IMAGE_END_TIME - IMPORT_IMAGE_START_TIME))
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 导入镜像共消耗:${EXECUTING_TIME}秒\n"
allImageDownloadSuccessful="true"
for image in ${images[@]}; do
if ! docker image inspect "${image}" >/dev/null 2>&1; then
allImageDownloadSuccessful="false"
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 镜像${image}未找到,请联系管理员\n"
fi
done
if [ "$allImageDownloadSuccessful" == "false" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 部分镜像导入失败,应用安装失败,请联系管理员排查具体原因\n"
exit 1
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 当前机器所有镜像导入成功,共消耗:$EXECUTING_TIME 秒\n"
fi
composePath="/opt/google/chrome/docker-compose.yml"
if [ ! -f "${composePath}" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] ${composePath}文件不存在,请联系管理员,应用安装失败"
exit 1
fi
if ! docker compose --file $composePath create chrome-interface; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 创建容器失败,请联系管理员"
exit 1
else
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 创建容器成功"
fi
if ! docker compose --file $composePath up -d; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 容器启动失败,请联系管理员"
exit 1
else
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 容器启动成功"
fi
if ! (systemctl daemon-reload && systemctl enable chrome); then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 设置应用开机启动失败,请联系管理员"
exit 1
else
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 设置应用开机启动成功"
fi
systemctl status chrome | cat
rm -rf "/opt/google/docker"
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 安装完成,请用浏览器访问 http://<IP>:8000 进行激活"
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 执行Chrome_On-Premise_VAPPVERSION.deb的postinst脚本结束\n"
build/deb/DEBIAN/prerm
#!/bin/bash
action=$(echo $* | awk '{print $1}')
if [ "${action}" == "abort-upgrade" ] || [ "${action}" == "abort-install" ]; then
exit 0
fi
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 开始执行Chrome_On-Premise_VAPPVERSION.deb的prerm脚本,dpkg传入参数为:[$*]\n"
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 移除应用相关的所有镜像"
composePath="/opt/google/chrome/docker-compose.yml"
if [ ! -f "${composePath}" ]; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] ${composePath}文件不存在,无法删除应用使用的容器\n"
fi
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 停止运行应用所有组件\n"
if ! docker compose --file $composePath down; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 停止应用容器异常\n"
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 停止应用所有容器成功!!!\n"
fi
declare -A images
images["chrome-interface"]="image/chrome-interface:latest"
for name in ${!images[*]}; do
{
image=${images[$name]}
if docker image inspect "${image}" >/dev/null 2>&1; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 移除当前机器导入的${image}镜像..."
if ! docker rmi -f $image; then
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 执行: docker rmi -f ${image}移除当前镜像失败\n"
else
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 镜像${image}移除成功\n"
fi
fi
}&
done
wait
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 删除/opt/google/chrome\n"
rm -rf /opt/google/chrome
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 应用成功卸载!\n"
echo -e "[$(date "+%Y-%m-%d %H:%M:%S")] 执行Chrome_On-Premise_VAPPVERSION.deb的prerm脚本结束\n"
build/deb/opt/google/chrome/docker-compose.yml
/var/google/chrome
docker.sock
和os-release
是业务需要version: "3.3"
x-appVersion: "1.0.0"
services:
chrome-interface:
image: 1.1.1.1/chrome-group/chrome-interface:latest
container_name: chrome-interface
restart: always
volumes:
- "/var/google/chrome/chrome-interface/data:/app/data"
- "/var/google/chrome/chrome-interface/logs:/app/logs"
- "./chrome-interface/configs:/app/configs"
- "/var/run/docker.sock:/var/run/docker.sock"
- "/etc/os-release:/etc/os-release"
ports:
- "8000:8000"
command:
- "./server"
- "-conf"
- "./configs/config.yaml"
- "-logLevel"
- "info"
build/deb/usr/lib/systemd/system/chrome.service
[Unit]
Description=chrome
After=network-online.target docker.socket firewalld.service containerd.service time-set.target
Wants=network-online.target containerd.service
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/chrome-start.sh
[Install]
WantedBy=multi-user.target
build/deb/usr/local/bin/chrome-start.sh
#!/bin/bash
/usr/bin/docker compose --file /opt/google/chrome/docker-compose.yml create chrome-interface && /usr/bin/docker compose --file /opt/google/chrome/docker-compose.yml up -d
chrome/build
目录
cp -r ~/data/* deb/opt/google/chrome/chrome-interface/data
cp ../deploy/interface/test/configs/* deb/opt/google/chrome/chrome-interface/configs
vim package-deb.sh
./package-deb.sh
dpkg -i Chrome_On-Premise_V1.0.0.deb
dpkg -l|grep chrome
dpkg -P chrome
rm -rf /var/google/chrome
Post Views: 33