搭建spug
安装docker
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install docker-ce docker-compose-plugin
systemctl enable docker
systemctl start docker
创建docker-compose.yml
要让spug容器内能执行docker命令,要设置让容器包含宿主机docker命令
vi docker-compose.yml
version: "3.3"
services:
db:
image: mariadb:10.8.2
container_name: spug-db
restart: always
command: --port 3306 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- /data/spug/mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=spug
- MYSQL_USER=spug
- MYSQL_PASSWORD=spug.cc
- MYSQL_ROOT_PASSWORD=spug.cc
spug:
image: openspug/spug-service
container_name: spug
privileged: true # 特权模式
restart: always
volumes:
- /data/spug:/data/spug
- /data/repos:/data/repos
- /var/run/docker.sock:/var/run/docker.sock # 挂载docker套接字,容器内能运行docker命令
- /usr/bin/docker:/usr/bin/docker # 挂载docker客户端工具,容器内能运行docker命令
- /data/scripts:/scripts
ports:
# 如果80端口被占用可替换为其他端口,例如: - "8000:80"
- "8080:80"
environment:
- DOCKER_HOST=unix:///var/run/docker.sock # 设置环境变量,确保Spug容器知道如何连接到 Docker守护进程
- MYSQL_DATABASE=spug
- MYSQL_USER=spug
- MYSQL_PASSWORD=spug.cc
- MYSQL_HOST=spug-db
- MYSQL_PORT=3306
depends_on:
- db
以上配置会启动两个容器,分别是spug和mysql容器。
如果自己有其他数据库,可在docker-compose.yml里指定已有的数据库,不需要再启动数据库容器了
version: "3.3"
services:
spug:
image: openspug/spug-service
container_name: ops
privileged: true
restart: always
volumes:
- /data/spug/service:/data/spug
- /data/spug/repos:/data/repos
- /var/run/docker.sock:/var/run/docker.sock # 挂载docker套接字,容器内能运行docker命令
- /usr/bin/docker:/usr/bin/docker # 挂载docker客户端工具,容器内能运行docker命令
- /data/scripts:/scripts
ports:
- "8080:80" # 映射端口
environment:
- MYSQL_DATABASE=ops # 数据库名
- MYSQL_USER=yunwei # 用户名
- MYSQL_PASSWORD=DrIVy9O7kzuS46 # 密码
- MYSQL_HOST=1.1.1.1 # MySQL 数据库地址(IP 或域名)
- MYSQL_PORT=3306 # MySQL 默认端口
启动容器
docker compose up -d
初始化
以下操作会创建一个用户名为ops密码为123456的管理员账户,可自行替换管理员账户/密码。
docker exec ops init_spug ops 123456
访问测试
在浏览器中输入 http://localhost:8080 访问
版本升级
你可以在 系统管理/系统设置/关于 中查看当前运行的 Spug 版本,可以在更新日志查看当前最新版本,如果需要升级 Spug 请参考 版本升级文档。
# 默认更新到最新版本
# spug 是容器名称,也可以替换为自己的容器ID
docker exec -i spug python3 /data/spug/spug_api/manage.py update
# 更新完成后重启容器
docker restart spug
配置主机
在主机管理里可以添加要管理的主机,且spug支持web端远程管理主机

下图为添加主机界面:

添加环境
添加环境,之后创建发布应用的时候便于分类管理

应用管理
spug的功能是让开发通过该平台发布应用到远程主机
所以着重讲如何配置应用
在应用发布—发布配置里添加应用

再在刚新建的应用上点击新建发布—常规发布
- 并行发布:可同时发布到多台主机,发布速度快,但是如果有报错可能导致多台主机的服务都不可用
- 串行发布:一台台发布。如果有报错会停止,但是发布速度慢。
- 在配置应用的时候,填写仓库地址"http://[username]:[password]@git.xxx.com:9090/[servername].git"



发布应用
上一步已经配置了应用的相关流水
当用户要发布版本的时候,点击应用发布—发布申请

点击发布后,状态变为待审核。

等审核人员审核通过后,状态变成待发布
此时发布者可以选择发布。发布有两种方式,分别是补偿和全量

点击发布后,弹出发布日志

在目标主机查看,发现代码已拉取到目标主机指定目录

服务器监控
进程和端口监控
在spug可以监控服务器的进程、端口存活,还可以设置自定义脚本


端口和进程的监控这里就不说明了
这里只说明监控服务器性能
自定义脚本
监控服务器性能
监控服务器的CPU、内存、磁盘、IO、负载
#!/bin/bash
# 检查内存使用率
mem_used=$(free | grep Mem | awk '{print $3/$2 * 100.0}' | bc -l)
mem_threshold=80
mem_alert=$(echo "$mem_used > $mem_threshold" | bc -l)
if (( $(echo "$mem_alert" | bc -l) )); then
echo "内存使用超过 ${mem_threshold}%!!"
exit 1
fi
# 检查CPU使用率
cpu_used=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
cpu_threshold=80
cpu_alert=$(echo "$cpu_used > $cpu_threshold" | bc -l)
if (( $(echo "$cpu_alert" | bc -l) )); then
echo "CPU使用超过 ${cpu_threshold}%!!"
exit 1
fi
# 检查磁盘使用率
disk_used=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
disk_threshold=90
disk_alert=$(echo "$disk_used > $disk_threshold" | bc -l)
if (( $(echo "$disk_alert" | bc -l) )); then
echo "磁盘使用超过 ${disk_threshold}%!!"
exit 1
fi
# 检查inode使用情况
inode_used=$(df -i / | grep / | awk '{ print $3 }')
inode_total=$(df -i / | grep / | awk '{ print $2 }')
inode_percentage=$(echo "$inode_used / $inode_total * 100" | bc -l)
inode_threshold=90
inode_alert=$(echo "$inode_percentage > $inode_threshold" | bc -l)
if (( $(echo "$inode_alert" | bc -l) )); then
echo "inode使用超过 ${inode_threshold}%!!"
exit 1
fi
# 检查IO情况
io_stats=$(iostat -c | awk 'NR==4 {print $1}')
io_threshold=5
io_alert=$(echo "$io_stats > $io_threshold" | bc -l)
if (( $(echo "$io_alert" | bc -l) )); then
echo "IO使用超过 ${io_threshold}ms!!"
exit 1
fi
# 检查系统负载
load_avg=$(cat /proc/loadavg | awk '{print $1}')
load_threshold=2.0
load_alert=$(echo "$load_avg > $load_threshold" | bc -l)
if (( $(echo "$load_alert" | bc -l) )); then
echo "系统负载超过 ${load_threshold}!!"
exit 1
fi
# 输出当前使用情况,保留两位小数
printf "内存使用率: %.2f%%.\n" "$mem_used"
printf "CPU使用率: %.2f%%.\n" "$cpu_used"
printf "磁盘使用率: %.2f%%.\n" "$disk_used"
printf "inode使用率: %.2f%%.\n" "$inode_percentage"
printf "IO延迟: %.2fms.\n" "$io_stats"
printf "系统负载: %.2f.\n" "$load_avg"
在设置脚本后,可先点执行测试,若弹窗是警告图标,则表示会触发告警。

当超过脚本设置的阈值后,会触发告警,在报警历史里可以查看到告警。

如果配置了邮箱或电话钉钉等告警,也会相应触发

监控数据库值
监控数据库中某个表的 static 值,持续监测数据库中的变动。当出现指定值,则告警。已告警的不会反复告,为了防止大量扫表,默认每次探测1小时内的值
本地维护两个文件:
alert_log.txt:记录所有告警信息,包括时间、静态值和对应的 ID。
alert_track.txt:跟踪已告警的时间戳,以避免重复告警。
#!/bin/bash
# 数据库连接配置
REMOTE_DB_HOST="165.██.███.244"
REMOTE_DB_USER="casdoor"
REMOTE_DB_PASSWORD="█████"
REMOTE_DB_NAME="█████"
REMOTE_TABLE_NAME="testtable"
# 本地告警记录文件
ALERT_LOG_FILE="alert_log.txt"
ALERT_TRACK_FILE="alert_track.txt"
# 设定要监控的 static 值和时间窗口(单位:分钟)
TARGET_STATIC_VALUE=0 # 可以设置为 0 或 1
TIME_WINDOW_MINUTES=60 # 监控过去60分钟的数据
# 创建告警跟踪文件(如果不存在)
touch "$ALERT_TRACK_FILE"
while true; do
# 获取当前时间和过去指定分钟的时间
CURRENT_TIME=$(date '+%Y-%m-%d %H:%M:%S')
PAST_TIME=$(date -d "-$TIME_WINDOW_MINUTES minutes" '+%Y-%m-%d %H:%M:%S')
# 查询过去指定分钟内的 static 值、created_at 和 id
RESULT=$(mysql -h "$REMOTE_DB_HOST" -u "$REMOTE_DB_USER" -p"$REMOTE_DB_PASSWORD" -D "$REMOTE_DB_NAME" -se "SELECT id, static, created_at FROM $REMOTE_TABLE_NAME WHERE created_at >= '$PAST_TIME' ORDER BY created_at DESC;")
# 检查是否找到任何记录
if [[ -z "$RESULT" ]]; then
echo "$CURRENT_TIME - 未找到 static=$TARGET_STATIC_VALUE,扫描行数: 0"
sleep 10 # 每10秒检查一次
continue
fi
# 加载已告警的时间戳
declare -A ALERTED_TIMESTAMPS
while IFS= read -r line; do
# 确保行不为空
if [[ -n "$line" ]]; then
ALERTED_TIMESTAMPS["$line"]=1
fi
done < "$ALERT_TRACK_FILE"
# 计算扫描的行数
LINE_COUNT=$(echo "$RESULT" | wc -l)
# 标志:是否发现新的告警
NEW_ALERT_FOUND=0
# 遍历查询结果并检查告警
while read -r line; do
ID=$(echo "$line" | awk '{print $1}') # 提取 ID
STATIC_VALUE=$(echo "$line" | awk '{print $2}') # 提取 static 值
CREATED_AT=$(echo "$line" | awk '{print substr($0, index($0,$3))}') # 提取完整的时间戳
# 判断 static 值
if [[ "$STATIC_VALUE" == "$TARGET_STATIC_VALUE" ]]; then
# 检查时间戳是否已告警
if [[ -z "${ALERTED_TIMESTAMPS[$CREATED_AT]}" ]]; then
# 记录告警
echo "$CURRENT_TIME - 告警: ID=$ID, static=$STATIC_VALUE,时间: $CREATED_AT"
echo "$CURRENT_TIME - 告警: ID=$ID, static=$STATIC_VALUE,时间: $CREATED_AT" >> "$ALERT_LOG_FILE"
# 将时间戳记录到跟踪文件
echo "$CREATED_AT" >> "$ALERT_TRACK_FILE"
# 设置标志,表示发现新的告警
NEW_ALERT_FOUND=1
fi
fi
done <<< "$RESULT" # 使用 here-string 来遍历查询结果
# 输出扫描行数
# echo "$CURRENT_TIME - 扫描行数: $LINE_COUNT"
# 检查是否发现新的告警
if [[ "$NEW_ALERT_FOUND" -eq 0 ]]; then
echo "$CURRENT_TIME - 无新增 static=$TARGET_STATIC_VALUE,扫描行数: $LINE_COUNT"
fi
sleep 10 # 每10秒检查一次
done
exit 0 # 正常情况
脚本输出结果:

可将脚本写入spug的自定义脚本中触发
告警系统接入Telegram
将告警接入Telegram,让告警消息通过telegram发出。
创建机器人
打开 Telegram,搜索 @BotFather。
发送 /newbot 命令,按照提示创建一个新的 Bot。
记下 Bot 的 Token,稍后会用到。
获取 Chat ID
在 Telegram 中找到创建的 Bot,发送一条消息。
接着使用以下 URL 获取您的 Chat ID:
https://api.telegram.org/bot<YourBOTToken>/getUpdates
# 替换 <YourBOTToken> 为您在第一步中获得的 Token。找到您发送消息后的 chat 对象中的 id。
得到的返回如下:

监控邮箱内容
需求:当邮箱收到来自告警邮箱发来的消息时,则转发到telegrambot里
前提是先安装了python3
安装pip3
yum install python3-pip
邮箱开启IMAP
以gmail为例,在邮箱设置页面–启用IMAP
在谷歌账号界面点安全性–启用两步验证
在创建和管理应用专用密码界面创建一个密码,记住这个密码,一会配置邮箱的。
部署服务
安装依赖
pip3 install python-telegram-bot==13.7
pip3 install configparser
pip3 install argparse
pip3 install imaplib2
监控代码
mkdir /opt/mailToTg && cd /opt/mailToTg
# ln -sT /opt/mailToTg/mailToTg.py /usr/local/bin/mailToTg
touch mailToTg.py chmod +x mailToTg.py
vim mailToTg.py
import imaplib
import logging
import email
import re
import requests
import os
import time
import argparse
import configparser
from email.header import decode_header
from logging.handlers import RotatingFileHandler
import sqlite3
class MailToTelegramForwarder:
def __init__(self, config):
self.config = config
self.mailbox = None
self.last_uid = None
self.allowed_senders = self.config['default'].get('allowed_senders', '').split(',')
self.check_interval = int(self.config['default'].get('check_interval', 10))
self.db = self.setup_database()
def setup_database(self):
"""设置数据库以存储最后处理的邮件 UID。"""
conn = sqlite3.connect('mail_forwarder.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS last_uid (
uid TEXT PRIMARY KEY
)
''')
conn.commit()
return conn
def connect(self):
"""连接到 IMAP 邮箱。"""
try:
self.mailbox = imaplib.IMAP4_SSL(self.config['default']['imap_host'])
self.mailbox.login(self.config['default']['imap_user'], self.config['default']['imap_password'])
logging.info("Logged in successfully to IMAP server.")
except Exception as e:
logging.error(f"Failed to connect to IMAP server: {e}")
raise
def search_mails(self):
"""搜索远程 IMAP 邮箱并返回邮件数据。"""
logging.info("Searching for new mails...")
self.last_uid = self.get_last_uid()
try:
self.mailbox.select("INBOX") # 选择收件箱
except Exception as e:
logging.error(f"Failed to select mailbox: {e}")
return []
search_string = self.config['default'].get('imap_search', "(UNSEEN)")
search_string = re.sub(r'\${lastUID}', str(self.last_uid), search_string, flags=re.IGNORECASE)
logging.info(f"Using search string: {search_string}")
try:
rv, data = self.mailbox.uid('search', None, search_string)
if rv != 'OK':
logging.info("No messages found!")
return []
email_uids = data[0].split()
if not email_uids:
logging.info("No new emails found.")
return []
mails = []
for email_uid in email_uids:
rv, email_data = self.mailbox.uid('fetch', email_uid, '(RFC822)')
if rv != 'OK':
logging.warning(f"Failed to fetch email with UID {email_uid}")
continue
raw_email = email_data[0][1]
msg = email.message_from_bytes(raw_email)
if not self.is_allowed_sender(msg["From"]):
logging.info(f"Ignoring email from: {msg['From']}")
continue
mail_data = self.parse_email(msg)
mails.append((email_uid.decode(), mail_data))
return mails
except Exception as e:
logging.error(f"Error during IMAP search: {e}")
self.disconnect()
return []
def is_allowed_sender(self, sender):
"""检查发件人是否在允许的发件人列表中。"""
return any(allowed_sender.strip() in sender for allowed_sender in self.allowed_senders)
def parse_email(self, msg):
"""解析邮件内容并返回所需的信息。"""
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
subject = subject.decode(encoding if encoding else 'utf-8')
from_email = self.decode_header_field(msg["From"])
date = msg["Date"]
body = self.extract_body(msg)
logging.debug("Extracted body: %s", body) # 输出提取的正文
body = self.filter_body(body) # 在此处过滤邮件正文
mail_data = {
"body": body # 仅保留过滤后的邮件正文
}
return mail_data
def decode_header_field(self, header_value):
"""解码邮件头字段。"""
decoded_fragments = decode_header(header_value)
decoded_string = ''
for fragment, encoding in decoded_fragments:
if isinstance(fragment, bytes):
decoded_string += fragment.decode(encoding if encoding else 'utf-8')
else:
decoded_string += fragment
return decoded_string
def extract_body(self, msg):
"""提取邮件正文,支持多种邮件格式。"""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
if content_type == "text/plain" and "attachment" not in content_disposition:
body = part.get_payload(decode=True).decode(part.get_content_charset() or 'utf-8', errors='ignore')
return body
elif content_type == "text/html":
body = part.get_payload(decode=True).decode(part.get_content_charset() or 'utf-8', errors='ignore')
return body
else:
body = msg.get_payload(decode=True).decode(msg.get_content_charset() or 'utf-8', errors='ignore')
return body
return ""
def filter_body(self, body):
"""根据配置文件中的规则过滤邮件正文。"""
# 从 REPLACEMENTS 节中读取配置
remove_lines = self.config['replacements'].get('remove_lines', '').strip().split(',')
replace_lines = self.config['replacements'].get('replace_lines', '').strip().split(',')
logging.debug("Loaded remove_lines: %s", remove_lines)
logging.debug("Loaded replace_lines: %s", replace_lines)
for line in remove_lines:
line = line.strip()
if line: # 确保行不为空
body = body.replace(line, '')
for replacement in replace_lines:
if ':' in replacement:
old, new = map(str.strip, replacement.split(':', 1))
body = body.replace(old, new)
logging.debug("Filtered body: %s", body) # 输出过滤后的结果
return body.strip()
def disconnect(self):
"""断开与 IMAP 邮箱的连接。"""
if self.mailbox:
self.mailbox.close()
self.mailbox.logout()
logging.info("Disconnected from IMAP server.")
def get_last_uid(self):
"""获取最后处理过的邮件 UID。"""
cursor = self.db.cursor()
cursor.execute("SELECT uid FROM last_uid")
row = cursor.fetchone()
return row[0] if row else ""
def save_last_uid(self, uid):
"""保存最后处理过的邮件 UID。"""
cursor = self.db.cursor()
cursor.execute("REPLACE INTO last_uid (uid) VALUES (?)", (uid,))
self.db.commit()
def forward_to_telegram(self, mail_data):
"""将邮件内容转发到 Telegram bot。"""
telegram_url = f"https://api.telegram.org/bot{self.config['default']['telegram_token']}/sendMessage"
body = mail_data['body'][:4000] # 只使用邮件正文
message = body # 直接使用正文内容
payload = {
"chat_id": self.config['default']['telegram_chat_id'],
"text": message,
"parse_mode": "Markdown"
}
for attempt in range(3): # Retry mechanism
try:
response = requests.post(telegram_url, data=payload)
response.raise_for_status()
logging.info(f"Message sent to Telegram: {response.text}")
return True
except requests.exceptions.HTTPError as http_err:
logging.error(f"HTTP error occurred: {http_err} - Response: {response.text}")
except requests.exceptions.RequestException as e:
logging.error(f"Error sending message to Telegram: {e}")
time.sleep(2) # Wait before retrying
return False
def run(self):
"""启动邮件转发任务,连接邮箱并定期检查未读邮件,转发到 Telegram。"""
self.connect()
while True:
try:
mails = self.search_mails()
if mails:
for email_uid, mail in mails:
success = self.forward_to_telegram(mail)
if success:
self.save_last_uid(email_uid)
else:
logging.info("No new mails to forward.")
except Exception as e:
logging.error(f"An error occurred in the run loop: {e}")
self.disconnect()
time.sleep(5) # 等待片刻再重试
time.sleep(self.check_interval)
def load_config(config_path):
"""从指定的文件路径加载配置。"""
parser = configparser.ConfigParser()
parser.read(config_path, encoding='utf-8')
if "DEFAULT" not in parser:
raise ValueError("Configuration file is missing the DEFAULT section.")
if "REPLACEMENTS" not in parser:
raise ValueError("Configuration file is missing the REPLACEMENTS section.")
return {
"default": parser["DEFAULT"], # 返回 DEFAULT 节的内容
"replacements": parser["REPLACEMENTS"] # 返回 REPLACEMENTS 节的内容
}
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Mail to Telegram Forwarder")
parser.add_argument("--config", required=True, help="Path to the configuration file")
args = parser.parse_args()
# 设置日志
handler = RotatingFileHandler('mail_to_telegram.log', maxBytes=5*1024*1024, backupCount=5)
logging.basicConfig(handlers=[handler], level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')
# 加载配置
config_file = args.config
config = load_config(config_file)
# 创建并运行邮件转发器
forwarder = MailToTelegramForwarder(config)
forwarder.run()
编辑配置文件
[DEFAULT]
# imap服务器、账号、密码
imap_host = imap.gmail.com
imap_user = ████@gmail.com
imap_password = v████whkcwyqkm
# imap搜索字符串,默认为未读邮件
imap_search = (UNSEEN)
# telegram机器人令牌
telegram_token = 8049819████████████A0imimPripiy8YMh4
# telegram聊天ID
telegram_chat_id = 11████6500
# 邮件检查间隔(秒)
check_interval = 10
# 转发的收件人列表
allowed_senders = 20████36@qq.com, ████44@qq.com, jus████@foxmail.com
[REPLACEMENTS]
# 要从转发的邮件正文中移除的文本。格式为(文本,文本2)
remove_lines = ,自动发送,请勿回复。
# 要替换的文本。格式为(旧文本:新文本)
replace_lines = 故障持续时间:故障持续
启动服务
# 启动服务
cd /opt/mailToTg && nohup python3 mailToTg.py --config conf.ini &
# 日志文件
在mail_to_telegram.log
用户与权限管理
待补充
实例
需求
为了满足使用 Spug 发布在 Docker 中启动的 Java 应用这一需求,需要完成一系列操作。具体为:利用 Spug 从 Git 仓库下载代码,接着对代码进行编译和打包,然后将打包好的内容传输到后端服务器,之后在后端服务器编译 Docker 镜像,最后基于该镜像启动 Docker 容器。
分析
为了实现对 Java 项目的自动化编译、测试以及打包,安装 “maven” 是必要的。具体的操作流程如下:当 spug 成功拉取 git 代码后,需借助宿主机创建一个 maven 容器来执行编译任务。然而,要在容器内使宿主机运行相关命令,关键在于挂载 “Docker socket”,通过这种方式,容器才能获得对宿主机 Docker 的控制权。但在此之前,容器中必须预先安装 docker 客户端,因为只有这样,容器才能够执行 docker 命令。所以,我们首先要基于 spug 镜像,提前安装好 docker 客户端,并生成新的镜像文件。完成这一步骤后,后续启动容器时,就可以直接使用相关功能,顺利推进 Java 项目的自动化流程了。
重新编译spug镜像
Dockerfile内容如下
FROM openspug/spug-service
# 安装必要的工具
RUN apt-get update && \
apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common
# 添加 Docker 的官方 GPG 密钥
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
# 添加 Docker 的稳定版本库
RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# 安装 Docker 客户端
RUN apt-get update && \
apt-get install -y docker-ce-cli
# 设置工作目录(可选)
WORKDIR /scripts
# 挂载点(可选)
VOLUME ["/scripts"]
编译镜像
docker build -t my-openspug-docker .
修改docker-compose.yml
version: "3.3"
services:
db:
image: mariadb:10.8.2
container_name: spug-db
restart: always
command: --port 3306 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- /data/spug/mysql:/var/lib/mysql
environment:
- MYSQL_DATABASE=spug
- MYSQL_USER=spug
- MYSQL_PASSWORD=spug.cc
- MYSQL_ROOT_PASSWORD=spug.cc
spug:
image: openspug/spug-service
container_name: spug
privileged: true # 特权模式
restart: always
volumes:
- /data/spug:/data/spug
- /data/repos:/data/repos
- /var/run/docker.sock:/var/run/docker.sock # 挂载docker套接字,容器内能运行docker命令
- /usr/bin/docker:/usr/bin/docker # 挂载docker客户端工具,容器内能运行docker命令
- /data/scripts:/scripts
ports:
# 如果80端口被占用可替换为其他端口,例如: - "8000:80"
- "8080:80"
environment:
- DOCKER_HOST=unix:///var/run/docker.sock # 设置环境变量,确保Spug容器知道如何连接到 Docker守护进程
- MYSQL_DATABASE=spug
- MYSQL_USER=spug
- MYSQL_PASSWORD=spug.cc
- MYSQL_HOST=spug-db
- MYSQL_PORT=3306
depends_on:
- db
运行容器
docker-compose up -d
现在就可以实现在spug容器中,让宿主机创建其他容器了
配置方法一:常规发布
(注:以下流程发布的有问题,会让并非的代码并非最新版。这可能和spug的bug有关,导致编译后的代码并未传到目标机器。以下流程仅供参考,生效流程见后续自定义发布脚本)
在配置发布时有以下几个阶段:
代码检出前执行:是在 Spug 服务器上,在拉取代码前,进行环境配置等工作;代码检出后执行,是在拉取代码后,进行依赖安装、编译和构建等工作;应用发布前执行,是在目标主机上,进行旧应用的停止、运行环境配置等工作;应用发布后执行,是在把项目发布到目标主机后,进行应用的启动等。
接下来是配置:
代码检出前执行:echo "【代码检出前】开始" echo "当前在$(pwd)" echo "当前公网IP: $(curl -s ifconfig.me)" echo "本次发布应用的源码目录(容器内目录)$SPUG_REPOS_DIR/$SPUG_DEPLOY_ID" ls -tl $SPUG_REPOS_DIR/$SPUG_DEPLOY_ID if ! docker run --rm --name ${SPUG_APP_NAME}_mvn_$(date +%Y%m%d%H%M) -v "$SPUG_REPOS_DIR/$SPUG_DEPLOY_ID:/app" -w /app maven:3.9.8-amazoncorretto-17 mvn clean package -Dmaven.test.skip=true -U; then echo "Maven 打包失败" exit 1 fi echo "当前在$(pwd)" echo "【代码检出前】结束"代码检出后执行:echo "【代码检出后】开始" echo "当前在$(pwd)" echo "查看$SPUG_REPOS_DIR/$SPUG_DEPLOY_ID/target/下jar文件" ls -lht $SPUG_REPOS_DIR/$SPUG_DEPLOY_ID/target/*.jar echo "查看jar文件md5sum(发布后进容器查看是否和以下一致)" md5sum $SPUG_REPOS_DIR/$SPUG_DEPLOY_ID/target/*.jar if ls $SPUG_REPOS_DIR/$SPUG_DEPLOY_ID/target/*.jar 1> /dev/null 2>&1; then cp $SPUG_REPOS_DIR/$SPUG_DEPLOY_ID/target/*.jar ./ else echo "没有找到 jar 文件" exit 1 fi echo "当前公网IP: $(curl -s ifconfig.me)" echo "查看本次编译目录$SPUG_REPOS_DIR/$SPUG_DEPLOY_ID" ls -lt $SPUG_REPOS_DIR/$SPUG_DEPLOY_ID # echo "删除编译目录,防止下次发布代码非最新" # rm -rf $SPUG_REPOS_DIR/$SPUG_DEPLOY_ID echo "【代码检出后】结束"应用发布前执行:echo "【应用发布前】开始" echo "当前公网IP: $(curl -s ifconfig.me)" echo "目标主机部署路径是:$SPUG_DST_DIR" echo "代码检出路径:$SPUG_REPOS_DIR/$SPUG_DEPLOY_ID" echo "当前显示的项目的真实地址是$(readlink -f /data/aladdin/$SPUG_APP_NAME)" echo "显示当前项目下的jar包的md5" md5sum $(readlink -f /data/aladdin/$SPUG_APP_NAME/aladdin*.jar)
echo "【应用发布前】结束"
应用发布后执行:
```bash
echo "【应用发布后】开始"
echo "当前显示的项目的真实地址是$(readlink -f /data/aladdin/$SPUG_APP_NAME)"
echo "显示当前项目下的jar包的md5"
md5sum $(readlink -f /data/aladdin/$SPUG_APP_NAME/aladdin*.jar)
echo "正在准备Dockerfile"
cat << EOF >Dockerfile
# VERSION 1.0.0
# Author: robin
# 2025
# 打包jar采用maven镜像
# 运行jar采用jdk基础镜像
FROM amazoncorretto:17.0.12
# 设置工作目录在镜像的 /app 目录下
WORKDIR /app
# 将jar包添加到容器中
# COPY *.jar /app/
COPY aladdin-be-*.jar /app/aladdin-be-1.0-SNAPSHOT.jar
# 运行jar包
ENTRYPOINT ["java","-jar","aladdin-be-1.0-SNAPSHOT.jar"]
EOF
echo "当前在$(pwd)"
echo "构建镜像"
if ! docker build -t aladdin-be:v1.0 -f Dockerfile .; then
echo "Docker 镜像构建失败"
exit 1
fi
echo "进入目标主机部署路径 $SPUG_DST_DIR"
cd $(readlink -f /data/aladdin/$SPUG_APP_NAME)
echo "当前在$(pwd)"
# 设置当前发布环境
sed -i.bak 's/-local}/-test}/' tools/start.sh
sed -i.bak 's/600m/1g/' tools/start.sh
chmod +x tools/*.sh
echo "正在重启服务"
sh tools/stop.sh
sh tools/start.sh
# 打印启动日志到文件
docker logs $SPUG_APP_NAME > $SPUG_APP_NAME.log 2>&1 &
echo "日志已输出到 $SPUG_APP_NAME.log"
# 检查服务是否启动成功
if docker ps -a | grep -q $SPUG_APP_NAME; then
echo "服务已成功启动"
else
echo "服务启动失败"
exit 1
fi
echo "【应用发布后】结束"
配置方法二:自定义发布
以下流程生效
本地动作1:拉取代码
echo "当前在$(pwd)"
rm -rf /data/repos/$SPUG_APP_NAME
cd /data/repos/
echo "当前在$(pwd)"
# 下载代码 SPUG_RELEASE为发布的分支,在发布时手动定义。分支可访问git仓库,在tree后查看
git clone http://liuhong:UA3PSokE@git.pgypay.com:9090/wallet/aladdin-be.git \
&& cd aladdin-be && git checkout "$SPUG_RELEASE"
echo "当前在$(pwd)"
echo "查看拉取的代码"
ls -lhta
本地动作2:编译代码
if ! docker run --rm --name ${SPUG_APP_NAME}_mvn_$(date +%Y%m%d%H%M) -v "/data/repos/$SPUG_APP_NAME:/app" -w /app maven:3.9.8-amazoncorretto-17 mvn clean package -Dmaven.test.skip=true -U; then
echo "Maven 打包失败"
exit 1
fi
echo "当前在$(pwd)"
echo "查看 JAR 文件信息:"
JAR_PATH=$(find "/data/repos/$SPUG_APP_NAME/target/" -name "*.jar" -print -quit)
if [ -z "$JAR_PATH" ]; then
echo "未找到 JAR 文件。"
else
echo "JAR 路径: $JAR_PATH"
JAR_MD5=$(md5sum "$JAR_PATH" | awk '{print $1}')
echo "JAR MD5: $JAR_MD5"
fi
# 将编译后的文件拷贝到根目录
cp /data/repos/$SPUG_APP_NAME/target/*.jar /data/repos/$SPUG_APP_NAME/
ls -lhta /data/repos/$SPUG_APP_NAME/*.jar
目标主机动作1:检查环境
echo "当前在$(pwd)"
mkdir -p /data/project/$SPUG_APP_NAME
cd /data/project/$SPUG_APP_NAME
echo "当前在$(pwd)"
echo "删除jar包"
rm -rf /data/project/$SPUG_APP_NAME/aladdin*.jar
目标主机动作2:数据传输
本地路径:/data/repos/$SPUG_APP_NAME
过滤规则:target
目标路径:/data/project/$SPUG_APP_NAME
目标主机动作3:数据传输后检查文件
echo "当前在$(pwd)"
cd /data/project/$SPUG_APP_NAME
if find . -maxdepth 1 -name 'aladdin*.jar' -print -quit | grep -q .; then
md5sum aladdin*.jar
else
echo "文件不存在"
exit 1
fi
目标主机动作4:编译启动镜像
echo "当前在$(pwd)"
cd /data/project/$SPUG_APP_NAME
echo "当前在$(pwd)"
echo "正在准备 Dockerfile"
cat << EOF > Dockerfile
FROM amazoncorretto:17.0.12
WORKDIR /app
COPY *.jar /app/aladdin-be-1.0-SNAPSHOT.jar
ENTRYPOINT ["java", "-jar", "aladdin-be-1.0-SNAPSHOT.jar"]
EOF
echo "构建 Docker 镜像"
if ! docker build -t aladdin-be:v1.0 -f Dockerfile .; then
echo "Docker 镜像构建失败"
exit 1
fi
# 设置当前发布环境
sed -i.bak 's/-local}/-test}/' tools/start.sh
sed -i.bak 's/600m/1g/' tools/start.sh
chmod +x tools/*.sh
echo "正在重启服务"
sh tools/stop.sh
sh tools/start.sh
# 打印启动日志到文件
docker logs $SPUG_APP_NAME > $SPUG_APP_NAME.log 2>&1 &
echo "日志已输出到 $SPUG_APP_NAME.log"
# 检查服务是否启动成功
if docker ps -a | grep -q $SPUG_APP_NAME; then
echo "服务已成功启动"
else
echo "服务启动失败"
exit 1
fi
echo "【应用发布后】结束"

评论(0)